diff --git a/.gitignore b/.gitignore index ec55cbe2..bcd89d60 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ npm-debug.log shinobi.sqlite dist ._* +generatedLanguageFiles diff --git a/COPYING.md b/COPYING.md index 956571a3..0cf4376c 100644 --- a/COPYING.md +++ b/COPYING.md @@ -3,95 +3,119 @@ 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. + +Definitions. ----------- -In this license, which also serves as a general End User License Agreement [EULA], the following +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 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 + 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 + 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 +**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 +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 +- Selling hardware with the software on it - Using the software in locations that engage in the buying and selling of goods and services +As of 2022-07-12 the noted situations below are seen the same as "Commercial Use". + +- 25 Active Monitor Rule : Having at least 25 Active Monitors and not a Primary School or Secondary School. Does not apply to Personal Use. +- 150 Active Monitor Rule : If you have more than 150 Active Monitors please contact support for an Enterprise License. The retail Shinobi Pro license will not be applicable for these installations. +- Used on a Device that was part of a commercial transaction. This can be, but not limited to, being sold or provided additionally to a sale. + #### Conditions for Free (Unpaid) use. +If you fall under any conditions for "Commercial Use" none of the following can apply unless otherwise noted. + +- Personal 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 +- Usage by Primary Education or Secondary Education Schools +- Use for Emergency Services and facilities associated like Search and Rescue Services or +Ambulance Services with less than 50 cameras +- Use in Health Care facility like a hospital or walk-in clinic with less than 100 cameras +- Schools and Organizations that have been given exemption + +As of 2022-07-12 If you are an organization that falls under one or more of these conditions for free use then you must display that you are using Shinobi Systems software as "shinobi.video". This can be physically on premise or on your organization's public web page. + +Falling under the conditions for Free use does not guarantee you a License Key. It simply means you are allowed to use it without paying or trading for that use. + +#### Unsure if your use is commercial or non-commercial + +Please contact us through the Live Chat of our website and we can negotiate or make exceptions based on the circumstances of your use case. + +#### Registration + +Schools and Resellers must register with Shinobi Systems at https://licenses.shinobi.video. +In your account please fill out the "Apply for a School License" form or "Apply to be a Reseller" form. #### Support Services. -The Maintenance and Support Service shall be contracted and provided as per selected plan +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 +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 +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 +LICENSEE may modify code for 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 +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 +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, +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 +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. @@ -100,31 +124,18 @@ 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 +#### List of Included Software - This list is completed to best of our knowledge. + This list is completed to the 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 + pam-diff (Motion Detector) - https://github.com/kevinGodell/pam-diff + pipe2pam (for pam-diff) - https://github.com/kevinGodell/pipe2pam + mp4frag (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 + pipe2jpeg - https://github.com/kevinGodell/pipe2jpeg hls.js - https://github.com/video-dev/hls.js/ flv.js - https://github.com/Bilibili/flv.js/ Material Design Lite - https://getmdl.io/ @@ -142,3 +153,50 @@ Courthouse Vancouver Robson Square Moment.js - https://momentjs.com/ Livestamp.js - https://mattbradley.github.io/livestampjs/ Lodash - https://lodash.com/ + + **npmjs.com packages** + + async - https://www.npmjs.com/package/async + aws-sdk - https://www.npmjs.com/package/aws-sdk + backblaze-b2 - https://www.npmjs.com/package/backblaze-b2 + body-parser - https://www.npmjs.com/package/body-parser + bson - https://www.npmjs.com/package/bson + connection-tester - https://www.npmjs.com/package/connection-tester + cws - https://www.npmjs.com/package/cws + digest-fetch - https://www.npmjs.com/package/digest-fetch + discord.js - https://www.npmjs.com/package/discord.js + ejs - https://www.npmjs.com/package/ejs + express - https://www.npmjs.com/package/express + express-fileupload - https://www.npmjs.com/package/express-fileupload + fs-extra - https://www.npmjs.com/package/fs-extra + ftp-srv - https://www.npmjs.com/package/ftp-srv + googleapis - https://www.npmjs.com/package/googleapis + http-proxy - https://www.npmjs.com/package/http-proxy + jsonfile - https://www.npmjs.com/package/jsonfile + knex - https://www.npmjs.com/package/knex + ldapauth-fork - https://www.npmjs.com/package/ldapauth-fork + moment - https://www.npmjs.com/package/moment + mp4frag - https://www.npmjs.com/package/mp4frag + mqtt - https://www.npmjs.com/package/mqtt + mysql - https://www.npmjs.com/package/mysql + node-abort-controller - https://www.npmjs.com/package/node-abort-controller + node-fetch - https://www.npmjs.com/package/node-fetch + node-onvif-events - https://www.npmjs.com/package/node-onvif-events + node-ssh - https://www.npmjs.com/package/node-ssh + node-telegram-bot-api - https://www.npmjs.com/package/node-telegram-bot-api + nodemailer - https://www.npmjs.com/package/nodemailer + pam-diff - https://www.npmjs.com/package/pam-diff + path - https://www.npmjs.com/package/path + pipe2pam - https://www.npmjs.com/package/pipe2pam + pixel-change - https://www.npmjs.com/package/pixel-change + pushover-notifications - https://www.npmjs.com/package/pushover-notifications + sat - https://www.npmjs.com/package/sat + shinobi-onvif - https://www.npmjs.com/package/shinobi-onvif + shinobi-sound-detection - https://www.npmjs.com/package/shinobi-sound-detection + shinobi-zwave - https://www.npmjs.com/package/shinobi-zwave + smtp-server - https://www.npmjs.com/package/smtp-server + socket.io - https://www.npmjs.com/package/socket.io + socket.io-client - https://www.npmjs.com/package/socket.io-client + tree-kill - https://www.npmjs.com/package/tree-kill + unzipper - https://www.npmjs.com/package/unzipper + webdav-fs - https://www.npmjs.com/package/webdav-fs diff --git a/Docker/README.md b/Docker/README.md index 519daa14..8dd44bb5 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -4,7 +4,11 @@ ## Docker Ninja Way -> This method uses `docker-compose` and has the ability to quick install the TensorFlow Object Detection plugin. +> This method uses `docker-compose` and has the ability to quick install the TensorFlow Object Detection plugin. This will build your container from the images hosted on Gitlab. + +> **We no longer use Docker Hub** and will not in the foreseeable future. + +Docker Image Used : `registry.gitlab.com/shinobi-systems/shinobi:dev` ``` bash <(curl -s https://gitlab.com/Shinobi-Systems/Shinobi-Installer/raw/master/shinobi-docker.sh) @@ -17,10 +21,11 @@ bash <(curl -s https://gitlab.com/Shinobi-Systems/Shinobi-Installer/raw/master/s > Please remember to check out the Environment Variables table further down this README. ``` -docker run -d --name='Shinobi' -p '8080:8080/tcp' -v "/dev/shm/Shinobi/streams":'/dev/shm/streams':'rw' -v "$HOME/Shinobi/config":'/config':'rw' -v "$HOME/Shinobi/customAutoLoad":'/home/Shinobi/libs/customAutoLoad':'rw' -v "$HOME/Shinobi/database":'/var/lib/mysql':'rw' -v "$HOME/Shinobi/videos":'/home/Shinobi/videos':'rw' -v "$HOME/Shinobi/plugins":'/home/Shinobi/plugins':'rw' -v '/etc/localtime':'/etc/localtime':'ro' shinobisystems/shinobi:dev +docker run -d --name='Shinobi' -p '8080:8080/tcp' -v "/dev/shm/Shinobi/streams":'/dev/shm/streams':'rw' -v "$HOME/Shinobi/config":'/config':'rw' -v "$HOME/Shinobi/customAutoLoad":'/home/Shinobi/libs/customAutoLoad':'rw' -v "$HOME/Shinobi/database":'/var/lib/mysql':'rw' -v "$HOME/Shinobi/videos":'/home/Shinobi/videos':'rw' -v "$HOME/Shinobi/plugins":'/home/Shinobi/plugins':'rw' -v '/etc/localtime':'/etc/localtime':'ro' registry.gitlab.com/shinobi-systems/shinobi:dev ``` #### Installing Object Detection (TensorFlow.js) +**DEPRECATED, UPDATED IMAGE COMING SOON** > This requires that you add the plugin key to the Shinobi container. This key is generated and displayed in the startup logs of the Object Detection docker container. diff --git a/INSTALL/CentOS - Quick Install.sh b/INSTALL/CentOS - Quick Install.sh index 614de9b9..053aeaa6 100644 --- a/INSTALL/CentOS - Quick Install.sh +++ b/INSTALL/CentOS - Quick Install.sh @@ -85,7 +85,7 @@ echo "=========================================================" #Check if Node.js is installed if ! [ -x "$(command -v node)" ]; then echo "Node.js not found, installing..." - sudo curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash - + sudo curl --silent --location https://rpm.nodesource.com/setup_16.x | sudo bash - sudo "$pkgmgr" install nodejs -y -q -e 0 else echo "Node.js is already installed..." diff --git a/INSTALL/cuda-10.sh b/INSTALL/cuda-10.sh index ac17cc22..192265fb 100644 --- a/INSTALL/cuda-10.sh +++ b/INSTALL/cuda-10.sh @@ -4,10 +4,11 @@ echo "-- Installing CUDA Toolkit and CUDA DNN --" echo "------------------------------------------" # Install CUDA Drivers and Toolkit if [ -x "$(command -v apt)" ]; then - wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.0.130-1_amd64.deb - sudo dpkg -i --force-overwrite cuda-repo-ubuntu1804_10.0.130-1_amd64.deb - sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub - + wget https://cdn.shinobi.video/installers/cuda-repo-ubuntu1804-10-0-local-10.0.130-410.48_1.0-1_amd64.deb -O cuda.deb + dpkg -i cuda.deb + sudo apt-key add /var/cuda-repo-10-0-local-10.0.130-410.48/7fa2af80.pub + sudo apt-get update + sudo apt install cuda-toolkit-10-0 sudo apt-get update -y sudo apt-get -o Dpkg::Options::="--force-overwrite" install cuda-toolkit-10-0 -y --no-install-recommends diff --git a/INSTALL/cuda-11.sh b/INSTALL/cuda-11.sh new file mode 100644 index 00000000..7abf11de --- /dev/null +++ b/INSTALL/cuda-11.sh @@ -0,0 +1,36 @@ +#!/bin/sh +echo "------------------------------------------" +echo "-- Installing CUDA Toolkit and CUDA DNN --" +echo "------------------------------------------" +# Install CUDA Drivers and Toolkit +if [ -x "$(command -v apt)" ]; then + wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin + sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600 + wget http://developer.download.nvidia.com/compute/cuda/11.0.2/local_installers/cuda-repo-ubuntu2004-11-0-local_11.0.2-450.51.05-1_amd64.deb + sudo dpkg -i cuda-repo-ubuntu2004-11-0-local_11.0.2-450.51.05-1_amd64.deb + sudo apt-key add /var/cuda-repo-ubuntu2004-11-0-local/7fa2af80.pub + + sudo apt-get update -y + + sudo apt-get -o Dpkg::Options::="--force-overwrite" install cuda-toolkit-11-0 -y --no-install-recommends + sudo apt-get -o Dpkg::Options::="--force-overwrite" install --fix-broken -y + sudo apt install nvidia-utils-495 nvidia-headless-495 -y + + # Install CUDA DNN + wget https://cdn.shinobi.video/installers/libcudnn8_8.2.1.32-1+cuda11.3_amd64.deb -O cuda-dnn.deb + sudo dpkg -i cuda-dnn.deb + wget https://cdn.shinobi.video/installers/libcudnn8-dev_8.2.0.53-1+cuda11.3_amd64.deb -O cuda-dnn-dev.deb + sudo dpkg -i cuda-dnn-dev.deb + echo "-- Cleaning Up --" + # Cleanup + sudo rm cuda-dnn.deb + sudo rm cuda-dnn-dev.deb +fi +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 diff --git a/INSTALL/freebsd.sh b/INSTALL/freebsd.sh index 6dc30438..079ae62a 100644 --- a/INSTALL/freebsd.sh +++ b/INSTALL/freebsd.sh @@ -46,7 +46,7 @@ npm install --unsafe-perm # sudo npm audit fix --force echo "=============" echo "Shinobi - Install PM2" -npm install pm2@3.0.0 -g +npm install pm2@latest -g if (! -e "./conf.json" ) then cp conf.sample.json conf.json endif diff --git a/INSTALL/freenas.csh b/INSTALL/freenas.csh index 69b168b0..8ff343b3 100644 --- a/INSTALL/freenas.csh +++ b/INSTALL/freenas.csh @@ -18,7 +18,7 @@ npm i npm -g #There are some errors in here that I don't want you to see. Redirecting to dev null :D npm install --unsafe-perm > & /dev/null # npm audit fix --force > & /dev/null -npm install pm2@3.0.0 -g +npm install pm2@latest -g cp conf.sample.json conf.json cp super.sample.json super.json pm2 start camera.js diff --git a/INSTALL/macos-part2.sh b/INSTALL/macos-part2.sh index e7a62602..b6d81fd5 100644 --- a/INSTALL/macos-part2.sh +++ b/INSTALL/macos-part2.sh @@ -18,7 +18,7 @@ sudo npm install --unsafe-perm # sudo npm audit fix --unsafe-perm echo "=============" echo "Shinobi - Install PM2" -sudo npm install pm2@3.0.0 -g +sudo npm install pm2@latest -g if [ ! -e "./conf.json" ]; then sudo cp conf.sample.json conf.json fi diff --git a/INSTALL/macos.sh b/INSTALL/macos.sh index a6163381..c26a7a8d 100644 --- a/INSTALL/macos.sh +++ b/INSTALL/macos.sh @@ -10,7 +10,7 @@ echo "Shinobi - Do you want to Install Node.js?" echo "(y)es or (N)o" read -r nodejsinstall if [ "$nodejsinstall" = "y" ]; then - curl -o node-installer.pkg https://nodejs.org/dist/v11.9.0/node-v11.9.0.pkg + curl -o node-installer.pkg https://nodejs.org/dist/v16.15.0/node-v16.15.0.pkg sudo installer -pkg node-installer.pkg -target / rm node-installer.pkg sudo ln -s /usr/local/bin/node /usr/bin/nodejs @@ -34,18 +34,13 @@ if [ "$ffmpeginstall" = "y" ]; then sudo chmod +x /usr/local/bin/ffserver fi echo "=============" -if [ ! -e "./shinobi.sqlite" ]; then - sudo npm install jsonfile - sudo cp sql/shinobi.sample.sqlite shinobi.sqlite - sudo node tools/modifyConfiguration.js databaseType=sqlite3 -fi echo "Shinobi - Install NPM Libraries" 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@3.0.0 -g +sudo npm install pm2@latest -g if [ ! -e "./conf.json" ]; then sudo cp conf.sample.json conf.json fi diff --git a/INSTALL/opensuse.sh b/INSTALL/opensuse.sh index e39e480a..ab522824 100644 --- a/INSTALL/opensuse.sh +++ b/INSTALL/opensuse.sh @@ -34,7 +34,7 @@ echo "(y)es or (N)o" NODEJSINSTALL=0 read -r nodejsinstall if [ "$nodejsinstall" = "y" ] || [ "$nodejsinstall" = "Y" ]; then - sudo zypper install -y nodejs11 + sudo zypper install -y nodejs16 NODEJSINSTALL=1 fi echo "=============" @@ -89,7 +89,7 @@ npm install --unsafe-perm # sudo npm audit fix --force echo "=============" echo "Shinobi - Install PM2" -sudo npm install pm2@3.0.0 -g +sudo npm install pm2@latest -g echo "Shinobi - Finished" sudo chmod -R 755 . touch INSTALL/installed.txt diff --git a/INSTALL/ubuntu-easyinstall.sh b/INSTALL/ubuntu-easyinstall.sh index 8ac4fa44..1c679adc 100644 --- a/INSTALL/ubuntu-easyinstall.sh +++ b/INSTALL/ubuntu-easyinstall.sh @@ -14,11 +14,11 @@ fi echo "=============" echo " Detecting Ubuntu Version" echo "=============" -declare -i getubuntuversion=$(lsb_release -r | awk '{print $2}' | cut -d . -f1) +getubuntuversion=$(lsb_release -r | awk '{print $2}' | cut -d . -f1) echo "=============" echo " Ubuntu Version: $getubuntuversion" echo "=============" -if [[ "$getubuntuversion" == "16" || "$getubuntuversion" < "16" ]]; then +if [[ "$getubuntuversion" == "16" || "$getubuntuversion" -le "16" ]]; then echo "=============" echo "Shinobi - Get FFMPEG 3.x from ppa:jonathonf/ffmpeg-3" sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y @@ -154,7 +154,7 @@ echo "=============" #Install PM2 echo "Shinobi - Install PM2" -sudo npm install pm2@3.0.0 -g +sudo npm install pm2@latest -g if [ ! -e "./conf.json" ]; then cp conf.sample.json conf.json fi diff --git a/INSTALL/ubuntu-touchless-iso.sh b/INSTALL/ubuntu-touchless-iso.sh index 5309b99f..c0e6488e 100644 --- a/INSTALL/ubuntu-touchless-iso.sh +++ b/INSTALL/ubuntu-touchless-iso.sh @@ -15,7 +15,7 @@ echo " Ubuntu Version: $getubuntuversion" echo "=============" apt update -y apt update --fix-missing -y -if [ "$getubuntuversion" = "18" ] || [ "$getubuntuversion" > "18" ]; then +if [ "$getubuntuversion" = "18" ] || [ "$getubuntuversion" -le "18" ]; then apt install sudo wget -y sudo apt install -y software-properties-common sudo add-apt-repository universe -y @@ -66,7 +66,7 @@ if ! [ -x "$(command -v npm)" ]; then fi sudo apt install make zip -y if ! [ -x "$(command -v ffmpeg)" ]; then - if [ "$getubuntuversion" = "16" ] || [ "$getubuntuversion" < "16" ]; then + if [ "$getubuntuversion" = "16" ] || [ "$getubuntuversion" -le "16" ]; then echo "=============" echo "Shinobi - Get FFMPEG 3.x from ppa:jonathonf/ffmpeg-3" sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y @@ -100,7 +100,7 @@ sudo npm install --unsafe-perm # sudo npm audit fix --force echo "=============" echo "Shinobi - Install PM2" -sudo npm install pm2@3.0.0 -g +sudo npm install pm2@latest -g echo "Shinobi - Finished" sudo chmod -R 755 . touch INSTALL/installed.txt diff --git a/INSTALL/ubuntu-touchless.sh b/INSTALL/ubuntu-touchless.sh index 33217272..418a58eb 100644 --- a/INSTALL/ubuntu-touchless.sh +++ b/INSTALL/ubuntu-touchless.sh @@ -22,7 +22,7 @@ if [ "$disableIpv6" = "y" ] || [ "$disableIpv6" = "Y" ]; then sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1 sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1 fi -if [ "$getubuntuversion" = "18" ] || [ "$getubuntuversion" > "18" ]; then +if [ "$getubuntuversion" = "18" ] || [ "$getubuntuversion" -gt "18" ]; then apt install sudo wget -y sudo apt install -y software-properties-common sudo add-apt-repository universe -y @@ -53,25 +53,20 @@ if ! [ -x "$(command -v ifconfig)" ]; then echo "Shinobi - Installing Net-Tools" sudo apt install net-tools -y fi -if ! [ -x "$(command -v node)" ]; then - echo "=============" - echo "Shinobi - Installing Node.js" - wget https://deb.nodesource.com/setup_12.x - chmod +x setup_12.x - ./setup_12.x - sudo apt install nodejs -y - sudo apt install node-pre-gyp -y - rm setup_12.x -else - echo "Node.js Found..." - echo "Version : $(node -v)" -fi +echo "=============" +echo "Shinobi - Installing Node.js" +wget https://deb.nodesource.com/setup_16.x +chmod +x setup_16.x +./setup_16.x +sudo apt install nodejs -y +sudo apt install node-pre-gyp -y +rm setup_16.x if ! [ -x "$(command -v npm)" ]; then sudo apt install npm -y fi sudo apt install make zip -y if ! [ -x "$(command -v ffmpeg)" ]; then - if [ "$getubuntuversion" = "16" ] || [ "$getubuntuversion" < "16" ]; then + if [ "$getubuntuversion" = "16" ] || [ "$getubuntuversion" -le "16" ]; then echo "=============" echo "Shinobi - Get FFMPEG 3.x from ppa:jonathonf/ffmpeg-3" sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y @@ -105,7 +100,7 @@ sudo npm install --unsafe-perm # sudo npm audit fix --force echo "=============" echo "Shinobi - Install PM2" -sudo npm install pm2@3.0.0 -g +sudo npm install pm2@latest -g echo "Shinobi - Finished" sudo chmod -R 755 . touch INSTALL/installed.txt diff --git a/INSTALL/ubuntu.sh b/INSTALL/ubuntu.sh index 397035f4..b7e9fa2e 100644 --- a/INSTALL/ubuntu.sh +++ b/INSTALL/ubuntu.sh @@ -13,7 +13,7 @@ getubuntuversion=$(lsb_release -r | awk '{print $2}' | cut -d . -f1) echo "=============" echo " Ubuntu Version: $getubuntuversion" echo "=============" -if [ "$getubuntuversion" = "18" ] || [ "$getubuntuversion" > "18" ]; then +if [ "$getubuntuversion" = "18" ] || [ "$getubuntuversion" -gt "18" ]; then apt install sudo wget -y sudo apt install -y software-properties-common sudo add-apt-repository universe -y @@ -47,11 +47,11 @@ fi if ! [ -x "$(command -v node)" ]; then echo "=============" echo "Shinobi - Installing Node.js" - wget https://deb.nodesource.com/setup_12.x - chmod +x setup_12.x - ./setup_12.x + wget https://deb.nodesource.com/setup_16.x + chmod +x setup_16.x + ./setup_16.x sudo apt install nodejs -y - rm setup_12.x + rm setup_16.x else echo "Node.js Found..." echo "Version : $(node -v)" @@ -61,7 +61,7 @@ if ! [ -x "$(command -v npm)" ]; then fi sudo apt install make zip -y if ! [ -x "$(command -v ffmpeg)" ]; then - if [ "$getubuntuversion" = "16" ] || [ "$getubuntuversion" < "16" ]; then + if [ "$getubuntuversion" = "16" ] || [ "$getubuntuversion" -le "16" ]; then echo "=============" echo "Shinobi - Get FFMPEG 3.x from ppa:jonathonf/ffmpeg-3" sudo add-apt-repository ppa:jonathonf/ffmpeg-3 -y @@ -112,7 +112,7 @@ sudo npm install --unsafe-perm # sudo npm audit fix --force echo "=============" echo "Shinobi - Install PM2" -sudo npm install pm2@3.0.0 -g +sudo npm install pm2@latest -g echo "Shinobi - Finished" sudo chmod -R 755 . touch INSTALL/installed.txt diff --git a/LICENSE.md b/LICENSE.md index e6512369..0cf4376c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -42,17 +42,41 @@ representative at support@shinobi.systems. #### Commercial Uses - Selling usage of the software +- Selling hardware with the software on it - Using the software in locations that engage in the buying and selling of goods and services +As of 2022-07-12 the noted situations below are seen the same as "Commercial Use". + +- 25 Active Monitor Rule : Having at least 25 Active Monitors and not a Primary School or Secondary School. Does not apply to Personal Use. +- 150 Active Monitor Rule : If you have more than 150 Active Monitors please contact support for an Enterprise License. The retail Shinobi Pro license will not be applicable for these installations. +- Used on a Device that was part of a commercial transaction. This can be, but not limited to, being sold or provided additionally to a sale. + #### Conditions for Free (Unpaid) use. +If you fall under any conditions for "Commercial Use" none of the following can apply unless otherwise noted. + +- Personal 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 +- Usage by Primary Education or Secondary Education Schools +- Use for Emergency Services and facilities associated like Search and Rescue Services or +Ambulance Services with less than 50 cameras +- Use in Health Care facility like a hospital or walk-in clinic with less than 100 cameras +- Schools and Organizations that have been given exemption + +As of 2022-07-12 If you are an organization that falls under one or more of these conditions for free use then you must display that you are using Shinobi Systems software as "shinobi.video". This can be physically on premise or on your organization's public web page. + +Falling under the conditions for Free use does not guarantee you a License Key. It simply means you are allowed to use it without paying or trading for that use. + +#### Unsure if your use is commercial or non-commercial + +Please contact us through the Live Chat of our website and we can negotiate or make exceptions based on the circumstances of your use case. + +#### Registration + +Schools and Resellers must register with Shinobi Systems at https://licenses.shinobi.video. +In your account please fill out the "Apply for a School License" form or "Apply to be a Reseller" form. #### Support Services. The Maintenance and Support Service shall be contracted and provided as per selected plan @@ -68,7 +92,7 @@ This software is property of Shinobi Systems. LICENSEE must keep all copyright n unchanged. #### Modification of this Software Product. -LICENSEE may modify code for but must provide these changes upon request from +LICENSEE may modify code for 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. @@ -102,29 +126,16 @@ Courthouse Vancouver Robson Square #### List of Included Software - This list is completed to best of our knowledge. + This list is completed to the 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 + pam-diff (Motion Detector) - https://github.com/kevinGodell/pam-diff + pipe2pam (for pam-diff) - https://github.com/kevinGodell/pipe2pam + mp4frag (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 + pipe2jpeg - https://github.com/kevinGodell/pipe2jpeg hls.js - https://github.com/video-dev/hls.js/ flv.js - https://github.com/Bilibili/flv.js/ Material Design Lite - https://getmdl.io/ @@ -142,3 +153,50 @@ Courthouse Vancouver Robson Square Moment.js - https://momentjs.com/ Livestamp.js - https://mattbradley.github.io/livestampjs/ Lodash - https://lodash.com/ + + **npmjs.com packages** + + async - https://www.npmjs.com/package/async + aws-sdk - https://www.npmjs.com/package/aws-sdk + backblaze-b2 - https://www.npmjs.com/package/backblaze-b2 + body-parser - https://www.npmjs.com/package/body-parser + bson - https://www.npmjs.com/package/bson + connection-tester - https://www.npmjs.com/package/connection-tester + cws - https://www.npmjs.com/package/cws + digest-fetch - https://www.npmjs.com/package/digest-fetch + discord.js - https://www.npmjs.com/package/discord.js + ejs - https://www.npmjs.com/package/ejs + express - https://www.npmjs.com/package/express + express-fileupload - https://www.npmjs.com/package/express-fileupload + fs-extra - https://www.npmjs.com/package/fs-extra + ftp-srv - https://www.npmjs.com/package/ftp-srv + googleapis - https://www.npmjs.com/package/googleapis + http-proxy - https://www.npmjs.com/package/http-proxy + jsonfile - https://www.npmjs.com/package/jsonfile + knex - https://www.npmjs.com/package/knex + ldapauth-fork - https://www.npmjs.com/package/ldapauth-fork + moment - https://www.npmjs.com/package/moment + mp4frag - https://www.npmjs.com/package/mp4frag + mqtt - https://www.npmjs.com/package/mqtt + mysql - https://www.npmjs.com/package/mysql + node-abort-controller - https://www.npmjs.com/package/node-abort-controller + node-fetch - https://www.npmjs.com/package/node-fetch + node-onvif-events - https://www.npmjs.com/package/node-onvif-events + node-ssh - https://www.npmjs.com/package/node-ssh + node-telegram-bot-api - https://www.npmjs.com/package/node-telegram-bot-api + nodemailer - https://www.npmjs.com/package/nodemailer + pam-diff - https://www.npmjs.com/package/pam-diff + path - https://www.npmjs.com/package/path + pipe2pam - https://www.npmjs.com/package/pipe2pam + pixel-change - https://www.npmjs.com/package/pixel-change + pushover-notifications - https://www.npmjs.com/package/pushover-notifications + sat - https://www.npmjs.com/package/sat + shinobi-onvif - https://www.npmjs.com/package/shinobi-onvif + shinobi-sound-detection - https://www.npmjs.com/package/shinobi-sound-detection + shinobi-zwave - https://www.npmjs.com/package/shinobi-zwave + smtp-server - https://www.npmjs.com/package/smtp-server + socket.io - https://www.npmjs.com/package/socket.io + socket.io-client - https://www.npmjs.com/package/socket.io-client + tree-kill - https://www.npmjs.com/package/tree-kill + unzipper - https://www.npmjs.com/package/unzipper + webdav-fs - https://www.npmjs.com/package/webdav-fs diff --git a/UPDATE-v2-to-v3.sh b/UPDATE-v2-to-v3.sh new file mode 100644 index 00000000..a3665096 --- /dev/null +++ b/UPDATE-v2-to-v3.sh @@ -0,0 +1,25 @@ +echo "=============" +echo "Updating Node.js to version 16..." +wget https://deb.nodesource.com/setup_16.x +chmod +x setup_16.x +./setup_16.x +apt install nodejs -y +apt install node-pre-gyp -y +rm setup_16.x +npm i npm -g + +echo "=============" +echo "Updating PM2..." +npm install pm2@latest -g + +echo "=============" +echo "Updating Shinobi dependencies..." +git reset --hard +git pull +rm -rf node_modules +npm install + +echo "=============" +echo "Restarting PM2..." +pm2 update +pm2 restart camera.js diff --git a/camera.js b/camera.js index 2f2d58e5..19ca4263 100644 --- a/camera.js +++ b/camera.js @@ -8,7 +8,7 @@ // Subscribe : https://licenses.shinobi.video/subscribe?planSubscribe=plan_G31AZ9mknNCa6z // PayPal : paypal@m03.ca // -const io = new (require('socket.io'))() +const io = new (require('socket.io').Server)() //process handlers const s = require('./libs/process.js')(process,__dirname) //load extender functions @@ -33,6 +33,10 @@ require('./libs/ffmpeg.js')(s,config,lang, async () => { require('./libs/auth.js')(s,config,lang) //express web server with ejs const app = require('./libs/webServer.js')(s,config,lang,io) + //data port + require('./libs/dataPort.js')(s,config,lang,app,io) + //page layout load + require('./libs/definitions.js')(s,config,lang,app,io) //web server routes : page handling.. require('./libs/webServerPaths.js')(s,config,lang,app,io) //web server routes for streams : streams.. @@ -67,8 +71,6 @@ require('./libs/ffmpeg.js')(s,config,lang, async () => { require('./libs/rtmpserver.js')(s,config,lang) //dropInEvents server (file manipulation to create event trigger) require('./libs/dropInEvents.js')(s,config,lang,app,io) - //form fields to drive the internals - require('./libs/definitions.js')(s,config,lang,app,io) //notifiers : discord.. require('./libs/notification.js')(s,config,lang) //branding functions and config defaults @@ -91,4 +93,6 @@ require('./libs/ffmpeg.js')(s,config,lang, async () => { await require('./libs/startup.js')(s,config,lang) //p2p, commander require('./libs/commander.js')(s,config,lang,app) + //cron + require('./libs/cron.js')(s,config,lang) }) diff --git a/conf.sample.json b/conf.sample.json index 1566a561..fe7fb37b 100644 --- a/conf.sample.json +++ b/conf.sample.json @@ -1,8 +1,14 @@ { "port": 8080, + "debugLog": false, + "videosDir": "__DIR__/videos", "passwordType": "sha256", "detectorMergePamRegionTriggers": true, "wallClockTimestampAsDefault": true, + "useBetterP2P": true, + "smtpServerOptions": { + "allowInsecureAuth": true + }, "addStorage": [ {"name":"second","path":"__DIR__/videos2"} ], diff --git a/cron.js b/cron.js index 414136e0..731e1eb6 100644 --- a/cron.js +++ b/cron.js @@ -1,524 +1,9 @@ -process.on('uncaughtException', function (err) { - console.error('uncaughtException',err); -}); -const fs = require('fs'); -const path = require('path'); -const moment = require('moment'); -const exec = require('child_process').exec; -const spawn = require('child_process').spawn; -const config = require(process.cwd() + '/conf.json') - -//set option defaults -s = { - mainDirectory: process.cwd(), - utcOffset: moment().utcOffset() -}; -if(config.cron===undefined)config.cron={}; -if(config.cron.deleteOld===undefined)config.cron.deleteOld=true; -if(config.cron.deleteOrphans===undefined)config.cron.deleteOrphans=false; -if(config.cron.deleteNoVideo===undefined)config.cron.deleteNoVideo=true; -if(config.cron.deleteNoVideoRecursion===undefined)config.cron.deleteNoVideoRecursion=false; -if(config.cron.deleteOverMax===undefined)config.cron.deleteOverMax=true; -if(config.cron.deleteLogs===undefined)config.cron.deleteLogs=true; -if(config.cron.deleteEvents===undefined)config.cron.deleteEvents=true; -if(config.cron.deleteFileBins===undefined)config.cron.deleteFileBins=true; -if(config.cron.interval===undefined)config.cron.interval=1; -if(config.databaseType===undefined){config.databaseType='mysql'} -if(config.databaseLogs===undefined){config.databaseLogs=false} -if(config.useUTC===undefined){config.useUTC=false} -if(config.debugLog===undefined){config.debugLog=false} - -if(!config.ip||config.ip===''||config.ip.indexOf('0.0.0.0')>-1)config.ip='localhost'; -if(!config.videosDir)config.videosDir = s.mainDirectory + '/videos/'; -if(!config.binDir){config.binDir = s.mainDirectory + '/fileBin/'} - -const { - checkCorrectPathEnding, - generateRandomId, - formattedTime, - localToUtc, -} = require('./libs/basic/utils.js')(s.mainDirectory) -const { - sqlDate, - knexQuery, - knexQueryPromise, - initiateDatabaseEngine -} = require('./libs/sql/utils.js')(s,config) -var theCronInterval = null -const overlapLocks = {} -const alreadyDeletedRowsWithNoVideosOnStart = {} -const videoDirectory = checkCorrectPathEnding(config.videosDir) -const fileBinDirectory = checkCorrectPathEnding(config.binDir) -s.debugLog = function(arg1,arg2){ - if(config.debugLog === true){ - if(!arg2)arg2 = '' - console.log(arg1,arg2) - } +const { parentPort, isMainThread } = require('worker_threads'); +if(isMainThread){ + console.log(`Shinobi now runs cron.js as a worker process from camera.js.`) + console.error(`Shinobi now runs cron.js as a worker process from camera.js.`) + setInterval(() => { + // console.log(`Please turn off cron.js process.`) + },1000 * 60 * 60 * 24 * 7) + return; } -const connectToMainProcess = () => { - const io = require('socket.io-client')('ws://'+config.ip+':'+config.port,{ - transports:['websocket'] - }); - io.on('connect',function(d){ - postMessage({ - f: 'init', - time: moment() - }) - }) - io.on('f',function(d){ - //command from main process - switch(d.f){ - case'start':case'restart': - setIntervalForCron() - break; - case'stop': - clearCronInterval() - break; - } - }) - return io -} -const postMessage = (x) => { - x.cronKey = config.cron.key; - return io.emit('cron',x) -} -const sendToWebSocket = (x,y) => { - //emulate master socket emitter - postMessage({f:'s.tx',data:x,to:y}) -} -const deleteVideo = (x) => { - postMessage({f:'s.deleteVideo',file:x}) -} -const deleteFileBinEntry = (x) => { - postMessage({f:'s.deleteFileBinEntry',file:x}) -} -const setDiskUsedForGroup = (groupKey,size,target,videoRow) => { - postMessage({f:'s.setDiskUsedForGroup', ke: groupKey, size: size, target: target, videoRow: videoRow}) -} -const getVideoDirectory = function(e){ - if(e.mid&&!e.id){e.id=e.mid}; - if(e.details&&(e.details instanceof Object)===false){ - try{e.details=JSON.parse(e.details)}catch(err){} - } - if(e.details.dir&&e.details.dir!==''){ - return checkCorrectPathEnding(e.details.dir)+e.ke+'/'+e.id+'/' - }else{ - return videoDirectory + e.ke + '/' + e.id + '/' - } -} -const getFileBinDirectory = function(e){ - if(e.mid && !e.id){e.id = e.mid} - return fileBinDirectory + e.ke + '/' + e.id + '/' -} -//filters set by the user in their dashboard -//deleting old videos is part of the filter - config.cron.deleteOld -const checkFilterRules = function(v){ - return new Promise((resolve,reject) => { - //filters - v.d.filters = v.d.filters ? v.d.filters : {} - s.debugLog('Checking Basic Filters...') - var keys = Object.keys(v.d.filters) - if(keys.length>0){ - keys.forEach(function(m,current){ - // b = filter - var b = v.d.filters[m]; - s.debugLog(b) - if(b.enabled==="1"){ - const whereQuery = [ - ['ke','=',v.ke], - ['status','!=',"0"], - ['details','NOT LIKE','%"archived":"1"%'], - ] - b.where.forEach(function(condition){ - if(condition.p1 === 'ke'){condition.p3 = v.ke} - whereQuery.push([condition.p1,condition.p2 || '=',condition.p3]) - }) - knexQuery({ - action: "select", - columns: "*", - table: "Videos", - where: whereQuery, - orderBy: [b.sort_by,b.sort_by_direction.toLowerCase()], - limit: b.limit - },(err,r) => { - if(r && r[0]){ - if(r.length > 0 || config.debugLog === true){ - postMessage({f:'filterMatch',msg:r.length+' SQL rows match "'+m+'"',ke:v.ke,time:moment()}) - } - b.cx={ - f:'filters', - name:b.name, - videos:r, - time:moment(), - ke:v.ke, - id:b.id - }; - if(b.archive==="1"){ - postMessage({f:'filters',ff:'archive',videos:r,time:moment(),ke:v.ke,id:b.id}); - }else if(b.delete==="1"){ - postMessage({f:'filters',ff:'delete',videos:r,time:moment(),ke:v.ke,id:b.id}); - } - if(b.email==="1"){ - b.cx.ff='email'; - b.cx.delete=b.delete; - b.cx.mail=v.mail; - b.cx.execute=b.execute; - b.cx.query=b.sql; - postMessage(b.cx); - } - if(b.execute&&b.execute!==""){ - postMessage({f:'filters',ff:'execute',execute:b.execute,time:moment()}); - } - } - }) - - } - if(current===keys.length-1){ - //last filter - resolve() - } - }) - }else{ - //no filters - resolve() - } - }) -} -const deleteVideosByDays = async (v,days,addedQueries) => { - const groupKey = v.ke; - const whereQuery = [ - ['ke','=',v.ke], - ['time','<', sqlDate(days+' DAY')], - addedQueries - ] - const selectResponse = await knexQueryPromise({ - action: "select", - columns: "*", - table: "Videos", - where: whereQuery - }) - const videoRows = selectResponse.rows - let affectedRows = 0 - if(videoRows.length > 0){ - let clearSize = 0; - var i; - for (i = 0; i < videoRows.length; i++) { - const row = videoRows[i]; - const dir = getVideoDirectory(row) - const filename = formattedTime(row.time) + '.' + row.ext - try{ - await fs.promises.unlink(dir + filename) - const fileSizeMB = row.size / 1048576; - setDiskUsedForGroup(groupKey,-fileSizeMB,null,row) - sendToWebSocket({ - f: 'video_delete', - filename: filename + '.' + row.ext, - mid: row.mid, - ke: row.ke, - time: row.time, - end: formattedTime(new Date,'YYYY-MM-DD HH:mm:ss') - },'GRP_' + row.ke) - }catch(err){ - console.log('Video Delete Error',row) - console.log(err) - } - } - const deleteResponse = await knexQueryPromise({ - action: "delete", - table: "Videos", - where: whereQuery - }) - affectedRows = deleteResponse.rows || 0 - } - return { - ok: true, - affectedRows: affectedRows, - } -} -const deleteOldVideos = async (v) => { - // v = group, admin user - if(config.cron.deleteOld === true){ - const daysOldForDeletion = v.d.days && !isNaN(v.d.days) ? parseFloat(v.d.days) : 5 - const monitorsIgnored = [] - const monitorsResponse = await knexQueryPromise({ - action: "select", - columns: "*", - table: "Monitors", - where: [ - ['ke','=',v.ke], - ] - }) - const monitorRows = monitorsResponse.rows - var i; - for (i = 0; i < monitorRows.length; i++) { - const monitor = monitorRows[i] - const monitorId = monitor.mid - const details = JSON.parse(monitor.details); - const monitorsMaxDaysToKeep = !isNaN(details.max_keep_days) ? parseFloat(details.max_keep_days) : null - if(monitorsMaxDaysToKeep){ - const { affectedRows } = await deleteVideosByDays(v,monitorsMaxDaysToKeep,['mid','=',monitorId]) - const hasDeletedRows = affectedRows && affectedRows.length > 0; - if(hasDeletedRows || config.debugLog === true){ - postMessage({ - f: 'deleteOldVideosByMonitorId', - msg: `${affectedRows} SQL rows older than ${monitorsMaxDaysToKeep} days deleted`, - ke: v.ke, - mid: monitorId, - time: moment(), - }) - } - monitorsIgnored.push(['mid','!=',monitorId]) - } - } - const { affectedRows } = await deleteVideosByDays(v,daysOldForDeletion,monitorsIgnored) - const hasDeletedRows = affectedRows && affectedRows.length > 0; - if(hasDeletedRows || config.debugLog === true){ - postMessage({ - f: 'deleteOldVideos', - msg: `${affectedRows} SQL rows older than ${daysOldForDeletion} days deleted`, - ke: v.ke, - time: moment(), - }) - } - } -} -//database rows with no videos in the filesystem -const deleteRowsWithNoVideo = function(v){ - return new Promise((resolve,reject) => { - if( - config.cron.deleteNoVideo===true&&( - config.cron.deleteNoVideoRecursion===true|| - (config.cron.deleteNoVideoRecursion===false&&!alreadyDeletedRowsWithNoVideosOnStart[v.ke]) - ) - ){ - alreadyDeletedRowsWithNoVideosOnStart[v.ke]=true; - knexQuery({ - action: "select", - columns: "*", - table: "Videos", - where: [ - ['ke','=',v.ke], - ['status','!=','0'], - ['details','NOT LIKE','%"archived":"1"%'], - ['time','<', sqlDate('10 MINUTE')], - ] - },(err,evs) => { - if(evs && evs[0]){ - const videosToDelete = []; - evs.forEach(function(ev){ - var filename - var details - try{ - details = JSON.parse(ev.details) - }catch(err){ - if(details instanceof Object){ - details = ev.details - }else{ - details = {} - } - } - var dir = getVideoDirectory(ev) - filename = formattedTime(ev.time)+'.'+ev.ext - fileExists = fs.existsSync(dir+filename) - if(fileExists !== true){ - deleteVideo(ev) - sendToWebSocket({f:'video_delete',filename:filename+'.'+ev.ext,mid:ev.mid,ke:ev.ke,time:ev.time,end: formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+ev.ke); - } - }); - if(videosToDelete.length > 0 || config.debugLog === true){ - postMessage({f:'deleteNoVideo',msg:videosToDelete.length+' SQL rows with no file deleted',ke:v.ke,time:moment()}) - } - } - setTimeout(function(){ - resolve() - },3000) - }) - }else{ - resolve() - } - }) -} -//info about what the application is doing -const deleteOldLogs = function(v){ - return new Promise((resolve,reject) => { - const daysOldForDeletion = v.d.log_days && !isNaN(v.d.log_days) ? parseFloat(v.d.log_days) : 10 - if(config.cron.deleteLogs === true && daysOldForDeletion !== 0){ - knexQuery({ - action: "delete", - table: "Logs", - where: [ - ['ke','=',v.ke], - ['time','<', sqlDate(daysOldForDeletion + ' DAY')], - ] - },(err,rrr) => { - resolve() - if(err)return console.error(err); - if(rrr && rrr > 0 || config.debugLog === true){ - postMessage({f:'deleteLogs',msg: rrr + ' SQL rows older than ' + daysOldForDeletion + ' days deleted',ke:v.ke,time:moment()}) - } - }) - }else{ - resolve() - } - }) -} -//events - motion, object, etc. detections -const deleteOldEvents = function(v){ - return new Promise((resolve,reject) => { - const daysOldForDeletion = v.d.event_days && !isNaN(v.d.event_days) ? parseFloat(v.d.event_days) : 10 - if(config.cron.deleteEvents === true && daysOldForDeletion !== 0){ - knexQuery({ - action: "delete", - table: "Events", - where: [ - ['ke','=',v.ke], - ['time','<', sqlDate(daysOldForDeletion + ' DAY')], - ] - },(err,rrr) => { - resolve() - if(err)return console.error(err); - if(rrr && rrr > 0 || config.debugLog === true){ - postMessage({f:'deleteEvents',msg:rrr + ' SQL rows older than ' + daysOldForDeletion + ' days deleted',ke:v.ke,time:moment()}) - } - }) - }else{ - resolve() - } - }) -} -//event counts -const deleteOldEventCounts = function(v){ - return new Promise((resolve,reject) => { - const daysOldForDeletion = v.d.event_days && !isNaN(v.d.event_days) ? parseFloat(v.d.event_days) : 10 - if(config.cron.deleteEvents === true && daysOldForDeletion !== 0){ - knexQuery({ - action: "delete", - table: "Events Counts", - where: [ - ['ke','=',v.ke], - ['time','<', sqlDate(daysOldForDeletion + ' DAY')], - ] - },(err,rrr) => { - resolve() - if(err && err.code !== 'ER_NO_SUCH_TABLE')return console.error(err); - if(rrr && rrr > 0 || config.debugLog === true){ - postMessage({f:'deleteEvents',msg:rrr + ' SQL rows older than ' + daysOldForDeletion + ' days deleted',ke:v.ke,time:moment()}) - } - }) - }else{ - resolve() - } - }) -} -//check for temporary files (special archive) -const deleteOldFileBins = function(v){ - return new Promise((resolve,reject) => { - const daysOldForDeletion = v.d.fileBin_days && !isNaN(v.d.fileBin_days) ? parseFloat(v.d.fileBin_days) : 10 - if(config.cron.deleteFileBins === true && daysOldForDeletion !== 0){ - var fileBinQuery = " FROM Files WHERE ke=? AND `time` < ?"; - knexQuery({ - action: "select", - columns: "*", - table: "Files", - where: [ - ['ke','=',v.ke], - ['time','<', sqlDate(daysOldForDeletion + ' DAY')], - ] - },(err,files) => { - if(files && files[0]){ - //delete the files - files.forEach(function(file){ - deleteFileBinEntry(file) - }) - if(config.debugLog === true){ - postMessage({ - f: 'deleteFileBins', - msg: files.length + ' files older than ' + daysOldForDeletion + ' days deleted', - ke: v.ke, - time: moment() - }) - } - } - resolve() - }) - }else{ - resolve() - } - }) -} -//user processing function -const processUser = async (v) => { - if(!v){ - //no user object given, end of group list - return - } - s.debugLog(`Group Key : ${v.ke}`) - s.debugLog(`Owner : ${v.mail}`) - if(!overlapLocks[v.ke]){ - s.debugLog(`Checking...`) - overlapLocks[v.ke] = true - v.d = JSON.parse(v.details); - try{ - await deleteOldVideos(v) - s.debugLog('--- deleteOldVideos Complete') - await deleteOldLogs(v) - s.debugLog('--- deleteOldLogs Complete') - await deleteOldFileBins(v) - s.debugLog('--- deleteOldFileBins Complete') - await deleteOldEvents(v) - s.debugLog('--- deleteOldEvents Complete') - await deleteOldEventCounts(v) - s.debugLog('--- deleteOldEventCounts Complete') - await checkFilterRules(v) - s.debugLog('--- checkFilterRules Complete') - await deleteRowsWithNoVideo(v) - s.debugLog('--- deleteRowsWithNoVideo Complete') - }catch(err){ - console.log(`Failed to Complete User : ${v.mail}`) - console.log(err) - } - //done user, unlock current, and do next - overlapLocks[v.ke] = false; - s.debugLog(`Complete Checking... ${v.mail}`) - }else{ - s.debugLog(`Locked, Skipped...`) - } -} -//recursive function -const setIntervalForCron = function(){ - clearCronInterval() - // theCronInterval = setInterval(doCronJobs,1000 * 10) - theCronInterval = setInterval(doCronJobs,parseFloat(config.cron.interval)*60000*60) -} -const clearCronInterval = function(){ - clearInterval(theCronInterval) -} -const doCronJobs = function(){ - postMessage({ - f: 'start', - time: moment() - }) - knexQuery({ - action: "select", - columns: "ke,uid,details,mail", - table: "Users", - where: [ - ['details','NOT LIKE','%"sub"%'], - ] - }, async (err,rows) => { - if(err){ - console.error(err) - } - if(rows.length > 0){ - var i; - for (i = 0; i < rows.length; i++) { - await processUser(rows[i]) - } - } - }) -} -initiateDatabaseEngine() -const io = connectToMainProcess() -setIntervalForCron() -doCronJobs() -console.log('Shinobi : cron.js started') diff --git a/definitions/en_CA.js b/definitions/base.js similarity index 60% rename from definitions/en_CA.js rename to definitions/base.js index 891c083b..aa6df0c9 100644 --- a/definitions/en_CA.js +++ b/definitions/base.js @@ -1,20 +1,55 @@ module.exports = function(s,config,lang){ - return { + const Theme = { + isDark: true, + } + const mainBackgroundColor = Theme.isDark ? 'bg-dark' : Theme.isDarkDefaultBg || 'bg-light' + const textWhiteOnBgDark = Theme.isDark ? 'text-white' : '' + return Object.assign({ + Theme: Theme, + },{ "Monitor Status Codes": { - "0": "Disabled", - "1": "Starting", - "2": "Watching", - "3": "Recording", - "4": "Restarting", - "5": "Stopped", - "6": "Idle", - "7": "Died", - "8": "Stopping", - "9": "Started", + "0": lang["Disabled"], + "1": lang["Starting"], + "2": lang["Watching"], + "3": lang["Recording"], + "4": lang["Restarting"], + "5": lang["Stopped"], + "6": lang["Idle"], + "7": lang["Died"], + "8": lang["Stopping"], + "9": lang["Started"], }, "Monitor Settings": { "section": "Monitor Settings", "blocks": { + "Page Control": { + name: lang.Monitor, + headerTitle: `
addStorage variable.",
+ "description": lang["fieldTextDir"],
"fieldType": "select",
"possible": s.listOfStorage
+ },
+ {
+ "name": "detail=auto_compress_videos",
+ "field": lang['Compress Completed Videos'],
+ "description": lang.compressCompletedVideosFieldText,
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
}
]
},
"Presets": {
id: "monSectionPresets",
+ "section-class": "am_notice am_notice_edit",
"name": lang.Presets,
"color": "purple",
isSection: true,
@@ -110,13 +162,14 @@ module.exports = function(s,config,lang){
]
},
{
- "fieldType": 'ul',
+ "fieldType": 'div',
"id": "monitorPresetsSelection",
+ "style": "max-height:400px;overflow:auto;",
"class": "mdl-list"
},
{
"fieldType": "btn",
- "attribute": `data-toggle="modal" data-target="#schedules"`,
+ "attribute": `page-open="schedules"`,
"class": `btn-info`,
"btnContent": ` ${lang['Schedules']}`,
},
@@ -137,49 +190,49 @@ module.exports = function(s,config,lang){
"fieldType": "select",
"selector": "h_t",
"field": lang["Input Type"],
- "description": "The method that will used to consume the video stream.",
+ "description": lang["fieldTextType"],
"default": "h264",
"example": "",
"possible": [
{
"name": "JPEG",
"value": "jpeg",
- "info": "Reading snapshots from a URL and making a stream and/or video from them."
+ "info": lang["fieldTextTypeJPEG"]
},
{
"name": "MJPEG",
"value": "mjpeg",
- "info": "Similar to JPEG except the frame handling is done by FFMPEG, not Shinobi."
+ "info": lang["fieldTextTypeMJPEG"]
},
{
"name": "H.264 / H.265 / H.265+",
"value": "h264",
- "info": "Reading a high quality video streas that sometimes include audio."
+ "info": lang["fieldTextTypeH.264/H.265/H.265+"]
},
{
"name": "HLS (.m3u8)",
"value": "hls",
- "info": "Reading a high quality video streas that sometimes include audio."
+ "info": lang["fieldTextTypeHLS(.m3u8)"]
},
{
"name": "MPEG-4 (.mp4 / .ts)",
"value": "mp4",
- "info": "A static file. Read at a lower rate and should not be used for an actual live stream."
+ "info": lang["fieldTextTypeMPEG4(.mp4/.ts)"]
},
{
"name": "Shinobi Streamer",
"value": "socket",
- "info": "Websocket JPEG-based P2P stream."
+ "info": lang["fieldTextTypeShinobiStreamer"]
},
{
"name": "Dashcam (Streamer v2)",
"value": "dashcam",
- "info": "Websocket WebM-based P2P stream."
+ "info": lang["fieldTextTypeDashcam(StreamerV2)"]
},
{
"name": lang.Local,
"value": "local",
- "info": "Reading Capture Cards, Webcams, or Integrated Cameras."
+ "info": lang["fieldTextTypeLocal"]
},
{
"evaluation": "!!config.rtmpServer",
@@ -190,7 +243,7 @@ module.exports = function(s,config,lang){
{
"name": "MxPEG",
"value": "mxpeg",
- "info": "Mobotix MJPEG Stream"
+ "info": lang["fieldTextTypeMxPEG"]
},
]
},
@@ -199,7 +252,7 @@ module.exports = function(s,config,lang){
"name": "detail=rtmp_key",
"form-group-class": "h_t_input h_t_rtmp",
"field": lang['Stream Key'],
- "description": "Stream Key for incoming streams on the RTMP port.",
+ "description": lang["fieldTextRtmpKey"],
"default": "",
"example": "",
"possible": ""
@@ -208,7 +261,7 @@ module.exports = function(s,config,lang){
hidden:true,
"name": "detail=auto_host_enable",
"field": lang.Automatic,
- "description": "Feed the individual pieces required to build a stream URL or provide the full URL and allow Shinobi to parse it for you.",
+ "description": lang["fieldTextAutoHostEnable"],
"selector": "h_auto_host",
"form-group-class": "h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg h_t_mxpeg",
"form-group-class-pre-layer":"h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg h_t_mxpeg h_t_local",
@@ -231,7 +284,7 @@ module.exports = function(s,config,lang){
"name": "detail=auto_host",
"field": lang["Full URL Path"],
"form-group-class": "h_auto_host_input h_auto_host_1",
- "description": "The full Stream URL.",
+ "description": lang["fieldTextAutoHost"],
"default": "",
"example": "rtsp://username:password@123.123.123.123/stream/1",
"possible": ""
@@ -240,7 +293,7 @@ module.exports = function(s,config,lang){
hidden:true,
"name": "protocol",
"field": lang["Connection Type"],
- "description": "The protocol that will used to consume the video stream.",
+ "description": lang["fieldTextProtocol"],
"default": "RTSP",
"example": "",
"form-group-class": "h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg",
@@ -277,7 +330,7 @@ module.exports = function(s,config,lang){
hidden:true,
"name": "detail=rtsp_transport",
"field": lang["RTSP Transport"],
- "description": "The transport protocol your camera will use. TCP is usually the best choice.",
+ "description": lang["fieldTextRtspTransport"],
"default": "",
"example": "",
"form-group-class": "h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg",
@@ -288,22 +341,22 @@ module.exports = function(s,config,lang){
{
"name": lang.Auto,
"value": "no",
- "info": "Let FFMPEG decide. Normally it will try UDP first."
+ "info": lang["fieldTextRtspTransportAuto"]
},
{
"name": "TCP",
"value": "tcp",
- "info": "Set it to this if UDP starts giving undesired results."
+ "info": lang["fieldTextRtspTransportTCP"]
},
{
"name": "UDP",
"value": "udp",
- "info": "FFMPEG tries this first."
+ "info": lang["fieldTextRtspTransportUDP"]
},
{
"name": "HTTP",
"value": "http",
- "info": "Standard connection method."
+ "info": lang["fieldTextRtspTransportHTTP"]
}
]
},
@@ -311,7 +364,7 @@ module.exports = function(s,config,lang){
hidden:true,
"name": "detail=muser",
"field": lang.Username,
- "description": "The user login for your camera",
+ "description": lang["fieldTextMuser"],
"default": "",
"example": "kittenFinder",
"form-group-class": "h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg",
@@ -323,7 +376,7 @@ module.exports = function(s,config,lang){
"name": "detail=mpass",
"fieldType": "password",
"field": lang.Password,
- "description": "The password for your camera",
+ "description": lang["fieldTextMpass"],
"default": "",
"example": "kittenCuddler",
"form-group-class": "h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg",
@@ -334,7 +387,7 @@ module.exports = function(s,config,lang){
hidden:true,
"name": "host",
"field": lang.Host,
- "description": "Connection address",
+ "description": lang["fieldTextHost"],
"default": "",
"example": "111.111.111.111",
"form-group-class": "h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg",
@@ -345,7 +398,7 @@ module.exports = function(s,config,lang){
hidden:true,
"name": "port",
"field": lang.Port,
- "description": "Port number that your camera is streaming out on.",
+ "description": lang["fieldTextPort"],
"default": "80",
"example": "554",
"possible": "1-65535",
@@ -356,7 +409,7 @@ module.exports = function(s,config,lang){
hidden:true,
"name": "detail=port_force",
"field": lang["Force Port"],
- "description": "Using the default web port can allow automatic switch to other ports for streams like RTSP.",
+ "description": lang["fieldTextPortForce"],
"default": "0",
"example": "",
"form-group-class": "h_t_input h_t_h264 h_t_hls h_t_mp4 h_t_jpeg h_t_mjpeg",
@@ -377,7 +430,7 @@ module.exports = function(s,config,lang){
hidden:true,
"name": "path",
"field": lang.Path,
- "description": "The path to your camera",
+ "description": lang["fieldTextPath"],
"default": "",
"example": "/videostream.cgi?1",
"possible": "",
@@ -385,10 +438,9 @@ module.exports = function(s,config,lang){
"form-group-class-pre-layer": "h_auto_host_input h_auto_host_0 auto_host_fill",
},
{
- hidden:true,
"name": "detail=fatal_max",
"field": lang['Retry Connection'],
- "description": "The number of times to retry for network connection between the server and camera before setting the monitor to Disabled. No decimals. Set to 0 to retry forever.",
+ "description": lang["fieldTextFatalMax"],
"default": "10",
"example": "",
"possible": "",
@@ -396,7 +448,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=skip_ping",
"field": lang['Skip Ping'],
- "description": "Choose if a successful ping is required before a monitor process is started.",
+ "description": lang["fieldTextSkipPing"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -414,7 +466,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=is_onvif",
"field": lang.ONVIF,
- "description": "Is this an ONVIF compliant camera?",
+ "description": lang["fieldTextIsOnvif"],
"default": "0",
"example": "",
"selector": "h_onvif",
@@ -433,7 +485,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=onvif_non_standard",
"field": lang['Non-Standard ONVIF'],
- "description": "Is this a Non-Standard ONVIF camera?",
+ "description": lang["fieldTextOnvifNonStandard"],
"default": "0",
"example": "",
"form-group-class": "h_onvif_input h_onvif_1",
@@ -489,23 +541,23 @@ module.exports = function(s,config,lang){
"form-group-class": "input-mapping",
"possible": [
{
- "name": lang['All streams in first feed'] + '(0, ' + lang.Default + ')',
+ "name": lang['All streams in first feed'] + ' (0, ' + lang.Default + ')',
"value": "0"
},
{
- "name": lang['First stream in feed'] + '(0:0)',
+ "name": lang['First stream in feed'] + ' (0:0)',
"value": "0:0"
},
{
- "name": lang['Second stream in feed'] + "(0:1)",
+ "name": lang['Second stream in feed'] + " (0:1)",
"value": "0:1"
},
{
- "name": lang['Video streams only'] + "(0:v)",
+ "name": lang['Video streams only'] + " (0:v)",
"value": "0:v"
},
{
- "name": lang['Video stream only from first feed'] + "(0:v:0)",
+ "name": lang['Video stream only from first feed'] + " (0:v:0)",
"value": "0:v:0"
}
]
@@ -513,7 +565,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=aduration",
"field": lang["Analyzation Duration"],
- "description": "Specify how many microseconds are analyzed to probe the input. Set to 100000 if you are using RTSP and having stream issues.",
+ "description": lang["fieldTextAduration"],
"default": "",
"example": "100000",
"possible": ""
@@ -521,7 +573,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=probesize",
"field": lang["Probe Size"],
- "description": "Specify how big to make the analyzation probe for the input. Set to 100000 if you are using RTSP and having stream issues.",
+ "description": lang["fieldTextProbesize"],
"default": "",
"example": "100000",
"possible": ""
@@ -530,7 +582,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=stream_loop",
"field": lang['Loop Stream'],
- "description": "Loop a static file so the file stream behaves like a live stream.",
+ "description": lang["fieldTextStreamLoop"],
"default": "1",
"example": "",
"form-group-class": "h_t_input h_t_mp4 h_t_local",
@@ -549,7 +601,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=sfps",
"field": lang['Monitor Capture Rate'],
- "description": "Specify the Frame Rate (FPS) in which the camera is providing its stream in.",
+ "description": lang["fieldTextSfps"],
"default": "",
"example": "25",
"possible": ""
@@ -557,7 +609,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=wall_clock_timestamp_ignore",
"field": lang['Use Camera Timestamps'],
- "description": "Base all incoming camera data in camera time instead of server time.",
+ "description": lang["fieldTextWallClockTimestampIgnore"],
"default": "0",
"example": "",
"form-group-class": "h_t_input h_t_h264",
@@ -577,7 +629,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "height",
"field": lang["Height"],
- "description": "Height of the stream image.",
+ "description": lang["fieldTextHeight"],
"default": "480",
"example": "720, 0 for Auto",
"possible": ""
@@ -586,7 +638,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "width",
"field": lang["Width"],
- "description": "Width of the stream image.",
+ "description": lang["fieldTextWidth"],
"default": "640",
"example": "1280, 0 for Auto",
"possible": ""
@@ -594,7 +646,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=accelerator",
"field": lang.Accelerator,
- "description": "Hardware Acceleration (HWAccel) for decoding streams.",
+ "description": lang["fieldTextAccelerator"],
"default": "",
"example": "",
"selector": "h_gpud",
@@ -613,7 +665,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=hwaccel",
"field": lang.hwaccel,
- "description": "Decoding Engine",
+ "description": lang["fieldTextHwaccel"],
"default": "",
"example": "",
"form-group-class": "h_gpud_input h_gpud_1",
@@ -623,7 +675,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=hwaccel_vcodec",
"field": lang.hwaccel_vcodec,
- "description": "Decoding Engine",
+ "description": lang["fieldTextHwaccelVcodec"],
"default": "",
"example": "",
"form-group-class": "h_gpud_input h_gpud_1",
@@ -699,12 +751,15 @@ module.exports = function(s,config,lang){
"form-group-class": "h_gpud_input h_gpud_1",
"possible": ""
},
- {
- "fieldType": 'div',
- "id": "monSectionInputMaps"
- },
]
},
+ "Input Maps": {
+ "name": lang["Additional Inputs"],
+ "color": "orange",
+ "id": "monSectionInputMaps",
+ "section-class": "pb-0",
+ "emptyDiv": true
+ },
"Stream": {
"name": lang.Stream,
@@ -718,7 +773,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=stream_type",
"field": lang["Stream Type"],
- "description": "The method that will used to consume the video stream.",
+ "description": lang["fieldTextStreamType"],
"default": "mp4",
"example": "",
"selector": "h_st",
@@ -728,16 +783,12 @@ module.exports = function(s,config,lang){
{
"name": lang.Poseidon,
"value": "mp4",
- "info": "Poseidon is built on Kevin Godell's MP4 processing code. It simulates a streaming MP4 file but using the data of a live stream. Includes Audio. Some browsers can play it like a regular MP4 file. Streams over HTTP or WebSocket."
- },
- {
- "name": lang['HEVC (H.265)'],
- "value": "h265"
+ "info": lang["fieldTextStreamTypePoseidon"]
},
{
"name": lang['Base64 over Websocket'],
"value": "b64",
- "info": "Sending Base64 encoded frames over WebSocket. This avoids caching but there is no audio."
+ "info": lang["fieldTextStreamTypeBase64OverWebsocket"]
},
{
"name": lang['JPEG (Auto Enables JPEG API)'],
@@ -746,29 +797,34 @@ module.exports = function(s,config,lang){
{
"name": lang['MJPEG'],
"value": "mjpeg",
- "info": "Standard Motion JPEG image. No audio."
+ "info": lang["fieldTextStreamTypeMJPEG"]
},
{
"name": lang['FLV'],
"value": "flv",
- "info": "Sending FLV encoded frames over WebSocket."
+ "info": lang["fieldTextStreamTypeFLV"]
},
{
"name": lang['HLS (includes Audio)'],
"value": "hls",
- "info": "Similar method to facebook live streams. Includes audio if input provides it. There is a delay of about 4-6 seconds because this method records segments then pushes them to the client rather than push as while it creates them."
+ "info": lang["fieldTextStreamTypeHLS(includesAudio)"]
+ },
+ {
+ "name": lang.useSubStreamOnlyWhenWatching,
+ "value": "useSubstream",
}
]
},
{
+ isAdvanced: true,
hidden:true,
"name": "detail=stream_flv_type",
"field": lang["Connection Type"],
- "description": "This is for the Shinobi dashboard only. Both stream methods are still active and ready to use.",
+ "description": lang["fieldTextStreamFlvType"],
"default": "0",
"example": "",
"fieldType": "select",
- "form-group-class": "h_st_input h_st_flv h_st_mp4 h_st_h265",
+ "form-group-class": "h_st_input h_st_flv h_st_mp4",
"possible": [
{
"name": lang.HTTP,
@@ -781,6 +837,7 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
hidden:true,
"name": "detail=stream_flv_maxLatency",
"field": lang["Max Latency"],
@@ -792,6 +849,7 @@ module.exports = function(s,config,lang){
"possible": ""
},
{
+ isAdvanced: true,
hidden:true,
"name": "detail=stream_mjpeg_clients",
"field": lang["# of Allow MJPEG Clients"],
@@ -804,32 +862,32 @@ module.exports = function(s,config,lang){
{
"name": "detail=stream_vcodec",
"field": lang['Video Codec'],
- "description": "Video codec for streaming.",
+ "description": lang["fieldTextStreamVcodec"],
"default": "copy",
"example": "",
- "form-group-class": "h_st_input h_st_hls h_st_flv h_st_mp4 h_st_h265",
+ "form-group-class": "h_st_input h_st_hls h_st_flv h_st_mp4",
"fieldType": "select",
"selector": "h_hls_v",
"possible": [
{
"name": lang.Auto,
"value": "no",
- "info": "Let FFMPEG choose."
+ "info": lang["fieldTextStreamVcodecAuto"]
},
{
"name": "libx264",
"value": "libx264",
- "info": "Used for MP4 video."
+ "info": lang["fieldTextStreamVcodecLibx264"]
},
{
"name": "libx265",
"value": "libx265",
- "info": "Used for MP4 video."
+ "info": lang["fieldTextStreamVcodecLibx265"]
},
{
"name": lang.copy,
"value": "copy",
- "info": "Used for MP4 video. Has very low CPU usage but cannot use video filters and filesizes may be gigantic. Best to setup your MP4 settings camera-side when using this option."
+ "info": lang["fieldTextStreamVcodecCopy"]
},
{
"name": lang['Hardware Accelerated'],
@@ -885,132 +943,126 @@ module.exports = function(s,config,lang){
{
"name": "detail=stream_acodec",
"field": lang["Audio Codec"],
- "description": "Audio codec for streaming.",
+ "description": lang["fieldTextStreamAcodec"],
"default": "0",
"example": "",
"fieldType": "select",
- "form-group-class": "h_st_input h_st_hls h_st_flv h_st_mp4 h_st_h265",
+ "form-group-class": "h_st_input h_st_hls h_st_flv h_st_mp4",
"possible": [
{
"name": lang.Auto,
- "info": "Let FFMPEG choose.",
+ "info": lang["fieldTextStreamAcodecAuto"],
"value": ""
},
{
"name": lang["No Audio"],
- "info": "No Audio, this is an option that must be set in some parts of the world due to legal reasons.",
+ "info": lang["fieldTextStreamAcodecNoAudio"],
"value": "no"
},
{
"name": "libvorbis",
- "info": "Used for WebM video.",
+ "info": lang["fieldTextStreamAcodecLibvorbis"],
"value": "libvorbis"
},
{
"name": "libopus",
- "info": "Used for WebM video.",
+ "info": lang["fieldTextStreamAcodecLibopus"],
"value": "libopus"
},
{
"name": "libmp3lame",
- "info": "Used for MP4 video.",
+ "info": lang["fieldTextStreamAcodecLibmp3lame"],
"value": "libmp3lame"
},
{
"name": "aac",
- "info": "Used for MP4 video.",
+ "info": lang["fieldTextStreamAcodecAac"],
"value": "aac"
},
{
"name": "ac3",
- "info": "Used for MP4 video.",
+ "info": lang["fieldTextStreamAcodecAc3"],
"value": "ac3"
},
{
"name": "copy",
- "info": "Used for MP4 video. Has very low CPU usage but some audio codecs need custom flags like -strict 2 for aac.",
+ "info": lang["fieldTextStreamAcodecCopy"],
"value": "copy"
}
]
},
{
+ isAdvanced: true,
"name": "detail=hls_time",
"field": "HLS Segment Length",
- "description": "How long each video segment should be, in minutes. Each segment will be drawn by the client through an m3u8 file. Shorter segments take less space.",
+ "description": lang["fieldTextHlsTime"],
"default": "2",
- "example": "",
- "form-group-class-pre-layer": "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",
"form-group-class": "h_st_input h_st_hls",
- "possible": ""
},
{
+ isAdvanced: true,
"name": "detail=hls_list_size",
"field": "HLS List Size",
- "description": "The number of segments maximum before deleting old segments automatically.",
+ "description": lang["fieldTextHlsListSize"],
"default": "2",
- "example": "",
- "form-group-class-pre-layer": "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",
"form-group-class": "h_st_input h_st_hls",
- "possible": ""
},
{
+ isAdvanced: true,
"name": "detail=preset_stream",
"field": "HLS Preset",
- "description": "Preset flag for certain video encoders. If you find your camera is crashing every few seconds : try leaving it blank.",
- "default": "",
+ "description": lang["fieldTextPresetStream"],
"example": "ultrafast",
- "form-group-class-pre-layer": "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",
- "form-group-class": "h_st_input h_st_hls h_st_flv h_st_mp4 h_st_h265",
- "possible": ""
+ "form-group-class": "h_st_input h_st_hls h_st_flv h_st_mp4",
},
{
"name": "detail=stream_quality",
"field": lang.Quality,
- "description": "Low number means higher quality. Higher number means less quality.",
+ "description": lang["fieldTextStreamQuality"],
"default": "15",
"example": "1",
- "form-group-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",
+ uiVisibilityConditions: 'streamSectionCopyModeVisibilities',
"possible": "1-23"
},
{
"name": "detail=stream_fps",
"field": lang['Frame Rate'],
- "description": "The speed in which frames are displayed to clients, in Frames Per Second. Be aware there is no default. This can lead to high bandwidth usage.",
+ "description": lang["fieldTextStreamFps"],
"default": "",
"example": "1",
- "form-group-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",
+ uiVisibilityConditions: 'streamSectionCopyModeVisibilities',
"possible": ""
},
{
"name": "detail=stream_scale_x",
"field": lang.Width,
- "description": "Width of the stream image that is output after processing.",
+ "description": lang["fieldTextStreamScaleX"],
"default": "",
"fieldType": "number",
"numberMin": "1",
"example": "640",
- "form-group-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",
+ uiVisibilityConditions: 'streamSectionCopyModeVisibilities',
"possible": ""
},
{
"name": "detail=stream_scale_y",
"field": lang.Height,
- "description": "Height of the stream image that is output after processing.",
+ "description": lang["fieldTextStreamScaleY"],
"default": "",
"fieldType": "number",
"numberMin": "1",
"example": "480",
- "form-group-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",
+ uiVisibilityConditions: 'streamSectionCopyModeVisibilities',
"possible": ""
},
{
"name": "detail=stream_rotate",
"field": lang["Rotate"],
- "description": "Change the viewing angle of the video stream.",
+ "description": lang["fieldTextStreamRotate"],
"default": "",
"example": "",
"fieldType": "select",
- "form-group-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",
+ uiVisibilityConditions: 'streamSectionCopyModeVisibilities',
"possible": [
{
"name": lang["No Rotation"],
@@ -1039,17 +1091,19 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
"name": "detail=signal_check",
"field": lang["Check Signal Interval"],
- "description": "How often your client will check the stream to see if it is alive. This is calculated in minutes.",
+ "description": lang["fieldTextSignalCheck"],
"default": "0",
"example": "",
"possible": ""
},
{
+ isAdvanced: true,
"name": "detail=signal_check_log",
"field": lang["Log Signal Event"],
- "description": "This is for the client side only. It will display in the log thread when client side signal checks occur.",
+ "description": lang["fieldTextSignalCheckLog"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1065,18 +1119,20 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
"name": "detail=stream_vf",
"field": lang["Video Filter"],
- "description": "Place FFMPEG video filters in this box to affect the streaming portion. No spaces.",
+ "description": lang["fieldTextStreamVf"],
"default": "",
"example": "",
- "form-group-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",
+ uiVisibilityConditions: 'streamSectionCopyModeVisibilities',
"possible": ""
- },
+ },
{
+ isAdvanced: true,
"name": "detail=tv_channel",
"field": lang["TV Channel"],
- "description": "This monitor will have TV Channel features enabled. You will be able to view it in your TV Channel list.",
+ "description": lang["fieldTextTvChannel"],
"default": "",
"selector": "h_tvc",
"fieldType": "select",
@@ -1093,18 +1149,20 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
"name": "detail=tv_channel_id",
"field": lang["TV Channel ID"],
- "description": "A Custom ID for the Channel.",
+ "description": lang["fieldTextTvChannelId"],
"default": "",
"example": "",
"form-group-class": "h_tvc_input h_tvc_1",
"possible": ""
},
{
+ isAdvanced: true,
"name": "detail=tv_channel_group_title",
"field": lang["TV Channel Group"],
- "description": "A Custom Group for the Channel.",
+ "description": lang["fieldTextTvChannelGroupTitle"],
"default": "",
"example": "",
"form-group-class": "h_tvc_input h_tvc_1",
@@ -1124,7 +1182,7 @@ module.exports = function(s,config,lang){
"name": "detail=stream_timestamp",
"selector":"h_stm",
"field": lang.Enabled,
- "description": "A clock that is burned onto the frames of the video stream.",
+ "description": lang["fieldTextStreamTimestamp"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1143,7 +1201,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=stream_timestamp_font",
"field": "Font Path",
- "description": "Font File to style your timestamp.",
+ "description": lang["fieldTextStreamTimestampFont"],
"default": "/usr/share/fonts/truetype/freefont/FreeSans.ttf",
"example": "",
"form-group-class": "h_stm_input h_stm_1",
@@ -1153,7 +1211,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=stream_timestamp_font_size",
"field": "Font Size",
- "description": "Font size in pt.",
+ "description": lang["fieldTextStreamTimestampFontSize"],
"default": "10",
"example": "",
"form-group-class": "h_stm_input h_stm_1",
@@ -1163,7 +1221,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=stream_timestamp_color",
"field": "Text Color",
- "description": "Timstamp text color.",
+ "description": lang["fieldTextStreamTimestampColor"],
"default": "white",
"example": "",
"form-group-class": "h_stm_input h_stm_1",
@@ -1173,7 +1231,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=stream_timestamp_box_color",
"field": "Text Box Color",
- "description": "Timstamp backdrop color.",
+ "description": lang["fieldTextStreamTimestampBoxColor"],
"default": "0x00000000@1",
"example": "",
"form-group-class": "h_stm_input h_stm_1",
@@ -1183,7 +1241,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=stream_timestamp_x",
"field": "Position X",
- "description": "Horiztonal Position of Timestamp",
+ "description": lang["fieldTextStreamTimestampX"],
"default": "(w-tw)/2",
"example": "",
"form-group-class": "h_stm_input h_stm_1",
@@ -1193,7 +1251,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=stream_timestamp_y",
"field": "Position Y",
- "description": "Vertical Position of Timestamp",
+ "description": lang["fieldTextStreamTimestampY"],
"default": "0",
"example": "",
"form-group-class": "h_stm_input h_stm_1",
@@ -1212,7 +1270,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=stream_watermark",
"field": lang.Enabled,
- "description": "An image that is burned onto the frames of the video stream.",
+ "description": lang["fieldTextStreamWatermark"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1232,7 +1290,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=stream_watermark_location",
"field": lang['Image Location'],
- "description": "Image Location that will be used as Watermark.",
+ "description": lang["fieldTextStreamWatermarkLocation"],
"default": "0",
"example": "/usr/share/watermark.logo",
"form-group-class": "h_wat_input h_wat_1",
@@ -1242,7 +1300,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=stream_watermark_position",
"field": lang['Image Position'],
- "description": "An image that is burned onto the frames of the video stream.",
+ "description": lang["fieldTextStreamWatermarkPosition"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1272,8 +1330,568 @@ module.exports = function(s,config,lang){
"name": "Stream Channels",
"color": "blue",
"id": "monSectionStreamChannels",
+ "section-class": "pb-0",
"emptyDiv": true
},
+ "Substream": {
+ "name": lang['Substream'],
+ "color": "blue",
+ "isSection": true,
+ "id": "monSectionSubstream",
+ "blockquote": lang.substreamText,
+ "blockquoteClass": 'global_tip',
+ "info": [
+ {
+ isAdvanced: true,
+ "name": lang['Connection'],
+ "color": "orange",
+ id: "monSectionSubstreamInput",
+ "blockquote": lang.substreamConnectionText,
+ "blockquoteClass": 'global_tip',
+ isSection: true,
+ isFormGroupGroup: true,
+ "info": [
+ {
+ name:'detail-substream-input=type',
+ field:lang['Input Type'],
+ default:'h264',
+ attribute:'selector="h_i_SUBSTREAM_FIELDS"',
+ "fieldType": "select",
+ type:'selector',
+ possible:[
+ {
+ "name": "H.264 / H.265 / H.265+",
+ "value": "h264",
+ selected: true,
+ },
+ {
+ "name": "JPEG",
+ "value": "jpeg"
+ },
+ {
+ "name": "MJPEG",
+ "value": "mjpeg"
+ },
+ {
+ "name": "HLS (.m3u8)",
+ "value": "hls"
+ },
+ {
+ "name": "MPEG-4 (.mp4 / .ts)",
+ "value": "mp4"
+ },
+ {
+ "name": "Local",
+ "value": "local"
+ },
+ {
+ "name": "Raw",
+ "value": "raw"
+ }
+ ]
+ },
+ {
+ name:'detail-substream-input=stream_flv_type',
+ field:lang['Loop Stream'],
+ class:'h_i_SUBSTREAM_FIELDS_input h_i_SUBSTREAM_FIELDS_mp4',
+ hidden:true,
+ default:'0',
+ "fieldType": "select",
+ type:'selector',
+ possible:[
+ {
+ "name": lang.HTTP,
+ "value": "http",
+ },
+ {
+ "name": lang.Websocket,
+ "value": "ws",
+ }
+ ]
+ },
+ {
+ name:'detail-substream-input=fulladdress',
+ field:lang['Full URL Path'],
+ placeholder:'Example : rtsp://admin:password@123.123.123.123/stream/1',
+ type:'text',
+ },
+ {
+ name:'detail-substream-input=sfps',
+ field:lang['Monitor Capture Rate'],
+ placeholder:'',
+ type:'text',
+ },
+ {
+ name:'detail-substream-input=aduration',
+ field:lang['Analyzation Duration'],
+ placeholder:'Example : 1000000',
+ type:'text',
+ },
+ {
+ name:'detail-substream-input=probesize',
+ field:lang['Probe Size'],
+ placeholder:'Example : 1000000',
+ type:'text',
+ },
+ {
+ name:'detail-substream-input=stream_loop',
+ field:lang['Loop Stream'],
+ class:'h_i_SUBSTREAM_FIELDS_input h_i_SUBSTREAM_FIELDS_mp4 h_i_SUBSTREAM_FIELDS_raw',
+ hidden:true,
+ default:'0',
+ "fieldType": "select",
+ type:'selector',
+ possible:[
+ {
+ "name": lang.No,
+ "value": "0",
+ },
+ {
+ "name": lang.Yes,
+ "value": "1",
+ selected: true,
+ }
+ ]
+ },
+ {
+ name:'detail-substream-input=rtsp_transport',
+ field:lang['RTSP Transport'],
+ class:'h_i_SUBSTREAM_FIELDS_input h_i_SUBSTREAM_FIELDS_h264',
+ default:'',
+ "fieldType": "select",
+ type:'selector',
+ possible:[
+ {
+ "name": lang.Auto,
+ "value": "",
+ "info": lang["fieldTextDetailSubstreamInputRtspTransportAuto"],
+ selected: true,
+ },
+ {
+ "name": "TCP",
+ "value": "tcp",
+ "info": lang["fieldTextDetailSubstreamInputRtspTransportTCP"]
+ },
+ {
+ "name": "UDP",
+ "value": "udp",
+ "info": lang["fieldTextDetailSubstreamInputRtspTransportUDP"]
+ }
+ ]
+ },
+ {
+ name:'detail-substream-input=accelerator',
+ field:lang['Accelerator'],
+ attribute:'selector="h_accel_SUBSTREAM_FIELDS"',
+ default:'0',
+ "fieldType": "select",
+ type:'selector',
+ possible:[
+ {
+ "name": lang.No,
+ "value": "0",
+ selected: true,
+ },
+ {
+ "name": lang.Yes,
+ "value": "1",
+ }
+ ]
+ },
+ {
+ name:'detail-substream-input=hwaccel',
+ field:lang['hwaccel'],
+ class:'h_accel_SUBSTREAM_FIELDS_input h_accel_SUBSTREAM_FIELDS_1',
+ hidden:true,
+ default:'',
+ "fieldType": "select",
+ type:'selector',
+ possible: s.listOfHwAccels
+ },
+ {
+ name:'detail-substream-input=hwaccel_vcodec',
+ field:lang['hwaccel_vcodec'],
+ class:'h_accel_SUBSTREAM_FIELDS_input h_accel_SUBSTREAM_FIELDS_1',
+ hidden:true,
+ default:'auto',
+ "fieldType": "select",
+ type:'selector',
+ possible:[
+ {
+ "name": lang.Auto + '('+lang.Recommended+')',
+ "value": "",
+ selected: true,
+ },
+ {
+ "name": lang.NVIDIA,
+ "optgroup": [
+ {
+ "name": lang.h264_cuvid,
+ "value": "h264_cuvid"
+ },
+ {
+ "name": lang.hevc_cuvid,
+ "value": "hevc_cuvid"
+ },
+ {
+ "name": lang.mjpeg_cuvid,
+ "value": "mjpeg_cuvid"
+ },
+ {
+ "name": lang.mpeg4_cuvid,
+ "value": "mpeg4_cuvid"
+ },
+ ]
+ },
+ {
+ "name": lang["Quick Sync Video"],
+ "optgroup": [
+ {
+ "name": lang.h264_qsv,
+ "value": "h264_qsv"
+ },
+ {
+ "name": lang.hevc_qsv,
+ "value": "hevc_qsv"
+ },
+ {
+ "name": lang.mpeg2_qsv,
+ "value": "mpeg2_qsv"
+ },
+ ]
+ },
+ {
+ "name": lang['Raspberry Pi'],
+ "optgroup": [
+ {
+ "name": lang.h264_mmal,
+ "value": "h264_mmal"
+ },
+ {
+ "name": lang.mpeg2_mmal,
+ "value": "mpeg2_mmal"
+ },
+ {
+ "name": lang["MPEG-4 (Raspberry Pi)"],
+ "value": "mpeg4_mmal"
+ }
+ ]
+ },
+ ]
+ },
+ {
+ name:'detail-substream-input=hwaccel_device',
+ field:lang['hwaccel_device'],
+ class:'h_accel_SUBSTREAM_FIELDS_input h_accel_SUBSTREAM_FIELDS_1',
+ hidden:true,
+ placeholder:'Example : /dev/dri/video0',
+ type:'text',
+ },
+ {
+ name:'detail-substream-input=cust_input',
+ field:lang['Input Flags'],
+ type:'text',
+ },
+ ]
+ },
+ {
+ "name": lang['Output'],
+ "color": "blue",
+ id: "monSectionSubstreamOutput",
+ "blockquote": lang.substreamOutputText,
+ "blockquoteClass": 'global_tip',
+ isSection: true,
+ isFormGroupGroup: true,
+ "info": [
+ {
+ "field": lang["Stream Type"],
+ "name": `detail-substream-output="stream_type"`,
+ "description": lang["fieldTextDetailSubstreamOutputStreamType"],
+ "default": "hls",
+ "selector": "h_st_channel_SUBSTREAM_FIELDS",
+ "fieldType": "select",
+ "attribute": `triggerChange="#monSectionChannelSUBSTREAM_FIELDS [detail-substream-output=stream_vcodec]" triggerChangeIgnore="b64,mjpeg"`,
+ "possible": [
+ {
+ "name": lang.Poseidon,
+ "value": "mp4",
+ },
+ {
+ "name": lang['MJPEG'],
+ "value": "mjpeg",
+ },
+ {
+ "name": lang['FLV'],
+ "value": "flv",
+ },
+ {
+ "name": lang['HLS (includes Audio)'],
+ "value": "hls",
+ selected: true,
+ }
+ ]
+ },
+ {
+ "field": lang['# of Allow MJPEG Clients'],
+ "name": `detail-substream-output="stream_mjpeg_clients"`,
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg",
+ "placeholder": "20",
+ },
+ {
+ "field": lang['Video Codec'],
+ "name": `detail-substream-output="stream_vcodec"`,
+ "description": lang["fieldTextDetailSubstreamOutputStreamVcodec"],
+ "default": "copy",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
+ "fieldType": "select",
+ "selector": "h_hls_v_channel_SUBSTREAM_FIELDS",
+ "possible": [
+ {
+ "name": lang.Auto,
+ "value": "no",
+ "info": lang["fieldTextDetailSubstreamOutputStreamVcodecAuto"],
+ selected: true,
+ },
+ {
+ "name": "libx264",
+ "value": "libx264",
+ "info": lang["fieldTextDetailSubstreamOutputStreamVcodecLibx264"]
+ },
+ {
+ "name": "libx265",
+ "value": "libx265",
+ "info": lang["fieldTextDetailSubstreamOutputStreamVcodecLibx265"]
+ },
+ {
+ "name": lang.copy,
+ "value": "copy",
+ "info": lang["fieldTextDetailSubstreamOutputStreamVcodecCopy"]
+ },
+ {
+ "name": lang['Hardware Accelerated'],
+ "optgroup": [
+ {
+ "name": "H.264 VA-API (Intel HW Accel)",
+ "value": "h264_vaapi"
+ },
+ {
+ "name": "H.265 VA-API (Intel HW Accel)",
+ "value": "hevc_vaapi"
+ },
+ {
+ "name": "H.264 NVENC (NVIDIA HW Accel)",
+ "value": "h264_nvenc"
+ },
+ {
+ "name": "H.265 NVENC (NVIDIA HW Accel)",
+ "value": "hevc_nvenc"
+ },
+ {
+ "name": "H.264 (Quick Sync Video)",
+ "value": "h264_qsv"
+ },
+ {
+ "name": "H.265 (Quick Sync Video)",
+ "value": "hevc_qsv"
+ },
+ {
+ "name": "MPEG2 (Quick Sync Video)",
+ "value": "mpeg2_qsv"
+ },
+ {
+ "name": "H.264 (Quick Sync Video)",
+ "value": "h264_qsv"
+ },
+ {
+ "name": "H.265 (Quick Sync Video)",
+ "value": "hevc_qsv"
+ },
+ {
+ "name": "MPEG2 (Quick Sync Video)",
+ "value": "mpeg2_qsv"
+ },
+ {
+ "name": "H.264 openMAX (Raspberry Pi)",
+ "value": "h264_omx"
+ }
+ ]
+ },
+ ]
+ },
+ {
+ "field": lang["Audio Codec"],
+ "name": `detail-substream-output="stream_acodec"`,
+ "description": lang["fieldTextDetailSubstreamOutputStreamAcodec"],
+ "default": "",
+ "example": "",
+ "fieldType": "select",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
+ "possible": [
+ {
+ "name": lang.Auto,
+ "info": lang["fieldTextDetailSubstreamOutputStreamAcodecAuto"],
+ "value": "",
+ selected: true,
+ },
+ {
+ "name": lang["No Audio"],
+ "info": lang["fieldTextDetailSubstreamOutputStreamAcodecNoAudio"],
+ "value": "no"
+ },
+ {
+ "name": "libvorbis",
+ "info": lang["fieldTextDetailSubstreamOutputStreamAcodecLibvorbis"],
+ "value": "libvorbis"
+ },
+ {
+ "name": "libopus",
+ "info": lang["fieldTextDetailSubstreamOutputStreamAcodecLibopus"],
+ "value": "libopus"
+ },
+ {
+ "name": "libmp3lame",
+ "info": lang["fieldTextDetailSubstreamOutputStreamAcodecLibmp3lame"],
+ "value": "libmp3lame"
+ },
+ {
+ "name": "aac",
+ "info": lang["fieldTextDetailSubstreamOutputStreamAcodecAac"],
+ "value": "aac"
+ },
+ {
+ "name": "ac3",
+ "info": lang["fieldTextDetailSubstreamOutputStreamAcodecAc3"],
+ "value": "ac3"
+ },
+ {
+ "name": "copy",
+ "info": lang["fieldTextDetailSubstreamOutputStreamAcodecCopy"],
+ "value": "copy"
+ }
+ ]
+ },
+ {
+ "name": "detail-substream-output=hls_time",
+ "field": lang["HLS Segment Length"],
+ "description": lang["fieldTextDetailSubstreamOutputHlsTime"],
+ "default": "2",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_hls",
+ },
+ {
+ "name": "detail-substream-output=hls_list_size",
+ "field": lang["HLS List Size"],
+ "description": lang["fieldTextDetailSubstreamOutputHlsListSize"],
+ "default": "2",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_hls",
+ },
+ {
+ "name": "detail-substream-output=preset_stream",
+ "field": lang["HLS Preset"],
+ "description": lang["fieldTextDetailSubstreamOutputPresetStream"],
+ "example": "ultrafast",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_hls",
+ },
+ {
+ "name": "detail-substream-output=stream_quality",
+ "field": lang.Quality,
+ "description": lang["fieldTextDetailSubstreamOutputStreamQuality"],
+ "default": "15",
+ "example": "1",
+ // "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
+ "possible": "1-23"
+ },
+ {
+ "name": "detail-substream-output=stream_v_br",
+ "field": lang["Video Bit Rate"],
+ "placeholder": "",
+ "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
+ },
+ {
+ "name": "detail-substream-output=stream_a_br",
+ "field": lang["Audio Bit Rate"],
+ "placeholder": "128k",
+ "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
+ },
+ {
+ "name": "detail-substream-output=stream_fps",
+ "field": lang['Frame Rate'],
+ "description": lang["fieldTextDetailSubstreamOutputStreamFps"],
+ // "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
+ },
+ {
+ "name": "detail-substream-output=stream_scale_x",
+ "field": lang.Width,
+ "description": lang["fieldTextDetailSubstreamOutputStreamScaleX"],
+ "fieldType": "number",
+ "numberMin": "1",
+ "example": "640",
+ // "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
+ },
+ {
+ "name": "detail-substream-output=stream_scale_y",
+ "field": lang.Height,
+ "description": lang["fieldTextDetailSubstreamOutputStreamScaleY"],
+ "fieldType": "number",
+ "numberMin": "1",
+ "example": "480",
+ // "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
+ },
+ {
+ "name": "detail-substream-output=stream_rotate",
+ "field": lang["Rotate"],
+ "description": lang["fieldTextDetailSubstreamOutputStreamRotate"],
+ "fieldType": "select",
+ // "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
+ "possible": [
+ {
+ "name": lang["No Rotation"],
+ "value": "no"
+ },
+ {
+ "name": lang["180 Degrees"],
+ "value": "2,transpose=2"
+ },
+ {
+ "name": lang["90 Counter Clockwise and Vertical Flip (default)"],
+ "value": "0"
+ },
+ {
+ "name": lang["90 Clockwise"],
+ "value": "1"
+ },
+ {
+ "name": lang["90 Clockwise and Vertical Flip"],
+ "value": "2"
+ },
+ {
+ "name": lang["90 Clockwise and Vertical Flip"],
+ "value": "3"
+ }
+ ]
+ },
+ {
+ isAdvanced: true,
+ "name": "detail-substream-output=svf",
+ "field": lang["Video Filter"],
+ "description": lang["fieldTextDetailSubstreamOutputSvf"],
+ // "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
+ "form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
+ },
+ {
+ "name": "detail-substream-output=cust_stream",
+ "field": lang["Stream Flags"],
+ },
+ ]
+ },
+ ]
+ },
"JPEG API": {
"name": lang['JPEG API'],
"headerTitle": `${lang['JPEG API']} ${lang.Snapshot} (cgi-bin)`,
@@ -1285,7 +1903,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=snap",
"field": lang.Enabled,
- "description": "Get the latest frame in JPEG.",
+ "description": lang["fieldTextSnap"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1332,6 +1950,7 @@ module.exports = function(s,config,lang){
"possible": ""
},
{
+ isAdvanced: true,
hidden: true,
"name": "detail=snap_vf",
"field": lang['Video Filter'],
@@ -1356,7 +1975,7 @@ module.exports = function(s,config,lang){
// {
// "name": "height",
// "field": lang.Height,
- // "description": "Height of the stream image.",
+ // "description": lang["fieldTextRecordScaleY"],
// "default": "640",
// "example": "1280",
// "possible": ""
@@ -1364,7 +1983,7 @@ module.exports = function(s,config,lang){
// {
// "name": "width",
// "field": lang.Width,
- // "description": "Width of the stream image.",
+ // "description": lang["fieldTextRecordScaleX"],
// "default": "480",
// "example": "720",
// "possible": ""
@@ -1372,7 +1991,7 @@ module.exports = function(s,config,lang){
{
"name": "ext",
"field": lang["Record File Type"],
- "description": "The file type for your recorded video file.",
+ "description": lang["fieldTextExt"],
"default": "MP4",
"example": "",
"selector": "h_f",
@@ -1381,19 +2000,19 @@ module.exports = function(s,config,lang){
{
"name": "MP4",
"value": "mp4",
- "info": "This file type is playable is almost all modern web browsers, that includes mobile. The filesize just tends to be larger unless you lower the quality."
+ "info": lang["fieldTextExtMP4"]
},
{
"name": "WebM",
"value": "webm",
- "info": "Small filesize, low client compatibility. Good for uploading to sites like YouTube."
+ "info": lang["fieldTextExtWebM"]
}
]
},
{
"name": "detail=vcodec",
"field": lang["Video Codec"],
- "description": "Video codec for recording.",
+ "description": lang["fieldTextVcodec"],
"default": "copy",
"example": "",
"selector": "h_vc",
@@ -1496,16 +2115,17 @@ module.exports = function(s,config,lang){
{
"name": "detail=crf",
"field": lang.Quality,
- "description": "Low number means higher quality. Higher number means less quality.",
+ "description": lang["fieldTextCrf"],
"default": "15",
"example": "1",
"form-group-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",
"possible": "1-23"
},
{
+ isAdvanced: true,
"name": "detail=preset_record",
"field": lang.Preset,
- "description": "Preset flag for certain video encoders. If you find your camera is crashing every few seconds : try leaving it blank.",
+ "description": lang["fieldTextPresetRecord"],
"default": "",
"example": "ultrafast",
"form-group-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",
@@ -1514,7 +2134,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=acodec",
"field": lang['Audio Codec'],
- "description": "Audio codec for recording.",
+ "description": lang["fieldTextAcodec"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1570,7 +2190,7 @@ module.exports = function(s,config,lang){
{
"name": "fps",
"field": lang["Video Record Rate"],
- "description": "The speed in which frames are recorded to files, Frames Per Second. Be aware there is no default. This can lead to large files. Best to set this camera-side.",
+ "description": lang["fieldTextFps"],
"default": "",
"example": "2",
"form-group-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",
@@ -1597,7 +2217,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=cutoff",
"field": lang['Recording Segment Interval'],
- "description": "In minutes. When to slice off and start a new video file.",
+ "description": lang["fieldTextCutoff"],
"default": "15",
"example": "60",
"attribute": `triggerChange="#add_monitor [detail=vcodec]"`,
@@ -1606,7 +2226,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=rotate",
"field": lang["Rotate"],
- "description": "Change the recording angle of the video stream.",
+ "description": lang["fieldTextRotate"],
"default": "copy",
"example": "",
"selector": "h_vc",
@@ -1640,9 +2260,10 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
"name": "detail=vf",
"field": lang['Record Video Filter'],
- "description": "Place FFMPEG video filters in this box to affect the recording portion. No spaces.",
+ "description": lang["fieldTextVf"],
"default": "",
"example": "",
"form-group-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",
@@ -1663,7 +2284,7 @@ module.exports = function(s,config,lang){
"name": "detail=timestamp",
"selector":"h_rtm",
"field": lang.Enabled,
- "description": "A clock that is burned onto the frames of the recorded video.",
+ "description": lang["fieldTextTimestamp"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1682,7 +2303,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=timestamp_font",
"field": "Font Path",
- "description": "Font File to style your timestamp.",
+ "description": lang["fieldTextTimestampFont"],
"default": "/usr/share/fonts/truetype/freefont/FreeSans.ttf",
"example": "",
"form-group-class": "h_rtm_input h_rtm_1",
@@ -1692,7 +2313,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=timestamp_font_size",
"field": "Font Size",
- "description": "Font size in pt.",
+ "description": lang["fieldTextTimestampFontSize"],
"default": "10",
"example": "",
"form-group-class": "h_rtm_input h_rtm_1",
@@ -1702,7 +2323,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=timestamp_color",
"field": "Text Color",
- "description": "Timstamp text color.",
+ "description": lang["fieldTextTimestampColor"],
"default": "white",
"example": "",
"form-group-class": "h_rtm_input h_rtm_1",
@@ -1712,7 +2333,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=timestamp_box_color",
"field": "Text Box Color",
- "description": "Timstamp backdrop color.",
+ "description": lang["fieldTextTimestampBoxColor"],
"default": "0x00000000@1",
"example": "",
"form-group-class": "h_rtm_input h_rtm_1",
@@ -1722,7 +2343,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=timestamp_x",
"field": "Position X",
- "description": "Horiztonal Position of Timestamp",
+ "description": lang["fieldTextTimestampX"],
"default": "(w-tw)/2",
"example": "",
"form-group-class": "h_rtm_input h_rtm_1",
@@ -1732,7 +2353,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=timestamp_y",
"field": "Position Y",
- "description": "Vertical Position of Timestamp",
+ "description": lang["fieldTextTimestampY"],
"default": "0",
"example": "",
"form-group-class": "h_rtm_input h_rtm_1",
@@ -1752,7 +2373,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=watermark",
"field": lang.Enabled,
- "description": "An image that is burned onto the frames of the recorded video.",
+ "description": lang["fieldTextWatermark"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1772,7 +2393,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=watermark_location",
"field": lang['Image Location'],
- "description": "Image Location that will be used as Watermark.",
+ "description": lang["fieldTextWatermarkLocation"],
"default": "0",
"example": "/usr/share/watermark.logo",
"form-group-class": "h_wat_input h_wat_1",
@@ -1782,7 +2403,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=watermark_position",
"field": lang['Image Position'],
- "description": "An image that is burned onto the frames of the recorded video.",
+ "description": lang["fieldTextWatermarkPosition"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1818,7 +2439,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=record_timelapse",
"field": lang.Enabled,
- "description": "Create a JPEG based timelapse.",
+ "description": lang["fieldTextRecordTimelapse"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1838,7 +2459,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=record_timelapse_mp4",
"field": lang.Enabled,
- "description": "Create an MP4 file at the end of each day for the timelapse.",
+ "description": lang["fieldTextRecordTimelapseMp4"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1857,9 +2478,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=record_timelapse_fps",
"field": lang['Creation Interval'],
- "description": "",
"default": "900",
- "example": "",
"form-group-class": "h_rec_ti_input h_rec_ti_1",
"fieldType": "select",
"possible": [
@@ -1875,6 +2494,10 @@ module.exports = function(s,config,lang){
"name": `.5 ${lang.minutes}`,
"value": "30"
},
+ {
+ "name": `1 ${lang.minute}`,
+ "value": "60"
+ },
{
"name": `5 ${lang.minutes}`,
"value": "300"
@@ -1905,31 +2528,20 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=record_timelapse_scale_x",
"field": lang['Image Width'],
- "description": "",
- "default": "",
- "example": "",
"form-group-class": "h_rec_ti_input h_rec_ti_1",
- "possible": ""
},
{
hidden: true,
"name": "detail=record_timelapse_scale_y",
"field": lang['Image Height'],
- "description": "",
- "default": "",
- "example": "",
"form-group-class": "h_rec_ti_input h_rec_ti_1",
- "possible": ""
},
{
+ isAdvanced: true,
hidden: true,
"name": "detail=record_timelapse_vf",
"field": lang['Video Filter'],
- "description": "",
- "default": "",
- "example": "",
"form-group-class": "h_rec_ti_input h_rec_ti_1",
- "possible": ""
},
]
},
@@ -1945,7 +2557,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=record_timelapse_watermark",
"field": lang.Enabled,
- "description": "An image that is burned onto the frames of the recorded video.",
+ "description": lang["fieldTextRecordTimelapseWatermark"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -1965,7 +2577,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=record_timelapse_watermark_location",
"field": lang['Image Location'],
- "description": "Image Location that will be used as Watermark.",
+ "description": lang["fieldTextRecordTimelapseWatermarkLocation"],
"default": "0",
"example": "/usr/share/watermark.logo",
"form-group-class": "h_wat_timelapse_input h_wat_timelapse_1",
@@ -1975,7 +2587,7 @@ module.exports = function(s,config,lang){
hidden: true,
"name": "detail=record_timelapse_watermark_position",
"field": lang['Image Position'],
- "description": "An image that is burned onto the frames of the recorded video.",
+ "description": lang["fieldTextRecordTimelapseWatermarkPosition"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -2001,106 +2613,10 @@ module.exports = function(s,config,lang){
},
]
},
- "Custom": {
- "name": lang.Custom,
- "color": "navy",
- "isSection": true,
- isAdvanced: true,
- "id": "monSectionCustom",
- "info": [
- {
- "name": "detail=cust_input",
- "field": lang['Input Flags'],
- "description": "Custom Flags that bind to the Input of the FFMPEG process.",
- "default": "",
- "example": "",
- "possible": ""
- },
- // {
- // hidden: true,
- // "name": "detail=cust_rtmp",
- // "field": lang['RTMP Stream Flags'],
- // "description": "Custom Flags that bind to the RTMP stream.",
- // "default": "",
- // "example": "",
- // "form-group-class": "h_rtmp_input h_rtmp_1",
- // "possible": ""
- // },
- {
- "name": "detail=cust_stream",
- "field": lang["Stream Flags"],
- "description": "Custom Flags that bind to the Stream (client side view) of the FFMPEG process.",
- "default": "",
- "example": "",
- "form-group-class": "",
- "possible": ""
- },
- {
- hidden: true,
- "name": "detail=cust_snap",
- "field": "Snapshot Flags",
- "description": "Custom Flags that bind to the Snapshots.",
- "default": "",
- "example": "",
- "form-group-class": "h_sn_input h_sn_1",
- "possible": ""
- },
- {
- hidden: true,
- "name": "detail=cust_record",
- "field": lang["Recording Flags"],
- "description": "Custom Flags that bind to the recording of the FFMPEG process.",
- "default": "",
- "example": "",
- "form-group-class": "h_m_input h_m_record",
- "possible": ""
- },
- {
- hidden: true,
- "name": "detail=cust_detect",
- "field": lang["Detector Flags"],
- "description": "Custom Flags that bind to the stream Detector uses for analyzation.",
- "default": "",
- "example": "",
- "form-group-class": "shinobi-detector",
- "possible": ""
- },
- {
- hidden: true,
- "name": "detail=cust_detect_object",
- "field": lang["Object Detector Flags"],
- "description": "Custom Flags that bind to the stream Detector uses for analyzation.",
- "default": "",
- "example": "",
- "form-group-class": "shinobi-detector",
- "possible": ""
- },
- {
- hidden: true,
- "name": "detail=cust_sip_record",
- "field": lang['Traditional Recording Flags'],
- "description": "Custom Flags that bind to the output that the Event-Based Recordings siphon from.",
- "default": "",
- "example": "",
- "form-group-class": "h_rec_mtd_input h_rec_mtd_sip",
- "possible": ""
- },
- {
- "name": "detail=custom_output",
- "field": "Output Method",
- "description": "Add a custom output like JPEG frames or send data straight to another server.",
- "default": "",
- "example": "",
- "form-group-class": "",
- "possible": ""
- }
- ]
- },
"Detector": {
- "name": lang['Global Detector Settings'],
- "headerTitle": `${lang['Global Detector Settings']} ${lang['Primary Engine']} : Pixel Array ${lang['Not Connected']}${lang['Connected']}`,
+ "name": lang['Detector Settings'],
+ "headerTitle": `${lang['Detector Settings']} ${lang['Primary Engine']} : Pixel Array ${lang['Not Connected']}${lang['Connected']}`,
"color": "orange",
- isAdvanced: true,
"isSection": true,
"input-mapping":"detector",
"id": "monSectionDetector",
@@ -2108,10 +2624,21 @@ module.exports = function(s,config,lang){
"attribute": `triggerChange="#add_monitor [detail=detector_record_method]"`,
"blockquote": `${lang.DetectorText}\n`,
"info": [
+ {
+ "fieldType": "btn",
+ "class": `btn-primary open-region-editor`,
+ "btnContent": ` ${lang['Region Editor']}`,
+ "description": "",
+ "default": "",
+ "example": "",
+ "form-group-class-pre-pre-layer": "h_det_input h_det_1",
+ "form-group-class-pre-layer": "form-group",
+ "possible": ""
+ },
{
"name": "detail=detector",
"field": lang.Enabled,
- "description": "This will add another output in the FFMPEG command for the motion detector.",
+ "description": lang["fieldTextDetector"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -2127,98 +2654,11 @@ module.exports = function(s,config,lang){
}
]
},
- {
- "name": "detail=detector_http_api",
- "field": lang["Allow API Trigger"],
- "description": "Do you want to allow HTTP triggers to this camera?",
- "default": "1",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": `${lang.Always} (${lang.Default})`,
- "value": "1"
- },
- {
- "name": lang[`When Detector is On`],
- "value": "2"
- },
- {
- "name": lang[`When Detector is Off`],
- "value": "3"
- },
- {
- "name": lang.Never,
- "value": "0"
- }
- ]
- },
- {
- hidden: true,
- "name": "detail=detector_send_frames",
- "field": lang["Send Frames"],
- "description": "Push frames to the connected plugin to be analyzed.",
- "default": "0",
- "example": "",
- "selector": "h_det_fra",
- "form-group-class": "h_det_input h_det_1",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- hidden: true,
- "form-group-class": "h_det_input h_det_1",
- "name": "detail=detector_fps",
- "field": lang["Detector Rate"],
- "description": "How many frames a second to send to the motion detector; 2 is the default.",
- "default": "2",
- "example": "",
- "possible": ""
- },
- {
- hidden: true,
- "form-group-class": "h_det_input h_det_1",
- "name": "detail=detector_scale_x",
- "field": lang["Feed-in Image Width"],
- "description": "Width of the image being detected. Smaller sizes take less CPU.",
- "default": "",
- "example": "640",
- "possible": ""
- },
- {
- hidden: true,
- "form-group-class": "h_det_input h_det_1",
- "name": "detail=detector_scale_y",
- "field": lang["Feed-in Image Height"],
- "description": "Height of the image being detected. Smaller sizes take less CPU.",
- "default": "",
- "example": "480",
- "possible": ""
- },
- {
- hidden: true,
- "name": "detail=detector_lock_timeout",
- "field": lang['Allow Next Trigger'],
- "description": "Lockout for when the next trigger is allowed, to avoid overloading the database and receiving clients. Measured in milliseconds.",
- "default": "2000",
- "example": "",
- "form-group-class": "h_det_input h_det_1",
- "possible": ""
- },
{
hidden: true,
"name": "detail=detector_save",
- "field": lang["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.",
+ "field": lang["Save Events"],
+ "description": lang["fieldTextDetectorSave"],
"default": "1",
"example": "",
"form-group-class": "h_det_input h_det_1",
@@ -2235,10 +2675,53 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
+ "name": "detail=use_detector_filters",
+ "field": lang['Event Filters'],
+ "description": lang.fieldTextEventFilters,
+ "default": "0",
+ "example": "",
+ "selector": "h_det_fil",
+ "fieldType": "select",
+ "form-group-class-pre-layer": "h_det_input h_det_1",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ isAdvanced: true,
+ "name": "detail=use_detector_filters_object",
+ "field": lang['Filter for Objects only'],
+ "description": "",
+ "default": "0",
+ "example": "",
+ "fieldType": "select",
+ "form-group-class": "h_det_fil_input h_det_fil_1",
+ "form-group-class-pre-layer": "h_det_input h_det_1",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ isAdvanced: true,
hidden: true,
"name": "detail=detector_record_method",
"field": lang['How to Record'],
- "description": "There are multiple ways to begin recording when an event occurs, like motion. Traditional Recording is the most user-friendly.",
+ "description": lang["fieldTextDetectorRecordMethod"],
"selector": "h_rec_mtd",
"default": "sip",
"example": "",
@@ -2246,11 +2729,11 @@ module.exports = function(s,config,lang){
"fieldType": "select",
"possible": [
{
- "name": lang['Traditional (Watch-Only, Includes Buffer)'],
+ "name": lang['Event-Based Recording (For Watch-Only Mode)'],
"value": "sip"
},
{
- "name": lang['Delete Motionless Videos (Record)'],
+ "name": lang['Delete Motionless Videos (For Record Mode)'],
"value": "del"
}
]
@@ -2277,16 +2760,12 @@ module.exports = function(s,config,lang){
]
},
{
- hidden: true,
- "name": "detail=detector_trigger_record_fps",
- "field": lang['Recording FPS Change on Start'],
- "description": "",
- "placeholder": lang['Blank for No Change'],
- "default": "",
- "example": "",
+ "name": "detail=detector_buffer_seconds_before",
+ "field": lang['Buffer Time from Event'],
+ "description": lang["fieldTextBufferTimeFromEvent"],
+ "default": "5",
"form-group-class": "h_det_input h_det_1",
- "form-group-class-pre-layer": "h_rec_mtd_input h_rec_mtd_hot h_rec_mtd_sip",
- "possible": ""
+ "form-group-class-pre-layer": "h_rec_mtd_input h_rec_mtd_sip",
},
{
hidden: true,
@@ -2299,24 +2778,12 @@ module.exports = function(s,config,lang){
"form-group-class-pre-layer": "h_rec_mtd_input h_rec_mtd_hot h_rec_mtd_sip",
"possible": ""
},
- {
- hidden: true,
- "name": "detail=detector_send_video_length",
- "field": lang["Notification Video Length"],
- "description": "In seconds. The length of the video that gets sent to your Notification service, like Email or Discord.",
- "default": "10",
- "example": "",
- "form-group-class": "h_det_input h_det_1",
- "form-group-class-pre-layer": "h_rec_mtd_input h_rec_mtd_hot h_rec_mtd_sip",
- "possible": ""
- },
{
hidden: true,
"name": "detail=watchdog_reset",
"field": lang["Timeout Reset on Next Event"],
- "description": "If there is an overlap in trigger record should it reset.",
+ "description": lang["fieldTextWatchdogReset"],
"default": "1",
- "example": "",
"fieldType": "select",
"form-group-class": "h_det_input h_det_1",
"form-group-class-pre-layer": "h_rec_mtd_input h_rec_mtd_hot h_rec_mtd_sip",
@@ -2332,12 +2799,11 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
hidden: true,
"name": "detail=detector_delete_motionless_videos",
"field": lang['Delete Motionless Video'],
- "description": "",
"default": "0",
- "example": "",
"form-group-class": "h_det_input h_det_1",
"form-group-class-pre-layer": "h_rec_mtd_input h_rec_mtd_del",
"fieldType": "select",
@@ -2353,6 +2819,122 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
+ "name": "detail=detector_http_api",
+ "field": lang["Allow API Trigger"],
+ "description": lang["fieldTextDetectorHttpApi"],
+ "default": "1",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": `${lang.Always} (${lang.Default})`,
+ "value": "1"
+ },
+ {
+ "name": lang[`When Detector is On`],
+ "value": "2"
+ },
+ {
+ "name": lang[`When Detector is Off`],
+ "value": "3"
+ },
+ {
+ "name": lang.Never,
+ "value": "0"
+ }
+ ]
+ },
+ {
+ isAdvanced: true,
+ hidden: true,
+ "name": "detail=detector_send_frames",
+ "field": lang["Send Frames"],
+ "description": lang["fieldTextDetectorSendFrames"],
+ "default": "0",
+ "example": "",
+ "selector": "h_det_fra",
+ "form-group-class": "h_det_input h_det_1",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ isAdvanced: true,
+ hidden: true,
+ "form-group-class": "h_det_input h_det_1",
+ "name": "detail=detector_fps",
+ "field": lang["Detector Rate"],
+ "description": lang["fieldTextDetectorFps"],
+ "default": "2",
+ "example": "",
+ "possible": ""
+ },
+ {
+ isAdvanced: true,
+ hidden: true,
+ "form-group-class": "h_det_input h_det_1",
+ "name": "detail=detector_scale_x",
+ "field": lang["Feed-in Image Width"],
+ "description": lang["fieldTextDetectorScaleX"],
+ "default": "",
+ "example": "640",
+ "possible": ""
+ },
+ {
+ isAdvanced: true,
+ hidden: true,
+ "form-group-class": "h_det_input h_det_1",
+ "name": "detail=detector_scale_y",
+ "field": lang["Feed-in Image Height"],
+ "description": lang["fieldTextDetectorScaleY"],
+ "default": "",
+ "example": "480",
+ "possible": ""
+ },
+ {
+ isAdvanced: true,
+ hidden: true,
+ "name": "detail=detector_lock_timeout",
+ "field": lang['Allow Next Trigger'],
+ "description": lang["fieldTextDetectorLockTimeout"],
+ "default": "2000",
+ "example": "",
+ "form-group-class": "h_det_input h_det_1",
+ "possible": ""
+ },
+ {
+ isAdvanced: true,
+ "name": "detail=detector_send_video_length",
+ "field": lang["Notification Video Length"],
+ "description": lang["fieldTextDetectorSendVideoLength"],
+ "default": "10"
+ },
+ {
+ isAdvanced: true,
+ "name": "detail=snap_seconds_inward",
+ "field": lang['Delay for Snapshot'],
+ "description": lang[lang["fieldTextSnapSecondsInward"]],
+ "default": "0"
+ },
+ {
+ hidden: true,
+ "name": "detail=cords",
+ },
+ {
+ hidden: true,
+ "name": "detail=detector_filters",
+ },
+ {
+ isAdvanced: true,
hidden: true,
"name": "detail=det_multi_trig",
"field": lang['Trigger Camera Groups'],
@@ -2374,6 +2956,7 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
hidden: true,
id: "monSectionDetectorGroupMulti",
"name": lang['Trigger Camera Groups'],
@@ -2398,6 +2981,490 @@ module.exports = function(s,config,lang){
},
],
},
+ {
+ hidden: true,
+ "name": lang['Motion Detection'],
+ "headerTitle": `${lang['Motion Detection']} ${lang['Primary Engine']} : Pixel Array ${lang['Not Connected']}${lang['Connected']}`,
+ "color": "orange",
+ id: "monSectionDetectorMotion",
+ isSection: true,
+ isFormGroupGroup: true,
+ "section-class": "h_det_input h_det_1",
+ "info": [
+ {
+ "name": "detail=detector_pam",
+ "field": lang["Use Built-In"],
+ "description": lang["fieldTextDetectorPam"],
+ "selector": "h_det_pam",
+ "default": "0",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ // {
+ // "name": "detail=detector_show_matrix",
+ // "field": lang["Show Matrices"],
+ // "description": "Outline which pixels are detected as changed in one matrix.",
+ // "default": "0",
+ // "example": "",
+ // "fieldType": "select",
+ // "form-group-class": "h_det_pam_input h_det_pam_1",
+ // "possible": [
+ // {
+ // "name": lang.No,
+ // "value": "0"
+ // },
+ // {
+ // "name": lang.Yes,
+ // "value": "1"
+ // }
+ // ]
+ // },
+ {
+ "name": "detail=detector_sensitivity",
+ "field": lang['Minimum Change'],
+ "description": "The motion confidence rating must exceed this value to be seen as a trigger. This number correlates directly to the confidence rating returned by the motion detector. This option was previously named \"Indifference\".",
+ "default": "10",
+ "example": "10",
+ "possible": ""
+ },
+ {
+ "name": "detail=detector_max_sensitivity",
+ "field": lang["Maximum Change"],
+ "description": "The motion confidence rating must be lower than this value to be seen as a trigger. Leave blank for no maximum. This option was previously named \"Max Indifference\".",
+ "default": "",
+ "example": "75",
+ "possible": ""
+ },
+ {
+ isAdvanced: true,
+ "name": "detail=detector_threshold",
+ "field": lang["Trigger Threshold"],
+ "description": lang["fieldTextDetectorThreshold"],
+ "default": "1",
+ "example": "3",
+ "possible": "Any non-negative integer."
+ },
+ {
+ isAdvanced: true,
+ "name": "detail=detector_color_threshold",
+ "field": lang["Color Threshold"],
+ "description": lang["fieldTextDetectorColorThreshold"],
+ "default": "9",
+ "example": "9",
+ "possible": "Any non-negative integer."
+ },
+ {
+ isAdvanced: true,
+ "name": "detail=inverse_trigger",
+ "field": lang["Inverse Trigger"],
+ "description": lang["fieldTextInverseTrigger"],
+ "default": "0",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ "name": "detail=detector_frame",
+ "field": lang["Full Frame Detection"],
+ "description": lang["fieldTextDetectorFrame"],
+ "default": "1",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ "name": "detail=detector_motion_tile_mode",
+ "field": lang['Accuracy Mode'],
+ "selector": "h_det_tile_mode",
+ "default": "1",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ "form-group-class": "h_det_tile_mode_input h_det_tile_mode_1",
+ "name": "detail=detector_tile_size",
+ "field": lang["Tile Size"],
+ "description": lang.fieldTextTileSize,
+ "default": "20",
+ },
+ {
+ isAdvanced: true,
+ "name": "detail=detector_noise_filter",
+ "field": lang['Noise Filter'],
+ "description": lang["fieldTextDetectorNoiseFilter"],
+ "default": "1",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ isAdvanced: true,
+ "name": "detail=detector_noise_filter_range",
+ "field": lang["Noise Filter Range"],
+ "description": lang["fieldTextDetectorNoiseFilterRange"],
+ "default": "6",
+ "example": "9",
+ "possible": "Any non-negative integer."
+ },
+ ]
+ },
+ {
+ "name": lang['Object Detection'],
+ "color": "orange",
+ id: "monSectionDetectorObject",
+ headerTitle: `${lang['Object Detection']} ${lang['Not Connected']}${lang['Connected']}`,
+ isFormGroupGroup: true,
+ isSection: true,
+ "input-mapping": "detector_object",
+ "section-class": "h_det_input h_det_1",
+ "info": [
+ {
+ "name": "detail=detector_use_detect_object",
+ "field": lang.Enabled,
+ "description": lang["fieldTextDetectorUseDetectObject"],
+ "default": "0",
+ "example": "",
+ "selector": "h_casc",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ isAdvanced: true,
+ "name": "detail=detector_send_frames_object",
+ "field": lang["Send Frames"],
+ "description": lang["fieldTextDetectorSendFramesObject"],
+ "default": "1",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ isAdvanced: true,
+ hidden: true,
+ "name": "detail=detector_obj_count_in_region",
+ "field": lang["Count Objects only inside Regions"],
+ "description": lang["fieldTextDetectorObjCountInRegion"],
+ "default": "0",
+ "example": "",
+ "form-group-class": "h_det_count_input h_det_count_1",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ "name": "detail=detector_obj_region",
+ "field": lang['Require Object to be in Region'],
+ "description": "",
+ "default": "1",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ isAdvanced: true,
+ "name": "detail=detector_use_motion",
+ "field": lang['Check for Motion First'],
+ "description": "",
+ "default": "1",
+ "example": "",
+ "selector": "h_det_mot_fir",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ hidden: true,
+ "name": "detail=detector_fps_object",
+ "field": lang['Frame Rate'],
+ "description": "",
+ "default": "2",
+ "example": "",
+ "form-group-class": "h_casc_input h_casc_1",
+ "possible": ""
+ },
+ {
+ isAdvanced: true,
+ hidden: true,
+ "name": "detail=detector_scale_x_object",
+ "field": lang['Image Width'],
+ "description": "",
+ "default": "1280",
+ "form-group-class": "h_casc_input h_casc_1",
+ "fieldType": "number",
+ "numberMin": "1",
+ "possible": ""
+ },
+ {
+ isAdvanced: true,
+ hidden: true,
+ "name": "detail=detector_scale_y_object",
+ "field": lang['Image Height'],
+ "description": "",
+ "default": "720",
+ "form-group-class": "h_casc_input h_casc_1",
+ "fieldType": "number",
+ "numberMin": "1",
+ "possible": ""
+ },
+ ]
+ },
+ {
+ isAdvanced: true,
+ hidden: true,
+ "name": lang['Event-Based Recording'],
+ "input-mapping": "detector_sip_buffer",
+ "color": "orange",
+ id: "monSectionDetectorTraditionalRecording",
+ isSection: true,
+ isFormGroupGroup: true,
+ "section-class": "h_det_input h_det_1",
+ "info": [
+ {
+ "name": "detail=detector_buffer_vcodec",
+ "field": lang['HLS Video Encoder'],
+ "description": "",
+ "default": "0",
+ "example": "",
+ "selector": "h_buff",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": "Auto",
+ "value": "auto"
+ },
+ {
+ "name": "libx264",
+ "value": "libx264"
+ },
+ {
+ "name": "H.264 VA-API (Intel HW Accel)",
+ "value": "h264_vaapi"
+ },
+ {
+ "name": "H.265 VA-API (Intel HW Accel)",
+ "value": "hevc_vaapi"
+ },
+ {
+ "name": lang.copy,
+ "value": "copy"
+ }
+ ]
+ },
+ {
+ "name": "detail=detector_buffer_acodec",
+ "field": lang['HLS Audio Encoder'],
+ "description": "",
+ "default": "no",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang['No Audio'],
+ "value": "no"
+ },
+ {
+ "name": "Auto",
+ "value": "auto"
+ },
+ {
+ "name": "aac",
+ "value": "aac"
+ },
+ {
+ "name": "ac3",
+ "value": "ac3"
+ },
+ {
+ "name": "libmp3lame",
+ "value": "libmp3lame"
+ },
+ {
+ "name": lang.copy,
+ "value": "copy"
+ }
+ ]
+ },
+ {
+ "name": "detail=detector_buffer_fps",
+ "field": lang['Frame Rate'],
+ "description": "",
+ "default": "30",
+ "example": "",
+ "form-group-class": "h_buff_input h_buff_libx264 h_buff_h264_vaapi h_buff_hevc_vaapi",
+ "possible": ""
+ },
+ {
+ "name": "detail=event_record_scale_x",
+ "field": lang.Width,
+ "description": lang["fieldTextEventRecordScaleX"],
+ "default": "",
+ "fieldType": "number",
+ "numberMin": "1",
+ "example": "640",
+ "form-group-class": "h_buff_input h_buff_libx264 h_buff_h264_vaapi h_buff_hevc_vaapi",
+ "possible": ""
+ },
+ {
+ "name": "detail=event_record_scale_y",
+ "field": lang.Height,
+ "description": lang["fieldTextEventRecordScaleY"],
+ "default": "",
+ "fieldType": "number",
+ "numberMin": "1",
+ "example": "480",
+ "form-group-class": "h_buff_input h_buff_libx264 h_buff_h264_vaapi h_buff_hevc_vaapi",
+ "possible": ""
+ },
+ {
+ name: 'detail=event_record_aduration',
+ field: lang['Analyzation Duration'],
+ default: '1000',
+ },
+ {
+ name: 'detail=event_record_probesize',
+ field: lang['Probe Size'],
+ default: '32',
+ },
+ {
+ "fieldType": "div",
+ // style: `width:100%;background:#eceaea;border-radius:5px;color:#333;font-family:monospace`,
+ divContent: ``
+ },
+ ]
+ },
+ {
+ hidden: true,
+ "name": lang['Audio Detector'],
+ "color": "orange",
+ id: "monSectionAudioDetector",
+ isSection: true,
+ isFormGroupGroup: true,
+ "section-class": "h_det_input h_det_1",
+ "info": [
+ {
+ "name": "detail=detector_audio",
+ "field": lang.Enabled,
+ "description": lang["fieldTextDetectorAudio"],
+ "default": "0",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ "name": "detail=detector_audio_min_db",
+ "field": lang['Minimum dB'],
+ "description": "",
+ "default": "5",
+ "example": "",
+ "possible": ""
+ },
+ {
+ "name": "detail=detector_audio_max_db",
+ "field": lang['Maximum dB'],
+ "description": "",
+ "default": "",
+ "example": "",
+ "possible": ""
+ }
+ ]
+ },
{
hidden: true,
"name": lang['Webhook'],
@@ -2411,7 +3478,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=detector_webhook",
"field": "Webhook",
- "description": "Send a GET request to a URL with some values from the event.",
+ "description": lang["fieldTextDetectorWebhook"],
"default": "0",
"example": "",
"selector": "h_det_web",
@@ -2431,7 +3498,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=detector_webhook_timeout",
"field": lang['Allow Next Webhook'],
- "description": "This value is a timer to allow the next running of your Webhook. This value is in minutes.",
+ "description": lang["fieldTextDetectorWebhookTimeout"],
"default": "10",
"example": "",
"form-group-class": "h_det_web_input h_det_web_1",
@@ -2508,7 +3575,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=detector_command",
"field": lang['Command'],
- "description": "The command that will run. This is the equivalent of running a shell command from terminal.",
+ "description": lang["fieldTextDetectorCommand"],
"default": "",
"form-group-class": "h_det_com_input h_det_com_1",
"example": "/home/script.sh {{MONITOR_ID}} {{GROUP_KEY}} {{CONFIDENCE}}",
@@ -2518,7 +3585,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=detector_command_timeout",
"field": lang['Allow Next Command'],
- "description": "This value is a timer to allow the next running of your script. This value is in minutes.",
+ "description": lang["fieldTextDetectorCommandTimeout"],
"default": "10",
"example": "",
"form-group-class": "h_det_com_input h_det_com_1",
@@ -2527,246 +3594,6 @@ module.exports = function(s,config,lang){
},
]
},
- {
- "name": "detail=snap_seconds_inward",
- "field": lang['Delay for Snapshot'],
- "description": lang['in seconds'],
- "form-group-class": "h_det_input h_det_1",
- "default": "0",
- },
- {
- "name": "detail=detector_mail",
- "field": lang['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": "0",
- "example": "",
- "selector": "h_det_email",
- "fieldType": "select",
- "form-group-class-pre-layer": "h_det_input h_det_1",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- "name": "detail=detector_mail_timeout",
- "field": lang['Allow Next Email'],
- "description": "The amount of time until a trigger is allowed to send another email with motion details and another image.",
- "default": "10",
- "example": "",
- "form-group-class-pre-layer": "h_det_input h_det_1",
- "possible": ""
- },
- {
- "name": "detail=use_detector_filters",
- "field": lang['Event Filters'],
- "description": "",
- "default": "0",
- "example": "",
- "selector": "h_det_fil",
- "fieldType": "select",
- "form-group-class-pre-layer": "h_det_input h_det_1",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- "name": "detail=use_detector_filters_object",
- "field": lang['Filter for Objects only'],
- "description": "",
- "default": "0",
- "example": "",
- "fieldType": "select",
- "form-group-class": "h_det_fil_input h_det_fil_1",
- "form-group-class-pre-layer": "h_det_input h_det_1",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- hidden: true,
- "name": "detail=cords",
- },
- {
- hidden: true,
- "name": "detail=detector_filters",
- },
- {
- "fieldType": "btn",
- "class": `btn-danger open-region-editor`,
- "btnContent": ` ${lang['Region Editor']}`,
- "description": "",
- "default": "",
- "example": "",
- "form-group-class-pre-pre-layer": "h_det_input h_det_1",
- "form-group-class-pre-layer": "form-group",
- "possible": ""
- },
- {
- hidden: true,
- "name": lang['Motion Detection'],
- "headerTitle": `${lang['Motion Detection']} ${lang['Primary Engine']} : Pixel Array ${lang['Not Connected']}${lang['Connected']}`,
- "color": "orange",
- id: "monSectionDetectorMotion",
- isSection: true,
- isAdvanced: true,
- isFormGroupGroup: true,
- "section-class": "h_det_input h_det_1",
- "info": [
- {
- "name": "detail=detector_pam",
- "field": lang["Use Built-In"],
- "description": "Use Kevin Godell's Motion Detector. This is built into Shinobi and requires no other configuration to activate.",
- "selector": "h_det_pam",
- "default": "0",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- // {
- // "name": "detail=detector_show_matrix",
- // "field": lang["Show Matrices"],
- // "description": "Outline which pixels are detected as changed in one matrix.",
- // "default": "0",
- // "example": "",
- // "fieldType": "select",
- // "form-group-class": "h_det_pam_input h_det_pam_1",
- // "possible": [
- // {
- // "name": lang.No,
- // "value": "0"
- // },
- // {
- // "name": lang.Yes,
- // "value": "1"
- // }
- // ]
- // },
- {
- "name": "detail=detector_sensitivity",
- "field": lang['Minimum Change'],
- "description": "The motion confidence rating must exceed this value to be seen as a trigger. This number correlates directly to the confidence rating returned by the motion detector. This option was previously named \"Indifference\".",
- "default": "10",
- "example": "10",
- "possible": ""
- },
- {
- "name": "detail=detector_max_sensitivity",
- "field": lang["Maximum Change"],
- "description": "The motion confidence rating must be lower than this value to be seen as a trigger. Leave blank for no maximum. This option was previously named \"Max Indifference\".",
- "default": "",
- "example": "75",
- "possible": ""
- },
- {
- "name": "detail=detector_threshold",
- "field": lang["Trigger Threshold"],
- "description": "Minimum number of detections to fire a motion event. Detections must be within the detector the threshold divided by detector fps seconds. For example, if detector fps is 2 and trigger threshold is 3, then three detections must occur within 1.5 seconds to trigger a motion event. This threshold is per detection region.",
- "default": "1",
- "example": "3",
- "possible": "Any non-negative integer."
- },
- {
- "name": "detail=detector_color_threshold",
- "field": lang["Color Threshold"],
- "description": "The amount of difference allowed in a pixel before it is considered motion.",
- "default": "9",
- "example": "9",
- "possible": "Any non-negative integer."
- },
- {
- "name": "detail=inverse_trigger",
- "field": lang["Inverse Trigger"],
- "description": "To trigger outside specified regions. Will not trigger with Full Frame Detection enabled.",
- "default": "0",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- "name": "detail=detector_frame",
- "field": lang["Full Frame Detection"],
- "description": "This will read the entire frame for pixel differences. This is the same as creating a region that covers the entire screen.",
- "default": "1",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- "name": "detail=detector_noise_filter",
- "field": lang['Noise Filter'],
- "description": "Attempt to filter grain or repeated motion at a particular indifference.",
- "default": "1",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- "name": "detail=detector_noise_filter_range",
- "field": lang["Noise Filter Range"],
- "description": "The amount of difference allowed in a pixel before it is considered motion.",
- "default": "6",
- "example": "9",
- "possible": "Any non-negative integer."
- },
- ]
- },
{
hidden: true,
"name": lang['\"No Motion"\ Detector'],
@@ -2780,7 +3607,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=detector_notrigger",
"field": lang.Enabled,
- "description": "Check if motion has occured on an interval. If motion has occurred the check will be reset.",
+ "description": lang["fieldTextDetectorNotrigger"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -2796,27 +3623,17 @@ module.exports = function(s,config,lang){
]
},
{
- "name": "detail=detector_notrigger_mail",
- "field": lang['Email'],
- "description": "If motion has not been detected after the timeout period you will recieve an email.",
- "default": "0",
+ "name": "detail=detector_notrigger_timeout",
+ "field": lang.Timeout,
+ "description": lang["fieldTextDetectorNotriggerTimeout"],
+ "default": "10",
"example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
+ "possible": ""
},
{
"name": "detail=detector_notrigger_discord",
"field": lang['No Trigger'],
- "description": "If motion has not been detected after the timeout period you will recieve an Discord notification.",
+ "description": lang["fieldTextDetectorNotriggerDiscord"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -2831,18 +3648,10 @@ module.exports = function(s,config,lang){
}
]
},
- {
- "name": "detail=detector_notrigger_timeout",
- "field": lang.Timeout,
- "description": "Timeout is calculated in minutes.",
- "default": "10",
- "example": "",
- "possible": ""
- },
{
"name": "detail=detector_notrigger_webhook",
"field": "Webhook",
- "description": "Send a GET request to a URL with some values from the event.",
+ "description": lang["fieldTextDetectorNotriggerWebhook"],
"default": "0",
"example": "",
"selector": "h_det_web_notrig",
@@ -2913,7 +3722,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=detector_notrigger_command",
"field": lang['Command'],
- "description": "The command that will run. This is the equivalent of running a shell command from terminal.",
+ "description": lang["fieldTextDetectorNotriggerCommand"],
"default": "",
"form-group-class": "h_det_com_notrig_input h_det_com_notrig_1",
"example": "/home/script.sh {{MONITOR_ID}} {{GROUP_KEY}} {{CONFIDENCE}}",
@@ -2922,7 +3731,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=detector_notrigger_command_timeout",
"field": lang['Allow Next Command'],
- "description": "This value is a timer to allow the next running of your script. This value is in minutes.",
+ "description": lang["fieldTextDetectorNotriggerCommandTimeout"],
"default": "10",
"example": "",
"form-group-class": "h_det_com_notrig_input h_det_com_notrig_1",
@@ -2930,413 +3739,6 @@ module.exports = function(s,config,lang){
},
]
},
- {
- hidden: true,
- "name": lang['Audio Detector'],
- "color": "orange",
- id: "monSectionAudioDetector",
- isSection: true,
- isAdvanced: true,
- isFormGroupGroup: true,
- "section-class": "h_det_input h_det_1",
- "info": [
- {
- "name": "detail=detector_audio",
- "field": lang.Enabled,
- "description": "Check if Audio has occured at a certiain decible. Decible reading may not be accurate to real-world measurement.",
- "default": "0",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- "name": "detail=detector_audio_min_db",
- "field": lang['Minimum dB'],
- "description": "",
- "default": "5",
- "example": "",
- "possible": ""
- },
- {
- "name": "detail=detector_audio_max_db",
- "field": lang['Maximum dB'],
- "description": "",
- "default": "",
- "example": "",
- "possible": ""
- }
- ]
- },
- {
- "name": lang['Object Detection'],
- "color": "orange",
- id: "monSectionDetectorObject",
- headerTitle: `${lang['Object Detection']} ${lang['Not Connected']}${lang['Connected']}`,
- isFormGroupGroup: true,
- isSection: true,
- "input-mapping": "detector_object",
- "section-class": "h_det_input h_det_1",
- "info": [
- {
- "name": "detail=detector_use_detect_object",
- "field": lang.Enabled,
- "description": "Create frames for sending to any connected Plugin.",
- "default": "0",
- "example": "",
- "selector": "h_casc",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- "name": "detail=detector_send_frames_object",
- "field": lang["Send Frames"],
- "description": "Push frames to the connected plugin to be analyzed.",
- "default": "0",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- hidden: true,
- "name": "detail=detector_obj_count_in_region",
- "field": lang["Count Objects only inside Regions"],
- "description": "Count Objects only inside Regions.",
- "default": "0",
- "example": "",
- "form-group-class": "h_det_count_input h_det_count_1",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- "name": "detail=detector_obj_region",
- "field": lang['Require Object to be in Region'],
- "description": "",
- "default": "0",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- "name": "detail=detector_use_motion",
- "field": lang['Check for Motion First'],
- "description": "",
- "default": "0",
- "example": "",
- "selector": "h_det_mot_fir",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- hidden: true,
- "name": "detail=detector_fps_object",
- "field": lang['Frame Rate'],
- "description": "",
- "default": "2",
- "example": "",
- "form-group-class": "h_casc_input h_casc_1",
- "possible": ""
- },
- {
- hidden: true,
- "name": "detail=detector_scale_x_object",
- "field": lang['Image Width'],
- "description": "",
- "default": "",
- "example": "",
- "form-group-class": "h_casc_input h_casc_1",
- "fieldType": "number",
- "numberMin": "1",
- "possible": ""
- },
- {
- hidden: true,
- "name": "detail=detector_scale_y_object",
- "field": lang['Image Height'],
- "description": "",
- "default": "",
- "example": "",
- "form-group-class": "h_casc_input h_casc_1",
- "fieldType": "number",
- "numberMin": "1",
- "possible": ""
- },
- ]
- },
- {
- hidden: true,
- "name": lang['License Plate Detector'],
- "color": "orange",
- id: "monSectionLisencePlateDetector",
- headerTitle: `${lang['Object Detection']} ${lang['Plugin']} : ${lang['Not Connected']}${lang['Connected']}`,
- isSection: true,
- isAdvanced: true,
- isFormGroupGroup: true,
- "section-pre-pre-class": "h_det_input h_det_1",
- "section-pre-class": "h_casc_input h_casc_1",
- "section-class": "shinobi-detector-opencv shinobi-detector-openalpr shinobi-detector_plug",
- "info": [
- {
- "name": "detail=detector_lisence_plate",
- "field": lang.Enabled,
- "description": "Enable License Plate Recognition. OpenALPR plugin has this always enabled.",
- "default": "0",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
- {
- "name": "detail=detector_lisence_plate_country",
- "field": lang['Country of Plates'],
- "description": "Choose the type of plates to recognize. Only US and EU are supported at this time.",
- "default": "0",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": "US",
- "value": "us"
- },
- {
- "name": "EU",
- "value": "eu"
- }
- ]
- },
- ]
- },
- {
- hidden: true,
- "name": lang['OpenCV Cascades'],
- "color": "orange",
- id: "monSectionOpenCVCascades",
- headerTitle: `${lang['OpenCV Cascades']} `,
- blockquote: lang.opencvCascadesText,
- isSection: true,
- isAdvanced: true,
- isFormGroupGroup: true,
- "section-pre-pre-class": "h_det_input h_det_1",
- "section-pre-class": "h_casc_input h_casc_1",
- "section-class": "shinobi-detector-opencv shinobi-detector-openalpr shinobi-detector_plug",
- "info": [
- {
- "fieldType": "div",
- id: "detector_cascade_list",
- style: "max-height: 300px;overflow: auto;"
- }
- ]
- },
- {
- hidden: true,
- "name": lang['Traditional Recording'],
- "input-mapping": "detector_sip_buffer",
- "color": "orange",
- id: "monSectionDetectorTraditionalRecording",
- isSection: true,
- isAdvanced: true,
- isFormGroupGroup: true,
- "section-class": "h_det_input h_det_1",
- "info": [
- {
- "name": "detail=detector_buffer_vcodec",
- "field": lang['HLS Video Encoder'],
- "description": "",
- "default": "0",
- "example": "",
- "selector": "h_buff",
- "fieldType": "select",
- "possible": [
- {
- "name": "Auto",
- "value": "auto"
- },
- {
- "name": "libx264",
- "value": "libx264"
- },
- {
- "name": "H.264 VA-API (Intel HW Accel)",
- "value": "h264_vaapi"
- },
- {
- "name": "H.265 VA-API (Intel HW Accel)",
- "value": "hevc_vaapi"
- },
- {
- "name": lang.copy,
- "value": "copy"
- }
- ]
- },
- {
- "name": "detail=detector_buffer_acodec",
- "field": lang['HLS Audio Encoder'],
- "description": "",
- "default": "0",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang['No Audio'],
- "value": "no"
- },
- {
- "name": "Auto",
- "value": "auto"
- },
- {
- "name": "aac",
- "value": "aac"
- },
- {
- "name": "ac3",
- "value": "ac3"
- },
- {
- "name": "libmp3lame",
- "value": "libmp3lame"
- },
- {
- "name": lang.copy,
- "value": "copy"
- }
- ]
- },
- {
- "name": "detail=detector_buffer_fps",
- "field": lang['Frame Rate'],
- "description": "",
- "default": "30",
- "example": "",
- "form-group-class": "h_buff_input h_buff_libx264 h_buff_h264_vaapi h_buff_hevc_vaapi",
- "possible": ""
- },
- {
- "name": "detail=event_record_scale_x",
- "field": lang.Width,
- "description": "Width of the Event-based Recording image that is output after processing.",
- "default": "",
- "fieldType": "number",
- "numberMin": "1",
- "example": "640",
- "form-group-class": "h_buff_input h_buff_libx264 h_buff_h264_vaapi h_buff_hevc_vaapi",
- "possible": ""
- },
- {
- "name": "detail=event_record_scale_y",
- "field": lang.Height,
- "description": "Height of the Event-based Recording image that is output after processing.",
- "default": "",
- "fieldType": "number",
- "numberMin": "1",
- "example": "480",
- "form-group-class": "h_buff_input h_buff_libx264 h_buff_h264_vaapi h_buff_hevc_vaapi",
- "possible": ""
- },
- {
- "name": "detail=detector_buffer_hls_time",
- "field": lang['HLS Segment Length'],
- "description": "How long each video segment should be, in seconds. Each segment will be drawn by the client through an m3u8 file. Shorter segments take less space.",
- "default": "2",
- "example": "",
- "possible": ""
- },
- {
- "name": "detail=detector_buffer_hls_list_size",
- "field": lang['HLS List Size'],
- "description": "The number of segments maximum before deleting old segments automatically.",
- "default": "10",
- "example": "",
- "possible": ""
- },
- {
- "name": "detail=detector_buffer_start_number",
- "field": lang['HLS Start Number'],
- "description": "",
- "default": "0",
- "example": "",
- "form-group-class": "h_buff_input h_buff_libx264 h_buff_h264_vaapi h_buff_hevc_vaapi",
- "possible": ""
- },
- {
- "name": "detail=detector_buffer_live_start_index",
- "field": lang['HLS Live Start Index'],
- "description": "",
- "default": "-3",
- "example": "",
- "form-group-class": "h_buff_input h_buff_libx264 h_buff_h264_vaapi h_buff_hevc_vaapi",
- "possible": ""
- },
- {
- "field": lang['Buffer Preview'],
- id: "monEditBufferPreview",
- "fieldType": "div",
- "style": "width:100%;height:300px;background:#eceaea;border-radius:5px;color:#333;font-family:monospace"
- },
- ]
- },
]
},
"Control": {
@@ -3365,6 +3767,7 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
"name": "detail=control_base_url",
"field": lang['Custom Base URL'],
"description": "",
@@ -3401,6 +3804,7 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
"name": "detail=control_digest_auth",
"field": lang['Digest Authentication'],
"description": "",
@@ -3434,8 +3838,12 @@ module.exports = function(s,config,lang){
"value": "0"
},
{
- "name": lang.Yes,
+ "name": lang.Timed,
"value": "1"
+ },
+ {
+ "name": lang['On Release'],
+ "value": "2"
}
]
},
@@ -3454,13 +3862,13 @@ module.exports = function(s,config,lang){
"description": "",
"default": "0.1",
"example": "",
- "form-group-class": "h_cs_input h_cs_1",
+ "form-group-class": "h_control_call_input h_control_call_ONVIF",
"possible": ""
},
{
"name": "detail=detector_ptz_follow",
"field": lang['PTZ Tracking'],
- "description": "Follow the largest detected object with PTZ? Requires an Object Detector running or matrices provided with events.",
+ "description": lang["fieldTextDetectorPtzFollow"],
"default": "0",
"example": "",
"selector": "h_det_tracking",
@@ -3477,6 +3885,7 @@ module.exports = function(s,config,lang){
]
},
{
+ isAdvanced: true,
"name": "detail=detector_ptz_follow_target",
"field": lang['PTZ Tracking Target'],
"description": "",
@@ -3485,25 +3894,25 @@ module.exports = function(s,config,lang){
"form-group-class": "h_det_tracking_input h_det_tracking_1",
"possible": ""
},
- {
- "name": "detail=detector_obj_count",
- "field": lang["Count Objects"],
- "description": "Count detected objects.",
- "default": "0",
- "example": "",
- "selector": "h_det_count",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
+ // {
+ // "name": "detail=detector_obj_count",
+ // "field": lang["Count Objects"],
+ // "description": lang["fieldTextDetectorObjCount"],
+ // "default": "0",
+ // "example": "",
+ // "selector": "h_det_count",
+ // "fieldType": "select",
+ // "possible": [
+ // {
+ // "name": lang.No,
+ // "value": "0"
+ // },
+ // {
+ // "name": lang.Yes,
+ // "value": "1"
+ // }
+ // ]
+ // },
{
"name": "detail=control_url_center",
"field": lang['Center'],
@@ -3528,7 +3937,7 @@ module.exports = function(s,config,lang){
"description": "",
"default": "/",
"example": "",
- "form-group-class-pre-layer": "h_cs_input h_cs_1",
+ "form-group-class-pre-layer": "h_cs_input h_cs_1 h_cs_2",
"form-group-class": "h_control_call_input h_control_call_GET h_control_call_PUT h_control_call_POST",
"possible": ""
},
@@ -3547,7 +3956,7 @@ module.exports = function(s,config,lang){
"description": "",
"default": "/",
"example": "",
- "form-group-class-pre-layer": "h_cs_input h_cs_1",
+ "form-group-class-pre-layer": "h_cs_input h_cs_1 h_cs_2",
"form-group-class": "h_control_call_input h_control_call_GET h_control_call_PUT h_control_call_POST",
"possible": ""
},
@@ -3566,7 +3975,7 @@ module.exports = function(s,config,lang){
"description": "",
"default": "/",
"example": "",
- "form-group-class-pre-layer": "h_cs_input h_cs_1",
+ "form-group-class-pre-layer": "h_cs_input h_cs_1 h_cs_2",
"form-group-class": "h_control_call_input h_control_call_GET h_control_call_PUT h_control_call_POST",
"possible": ""
},
@@ -3585,7 +3994,7 @@ module.exports = function(s,config,lang){
"description": "",
"default": "/",
"example": "",
- "form-group-class-pre-layer": "h_cs_input h_cs_1",
+ "form-group-class-pre-layer": "h_cs_input h_cs_1 h_cs_2",
"form-group-class": "h_control_call_input h_control_call_GET h_control_call_PUT h_control_call_POST",
"possible": ""
},
@@ -3622,7 +4031,7 @@ module.exports = function(s,config,lang){
"description": "",
"default": "/",
"example": "",
- "form-group-class-pre-layer": "h_cs_input h_cs_1",
+ "form-group-class-pre-layer": "h_cs_input h_cs_1 h_cs_2",
"form-group-class": "h_control_call_input h_control_call_GET h_control_call_PUT h_control_call_POST",
"possible": ""
},
@@ -3641,14 +4050,15 @@ module.exports = function(s,config,lang){
"description": "",
"default": "/",
"example": "",
- "form-group-class-pre-layer": "h_cs_input h_cs_1",
+ "form-group-class-pre-layer": "h_cs_input h_cs_1 h_cs_2",
"form-group-class": "h_control_call_input h_control_call_GET h_control_call_PUT h_control_call_POST",
"possible": ""
},
{
+ isAdvanced: true,
"name": "detail=control_invert_y",
"field": lang["Invert Y-Axis"],
- "description": "For When your camera is mounted upside down or uses inverted vertical controls.",
+ "description": lang["fieldTextControlInvertY"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -3674,7 +4084,7 @@ module.exports = function(s,config,lang){
isAdvanced: true,
"info": [
{
- "fieldType": 'ul',
+ "fieldType": 'div',
"id": "monitor_groups",
"class": "mdl-list"
},
@@ -3689,10 +4099,11 @@ module.exports = function(s,config,lang){
"name": lang['Copy Settings'],
"color": "orange",
isSection: true,
+ "box-wrapper-class": "row",
"info": [
{
"id": "copy_settings",
- "field": lang['Copy to Settings'],
+ "field": lang['Copy to Selected Monitor(s)'],
"description": "",
"default": "0",
"example": "",
@@ -3717,6 +4128,7 @@ module.exports = function(s,config,lang){
"fieldType": "select",
"attribute": `copy="field=mode"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3729,13 +4141,32 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy Stream Channels'],
+ "field": lang['Compress Completed Videos'],
+ "default": "0",
+ "fieldType": "select",
+ "attribute": `copy="field=detail=auto_compress_videos"`,
+ "form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ "field": lang['Stream Channels'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="field=detail=stream_channels"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3748,13 +4179,14 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy Connection Settings'],
+ "field": lang['Connection'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="#monSectionConnection"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3767,13 +4199,14 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy Input Settings'],
+ "field": lang['Input'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="#monSectionInput"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3786,13 +4219,14 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy Timelapse Settings'],
+ "field": lang['Timelapse'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="#monSectionTimelapse"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3805,13 +4239,14 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy Stream Settings'],
+ "field": lang['Stream'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="#monSectionStream,#monSectionStreamTimestamp,#monSectionStreamWatermark"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3824,13 +4259,14 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy JPEG API Settings'],
+ "field": lang['JPEG API'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="#monSectionJPEGAPI"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3843,13 +4279,14 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy Recording Settings'],
+ "field": lang['Recording'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="#monSectionRecording,#monSectionRecordingTimestamp,#monSectionRecordingWatermark"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3862,13 +4299,14 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy Detector Settings'],
+ "field": lang['Detector Settings'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="#monSectionDetector,#monSectionDetectorBuffer,#monSectionLisencePlateDetector,#monSectionNoMotionDetector"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3881,13 +4319,34 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy Custom Settings'],
+ "field": lang['Control'],
+ "description": "",
+ "default": "0",
+ "example": "",
+ "fieldType": "select",
+ "attribute": `copy="#monSectionControl"`,
+ "form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ "field": lang['Custom'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="#monSectionCustom"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3900,13 +4359,14 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy Group Settings'],
+ "field": lang['Grouping'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="#monSectionGrouping"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3919,13 +4379,14 @@ module.exports = function(s,config,lang){
]
},
{
- "field": lang['Copy Logging Settings'],
+ "field": lang['Logging'],
"description": "",
"default": "0",
"example": "",
"fieldType": "select",
"attribute": `copy="#monSectionLogging"`,
"form-group-class": "h_copy_settings_input h_copy_settings_1",
+ "form-group-class-pre-layer": "col-md-6",
"possible": [
{
"name": lang.No,
@@ -3965,32 +4426,13 @@ module.exports = function(s,config,lang){
isAdvanced: true,
"isSection": true,
"id": "monSectionNotifications",
- "selector": "h_det",
- "attribute": `triggerChange="#add_monitor [detail=detector_record_method]"`,
- "blockquote": `${lang.DetectorText}\n`,
"info": [
{
"name": lang.Methods,
"color": "blue",
isFormGroupGroup: true,
"info": [
- {
- "name": "detail=notify_email",
- "field": lang.Email,
- "default": "0",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
+
],
},
{
@@ -4026,20 +4468,114 @@ module.exports = function(s,config,lang){
"value": "1"
}
]
- },
+ }
]
},
+ "Custom": {
+ "name": lang.Custom,
+ "color": "navy",
+ "isSection": true,
+ isAdvanced: true,
+ "id": "monSectionCustom",
+ "info": [
+ {
+ "name": "detail=cust_input",
+ "field": lang['Input Flags'],
+ "description": lang["fieldTextCustInput"],
+ "default": "",
+ "example": "",
+ "possible": ""
+ },
+ // {
+ // hidden: true,
+ // "name": "detail=cust_rtmp",
+ // "field": lang['RTMP Stream Flags'],
+ // "description": "Custom Flags that bind to the RTMP stream.",
+ // "default": "",
+ // "example": "",
+ // "form-group-class": "h_rtmp_input h_rtmp_1",
+ // "possible": ""
+ // },
+ {
+ "name": "detail=cust_stream",
+ "field": lang["Stream Flags"],
+ "description": lang["fieldTextCustStream"],
+ "default": "",
+ "example": "",
+ "form-group-class": "",
+ "possible": ""
+ },
+ {
+ hidden: true,
+ "name": "detail=cust_snap",
+ "field": "Snapshot Flags",
+ "description": lang["fieldTextCustSnap"],
+ "default": "",
+ "example": "",
+ "form-group-class": "h_sn_input h_sn_1",
+ "possible": ""
+ },
+ {
+ hidden: true,
+ "name": "detail=cust_record",
+ "field": lang["Recording Flags"],
+ "description": lang["fieldTextCustRecord"],
+ "default": "",
+ "example": "",
+ "form-group-class": "h_m_input h_m_record",
+ "possible": ""
+ },
+ {
+ hidden: true,
+ "name": "detail=cust_detect",
+ "field": lang["Detector Flags"],
+ "description": lang["fieldTextCustDetect"],
+ "default": "",
+ "example": "",
+ "form-group-class": "shinobi-detector",
+ "possible": ""
+ },
+ {
+ hidden: true,
+ "name": "detail=cust_detect_object",
+ "field": lang["Object Detector Flags"],
+ "description": lang["fieldTextCustDetectObject"],
+ "default": "",
+ "example": "",
+ "form-group-class": "shinobi-detector",
+ "possible": ""
+ },
+ {
+ hidden: true,
+ "name": "detail=cust_sip_record",
+ "field": lang['Event-Based Recording Flags'],
+ "description": lang["fieldTextCustSipRecord"],
+ "default": "",
+ "example": "",
+ "form-group-class": "h_rec_mtd_input h_rec_mtd_sip",
+ "possible": ""
+ },
+ {
+ "name": "detail=custom_output",
+ "field": "Output Method",
+ "description": lang["fieldTextCustomOutput"],
+ "default": "",
+ "example": "",
+ "form-group-class": "",
+ "possible": ""
+ }
+ ]
+ },
"Logging": {
"name": lang.Logging,
"color": "green",
id: "monSectionLogging",
- isAdvanced: true,
isSection: true,
"info": [
{
"name": "detail=loglevel",
"field": lang['Log Level'],
- "description": "The amount of data to provide while doing the job.",
+ "description": lang["fieldTextLoglevel"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -4047,29 +4583,29 @@ module.exports = function(s,config,lang){
{
"name": lang.Silent,
"value": "quiet",
- "info": "None. This will silence all logging."
+ "info": lang["fieldTextLoglevelSilent"]
},
{
"name": lang.Fatal,
"value": "fatal",
- "info": "Display only fatal errors."
+ "info": lang["fieldTextLoglevelFatal"]
},
{
"name": lang['on Error'],
"value": "error",
- "info": "Display all important errors. Note : this doesn't always show important information."
+ "info": lang["fieldTextLoglevelOnError"]
},
{
"name": lang['All Warnings'],
"value": "warning",
- "info": "Display all warnings. Use this if you can't find out what's wrong with your camera."
+ "info": lang["fieldTextLoglevelAllWarnings"]
}
]
},
{
"name": "detail=sqllog",
"field": lang["Save Log in SQL"],
- "description": "Use this with caution as FFMPEG likes to throw up superfluous data at times which can lead to a lot of database rows.",
+ "description": lang["fieldTextSqllog"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -4077,12 +4613,12 @@ module.exports = function(s,config,lang){
{
"name": lang.No,
"value": "0",
- "info": "No is the default."
+ "info": lang["fieldTextSqllogNo"]
},
{
"name": lang.Yes,
"value": "1",
- "info": "Do this if you are having recurring issues only."
+ "info": lang["fieldTextSqllogYes"]
}
]
},
@@ -4197,7 +4733,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=factorAuth",
"field": lang.Enabled,
- "description": "Enable a secondary requirement for login through one of the enabled methods.",
+ "description": lang["fieldTextFactorAuth"],
"default": "0",
"example": "",
"fieldType": "select",
@@ -4212,24 +4748,6 @@ module.exports = function(s,config,lang){
}
]
},
- {
- "name": "detail=factor_mail",
- "field": lang.Email,
- "description": "Send 2-Factor Authentication codes to the email address of the account.",
- "default": "1",
- "example": "",
- "fieldType": "select",
- "possible": [
- {
- "name": lang.No,
- "value": "0"
- },
- {
- "name": lang.Yes,
- "value": "1"
- }
- ]
- },
]
},
"Profile": {
@@ -4239,7 +4757,7 @@ module.exports = function(s,config,lang){
{
"name": "mail",
"field": lang.Email,
- "description": "The login for accounts. The main account holder's email address will get notifications.",
+ "description": lang["fieldTextMail"],
"default": "",
"example": "ccio@m03.ca",
"possible": ""
@@ -4248,7 +4766,7 @@ module.exports = function(s,config,lang){
"name": "pass",
"field": lang.Password,
"fieldType": "password",
- "description": "Leave blank to keep the same password during settings modification.",
+ "description": lang["fieldTextPass"],
"fieldType": "password",
"default": "",
"example": "",
@@ -4258,7 +4776,7 @@ module.exports = function(s,config,lang){
"name": "password_again",
"field": lang["Password Again"],
"fieldType": "password",
- "description": "Must match Password field if you desire to change it.",
+ "description": lang["fieldTextPasswordAgain"],
"default": "",
"example": "",
"possible": ""
@@ -4266,7 +4784,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=size",
"field": lang["Max Storage Amount"],
- "description": "The amount of disk space Shinobi will allow to be consumed before purging. This value is read in megabytes.",
+ "description": lang["fieldTextSize"],
"default": "10000",
"example": "600000",
"possible": "Up to 95% of your maximum storage space if only one master account exists.",
@@ -4276,21 +4794,21 @@ module.exports = function(s,config,lang){
{
"name": "detail=size_video_percent",
"field": lang["Video Share"],
- "description": "Percent of Max Storage Amount the videos can record to.",
+ "description": lang["fieldTextSizeVideoPercent"],
"default": "90",
"notForSubAccount": true,
},
{
"name": "detail=size_timelapse_percent",
"field": lang["Timelapse Frames Share"],
- "description": "Percent of Max Storage Amount the timelapse frames can record to.",
+ "description": lang["fieldTextSizeTimelapsePercent"],
"default": "5",
"notForSubAccount": true,
},
{
"name": "detail=size_filebin_percent",
"field": lang["FileBin Share"],
- "description": "Percent of Max Storage Amount the FileBin archive can use.",
+ "description": lang["fieldTextSizeFilebinPercent"],
"default": "5",
"notForSubAccount": true,
},
@@ -4307,7 +4825,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=days",
"field": lang["Number of Days to keep"] + ' ' + lang['Videos'],
- "description": "The number of days to keep videos before purging.",
+ "description": lang["fieldTextDays"],
"default": "5",
"example": "30",
"possible": "",
@@ -4317,17 +4835,25 @@ module.exports = function(s,config,lang){
{
"name": "detail=event_days",
"field": lang["Number of Days to keep"] + ' ' + lang['Events'],
- "description": "The number of days to keep events before purging.",
+ "description": lang["fieldTextEventDays"],
"default": "10",
"example": "30",
"possible": "",
"notForSubAccount": true,
"evaluation": "details.edit_event_days !== '0'"
},
+ {
+ "name": "detail=timelapseFrames_days",
+ "field": lang["Number of Days to keep"] + ' ' + lang['Timelapse'],
+ "description": lang["fieldTextEventDays"],
+ "default": "60",
+ "notForSubAccount": true,
+ "evaluation": "details.edit_timelapseFrames_days !== '0'"
+ },
{
"name": "detail=log_days",
"field": lang["Number of Days to keep"] + ' ' + lang['Logs'],
- "description": "The number of days to keep logs before purging.",
+ "description": lang["fieldTextLogDays"],
"default": "10",
"example": "30",
"possible": "",
@@ -4337,7 +4863,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=lang",
"field": lang["Dashboard Language"],
- "description": 'The primary language of text elements. For complete translation add your language in conf.json e.g:"language": "en_CA",',
+ "description": lang["fieldTextLang"],
"default": "en_CA",
"example": "",
"fieldType": "select",
@@ -4346,7 +4872,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=audio_note",
"field": lang["Notification Sound"],
- "description": "Sound when information bubble appears.",
+ "description": lang["fieldTextAudioNote"],
"default": "",
"example": "",
"fieldType": "select",
@@ -4355,7 +4881,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=audio_alert",
"field": lang["Alert Sound"],
- "description": "Sound when Event occurs.",
+ "description": lang["fieldTextAudioAlert"],
"default": "",
"example": "",
"fieldType": "select",
@@ -4364,7 +4890,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=audio_delay",
"field": lang["Alert Sound Delay"],
- "description": "Delay until next time an Event can start an Alert. Measured in seconds.",
+ "description": lang["fieldTextAudioDelay"],
"default": "1",
"example": "",
"possible": ""
@@ -4372,7 +4898,7 @@ module.exports = function(s,config,lang){
{
"name": "detail=event_mon_pop",
"field": lang["Popout Monitor on Event"],
- "description": "When an Event occurs popout the monitor stream.",
+ "description": lang["fieldTextEventMonPop"],
"default": "en_CA",
"example": "",
"fieldType": "select",
@@ -4445,12 +4971,17 @@ module.exports = function(s,config,lang){
"Uploaders": {
"name": lang["Uploaders"],
"color": "forestgreen",
- "blocks": s.uploaderFields
+ "info": []
},
"Preferences": {
"name": lang.Preferences,
"color": "navy",
"info": [
+ {
+ "field": lang['Clock Format'],
+ "name": "detail=clock_date_format",
+ "placeholder": "$DAYNAME $DAY $MONTHNAME $YEAR",
+ },
{
"field": lang.CSS,
"name": "detail=css",
@@ -4461,6 +4992,12 @@ module.exports = function(s,config,lang){
"example": "",
"possible": ""
},
+ {
+ "field": lang.hlsOptions,
+ "name": "localStorage=hlsOptions",
+ fieldType:"textarea",
+ "placeholder": "{}",
+ },
{
"field": lang['Force Monitors Per Row'],
"form-group-class":"st_force_mon_rows_input st_force_mon_rows_1",
@@ -4568,11 +5105,22 @@ module.exports = function(s,config,lang){
"blockquoteClass": "global_tip",
"blockquote": lang.onvifdeviceManagerGlobalTip,
"info": [
+ {
+ "field": lang["Monitor"],
+ "fieldType": "select",
+ "class": "monitors_list",
+ "possible": []
+ },
{
"fieldType": "btn",
"class": `btn-warning onvif-device-reboot`,
"btnContent": ` ${lang['Reboot Camera']}`,
},
+ {
+ "fieldType": "div",
+ "class": "p-2",
+ "divContent": ``,
+ }
]
},
"Network": {
@@ -4746,17 +5294,17 @@ module.exports = function(s,config,lang){
{
"name": lang.On,
"value": "ON",
- "info": "Enable Ir cut fiter. Typically Day mode."
+ "info": lang["fieldTextIrCutFilterOn"]
},
{
"name": lang.Off,
"value": "OFF",
- "info": "Disable Ir cut fiter. Typically Night mode."
+ "info": lang["fieldTextIrCutFilterOff"]
},
{
"name": lang.Auto,
"value": "AUTO",
- "info": "Ir cut filter is automatically activated by the device."
+ "info": lang["fieldTextIrCutFilterAuto"]
},
]
},
@@ -5232,7 +5780,7 @@ module.exports = function(s,config,lang){
"field": lang['Allowed IPs'],
"default": `0.0.0.0`,
"placeholder": `0.0.0.0 ${lang['for Global Access']}`,
- "description": lang['Separate with commas, no spaces'],
+ "description": lang[lang["fieldTextIp"]],
"fieldType": "text"
},
{
@@ -5373,23 +5921,26 @@ module.exports = function(s,config,lang){
"name": lang["Regions"],
"headerTitle": `
`,
"color": "orange",
"section-pre-class": "col-md-6",
"section-class": "where",
+ "box-wrapper-class": "row",
"info": [
{
"field": lang["Monitor"],
"id": "region_editor_monitors",
"fieldType": "select",
+ "form-group-class": "col-md-6",
},
{
"id": "regions_list",
"field": lang["Regions"],
"fieldType": "select",
- "possible": []
+ "possible": [],
+ "form-group-class": "col-md-6",
},
{
"name": "name",
@@ -5398,28 +5949,47 @@ module.exports = function(s,config,lang){
{
"name": "sensitivity",
"field": lang['Minimum Change'],
+ "form-group-class": "col-md-6",
},
{
"name": "max_sensitivity",
"field": lang['Maximum Change'],
+ "form-group-class": "col-md-6",
},
{
"name": "threshold",
"field": lang['Trigger Threshold'],
+ "form-group-class": "col-md-6",
},
{
"name": "color_threshold",
"field": lang['Color Threshold'],
+ "form-group-class": "col-md-6",
},
{
+ hidden: true,
id: "regions_points",
"fieldType": "table",
"class": 'table table-striped',
},
{
- "fieldType": "btn",
- "class": `btn-info toggle-region-still-image`,
- "btnContent": ` ${lang['Live Stream Toggle']}`,
+ "class": 'col-md-12',
+ "fieldType": 'div',
+ info: [
+ {
+ "fieldType": "btn",
+ attribute: "href=#",
+ "class": `btn-info toggle-region-still-image`,
+ "btnContent": ` ${lang['Live Stream Toggle']}`,
+ },
+ {
+ "fieldType": "btn",
+ forForm: true,
+ attribute: "href=#",
+ "class": `btn-success`,
+ "btnContent": ` ${lang['Save']}`,
+ },
+ ]
},
]
},
@@ -5427,6 +5997,7 @@ module.exports = function(s,config,lang){
"name": lang["Points"],
"color": "orange",
"section-pre-class": "col-md-6",
+ "style": "overflow:auto",
"blockquoteClass": "global_tip",
"blockquote": lang.RegionNote,
"info": [
@@ -5434,7 +6005,7 @@ module.exports = function(s,config,lang){
"fieldType": "div",
class: "canvas_holder",
divContent: `عندما المربعات عرض وارتفاع ترد عليك أن مجموعة منهم إلى 640 × 480 أو أقل. هذا وسوف تحسين سرعة القراءة من الإطارات.
", - "Disable Night Vision": "تعطيل للرؤية الليلية عنوان URL", - "Disable Nightvision": "تعطيل رؤية ليلية", - "Disabled": "تعطيل", - "Documentation": "الوثائق", - "Don't show this anymore": "لا تظهر هذا بعد الآن", - "Double Quote Directory": "اقتباس مزدوجة الدليل بعض الدلائل التي تحتوي على مسافات. باستخدام هذا قد يتلف بعض الكاميرات.", - "Down": "أسفل عنوان URL", - "Down Stop": "أسفل وقف عنوان URL", - "Download": "تحميل", - "EU": "الاتحاد الأوروبي", - "Edit": "تحرير", - "Email": "البريد الإلكتروني", - "Email Details": "البريد الإلكتروني التفاصيل", - "Email on No Motion": "البريد الإلكتروني على \"لا الحركة\"", - "Email on Trigger": "البريد الإلكتروني على الزناد رسائل البريد الإلكتروني الذهاب إلى الحساب الرئيسي حامل عنوان تسجيل الدخول.", - "Enable Night Vision": "تمكين الرؤية الليلية عنوان URL", - "Enable Nightvision": "تمكن رؤية ليلية", - "Enabled": "تمكين", - "End": "نهاية", - "End Time": "نهاية الوقت", - "Ended": "انتهت", - "Enlarge": "تكبير", - "Enter this code to proceed": "أدخل هذا الرمز إلى المضي قدما", - "Equal to": "يساوي", - "Error Connecting": "حدث خطأ أثناء الاتصال", - "Event": "الحدث", - "Event Limit": "الحدث الحد", - "EventText1": "أثار الحدث الحركة في", - "EventText2": "لا يمكن أن البريد الإلكتروني صورة الملف لا يمكن الوصول إليها", - "Events": "الأحداث", - "Example": "على سبيل المثال", - "Execute Command": "تنفيذ الأوامر", - "Executed": "أعدم", - "Export": "التصدير", - "FFmpegCantStart": "FFmpeg لم تبدأ", - "FFmpegCantStartText": "تسجيل محرك هذه الكاميرا لا يمكن أن تبدأ. قد يكون هناك شيء خاطئ مع الكاميرا التكوين. إذا كان هناك أي سجلات أخرى من هذا واحد يرجى نشرها في القضايا على جيثب.", - "FFmpegTip": "FFprobe بسيط تيارات متعددة الوسائط محلل. يمكنك استخدامه لانتاج جميع أنواع المعلومات حول المدخلات بما في ذلك مدة, معدل الإطار, حجم الإطار ، إلخ.", - "FFprobe": "التحقيق", - "FactorAuthText1": "كود سوف يكون نشطا فقط لمدة 15 دقيقة. إذا كنت تسجيل الدخول مرة أخرى الموقت سيتم إعادة تعيين إلى 15 دقيقة مع نفس القانون.", - "Fatal": "قاتلة", - "Fatal Maximum Reached": "قاتلة الأقصى التوصل إلى وقف الكاميرا.", - "FatalMaximumReachedText": "JPEG كان خطأ فادح.", - "Feed-in Image Height": "تغذية في ارتفاع الصورة", - "Feed-in Image Width": "تغذية في صورة العرض", - "Fields cannot be empty": "المجالات لا يمكن أن تكون فارغة", - "File Not Exist": "الملف غير موجود", - "File Not Found": "لم يتم العثور على الملف", - "File Type": "نوع الملف", - "FileNotExistText": "لا يمكن حفظ غير متوفرة. شيء ذهب على نحو خاطئ.", - "Filename": "اسم الملف", - "Filesize": "حجم الملف", - "Filter ID": "تصفية معرف", - "Filter Matches": "تصفية المباريات", - "Filter Name": "اسم عامل التصفية", - "FilterMatchesText1": "هذا الفلتر قد استوفت الشروط.", - "FilterMatchesText2": "أشرطة الفيديو وجدت.", - "Filters": "المرشحات", - "Filters Updated": "مرشحات تحديث", - "FiltersUpdatedText": "التغييرات التي تم حفظها وتطبيقها.", - "Find Where": "تجد فيها", - "Fix": "Fix", - "Fix Video": "إصلاح الفيديو", - "FixVideoMsg": "هل تريد إصلاح هذا الفيديو ؟ لا يمكن التراجع عن هذا الإجراء.", - "Font Path": "مسار الخط", - "Font Size": "حجم الخط", - "Force Port": "القوة ميناء", - "Found Devices": "وجدت الأجهزة", - "Frame Rate": "معدل الإطار (FPS)", - "Full Frame Detection": "الإطار الكامل الكشف", - "Fullscreen": "ملء الشاشة", - "Greater Than": "أكبر من", - "Greater Than or Equal to": "أكبر من أو يساوي", - "Group Key": "المجموعة الرئيسية", - "Group Name": "اسم المجموعة", - "Grouping": "تجمع ", - "H.264 / H.265 / H.265+": "H. 264 / H. 265 / H. 265 ", - "HLS (.m3u8)": "HLS (.m3u8)", - "HLS (includes Audio)": "HLS (ويشمل الصوت)", - "HLS Audio Encoder": "HLS ترميز الصوت", - "HLS List Size": "HLS قائمة الحجم", - "HLS Preset": "HLS مسبقا", - "HLS Segment Length": "HLS قطعة طول في ثواني", - "HLS Video Encoder": "HLS ترميز الفيديو", - "HTTP": "HTTP", - "HTTPS": "HTTPS", - "Height": "ارتفاع", - "Help": "مساعدة", - "Hide List": "إخفاء قائمة", - "Hide Notes": "إخفاء الملاحظات", - "Host": "المضيف", - "Hotswap Modes (Watch-Only)": "Hotswap وسائط (مشاهدة فقط)", - "How to Record": "كيفية تسجيل", - "IP Address": "عنوان IP", - "Identity": "الهوية", - "IdentityText1": "هذا هو كيف نظام تحديد البيانات عن هذا التيار. لا يمكنك تغيير الشاشة معرف بمجرد الضغط على حفظ. إذا كنت تريد أن تجعل الشاشة معرف المزيد الإنسان للقراءة قبل المتابعة.", - "IdentityText2": "يمكنك تكرار رصد بتعديل رصد الهوية ثم الضغط على حفظ. هل يمكن استخدام معرف جهاز موجود بالفعل أو أنه سيوفر أكثر من أن الشاشة معلومات قاعدة البيانات.", - "Idle": "الخمول", - "Image Height": "ارتفاع الصورة", - "Image Location": "صورة موقع المسار المطلق أو ترك فارغا لاستخدام العالمية", - "Image Position": "صورة الموقف", - "Image Width": "صورة العرض", - "Import": "استيراد", - "Import Monitor Configuration": "استيراد رصد التكوين", - "ImportMonitorConfigurationText": "عند القيام بذلك سوف overrwrite أي تغييرات في الوقت الراهن لم يتم حفظها. التغييرات المستوردة تطبق فقط عند الضغط على حفظ.", - "In": "في", - "Incorrect Settings Chosen": "إعدادات غير صحيحة المختار", - "Indifference": "اللامبالاة", - "Input": "المدخلات", - "Input Flags": "إدخال الأعلام", - "Input Type": "نوع الإدخال", - "InputText1": "هذا المقطع يحكي شينوبي كيفية تستهلك تيار. للحصول على الأداء الأمثل محاولة ضبط الكاميرا الإعدادات الداخلية. العثور على الخيارات التالية ومجموعة منهم كما هو مبين. العثور على الكاميرا الخاصة بك يمكنك استخدام بنيت في ONVIF الماسح الضوئي من شينوبي. بعض كاميرات ONVIF تتطلب استخدام أداة إدارة إلى تعديل الإعدادات الداخلية. إذا كنت لا يمكن العثور على الكاميرات الخاصة بك يمكنك محاولة ONVIF مدير جهاز ويندوز.", - "InputText2": "هذا القسم سوف تعين الابتدائي تيار الأسلوب وإعدادات. هذا التيار سوف يتم عرضها في لوحة القيادة. إذا اخترت استخدام HLS, JPEG, أو MJPEG ثم يمكنك تستهلك تيار من خلال البرامج الأخرى.
استخدام JPEG تيار أساسا إيقاف البث الأساسية يستخدم اللقطة بن للحصول على إطارات.
", - "Streamer": "غاسل", - "Streams": "تيارات", - "Superuser": "الخارق", - "Switch on for Still Image": "التبديل على صورة ثابتة", - "TCP": "TCP", - "Text Box Color": "النص مربع اللون", - "Text Color": "لون النص", - "Time-lapse": "الوقت الفاصل بين", - "Time-lapse Tool": "الوقت الفاصل بين أداة", - "Timeout": "مهلة", - "Timeout Reset on Next Motion": "مهلة إعادة تعيين على الحركة القادمة", - "Toggle Sidebar": "تبديل الشريط الجانبي", - "Top Left": "أعلى اليسار", - "Top Right": "أعلى اليمين", - "Trigger Record": "الزناد سجل", - "Trigger Successful": "الزناد ناجحة", - "UDP": "UDP", - "URL": "URL", - "URL Stop Timeout": "URL وقف مهلة تشغيل إيقاف URL بعد X ثانية", - "US": "لنا", - "Unable to Launch": "قادر على إطلاق", - "UnabletoLaunchText": "الرجاء حفظ رصد جديدة أولا. ثم محاولة إطلاق محرر المنطقة.", - "Up": "حتى عنوان URL", - "Up Stop": "حتى وقف عنوان URL", - "Username": "اسم المستخدم", - "Value": "القيمة", - "Video": "فيديو", - "Video Codec": "ترميز الفيديو", - "Video Filter": "فيديو مرشح", - "Video Finished": "الفيديو النهائي", - "Video Length (minutes) and Motion Count per video": "طول الفيديو (دقيقة) و الحركة الاعتماد في الفيديو", - "Video Record Rate": "الفيديو سجل معدل (FPS)", - "Video Status": "فيديو حالة", - "Video and Time Span (Minutes)": "فيديو من الزمن (دقيقة)", - "Videos": "الفيديو", - "Videos List": "قائمة أشرطة الفيديو", - "Watch": "مشاهدة", - "Watch Only": "مشاهدة فقط", - "WebDAV": "WebDAV", - "WebM (libvpx)": "WebM (libvpx)", - "Webdav Error": "خطأ Webdav", - "WebdavErrorText": "لا يمكن حفظ. هل جعل الكاميرا المجلدات داخل اخترتها حفظ الدليل ؟ ", - "Webhook": "Webhook", - "Webhook URL": "Webhook URL", - "Width": "العرض", - "Yes": "نعم", - "Zoom In": "التكبير في عنوان URL", - "Zoom In Stop": "التكبير في التوقف عن عنوان URL", - "Zoom Out": "تصغير عنوان URL", - "Zoom Out Stop": "تصغير وقف عنوان URL", - "a day": "اليوم", - "a few seconds": "بضع ثوان", - "a minute": "دقيقة", - "a month": "شهر", - "a year": "في السنة", - "aac": "الجميح للسيارات", - "aac (Default)": "الجميح للسيارات (افتراضي)", - "ac3": "ac3", - "ago": "قبل", - "an hour": "ساعة", - "blankPassword": "تترك فارغة إلى الحفاظ على نفس كلمة المرور", - "calendar": "التقويم", - "clientStreamFailedattemptingReconnect": "العميل ctream تحقق فشلت محاولة إعادة الاتصال.", - "confirmDeleteFilter": "هل تريد حذف هذا الفلتر ؟ لا يمكنك استرداد.", - "copy": "نسخ", - "days": "أيام", - "dropBoxSuccess": "النجاح! الملفات المحفوظة إلى دروببوإكس الخاص بك.", - "for Global Access": "من أجل الوصول العالمي", - "hours": "ساعات", - "in": "في", - "libmp3lame": "libmp3lame", - "libopus": "libopus", - "libvorbis (Default)": "libvorbis (الافتراضي)", - "libvpx (Default)": "libvpx (الافتراضي)", - "libvpx-vp9": "libvpx-vp9", - "libx264": "libx264", - "libx264 (Default)": "libx264 (الافتراضي)", - "libx265": "libx265", - "minutes": "دقائق", - "modifyVideoText1": "طريقة لا وجود لها. تحقق للتأكد من أن آخر قيمة URL ليست فارغة.", - "monitorEditFailedMaxReached": "حسابك قد بلغ أقصى عدد من الكاميرات التي يمكن إنشاؤها. التحدث إلى مسؤول إذا كنت ترغب في تغيير هذا.", - "monitorEditText1": "بيانات غير صالحة ، تحقق لمعرفة هذا هو صالح استيراد السلسلة.", - "monitorEditText2": "غير صالحة تفاصيل السلسلة. تحقق لمعرفة ما هو سلسلة JSON و غير منتظم وجوه يتم تمريرها.", - "monitorGetText1": "الطلب غير المكتمل ، وإزالة الماضي مائل في URL أو وضع مقبول القيمة.", - "months": "أشهر", - "noSpecialCharacters": "بدون مسافات أو أحرف خاصة.", - "on": "على", - "on Error": "على خطأ", - "startUpText0": "حجم التحقق من أشرطة الفيديو", - "startUpText1": "الغاية من حجم التحقق من أشرطة الفيديو", - "startUpText2": "جميع المستخدمين فحص الانتظار لإغلاق الملفات المفتوحة وإزالة الملفات على حد المستخدم", - "startUpText3": "في انتظار أن تعطي لم تنته الفيديو تحقق بعض الوقت. 3 ثوان.", - "startUpText4": "بدأت كل مجموعة شاشات لمشاهدة وتسجيل", - "startUpText5": "شينوبي جاهز.", - "superAdminText": "\"السوبر.سلمان\" لا وجود لها. الرجاء إعادة تسمية \"سوبر.العينة.سلمان\" إلى \"السوبر.سلمان\".", - "superAdminTitle": "شينوبي : المشرف المميز", - "total": "مجموع", - "undefined": "غير معرف", - "updateKeyText1": "\"updateKey\" مفقود من \"conf.json\", لا يمكن أن تفعل التحديثات هذه الطريقة حتى يمكنك إضافته.", - "updateKeyText2": "\"updateKey\" غير صحيحة.", - "years": "سنوات" -} + "\"No Motion\" Detector": "\"لا الحركة\" كاشف", + "# of Allow MJPEG Clients": "# من السماح لعملاء MJPEG 0 لـ Infinite ", + "'Already Installing...'": "\"تثبيت بالفعل ...\"", + "180 Degrees": "180 درجة", + "2-Factor Authentication": "2-عامل التوثيق", + "90 Clockwise": "90 اتجاه عقارب الساعة", + "90 Clockwise and Vertical Flip": "90 اتجاه عقارب الساعة العمودي الوجه", + "90 Counter Clockwise and Vertical Flip (default)": "90 عكس اتجاه عقارب الساعة و انعكاس عمودي (الافتراضي)", + "AND": "و", + "API": "API", + "API Key": "مفتاح API", + "API Key Action Failed": "فشل إجراء مفتاح API", + "API Key Added": "مفتاح API وأضاف", + "API Key Deleted": "مفتاح API حذف", + "API Keys": "مفاتيح API", + "APIKeyAddedText": "يمكنك استخدام هذا المفتاح الآن.", + "APIKeyDeletedText": "تم حذف مفتاح. وسوف لم يعد يعمل.", + "ASC": "ASC", + "Accelerator": "مسرع", + "Account Info": "معلومات الحساب", + "Account Information": "معلومات الحساب", + "Account Privileges": "امتيازات الحساب", + "Account Save": "حفظ الحساب", + "Account Settings": "إعدادت الحساب", + "AccountEditText1": "لا يمكن تحريرها. تحديث الصفحة إذا كانت المشكلة لا تزال.", + "Accounts": "حسابات", + "Action for Selected": "العمل المحدد", + "Activated": "مفعل", + "Active Monitors": "شاشات نشطة", + "Add": "إضافة", + "Add All": "إضافة الجميع", + "Add Camera": "أضف الكاميرا", + "Add Cameras": "إضافة الكاميرات", + "Add Channel": "إضافة قناة", + "Add Input Feed": "إضافة تغذية الإدخال", + "Add Map": "أضف الخريطة", + "Add Monitor": "إضافة مراقبة", + "Add New": "إضافة جديدة", + "AddToPreset": "أضف إلى الإعداد المسبق", + "Additional Inputs": "مدخلات إضافية", + "Admin": "المشرف", + "Advanced": "المتقدمة", + "After": "بعد", + "Again": "مرة أخرى", + "Age": "سن", + "Alert Sound": "صوت في حالة تأهب", + "Alert Sound Delay": "تنبيه تأخير الصوت", + "All Logs": "جميع السجلات", + "All Monitors": "جميع الشاشات", + "All Monitors and Privileges": "جميع شاشات والامتيازات", + "All Privileges": "جميع الامتيازات", + "All Warnings": "كل التحذيرات", + "All streams in first feed": "جميع التدفقات في التغذية الأولى", + "Allow API Trigger": "السماح API Trigger", + "Allow Next Alert": "السماح التنبيه التالي", + "Allow Next Command": "تسمح الأمر التالي في دقائق", + "Allow Next Discord Alert": "اسمح تنبيه الخلاف التالي في دقائق ", + "Allow Next Email": "تسمح المقبل البريد الإلكتروني في غضون دقائق", + "Allow Next Trigger": "تسمح الزناد التالي في ميلي ثانية", + "Allow Next Webhook": "السماح التالي webhook", + "Allowed IPs": "يسمح IPs", + "Already Processing": "المعالجة بالفعل", + "Already exists": "موجود مسبقا", + "Alternate Logins": "تسجيلات تسجيلات بديلة", + "Always": "دائماً", + "Amazon S3": "Amazon S3", + "Amazon S3 Upload Error": "خطأ في تحميل Amazon S3", + "Analyzation Duration": "Analyzation مدة", + "AppNotEnabledText": "لم يتم تمكين التطبيق ، وتمكينه في إعدادات حسابك.", + "April": "أبريل", + "Archive": "أرشيف", + "Are you sure?": "هل أنت واثق؟", + "Attach Snapshot": "نعلق لقطة", + "Attach Video Clip": "إرفاق مقطع الفيديو", + "Audio": "صوتي", + "Audio Bit Rate": "معدل البت الصوت", + "Audio Codec": "ترميز الصوت", + "Audio Detection": "اكتشاف الصوت", + "Audio Detector": "كاشف الصوت", + "Audio stream only from first feed": "دفق الصوت فقط من الخلاصة الأولى", + "Audio streams only": "تدفقات الصوت فقط", + "August": "أغسطس", + "Authenticate": "المصادقة", + "Authenticated": "مصادقة", + "Authentication Failed": "المصادقة فشلت", + "Auto": "السيارات", + "Automatic": "تلقائي", + "Automatic Checking Cancelled": "فحص تلقائي تم إلغاؤه", + "Automatic Codec Repair": "إصلاح الترميز التلقائي", + "Automatic Field Fill": "ملء المجال التلقائي", + "Autosave": "الحفظ التلقائي", + "Back": "خلف", + "Backblaze B2": "Backblaze B2", + "Backblaze Error": "خطأ backblaze", + "BacklightCompensation": "وتعويض الإضاءة الخلفية", + "Backup": "دعم", + "Bandwidth": "عرض النطاق", + "Base64 over Websocket": "Base64 على Websocket", + "Basic Authentication": "المصادقة الأساسية", + "Batch": "حزمة", + "Before": "قبل", + "Bind Credentials": "ربط بيانات الاعتماد (كلمة المرور)", + "BitrateLimit": "حد البت", + "Blank for No Change": "فارغة لعدم تغيير", + "Bottom Left": "أسفل اليسار", + "Bottom Right": "أسفل اليمين", + "Brightness": "سطوع", + "Browser Console Log": "المتصفح سجل وحدة", + "Bucket": "دلو", + "Buffer Preview": "معاينة المخزن المؤقت", + "Build": "يبني", + "Build Video": "بناء الفيديو", + "Building": "مبنى", + "CPU": "وحدة المعالجة المركزية", + "CPU indicator will not work. Continuing...": "وحدة المعالجة المركزية المؤشر لا يعمل. استمرار...", + "CPU used by this stream": "وحدة المعالجة المركزية المستخدمة من قبل هذا الدفق", + "CSS": "CSS أسلوب لوحة القيادة الخاصة بك.", + "Calendar": "التقويم", + "Call Method": "طريقة الاتصال", + "Camera Password": "كاميرا المرور", + "Camera Username": "الكاميرا المستخدم", + "Camera is not recording": "الكاميرا ليست تسجيل", + "Camera is not running": "الكاميرا لا تعمل", + "Camera is not streaming": "الكاميرا لا تتدفق", + "CameraNotRecordingText": "الإعدادات قد تكون غير متوافقة. التحقق من الترميز. إعادة تشغيل...", + "Can Authenticate Websocket": "يمكن مصادقة WebSocket", + "Can Change User Settings": "يمكن تغيير إعدادات المستخدم", + "Can Control Monitors": "يمكن التحكم في الشاشات", + "Can Create and Delete Monitors": "يمكن إنشاء وحذف الشاشات", + "Can Delete Videos": "يمكن حذف الفيديو", + "Can Delete Videos and Events": "يمكن حذف الفيديو و الأحداث", + "Can Edit Monitor": "يمكن تحرير رصد", + "Can Get Logs": "يمكن الحصول على سجلات", + "Can Get Monitors": "يمكن الحصول على شاشات", + "Can View Logs": "يمكن عرض سجلات", + "Can View Monitor": "يمكن عرض الشاشة", + "Can View Snapshots": "يمكن عرض لقطات", + "Can View Streams": "يمكن عرض الجداول", + "Can View Videos": "يمكن عرض الفيديو", + "Can View Videos and Events": "يمكن عرض أشرطة الفيديو والأحداث", + "Can edit Max Days": "يمكن تحرير أيام كحد أقصى", + "Can edit Max Storage": "يمكن تحرير الحد الأقصى للتخزين", + "Can edit how long to keep Events": "يمكن تعديل كم من الوقت للحفاظ على الأحداث", + "Can edit how long to keep Logs": "يمكن تعديل كم من الوقت للحفاظ على السجلات", + "Can use Admin Panel": "يمكن استخدام لوحة المسؤول", + "Can use Amazon S3": "يمكن استخدام Amazon S3", + "Can use Discord Bot": "يمكن استخدام روبوت الخلاف", + "Can use LDAP": "يمكن استخدام LDAP", + "Can use SFTP": "يمكن استخدام SFTP", + "Can use Wasabi Hot Cloud Storage": "يمكن استخدام التخزين السحابي الساخن Wasabi", + "Can use WebDAV": "يمكن استخدام WebDav", + "Can't Connect": "لا يمكن الاتصال", + "Cannot watch a monitor that isn't running.": "لا يمكن مشاهدة شاشة لا تعمل.", + "Cards": "البطاقات", + "Carousel in Background": "كاروسيل في الخلفية", + "Center": "مركز عنوان URL", + "Channel": "قناة", + "Channel ID": "معرف القناة", + "Chat on Discord": "الدردشة على الفتنة", + "Check": "تحقق", + "Check Signal Interval": "التحقق من إشارة الفاصل الزمني في دقائق", + "Check for Motion First": "تحقق من الحركة الأولى", + "Check the Channel ID": "تحقق من معرف القناة", + "Check the Recipient ID": "تحقق من معرف المستلم", + "Clear": "واضح", + "Clear Recorder Process": "عملية مسجل واضحة", + "Close": "قريب", + "Close All Monitors": "أغلق جميع الشاشات", + "Closed": "مغلقة", + "Cloud": "سحاب", + "Codec Mismatch": "عدم تطابق الترميز", + "Color Threshold": "عتبة اللون", + "ColorSaturation": "تشبع اللون", + "Command": "الأمر", + "Command on Trigger": "القيادة على الزناد", + "Common Objects": "أشياء مشتركة", + "Complete Stream URL": "كاملة تيار URL", + "Conditions": "الظروف", + "Confidence": "الثقة", + "Confidence of Detection": "ثقة الكشف", + "Configuration": "إعدادات", + "Confirm": "تأكيد", + "Connected": "متصل", + "Connected Users": "المستخدمين المتصلون", + "Connection": "اتصال", + "Connection Type": "نوع الاتصال", + "Connection timed out": "انتهت مدة الاتصال", + "Contains": "يتضمن", + "Contrast": "التباين", + "Control": "التحكم", + "Control Error": "السيطرة على خطأ", + "ControlErrorText1": "لم يتم تمكين التحكم", + "ControlErrorText2": "تحقق من تفاصيل الاتصال الخاصة بك. قد تحتاج إلى توجيه عنوان URL الأساسي إلى المنفذ 8000 أو 80. تحقق من معلومات المصادقة الخاصة بك.", + "Controllable": "يمكن السيطرة عليها", + "Controls and Logs": "الضوابط والسجلات", + "Copied": "نسخ", + "Copied to Clipboard": "نسخ إلى الحافظة", + "Copy": "ينسخ", + "Copy Connection Settings": "نسخ إعدادات الاتصال", + "Copy Custom Settings": "نسخ الإعدادات المخصصة", + "Copy Detector Settings": "نسخ إعدادات كاشف", + "Copy Group Settings": "نسخ إعدادات المجموعة", + "Copy Input Settings": "نسخ إعدادات الإدخال", + "Copy JPEG API Settings": "نسخ إعدادات API JPEG", + "Copy Logging Settings": "نسخ إعدادات التسجيل", + "Copy Mode": "وضع النسخ", + "Copy Recording Settings": "نسخ إعدادات التسجيل", + "Copy Remote Link": "نسخ الرابط البعيد", + "Copy Settings": "نسخ الإعدادات", + "Copy Stream Channel Settings": "نسخ إعدادات قناة الدفق", + "Copy Stream Channels": "نسخ قنوات الدفق", + "Copy Stream Settings": "نسخ إعدادات الدفق", + "Copy Stream URL": "نسخ URL دفق", + "Copy Timelapse Settings": "نسخ إعدادات timelapse", + "Copy to Settings": "انسخ إلى الإعدادات", + "Cores": "النوى", + "Could not create Bucket.": "لا يمكن إنشاء دلو.", + "Count Objects": "عدد الكائنات", + "Count Objects only inside Regions": "عد الكائنات فقط داخل المناطق", + "Country of Plates": "البلد من لوحات", + "Counts of Motion": "تهم الحركة", + "Create Sub-Accounts at /admin": "إنشاء حسابات فرعية في /المسؤول", + "Creating New Account": "إنشاء حساب جديد", + "Creation Interval": "فاصل الخلق", + "Current": "الحالي", + "Currently Active": "نشط حاليا", + "Currently viewing": "حاليا عرض", + "Custom": "مخصص", + "Custom Auto Load": "تحميل تلقائي مخصص", + "Custom Base URL": "العرف قاعدة URL ترك فارغا لاستخدام المضيف URL", + "Custom Endpoint": "نقطة نهاية مخصصة", + "DB Lost.. Retrying..": "قاعدة البيانات المفقودة.. إعادة المحاولة..", + "DESC": "DESC", + "DHCP": "DHCP", + "DNS": "DNS", + "Daily Events": "الأحداث اليومية", + "Dashboard": "لوحة القيادة", + "Dashboard Language": "لوحة أجهزة القياس في اللغة", + "Dashcam": "Dashcam", + "Dashcam (Streamer v2)": "Dashcam (غاسل v2)", + "Database": "قاعدة البيانات", + "Database Not Found": "قاعدة البيانات غير موجودة", + "Database row does not exist": "صف قاعدة البيانات غير موجود", + "Date": "تاريخ", + "Date Added": "تم إضافة التاريخ", + "Date Range": "نطاق التاريخ", + "Date Updated": "تاريخ تحديث", + "Date and Time": "التاريخ و الوقت", + "DateTimeType": "إدارة التاريخ", + "DaylightSavings": "التوقيت الصيفي", + "Days": "أيام", + "Debug": "التصحيح", + "December": "ديسمبر", + "Default": "الافتراضي", + "Delay for Snapshot": "تأخير لقطة", + "Delete": "حذف", + "Delete Camera": "حذف الكاميرا", + "Delete Filter": "حذف الفلتر", + "Delete Logs": "حذف السجلات", + "Delete Matches": "حذف المباريات", + "Delete Monitor": "حذف رصد", + "Delete Monitor State?": "حذف حالة الشاشة", + "Delete Monitor States Preset": "حذف الحالات المسبقة للمراقبة", + "Delete Monitors and Files": "حذف الشاشات والملفات", + "Delete Motionless Video": "حذف حراك الفيديو", + "Delete Motionless Videos (Record)": "حذف حراك الفيديو (رقم قياسي)", + "Delete Region": "حذف المنطقة", + "Delete Schedule": "حذف الجدول الزمني", + "Delete Selected Videos": "حذف الفيديو المختار", + "Delete Timelapse Frame": "حذف إطار timelapse", + "Delete Video": "حذف الفيديو", + "Delete selected": "حذف المحدد", + "DeleteMonitorText": "هل تريد حذف هذا العرض ؟ لا يمكنك استرداد. ملفات هذه الهوية ستبقى في نظام الملفات. إذا اخترت إعادة رصد مع نفس معرف الفيديو و أحداث سوف تصبح مرئية في لوحة القيادة.", + "DeleteMonitorsText": "هل تريد حذف هذه الشاشات؟ لا يمكنك استعادتها. يمكنك اختيار الاحتفاظ بالملفات لهذه المعرفات في نظام الملفات. إذا اخترت إعادة إنشاء شاشة باستخدام أحد المعرفات ، فستصبح مقاطع الفيديو والأحداث مرئية في لوحة القيادة.", + "DeleteSelectedVideosMsg": "هل تريد حذف هذا الفيديو ؟ لا يمكنك استرداد.", + "DeleteThisMsg": "هل تريد حذف هذا؟ لا يمكنك استعادته.", + "DeleteVideoMsg": "هل تريد حذف هذا الفيديو ؟ لا يمكنك استرداد.", + "Deleted": "حذف", + "Deleted Schedule Configuration": "تحذف تكوين الجدول الزمني", + "Deleted State Configuration": "تحذف تكوين الحالة", + "Detect Objects": "الكشف عن الكائنات انظر أدناه", + "Detection": "كشف", + "Detection Engine": "محرك الكشف", + "Detection Event": "حدث الكشف", + "Detector": "كاشف", + "Detector Buffer": "الكاشف العازلة", + "Detector Filters": "مرشحات الكاشف", + "Detector Flags": "كشف أعلام", + "Detector Grouping": "تجميع الكاشف أضف مجموعات في إعدادات ", + "Detector Rate": "للكشف عن معدل (FPS)", + "Detector Recording Complete": "تسجيل الكشف الكامل", + "Detector Recording Process Exited Prematurely. Restarting.": "عملية تسجيل الكاشف خرجت قبل الأوان. إعادة التشغيل.", + "DetectorText": "عندما المربعات عرض وارتفاع ترد عليك أن مجموعة منهم إلى 640 × 480 أو أقل. هذا وسوف تحسين سرعة القراءة من الإطارات.
", + "Died": "مات", + "Digest Authentication": "ملخص صحة البيانات", + "Disable": "إبطال", + "Disable Night Vision": "تعطيل للرؤية الليلية عنوان URL", + "Disable Nightvision": "تعطيل رؤية ليلية", + "Disabled": "تعطيل", + "Discord": "خلاف", + "Discord Alert on Trigger": "تنبيه الخلاف على الزناد", + "Discord Bot": "بوت الخلاف", + "Discord on No Motion": "خلاف على \"لا حركة\"", + "DiscordErrorText": "تسبب الإرسال إلى Discord في خطأ", + "DiscordFailedText": "فشل إرسال إلى Discord", + "DiscordLoggedIn": "Discord Bot مصادقة", + "DiscordNotEnabledText": "لم يتم تمكين Bot Discord ، وتمكينه في إعدادات حسابك.", + "Documentation": "الوثائق", + "Does Not Contain": "لا يحتوي", + "Don't Show for 1 Week": "لا تظهر لمدة أسبوع واحد", + "Don't show this anymore": "لا تظهر هذا بعد الآن", + "DontAddToPreset": "لا تضيف إلى الإعداد المسبق", + "Double Quote Directory": "اقتباس مزدوجة الدليل بعض الدلائل التي تحتوي على مسافات. باستخدام هذا قد يتلف بعض الكاميرات.", + "Down": "أسفل عنوان URL", + "Down Stop": "أسفل وقف عنوان URL", + "Download": "تحميل", + "Download Bandwidth": "تنزيل النطاق الترددي", + "Downloading Videos": "تنزيل مقاطع الفيديو", + "Downloading...": "جارى التحميل...", + "Duplicate": "ينسخ", + "EU": "الاتحاد الأوروبي", + "Easy Remote Access (P2P)": "الوصول عن بُعد سهل (P2P)", + "Edit": "تحرير", + "Edit Configuration": "تحرير التكوين", + "Edit Selected": "تحرير محدد", + "Edited Schedule Configuration": "تحرير التكوين الجدول الزمني", + "Edited State Configuration": "تحرير تكوين الحالة", + "Email": "البريد الإلكتروني", + "Email Details": "البريد الإلكتروني التفاصيل", + "Email address is in use.": "عنوان البريد الإلكتروني قيد الاستخدام.", + "Email and Password fields cannot be empty": "لا يمكن أن تكون حقول البريد الإلكتروني وكلمة المرور فارغة", + "Email on No Motion": "البريد الإلكتروني على \"لا الحركة\"", + "Email on Trigger": "البريد الإلكتروني على الزناد رسائل البريد الإلكتروني الذهاب إلى الحساب الرئيسي حامل عنوان تسجيل الدخول.", + "Emotion": "المشاعر", + "Emotion Average": "متوسط العاطفة", + "Enable": "ممكن", + "Enable Night Vision": "تمكين الرؤية الليلية عنوان URL", + "Enable Nightvision": "تمكن رؤية ليلية", + "Enabled": "تمكين", + "Encoding": "التشفير", + "EncodingInterval": "I-Frame", + "End": "نهاية", + "End Time": "نهاية الوقت", + "Ended": "انتهت", + "Endpoint": "نقطة النهاية", + "Endpoint Address": "عنوان نقطة النهاية", + "Enlarge": "تكبير", + "Enter at least one IP": "أدخل IP واحد على الأقل", + "Enter this code to proceed": "أدخل هذا الرمز إلى المضي قدما", + "Equal to": "يساوي", + "Error Connecting": "حدث خطأ أثناء الاتصال", + "Error While Decoding": "خطأ أثناء فك التشفير", + "ErrorWhileDecodingText": "قد يكون لجهازك اتصال غير مستقر بالشبكة. تحقق من اتصالات الشبكة الخاصة بك.", + "ErrorWhileDecodingTextAudio": "تقدم الكاميرا الخاصة بك بيانات مكسورة. حاول تعطيل الصوت في الإعدادات الداخلية للكاميرا.", + "Event": "الحدث", + "Event Counts": "تعداد الأحداث", + "Event Filter Error": "خطأ مرشح الحدث", + "Event Filters": "مرشحات الأحداث", + "Event Limit": "الحدث الحد", + "Event Occurred": "حدث الحدث", + "Event Rules": "قواعد الحدث", + "Event Webhook Error": "حدث خطأ Webhook", + "EventText1": "أثار الحدث الحركة في", + "EventText2": "لا يمكن أن البريد الإلكتروني صورة الملف لا يمكن الوصول إليها", + "Events": "الأحداث", + "Events Found": "وجدت الأحداث", + "Example": "على سبيل المثال", + "Execute Command": "تنفيذ الأوامر", + "Executed": "أعدم", + "Export": "التصدير", + "Export Selected Videos": "تصدير مقاطع الفيديو المحددة", + "Export Video": "تصدير الفيديو", + "ExportSelectedVideosMsg": "هل تريد تصدير مقاطع الفيديو هذه؟ قد يستغرق الأمر بعض الوقت للضغط والتنزيل.", + "Exposure": "التعرض", + "FFmpegCantStart": "FFmpeg لم تبدأ", + "FFmpegCantStartText": "تسجيل محرك هذه الكاميرا لا يمكن أن تبدأ. قد يكون هناك شيء خاطئ مع الكاميرا التكوين. إذا كان هناك أي سجلات أخرى من هذا واحد يرجى نشرها في القضايا على جيثب.", + "FFmpegTip": "FFprobe بسيط تيارات متعددة الوسائط محلل. يمكنك استخدامه لانتاج جميع أنواع المعلومات حول المدخلات بما في ذلك مدة, معدل الإطار, حجم الإطار ، إلخ.", + "FFprobe": "التحقيق", + "FLV": "FLV", + "FLV Stream Type": "نوع دفق FLV", + "FactorAuthText1": "كود سوف يكون نشطا فقط لمدة 15 دقيقة. إذا كنت تسجيل الدخول مرة أخرى الموقت سيتم إعادة تعيين إلى 15 دقيقة مع نفس القانون.", + "Fatal": "قاتلة", + "Fatal Maximum Reached": "قاتلة الأقصى التوصل إلى وقف الكاميرا.", + "FatalMaximumReachedText": "JPEG كان خطأ فادح.", + "February": "شهر فبراير", + "Feed-in Image Height": "تغذية في ارتفاع الصورة", + "Feed-in Image Width": "تغذية في صورة العرض", + "Female": "أنثى", + "Field Missing Value": "الحقل القيمة المفقودة", + "Fields cannot be empty": "المجالات لا يمكن أن تكون فارغة", + "File Delete Error": "خطأ حذف خطأ", + "File Not Exist": "الملف غير موجود", + "File Not Found": "لم يتم العثور على الملف", + "File Not Found in Database": "الملف غير موجود في قاعدة البيانات", + "File Not Found in Filesystem": "الملف غير موجود في نظام الملفات", + "File Type": "نوع الملف", + "FileBin Share": "مشاركة FileBin", + "FileNotExistText": "لا يمكن حفظ غير متوفرة. شيء ذهب على نحو خاطئ.", + "Filename": "اسم الملف", + "Filesize": "حجم الملف", + "Filter ID": "تصفية معرف", + "Filter Matches": "تصفية المباريات", + "Filter Name": "اسم عامل التصفية", + "Filter for Objects only": "تصفية للكائنات فقط", + "FilterMatchesText1": "هذا الفلتر قد استوفت الشروط.", + "FilterMatchesText2": "أشرطة الفيديو وجدت.", + "Filters": "المرشحات", + "Filters Updated": "مرشحات تحديث", + "FiltersUpdatedText": "التغييرات التي تم حفظها وتطبيقها.", + "Find Where": "تجد فيها", + "First stream in feed": "الدفق الأول في التغذية", + "Fix": "يصلح", + "Fix Video": "إصلاح الفيديو", + "FixVideoMsg": "هل تريد إصلاح هذا الفيديو ؟ لا يمكن التراجع عن هذا الإجراء.", + "Flush PM2 Logs": "سجلات PM2 Flush", + "Font Path": "مسار الخط", + "Font Size": "حجم الخط", + "For Group": "للمجموعة", + "Force Monitors Per Row": "شاشات القوة لكل صف", + "Force Port": "القوة ميناء", + "Form Data Not Found": "لم يتم العثور على بيانات النموذج", + "Found Devices": "وجدت الأجهزة", + "Frame Rate": "معدل الإطار (FPS)", + "FrameRateLimit": "حد معدل الإطارات (FPS)", + "Frames": "إطارات", + "Friday": "جمعة", + "Frigate": "فرقاطة", + "Full Frame Detection": "الإطار الكامل الكشف", + "Full Stream URL": "URL دفق كامل", + "Full URL Path": "مسار URL الكامل", + "Fullscreen": "ملء الشاشة", + "Gateway": "بوابة", + "Gender": "جنس", + "Generate Subtitles": "توليد ترجمات", + "Get Code": "الحصول على رمز", + "Get Logs to Client": "احصل على سجلات إلى العميل", + "Global Detector Settings": "إعدادات الكاشف العالمية", + "Google Drive": "محرك Google", + "GovLength": "الحكومة", + "Greater Than": "أكبر من", + "Greater Than or Equal to": "أكبر من أو يساوي", + "Grid": "جريد", + "Group Key": "المجموعة الرئيسية", + "Group Key is in use.": "مفتاح المجموعة قيد الاستخدام.", + "Group Name": "اسم المجموعة", + "Grouping": "تجمع ", + "H.264 / H.265 / H.265+": "H. 264 / H. 265 / H. 265 ", + "H264Profile": "H264 profile", + "HEVC (H.265)": "HEVC (H.265)", + "HLS (.m3u8)": "HLS (.M3U8)", + "HLS (includes Audio)": "HLS (ويشمل الصوت)", + "HLS Audio Encoder": "HLS ترميز الصوت", + "HLS List Size": "HLS قائمة الحجم", + "HLS Live Start Index": "HLS Live Start Index", + "HLS Preset": "HLS مسبقا", + "HLS Segment Length": "HLS قطعة طول في ثواني", + "HLS Start Number": "HLS رقم بدء", + "HLS Video Encoder": "HLS ترميز الفيديو", + "HTTP": "http", + "HTTPS": "https", + "Hardware Accelerated": "تسارع الأجهزة", + "Height": "ارتفاع", + "Help": "مساعدة", + "Hide List": "إخفاء قائمة", + "Hide Notes": "إخفاء الملاحظات", + "Home": "مسكن", + "Host": "المضيف", + "Host Type": "نوع المضيف", + "Hostname": "اسم المضيف", + "Hotswap Modes (Watch-Only)": "Hotswap وسائط (مشاهدة فقط)", + "How to Record": "كيفية تسجيل", + "IP Address": "عنوان IP", + "Identity": "الهوية", + "IdentityText1": "هذا هو كيف نظام تحديد البيانات عن هذا التيار. لا يمكنك تغيير الشاشة معرف بمجرد الضغط على حفظ. إذا كنت تريد أن تجعل الشاشة معرف المزيد الإنسان للقراءة قبل المتابعة.", + "IdentityText2": "يمكنك تكرار رصد بتعديل رصد الهوية ثم الضغط على حفظ. هل يمكن استخدام معرف جهاز موجود بالفعل أو أنه سيوفر أكثر من أن الشاشة معلومات قاعدة البيانات.", + "Idle": "الخمول", + "Image Height": "ارتفاع الصورة", + "Image Location": "صورة موقع المسار المطلق أو ترك فارغا لاستخدام العالمية", + "Image Position": "صورة الموقف", + "Image Width": "صورة العرض", + "Imaging": "التصوير", + "Import": "استيراد", + "Import Monitor Configuration": "استيراد رصد التكوين", + "ImportMonitorConfigurationText": "عند القيام بذلك سوف overrwrite أي تغييرات في الوقت الراهن لم يتم حفظها. التغييرات المستوردة تطبق فقط عند الضغط على حفظ.", + "ImportMultiMonitorConfigurationText": "سيؤدي القيام بذلك إلى تجاوز أي شاشات باستخدام IDS الموجودة في ملف الاستيراد.", + "In": "في", + "Incorrect Settings Chosen": "إعدادات غير صحيحة المختار", + "Indifference": "اللامبالاة", + "Info": "معلومات", + "Information": "معلومة", + "Input": "المدخلات", + "Input Feed": "تغذية المدخلات", + "Input Feeds Selected": "تم اختيار تغذية الإدخال", + "Input Flags": "إدخال الأعلام", + "Input Map": "خريطة الإدخال", + "Input Selector": "محدد الإدخال", + "Input Settings": "إعدادات الإدخال", + "Input Type": "نوع الإدخال", + "InputText1": "هذا المقطع يحكي شينوبي كيفية تستهلك تيار. للحصول على الأداء الأمثل محاولة ضبط الكاميرا الإعدادات الداخلية. العثور على الخيارات التالية ومجموعة منهم كما هو مبين. العثور على الكاميرا الخاصة بك يمكنك استخدام بنيت في ONVIF الماسح الضوئي من شينوبي. بعض كاميرات ONVIF تتطلب استخدام أداة إدارة إلى تعديل الإعدادات الداخلية. إذا كنت لا يمكن العثور على الكاميرات الخاصة بك يمكنك محاولة ONVIF مدير جهاز ويندوز.", + "InputText2": "هذا القسم سوف تعين الابتدائي تيار الأسلوب وإعدادات. هذا التيار سوف يتم عرضها في لوحة القيادة. إذا اخترت استخدام HLS, JPEG, أو MJPEG ثم يمكنك تستهلك تيار من خلال البرامج الأخرى.
استخدام JPEG تيار أساسا إيقاف البث الأساسية يستخدم اللقطة بن للحصول على إطارات.
", + "Streamed Logs": "سجلات البث", + "Streamer": "غاسل", + "Streams": "تيارات", + "Sub-Accounts": "الحسابات الفرعية", + "Subdivision": "التقسيم الفرعي", + "Substream": "تيار فرعي", + "Substream Process": "عملية سجن", + "SubstreamNotConfigured": "لم يتم تكوين السلع الفرعية. افتح إعدادات الشاشة الخاصة بك وتكوينها.", + "Subtitle": "الترجمة", + "Success": "نجاح", + "Sunday": "الأحد", + "Superuser": "الخارق", + "Superuser Logs": "سجلات Superuser", + "Switch on for Still Image": "التبديل على صورة ثابتة", + "System": "نظام", + "System Level": "مستوى النظام", + "TCP": "TCP", + "TV Channel": "قناة تلفزيونية", + "TV Channel Group": "مجموعة القناة التلفزيونية", + "TV Channel ID": "معرف القناة التلفزيونية", + "Telegram": "برقية", + "Text Box Color": "النص مربع اللون", + "Text Color": "لون النص", + "Text criteria unsupported for Object Count tests, Ignoring Conditional": "معايير النص غير مدعومة لاختبارات عدد الكائنات ، متجاهلة المشروط", + "Themes": "موضوعات", + "There are no monitors that you can view with this account.": "لا توجد شاشات يمكنك عرضها مع هذا الحساب.", + "Threads": "الخيوط", + "Thumbnail": "ظفري", + "Thursday": "يوم الخميس", + "Time": "وقت", + "Time Created": "الوقت الذي تم إنشاؤه", + "Time Left": "الوقت المتبقي", + "Time Occurred": "حدث الوقت", + "Time-lapse": "الوقت الفاصل بين", + "Time-lapse Tool": "الوقت الفاصل بين أداة", + "TimeZone": "وحدة زمنية", + "Timelapse": "timelapse", + "Timelapse Frames Share": "إطارات timelapse حصة", + "Timelapse Watermark": "Timelapse Watermark", + "Timeout": "مهلة", + "Timeout Reset on Next Event": "إعادة تعيين المهلة في الحدث التالي", + "Timeout Reset on Next Motion": "مهلة إعادة تعيين على الحركة القادمة", + "Timezone": "وحدة زمنية", + "Timezone Offset": "إزاحة المنطقة الزمنية", + "Title": "عنوان", + "Today": "اليوم", + "Toggle Sidebar": "تبديل الشريط الجانبي", + "Toggle Substream": "تبديل سجن", + "Token": "رمز", + "Top Left": "أعلى اليسار", + "Top Right": "أعلى اليمين", + "Traditional (Watch-Only, Includes Buffer)": "تقليدي (ساعة فقط ، تشمل المخزن المؤقت)", + "Traditional Recording": "التسجيل التقليدي", + "Traditional Recording Flags": "أعلام التسجيل التقليدية", + "Train": "قطار", + "TrainConfirm": "هل أنت متأكد أنك تريد أن تبدأ التدريب؟ هذا يمكن أن يستغرق أكثر من 12 ساعة مع أكثر من 500 صورة. هذا سوف يستهلك كمية كبيرة من الموارد ، مثل ذاكرة الوصول العشوائي و/أو وحدة المعالجة المركزية.", + "TrainConfirmStop": "هل أنت متأكد أنك تريد التوقف عن التدريب؟", + "Trainer Engine": "محرك المدرب", + "Trigger Blocked": "الزناد محظور", + "Trigger Camera Groups": "تشغيل مجموعات الكاميرا", + "Trigger Event": "حدث مسبب", + "Trigger Group to Record": "مجموعة الزناد لتسجيل", + "Trigger Record": "الزناد سجل", + "Trigger Successful": "الزناد ناجحة", + "Trigger Threshold": "عتبة الزناد", + "Tuesday": "يوم الثلاثاء", + "Turn Speed": "بدوره السرعة", + "Type": "يكتب", + "UDP": "UDP", + "URL": "عنوان URL", + "URL Stop Timeout": "URL وقف مهلة تشغيل إيقاف URL بعد X ثانية", + "US": "لنا", + "UTCDateTime": "تاريخ", + "Unable to Launch": "قادر على إطلاق", + "UnabletoLaunchText": "الرجاء حفظ رصد جديدة أولا. ثم محاولة إطلاق محرر المنطقة.", + "Uncommon Objects": "كائنات غير شائعة", + "Uniform": "زى موحد", + "Unlink": "غير محدود", + "Unlink Login": "تسجيل الدخول غير المرتبط؟", + "Unlinked": "غير مترابط", + "Up": "حتى عنوان URL", + "Up Stop": "حتى وقف عنوان URL", + "Update": "تحديث", + "Update to Development": "تحديث للتطوير", + "Update to Master": "تحديث إلى Master", + "Upload Bandwidth": "تحميل النطاق الترددي", + "Upload File": "رفع ملف", + "Uploaded Only": "تم الرفع فقط", + "Uploaders": "تحميلات", + "Use Built-In": "استخدام مدمج", + "Use Camera Timestamps": "استخدام الطابع الزمني للكاميرا", + "Use Global Amazon S3 Video Storage": "استخدم تخزين فيديو Amazon S3 العالمي", + "Use Global Backblaze B2 Video Storage": "استخدم تخزين الفيديو Global Backblaze B2", + "Use Global Wasabi Hot Cloud Storage Video Storage": "استخدم تخزين فيديو تخزين السحابة الساخن العالمي Wasabi", + "Use Global WebDAV Video Storage": "استخدم تخزين فيديو WebDav العالمي", + "Use HTML5 Play Method": "استخدم طريقة تشغيل HTML5", + "Use Max Storage Amount": "استخدم أقصى مبلغ التخزين", + "Use Raw Snapshot": "استخدام لقطة الخام", + "Use Substream": "استخدام Substream", + "Use coProcessor": "استخدام المعالج", + "UseCount": "usecount", + "User Log": "سجل المستخدم", + "User Not Found": "لم يتم العثور على المستخدم", + "Username": "اسم المستخدم", + "VA-API": "VA-api", + "Value": "القيمة", + "Video": "فيديو", + "Video Bit Rate": "معدل بت الفيديو", + "Video Codec": "ترميز الفيديو", + "Video Configuration": "تكوين الفيديو", + "Video Filter": "فيديو مرشح", + "Video Finished": "الفيديو النهائي", + "Video Length (minutes) and Motion Count per video": "طول الفيديو (دقيقة) و الحركة الاعتماد في الفيديو", + "Video Limit": "حد الفيديو", + "Video Record Rate": "الفيديو سجل معدل (FPS)", + "Video Set": "مجموعة الفيديو", + "Video Share": "مشاركة الفيديو", + "Video Status": "فيديو حالة", + "Video and Time Span (Minutes)": "فيديو من الزمن (دقيقة)", + "Video stream only from first feed": "دفق الفيديو فقط من الخلاصة الأولى", + "Video streams only": "تدفقات الفيديو فقط", + "Videos": "الفيديو", + "Videos List": "قائمة أشرطة الفيديو", + "Videos Merge": "دمج مقاطع الفيديو", + "Viewing Server Stats": "عرض إحصائيات الخادم", + "Warning": "تحذير", + "Wasabi Hot Cloud Storage": "تخزين السحابة الساخنة في Wasabi", + "Wasabi Hot Cloud Storage Upload Error": "خطأ في تحميل تخزين السحابة الساخنة", + "Watch": "مشاهدة", + "Watch Only": "مشاهدة فقط", + "Watch-Only": "الساعة فقط", + "Watching": "مشاهدة", + "Web Page": "صفحة ويب", + "WebDAV": "WebDav", + "WebM (libvpx)": "WebM (libvpx)", + "Webdav Error": "خطأ Webdav", + "WebdavErrorText": "لا يمكن حفظ. هل جعل الكاميرا المجلدات داخل اخترتها حفظ الدليل ؟ ", + "WebdavErrorTextCreatingDir": "لا يمكن إنشاء دليل.", + "WebdavErrorTextTryCreatingDir": "لا يمكن حفظ. محاولة إنشاء دليل.", + "Webhook": "webhook", + "Webhook URL": "Webhook URL", + "Websocket": "WebSocket", + "Websocket Connected": "WebSocket متصل", + "Websocket Disconnected": "WebSocket غير متصل", + "Wednesday": "الأربعاء", + "Welcome": "أهلا بك!", + "When Detector is Off": "عندما يكون الكاشف مطفئًا", + "When Detector is On": "عندما يكون الكاشف في", + "WhiteBalance": "توازن اللون الأبيض", + "WideDynamicRange": "مجال حركي واسع", + "Width": "العرض", + "X Point": "x نقطة", + "Y Point": "ص نقطة", + "Yes": "نعم", + "Zip and Download": "الرمز البريدي والتنزيل", + "Zipping Videos": "انقطاع مقاطع الفيديو", + "Zones": "المناطق", + "Zoom In": "التكبير في عنوان URL", + "Zoom In Stop": "التكبير في التوقف عن عنوان URL", + "Zoom Out": "تصغير عنوان URL", + "Zoom Out Stop": "تصغير وقف عنوان URL", + "a day": "اليوم", + "a few seconds": "بضع ثوان", + "a minute": "دقيقة", + "a month": "شهر", + "a year": "في السنة", + "aac": "الجميح للسيارات", + "aac (Default)": "الجميح للسيارات (افتراضي)", + "ac3": "AC3", + "accountActionFailed": "فشل إجراء الحساب", + "accountAdded": "تمت إضافة الحساب", + "accountAddedText": "تمت إضافة الحساب.", + "accountDeleted": "تم حذف الحساب", + "accountDeletedText": "تم حذف الحساب.", + "accountId": "معرف الحساب", + "accountSettingsDescription": "إدارة ملف التعريف الخاص بك وتعيين خيارات مثل Max Storage Come و Max عدد الأيام للحفاظ على مقاطع الفيديو.", + "accountSettingsError": "خطأ إعدادات الحساب", + "activatedText": "تم تنشيط التثبيت الخاص بك.", + "ago": "قبل", + "airplane": "مطار", + "alreadyLinked": "مرتبط بالفعل بحساب", + "an hour": "ساعة", + "apple": "تفاح", + "applicationKey": "مفتاح التطبيق", + "aws_accessKeyId": "الوصول إلى معرف المفتاح", + "aws_secretAccessKey": "مفتاح الوصول السري", + "backpack": "حقيبة ظهر", + "banana": "موز", + "baseball bat": "مضرب البيسبول", + "baseball glove": "قفاز البيسبول", + "bear": "دب", + "bed": "سرير", + "bench": "مقعد", + "bicycle": "دراجة", + "bindDN": "binddn", + "bird": "عصفور", + "blankPassword": "تترك فارغة إلى الحفاظ على نفس كلمة المرور", + "boat": "قارب", + "book": "الكتاب", + "bottle": "زجاجة", + "bowl": "صَحن", + "broccoli": "بروكلي", + "bus": "أوتوبيس", + "cake": "كيك", + "calendar": "التقويم", + "car": "السيارات", + "carrot": "جزرة", + "cat": "قطة", + "cell phone": "الهاتف الخلوي", + "chair": "كرسي", + "clientStreamFailedattemptingReconnect": "العميل ctream تحقق فشلت محاولة إعادة الاتصال.", + "clock": "ساعة حائط", + "coProcess Crashed for Monitor": "تحطمت المعالجة المشتركة للرصد", + "coProcess Unexpected Exit": "المعالجة المشتركة مخرج غير متوقع", + "coProcessor": "المعالج", + "coProcessor Started": "بدأ المعالج المشترك", + "coProcessor Stopped": "توقف المعالج", + "coProcessorTextStarted": "بدأ Coprocessor لمخرجات وحدة المعالجة المركزية فقط.", + "coProcessorTextStopped": "انتهى المعالج.", + "codecMismatchText1": "توفر الكاميرا بيانات دفق H.265 (HEVC) وتستخدم نسخًا كبرنامج ترميز الفيديو لقسم الدفق. قد لا يظهر دفقك من Shinobi على الأجهزة التي لا يمكنها استخدام برنامج الترميز هذا. يمكن لتطبيق Shinobi Mobile عرض هذه التدفقات.", + "codecMismatchText2": "برنامج ترميز الفيديو الذي اخترته غير قابل للتطبيق. توفر الكاميرا الخاصة بك بيانات دفق MJPEG وتستخدم نسخًا كبرنامج ترميز الفيديو لقسم الدفق. غيرت نوع الدفق إلى MJPEG.", + "codecMismatchText3": "برنامج ترميز الفيديو الذي اخترته غير قابل للتطبيق. توفر الكاميرا الخاصة بك بيانات دفق MJPEG وتستخدم نسخًا كبرنامج ترميز الفيديو لقسم التسجيل. غيرت برنامج ترميز الفيديو إلى libx264.", + "confirmDeleteFilter": "هل تريد حذف هذا الفلتر ؟ لا يمكنك استرداد.", + "contactAdmin": "اتصل بصيانة تثبيت Shinobi الخاص بك.", + "copy": "نسخ", + "couch": "أريكة", + "cow": "بقرة", + "cuda": "كودا (نفيديا نفينك)", + "cup": "فنجان", + "cuvid": "cuvid (nvidia nvenc)", + "days": "أيام", + "deleteApiKey": "حذف مفتاح API", + "deleteApiKeyText": "هل تريد حذف مفتاح API هذا؟ لا يمكنك استعادته.", + "deleteMonitorStateText1": "هل تريد حذف هذه الشاشة المسبقة؟ لا يمكن استرداد تكوينات الشاشة المرتبطة.", + "deleteMonitorStateText2": "هل تريد حذف مسبق هذا الشاشة؟", + "deleteScheduleText": "هل تريد حذف هذا الجدول؟ لن يتم تعديل مراقبة الإعدادات المسبقة المرتبطة ..", + "deleteSubAccount": "حذف الحساب الفرعي", + "deleteSubAccountText": "هل تريد حذف هذا الحساب الفرعي؟ لا يمكنك استعادته.", + "dining table": "طاولة الطعام", + "dog": "كلب", + "donut": "كعكة محلاة", + "drm": "مشاركة كائن DRM", + "dropBoxSuccess": "النجاح! الملفات المحفوظة إلى دروببوإكس الخاص بك.", + "dxva2": "DXVA2 (Video DirectX ، Windows)", + "elephant": "الفيل", + "eventFilterActionText": "هذه هي الإجراءات التي تحدث من ظروف المرشح التي نجحت. يشير \"الاختيار الأصلي\" إلى الخيار الذي اخترته في إعدادات شاشتك.", + "eventFilterErrorBrackets": "لديك عدد من الأقواس غير المقدمة. يتم تجاهلهم.", + "eventFiltersDescription": "مرشحات الإعداد عندما تحدث الأحداث.", + "failedLoginText1": "لقد فشلت في تسجيل الدخول عدة مرات. يجب أن تنتظر 15 دقيقة قبل المحاولة مرة أخرى.", + "failedLoginText2": "يرجى التحقق من بيانات اعتماد تسجيل الدخول الخاصة بك.", + "fieldMissingValueText1": "توفر الكاميرا بيانات دفق MJPEG. تحتاج إلى ضبط معدل التقاط الشاشة. سيحاول Shinobi اكتشافها وملءها تلقائيًا.", + "fieldTextAccelerator": "تسريع الأجهزة (Hwaccel) لفك تشفير التدفقات.", + "fieldTextAcodec": "برنامج ترميز الصوت للتسجيل.", + "fieldTextActionsCommand": "يمكنك استخدام هذا لإطلاق برنامج نصي على الأمر.", + "fieldTextActionsHalt": "اجعل الحدث لا يفعل شيئًا ، كما لو لم يحدث أبدًا.", + "fieldTextActionsIndifference": "تعديل الحد الأدنى من اللامبالاة المطلوبة للحدث.", + "fieldTextActionsRecord": "استخدم التسجيل التقليدي أو hotswap أو حذف بلا حراك مع خياراتهم المحددة حاليًا في قسم إعدادات الكشف العالمي.", + "fieldTextAduration": "حدد عدد microseconds التي يتم تحليلها للتحقيق في المدخلات. قم بتعيين 100000 إذا كنت تستخدم RTSP وتواجد مشكلات الدفق.", + "fieldTextAudioAlert": "الصوت عندما يحدث الحدث.", + "fieldTextAudioDelay": "تأخير حتى في المرة القادمة يمكن أن يبدأ الحدث في تنبيه. تقاس في ثوان.", + "fieldTextAudioNote": "الصوت عندما تظهر فقاعة المعلومات.", + "fieldTextAutoHost": "عنوان URL للتيار الكامل.", + "fieldTextAutoHostEnable": "قم بتغذية القطع الفردية المطلوبة لإنشاء عنوان URL للتيار أو توفير عنوان URL الكامل والسماح لشينوبي بتحليله لك.", + "fieldTextChannelHlsListSize": "عدد الأجزاء الحد الأقصى قبل حذف الأجزاء القديمة تلقائيًا.", + "fieldTextChannelHlsTime": "كم من الوقت يجب أن يكون كل قطعة فيديو ، في دقائق. سيتم رسم كل قطعة بواسطة العميل من خلال ملف M3U8. الأجزاء الأقصر تأخذ مساحة أقل.", + "fieldTextChannelPresetStream": "العلم المسبق لبعض الترميزات الفيديو. إذا وجدت أن الكاميرا تتعطل كل بضع ثوانٍ: حاول تركها فارغة.", + "fieldTextChannelStreamAcodec": "برامج الترميز الصوتي للبث.", + "fieldTextChannelStreamAcodecAac": "تستخدم لفيديو mp4.", + "fieldTextChannelStreamAcodecAc3": "تستخدم لفيديو mp4.", + "fieldTextChannelStreamAcodecAuto": "دع FFMPEG يختار.", + "fieldTextChannelStreamAcodecCopy": "تستخدم لفيديو mp4. يحتوي على استخدام منخفض للغاية لوحدة المعالجة المركزية ولكن بعض برامج ترميز الصوت تحتاج إلى أعلام مخصصة مثل -strict 2 لـ AAC.",
+ "fieldTextChannelStreamAcodecLibmp3lame": "تستخدم لفيديو mp4.",
+ "fieldTextChannelStreamAcodecLibopus": "تستخدم للفيديو webm.",
+ "fieldTextChannelStreamAcodecLibvorbis": "تستخدم للفيديو webm.",
+ "fieldTextChannelStreamAcodecNoAudio": "لا يوجد صوت ، هذا خيار يجب تعيينه في بعض أجزاء العالم لأسباب قانونية.",
+ "fieldTextChannelStreamFps": "السرعة التي يتم بها عرض الإطارات للعملاء ، في إطارات في الثانية. كن على دراية لا يوجد افتراضي. هذا يمكن أن يؤدي إلى استخدام النطاق الترددي العالي.",
+ "fieldTextChannelStreamQuality": "عدد منخفض يعني جودة أعلى. العدد الأعلى يعني أقل جودة.",
+ "fieldTextChannelStreamRotate": "قم بتغيير زاوية عرض دفق الفيديو.",
+ "fieldTextChannelStreamScaleX": "عرض صورة الدفق التي يتم إخراجها بعد المعالجة.",
+ "fieldTextChannelStreamScaleY": "ارتفاع صورة الدفق التي يتم إخراجها بعد المعالجة.",
+ "fieldTextChannelStreamType": "الطريقة التي سيتم استخدامها لاستهلاك دفق الفيديو.",
+ "fieldTextChannelStreamTypeFLV": "إرسال إطارات مشفرة FLV عبر WebSocket.",
+ "fieldTextChannelStreamTypeHLS(includesAudio)": "طريقة مماثلة لتدفقات Facebook Live. يتضمن الصوت إذا كان الإدخال يوفره. هناك تأخير من حوالي 4-6 ثوان لأن هذه الطريقة تسجل شرائح ثم يدفعها إلى العميل بدلاً من الدفع كما تنشئها.",
+ "fieldTextChannelStreamTypeMJPEG": "صورة قياسية JPEG صورة. لا صوت.",
+ "fieldTextChannelStreamTypePoseidon": "تم بناء Poseidon على رمز معالجة MP4 من Kevin Godell. يحاكي ملف MP4 المتداول ولكن باستخدام بيانات الدفق المباشر. يشمل الصوت. يمكن لبعض المتصفحات تشغيله مثل ملف MP4 عادي. تدفقات فوق HTTP أو WebSocket.",
+ "fieldTextChannelStreamVcodec": "برنامج ترميز الفيديو للدفق.",
+ "fieldTextChannelStreamVcodecAuto": "دع FFMPEG يختار.",
+ "fieldTextChannelStreamVcodecCopy": "تستخدم لفيديو mp4. يحتوي على استخدام منخفض للغاية لوحدة المعالجة المركزية ولكن لا يمكن استخدام مرشحات الفيديو والملفات قد يكون عملاقًا. من الأفضل إعداد إعدادات إعدادات MP4 الخاصة بك عند استخدام هذا الخيار.",
+ "fieldTextChannelStreamVcodecLibx264": "تستخدم لفيديو mp4.",
+ "fieldTextChannelStreamVcodecLibx265": "تستخدم لفيديو mp4.",
+ "fieldTextChannelSvf": "ضع مرشحات الفيديو FFMPEG في هذا المربع للتأثير على جزء البث. بدون مسافات.",
+ "fieldTextControlInvertY": "لأنه عندما يتم تثبيت الكاميرا رأسًا على عقب أو تستخدم عناصر تحكم رأسية مقلوبة.",
+ "fieldTextCrf": "عدد منخفض يعني جودة أعلى. العدد الأعلى يعني أقل جودة.",
+ "fieldTextCustDetect": "أعلام مخصصة ترتبط بكاشف الدفق للتحليل.",
+ "fieldTextCustDetectObject": "أعلام مخصصة ترتبط بكاشف الدفق للتحليل.",
+ "fieldTextCustInput": "أعلام مخصصة ترتبط بإدخال عملية FFMPEG.",
+ "fieldTextCustRecord": "أعلام مخصصة ترتبط بتسجيل عملية FFMPEG.",
+ "fieldTextCustSipRecord": "أعلام مخصصة ترتبط بالإخراج الذي تسجله الحدث.",
+ "fieldTextCustSnap": "الأعلام المخصصة التي ترتبط اللقطات.",
+ "fieldTextCustStream": "أعلام مخصصة ترتبط بالدفق (العرض جانب العميل) لعملية FFMPEG.",
+ "fieldTextCustomOutput": "إضافة إخراج مخصص مثل إطارات JPEG أو إرسال البيانات مباشرة إلى خادم آخر.",
+ "fieldTextCutoff": "في دقائق. متى تنطلق وبدء ملف فيديو جديد.",
+ "fieldTextDays": "عدد الأيام للحفاظ على مقاطع الفيديو قبل التطهير.",
+ "fieldTextDetailSubstreamInputRtspTransportAuto": "دع FFMPEG تقرر. عادة سيحاول UDP أولاً.",
+ "fieldTextDetailSubstreamInputRtspTransportTCP": "اضبطه على هذا إذا بدأ UDP في إعطاء نتائج غير مرغوب فيها.",
+ "fieldTextDetailSubstreamInputRtspTransportUDP": "FFMPEG يحاول هذا أولا.",
+ "fieldTextDetailSubstreamOutputHlsListSize": "عدد الأجزاء الحد الأقصى قبل حذف الأجزاء القديمة تلقائيًا.",
+ "fieldTextDetailSubstreamOutputHlsTime": "كم من الوقت يجب أن يكون كل قطعة فيديو ، في دقائق. سيتم رسم كل قطعة بواسطة العميل من خلال ملف M3U8. الأجزاء الأقصر تأخذ مساحة أقل.",
+ "fieldTextDetailSubstreamOutputPresetStream": "العلم المسبق لبعض الترميزات الفيديو. إذا وجدت أن الكاميرا تتعطل كل بضع ثوانٍ: حاول تركها فارغة.",
+ "fieldTextDetailSubstreamOutputStreamAcodec": "برامج الترميز الصوتي للبث.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAac": "تستخدم لفيديو mp4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAc3": "تستخدم لفيديو mp4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAuto": "دع FFMPEG يختار.",
+ "fieldTextDetailSubstreamOutputStreamAcodecCopy": "تستخدم لفيديو mp4. يحتوي على استخدام منخفض للغاية لوحدة المعالجة المركزية ولكن بعض برامج ترميز الصوت تحتاج إلى أعلام مخصصة مثل -strict 2 لـ AAC.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibmp3lame": "تستخدم لفيديو mp4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibopus": "تستخدم للفيديو webm.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibvorbis": "تستخدم للفيديو webm.",
+ "fieldTextDetailSubstreamOutputStreamAcodecNoAudio": "لا يوجد صوت ، هذا خيار يجب تعيينه في بعض أجزاء العالم لأسباب قانونية.",
+ "fieldTextDetailSubstreamOutputStreamFps": "السرعة التي يتم بها عرض الإطارات للعملاء ، في إطارات في الثانية. كن على دراية لا يوجد افتراضي. هذا يمكن أن يؤدي إلى استخدام النطاق الترددي العالي.",
+ "fieldTextDetailSubstreamOutputStreamQuality": "عدد منخفض يعني جودة أعلى. العدد الأعلى يعني أقل جودة.",
+ "fieldTextDetailSubstreamOutputStreamRotate": "قم بتغيير زاوية عرض دفق الفيديو.",
+ "fieldTextDetailSubstreamOutputStreamScaleX": "عرض صورة الدفق التي يتم إخراجها بعد المعالجة.",
+ "fieldTextDetailSubstreamOutputStreamScaleY": "ارتفاع صورة الدفق التي يتم إخراجها بعد المعالجة.",
+ "fieldTextDetailSubstreamOutputStreamType": "الطريقة التي سيتم استخدامها لاستهلاك دفق الفيديو.",
+ "fieldTextDetailSubstreamOutputStreamVcodec": "برنامج ترميز الفيديو للدفق.",
+ "fieldTextDetailSubstreamOutputStreamVcodecAuto": "دع FFMPEG يختار.",
+ "fieldTextDetailSubstreamOutputStreamVcodecCopy": "تستخدم لفيديو mp4. يحتوي على استخدام منخفض للغاية لوحدة المعالجة المركزية ولكن لا يمكن استخدام مرشحات الفيديو والملفات قد يكون عملاقًا. من الأفضل إعداد إعدادات إعدادات MP4 الخاصة بك عند استخدام هذا الخيار.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx264": "تستخدم لفيديو mp4.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx265": "تستخدم لفيديو mp4.",
+ "fieldTextDetailSubstreamOutputSvf": "ضع مرشحات الفيديو FFMPEG في هذا المربع للتأثير على جزء البث. بدون مسافات.",
+ "fieldTextDetector": "سيؤدي ذلك إلى إضافة إخراج آخر في أمر FFMPEG للكشف عن الحركة.",
+ "fieldTextDetectorAudio": "تحقق مما إذا كان الصوت قد حدث على شهادة Certiain. قد لا تكون القراءة المألوفة دقيقة لقياس العالم الحقيقي.",
+ "fieldTextDetectorBufferHlsListSize": "عدد الأجزاء الحد الأقصى قبل حذف الأجزاء القديمة تلقائيًا.",
+ "fieldTextDetectorBufferHlsTime": "كم من الوقت يجب أن يكون كل قطعة فيديو ، في ثوان. سيتم رسم كل قطعة بواسطة العميل من خلال ملف M3U8. الأجزاء الأقصر تأخذ مساحة أقل.",
+ "fieldTextDetectorColorThreshold": "مقدار الاختلاف المسموح به في بكسل قبل اعتباره الحركة.",
+ "fieldTextDetectorCommand": "الأمر الذي سيتم تشغيله. هذا هو ما يعادل تشغيل أمر شل من المحطة.",
+ "fieldTextDetectorCommandTimeout": "هذه القيمة هي مؤقت للسماح بالتشغيل التالي للنص الخاص بك. هذه القيمة في دقائق.",
+ "fieldTextDetectorFps": "كم عدد الإطارات الثانية لإرسالها إلى كاشف الحركة ؛ 2 هو الافتراضي.",
+ "fieldTextDetectorFrame": "هذا سوف يقرأ الإطار بأكمله للاختلافات بكسل. هذا هو نفسه إنشاء منطقة تغطي الشاشة بأكملها.",
+ "fieldTextDetectorHttpApi": "هل تريد السماح بمحفزات HTTP لهذه الكاميرا؟",
+ "fieldTextDetectorLisencePlate": "تمكين التعرف على لوحة الترخيص. قام OpenAlpr Plugin بتمكين هذا دائمًا.",
+ "fieldTextDetectorLisencePlateCountry": "اختر نوع اللوحات للتعرف عليها. فقط نحن والاتحاد الأوروبي مدعومون في هذا الوقت.",
+ "fieldTextDetectorLockTimeout": "قفل عندما يُسمح للمشغل التالي ، لتجنب التحميل الزائد لقاعدة البيانات واستقبال العملاء. تقاس بالميلي ثانية.",
+ "fieldTextDetectorMaxSensitivity": "يجب أن يكون تصنيف ثقة الحركة أقل من هذه القيمة ليتم اعتباره مشغلًا. اترك فارغًا بدون الحد الأقصى. تم تسمية هذا الخيار مسبقًا \"Max Lateperference\".",
+ "fieldTextDetectorNoiseFilter": "محاولة تصفية الحبوب أو الحركة المتكررة في لامبالاة معينة.",
+ "fieldTextDetectorNoiseFilterRange": "مقدار الاختلاف المسموح به في بكسل قبل اعتباره الحركة.",
+ "fieldTextDetectorNotrigger": "تحقق مما إذا كانت الحركة قد حدثت على فاصل. إذا حدث الحركة ، فسيتم إعادة تعيين الشيك.",
+ "fieldTextDetectorNotriggerCommand": "الأمر الذي سيتم تشغيله. هذا هو ما يعادل تشغيل أمر شل من المحطة.",
+ "fieldTextDetectorNotriggerCommandTimeout": "هذه القيمة هي مؤقت للسماح بالتشغيل التالي للنص الخاص بك. هذه القيمة في دقائق.",
+ "fieldTextDetectorNotriggerDiscord": "إذا لم يتم اكتشاف الحركة بعد فترة المهلة ، فسوف تتلقى إخطارًا بالخلاف.",
+ "fieldTextDetectorNotriggerTimeout": "يتم حساب المهلة في دقائق.",
+ "fieldTextDetectorNotriggerWebhook": "إرسال طلب الحصول على عنوان URL مع بعض القيم من الحدث.",
+ "fieldTextDetectorObjCount": "العد الكشف عن الكائنات.",
+ "fieldTextDetectorObjCountInRegion": "عدد الكائنات فقط داخل المناطق.",
+ "fieldTextDetectorPam": "استخدم كاشف حركة كيفن جوديل. هذا مدمج في شينوبي ولا يتطلب أي تكوين آخر لتفعيله.",
+ "fieldTextDetectorPtzFollow": "اتبع أكبر كائن تم اكتشافه مع PTZ؟ يتطلب تشغيل كاشف الكائن أو المصفوفات المتوفرة بالأحداث.",
+ "fieldTextDetectorRecordMethod": "هناك طرق متعددة للبدء في التسجيل عند حدوث حدث ، مثل الحركة. التسجيل التقليدي هو الأكثر سهولة في الاستخدام.",
+ "fieldTextDetectorSave": "حفظ أحداث الحركة في SQL. سيسمح ذلك بعرض الحركة عبر الفيديو خلال الوقت الذي حدثت فيه أحداث الحركة في عارض الطاقة.",
+ "fieldTextDetectorScaleX": "عرض الصورة التي يتم اكتشافها. أحجام أصغر تأخذ أقل وحدة المعالجة المركزية.",
+ "fieldTextDetectorScaleY": "ارتفاع الصورة التي يتم اكتشافها. أحجام أصغر تأخذ أقل وحدة المعالجة المركزية.",
+ "fieldTextDetectorSendFrames": "ادفع إطارات إلى المكون الإضافي المتصل ليتم تحليله.",
+ "fieldTextDetectorSendFramesObject": "ادفع إطارات إلى المكون الإضافي المتصل ليتم تحليله.",
+ "fieldTextDetectorSendVideoLength": "في ثوان. طول الفيديو الذي يتم إرساله إلى خدمة الإخطار الخاصة بك ، مثل البريد الإلكتروني أو الخلاف.",
+ "fieldTextDetectorSensitivity": "يجب أن يتجاوز تصنيف ثقة الحركة هذه القيمة المراد اعتبارها مشغلًا. يرتبط هذا الرقم مباشرة بتصنيف الثقة الذي تم إرجاعه بواسطة كاشف الحركة. تم تسمية هذا الخيار مسبقًا \"اللامبالاة\".",
+ "fieldTextDetectorThreshold": "الحد الأدنى لعدد الاكتشافات لإطلاق حدث حركة. يجب أن تكون الاكتشافات داخل الكاشف ، العتبة مقسومًا على ثواني الكاشف. على سبيل المثال ، إذا كان الكاشف FPS هو 2 وعتبة الزناد هي 3 ، فيجب أن تحدث ثلاثة اكتشافات في غضون 1.5 ثانية لتشغيل حدث حركة. هذه العتبة هي منطقة الكشف.",
+ "fieldTextDetectorTimeout": "سيتم تشغيل طول الوقت \"سجل الزناد\". هذا يقرأ في دقائق.",
+ "fieldTextDetectorTrigger": "سيطلب هذا الكاميرا لتسجيل ما إذا كانت تم ضبطها على \"الساعة فقط\" عند اكتشاف حدث ما.",
+ "fieldTextDetectorUseDetectObject": "قم بإنشاء إطارات للإرسال إلى أي مكون إضافي متصل.",
+ "fieldTextDetectorWebhook": "إرسال طلب الحصول على عنوان URL مع بعض القيم من الحدث.",
+ "fieldTextDetectorWebhookTimeout": "هذه القيمة عبارة عن مؤقت للسماح بالتشغيل التالي لـ WebHook. هذه القيمة في دقائق.",
+ "fieldTextDir": "موقع المكان الذي سيتم حفظ فيه الملفات المسجلة. يمكنك تكوين المزيد من المواقع باستخدام متغير AddStorage .",
+ "fieldTextEventDays": "عدد الأيام للحفاظ على الأحداث قبل التطهير.",
+ "fieldTextEventMonPop": "عندما يحدث حدث popout دفق الشاشة.",
+ "fieldTextEventRecordScaleX": "عرض صورة التسجيل المستندة إلى الحدث التي يتم إخراجها بعد المعالجة.",
+ "fieldTextEventRecordScaleY": "ارتفاع صورة التسجيل المستندة إلى الأحداث التي يتم إخراجها بعد المعالجة.",
+ "fieldTextExt": "نوع الملف لملف الفيديو المسجل.",
+ "fieldTextExtMP4": "نوع الملف قابل للتشغيل هو جميع متصفحات الويب الحديثة تقريبًا ، والتي تشمل الهاتف المحمول. تميل الملفات فقط إلى أن تكون أكبر إلا إذا قمت بتخفيض الجودة.",
+ "fieldTextExtWebM": "ملفات صغيرة ، انخفاض توافق العميل. جيد للتحميل إلى مواقع مثل YouTube.",
+ "fieldTextFactorAuth": "تمكين متطلبات ثانوية لتسجيل الدخول من خلال إحدى الطرق الممكّنة.",
+ "fieldTextFatalMax": "عدد المرات التي يجب إعادة محاولتها لاتصال الشبكة بين الخادم والكاميرا قبل إعداد الشاشة على تعطيلها. لا عشرية. ضبط على 0 لإعادة المحاولة إلى الأبد.",
+ "fieldTextFps": "السرعة التي يتم فيها تسجيل الإطارات على الملفات ، إطارات في الثانية. كن على دراية لا يوجد افتراضي. هذا يمكن أن يؤدي إلى ملفات كبيرة. من الأفضل ضبط هذا الكاميرا.",
+ "fieldTextHeight": "ارتفاع صورة الدفق.",
+ "fieldTextHlsListSize": "عدد الأجزاء الحد الأقصى قبل حذف الأجزاء القديمة تلقائيًا.",
+ "fieldTextHlsTime": "كم من الوقت يجب أن يكون كل قطعة فيديو ، في دقائق. سيتم رسم كل قطعة بواسطة العميل من خلال ملف M3U8. الأجزاء الأقصر تأخذ مساحة أقل.",
+ "fieldTextHost": "عنوان الاتصال",
+ "fieldTextHwaccel": "فك تشفير المحرك",
+ "fieldTextHwaccelVcodec": "فك تشفير المحرك",
+ "fieldTextInverseTrigger": "لتشغيل خارج المناطق المحددة. لن يؤدي إلى تمكين الكشف عن الإطار الكامل.",
+ "fieldTextIp": "المدى أو واحد",
+ "fieldTextIrCutFilterAuto": "يتم تنشيط مرشح CUT IR تلقائيًا بواسطة الجهاز.",
+ "fieldTextIrCutFilterOff": "تعطيل IR Cut Fiter. وضع الليل عادة.",
+ "fieldTextIrCutFilterOn": "تمكين الأشعة تحت الحمراء. وضع اليوم عادة.",
+ "fieldTextIsOnvif": "هل هذه كاميرا متوافقة مع onvif؟",
+ "fieldTextLang": "اللغة الأساسية لعناصر النص. للحصول على ترجمة كاملة ، أضف لغتك في conf.json على سبيل المثال: \"لغة\": \"EN_CA\" ، ",
+ "fieldTextLogDays": "عدد الأيام للحفاظ على السجلات قبل التطهير.",
+ "fieldTextLoglevel": "كمية البيانات التي يجب توفيرها أثناء القيام بالمهمة.",
+ "fieldTextLoglevelAllWarnings": "عرض جميع التحذيرات. استخدم هذا إذا لم تتمكن من معرفة الخطأ في الكاميرا.",
+ "fieldTextLoglevelFatal": "عرض أخطاء مميتة فقط.",
+ "fieldTextLoglevelOnError": "عرض جميع الأخطاء المهمة. ملاحظة: هذا لا يعرض دائمًا معلومات مهمة.",
+ "fieldTextLoglevelSilent": "لا أحد. هذا سوف يسيطر على كل قطع الأشجار.",
+ "fieldTextMail": "تسجيل الدخول للحسابات. سيحصل عنوان البريد الإلكتروني لحامل الحساب الرئيسي على إشعارات.",
+ "fieldTextMapRtspTransportAuto": "دع FFMPEG تقرر. عادة سيحاول UDP أولاً.",
+ "fieldTextMapRtspTransportTCP": "اضبطه على هذا إذا بدأ UDP في إعطاء نتائج غير مرغوب فيها.",
+ "fieldTextMapRtspTransportUDP": "FFMPEG يحاول هذا أولا.",
+ "fieldTextMaxKeepDays": "عدد الأيام التي يجب الاحتفاظ بها في مقاطع الفيديو قبل التطهير لهذا الشاشة على وجه التحديد.",
+ "fieldTextMid": "هذا هو معرف غير قابل للتغيير للشاشة. يمكنك تكرار الشاشة بالنقر المزدوج على معرف الشاشة وتغييره.",
+ "fieldTextMode": "هذه هي المهمة الأساسية للشاشة.",
+ "fieldTextModeDisabled": "شاشة غير نشطة ، لن يتم إنشاء أي عملية في هذا الوضع.",
+ "fieldTextModeRecord": "تسجيل مستمر. يتم تصنيع الأجزاء كل 15 دقيقة بشكل افتراضي.",
+ "fieldTextModeWatchOnly": "سيتم بث الشاشة فقط ، ولن يحدث أي تسجيل ما لم يتم طلب API أو الكاشف خلاف ذلك.",
+ "fieldTextMpass": "كلمة المرور للكاميرا الخاصة بك",
+ "fieldTextMuser": "تسجيل دخول المستخدم للكاميرا",
+ "fieldTextName": "هذا هو اسم العرض القابل للقراءة البشرية للشاشة.",
+ "fieldTextNotes": "التعليقات التي تريد مغادرتها لهذه الكاميرا.",
+ "fieldTextOnvifNonStandard": "هل هذه كاميرا غير قياسية؟",
+ "fieldTextOnvifPort": "عادة ما يتم تشغيل OnVIF على المنفذ 8000 . يمكن أن يكون هذا 80 وكذلك اعتمادًا على طراز الكاميرا.",
+ "fieldTextPass": "اترك فارغًا للحفاظ على نفس كلمة المرور أثناء تعديل الإعدادات.",
+ "fieldTextPasswordAgain": "يجب مطابقة حقل كلمة المرور إذا كنت ترغب في تغييره.",
+ "fieldTextPath": "الطريق إلى الكاميرا",
+ "fieldTextPort": "منفصل عن طريق الفواصل أو النطاق",
+ "fieldTextPortForce": "يمكن أن يسمح استخدام منفذ الويب الافتراضي بالتبديل التلقائي إلى منافذ أخرى لدارات مثل RTSP.",
+ "fieldTextPresetRecord": "العلم المسبق لبعض الترميزات الفيديو. إذا وجدت أن الكاميرا تتعطل كل بضع ثوانٍ: حاول تركها فارغة.",
+ "fieldTextPresetStream": "العلم المسبق لبعض الترميزات الفيديو. إذا وجدت أن الكاميرا تتعطل كل بضع ثوانٍ: حاول تركها فارغة.",
+ "fieldTextProbesize": "حدد مدى حجم مسبار التحليل للمدخلات. قم بتعيين 100000 إذا كنت تستخدم RTSP وتواجد مشكلات الدفق.",
+ "fieldTextProtocol": "البروتوكول الذي سيستخدم لاستهلاك دفق الفيديو.",
+ "fieldTextRecordScaleX": "عرض صورة الدفق.",
+ "fieldTextRecordScaleY": "ارتفاع صورة الدفق.",
+ "fieldTextRecordTimelapse": "إنشاء timelapse يعتمد على JPEG.",
+ "fieldTextRecordTimelapseMp4": "قم بإنشاء ملف MP4 في نهاية كل يوم ل timelapse.",
+ "fieldTextRecordTimelapseWatermark": "صورة يتم حرقها على إطارات الفيديو المسجل.",
+ "fieldTextRecordTimelapseWatermarkLocation": "موقع الصورة الذي سيتم استخدامه كعلامة مائية.",
+ "fieldTextRecordTimelapseWatermarkPosition": "صورة يتم حرقها على إطارات الفيديو المسجل.",
+ "fieldTextRotate": "قم بتغيير زاوية تسجيل دفق الفيديو.",
+ "fieldTextRtmpKey": "دفق مفتاح التدفقات الواردة على منفذ RTMP.",
+ "fieldTextRtspTransport": "بروتوكول النقل سوف تستخدم الكاميرا. TCP هو عادة الخيار الأفضل.",
+ "fieldTextRtspTransportAuto": "دع FFMPEG تقرر. عادة سيحاول UDP أولاً.",
+ "fieldTextRtspTransportHTTP": "طريقة الاتصال القياسية.",
+ "fieldTextRtspTransportTCP": "اضبطه على هذا إذا بدأ UDP في إعطاء نتائج غير مرغوب فيها.",
+ "fieldTextRtspTransportUDP": "FFMPEG يحاول هذا أولا.",
+ "fieldTextSfps": "حدد معدل الإطار (FPS) الذي توفر فيه الكاميرا دفقها.",
+ "fieldTextSignalCheck": "عدد المرات التي سيتحقق فيها عميلك من الدفق لمعرفة ما إذا كان على قيد الحياة. يتم حساب هذا في دقائق.",
+ "fieldTextSignalCheckLog": "هذا لجانب العميل فقط. سيتم عرضه في مؤشر ترابط السجل عند حدوث فحص إشارة جانب العميل.",
+ "fieldTextSize": "سوف يسمح كمية مساحة القرص Shinobi بالاستهلاك قبل التطهير. تتم قراءة هذه القيمة في Megabytes.",
+ "fieldTextSizeFilebinPercent": "في المئة من أقصى مبلغ تخزين يمكن أن يستخدمه أرشيف FileBin.",
+ "fieldTextSizeTimelapsePercent": "في المئة من الحد الأقصى للتخزين مبلغ إطارات timelapse يمكن أن تسجل.",
+ "fieldTextSizeVideoPercent": "في المئة من الحد الأقصى للتخزين مبلغ يمكن أن تسجل مقاطع الفيديو.",
+ "fieldTextSkipPing": "اختر ما إذا كانت هناك حاجة إلى نقطة انتشار ناجحة قبل بدء عملية الشاشة.",
+ "fieldTextSnap": "احصل على أحدث إطار في JPEG.",
+ "fieldTextSnapSecondsInward": "في ثوان",
+ "fieldTextSqllog": "استخدم هذا بحذر حيث يحب FFMPEG إلقاء بيانات غير ضرورية في بعض الأحيان والتي يمكن أن تؤدي إلى الكثير من صفوف قاعدة البيانات.",
+ "fieldTextSqllogNo": "لا هو الافتراضي.",
+ "fieldTextSqllogYes": "افعل هذا إذا كنت تواجه مشكلات متكررة فقط.",
+ "fieldTextStreamAcodec": "برامج الترميز الصوتي للبث.",
+ "fieldTextStreamAcodecAac": "تستخدم لفيديو mp4.",
+ "fieldTextStreamAcodecAc3": "تستخدم لفيديو mp4.",
+ "fieldTextStreamAcodecAuto": "دع FFMPEG يختار.",
+ "fieldTextStreamAcodecCopy": "تستخدم لفيديو mp4. يحتوي على استخدام منخفض للغاية لوحدة المعالجة المركزية ولكن بعض برامج ترميز الصوت تحتاج إلى أعلام مخصصة مثل -strict 2 لـ AAC.",
+ "fieldTextStreamAcodecLibmp3lame": "تستخدم لفيديو mp4.",
+ "fieldTextStreamAcodecLibopus": "تستخدم للفيديو webm.",
+ "fieldTextStreamAcodecLibvorbis": "تستخدم للفيديو webm.",
+ "fieldTextStreamAcodecNoAudio": "لا يوجد صوت ، هذا خيار يجب تعيينه في بعض أجزاء العالم لأسباب قانونية.",
+ "fieldTextStreamFlvType": "هذا هو لوحة القيادة Shinobi فقط. لا تزال كلتا طريقتان الدفق نشطة وجاهزة للاستخدام.",
+ "fieldTextStreamFps": "السرعة التي يتم بها عرض الإطارات للعملاء ، في إطارات في الثانية. كن على دراية لا يوجد افتراضي. هذا يمكن أن يؤدي إلى استخدام النطاق الترددي العالي.",
+ "fieldTextStreamLoop": "حلقة ملف ثابت بحيث يتصرف دفق الملف مثل دفق مباشر.",
+ "fieldTextStreamQuality": "عدد منخفض يعني جودة أعلى. العدد الأعلى يعني أقل جودة.",
+ "fieldTextStreamRotate": "قم بتغيير زاوية عرض دفق الفيديو.",
+ "fieldTextStreamScaleX": "عرض صورة الدفق التي يتم إخراجها بعد المعالجة.",
+ "fieldTextStreamScaleY": "ارتفاع صورة الدفق التي يتم إخراجها بعد المعالجة.",
+ "fieldTextStreamTimestamp": "ساعة يتم حرقها على إطارات دفق الفيديو.",
+ "fieldTextStreamTimestampBoxColor": "Timstamp Backdrop Color.",
+ "fieldTextStreamTimestampColor": "لون النص timstamp.",
+ "fieldTextStreamTimestampFont": "ملف الخط لأسلوب الطابع الزمني الخاص بك.",
+ "fieldTextStreamTimestampFontSize": "حجم الخط في حزب العمال.",
+ "fieldTextStreamTimestampX": "الموقف الأفقي من الطابع الزمني",
+ "fieldTextStreamTimestampY": "الموقف الرأسي من الطابع الزمني",
+ "fieldTextStreamType": "الطريقة التي سيتم استخدامها لاستهلاك دفق الفيديو.",
+ "fieldTextStreamTypeBase64OverWebsocket": "إرسال إطارات مشفرة BASE64 عبر WebSocket. هذا يتجنب التخزين المؤقت ولكن لا يوجد صوت.",
+ "fieldTextStreamTypeFLV": "إرسال إطارات مشفرة FLV عبر WebSocket.",
+ "fieldTextStreamTypeHLS(includesAudio)": "طريقة مماثلة لتدفقات Facebook Live. يتضمن الصوت إذا كان الإدخال يوفره. هناك تأخير من حوالي 4-6 ثوان لأن هذه الطريقة تسجل شرائح ثم يدفعها إلى العميل بدلاً من الدفع كما تنشئها.",
+ "fieldTextStreamTypeMJPEG": "صورة قياسية JPEG صورة. لا صوت.",
+ "fieldTextStreamTypePoseidon": "تم بناء Poseidon على رمز معالجة MP4 من Kevin Godell. يحاكي ملف MP4 المتداول ولكن باستخدام بيانات الدفق المباشر. يشمل الصوت. يمكن لبعض المتصفحات تشغيله مثل ملف MP4 عادي. تدفقات فوق HTTP أو WebSocket.",
+ "fieldTextStreamVcodec": "برنامج ترميز الفيديو للدفق.",
+ "fieldTextStreamVcodecAuto": "دع FFMPEG يختار.",
+ "fieldTextStreamVcodecCopy": "تستخدم لفيديو mp4. يحتوي على استخدام منخفض للغاية لوحدة المعالجة المركزية ولكن لا يمكن استخدام مرشحات الفيديو والملفات قد يكون عملاقًا. من الأفضل إعداد إعدادات إعدادات MP4 الخاصة بك عند استخدام هذا الخيار.",
+ "fieldTextStreamVcodecLibx264": "تستخدم لفيديو mp4.",
+ "fieldTextStreamVcodecLibx265": "تستخدم لفيديو mp4.",
+ "fieldTextStreamVf": "ضع مرشحات الفيديو FFMPEG في هذا المربع للتأثير على جزء البث. بدون مسافات.",
+ "fieldTextStreamWatermark": "صورة يتم حرقها على إطارات دفق الفيديو.",
+ "fieldTextStreamWatermarkLocation": "موقع الصورة الذي سيتم استخدامه كعلامة مائية.",
+ "fieldTextStreamWatermarkPosition": "صورة يتم حرقها على إطارات دفق الفيديو.",
+ "fieldTextTimestamp": "ساعة يتم حرقها على إطارات الفيديو المسجل.",
+ "fieldTextTimestampBoxColor": "Timstamp Backdrop Color.",
+ "fieldTextTimestampColor": "لون النص timstamp.",
+ "fieldTextTimestampFont": "ملف الخط لأسلوب الطابع الزمني الخاص بك.",
+ "fieldTextTimestampFontSize": "حجم الخط في حزب العمال.",
+ "fieldTextTimestampX": "الموقف الأفقي من الطابع الزمني",
+ "fieldTextTimestampY": "الموقف الرأسي من الطابع الزمني",
+ "fieldTextTvChannel": "سيكون لهذا الشاشة ميزات قناة تلفزيونية ممكّنة. ستتمكن من مشاهدته في قائمة قناتك التلفزيونية.",
+ "fieldTextTvChannelGroupTitle": "مجموعة مخصصة للقناة.",
+ "fieldTextTvChannelId": "معرف مخصص للقناة.",
+ "fieldTextType": "الطريقة التي سيتم استخدامها لاستهلاك دفق الفيديو.",
+ "fieldTextTypeDashcam(StreamerV2)": "دفق P2P المستند إلى WebSocket.",
+ "fieldTextTypeH.264/H.265/H.265+": "قراءة مقطع فيديو عالي الجودة تتضمن في بعض الأحيان الصوت.",
+ "fieldTextTypeHLS(.m3u8)": "قراءة مقطع فيديو عالي الجودة تتضمن في بعض الأحيان الصوت.",
+ "fieldTextTypeJPEG": "قراءة لقطات من عنوان URL وصنع دفق و/أو فيديو منها.",
+ "fieldTextTypeLocal": "قراءة بطاقات الالتقاط أو كاميرات الويب أو الكاميرات المتكاملة.",
+ "fieldTextTypeMJPEG": "على غرار JPEG باستثناء معالجة الإطار يتم بواسطة FFMPEG ، وليس شينوبي.",
+ "fieldTextTypeMPEG4(.mp4/.ts)": "ملف ثابت. اقرأ بمعدل أقل ويجب عدم استخدامه في البث المباشر الفعلي.",
+ "fieldTextTypeMxPEG": "Mobotix MJPEG تيار",
+ "fieldTextTypeRTMP": "تعلم الاتصال هنا: المقالة: كيفية دفع التدفقات عبر RTMP إلى Shinobi ",
+ "fieldTextTypeShinobiStreamer": "WebSocket B2P Stream المستند إلى JPEG.",
+ "fieldTextVcodec": "برنامج ترميز الفيديو للتسجيل.",
+ "fieldTextVf": "ضع مرشحات الفيديو FFMPEG في هذا المربع للتأثير على جزء التسجيل. بدون مسافات.",
+ "fieldTextWallClockTimestampIgnore": "بناء جميع بيانات الكاميرا الواردة في وقت الكاميرا بدلاً من وقت الخادم.",
+ "fieldTextWatchdogReset": "إذا كان هناك تداخل في سجل الزناد في حالة إعادة تعيينه.",
+ "fieldTextWatermark": "صورة يتم حرقها على إطارات الفيديو المسجل.",
+ "fieldTextWatermarkLocation": "موقع الصورة الذي سيتم استخدامه كعلامة مائية.",
+ "fieldTextWatermarkPosition": "صورة يتم حرقها على إطارات الفيديو المسجل.",
+ "fieldTextWidth": "عرض صورة الدفق.",
+ "fire hydrant": "صنبور الاطفاء",
+ "flv": "FLV",
+ "for Global Access": "من أجل الوصول العالمي",
+ "fork": "شوكة",
+ "frisbee": "الطبق الطائر",
+ "getAMonitor": "احصل على شاشة",
+ "getATvChannel": "احصل على قنوات تلفزيونية للشاشة",
+ "getATvChannelText": "احصل على تدفقات H.264 المتاحة لشاشة واحدة في قائمة تشغيل .m3u8.",
+ "getAllMonitors": "احصل على جميع الشاشات",
+ "getAllTvChannels": "احصل على جميع القنوات التلفزيونية",
+ "getAllTvChannelsText": "الحصول على جميع تدفقات H.264 في قائمة تشغيل .m3u8. قم بتمكين خيار قناة التلفزيون في إعدادات الشاشة لمشاهدة تدفقاتها في هذه القائمة.",
+ "getUserInfo": "الحصول على معلومات المستخدم",
+ "getVideos": "احصل على مقاطع فيديو",
+ "getVideosForMonitor": "احصل على مقاطع فيديو للشاشة",
+ "giraffe": "زرافة",
+ "h264_cuvid": "H.264 Cuvid",
+ "h264_mmal": "H.264 (Raspberry Pi)",
+ "h264_nvenc": "H.264 Nvenc (Nvidia HW Accel)",
+ "h264_omx": "H.264 OpenMax (Raspberry PI)",
+ "h264_qsv": "H.264 (فيديو مزامنة سريع)",
+ "h264_vaapi": "H.264 VA-API (Intel HW Accel)",
+ "h265BrowserText1": "إذا كنت تحاول تشغيل ملف H.265 ، فقد تحتاج إلى تنزيله وفتحه في تطبيق آخر مثل VLC.",
+ "hair drier": "مجفف الشعر",
+ "handbag": "حقيبة يد",
+ "hevc_cuvid": "H.265 Cuvid",
+ "hevc_nvenc": "H.265 Nvenc (Nvidia HW Accel)",
+ "hevc_qsv": "H.265 (فيديو مزامنة سريع)",
+ "hevc_vaapi": "H.265 VA-API (Intel HW Accel)",
+ "hlsOptions": "خيارات HLS",
+ "hlsOptionsInvalid": "خيارات HLS غير صالحة",
+ "horse": "حصان",
+ "hot dog": "نقانق",
+ "hour": "ساعة",
+ "hours": "ساعات",
+ "hwaccel": "محرك التسارع",
+ "hwaccel_device": "جهاز Hwaccel",
+ "hwaccel_vcodec": "فك تشفير الفيديو",
+ "in": "في",
+ "in Days": "في الايام",
+ "in seconds": "في ثوان",
+ "keyId": "المعرف الرئيسي",
+ "keyboard": "لوحة المفاتيح",
+ "kite": "طائرة ورقية",
+ "knife": "سكين",
+ "laptop": "حاسوب محمول",
+ "lastLogin": "آخر تسجيل دخول",
+ "libmp3lame": "libmp3lame",
+ "libopus": "libopus",
+ "libvorbis (Default)": "libvorbis (الافتراضي)",
+ "libvpx (Default)": "libvpx (الافتراضي)",
+ "libvpx-vp9": "libvpx-vp9",
+ "libx264": "libx264",
+ "libx264 (Default)": "libx264 (الافتراضي)",
+ "libx265": "libx265",
+ "liveGridDescription": "Live Grid هي شاشة الدفق المتعددة لشينوبي. تم تصميم طريقة المشاهدة هذه بشكل أساسي لسطح المكتب.",
+ "loginHandleUnbound": "لقد تم إلغاء ارتباط تسجيل الدخول من هذا الحساب.",
+ "microwave": "الميكروويف",
+ "migrateText1": " نوع الإدخال لا يمكن تحليله. يرجى تعيينه يدويًا.",
+ "minute": "اللحظة",
+ "minutes": "دقائق",
+ "mjpeg_cuvid": "MJPEG CUVID",
+ "modifyVideoText1": "طريقة لا وجود لها. تحقق للتأكد من أن آخر قيمة URL ليست فارغة.",
+ "monSavedButNotCopied": "تم حفظ شاشتك ولكن لم يتم نسخها إلى أي شاشة أخرى.",
+ "monitorConfigFinderDescription": "ستساعدك هذه الأداة في البحث عن تكوينات للكاميرات التي نشرها المجتمع. جميع المستضافة على shinobihub . يمكنك نشرك أيضًا ، فسيساعد المجتمع حقًا :)",
+ "monitorEditFailedMaxReached": "حسابك قد بلغ أقصى عدد من الكاميرات التي يمكن إنشاؤها. التحدث إلى مسؤول إذا كنت ترغب في تغيير هذا.",
+ "monitorEditText1": "بيانات غير صالحة ، تحقق لمعرفة هذا هو صالح استيراد السلسلة.",
+ "monitorEditText2": "غير صالحة تفاصيل السلسلة. تحقق لمعرفة ما هو سلسلة JSON و غير منتظم وجوه يتم تمريرها.",
+ "monitorGetText1": "الطلب غير المكتمل ، وإزالة الماضي مائل في URL أو وضع مقبول القيمة.",
+ "monitorStateNotEnoughChanges": "تحتاج إلى إجراء تغيير في تكوين الشاشة الخاص بك قبل محاولة إضافته إلى إعداد مسبق.",
+ "monitorStatesError": "مراقبة خطأ الإعدادات المسبقة",
+ "months": "أشهر",
+ "motorcycle": "دراجة نارية",
+ "mouse": "الفأر",
+ "mpeg2_mmal": "MPEG-2 (Raspberry PI)",
+ "mpeg2_qsv": "MPEG2 (فيديو مزامنة سريع)",
+ "mpeg4_cuvid": "MPEG4 Cuvid",
+ "mpeg4_mmal": "MPEG-4 (Raspberry PI)",
+ "noLoginTokensAdded": "لا توجد تسجيلات دخول بديلة مرتبطة بهذا الحساب.",
+ "noSpecialCharacters": "بدون مسافات أو أحرف خاصة.",
+ "noTriggerText": "إذا لم يتم اكتشاف الحركة بعد فترة المهلة ، فسوف تتلقى إخطارًا بالخلاف.",
+ "noUndoForAction": "لا يمكنك التراجع عن هذا الإجراء.",
+ "notActivatedText": "لقد فشل التثبيت في التنشيط.",
+ "notEnoughFramesText1": "لا توجد إطارات كافية للتجميع.",
+ "notPermitted1": "هذا الإجراء لا يسمح به مسؤول حسابك.",
+ "on": "على",
+ "on Error": "على خطأ",
+ "on Event": "على الحدث",
+ "onvifdeviceManagerGlobalTip": "يسمح OnVIF بتعديل الإعدادات الداخلية للكاميرا. Onvif هو إلى حد ما من مصطلح مظلة ، يمكن أن يعني الكثير من الأشياء للأسف. مع هذا هو الحال ، قد ترى خيارًا في هذه الأداة ولكن قد لا يكون قابلاً للتحرير. هذا عادةً لأن بائع الكاميرا لم يضف هذه الطريقة أو انحرفت عن استخدامه المقصود. في هذه الحالات ، ستحتاج إلى إدخال تكوين الكاميرا من خلال الطريقة المحددة لبائع الكاميرا ، وهذا يفتح عمومًا عنوان IP للكاميرا في متصفح الويب الخاص بك.",
+ "onvifdeviceSavedFoundErrorText": "قد تكون بعض الإعدادات قد عادت إلى قيمة سابقة. من الممكن أن يكون الخيار المعدل غير متوفر مع هذه الكاميرا من خلال ONVIF.",
+ "onvifdeviceSavedText": "تم حفظ الإعدادات الداخلية للكاميرا. قد تحتاج إلى إعادة تشغيل الكاميرا لتصبح هذه التغييرات سارية المفعول.",
+ "openImagesDownloadConfirm": "هل أنت متأكد من أنك ترغب في بدء تنزيل الصور والمربعات المحيطة (المصفوفات المسبقة) من OpenImages؟",
+ "openImagesDownloadConfirmStop": "هل أنت متأكد أنك تريد التوقف عن التدريب؟",
+ "opencl": "opencl",
+ "opencvCascadesText": "إذا لم ترى شيئًا هنا ، فما عليك سوى تنزيل هذه الحزمة من cascades . قم بإسقاطها إلى Plugins/OpenCV/Cascades ثم اضغط على Refresh .",
+ "orange": "البرتقالي",
+ "oven": "فرن",
+ "p2pServerNotSelectedText": "حدد خادمًا من القائمة واضغط على حفظ. انتظر 10 ثوان ثم حاول فتح لوحة القيادة عن بعد.",
+ "p2pSettingsText1": "ستحتاج إلى تحديث هذه الصفحة لتطبيق التغييرات.",
+ "parking meter": "عداد موقف السيارات",
+ "performanceOptimizeText1": "توفر الكاميرا الخاصة بك بيانات دفق H.264. يمكنك تعيين نوع الدفق على HLS و Poseidon وبرنامج ترميز الفيديو لنسخه.",
+ "person": "شخص",
+ "pizza": "بيتزا",
+ "possibleInternalError": "خطأ داخلي ممكن",
+ "postDataBroken": "تحقق من تنسيق JSON. تأكد من أنه تم تشييده وتحديده تحت \"البيانات\"",
+ "potted plant": "النبات المحفوظ بوعاء",
+ "powerVideoEventLimit": "لقد حددت حد الحدث العالي. هل أنت متأكد أنك تريد تقديم هذا الطلب؟",
+ "privateKey": "مفتاح سري",
+ "qsv": "QSV",
+ "rebootingCamera": "إعادة تشغيل الكاميرا",
+ "refrigerator": "ثلاجة",
+ "remote": "التحكم عن بعد",
+ "restartRequired": "إعادة تشغيل Core Shinobi مطلوب للتغييرات لتسريع.",
+ "sandwich": "ساندويتش",
+ "scissors": "مقص",
+ "separateByCommasOrRange": "منفصل عن طريق الفواصل أو النطاق",
+ "setMaxStorageAmountText": "يجب عليك تعيين مبلغ تخزين Max في إعدادات حسابك الموجودة على اليسار. ابحث عن الخيار ضمن قسم الملف الشخصي. الافتراضي هو 10 جيجابايت.",
+ "sheep": "خروف",
+ "sink": "حوض",
+ "sizePurgeLockedText": "يبدو أن قفل تطهير حجم (DeleteOverMax) فشل في فتحه. فتح الآن ...",
+ "skateboard": "لوح تزلج",
+ "skipPingText1": "حاول تعيين \"تخطي ping\" إلى نعم.",
+ "skis": "الزلاجات",
+ "snowboard": "لوح التزلج",
+ "sorryNo": "اسف لا",
+ "sorryNothingWasFound": "آسف ، لم يتم العثور على شيء.",
+ "spoon": "ملعقة",
+ "sports ball": "كرة رياضية",
+ "startUpText0": "حجم التحقق من أشرطة الفيديو",
+ "startUpText1": "الغاية من حجم التحقق من أشرطة الفيديو",
+ "startUpText2": "جميع المستخدمين فحص الانتظار لإغلاق الملفات المفتوحة وإزالة الملفات على حد المستخدم",
+ "startUpText3": "في انتظار أن تعطي لم تنته الفيديو تحقق بعض الوقت. 3 ثوان.",
+ "startUpText4": "بدأت كل مجموعة شاشات لمشاهدة وتسجيل",
+ "startUpText5": "شينوبي جاهز.",
+ "startUpText6": "تم العثور على مقاطع الفيديو اليتيمة وإدراجها",
+ "stop sign": "لافتة توقف",
+ "subAccountManager": "مدير الحساب الفرعي",
+ "substreamConnectionText": "يمكنك ترك تفاصيل الاتصال كما هو الحال إذا كنت تريد استخدام معلومات الاتصال الرئيسية المذكورة أعلاه.",
+ "substreamOutputText": "هنا يمكنك تعيين تكوين الدفق عند الطلب. تعرف على الكمون من أنواع الدفق هنا. ",
+ "substreamText": "هذه طريقة عند الطلب لعرض البث المباشر. يمكنك القيام بذلك حتى تتوفر عملية المشاهدة فقط عندما يراقب شخص ما أو لاستخدامه للتبديل بين الدقة المنخفضة والعالية.",
+ "suitcase": "حقيبة سفر",
+ "superAdminText": "\"السوبر.سلمان\" لا وجود لها. الرجاء إعادة تسمية \"سوبر.العينة.سلمان\" إلى \"السوبر.سلمان\".",
+ "superAdminTitle": "شينوبي : المشرف المميز",
+ "surfboard": "لوح ركوب الأمواج",
+ "teddy bear": "دمية دب",
+ "tennis racket": "مضرب التنس",
+ "tie": "رَابِطَة",
+ "toaster": "محمصة",
+ "toilet": "الحمام",
+ "tokenNotUserBound": "لا يرتبط مقبض تسجيل الدخول هذا بمستخدم على هذا الخادم!",
+ "tokenNotUserBoundPt2": "اكتب بيانات الاعتماد الخاصة بك ثم استخدم زر تسجيل الدخول إلى Google للربط بسرعة.",
+ "toothbrush": "فرشاة الأسنان",
+ "total": "مجموع",
+ "traffic light": "إشارة المرور",
+ "train": "قطار",
+ "truck": "شاحنة",
+ "tv": "تلفزيون",
+ "umbrella": "مظلة",
+ "undefined": "غير معرف",
+ "undoAllUnsaveChanges": "هل انت متأكد من أنك تريد أن تفعل هذا؟ هذا سوف التراجع عن جميع التغييرات غير المحفوظة.",
+ "unexpectedExitText": "سيتم العثور على معلومات حول هذا المخرج قبل هذا السجل. بالإضافة إلى ذلك ، هنا أمر FFMPEG الذي تم استخدامه عندما تحطمت العملية.",
+ "updateCamerasInternalSettings": "تحديث الإعدادات الداخلية للكاميرا؟",
+ "updateKeyText1": "\"updateKey\" مفقود من \"conf.json\", لا يمكن أن تفعل التحديثات هذه الطريقة حتى يمكنك إضافته.",
+ "updateKeyText2": "\"updateKey\" غير صحيحة.",
+ "updateNotice1": "تحديث Shinobi يعني ملفات الكتابة فوق. إذا قمت بتعديل أي ملفات بنفسك ، فيجب عليك تحديث Shinobi يدويًا. لن يتم تعديل التكوينات وملفات الفيديو الخاصة بك.",
+ "useSubStreamOnlyWhenWatching": "فقط عند المشاهدة ، استخدم Substream",
+ "vaapi": "VAAPI (VA-API)",
+ "vase": "مزهرية",
+ "vda": "VDA (تسريع أجهزة Apple VDA)",
+ "vdpau": "VDPAU",
+ "videoBuildingText1": "الفيديو بناء حاليا. تحقق مرة أخرى لاحقًا.",
+ "videotoolbox": "Videotoolbox",
+ "vp8_cuvid": "VP8 NVenc (Nvidia HW Accel)",
+ "vp8_qsv": "VP8 (فيديو مزامنة سريع)",
+ "vp9_cuvid": "VP9 Nvenc (Nvidia HW Accel)",
+ "wannaReset": "هل تريد إعادة التعيين؟",
+ "willTriggerAnEvent": "سوف يؤدي إلى حدث",
+ "wine glass": "كأس نبيذ",
+ "years": "سنوات",
+ "zebra": "الحمار الوحشي"
+}
\ No newline at end of file
diff --git a/languages/cs.json b/languages/cs.json
new file mode 100644
index 00000000..0a035267
--- /dev/null
+++ b/languages/cs.json
@@ -0,0 +1,1692 @@
+{
+ "\"No Motion\" Detector": "Detektor „žádný pohyb“",
+ "# of Allow MJPEG Clients": "# Povolit MJPEG Klienti 0 pro nekonečné ",
+ "'Already Installing...'": "\"Již nainstaluji ... '",
+ "180 Degrees": "180 stupňů",
+ "2-Factor Authentication": "Ověřování 2-faktorů",
+ "90 Clockwise": "90 ve směru hodinových ručiček",
+ "90 Clockwise and Vertical Flip": "90 ve směru hodinových ručiček a vertikální převrácení",
+ "90 Counter Clockwise and Vertical Flip (default)": "90 proti směru hodinových ručiček a vertikální flip (výchozí)",
+ "AND": "A",
+ "API": "API",
+ "API Key": "Klíč API",
+ "API Key Action Failed": "Akce klíče API selhala",
+ "API Key Added": "Přidán klíč API",
+ "API Key Deleted": "Smazáno klíč API",
+ "API Keys": "Klávesy API",
+ "APIKeyAddedText": "Tento klíč můžete použít nyní.",
+ "APIKeyDeletedText": "Klíč byl odstraněn. Už to nebude fungovat.",
+ "ASC": "ASC",
+ "Accelerator": "Plynový pedál",
+ "Account Edited": "Účet upraven",
+ "Account Info": "Informace o účtu",
+ "Account Information": "Informace o účtu",
+ "Account Privileges": "Oprávnění účtu",
+ "Account Save": "Uložení účtu",
+ "Account Settings": "Nastavení účtu",
+ "AccountEditText1": "Nemohl upravit. Obnovit stránka, pokud problém pokračuje.",
+ "Accounts": "Účty",
+ "Accuracy Mode": "Režim přesnosti",
+ "Action for Selected": "Akce pro vybrané",
+ "Activated": "Aktivováno",
+ "Active Monitors": "Aktivní monitory",
+ "Add": "Přidat",
+ "Add All": "Přidat vše",
+ "Add Camera": "Přidejte kameru",
+ "Add Cameras": "Přidejte kamery",
+ "Add Channel": "Přidat kanál",
+ "Add Input Feed": "Přidejte vstupní zdroj",
+ "Add Map": "Přidejte mapu",
+ "Add Monitor": "Přidat monitor",
+ "Add New": "Přidat nový",
+ "AddToPreset": "Přidat k předvolbě",
+ "Additional Inputs": "Další vstupy",
+ "Admin": "Admin",
+ "Advanced": "Pokročilý",
+ "After": "Po",
+ "Again": "Znovu",
+ "Age": "Stáří",
+ "Alert Sound": "Varovný zvuk",
+ "Alert Sound Delay": "Upozornění zpoždění zvuku",
+ "All Logs": "Všechny protokoly",
+ "All Monitors": "Všechny monitory",
+ "All Monitors and Privileges": "Všechny monitory a privilegia",
+ "All Privileges": "Všechna privilegia",
+ "All Warnings": "Všechna varování",
+ "All streams in first feed": "Všechny potoky v prvním krmivu",
+ "Allow API Trigger": "Povolte spoušť API",
+ "Allow Next Alert": "Povolit další upozornění",
+ "Allow Next Command": "Povolit další příkaz",
+ "Allow Next Discord Alert": "Povolte další výstrahu na Discord Když jsou zobrazeny krabice šířky a výšky, měli byste je nastavit na 640x480 nebo níže. Tím se optimalizují rychlost čtení rámů.
", + "Died": "Zemřel", + "Digest Authentication": "Autentizace digest", + "Disable": "Zakázat", + "Disable Night Vision": "Zakázat noční vidění adresa URL ", + "Disable Nightvision": "Zakázat noční vizi", + "Disabled": "Deaktivované", + "Discord": "Svár", + "Discord Alert on Trigger": "Upozornění na Discord na Trigger", + "Discord Bot": "Discord Bot", + "Discord on No Motion": "Discord na „Žádný pohyb“", + "DiscordErrorText": "Odeslání k Discordu způsobilo chybu", + "DiscordFailedText": "Odeslání k Discordu selhalo", + "DiscordLoggedIn": "Discord Bot ověřeno", + "DiscordNotEnabledText": "Bot Discord není povolen, povolte jej v nastavení vašeho účtu.", + "Documentation": "Dokumentace", + "Does Not Contain": "Neobsahuje", + "Don't Show for 1 Week": "Nezobrazujte se po dobu 1 týdne", + "Don't Stretch Monitors": "Netáhněte monitory", + "Don't show this anymore": "Už to neukazujte", + "Done": "Hotovo", + "Done!": "Hotovo!", + "DontAddToPreset": "Nepřidávejte do předvolby", + "Double Quote Directory": "Directory Double Quote DirectoryTato část označí primární metodu streamování a jeho nastavení. Tento proud se zobrazí na palubní desce. Pokud se rozhodnete použít HLS, JPEG nebo MJPEG, pak můžete konzumovat proud prostřednictvím jiných programů. rámečky.
", + "Streamed Logs": "Streamované protokoly", + "Streamer": "Stuha", + "Streams": "Streamy", + "Sub-Accounts": "Dílčí účty", + "Subdivision": "Pododdělení", + "Substream": "Substream", + "Substream Process": "Proces substream", + "SubstreamNotConfigured": "Substream není nakonfigurován. Otevřete nastavení monitoru a nakonfigurujte jej.", + "Subtitle": "Podtitul", + "Success": "Úspěch", + "Sunday": "Neděle", + "Superuser": "Superužitel", + "Superuser Logs": "Protokoly superužitelů", + "Switch on for Still Image": "Zapněte se pro statický obrázek", + "System": "Systém", + "System Level": "Úroveň systému", + "TCP": "TCP", + "TV Channel": "Televizní kanál", + "TV Channel Group": "Skupina televizních kanálů", + "TV Channel ID": "ID televizního kanálu", + "Telegram": "Telegram", + "Text Box Color": "Barva textového pole", + "Text Color": "Barva textu", + "Text criteria unsupported for Object Count tests, Ignoring Conditional": "Textová kritéria nepodporovaná pro testy počtu objektů, ignorování podmíněného", + "Themes": "Témata", + "There are no monitors that you can view with this account.": "Na tomto účtu si můžete prohlédnout žádné monitory.", + "Threads": "Vlákna", + "Thumbnail": "Miniatura", + "Thursday": "Čtvrtek", + "Tile Size": "Velikost dlaždic", + "Time": "Čas", + "Time Created": "Vytvořený čas", + "Time Left": "Zbývající čas", + "Time Occurred": "Došlo k času", + "Time-lapse": "Časová prodleva", + "Time-lapse Tool": "Časosběrný nástroj", + "TimeZone": "Časové pásmo", + "Timed": "Načasované", + "Timelapse": "Časová prodleva", + "Timelapse Frames Share": "Sdílet snímky časové doby", + "Timelapse Frames Video": "VIDEO TIMELAPSE STRAMES", + "Timelapse Video Build Complete": "Vytvoření videa Timelapse Complete", + "Timelapse Watermark": "Vodoznak timelapse", + "Timeout": "Časový limit", + "Timeout Reset on Next Event": "Obnovení časového limitu na další události", + "Timeout Reset on Next Motion": "Resetování časového limitu na další pohyb", + "Timezone": "Časové pásmo", + "Timezone Offset": "Offset TimeZone", + "Title": "Titul", + "Today": "Dnes", + "Toggle Sidebar": "Přepínač postranního panelu", + "Toggle Substream": "Přepínač substream", + "Token": "Žeton", + "Top Left": "Vlevo nahoře", + "Top Right": "Vpravo nahoře", + "Train": "Vlak", + "TrainConfirm": "Jste si jisti, že chcete začít trénovat? To může trvat déle než 12 hodin s více než 500 obrázky. Tím bude spotřebovat velké množství zdrojů, jako je RAM a/nebo CPU.", + "TrainConfirmStop": "Jste si jisti, že chcete přestat trénovat?", + "Trainer Engine": "Trainer Engine", + "Trigger Blocked": "Spouštěč blokován", + "Trigger Camera Groups": "Spouštěcí skupiny fotoaparátů", + "Trigger Event": "Spouštěcí událost", + "Trigger Group to Record": "Spouštěcí skupina pro nahrávání", + "Trigger Record": "Spouštěcí záznam", + "Trigger Successful": "Spouštět úspěšný", + "Trigger Threshold": "Spouštěcí prahová hodnota", + "Tuesday": "úterý", + "Turn Speed": "Otočit rychlost", + "Type": "Typ", + "UDP": "UDP", + "URL": "URL", + "URL Stop Timeout": "URL STOP TIMEOUT Run Stop URL po x milisekundách ", + "US": "NÁS", + "UTCDateTime": "datum", + "Unable to Launch": "Nelze spustit", + "UnabletoLaunchText": "Nejprve uložte nový monitor. Poté se pokuste spustit editor regionu.", + "Uncommon Objects": "Neobvyklé objekty", + "Uniform": "Jednotný", + "Unlink": "Unlink", + "Unlink Login": "Nelink přihlášení?", + "Unlinked": "Nepřipojeno", + "Up": "Up adresa URL ", + "Up Stop": "Up Stop adresa URL ", + "Update": "Aktualizace", + "Update to Development": "Aktualizace vývoje", + "Update to Master": "Aktualizace Master", + "Upload Bandwidth": "Nahrajte šířku pásma", + "Upload File": "Nahrát soubor", + "Uploaded Only": "Pouze nahráno", + "Uploaders": "Nahrávače", + "Use Built-In": "Použijte vestavěný", + "Use Camera Timestamps": "Používejte časová razítka fotoaparátu", + "Use Global Amazon S3 Video Storage": "Použijte Global Amazon S3 Video Storage", + "Use Global Backblaze B2 Video Storage": "Použijte Global BackBlaze B2 Video Storage", + "Use Global Wasabi Hot Cloud Storage Video Storage": "Používejte globální úložiště s úložištěm cloudu Wasabi", + "Use Global WebDAV Video Storage": "Použijte globální úložiště videa WebDAV", + "Use HTML5 Play Method": "Použijte metodu přehrávání HTML5", + "Use Max Storage Amount": "Použijte maximální částku úložiště", + "Use Raw Snapshot": "Použijte surový snímek", + "Use Substream": "Použijte substream", + "Use coProcessor": "Použijte koprocesor", + "UseCount": "Usecunt", + "User Log": "Protokol uživatele", + "User Not Found": "Uživatel nenalezen", + "Username": "Uživatelské jméno", + "VA-API": "Va-api", + "Value": "Hodnota", + "Video": "Video", + "Video Bit Rate": "Bitová rychlost videa", + "Video Codec": "Video Codec", + "Video Configuration": "Konfigurace videa", + "Video Filter": "Video filtr", + "Video Finished": "Video dokončeno", + "Video Length (minutes) and Motion Count per video": "Délka videa (minuty) a počet pohybů na video", + "Video Limit": "Video limit", + "Video Record Rate": "Sazba záznamu videa", + "Video Set": "Video sada", + "Video Share": "Sdílet videa", + "Video Status": "Stav videa", + "Video and Time Span (Minutes)": "Video a časové rozpětí (minuty)", + "Video stream only from first feed": "Video stream pouze z prvního krmiva", + "Video streams only": "Pouze video streamy", + "Videos": "Videa", + "Videos List": "Seznam videí", + "Videos Merge": "Videa se sloučí", + "Viewing Server Stats": "Prohlížení statistik serveru", + "Warning": "Varování", + "Wasabi Hot Cloud Storage": "Wasabi Hot Cloud Storage", + "Wasabi Hot Cloud Storage Upload Error": "Chyba uložení úložiště Wasabi Hot Cloud Storage", + "Watch": "Hodinky", + "Watch Only": "Sledujte pouze", + "Watch-Only": "Pouze pro sledování", + "Watching": "Sledování", + "Web Page": "Webová stránka", + "WebDAV": "WebDav", + "WebM (libvpx)": "WebM (libvpx)", + "Webdav Error": "Chyba webdav", + "WebdavErrorTextCreatingDir": "Nelze vytvořit adresář.", + "WebdavErrorTextTryCreatingDir": "Nelze ušetřit. Pokus o vytvoření adresáře.", + "Webhook": "Webhook", + "Webhook URL": "Webhook URL", + "Websocket": "WebSocket", + "Websocket Connected": "WebSocket připojeno", + "Websocket Disconnected": "WebSocket odpojeno", + "Wednesday": "středa", + "Welcome": "Vítejte!", + "When Detector is Off": "Když je detektor vypnutý", + "When Detector is On": "Když je detektor zapnutý", + "WhiteBalance": "Bilance bílé", + "WideDynamicRange": "Široký dynamický rozsah", + "Width": "Šířka", + "X Point": "X bod", + "Y Point": "Y bod", + "Yes": "Ano", + "Z-Wave": "Z-wave", + "Z-Wave Manager": "Z-Wave Manager", + "Zip and Download": "Zip a stahování", + "Zipping Videos": "Ziphing videa", + "Zones": "Zóny", + "Zoom In": "Přiblížit", + "Zoom In Stop": "Zoom in Stop adresa URL ", + "Zoom Out": "Oddálit", + "Zoom Out Stop": "Oddálit zastávku adresa URL ", + "a day": "den", + "a few seconds": "pár sekund", + "a minute": "minutu", + "a month": "měsíc", + "a year": "rok", + "aac": "AAC", + "aac (Default)": "AAC (výchozí)", + "ac3": "AC3", + "accountActionFailed": "Akce účtu selhala", + "accountAdded": "Přidán účet", + "accountAddedText": "Byl přidán účet.", + "accountDeleted": "Smazán účet", + "accountDeletedText": "Účet byl odstraněn.", + "accountId": "Číslo účtu", + "accountSettingsDescription": "Spravujte svůj profil a nastavte možnosti, jako je maximální částka úložiště a maximální počet dní, abyste si uchovávali videa.", + "accountSettingsError": "Chyba nastavení účtu", + "activatedText": "Vaše instalace byla aktivována.", + "ago": "před", + "airplane": "letoun", + "alreadyLinked": "Již propojeno s účtem", + "an hour": "hodina", + "apple": "jablko", + "applicationKey": "Klíč aplikace", + "aws_accessKeyId": "ID přístupu k klíči", + "aws_secretAccessKey": "Tajný přístupový klíč", + "backpack": "batoh", + "banana": "banán", + "baseball bat": "baseballovou pálkou", + "baseball glove": "baseballová rukavice", + "batchDownloadText": "Chcete si stáhnout vybrané soubory?", + "bear": "medvěd", + "bed": "postel", + "bench": "lavice", + "bicycle": "jízdní kolo", + "bindDN": "Binddn", + "bird": "pták", + "blankPassword": "Nechte prázdné, abyste si udrželi stejné heslo", + "boat": "loď", + "book": "rezervovat", + "bottle": "láhev", + "bowl": "miska", + "broccoli": "brokolice", + "bus": "autobus", + "cake": "dort", + "car": "auto", + "carrot": "mrkev", + "cat": "kočka", + "cell phone": "mobilní telefon", + "chair": "židle", + "clientStreamFailedattemptingReconnect": "Kontrola bočního proudu klienta selhala a pokus o opětovné připojení.", + "clock": "hodiny", + "coProcess Crashed for Monitor": "Coprocess havaroval pro monitor", + "coProcess Unexpected Exit": "coprocess neočekávaný východ", + "coProcessor": "coprocessor", + "coProcessor Started": "Coprocessor začal", + "coProcessor Stopped": "Coprocessor se zastavil", + "coProcessorTextStarted": "Coprocessor zahájil pouze výstupy CPU.", + "coProcessorTextStopped": "Coprocessor skončil.", + "codecMismatchText1": "Váš fotoaparát poskytuje data streamu H.265 (HEVC) a jako video kodek používáte kopii pro sekci Stream. Váš proud ze Shinobi se nemusí objevit na zařízeních, která nemohou tento kodek používat. Mobilní aplikace Shinobi může tyto streamy zobrazit.", + "codecMismatchText2": "Váš vybraný video kodek není použitelný. Váš fotoaparát poskytuje data streamu MJPEG a jako video kodek používáte kopii pro sekci Stream. Změnil typ proudu na mjpeg.", + "codecMismatchText3": "Váš vybraný video kodek není použitelný. Váš fotoaparát poskytuje data streamu MJPEG a jako video kodek používáte kopii pro sekci nahrávání. Změnil video kodek na libx264.", + "confirmDeleteFilter": "Chcete tento filtr smazat? Nemůžete to obnovit.", + "contactAdmin": "Kontaktujte správce instalace Shinobi.", + "copy": "kopírovat", + "couch": "gauč", + "cow": "kráva", + "cuda": "CUDA (NVIDIA NVENC)", + "cup": "pohár", + "cuvid": "CUVID (NVIDIA NVENC)", + "days": "dny", + "deleteApiKey": "Odstranit klíč API", + "deleteApiKeyText": "Chcete odstranit tento klíč API? Nemůžete to obnovit.", + "deleteMonitorStateText1": "Chcete odstranit předvolbu tohoto monitoru? Přidružené konfigurace monitoru nelze obnovit.", + "deleteMonitorStateText2": "Chcete odstranit předvolbu tohoto monitoru?", + "deleteScheduleText": "Chcete tento plán odstranit? Předvolba monitoru nebude upravena ..", + "deleteSubAccount": "Odstranit sub-účty", + "deleteSubAccountText": "Chcete tento sub-účty smazat? Nemůžete to obnovit.", + "dining table": "jídelní stůl", + "dog": "Pes", + "donut": "Kobliha", + "drm": "Sdílení objektů DRM", + "dropBoxSuccess": "Úspěch! Soubory uložené do vašeho Dropboxu.", + "dxva2": "DXVA2 (DirectX Video, Windows)", + "elephant": "slon", + "eventFilterActionText": "Toto jsou akce, které se vyskytují z podmínek filtru, které uspěly. „Původní volba“ odkazuje na možnost, kterou jste si vybrali v nastavení monitoru.", + "eventFilterErrorBrackets": "Máte nerovnoměrné množství závorek. Jsou ignorovány.", + "eventFiltersDescription": "Nastavení filtrů, kdy se objeví události.", + "failedLoginText1": "Nepodařilo se vám přihlásit příliš mnohokrát. Než to zkusíte znovu, musíte počkat 15 minut.", + "failedLoginText2": "Zkontrolujte prosím své přihlašovací údaje.", + "fieldMissingValueText1": "Váš fotoaparát poskytuje data streamu MJPEG. Musíte nastavit rychlost zachycení monitoru. Shinobi se ji pokusí detekovat a vyplnit jej automaticky.", + "fieldTextAccelerator": "Hardwarové zrychlení (HwaCcel) pro dekódování toků.", + "fieldTextAcodec": "Zvukový kodek pro nahrávání.", + "fieldTextActionsCommand": "Můžete to použít ke spuštění skriptu na příkazu.", + "fieldTextActionsHalt": "Aby se událost nestala, jako by se to nikdy nestalo.", + "fieldTextActionsIndifference": "Upravit minimální lhostejnost potřebná pro událost.", + "fieldTextActionsRecord": "Použijte nahrávání založenou na událostech, Hotswap nebo smažte nehybnou s jejich aktuálně nastavenými možnostmi v části Nastavení globální detekce.", + "fieldTextAduration": "Určete, kolik mikrosekund je analyzováno pro sondu vstup. Nastavte na 100 000, pokud používáte RTSP a máte problémy s proudem.", + "fieldTextAudioAlert": "Zvuk, když dojde k události.", + "fieldTextAudioDelay": "Zpoždění až příště může událost spustit upozornění. Měřeno během několika sekund.", + "fieldTextAudioNote": "Zvuk, když se objeví informační bublina.", + "fieldTextAutoHost": "URL úplného proudu.", + "fieldTextAutoHostEnable": "Nakrmte jednotlivé kusy potřebné k vytvoření URL proudu nebo poskytnutí celé adresy URL a umožnění Shinobi, aby ji analyzoval za vás.", + "fieldTextBufferTimeFromEvent": "Množství sekund zaznamenat předtím, než došlo k spuštění. Pokud je to důsledně nepřesné, budete se muset podívat na Optimalizační průvodce nebo silou kódování na server.", + "fieldTextChannelHlsListSize": "Počet segmentů maximální před automaticky odstraněním starých segmentů.", + "fieldTextChannelHlsTime": "Jak dlouho by měl být každý segment videa za pár minut. Každý segment bude nakreslen klientem prostřednictvím souboru M3U8. Kratší segmenty zabírají méně místa.", + "fieldTextChannelPresetStream": "Předvolba příznaku pro určité videokodéry videa. Pokud zjistíte, že váš fotoaparát zhroutí každých několik sekund: Zkuste jej nechat prázdné.", + "fieldTextChannelStreamAcodec": "Zvukový kodek pro streamování.", + "fieldTextChannelStreamAcodecAac": "Používá se pro video MP4.", + "fieldTextChannelStreamAcodecAc3": "Používá se pro video MP4.", + "fieldTextChannelStreamAcodecAuto": "Nechte FFMPEG zvolit.", + "fieldTextChannelStreamAcodecCopy": "Používá se pro video MP4. Má velmi nízké využití CPU, ale některé zvukové kodeky potřebují vlastní příznaky jako -strict 2 pro AAC.",
+ "fieldTextChannelStreamAcodecLibmp3lame": "Používá se pro video MP4.",
+ "fieldTextChannelStreamAcodecLibopus": "Používá se pro webm video.",
+ "fieldTextChannelStreamAcodecLibvorbis": "Používá se pro webm video.",
+ "fieldTextChannelStreamAcodecNoAudio": "Žádný zvuk, jedná se o možnost, která musí být stanovena v některých částech světa z právních důvodů.",
+ "fieldTextChannelStreamFps": "Rychlost, ve které jsou snímky zobrazovány klientům, v rámcích za sekundu. Uvědomte si, že není výchozí. To může vést k využití vysoké šířky pásma.",
+ "fieldTextChannelStreamQuality": "Nízký počet znamená vyšší kvalitu. Vyšší počet znamená méně kvality.",
+ "fieldTextChannelStreamRotate": "Změňte úhel pohledu proudu videa.",
+ "fieldTextChannelStreamScaleX": "Šířka obrazu proudu, který je výstup po zpracování.",
+ "fieldTextChannelStreamScaleY": "Výška obrazu proudu, který je výstup po zpracování.",
+ "fieldTextChannelStreamType": "Metoda, která se použije ke spotřebě proudu videa.",
+ "fieldTextChannelStreamTypeFLV": "Odesílání FLV kódovaných rámců přes WebSocket.",
+ "fieldTextChannelStreamTypeHLS(includesAudio)": "Podobná metoda jako živé streamy na Facebooku. Zahrnuje zvuk , pokud jej vstup poskytne. Existuje zpoždění asi 4-6 sekund, protože tato metoda zaznamenává segmenty a poté je tlačí k klientovi, spíše než tlačí, protože je vytváří.",
+ "fieldTextChannelStreamTypeMJPEG": "Standardní obrázek JPEG. Žádný zvuk.",
+ "fieldTextChannelStreamTypePoseidon": "Poseidon je postaven na zpracovatelském kódu MP4 Kevina Godella. Simuluje soubor streamování MP4, ale pomocí dat živého proudu. Zahrnuje zvuk. Některé prohlížeče to mohou hrát jako běžný soubor MP4. Streamy přes HTTP nebo WebSocket.",
+ "fieldTextChannelStreamVcodec": "Video kodek pro streamování.",
+ "fieldTextChannelStreamVcodecAuto": "Nechte FFMPEG zvolit.",
+ "fieldTextChannelStreamVcodecCopy": "Používá se pro video MP4. Má velmi nízké využití CPU, ale nemůže používat filtry a soubory pro soubory mohou být gigantické. Nejlepší je nastavit nastavení MP4 na straně fotoaparátu při použití této možnosti.",
+ "fieldTextChannelStreamVcodecLibx264": "Používá se pro video MP4.",
+ "fieldTextChannelStreamVcodecLibx265": "Používá se pro video MP4.",
+ "fieldTextChannelSvf": "Umístěte filtry videa FFMPEG do tohoto pole a ovlivňují to streamovací část. Žádné prostory.",
+ "fieldTextControlInvertY": "Když je fotoaparát namontován vzhůru nohama nebo používá obrácené vertikální ovládací prvky.",
+ "fieldTextCrf": "Nízký počet znamená vyšší kvalitu. Vyšší počet znamená méně kvality.",
+ "fieldTextCustDetect": "Vlastní příznaky, které se vážou na detektor proudu, používá pro analýzu.",
+ "fieldTextCustDetectObject": "Vlastní příznaky, které se vážou na detektor proudu, používá pro analýzu.",
+ "fieldTextCustInput": "Vlastní příznaky, které se vážou na vstup procesu FFMPEG.",
+ "fieldTextCustRecord": "Vlastní příznaky, které se vážou na záznam procesu FFMPEG.",
+ "fieldTextCustSipRecord": "Vlastní příznaky, které se vážou na výstup, od kterého nahrávky založené na událostech sifonují.",
+ "fieldTextCustSnap": "Vlastní příznaky, které se vážou na snímky.",
+ "fieldTextCustStream": "Vlastní příznaky, které se vážou na proud (pohled na straně klienta) procesu FFMPEG.",
+ "fieldTextCustomOutput": "Přidejte vlastní výstup, jako jsou rámečky JPEG nebo odesílejte data přímo na jiný server.",
+ "fieldTextCutoff": "Za pár minut. Kdy se odřízne a spustí nový video soubor.",
+ "fieldTextDays": "Počet dní na ponechání videí před očištěním.",
+ "fieldTextDetailSubstreamInputRtspTransportAuto": "Nechte FFMPEG rozhodnout. Normálně to zkusí nejprve UDP.",
+ "fieldTextDetailSubstreamInputRtspTransportTCP": "Nastavte to, pokud UDP začne poskytovat nežádoucí výsledky.",
+ "fieldTextDetailSubstreamInputRtspTransportUDP": "FFMPEG to zkusí první.",
+ "fieldTextDetailSubstreamOutputHlsListSize": "Počet segmentů maximální před automaticky odstraněním starých segmentů.",
+ "fieldTextDetailSubstreamOutputHlsTime": "Jak dlouho by měl být každý segment videa za pár minut. Každý segment bude nakreslen klientem prostřednictvím souboru M3U8. Kratší segmenty zabírají méně místa.",
+ "fieldTextDetailSubstreamOutputPresetStream": "Předvolba příznaku pro určité videokodéry videa. Pokud zjistíte, že váš fotoaparát zhroutí každých několik sekund: Zkuste jej nechat prázdné.",
+ "fieldTextDetailSubstreamOutputStreamAcodec": "Zvukový kodek pro streamování.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAac": "Používá se pro video MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAc3": "Používá se pro video MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAuto": "Nechte FFMPEG zvolit.",
+ "fieldTextDetailSubstreamOutputStreamAcodecCopy": "Používá se pro video MP4. Má velmi nízké využití CPU, ale některé zvukové kodeky potřebují vlastní příznaky jako -strict 2 pro AAC.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibmp3lame": "Používá se pro video MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibopus": "Používá se pro webm video.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibvorbis": "Používá se pro webm video.",
+ "fieldTextDetailSubstreamOutputStreamAcodecNoAudio": "Žádný zvuk, jedná se o možnost, která musí být stanovena v některých částech světa z právních důvodů.",
+ "fieldTextDetailSubstreamOutputStreamFps": "Rychlost, ve které jsou snímky zobrazovány klientům, v rámcích za sekundu. Uvědomte si, že není výchozí. To může vést k využití vysoké šířky pásma.",
+ "fieldTextDetailSubstreamOutputStreamQuality": "Nízký počet znamená vyšší kvalitu. Vyšší počet znamená méně kvality.",
+ "fieldTextDetailSubstreamOutputStreamRotate": "Změňte úhel pohledu proudu videa.",
+ "fieldTextDetailSubstreamOutputStreamScaleX": "Šířka obrazu proudu, který je výstup po zpracování.",
+ "fieldTextDetailSubstreamOutputStreamScaleY": "Výška obrazu proudu, který je výstup po zpracování.",
+ "fieldTextDetailSubstreamOutputStreamType": "Metoda, která se použije ke spotřebě proudu videa.",
+ "fieldTextDetailSubstreamOutputStreamVcodec": "Video kodek pro streamování.",
+ "fieldTextDetailSubstreamOutputStreamVcodecAuto": "Nechte FFMPEG zvolit.",
+ "fieldTextDetailSubstreamOutputStreamVcodecCopy": "Používá se pro video MP4. Má velmi nízké využití CPU, ale nemůže používat filtry a soubory pro soubory mohou být gigantické. Nejlepší je nastavit nastavení MP4 na straně fotoaparátu při použití této možnosti.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx264": "Používá se pro video MP4.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx265": "Používá se pro video MP4.",
+ "fieldTextDetailSubstreamOutputSvf": "Umístěte filtry videa FFMPEG do tohoto pole a ovlivňují to streamovací část. Žádné prostory.",
+ "fieldTextDetector": "Tím se přidá další výstup do příkazu FFMPEG pro detektor pohybu.",
+ "fieldTextDetectorAudio": "Zkontrolujte, zda došlo k zvuku na Certiain Decible. Decibilní čtení nemusí být přesné pro měření v reálném světě.",
+ "fieldTextDetectorBufferHlsListSize": "Počet segmentů maximální před automaticky odstraněním starých segmentů.",
+ "fieldTextDetectorBufferHlsTime": "Jak dlouho by měl být každý segment videa, během několika sekund. Každý segment bude nakreslen klientem prostřednictvím souboru M3U8. Kratší segmenty zabírají méně místa.",
+ "fieldTextDetectorColorThreshold": "Množství rozdílu povoleného v pixelu před jeho pohybem.",
+ "fieldTextDetectorCommand": "Příkaz, který bude spuštěn. Toto je ekvivalent spuštění příkazu Shell z terminálu.",
+ "fieldTextDetectorCommandTimeout": "Tato hodnota je časovač, který umožňuje další spuštění skriptu. Tato hodnota je za pár minut.",
+ "fieldTextDetectorFps": "Kolik rámců na sekundu odesláním do detektoru pohybu; 2 je výchozí.",
+ "fieldTextDetectorFrame": "Tím se přečte celý snímky pro rozdíly v pixelech. To je stejné jako vytvoření oblasti, která pokrývá celou obrazovku. Pokud do tohoto monitoru není přidána žádná oblast, bude tato možnost výchozí pro ano.",
+ "fieldTextDetectorHttpApi": "Chcete k této kameře povolit spouštěče HTTP?",
+ "fieldTextDetectorLisencePlate": "Povolit rozpoznávání poznávací značky. Plugin OpenALPR to má vždy povoleno.",
+ "fieldTextDetectorLisencePlateCountry": "Vyberte typ desek, které chcete rozpoznat. V tuto chvíli jsme podporováni pouze my a EU.",
+ "fieldTextDetectorLockTimeout": "Uzavření, když je povolen další spoušť, zabránit přetížení databáze a přijímání klientů. Měřeno v milisekundách.",
+ "fieldTextDetectorMaxSensitivity": "Hodnocení spolehlivosti pohybu musí být nižší než tato hodnota, kterou je třeba považovat za spouštěč. Nechte prázdné bez maxima. Tato možnost byla dříve pojmenována „Max Insiferenference“.",
+ "fieldTextDetectorNoiseFilter": "Pokuste se filtrovat zrno nebo opakovaný pohyb při zvláštní lhostejnosti.",
+ "fieldTextDetectorNoiseFilterRange": "Množství rozdílu povoleného v pixelu před jeho pohybem.",
+ "fieldTextDetectorNotrigger": "Zkontrolujte, zda k pohybu došlo v intervalu. Pokud došlo k pohybu, bude kontrola resetována.",
+ "fieldTextDetectorNotriggerCommand": "Příkaz, který bude spuštěn. Toto je ekvivalent spuštění příkazu Shell z terminálu.",
+ "fieldTextDetectorNotriggerCommandTimeout": "Tato hodnota je časovač, který umožňuje další spuštění skriptu. Tato hodnota je za pár minut.",
+ "fieldTextDetectorNotriggerDiscord": "Pokud nebyl po časovém období detekován pohyb, obdržíte oznámení o neshodě.",
+ "fieldTextDetectorNotriggerTimeout": "Časový limit se počítá v minutách.",
+ "fieldTextDetectorNotriggerWebhook": "Pošlete žádost GET na adresu URL s některými hodnotami z události.",
+ "fieldTextDetectorObjCount": "Počet zjištěných objektů.",
+ "fieldTextDetectorObjCountInRegion": "Počítejte objekty pouze uvnitř regionů.",
+ "fieldTextDetectorPam": "Použijte detektor pohybu Kevina Godella. To je zabudováno do Shinobi a nevyžaduje žádnou jinou konfiguraci k aktivaci.",
+ "fieldTextDetectorPtzFollow": "Sledujte největší detekovaný objekt s PTZ? Vyžaduje běh detektoru objektů nebo matice poskytované událostmi.",
+ "fieldTextDetectorRecordMethod": "Když dojde k události, jako je pohyb, existuje několik způsobů, jak začít nahrávat. Nahrávání založené na událostech je nejúžasnější.",
+ "fieldTextDetectorSave": "Uložit události v databázi. To umožní zobrazení událostí přes video během přehrávání.",
+ "fieldTextDetectorScaleX": "Šířka detekovaného obrazu. Menší velikosti mají méně CPU.",
+ "fieldTextDetectorScaleY": "Výška detekovaného obrazu. Menší velikosti mají méně CPU.",
+ "fieldTextDetectorSendFrames": "Stiskněte rámečky k připojenému pluginu, které mají být analyzovány.",
+ "fieldTextDetectorSendFramesObject": "Stiskněte rámečky k připojenému pluginu, které mají být analyzovány.",
+ "fieldTextDetectorSendVideoLength": "Za sekundy. Délka videa, která je odeslána do vaší oznámení, jako je e -mail nebo Discord.",
+ "fieldTextDetectorSensitivity": "Hodnocení spolehlivosti pohybu musí tuto hodnotu překročit, aby byla vnímána jako spouštěč. Toto číslo koreluje přímo s hodnocením spolehlivosti vráceného detektorem pohybu. Tato možnost byla dříve s názvem „lhostejnost“.",
+ "fieldTextDetectorThreshold": "Minimální počet detekcí k vystřelení události pohybu. Detekce musí být v detektoru prahová hodnota dělena detektorem FPS sekundy. Například, pokud je detektor FPS 2 a prahová hodnota spuštění je 3, musí se do 1,5 sekundy vyskytnout tři detekce, aby se spustila událost pohybu. Tato prahová hodnota je na detekční oblast.",
+ "fieldTextDetectorTimeout": "Doba doba „spouštěcí záznam“ se bude spustit. Toto se čte během několika minut.",
+ "fieldTextDetectorTrigger": "To nařídí kameru zaznamenat, pokud je nastavena na „pouze pro sledování“, když je detekována událost.",
+ "fieldTextDetectorUseDetectObject": "Vytvořte rámce pro odeslání do jakéhokoli připojeného pluginu.",
+ "fieldTextDetectorWebhook": "Pošlete žádost GET na adresu URL s některými hodnotami z události.",
+ "fieldTextDetectorWebhookTimeout": "Tato hodnota je časovač, který umožňuje další spuštění vašeho webhook. Tato hodnota je za pár minut.",
+ "fieldTextDir": "Uložení umístění, kde budou uloženy zaznamenané soubory. Můžete nakonfigurovat více míst pomocí proměnné addStorage .",
+ "fieldTextEventDays": "Počet dní na udržení událostí před očištěním.",
+ "fieldTextEventFilters": "Umožněte, aby všechny události ctily pravidla filtru událostí.",
+ "fieldTextEventMonPop": "Když se objeví událost, potok proudu monitoru.",
+ "fieldTextEventRecordScaleX": "Šířka nahrávacího obrázku založeného na události, která je výstupem po zpracování.",
+ "fieldTextEventRecordScaleY": "Výška nahrávacího obrázku založeného na události, která je po zpracování výstupem.",
+ "fieldTextExt": "Typ souboru pro váš zaznamenaný video soubor.",
+ "fieldTextExtMP4": "Tento typ souboru je hratelný téměř všechny moderní webové prohlížeče, které zahrnují mobilní. Soubore má tendenci být větší, pokud nesnížíte kvalitu.",
+ "fieldTextExtWebM": "Malá souborovárize, nízká kompatibilita klienta. Dobré pro nahrávání na weby, jako je YouTube.",
+ "fieldTextFactorAuth": "Povolte sekundární požadavek na přihlášení prostřednictvím jedné z povolených metod.",
+ "fieldTextFatalMax": "Před nastavením monitoru na deaktivovaný počet je třeba opakovat pro připojení sítě mezi serverem a fotoaparátem. Žádné desetinné místa. Nastavit na 0, aby se znovu opakoval.",
+ "fieldTextFps": "Rychlost, ve které jsou rámečky zaznamenány do souborů, snímky za sekundu. Uvědomte si, že není výchozí. To může vést k velkým souborům. Nejlepší je nastavit tuto stranu fotoaparátu.",
+ "fieldTextHeight": "Výška obrazu proudu.",
+ "fieldTextHlsListSize": "Počet segmentů maximální před automaticky odstraněním starých segmentů.",
+ "fieldTextHlsTime": "Jak dlouho by měl být každý segment videa za pár minut. Každý segment bude nakreslen klientem prostřednictvím souboru M3U8. Kratší segmenty zabírají méně místa.",
+ "fieldTextHost": "Připojení adresy",
+ "fieldTextHwaccel": "Dekódovací motor",
+ "fieldTextHwaccelVcodec": "Dekódovací motor",
+ "fieldTextInverseTrigger": "Spuštění mimo zadané regiony. Nebude spuštěno s povolenou detekcí s plným snímkem.",
+ "fieldTextIp": "Rozsah nebo svobodný",
+ "fieldTextIrCutFilterAuto": "IR Cut Filtr je zařízením automaticky aktivován zařízením.",
+ "fieldTextIrCutFilterOff": "Zakázat fiter IR Cut. Obvykle noční režim.",
+ "fieldTextIrCutFilterOn": "Povolte IR cut firt. Obvykle denní režim.",
+ "fieldTextIsOnvif": "Je to kamera kompatibilní s ONVIF?",
+ "fieldTextLang": "Primární jazyk textových prvků. Pro úplný překlad přidejte svůj jazyk v konf.",
+ "fieldTextLogDays": "Počet dní pro ponechání protokolů před očištěním.",
+ "fieldTextLoglevel": "Množství údajů, které je třeba poskytnout při práci.",
+ "fieldTextLoglevelAllWarnings": "Zobrazit všechna varování. Použijte to, pokud nemůžete zjistit, co se děje s fotoaparátem.",
+ "fieldTextLoglevelFatal": "Zobrazit pouze fatální chyby.",
+ "fieldTextLoglevelOnError": "Zobrazit všechny důležité chyby. POZNÁMKA: To ne vždy ukazuje důležité informace.",
+ "fieldTextLoglevelSilent": "Žádný. To umlčí veškerou protokolování.",
+ "fieldTextMail": "Přihlášení k účtům. E -mailová adresa držitele hlavního účtu získá oznámení.",
+ "fieldTextMapRtspTransportAuto": "Nechte FFMPEG rozhodnout. Normálně to zkusí nejprve UDP.",
+ "fieldTextMapRtspTransportTCP": "Nastavte to, pokud UDP začne poskytovat nežádoucí výsledky.",
+ "fieldTextMapRtspTransportUDP": "FFMPEG to zkusí první.",
+ "fieldTextMaxKeepDays": "Počet dní, kdy se videa uchovává před očistou pro tento monitor konkrétně.",
+ "fieldTextMid": "Jedná se o nevýrazný identifikátor monitoru. Monitor můžete duplikovat dvojitým kliknutím na ID monitoru a jeho změnou.",
+ "fieldTextMode": "Toto je primární úkol monitoru.",
+ "fieldTextModeDisabled": "Neaktivní monitor, v tomto režimu nebude vytvořen žádný proces.",
+ "fieldTextModeRecord": "Nepřetržité nahrávání. Ve výchozím nastavení jsou segmenty prováděny každých 15 minut.",
+ "fieldTextModeWatchOnly": "Monitor bude streamovat pouze, žádné nahrávání nedojde k jinak, pokud není API nebo detektor objednáno jinak.",
+ "fieldTextMpass": "Heslo pro fotoaparát",
+ "fieldTextMuser": "Přihlášení uživatele pro fotoaparát",
+ "fieldTextName": "Toto je název pro monitor čitelný člověkem.",
+ "fieldTextNotes": "Komentáře, které chcete pro tuto kameru odejít.",
+ "fieldTextOnvifNonStandard": "Je to nestandardní kamera ONVIF?",
+ "fieldTextOnvifPort": "OnVif je obvykle spuštěn na portu 8000 . To může být 80 také v závislosti na modelu fotoaparátu.",
+ "fieldTextPass": "Nechte prázdné, abyste si během úpravy nastavení ponechali stejné heslo.",
+ "fieldTextPasswordAgain": "Pokud si přejete změnit pole hesla.",
+ "fieldTextPath": "Cesta k fotoaparátu",
+ "fieldTextPort": "Oddělit čárkami nebo rozsahem",
+ "fieldTextPortForce": "Použití výchozího webového portu může umožnit automatický přepínač na jiné porty pro proudy, jako je RTSP.",
+ "fieldTextPresetRecord": "Předvolba příznaku pro určité videokodéry videa. Pokud zjistíte, že váš fotoaparát zhroutí každých několik sekund: Zkuste jej nechat prázdné.",
+ "fieldTextPresetStream": "Předvolba příznaku pro určité videokodéry videa. Pokud zjistíte, že váš fotoaparát zhroutí každých několik sekund: Zkuste jej nechat prázdné.",
+ "fieldTextProbesize": "Určete, jak velká pro vytvoření analyzační sondy pro vstup. Nastavte na 100 000, pokud používáte RTSP a máte problémy s proudem.",
+ "fieldTextProtocol": "Protokol, který bude použit k konzumaci proudu videa.",
+ "fieldTextRecordScaleX": "Šířka obrazu proudu.",
+ "fieldTextRecordScaleY": "Výška obrazu proudu.",
+ "fieldTextRecordTimelapse": "Vytvořte timelapse založený na JPEG.",
+ "fieldTextRecordTimelapseMp4": "Vytvořte soubor MP4 na konci každého dne pro timelapse.",
+ "fieldTextRecordTimelapseWatermark": "Obrázek, který je spálen na rámečky zaznamenaného videa.",
+ "fieldTextRecordTimelapseWatermarkLocation": "Umístění obrázku, které bude použito jako vodoznak.",
+ "fieldTextRecordTimelapseWatermarkPosition": "Obrázek, který je spálen na rámečky zaznamenaného videa.",
+ "fieldTextRotate": "Změňte úhel záznamu proudu videa.",
+ "fieldTextRtmpKey": "Klíč proudu pro příchozí proudy na portu RTMP.",
+ "fieldTextRtspTransport": "Transportní protokol, který váš fotoaparát použije. TCP je obvykle nejlepší volbou.",
+ "fieldTextRtspTransportAuto": "Nechte FFMPEG rozhodnout. Normálně to zkusí nejprve UDP.",
+ "fieldTextRtspTransportHTTP": "Standardní metoda připojení.",
+ "fieldTextRtspTransportTCP": "Nastavte to, pokud UDP začne poskytovat nežádoucí výsledky.",
+ "fieldTextRtspTransportUDP": "FFMPEG to zkusí první.",
+ "fieldTextSfps": "Určete snímkovou frekvenci (FPS), ve které fotoaparát poskytuje svůj proud.",
+ "fieldTextSignalCheck": "Jak často váš klient zkontroluje proud, aby zjistil, zda je naživu. To se počítá v minutách.",
+ "fieldTextSignalCheckLog": "To je pouze pro klientskou stranu. Když dojde ke kontrole signálu na straně protokolu, zobrazí se v vlákně protokolu.",
+ "fieldTextSize": "Množství prostoru na disku Shinobi bude před očištěním spotřebováno. Tato hodnota se čte v megabajtech.",
+ "fieldTextSizeFilebinPercent": "Procento maximální částky úložiště, kterou může archiv FileBin použít.",
+ "fieldTextSizeTimelapsePercent": "Procento maximálního množství úložiště, na které mohou rámečky časové časové hodnoty zaznamenat.",
+ "fieldTextSizeVideoPercent": "Procento maximálního množství úložiště, na kterou mohou videa nahrávat.",
+ "fieldTextSkipPing": "Vyberte, zda je před spuštěním procesu monitoru vyžadován úspěšný ping.",
+ "fieldTextSnap": "Získejte nejnovější rámec v JPEG.",
+ "fieldTextSnapSecondsInward": "za sekundy",
+ "fieldTextSqllog": "Použijte to s opatrností, protože FFMPEG rád občas hází nadbytečná data, která mohou vést k mnoha datovým řádkům.",
+ "fieldTextSqllogNo": "Ne je výchozí.",
+ "fieldTextSqllogYes": "Udělejte to, pokud máte pouze opakující se problémy.",
+ "fieldTextStreamAcodec": "Zvukový kodek pro streamování.",
+ "fieldTextStreamAcodecAac": "Používá se pro video MP4.",
+ "fieldTextStreamAcodecAc3": "Používá se pro video MP4.",
+ "fieldTextStreamAcodecAuto": "Nechte FFMPEG zvolit.",
+ "fieldTextStreamAcodecCopy": "Používá se pro video MP4. Má velmi nízké využití CPU, ale některé zvukové kodeky potřebují vlastní příznaky jako -strict 2 pro AAC.",
+ "fieldTextStreamAcodecLibmp3lame": "Používá se pro video MP4.",
+ "fieldTextStreamAcodecLibopus": "Používá se pro webm video.",
+ "fieldTextStreamAcodecLibvorbis": "Používá se pro webm video.",
+ "fieldTextStreamAcodecNoAudio": "Žádný zvuk, jedná se o možnost, která musí být stanovena v některých částech světa z právních důvodů.",
+ "fieldTextStreamFlvType": "To je pouze pro řídicí panel Shinobi. Obě metody proudu jsou stále aktivní a připravené k použití.",
+ "fieldTextStreamFps": "Rychlost, ve které jsou snímky zobrazovány klientům, v rámcích za sekundu. Uvědomte si, že není výchozí. To může vést k využití vysoké šířky pásma.",
+ "fieldTextStreamLoop": "Smyčka statického souboru, takže tok souborů se chová jako živý proud.",
+ "fieldTextStreamQuality": "Nízký počet znamená vyšší kvalitu. Vyšší počet znamená méně kvality.",
+ "fieldTextStreamRotate": "Změňte úhel pohledu proudu videa.",
+ "fieldTextStreamScaleX": "Šířka obrazu proudu, který je výstup po zpracování.",
+ "fieldTextStreamScaleY": "Výška obrazu proudu, který je výstup po zpracování.",
+ "fieldTextStreamTimestamp": "Hodiny, které jsou spáleny na rámečky proudu videa.",
+ "fieldTextStreamTimestampBoxColor": "Barva pozadí Timstamp.",
+ "fieldTextStreamTimestampColor": "Barva textu Timstamp.",
+ "fieldTextStreamTimestampFont": "Soubor písma pro styling vašeho časového razítka.",
+ "fieldTextStreamTimestampFontSize": "Velikost písma v Pt.",
+ "fieldTextStreamTimestampX": "Horizontní pozice časového razítka",
+ "fieldTextStreamTimestampY": "Svislá poloha časového razítka",
+ "fieldTextStreamType": "Metoda, která se použije ke spotřebě proudu videa.",
+ "fieldTextStreamTypeBase64OverWebsocket": "Odesílání kódovaných rámců Base64 přes WebSocket. Tím se vyhýbá ukládání do mezipaměti, ale neexistuje žádný zvuk.",
+ "fieldTextStreamTypeFLV": "Odesílání FLV kódovaných rámců přes WebSocket.",
+ "fieldTextStreamTypeHLS(includesAudio)": "Podobná metoda jako živé streamy na Facebooku. Zahrnuje zvuk , pokud jej vstup poskytne. Existuje zpoždění asi 4-6 sekund, protože tato metoda zaznamenává segmenty a poté je tlačí k klientovi, spíše než tlačí, protože je vytváří.",
+ "fieldTextStreamTypeMJPEG": "Standardní obrázek JPEG. Žádný zvuk.",
+ "fieldTextStreamTypePoseidon": "Poseidon je postaven na zpracovatelském kódu MP4 Kevina Godella. Simuluje soubor streamování MP4, ale pomocí dat živého proudu. Zahrnuje zvuk. Některé prohlížeče to mohou hrát jako běžný soubor MP4. Streamy přes HTTP nebo WebSocket.",
+ "fieldTextStreamVcodec": "Video kodek pro streamování.",
+ "fieldTextStreamVcodecAuto": "Nechte FFMPEG zvolit.",
+ "fieldTextStreamVcodecCopy": "Používá se pro video MP4. Má velmi nízké využití CPU, ale nemůže používat filtry a soubory pro soubory mohou být gigantické. Nejlepší je nastavit nastavení MP4 na straně fotoaparátu při použití této možnosti.",
+ "fieldTextStreamVcodecLibx264": "Používá se pro video MP4.",
+ "fieldTextStreamVcodecLibx265": "Používá se pro video MP4.",
+ "fieldTextStreamVf": "Umístěte filtry videa FFMPEG do tohoto pole a ovlivňují to streamovací část. Žádné prostory.",
+ "fieldTextStreamWatermark": "Obrázek, který je spálen na rámečcích proudu videa.",
+ "fieldTextStreamWatermarkLocation": "Umístění obrázku, které bude použito jako vodoznak.",
+ "fieldTextStreamWatermarkPosition": "Obrázek, který je spálen na rámečcích proudu videa.",
+ "fieldTextTileSize": "Když je v režimu přesnosti velikost každé dlaždice v pixelech na druhou. Nižší počet bude mít vyšší přesnost, ale více využití zdrojů.",
+ "fieldTextTimestamp": "Hodiny, které jsou spáleny na rámečky zaznamenaného videa.",
+ "fieldTextTimestampBoxColor": "Barva pozadí Timstamp.",
+ "fieldTextTimestampColor": "Barva textu Timstamp.",
+ "fieldTextTimestampFont": "Soubor písma pro styling vašeho časového razítka.",
+ "fieldTextTimestampFontSize": "Velikost písma v Pt.",
+ "fieldTextTimestampX": "Horizontní pozice časového razítka",
+ "fieldTextTimestampY": "Svislá poloha časového razítka",
+ "fieldTextTvChannel": "Tento monitor bude mít povolené funkce televizního kanálu. Budete si jej moci zobrazit ve svém seznamu televizních kanálů.",
+ "fieldTextTvChannelGroupTitle": "Vlastní skupina pro kanál.",
+ "fieldTextTvChannelId": "Vlastní ID pro kanál.",
+ "fieldTextType": "Metoda, která se použije ke spotřebě proudu videa.",
+ "fieldTextTypeDashcam(StreamerV2)": "Stream P2P založený na webm založený na webm.",
+ "fieldTextTypeH.264/H.265/H.265+": "Čtení vysoce kvalitních videí, které někdy zahrnují zvuk.",
+ "fieldTextTypeHLS(.m3u8)": "Čtení vysoce kvalitních videí, které někdy zahrnují zvuk.",
+ "fieldTextTypeJPEG": "Čtení snímků z URL a vytvoření proudu a/nebo videa z nich.",
+ "fieldTextTypeLocal": "Čtení karty, webové kamery nebo integrované kamery.",
+ "fieldTextTypeMJPEG": "Podobně jako JPEG s výjimkou manipulace s rámečkem je prováděna FFMPEG, nikoli Shinobi.",
+ "fieldTextTypeMPEG4(.mp4/.ts)": "Statický soubor. Přečtěte si nižší sazbu a neměli by být použit pro skutečný živý proud.",
+ "fieldTextTypeMxPEG": "Stream Mobotix MJPEG",
+ "fieldTextTypeRTMP": "Naučte se připojit zde: Článek: Jak tlačit proudy přes RTMP na shinobi ",
+ "fieldTextTypeShinobiStreamer": "Stream P2P založený na WebSocket JPEG.",
+ "fieldTextVcodec": "Video kodek pro nahrávání.",
+ "fieldTextVf": "Umístěte filtry videa FFMPEG do tohoto pole a ovlivní část záznamu. Žádné prostory.",
+ "fieldTextWallClockTimestampIgnore": "Namísto času serveru založte všechna příchozí data kamery v době fotoaparátu.",
+ "fieldTextWatchdogReset": "Povolte resetování časového limitu aktivního záznamu zpět na začátek, když při nahrávání dojde k nové události.",
+ "fieldTextWatermark": "Obrázek, který je spálen na rámečky zaznamenaného videa.",
+ "fieldTextWatermarkLocation": "Umístění obrázku, které bude použito jako vodoznak.",
+ "fieldTextWatermarkPosition": "Obrázek, který je spálen na rámečky zaznamenaného videa.",
+ "fieldTextWidth": "Šířka obrazu proudu.",
+ "fire hydrant": "požární hydrant",
+ "flv": "flv",
+ "for Global Access": "pro globální přístup",
+ "fork": "Vidlička",
+ "frisbee": "létající talíř",
+ "getAMonitor": "Získejte monitor",
+ "getATvChannel": "Získejte televizní kanály pro monitor",
+ "getATvChannelText": "Získejte dostupné streamy H.264 jednoho monitoru v seznamu skladeb .m3u8.",
+ "getAllMonitors": "Získejte všechny monitory",
+ "getAllTvChannels": "Získejte všechny televizní kanály",
+ "getAllTvChannelsText": "Získejte všechny dostupné proudy H.264 v seznamu skladeb .m3u8. Umožněte možnosti televizního kanálu v nastavení monitoru zobrazovat jejich proudy v tomto seznamu.",
+ "getUserInfo": "Získejte informace o uživateli",
+ "getVideos": "Získejte videa",
+ "getVideosForMonitor": "Získejte videa pro monitor",
+ "giraffe": "žirafa",
+ "h264_cuvid": "H.264 CUVID",
+ "h264_mmal": "H.264 (Raspberry Pi)",
+ "h264_nvenc": "H.264 NVENC (NVIDIA HW Accel)",
+ "h264_omx": "H.264 OpenMax (Raspberry Pi)",
+ "h264_qsv": "H.264 (rychlé synchronizace)",
+ "h264_vaapi": "H.264 VA-API (Intel HW Accel)",
+ "h265BrowserText1": "Pokud se pokoušíte přehrávat soubor H.265, možná budete muset jej stáhnout a otevřít v jiné aplikaci, jako je VLC.",
+ "hair drier": "fén",
+ "handbag": "kabelka",
+ "hevc_cuvid": "H.265 CUVID",
+ "hevc_nvenc": "H.265 NVENC (NVIDIA HW Accel)",
+ "hevc_qsv": "H.265 (rychlé synchronizace)",
+ "hevc_vaapi": "H.265 VA-API (Intel HW Accel)",
+ "hlsOptions": "Možnosti HLS",
+ "hlsOptionsInvalid": "Možnosti HLS jsou neplatné",
+ "horse": "kůň",
+ "hot dog": "párek v rohlíku",
+ "hour": "hodina",
+ "hours": "hodiny",
+ "hwaccel": "Zrychlovací motor",
+ "hwaccel_device": "Zařízení HwaCcel",
+ "hwaccel_vcodec": "Video dekodér",
+ "in": "v",
+ "in Days": "za dny",
+ "in seconds": "za sekundy",
+ "keyId": "Klíčové ID",
+ "keyboard": "klávesnice",
+ "kite": "papírový drak",
+ "knife": "nůž",
+ "laptop": "notebook",
+ "lastLogin": "Poslední přihlášení",
+ "libmp3lame": "libmp3lame",
+ "libopus": "Libopus",
+ "libvorbis (Default)": "libvorbis (výchozí)",
+ "libvpx (Default)": "libvpx (výchozí)",
+ "libvpx-vp9": "libvpx-vp9",
+ "libx264": "libx264",
+ "libx264 (Default)": "libx264 (výchozí)",
+ "libx265": "libx265",
+ "liveGridDescription": "Live Grid je zobrazení více proudů pro Shinobi. Tato metoda prohlížení je navržena hlavně pro plochu.",
+ "loginHandleUnbound": "Přihlášení bylo z tohoto účtu nespojeno.",
+ "microwave": "mikrovlnná trouba",
+ "migrateText1": " typ vstupu nelze analyzovat. Nastavte to ručně.",
+ "minute": "minuta",
+ "minutes": "minut",
+ "mjpeg_cuvid": "MJPEG CUVID",
+ "modifyVideoText1": "Metoda neexistuje. Zkontrolujte, zda poslední hodnota URL není prázdná.",
+ "monSavedButNotCopied": "Váš monitor byl uložen, ale nebyl zkopírován na žádný jiný monitor.",
+ "monitorConfigFinderDescription": "Tento nástroj vám pomůže vyhledat konfigurace pro kamery zveřejněné komunitou. Všichni hostovali na shinobihub . Můžete také zveřejnit své, opravdu by to pomohlo komunitě :)",
+ "monitorEditFailedMaxReached": "Váš účet dosáhl maximálního počtu kamer, které lze vytvořit. Promluvte si s administrátorem, pokud chcete, aby se to změnilo.",
+ "monitorEditText1": "Neplatná data, zkontrolujte, zda se jedná o platný importní řetězec.",
+ "monitorEditText2": "Neplatný řetězec detailů. Zkontrolujte, zda se jedná o řetězec JSON a není propuštěn běžný objekt.",
+ "monitorGetText1": "Neúplný požadavek, odstraňte poslední lomítko v adrese URL nebo vložte přijatelnou hodnotu.",
+ "monitorStateNotEnoughChanges": "Než se pokusíte přidat do předvolby, musíte provést změnu v konfiguraci monitoru.",
+ "monitorStatesError": "Chyba předvoleb monitoru",
+ "months": "měsíce",
+ "motorcycle": "motocykl",
+ "mouse": "myš",
+ "mpeg2_mmal": "MPEG-2 (Raspberry Pi)",
+ "mpeg2_qsv": "MPEG2 (rychlé synchronizace)",
+ "mpeg4_cuvid": "MPEG4 CUVID",
+ "mpeg4_mmal": "MPEG-4 (Raspberry Pi)",
+ "noLoginTokensAdded": "K tomuto účtu nejsou spojeny žádné alternativní přihlášení.",
+ "noSpecialCharacters": "Žádné mezery nebo speciální znaky.",
+ "noTriggerText": "Pokud nebyl po časovém období detekován pohyb, obdržíte oznámení o neshodě.",
+ "noUndoForAction": "Tuto akci nemůžete zrušit.",
+ "notActivatedText": "Vaše instalace selhala aktivace.",
+ "notEnoughFramesText1": "Nedostatek snímků pro kompilaci.",
+ "notPermitted1": "Tato akce není povolena správcem vašeho účtu. “",
+ "on": "na",
+ "on Error": "při chybě",
+ "on Event": "na akci",
+ "onvifdeviceManagerGlobalTip": "ONVIF umožňuje úpravu interního nastavení kamery. Onvif je poněkud deštníkovým termínem, bohužel to může znamenat mnoho věcí. Vzhledem k tomu, že v tomto nástroji můžete vidět možnost, ale nemusí být upravitelná. Důvodem je obvykle proto, že dodavatel kamery tuto metodu nepřidal nebo se odchýlil od zamýšleného použití. V těchto případech budete muset zadat konfiguraci kamery prostřednictvím předepsané metody dodavatele kamery, to obecně otevírá IP adresu fotoaparátu ve webovém prohlížeči.",
+ "onvifdeviceSavedFoundErrorText": "Některá nastavení se mohla vrátit k předchozí hodnotě. Je možné, že modifikovaná možnost není s touto kamerou k dispozici prostřednictvím ONVIF.",
+ "onvifdeviceSavedText": "Interní nastavení kamery bylo uloženo. Možná budete muset restartovat kameru, aby se tyto změny projevily.",
+ "openImagesDownloadConfirm": "Jste si jisti, že chcete začít stahovat obrázky a ohraničující boxy (předvolební matice) z OpenImages?",
+ "openImagesDownloadConfirmStop": "Jste si jisti, že chcete přestat trénovat?",
+ "opencl": "OpenCl",
+ "opencvCascadesText": "Pokud zde nic nevidíte, stačí si stáhnout tento balíček kaskády . Druh je do plugins/opencv/kascades a poté stiskněte obnovení .",
+ "orange": "oranžový",
+ "oven": "trouba",
+ "p2pServerNotSelectedText": "Vyberte server ze seznamu a stiskněte Uložit. Počkejte 10 sekund a poté se pokuste otevřeně otevřít palubní desku.",
+ "p2pSettingsText1": "Pro použití změn budete muset tuto stránku obnovit.",
+ "parking meter": "parkovací automat",
+ "performanceOptimizeText1": "Váš fotoaparát poskytuje data streamu H.264. Typ proudu můžete nastavit na HLS, Poseidon a Video Codec a kopírovat.",
+ "person": "osoba",
+ "pizza": "pizza",
+ "possibleInternalError": "Možná interní chyba",
+ "postDataBroken": "Zkontrolujte formát JSON. Ujistěte se, že je napravován a definován v části „Data“",
+ "potted plant": "květináč",
+ "powerVideoEventLimit": "Nastavili jste vysoký limit události. Jste si jisti, že chcete tuto žádost podat?",
+ "privateKey": "Soukromý klíč",
+ "qsv": "QSV",
+ "rebootingCamera": "Restartování kamery",
+ "refrigerator": "lednička",
+ "remote": "dálkový",
+ "restartRequired": "Pro změny, které se projeví, je nutné restartovat jádro Shinobi.",
+ "sandwich": "sendvič",
+ "scissors": "nůžky",
+ "separateByCommasOrRange": "Oddělit čárkami nebo rozsahem",
+ "setMaxStorageAmountText": "Měli byste nastavit maximální částku úložiště do nastavení účtu umístěného vlevo. Najděte možnost v části profilu. Výchozí hodnota je 10 GB.",
+ "sheep": "ovce",
+ "sink": "dřez",
+ "sizePurgeLockedText": "Zdá se, že zámek pro očištění velikosti (DeleteOverMax) se nepodařilo odemknout. Odemčení nyní ...",
+ "skateboard": "Skateboard",
+ "skipPingText1": "Zkuste nastavit „přeskočit ping“ na ano.",
+ "skis": "lyže",
+ "snowboard": "snowboard",
+ "sorryNo": "Promiň ne",
+ "sorryNothingWasFound": "Omlouvám se, nic nebylo nalezeno.",
+ "spoon": "lžíce",
+ "sports ball": "sportovní míč",
+ "startUpText0": "Kontrola použitého disku ..",
+ "startUpText1": "Dokončený použitý kontrolní disk.",
+ "startUpText2": "Všichni uživatelé zkontrolovali, počkejte na otevření souborů a odstraňte soubory přes limit uživatele",
+ "startUpText3": "Čekání na poskytnutí nedokončeného videa zkontrolujte nějaký čas. 3 sekundy.",
+ "startUpText4": "Spuštění monitorů ... Počkejte prosím ...",
+ "startUpText5": "Shinobi je připraven.",
+ "startUpText6": "Nalezená a vložená videa osiřelá",
+ "stop sign": "stopka",
+ "subAccountManager": "Správce sub-účtování",
+ "substreamConnectionText": "Pokud chcete, abyste použili výše uvedenou sadu hlavních informací o připojení, můžete ponechat podrobnosti o připojení.",
+ "substreamOutputText": "Zde můžete nastavit konfiguraci Stream na vyžádání. Zjistěte o latence typů toků zde. ",
+ "substreamText": "Toto je metoda na vyžádání při prohlížení živého proudu. Můžete to udělat, takže proces sledování je k dispozici pouze tehdy, když někdo sleduje nebo musí být použit pro přepínání mezi nízkým a vysokým rozlišením.",
+ "suitcase": "kufr",
+ "superAdminText": "„Super.Json“ neexistuje. Přejmenujte prosím \"super.sample.json\" na \"super.json\".",
+ "superAdminTitle": "Shinobi: Super Admin",
+ "surfboard": "surf",
+ "teddy bear": "Medvídek",
+ "tennis racket": "tenisová raketa",
+ "tie": "kravata",
+ "toaster": "Toater",
+ "toilet": "toaleta",
+ "tokenNotUserBound": "Tato přihlašovací rukojeť není propojena s uživatelem na tomto serveru!",
+ "tokenNotUserBoundPt2": "Zadejte své přihlašovací údaje a poté pomocí tlačítka přihlášení Google propojte rychle.",
+ "toothbrush": "Kartáček na zuby",
+ "total": "celkový",
+ "traffic light": "Semafor",
+ "train": "vlak",
+ "truck": "kamion",
+ "tv": "televize",
+ "umbrella": "deštník",
+ "undoAllUnsaveChanges": "Jste si jisti, že to chcete udělat? Tím se vrátí všechny nezměněné změny.",
+ "unexpectedExitText": "Informace o tomto odchodu budou nalezeny před tímto protokolem. Navíc je zde příkaz FFMPEG, který byl použit při havárii procesu.",
+ "updateCamerasInternalSettings": "Aktualizovat interní nastavení kamery?",
+ "updateKeyText1": "„UpdateKey“ chybí v „Conf.Json“, nemůže to dělat aktualizace tímto způsobem, dokud jej nepřidáte.",
+ "updateKeyText2": "„UpdateKey“ je nesprávné.",
+ "updateNotice1": "Aktualizace Shinobi znamená přepisovací soubory. Pokud jste sami upravili jakékoli soubory, měli byste shinobi aktualizovat ručně. Vaše konfigurace a video soubory nebudou upraveny.",
+ "useSubStreamOnlyWhenWatching": "Pouze při sledování použijte substream",
+ "vaapi": "vaapi (va-api)",
+ "vase": "váza",
+ "vda": "VDA (Apple VDA Hardware Acceleration)",
+ "vdpau": "vdpau",
+ "videoBuildingText1": "Video se v současné době staví. Zkontrolujte znovu později.",
+ "videotoolbox": "videootoolbox",
+ "vp8_cuvid": "VP8 NVENC (NVIDIA HW Accel)",
+ "vp8_qsv": "VP8 (rychlé synchronizace)",
+ "vp9_cuvid": "VP9 NVENC (NVIDIA HW Accel)",
+ "wannaReset": "Chcete resetovat?",
+ "willTriggerAnEvent": "Spustí událost",
+ "wine glass": "sklenice na víno",
+ "years": "roky",
+ "yourFileDownloadedShortly": "Prosím, čekejte. Váš soubor bude stažen brzy.",
+ "zebra": "zebra"
+}
\ No newline at end of file
diff --git a/languages/en_CA.json b/languages/en_CA.json
index 8b14f7d0..b430cc25 100644
--- a/languages/en_CA.json
+++ b/languages/en_CA.json
@@ -1,6 +1,6 @@
{
"Shinobi": "Shinobi",
- "superAdminTitle": "Shinobi : Super Admin",
+ "superAdminTitle": "Shinobi : Super User",
"failedLoginText1": "You have failed to login too many times. You must wait 15 minutes before trying again.",
"failedLoginText2": "Please check your login credentials.",
"Time Left": "Time Left",
@@ -16,10 +16,32 @@
"accountActionFailed": "Account Action Failed",
"deleteSubAccount": "Delete Sub-Account",
"deleteSubAccountText": "Do you want to delete this Sub-Account? You cannot recover it.",
+ "Accuracy Mode": "Accuracy Mode",
+ "Refresh": "Refresh",
+ "Compress": "Compress",
+ "Tile Size": "Tile Size",
+ "fieldTextTileSize": "When in Accuracy Mode this is the size of each tile in pixels squared. A lower number will have higher accuracy but more resource use.",
"Turn Speed": "Turn Speed",
"Session Key": "Session Key",
+ "Active Monitors": "Active Monitors",
+ "Storage Use": "Storage Use",
"Use Raw Snapshot": "Use Raw Snapshot",
+ "Account Edited": "Account Edited",
+ "Failed to Edit Account": "Failed to Edit Account",
+ "How to Connect": "How to Connect",
"Login": "Login",
+ "Room ID": "Room ID",
+ "Substream": "Substream",
+ "Use Substream": "Use Substream",
+ "useSubStreamOnlyWhenWatching": "Only When Watching, Use Substream",
+ "substreamText": "This is an On-Demand method of viewing the Live Stream. You can make it so the viewing process is available only when someone is watching or to be used for switching between Low and High Resolution.",
+ "substreamConnectionText": "You can leave the Connection detail as-is if you want it to use the main Connection information set above.",
+ "substreamOutputText": "Here you can set the On-Demand Stream's configuration. Learn about latency of Stream types here.",
+ "Toggle Substream": "Toggle Substream",
+ "Output": "Output",
+ "SubstreamNotConfigured": "Substream not configured. Open your Monitor Settings and configure it.",
+ "Substream Process": "Substream Process",
+ "Welcome": "Welcome!",
"API Key Action Failed": "API Key Action Failed",
"Authenticate": "Authenticate",
"Dashboard": "Dashboard",
@@ -27,6 +49,7 @@
"Admin": "Admin",
"Superuser": "Superuser",
"Dashcam": "Dashcam",
+ "System Level": "System Level",
"Email": "Email",
"Username": "Username",
"Profile": "Profile",
@@ -58,6 +81,7 @@
"on": "on",
"OAuth Credentials": "OAuth Credentials",
"Token": "Token",
+ "Admin Account Settings": "Admin Account Settings",
"OAuth Code": "OAuth Code",
"Google Drive": "Google Drive",
"Invert Y-Axis": "Invert Y-Axis",
@@ -92,18 +116,18 @@
"Allow API Trigger": "Allow API Trigger",
"When Detector is Off": "When Detector is Off",
"When Detector is On": "When Detector is On",
- "January" : "January",
- "February" : "February",
- "March" : "March",
- "April" : "April",
+ "January": "January",
+ "February": "February",
+ "March": "March",
+ "April": "April",
"May": "May",
- "June" : "June",
- "July" : "July",
- "August" : "August",
- "September" : "September",
- "October" : "October",
- "November" : "November",
- "December" : "December",
+ "June": "June",
+ "July": "July",
+ "August": "August",
+ "September": "September",
+ "October": "October",
+ "November": "November",
+ "December": "December",
"Sunday": "Sunday",
"Monday": "Monday",
"Tuesday": "Tuesday",
@@ -149,13 +173,17 @@
"Motion GUI": "Motion GUI",
"Motion": "Motion",
"Global Detector Settings": "Global Detector Settings",
+ "Detector Settings": "Detector Settings",
"Trigger Group to Record": "Trigger Group to Record",
"Trigger Camera Groups": "Trigger Camera Groups",
"Motion Detection": "Motion Detection",
"Object Detection": "Object Detection",
+ "Hide Detection on Stream": "Hide Detection on Stream",
"JPEG Mode": "JPEG Mode",
"Reconnect Stream": "Reconnect Stream",
"Order Streams": "Order Streams",
+ "Original Aspect Ratio": "Original Aspect Ratio",
+ "Remember Positions": "Remember Positions",
"Hide Notes": "Hide Notes",
"Example": "Example",
"Logout": "Logout",
@@ -188,6 +216,7 @@
"Videos": "Videos",
"Events": "Events",
"Events Found": "Events Found",
+ "Objects Found": "Objects Found",
"Recent Events": "Recent Events",
"Streams": "Streams",
"Snapshot": "Snapshot",
@@ -201,6 +230,7 @@
"migrateText1": "Input Type could not be parsed. Please set it manually.",
"Build": "Build",
"Building": "Building",
+ "Compressing": "Compressing",
"Started Building": "Started Building",
"Add": "Add",
"Save": "Save",
@@ -235,6 +265,9 @@
"Can Edit Monitor": "Can Edit Monitor",
"Can Delete Videos": "Can Delete Videos",
"Delete Video": "Delete Video",
+ "Delete Videos": "Delete Videos",
+ "Batch Download": "Batch Download",
+ "batchDownloadText": "Do you want to download the selected files?",
"Delete Timelapse Frame": "Delete Timelapse Frame",
"Can View Videos and Events": "Can View Videos and Events",
"Can Delete Videos and Events": "Can Delete Videos and Events",
@@ -262,6 +295,8 @@
"Type": "Type",
"File Type": "File Type",
"Filesize": "Filesize",
+ "Created": "Created",
+ "Size": "Size",
"Video Status": "Video Status",
"Custom Auto Load": "Custom Auto Load",
"Easy Remote Access (P2P)": "Easy Remote Access (P2P)",
@@ -297,6 +332,7 @@
"Start": "Start",
"End": "End",
"Archive": "Archive",
+ "Unarchive": "Unarchive",
"Email Details": "Email Details",
"Delete Matches": "Delete Matches",
"Delete selected": "Delete selected",
@@ -322,6 +358,7 @@
"Event Occurred": "Event Occurred",
"ONVIF Port": "ONVIF Port",
"ONVIF Scanner": "ONVIF Scanner",
+ "ONVIF Events": "ONVIF Events",
"ONVIFEventsNotAvailable": "ONVIF Events not Available",
"ONVIFEventsNotAvailableText1": "This service may not be available for this camera or ONVIF has not initialized yet.",
"ONVIFnotCompliantProfileT": "Camera is not ONVIF Profile T Compliant",
@@ -356,8 +393,16 @@
"Use Max Storage Amount": "Use Max Storage Amount",
"Max Storage Amount": "Max Storage Amount",
"Video Share": "Video Share",
+ "FileBin": "FileBin",
+ "File Download Ready": "File Download Ready",
+ "Timelapse Video Build Complete": "Timelapse Video Build Complete",
+ "yourFileDownloadedShortly": "Please wait. Your file will be downloaded shortly.",
+ "Save Built Video on Completion": "Save Built Video on Completion",
+ "Save Compressed Video on Completion": "Save Compressed Video on Completion",
"FileBin Share": "FileBin Share",
+ "Timelapse Frames": "Timelapse Frames",
"Timelapse Frames Share": "Timelapse Frames Share",
+ "Timelapse Frames Video": "Timelapse Frames Video",
"Number of Days to keep": "Number of Days to keep",
"Monitor Groups": "Monitor Groups",
"Group Name": "Group Name",
@@ -403,6 +448,7 @@
"Autosave": "Autosave",
"Save Directory": "Save Directory",
"CSS": "CSS Style your dashboard.",
+ "Don't Stretch Monitors": "Don't Stretch Monitors",
"Force Monitors Per Row": "Force Monitors Per Row",
"Monitors per row": "Monitors per row for Montage",
"Browser Console Log": "Browser Console Log",
@@ -451,9 +497,19 @@
"Delete Filter": "Delete Filter",
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
"Fix Video": "Fix Video",
- "FixVideoMsg": "Do you want to fix this video? You cannot undo this action.",
+ "Archived": "Archived",
+ "Archive Videos": "Archive Videos",
+ "Compress Videos": "Compress Videos",
+ "Compress Completed Videos": "Compress Completed Videos",
+ "compressCompletedVideosFieldText": "Automatically compress videos to WebM once recorded. Doing this requires a powerful CPU or you must allow a large amount of time to allow compression. The rate in which videos are added to the database can't be faster than the rate for compression.",
+ "FixVideoMsg": "Do you want to fix this video? This will create a new file and overwrite the old one. You cannot undo this action.",
"DeleteVideoMsg": "Do you want to delete this video? You cannot recover it.",
+ "CompressVideoMsg": "Do you want to compress this video? The original will be moved to your FileBin. Videos that are already completed compressing will be skipped if already queued.",
+ "ArchiveVideoMsg": "Do you want to Archive this video? This video won't be deleted by the automated cleanup processes.",
"DeleteThisMsg": "Do you want to delete this? You cannot recover it.",
+ "DeleteTheseMsg": "Do you want to delete these? You cannot recover them.",
+ "CompressTheseMsg": "Do you want to compress these? The originals will be moved to your FileBin. Videos that are already WebM will be skipped. Videos that are already completed compressing will be skipped if ordered to compress again.",
+ "ArchiveTheseMsg": "Do you want to Archive these? They won't be deleted by the automated cleanup processes.",
"dropBoxSuccess": "Success! Files saved to your Dropbox.",
"API Key Deleted": "API Key Deleted",
"APIKeyDeletedText": "Key has been deleted. It will no longer work.",
@@ -496,12 +552,16 @@
"monitorEditFailedMaxReached": "Your account has reached the maximum number of cameras that can be created. Speak to an administrator if you would like this changed.",
"Sub-Accounts": "Sub-Accounts",
"Stream in Background": "Stream in Background",
+ "Carousel in Background": "Carousel in Background",
+ "Last": "Last",
"in": "in",
"ago": "ago",
"a few seconds": "a few seconds",
"a minute": "a minute",
+ "minute": "minute",
"minutes": "minutes",
"an hour": "an hour",
+ "hour": "hour",
"hours": "hours",
"a day": "a day",
"days": "days",
@@ -511,8 +571,11 @@
"a year": "a year",
"years": "years",
"Identity": "Identity",
+ "Additional Inputs": "Additional Inputs",
+ "Input Map": "Input Map",
"Input": "Input",
"Input Feed": "Input Feed",
+ "Input Feeds Selected": "Input Feed Selected",
"Timezone": "Timezone",
"Timezone Offset": "Timezone Offset",
"Stream": "Stream",
@@ -545,12 +608,19 @@
"accountSettingsDescription": "Manage your Profile and set options like Max Storage Amount and Max Number of Days to keep videos.",
"eventFiltersDescription": "Setup filters for when Events occur.",
"monitorConfigFinderDescription": "This tool will help you search for configurations for cameras posted by the community. All hosted on ShinobiHub. You can post yours too, it would really help the community :)",
+ "License Key": "License Key",
+ "License Activation": "License Activation",
+ "License Activation Failed": "License Activation Failed",
+ "License Activated": "License Activated",
+ "Network Manager": "Network Manager",
+ "Nameservers": "Nameservers",
+ "Interface": "Interface",
+ "Optional": "Optional",
+ "Prefix": "Prefix",
"Saved": "Saved",
"Not Saved": "Not Saved",
"Not Connected": "Not Connected",
"License Plate Detector": "License Plate Detector",
- "OpenCV Cascades": "OpenCV Cascades",
- "Refresh List of Cascades": "Refresh List of Cascades",
"\"No Motion\" Detector": "\"No Motion\" Detector",
"Control": "Control",
"Grouping": "Grouping",
@@ -566,6 +636,7 @@
"Creation Interval": "Creation Interval",
"Plugin": "Plugin",
"Plugin Manager": "Plugin Manager",
+ "MonitorStatesText": "You can learn about how to use this here on ShinobiHub.",
"IdentityText1": "This is how the system will identify the data for this stream. You cannot change the Monitor ID once you have pressed save. If you want you can make the Monitor ID more human readable before you continue.",
"IdentityText2": "You can duplicate a monitor by modifying the Monitor ID then pressing save. You cannot use the ID of a monitor that already exists or it will save over that monitor's database information.",
"opencvCascadesText": "If you see nothing here then just download this package of cascades. Drop them into plugins/opencv/cascades then press refresh .",
@@ -583,9 +654,7 @@
"Mode": "Mode",
"Run Installer": "Run Installer",
"Install": "Install",
- "Enable": "Enable",
"Disable": "Disable",
- "Delete": "Delete",
"Add All": "Add All",
"Name": "Name",
"Skip Ping": "Skip Ping",
@@ -611,7 +680,7 @@
"HLS Preset": "Preset Template",
"in seconds": "in seconds",
"HLS List Size": "List Size",
- "Traditional Recording": "Traditional Recording",
+ "Event-Based Recording": "Event-Based Recording",
"Recorded Buffer": "Recorded Buffer",
"Buffer Preview": "Buffer Preview",
"HLS Start Number": "HLS Start Number",
@@ -684,7 +753,7 @@
"Stream to YouTube": "Stream to YouTube",
"Stream to YouTube Flags": "Stream to YouTube Flags",
"Recording Flags": "Recording Flags",
- "Traditional Recording Flags": "Traditional Recording Flags",
+ "Event-Based Recording Flags": "Event-Based Recording Flags",
"Output Method": "Output Method",
"Webhook": "Webhook",
"Event Webhook Error": "Event Webhook Error",
@@ -694,13 +763,14 @@
"Command": "Command",
"No Trigger": "No Trigger",
"noTriggerText": "If motion has not been detected after the timeout period you will recieve an Discord notification.",
+ "On Event": "On Event",
"on Event": "on Event",
"Allow Next Alert": "Allow Next Alert",
"Allow Next Webhook": "Allow Next Webhook",
"Allow Next Command": "Allow Next Command",
"Allow Next Trigger": "Allow Next Trigger",
"Save Events to SQL": "Save Events to SQL",
- "Email on Trigger": "Email on Trigger Emails go to the main account holder's login address.",
+ "Email on Trigger": "Email on Trigger",
"Attach Video Clip": "Attach Video Clip",
"Error While Decoding": "Error While Decoding",
"ErrorWhileDecodingText": "Your hardware may have an unstable connection to the network. Check your network connections.",
@@ -716,7 +786,10 @@
"NotifyErrorText": "Sending Notification caused an Error",
"Check the Channel ID": "Check the Channel ID",
"Check the Recipient ID": "Check the Recipient ID",
+ "AppNotEnabledText": "App Not Enabled, Enable it in your Account Settings.",
"DiscordNotEnabledText": "Discord Bot Not Enabled, Enable it in your Account Settings.",
+ "PushoverNotifyErrorText": "An error occurred while sending Pushover notification",
+ "PushoverNotEnabledText": "Pushover notifications are not enabled. Enable Pushover notifications in Account Settings.",
"Account Settings": "Account Settings",
"How to Record": "How to Record",
"Trigger Record": "Trigger Record",
@@ -810,9 +883,8 @@
"libvorbis (Default)": "libvorbis (Default)",
"libopus": "libopus",
"aac (Default)": "aac (Default)",
- "Traditional (Watch-Only, Includes Buffer)": "Traditional (Watch-Only, Includes Buffer)",
- "Hotswap Modes (Watch-Only)": "Hotswap Modes (Watch-Only)",
- "Delete Motionless Videos (Record)": "Delete Motionless Videos (Record)",
+ "Event-Based Recording (For Watch-Only Mode)": "Event-Based Recording (For Watch-Only Mode)",
+ "Delete Motionless Videos (For Record Mode)": "Delete Motionless Videos (For Record Mode)",
"US": "US",
"EU": "EU",
"Silent": "Silent",
@@ -862,8 +934,8 @@
"Restarting": "Restarting",
"Starting": "Starting",
"Watching": "Watching",
- "Recording": "Recording",
"Stopped": "Stopped",
+ "Stopping": "Stopping",
"Died": "Died",
"Restart": "Restart",
"Monitor Stopped": "Monitor Stopped",
@@ -912,7 +984,13 @@
"MailError": "MAIL ERROR : Could not send email, Check conf.json. Skipping any features relying on mailing.",
"updateKeyText1": "\"updateKey\" is missing from \"conf.json\", cannot do updates this way until you add it.",
"updateKeyText2": "\"updateKey\" is incorrect.",
+ "Control Trigger Started": "Control Trigger Started",
+ "Control Trigger Ended": "Control Trigger Ended",
+ "Control Triggered": "Control Triggered",
"Control Error": "Control Error",
+ "Timed": "Timed",
+ "On Release": "On Release",
+ "Moving to Home Preset": "Moving to Home Preset",
"Database row does not exist": "Database row does not exist",
"File Delete Error": "File Delete Error",
"List of Videos Delete Error": "List of Videos Delete Error",
@@ -937,11 +1015,15 @@
"Monitor mode is already": "Monitor mode is already",
"Monitor or Key does not exist.": "Monitor or Key does not exist.",
"No Group with this key exists": "No Group with this key exists",
+ "Downloaded!": "Downloaded!",
"Downloading...": "Downloading...",
+ "Loading...": "Loading...",
"Downloading Videos": "Downloading Videos",
"Zipping Videos": "Zipping Videos",
"Automatic Checking Cancelled": "Automatic Checking Cancelled",
"Success": "Success",
+ "Done": "Done",
+ "Done!": "Done!",
"Please Wait or Click to Stop Checking": "Please Wait or Click to Stop Checking",
"Search Settings": "Search Settings",
"Trigger Successful": "Trigger Successful",
@@ -992,52 +1074,53 @@
"notPermitted1": "This action is not permitted by the administrator of your account.'",
"Not Authorized": "Not Authorized",
"Generate Subtitles": "Generate Subtitles",
- "Video Limit":"Video Limit",
- "Preview":"Preview",
- "Websocket Connected":"Websocket Connected",
- "Websocket Disconnected":"Websocket Disconnected",
- "Videos Merge":"Videos Merge",
- "Token":"Token",
- "Channel ID":"Channel ID",
- "Recipient ID":"Recipient ID",
- "New Authentication Token":"New Authentication Token",
- "All Logs":"All Logs",
- "For Group":"For Group",
- "Basic Authentication":"Basic Authentication",
- "Superuser Logs":"Superuser Logs",
- "Authentication Failed":"Authentication Failed",
- "Max Number of Cameras":"Max Number of Cameras",
- "Can edit Max Storage":"Can edit Max Storage",
- "Can edit Max Days":"Can edit Max Days",
- "in Days":"in Days",
- "Can edit how long to keep Logs":"Can edit how long to keep Logs",
- "Can use Admin Panel":"Can use Admin Panel",
- "Can use Discord Bot":"Can use Discord Bot",
- "Can use WebDAV":"Can use WebDAV",
- "Can use Amazon S3":"Can use Amazon S3",
- "Can use SFTP":"Can use SFTP",
- "Can use Wasabi Hot Cloud Storage":"Can use Wasabi Hot Cloud Storage",
- "Can use LDAP":"Can use LDAP",
- "Can View Logs":"Can View Logs",
- "Can edit how long to keep Events":"Can edit how long to keep Events",
- "Leave blank for unlimited":"Leave blank for unlimited",
- "privateKey":"Private Key",
- "Limited":"Limited",
- "All Privileges":"All Privileges",
- "LDAP":"LDAP",
- "LDAP Success":"LDAP Success",
- "LDAP User Authenticated":"LDAP User Authenticated",
- "LDAP User is New":"LDAP User is New",
- "Creating New Account":"Creating New Account",
- "bindDN":"bindDN",
- "Bind Credentials":"Bind Credentials (Password)",
- "Search Base":"Search Base",
- "Configuration":"Configuration",
- "Blank for No Change":"Blank for No Change",
- "Pop":"Pop",
- "Recording FPS Change on Start":"Recording FPS Change on Start",
- "Save Frames to Events":"Save Frames to Events",
- "Search Filter":"Search Filter",
+ "Video Limit": "Video Limit",
+ "Preview": "Preview",
+ "Websocket Connected": "Websocket Connected",
+ "Websocket Disconnected": "Websocket Disconnected",
+ "Videos Merge": "Videos Merge",
+ "Channel ID": "Channel ID",
+ "Recipient ID": "Recipient ID",
+ "New Authentication Token": "New Authentication Token",
+ "No Image": "No Image",
+ "All Logs": "All Logs",
+ "For Group": "For Group",
+ "Basic Authentication": "Basic Authentication",
+ "Superuser Logs": "Superuser Logs",
+ "Action Failed": "Action Failed",
+ "Authentication Failed": "Authentication Failed",
+ "Max Number of Cameras": "Max Number of Cameras",
+ "Can edit Max Storage": "Can edit Max Storage",
+ "Can edit Max Days": "Can edit Max Days",
+ "in Days": "in Days",
+ "Can edit how long to keep Logs": "Can edit how long to keep Logs",
+ "Can use Admin Panel": "Can use Admin Panel",
+ "Can use Discord Bot": "Can use Discord Bot",
+ "Can use WebDAV": "Can use WebDAV",
+ "Can use Amazon S3": "Can use Amazon S3",
+ "Can use SFTP": "Can use SFTP",
+ "Can use Wasabi Hot Cloud Storage": "Can use Wasabi Hot Cloud Storage",
+ "Can use LDAP": "Can use LDAP",
+ "Can View Logs": "Can View Logs",
+ "Can edit how long to keep Events": "Can edit how long to keep Events",
+ "Leave blank for unlimited": "Leave blank for unlimited",
+ "privateKey": "Private Key",
+ "Limited": "Limited",
+ "All Privileges": "All Privileges",
+ "LDAP": "LDAP",
+ "LDAP Success": "LDAP Success",
+ "LDAP User Authenticated": "LDAP User Authenticated",
+ "LDAP User is New": "LDAP User is New",
+ "Creating New Account": "Creating New Account",
+ "bindDN": "bindDN",
+ "Bind Credentials": "Bind Credentials (Password)",
+ "Search Base": "Search Base",
+ "Configuration": "Configuration",
+ "Blank for No Change": "Blank for No Change",
+ "Pop": "Pop",
+ "Recording FPS Change on Start": "Recording FPS Change on Start",
+ "Save Frames to Events": "Save Frames to Events",
+ "Search Filter": "Search Filter",
"h264_cuvid": "H.264 CUVID",
"hevc_cuvid": "H.265 CUVID",
"mjpeg_cuvid": "MJPEG CUVID",
@@ -1084,72 +1167,74 @@
"FLV": "FLV",
"FLV Stream Type": "FLV Stream Type",
"Link Shinobi": "Link Shinobi",
- "Show Stream HUD":"Show Stream HUD",
- "Call Method":"Call Method",
- "Gender":"Gender",
- "Emotion":"Emotion",
- "Age":"Age",
- "Object":"Object",
- "Uniform":"Uniform",
- "Pose":"Pose",
- "Male":"Male",
- "Female":"Female",
- "Channel":"Channel",
- "Stream Key":"Stream Key",
- "Server URL":"Server URL",
- "Video Bit Rate":"Video Bit Rate",
- "Audio Bit Rate":"Audio Bit Rate",
- "RTMP Stream Flags":"RTMP Stream Flags",
- "RTMP Stream":"RTMP Stream",
- "Stream Channel":"Stream Channel",
- "Confidence":"Confidence",
- "Trainer Engine":"Trainer Engine",
- "Train":"Train",
- "openImagesDownloadConfirm":"Are you sure you want to begin download images and bounding boxes (preset Matrices) from OpenImages?",
- "openImagesDownloadConfirmStop":"Are you sure you want to stop training?",
- "TrainConfirm":"Are you sure you want to begin training? This can take more than 12 hours with over 500 images. This will consume a large amount of resources, like RAM and/or CPU.",
- "TrainConfirmStop":"Are you sure you want to stop training?",
- "Batch":"Batch",
- "Subdivision":"Subdivision",
- "Map":"Map",
- "Delay for Snapshot":"Delay for Snapshot",
- "Add Map":"Add Map",
- "Add Input Feed":"Add Input Feed",
- "Add Channel":"Add Channel",
- "Automatic":"Automatic",
- "Max Latency":"Max Latency",
- "Loop Stream":"Loop Stream",
- "Object Count":"Object Count",
- "Object Tag":"Object Tag",
- "Noise Filter":"Noise Filter",
- "Noise Filter Range":"Noise Filter Range",
- "TV Channel":"TV Channel",
- "Channel ID":"Channel ID",
- "TV Channel ID":"TV Channel ID",
- "TV Channel Group":"TV Channel Group",
- "Emotion Average":"Emotion Average",
- "Require Object to be in Region":"Require Object to be in Region",
- "Numeric criteria unsupported for Region tests, Ignoring Conditional":"Numeric criteria unsupported for Region tests, Ignoring Conditional",
- "Text criteria unsupported for Object Count tests, Ignoring Conditional":"Text criteria unsupported for Object Count tests, Ignoring Conditional",
- "Show Regions of Interest":"Show Regions of Interest",
- "Confidence of Detection":"Confidence of Detection",
- "Edit Selected":"Edit Selected",
- "Copy Stream Channels":"Copy Stream Channels",
- "Copy Settings":"Copy Settings",
- "Copy to Settings":"Copy to Settings",
- "Copy Mode":"Copy Mode",
- "Copy Group Settings":"Copy Group Settings",
- "Copy Timelapse Settings":"Copy Timelapse Settings",
- "Copy Connection Settings":"Copy Connection Settings",
- "Copy Custom Settings":"Copy Custom Settings",
- "Copy Logging Settings":"Copy Logging Settings",
- "Copy JPEG API Settings":"Copy JPEG API Settings",
- "Copy Input Settings":"Copy Input Settings",
- "Copy Stream Settings":"Copy Stream Settings",
- "Copy Stream Channel Settings":"Copy Stream Channel Settings",
- "Copy Recording Settings":"Copy Recording Settings",
- "Copy Detector Settings":"Copy Detector Settings",
- "Monitors to Copy to":"Monitors to Copy to",
+ "Show Stream HUD": "Show Stream HUD",
+ "Call Method": "Call Method",
+ "Gender": "Gender",
+ "Emotion": "Emotion",
+ "Age": "Age",
+ "Object": "Object",
+ "Uniform": "Uniform",
+ "Pose": "Pose",
+ "Male": "Male",
+ "Female": "Female",
+ "Channel": "Channel",
+ "Stream Key": "Stream Key",
+ "Server URL": "Server URL",
+ "Video Bit Rate": "Video Bit Rate",
+ "Audio Bit Rate": "Audio Bit Rate",
+ "RTMP Stream Flags": "RTMP Stream Flags",
+ "RTMP Stream": "RTMP Stream",
+ "Stream Channel": "Stream Channel",
+ "Stream Channels": "Stream Channels",
+ "Confidence": "Confidence",
+ "Trainer Engine": "Trainer Engine",
+ "Train": "Train",
+ "openImagesDownloadConfirm": "Are you sure you want to begin download images and bounding boxes (preset Matrices) from OpenImages?",
+ "openImagesDownloadConfirmStop": "Are you sure you want to stop training?",
+ "TrainConfirm": "Are you sure you want to begin training? This can take more than 12 hours with over 500 images. This will consume a large amount of resources, like RAM and/or CPU.",
+ "TrainConfirmStop": "Are you sure you want to stop training?",
+ "Batch": "Batch",
+ "Subdivision": "Subdivision",
+ "Map": "Map",
+ "Delay for Snapshot": "Delay for Snapshot",
+ "Add Map": "Add Map",
+ "Add Input Feed": "Add Input Feed",
+ "Add Channel": "Add Channel",
+ "Automatic": "Automatic",
+ "Max Latency": "Max Latency",
+ "Loop Stream": "Loop Stream",
+ "Object Count": "Object Count",
+ "Object Tag": "Object Tag",
+ "Search Object Tags": "Search Object Tags",
+ "Noise Filter": "Noise Filter",
+ "Noise Filter Range": "Noise Filter Range",
+ "TV Channel": "TV Channel",
+ "TV Channel ID": "TV Channel ID",
+ "TV Channel Group": "TV Channel Group",
+ "Emotion Average": "Emotion Average",
+ "Require Object to be in Region": "Require Object to be in Region",
+ "Numeric criteria unsupported for Region tests, Ignoring Conditional": "Numeric criteria unsupported for Region tests, Ignoring Conditional",
+ "Text criteria unsupported for Object Count tests, Ignoring Conditional": "Text criteria unsupported for Object Count tests, Ignoring Conditional",
+ "Show Regions of Interest": "Show Regions of Interest",
+ "Confidence of Detection": "Confidence of Detection",
+ "Edit Selected": "Edit Selected",
+ "Copy Stream Channels": "Copy Stream Channels",
+ "Copy to Selected Monitor(s)": "Copy to Selected Monitor(s)",
+ "Copy Settings": "Copy Settings",
+ "Copy to Settings": "Copy to Settings",
+ "Copy Mode": "Copy Mode",
+ "Copy Group Settings": "Copy Group Settings",
+ "Copy Timelapse Settings": "Copy Timelapse Settings",
+ "Copy Connection Settings": "Copy Connection Settings",
+ "Copy Custom Settings": "Copy Custom Settings",
+ "Copy Logging Settings": "Copy Logging Settings",
+ "Copy JPEG API Settings": "Copy JPEG API Settings",
+ "Copy Input Settings": "Copy Input Settings",
+ "Copy Stream Settings": "Copy Stream Settings",
+ "Copy Stream Channel Settings": "Copy Stream Channel Settings",
+ "Copy Recording Settings": "Copy Recording Settings",
+ "Copy Detector Settings": "Copy Detector Settings",
+ "Monitors to Copy to": "Monitors to Copy to",
"Video Configuration": "Video Configuration",
"ONVIF Device Manager": "ONVIF Device Manager",
"UseCount": "UseCount",
@@ -1208,15 +1293,419 @@
"getVideos": "Get Videos",
"getVideosForMonitor": "Get Videos for Monitor",
"No Sound": "No Sound",
- "Notification Sound":"Notification Sound",
- "Alert Sound":"Alert Sound",
- "Alert Sound Delay":"Alert Sound Delay",
- "onvifdeviceManagerGlobalTip":"ONVIF allows modifying the camera's internal settings. ONVIF is somewhat of an umbrella term, it can mean many things unfortunately. With that being the case you may see an option in this tool but it may not be editable. This is usually because the camera vendor has not added this method or has deviated from its intended usage. In those cases you will need to enter the camera's configuration through the prescribed method of the camera vendor, this is generally opening the IP Address of the camera in your web browser.",
- "onvifdeviceSavedText":"Camera's internal settings have been saved. You may need to restart the camera to have these changes take effect.",
- "onvifdeviceSavedFoundErrorText":"Some settings may have reverted to a previous value. Its possible that the modified option is not available with this camera through ONVIF.",
- "powerVideoEventLimit":"You have set a high event limit. Are you sure you want to make this request?",
- "There are no monitors that you can view with this account.":"There are no monitors that you can view with this account.",
+ "Notification Sound": "Notification Sound",
+ "Alert Sound": "Alert Sound",
+ "Alert Sound Delay": "Alert Sound Delay",
+ "onvifdeviceManagerGlobalTip": "ONVIF allows modifying the camera's internal settings. ONVIF is somewhat of an umbrella term, it can mean many things unfortunately. With that being the case you may see an option in this tool but it may not be editable. This is usually because the camera vendor has not added this method or has deviated from its intended usage. In those cases you will need to enter the camera's configuration through the prescribed method of the camera vendor, this is generally opening the IP Address of the camera in your web browser.",
+ "onvifdeviceSavedText": "Camera's internal settings have been saved. You may need to restart the camera to have these changes take effect.",
+ "onvifdeviceSavedFoundErrorText": "Some settings may have reverted to a previous value. Its possible that the modified option is not available with this camera through ONVIF.",
+ "powerVideoEventLimit": "You have set a high event limit. Are you sure you want to make this request?",
+ "There are no monitors that you can view with this account.": "There are no monitors that you can view with this account.",
"Delete Monitors and Files": "Delete Monitors and Files",
"Select atleast one monitor to delete": "Select atleast one monitor to delete.",
- "Use Built-In":"Use Built-In"
+ "Use Built-In": "Use Built-In",
+ "Add Cameras": "Add Cameras",
+ "Add Camera": "Add Camera",
+ "Delete Camera": "Delete Camera",
+ "Event Rules": "Event Rules",
+ "Other Devices": "Other Devices",
+ "Zones": "Zones",
+ "Information": "Information",
+ "Info": "Info",
+ "Motion Threshold": "Motion Threshold",
+ "Attach Snapshot": "Attach Snapshot",
+ "Invalid Settings": "Invalid Settings",
+ "Detection": "Detection",
+ "Playback": "Playback",
+ "Backup": "Backup",
+ "Close All Monitors": "Close All Monitors",
+ "Daily Events": "Daily Events",
+ "Send Notification": "Send Notification",
+ "Send to": "Send to",
+ "setMaxStorageAmountText": "You should set your Max Storage Amount in your Account Settings located on the left. Find the option under the Profile section. Default is 10 GB.",
+ "Save Events": "Save Events",
+ "Original Choice": "Original Choice",
+ "Legacy Webhook": "Legacy Webhook",
+ "eventFilterActionText": "These are the actions that occur from the filter conditions that have succeeded. \"Original Choice\" refers to the option you had chosen in your Monitor's Settings.",
+ "Telegram": "Telegram",
+ "Before": "Before",
+ "After": "After",
+ "Rule": "Rule",
+ "Event Filter Error": "Event Filter Error",
+ "eventFilterErrorBrackets": "You have an un-even number of brackets. They are being ignored.",
+ "Quick Settings": "Quick Settings",
+ "Copy Stream URL": "Copy Stream URL",
+ "willTriggerAnEvent": "will trigger an event",
+ "Cloud": "Cloud",
+ "Objects to look for": "Objects to look for",
+ "Common Objects": "Common Objects",
+ "Uncommon Objects": "Uncommon Objects",
+ "person": "person",
+ "bicycle": "bicycle",
+ "car": "car",
+ "motorcycle": "motorcycle",
+ "airplane": "airplane",
+ "bus": "bus",
+ "train": "train",
+ "truck": "truck",
+ "boat": "boat",
+ "traffic light": "traffic light",
+ "fire hydrant": "fire hydrant",
+ "stop sign": "stop sign",
+ "parking meter": "parking meter",
+ "bench": "bench",
+ "bird": "bird",
+ "cat": "cat",
+ "dog": "dog",
+ "horse": "horse",
+ "sheep": "sheep",
+ "cow": "cow",
+ "elephant": "elephant",
+ "bear": "bear",
+ "zebra": "zebra",
+ "giraffe": "giraffe",
+ "backpack": "backpack",
+ "umbrella": "umbrella",
+ "handbag": "handbag",
+ "tie": "tie",
+ "suitcase": "suitcase",
+ "frisbee": "frisbee",
+ "skis": "skis",
+ "snowboard": "snowboard",
+ "sports ball": "sports ball",
+ "kite": "kite",
+ "baseball bat": "baseball bat",
+ "baseball glove": "baseball glove",
+ "skateboard": "skateboard",
+ "surfboard": "surfboard",
+ "tennis racket": "tennis racket",
+ "bottle": "bottle",
+ "wine glass": "wine glass",
+ "cup": "cup",
+ "fork": "fork",
+ "knife": "knife",
+ "spoon": "spoon",
+ "bowl": "bowl",
+ "banana": "banana",
+ "apple": "apple",
+ "sandwich": "sandwich",
+ "orange": "orange",
+ "broccoli": "broccoli",
+ "carrot": "carrot",
+ "hot dog": "hot dog",
+ "pizza": "pizza",
+ "donut": "donut",
+ "cake": "cake",
+ "chair": "chair",
+ "couch": "couch",
+ "potted plant": "potted plant",
+ "bed": "bed",
+ "dining table": "dining table",
+ "toilet": "toilet",
+ "tv": "tv",
+ "laptop": "laptop",
+ "mouse": "mouse",
+ "remote": "remote",
+ "keyboard": "keyboard",
+ "cell phone": "cell phone",
+ "microwave": "microwave",
+ "oven": "oven",
+ "toaster": "toaster",
+ "sink": "sink",
+ "refrigerator": "refrigerator",
+ "book": "book",
+ "clock": "clock",
+ "Clock Format": "Clock Format",
+ "vase": "vase",
+ "scissors": "scissors",
+ "teddy bear": "teddy bear",
+ "hair drier": "hair drier",
+ "toothbrush": "toothbrush",
+ "Detection Event": "Detection Event",
+ "Monitor Edit": "Monitor Edit",
+ "Monitor Start": "Monitor Start",
+ "Monitor Stop": "Monitor Stop",
+ "Monitor Died": "Monitor Died",
+ "Account Save": "Account Save",
+ "User Log": "User Log",
+ "Frigate": "Frigate",
+ "Plain": "Plain",
+ "MQTT Error": "MQTT Error",
+ "MQTT Inbound": "MQTT Inbound",
+ "MQTT Outbound": "MQTT Outbound",
+ "MQTT Client": "MQTT Client",
+ "Buffer Time from Event": "Buffer Time from Event",
+ "fieldTextEventFilters": "Enable to have all Events honor your Event Filter rules.",
+ "fieldTextBufferTimeFromEvent": "The amount of seconds to record before the trigger happened. If this is consistently inaccurate you will need to look at the optimization guide or force encoding on the server.",
+ "fieldTextMode": "This is the primary task of the monitor.",
+ "fieldTextModeDisabled": "Inactive monitor, no process will be created in this mode.",
+ "fieldTextModeWatchOnly": "Monitor will only stream, no recording will occur unless otherwise ordered by API or Detector.",
+ "fieldTextModeRecord": "Continuous Recording. Segments are made every 15 minutes by default.",
+ "fieldTextMid": "This is a non-changeable identifier for the monitor. You can duplicate a monitor by double clicking the Monitor ID and changing it.",
+ "fieldTextName": "This is the human-readable display name for the monitor.",
+ "fieldTextMaxKeepDays": "The number of days to keep videos before purging for this monitor specifically.",
+ "fieldTextNotes": "Comments you want to leave for this camera.",
+ "fieldTextDir": "Location of where recorded files will be saved. You can configure more locations with the addStorage variable.",
+ "fieldTextType": "The method that will used to consume the video stream.",
+ "fieldTextTypeJPEG": "Reading snapshots from a URL and making a stream and/or video from them.",
+ "fieldTextTypeMJPEG": "Similar to JPEG except the frame handling is done by FFMPEG, not Shinobi.",
+ "fieldTextTypeH.264/H.265/H.265+": "Reading a high quality video streas that sometimes include audio.",
+ "fieldTextTypeHLS(.m3u8)": "Reading a high quality video streas that sometimes include audio.",
+ "fieldTextTypeMPEG4(.mp4/.ts)": "A static file. Read at a lower rate and should not be used for an actual live stream.",
+ "fieldTextTypeShinobiStreamer": "Websocket JPEG-based P2P stream.",
+ "fieldTextTypeDashcam(StreamerV2)": "Websocket WebM-based P2P stream.",
+ "fieldTextTypeLocal": "Reading Capture Cards, Webcams, or Integrated Cameras.",
+ "fieldTextTypeRTMP": "Learn to connect here : Article : How to Push Streams via RTMP to Shinobi",
+ "fieldTextTypeMxPEG": "Mobotix MJPEG Stream",
+ "fieldTextRtmpKey": "Stream Key for incoming streams on the RTMP port.",
+ "fieldTextAutoHostEnable": "Feed the individual pieces required to build a stream URL or provide the full URL and allow Shinobi to parse it for you.",
+ "fieldTextAutoHost": "The full Stream URL.",
+ "fieldTextProtocol": "The protocol that will used to consume the video stream.",
+ "fieldTextRtspTransport": "The transport protocol your camera will use. TCP is usually the best choice.",
+ "fieldTextRtspTransportAuto": "Let FFMPEG decide. Normally it will try UDP first.",
+ "fieldTextRtspTransportTCP": "Set it to this if UDP starts giving undesired results.",
+ "fieldTextRtspTransportUDP": "FFMPEG tries this first.",
+ "fieldTextRtspTransportHTTP": "Standard connection method.",
+ "fieldTextMuser": "The user login for your camera",
+ "fieldTextMpass": "The password for your camera",
+ "fieldTextHost": "Connection address",
+ "fieldTextPort": "Separate by Commas or a Range",
+ "fieldTextPortForce": "Using the default web port can allow automatic switch to other ports for streams like RTSP.",
+ "fieldTextPath": "The path to your camera",
+ "fieldTextFatalMax": "The number of times to retry for network connection between the server and camera before setting the monitor to Disabled. No decimals. Set to 0 to retry forever.",
+ "fieldTextSkipPing": "Choose if a successful ping is required before a monitor process is started.",
+ "fieldTextIsOnvif": "Is this an ONVIF compliant camera?",
+ "fieldTextOnvifNonStandard": "Is this a Non-Standard ONVIF camera?",
+ "fieldTextOnvifPort": "ONVIF is usually run on port 8000. This can be 80 as well depending on your camera model.",
+ "fieldTextAduration": "Specify how many microseconds are analyzed to probe the input. Set to 100000 if you are using RTSP and having stream issues.",
+ "fieldTextProbesize": "Specify how big to make the analyzation probe for the input. Set to 100000 if you are using RTSP and having stream issues.",
+ "fieldTextStreamLoop": "Loop a static file so the file stream behaves like a live stream.",
+ "fieldTextSfps": "Specify the Frame Rate (FPS) in which the camera is providing its stream in.",
+ "fieldTextWallClockTimestampIgnore": "Base all incoming camera data in camera time instead of server time.",
+ "fieldTextHeight": "Height of the stream image.",
+ "fieldTextWidth": "Width of the stream image.",
+ "fieldTextAccelerator": "Hardware Acceleration (HWAccel) for decoding streams.",
+ "fieldTextHwaccel": "Decoding Engine",
+ "fieldTextHwaccelVcodec": "Decoding Engine",
+ "fieldTextStreamType": "The method that will used to consume the video stream.",
+ "fieldTextStreamTypePoseidon": "Poseidon is built on Kevin Godell's MP4 processing code. It simulates a streaming MP4 file but using the data of a live stream. Includes Audio. Some browsers can play it like a regular MP4 file. Streams over HTTP or WebSocket.",
+ "fieldTextStreamTypeBase64OverWebsocket": "Sending Base64 encoded frames over WebSocket. This avoids caching but there is no audio.",
+ "fieldTextStreamTypeMJPEG": "Standard Motion JPEG image. No audio.",
+ "fieldTextStreamTypeFLV": "Sending FLV encoded frames over WebSocket.",
+ "fieldTextStreamTypeHLS(includesAudio)": "Similar method to facebook live streams. Includes audio if input provides it. There is a delay of about 4-6 seconds because this method records segments then pushes them to the client rather than push as while it creates them.",
+ "fieldTextStreamFlvType": "This is for the Shinobi dashboard only. Both stream methods are still active and ready to use.",
+ "fieldTextStreamVcodec": "Video codec for streaming.",
+ "fieldTextStreamVcodecAuto": "Let FFMPEG choose.",
+ "fieldTextStreamVcodecLibx264": "Used for MP4 video.",
+ "fieldTextStreamVcodecLibx265": "Used for MP4 video.",
+ "fieldTextStreamVcodecCopy": "Used for MP4 video. Has very low CPU usage but cannot use video filters and filesizes may be gigantic. Best to setup your MP4 settings camera-side when using this option.",
+ "fieldTextStreamAcodec": "Audio codec for streaming.",
+ "fieldTextStreamAcodecAuto": "Let FFMPEG choose.",
+ "fieldTextStreamAcodecNoAudio": "No Audio, this is an option that must be set in some parts of the world due to legal reasons.",
+ "fieldTextStreamAcodecLibvorbis": "Used for WebM video.",
+ "fieldTextStreamAcodecLibopus": "Used for WebM video.",
+ "fieldTextStreamAcodecLibmp3lame": "Used for MP4 video.",
+ "fieldTextStreamAcodecAac": "Used for MP4 video.",
+ "fieldTextStreamAcodecAc3": "Used for MP4 video.",
+ "fieldTextStreamAcodecCopy": "Used for MP4 video. Has very low CPU usage but some audio codecs need custom flags like -strict 2 for aac.",
+ "fieldTextHlsTime": "How long each video segment should be, in minutes. Each segment will be drawn by the client through an m3u8 file. Shorter segments take less space.",
+ "fieldTextHlsListSize": "The number of segments maximum before deleting old segments automatically.",
+ "fieldTextPresetStream": "Preset flag for certain video encoders. If you find your camera is crashing every few seconds : try leaving it blank.",
+ "fieldTextStreamQuality": "Low number means higher quality. Higher number means less quality.",
+ "fieldTextStreamFps": "The speed in which frames are displayed to clients, in Frames Per Second. Be aware there is no default. This can lead to high bandwidth usage.",
+ "fieldTextStreamScaleX": "Width of the stream image that is output after processing.",
+ "fieldTextStreamScaleY": "Height of the stream image that is output after processing.",
+ "fieldTextStreamRotate": "Change the viewing angle of the video stream.",
+ "fieldTextSignalCheck": "How often your client will check the stream to see if it is alive. This is calculated in minutes.",
+ "fieldTextSignalCheckLog": "This is for the client side only. It will display in the log thread when client side signal checks occur.",
+ "fieldTextStreamVf": "Place FFMPEG video filters in this box to affect the streaming portion. No spaces.",
+ "fieldTextTvChannel": "This monitor will have TV Channel features enabled. You will be able to view it in your TV Channel list.",
+ "fieldTextTvChannelId": "A Custom ID for the Channel.",
+ "fieldTextTvChannelGroupTitle": "A Custom Group for the Channel.",
+ "fieldTextStreamTimestamp": "A clock that is burned onto the frames of the video stream.",
+ "fieldTextStreamTimestampFont": "Font File to style your timestamp.",
+ "fieldTextStreamTimestampFontSize": "Font size in pt.",
+ "fieldTextStreamTimestampColor": "Timstamp text color.",
+ "fieldTextStreamTimestampBoxColor": "Timstamp backdrop color.",
+ "fieldTextStreamTimestampX": "Horiztonal Position of Timestamp",
+ "fieldTextStreamTimestampY": "Vertical Position of Timestamp",
+ "fieldTextStreamWatermark": "An image that is burned onto the frames of the video stream.",
+ "fieldTextStreamWatermarkLocation": "Image Location that will be used as Watermark.",
+ "fieldTextStreamWatermarkPosition": "An image that is burned onto the frames of the video stream.",
+ "fieldTextDetailSubstreamInputRtspTransportAuto": "Let FFMPEG decide. Normally it will try UDP first.",
+ "fieldTextDetailSubstreamInputRtspTransportTCP": "Set it to this if UDP starts giving undesired results.",
+ "fieldTextDetailSubstreamInputRtspTransportUDP": "FFMPEG tries this first.",
+ "fieldTextDetailSubstreamOutputStreamType": "The method that will used to consume the video stream.",
+ "fieldTextDetailSubstreamOutputStreamVcodec": "Video codec for streaming.",
+ "fieldTextDetailSubstreamOutputStreamVcodecAuto": "Let FFMPEG choose.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx264": "Used for MP4 video.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx265": "Used for MP4 video.",
+ "fieldTextDetailSubstreamOutputStreamVcodecCopy": "Used for MP4 video. Has very low CPU usage but cannot use video filters and filesizes may be gigantic. Best to setup your MP4 settings camera-side when using this option.",
+ "fieldTextDetailSubstreamOutputStreamAcodec": "Audio codec for streaming.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAuto": "Let FFMPEG choose.",
+ "fieldTextDetailSubstreamOutputStreamAcodecNoAudio": "No Audio, this is an option that must be set in some parts of the world due to legal reasons.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibvorbis": "Used for WebM video.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibopus": "Used for WebM video.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibmp3lame": "Used for MP4 video.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAac": "Used for MP4 video.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAc3": "Used for MP4 video.",
+ "fieldTextDetailSubstreamOutputStreamAcodecCopy": "Used for MP4 video. Has very low CPU usage but some audio codecs need custom flags like -strict 2 for aac.",
+ "fieldTextDetailSubstreamOutputHlsTime": "How long each video segment should be, in minutes. Each segment will be drawn by the client through an m3u8 file. Shorter segments take less space.",
+ "fieldTextDetailSubstreamOutputHlsListSize": "The number of segments maximum before deleting old segments automatically.",
+ "fieldTextDetailSubstreamOutputPresetStream": "Preset flag for certain video encoders. If you find your camera is crashing every few seconds : try leaving it blank.",
+ "fieldTextDetailSubstreamOutputStreamQuality": "Low number means higher quality. Higher number means less quality.",
+ "fieldTextDetailSubstreamOutputStreamFps": "The speed in which frames are displayed to clients, in Frames Per Second. Be aware there is no default. This can lead to high bandwidth usage.",
+ "fieldTextDetailSubstreamOutputStreamScaleX": "Width of the stream image that is output after processing.",
+ "fieldTextDetailSubstreamOutputStreamScaleY": "Height of the stream image that is output after processing.",
+ "fieldTextDetailSubstreamOutputStreamRotate": "Change the viewing angle of the video stream.",
+ "fieldTextDetailSubstreamOutputSvf": "Place FFMPEG video filters in this box to affect the streaming portion. No spaces.",
+ "fieldTextSnap": "Get the latest frame in JPEG.",
+ "fieldTextExt": "The file type for your recorded video file.",
+ "fieldTextExtMP4": "This file type is playable is almost all modern web browsers, that includes mobile. The filesize just tends to be larger unless you lower the quality.",
+ "fieldTextExtWebM": "Small filesize, low client compatibility. Good for uploading to sites like YouTube.",
+ "fieldTextVcodec": "Video codec for recording.",
+ "fieldTextCrf": "Low number means higher quality. Higher number means less quality.",
+ "fieldTextPresetRecord": "Preset flag for certain video encoders. If you find your camera is crashing every few seconds : try leaving it blank.",
+ "fieldTextAcodec": "Audio codec for recording.",
+ "fieldTextFps": "The speed in which frames are recorded to files, Frames Per Second. Be aware there is no default. This can lead to large files. Best to set this camera-side.",
+ "fieldTextRecordScaleY": "Height of the stream image.",
+ "fieldTextRecordScaleX": "Width of the stream image.",
+ "fieldTextCutoff": "In minutes. When to slice off and start a new video file.",
+ "fieldTextRotate": "Change the recording angle of the video stream.",
+ "fieldTextVf": "Place FFMPEG video filters in this box to affect the recording portion. No spaces.",
+ "fieldTextTimestamp": "A clock that is burned onto the frames of the recorded video.",
+ "fieldTextTimestampFont": "Font File to style your timestamp.",
+ "fieldTextTimestampFontSize": "Font size in pt.",
+ "fieldTextTimestampColor": "Timstamp text color.",
+ "fieldTextTimestampBoxColor": "Timstamp backdrop color.",
+ "fieldTextTimestampX": "Horiztonal Position of Timestamp",
+ "fieldTextTimestampY": "Vertical Position of Timestamp",
+ "fieldTextWatermark": "An image that is burned onto the frames of the recorded video.",
+ "fieldTextWatermarkLocation": "Image Location that will be used as Watermark.",
+ "fieldTextWatermarkPosition": "An image that is burned onto the frames of the recorded video.",
+ "fieldTextRecordTimelapse": "Create a JPEG based timelapse.",
+ "fieldTextRecordTimelapseMp4": "Create an MP4 file at the end of each day for the timelapse.",
+ "fieldTextRecordTimelapseWatermark": "An image that is burned onto the frames of the recorded video.",
+ "fieldTextRecordTimelapseWatermarkLocation": "Image Location that will be used as Watermark.",
+ "fieldTextRecordTimelapseWatermarkPosition": "An image that is burned onto the frames of the recorded video.",
+ "fieldTextCustInput": "Custom Flags that bind to the Input of the FFMPEG process.",
+ "fieldTextCustStream": "Custom Flags that bind to the Stream (client side view) of the FFMPEG process.",
+ "fieldTextCustSnap": "Custom Flags that bind to the Snapshots.",
+ "fieldTextCustRecord": "Custom Flags that bind to the recording of the FFMPEG process.",
+ "fieldTextCustDetect": "Custom Flags that bind to the stream Detector uses for analyzation.",
+ "fieldTextCustDetectObject": "Custom Flags that bind to the stream Detector uses for analyzation.",
+ "fieldTextCustSipRecord": "Custom Flags that bind to the output that the Event-Based Recordings siphon from.",
+ "fieldTextCustomOutput": "Add a custom output like JPEG frames or send data straight to another server.",
+ "fieldTextDetector": "This will add another output in the FFMPEG command for the motion detector.",
+ "fieldTextDetectorHttpApi": "Do you want to allow HTTP triggers to this camera?",
+ "fieldTextDetectorSendFrames": "Push frames to the connected plugin to be analyzed.",
+ "fieldTextDetectorFps": "How many frames a second to send to the motion detector; 2 is the default.",
+ "fieldTextDetectorScaleX": "Width of the image being detected. Smaller sizes take less CPU.",
+ "fieldTextDetectorScaleY": "Height of the image being detected. Smaller sizes take less CPU.",
+ "fieldTextDetectorLockTimeout": "Lockout for when the next trigger is allowed, to avoid overloading the database and receiving clients. Measured in milliseconds.",
+ "fieldTextDetectorSave": "Save Events in the database. This will allow display of the events over video during playback.",
+ "fieldTextDetectorRecordMethod": "There are multiple ways to begin recording when an event occurs, like motion. Event-Based Recording is the most user-friendly.",
+ "fieldTextDetectorTrigger": "This will order the camera to record if it is set to \"Watch-Only\" when an Event is detected.",
+ "fieldTextDetectorTimeout": "The length of time \"Trigger Record\" will run for. This is read in minutes.",
+ "fieldTextWatchdogReset": "Enable to reset the active Recording Timeout back to the beginning when a new event occurs while recording.",
+ "fieldTextDetectorWebhook": "Send a GET request to a URL with some values from the event.",
+ "fieldTextDetectorWebhookTimeout": "This value is a timer to allow the next running of your Webhook. This value is in minutes.",
+ "fieldTextDetectorCommand": "The command that will run. This is the equivalent of running a shell command from terminal.",
+ "fieldTextDetectorCommandTimeout": "This value is a timer to allow the next running of your script. This value is in minutes.",
+ "fieldTextSnapSecondsInward": "in seconds",
+ "fieldTextDetectorPam": "Use Kevin Godell's Motion Detector. This is built into Shinobi and requires no other configuration to activate.",
+ "fieldTextDetectorSensitivity": "The motion confidence rating must exceed this value to be seen as a trigger. This number correlates directly to the confidence rating returned by the motion detector. This option was previously named \"Indifference\".",
+ "fieldTextDetectorMaxSensitivity": "The motion confidence rating must be lower than this value to be seen as a trigger. Leave blank for no maximum. This option was previously named \"Max Indifference\".",
+ "fieldTextDetectorThreshold": "Minimum number of detections to fire a motion event. Detections must be within the detector the threshold divided by detector fps seconds. For example, if detector fps is 2 and trigger threshold is 3, then three detections must occur within 1.5 seconds to trigger a motion event. This threshold is per detection region.",
+ "fieldTextDetectorColorThreshold": "The amount of difference allowed in a pixel before it is considered motion.",
+ "fieldTextInverseTrigger": "To trigger outside specified regions. Will not trigger with Full Frame Detection enabled.",
+ "fieldTextDetectorFrame": "This will read the entire frame for pixel differences. This is the same as creating a region that covers the entire screen. If no Region is added to this Monitor this option will default to Yes.",
+ "fieldTextDetectorNoiseFilter": "Attempt to filter grain or repeated motion at a particular indifference.",
+ "fieldTextDetectorNoiseFilterRange": "The amount of difference allowed in a pixel before it is considered motion.",
+ "fieldTextDetectorNotrigger": "Check if motion has occured on an interval. If motion has occurred the check will be reset.",
+ "fieldTextDetectorNotriggerTimeout": "Timeout is calculated in minutes.",
+ "fieldTextDetectorNotriggerDiscord": "If motion has not been detected after the timeout period you will recieve an Discord notification.",
+ "fieldTextDetectorNotriggerWebhook": "Send a GET request to a URL with some values from the event.",
+ "fieldTextDetectorNotriggerCommand": "The command that will run. This is the equivalent of running a shell command from terminal.",
+ "fieldTextDetectorNotriggerCommandTimeout": "This value is a timer to allow the next running of your script. This value is in minutes.",
+ "fieldTextDetectorAudio": "Check if Audio has occured at a certiain decible. Decible reading may not be accurate to real-world measurement.",
+ "fieldTextDetectorUseDetectObject": "Create frames for sending to any connected Plugin.",
+ "fieldTextDetectorSendFramesObject": "Push frames to the connected plugin to be analyzed.",
+ "fieldTextDetectorObjCountInRegion": "Count Objects only inside Regions.",
+ "fieldTextDetectorLisencePlate": "Enable License Plate Recognition. OpenALPR plugin has this always enabled.",
+ "fieldTextDetectorLisencePlateCountry": "Choose the type of plates to recognize. Only US and EU are supported at this time.",
+ "fieldTextEventRecordScaleX": "Width of the Event-based Recording image that is output after processing.",
+ "fieldTextEventRecordScaleY": "Height of the Event-based Recording image that is output after processing.",
+ "fieldTextDetectorBufferHlsTime": "How long each video segment should be, in seconds. Each segment will be drawn by the client through an m3u8 file. Shorter segments take less space.",
+ "fieldTextDetectorBufferHlsListSize": "The number of segments maximum before deleting old segments automatically.",
+ "fieldTextDetectorPtzFollow": "Follow the largest detected object with PTZ? Requires an Object Detector running or matrices provided with events.",
+ "fieldTextDetectorObjCount": "Count detected objects.",
+ "fieldTextControlInvertY": "For When your camera is mounted upside down or uses inverted vertical controls.",
+ "fieldTextDetectorSendVideoLength": "In seconds. The length of the video that gets sent to your Notification service, like Email or Discord.",
+ "fieldTextLoglevel": "The amount of data to provide while doing the job.",
+ "fieldTextLoglevelSilent": "None. This will silence all logging.",
+ "fieldTextLoglevelFatal": "Display only fatal errors.",
+ "fieldTextLoglevelOnError": "Display all important errors. Note : this doesn't always show important information.",
+ "fieldTextLoglevelAllWarnings": "Display all warnings. Use this if you can't find out what's wrong with your camera.",
+ "fieldTextSqllog": "Use this with caution as FFMPEG likes to throw up superfluous data at times which can lead to a lot of database rows.",
+ "fieldTextSqllogNo": "No is the default.",
+ "fieldTextSqllogYes": "Do this if you are having recurring issues only.",
+ "fieldTextFactorAuth": "Enable a secondary requirement for login through one of the enabled methods.",
+ "fieldTextMail": "The login for accounts. The main account holder's email address will get notifications.",
+ "fieldTextPass": "Leave blank to keep the same password during settings modification.",
+ "fieldTextPasswordAgain": "Must match Password field if you desire to change it.",
+ "fieldTextSize": "The amount of disk space Shinobi will allow to be consumed before purging. This value is read in megabytes.",
+ "fieldTextSizeVideoPercent": "Percent of Max Storage Amount the videos can record to.",
+ "fieldTextSizeTimelapsePercent": "Percent of Max Storage Amount the timelapse frames can record to.",
+ "fieldTextSizeFilebinPercent": "Percent of Max Storage Amount the FileBin archive can use.",
+ "fieldTextDays": "The number of days to keep videos before purging.",
+ "fieldTextEventDays": "The number of days to keep events before purging.",
+ "fieldTextLogDays": "The number of days to keep logs before purging.",
+ "fieldTextLang": "The primary language of text elements. For complete translation add your language in conf.json e.g:\"language\": \"en_CA\",",
+ "fieldTextAudioNote": "Sound when information bubble appears.",
+ "fieldTextAudioAlert": "Sound when Event occurs.",
+ "fieldTextAudioDelay": "Delay until next time an Event can start an Alert. Measured in seconds.",
+ "fieldTextEventMonPop": "When an Event occurs popout the monitor stream.",
+ "fieldTextIrCutFilterOn": "Enable Ir cut fiter. Typically Day mode.",
+ "fieldTextIrCutFilterOff": "Disable Ir cut fiter. Typically Night mode.",
+ "fieldTextIrCutFilterAuto": "Ir cut filter is automatically activated by the device.",
+ "fieldTextIp": "Range or Single",
+ "fieldTextActionsHalt": "Make the event do nothing, as if it never happened.",
+ "fieldTextActionsIndifference": "Modify minimum indifference required for event.",
+ "fieldTextActionsCommand": "You may use this to trigger a script on command.",
+ "fieldTextActionsRecord": "Use Event-Based Recording, Hotswap, or Delete Motionless with their currently set options in the Global Detection Settings section.",
+ "fieldTextMapRtspTransportAuto": "Let FFMPEG decide. Normally it will try UDP first.",
+ "fieldTextMapRtspTransportTCP": "Set it to this if UDP starts giving undesired results.",
+ "fieldTextMapRtspTransportUDP": "FFMPEG tries this first.",
+ "fieldTextChannelStreamType": "The method that will used to consume the video stream.",
+ "fieldTextChannelStreamTypePoseidon": "Poseidon is built on Kevin Godell's MP4 processing code. It simulates a streaming MP4 file but using the data of a live stream. Includes Audio. Some browsers can play it like a regular MP4 file. Streams over HTTP or WebSocket.",
+ "fieldTextChannelStreamTypeMJPEG": "Standard Motion JPEG image. No audio.",
+ "fieldTextChannelStreamTypeFLV": "Sending FLV encoded frames over WebSocket.",
+ "fieldTextChannelStreamTypeHLS(includesAudio)": "Similar method to facebook live streams. Includes audio if input provides it. There is a delay of about 4-6 seconds because this method records segments then pushes them to the client rather than push as while it creates them.",
+ "fieldTextChannelStreamVcodec": "Video codec for streaming.",
+ "fieldTextChannelStreamVcodecAuto": "Let FFMPEG choose.",
+ "fieldTextChannelStreamVcodecLibx264": "Used for MP4 video.",
+ "fieldTextChannelStreamVcodecLibx265": "Used for MP4 video.",
+ "fieldTextChannelStreamVcodecCopy": "Used for MP4 video. Has very low CPU usage but cannot use video filters and filesizes may be gigantic. Best to setup your MP4 settings camera-side when using this option.",
+ "fieldTextChannelStreamAcodec": "Audio codec for streaming.",
+ "fieldTextChannelStreamAcodecAuto": "Let FFMPEG choose.",
+ "fieldTextChannelStreamAcodecNoAudio": "No Audio, this is an option that must be set in some parts of the world due to legal reasons.",
+ "fieldTextChannelStreamAcodecLibvorbis": "Used for WebM video.",
+ "fieldTextChannelStreamAcodecLibopus": "Used for WebM video.",
+ "fieldTextChannelStreamAcodecLibmp3lame": "Used for MP4 video.",
+ "fieldTextChannelStreamAcodecAac": "Used for MP4 video.",
+ "fieldTextChannelStreamAcodecAc3": "Used for MP4 video.",
+ "fieldTextChannelStreamAcodecCopy": "Used for MP4 video. Has very low CPU usage but some audio codecs need custom flags like -strict 2 for aac.",
+ "fieldTextChannelHlsTime": "How long each video segment should be, in minutes. Each segment will be drawn by the client through an m3u8 file. Shorter segments take less space.",
+ "fieldTextChannelHlsListSize": "The number of segments maximum before deleting old segments automatically.",
+ "fieldTextChannelPresetStream": "Preset flag for certain video encoders. If you find your camera is crashing every few seconds : try leaving it blank.",
+ "fieldTextChannelStreamQuality": "Low number means higher quality. Higher number means less quality.",
+ "fieldTextChannelStreamFps": "The speed in which frames are displayed to clients, in Frames Per Second. Be aware there is no default. This can lead to high bandwidth usage.",
+ "fieldTextChannelStreamScaleX": "Width of the stream image that is output after processing.",
+ "fieldTextChannelStreamScaleY": "Height of the stream image that is output after processing.",
+ "fieldTextChannelStreamRotate": "Change the viewing angle of the video stream.",
+ "fieldTextChannelSvf": "Place FFMPEG video filters in this box to affect the streaming portion. No spaces.",
+ "Last Updated": "Last Updated",
+ "Z-Wave Manager": "Z-Wave Manager",
+ "Z-Wave": "Z-Wave"
}
diff --git a/languages/it.json b/languages/it.json
new file mode 100644
index 00000000..c67e1c41
--- /dev/null
+++ b/languages/it.json
@@ -0,0 +1,1652 @@
+{
+ "\"No Motion\" Detector": "Riflettore \"nessun movimento\"",
+ "# of Allow MJPEG Clients": "# di consentire clienti mjpeg 0 per infinite ",
+ "'Already Installing...'": "\"Già installando ...\"",
+ "180 Degrees": "180 gradi",
+ "2-Factor Authentication": "Autenticazione a 2 fattori",
+ "90 Clockwise": "90 in senso orario",
+ "90 Clockwise and Vertical Flip": "90 Flip in senso orario e verticale",
+ "90 Counter Clockwise and Vertical Flip (default)": "90 Flip in senso antiorario e verticale (impostazione predefinita)",
+ "AND": "E",
+ "API": "API",
+ "API Key": "Chiave API",
+ "API Key Action Failed": "Azione chiave API non è riuscita",
+ "API Key Added": "Chiave API aggiunta",
+ "API Key Deleted": "Chiave API Eliminata",
+ "API Keys": "Tasti API",
+ "APIKeyAddedText": "Puoi usare questa chiave ora.",
+ "APIKeyDeletedText": "La chiave è stata eliminata. Non funzionerà più.",
+ "ASC": "Asc",
+ "Accelerator": "Acceleratore",
+ "Account Edited": "Account modificato",
+ "Account Info": "Informazioni sull'account",
+ "Account Information": "Informazioni account",
+ "Account Privileges": "Privilegi di account",
+ "Account Save": "Salva account",
+ "Account Settings": "Impostazioni dell'account",
+ "AccountEditText1": "Non poteva modificare. Aggiorna pagina se il problema continua.",
+ "Accounts": "Account",
+ "Action for Selected": "Azione per selezionata",
+ "Activated": "Attivato",
+ "Active Monitors": "Monitor attivi",
+ "Add": "Aggiungere",
+ "Add All": "Aggiungi tutto",
+ "Add Camera": "Aggiungi la fotocamera",
+ "Add Cameras": "Aggiungi telecamere",
+ "Add Channel": "Aggiungi canale",
+ "Add Input Feed": "Aggiungi feed di input",
+ "Add Map": "Aggiungi mappa",
+ "Add Monitor": "Aggiungi monitor",
+ "Add New": "Aggiungere nuova",
+ "AddToPreset": "Aggiungi a preimpostazione",
+ "Additional Inputs": "Input aggiuntivi",
+ "Admin": "Amministratrice",
+ "Advanced": "Avanzate",
+ "After": "Dopo",
+ "Again": "Ancora",
+ "Age": "Età",
+ "Alert Sound": "Suono di allerta",
+ "Alert Sound Delay": "Avviso il ritardo del suono",
+ "All Logs": "Tutti i registri",
+ "All Monitors": "Tutti i monitor",
+ "All Monitors and Privileges": "Tutti i monitor e i privilegi",
+ "All Privileges": "Tutti i privilegi",
+ "All Warnings": "Tutti gli avvertimenti",
+ "All streams in first feed": "Tutti i flussi nel primo feed",
+ "Allow API Trigger": "Consenti il trigger API",
+ "Allow Next Alert": "Consenti l'allerta successiva",
+ "Allow Next Command": "Consenti il comando successivo",
+ "Allow Next Discord Alert": "Consenti l'allerta discordia successiva in minuti ",
+ "Allow Next Email": "Consenti email successiva in minuti ",
+ "Allow Next Trigger": "Consenti il grilletto successivo",
+ "Allow Next Webhook": "Consenti il prossimo webhook",
+ "Allowed IPs": "Consentito IPS",
+ "Already Processing": "Già elaborazione",
+ "Already exists": "Esiste già",
+ "Alternate Logins": "Accessi alternativi",
+ "Always": "Sempre",
+ "Amazon S3": "Amazon S3",
+ "Amazon S3 Upload Error": "Errore di caricamento di Amazon S3",
+ "Analyzation Duration": "Durata dell'analisi",
+ "AppNotEnabledText": "App non abilitata, abilitalo nelle impostazioni dell'account.",
+ "April": "aprile",
+ "Archive": "Archivio",
+ "Are you sure?": "Sei sicuro?",
+ "Attach Snapshot": "Allega l'istantanea",
+ "Attach Video Clip": "Allega video clip",
+ "Audio": "Audio",
+ "Audio Bit Rate": "Bitrate audio",
+ "Audio Codec": "Codec audio",
+ "Audio Detection": "Rilevamento audio",
+ "Audio Detector": "Rilevatore audio",
+ "Audio stream only from first feed": "Flusso audio solo dal primo feed",
+ "Audio streams only": "Solo flussi audio",
+ "August": "agosto",
+ "Authenticate": "Autenticare",
+ "Authenticated": "Autenticata",
+ "Authentication Failed": "Autenticazione fallita",
+ "Auto": "Auto",
+ "Automatic": "Automatica",
+ "Automatic Checking Cancelled": "Controllo automatico annullato",
+ "Automatic Codec Repair": "Riparazione del codec automatico",
+ "Automatic Field Fill": "Riempimento del campo automatico",
+ "Autosave": "Autosave",
+ "Back": "Di ritorno",
+ "Backblaze B2": "Backblaze B2",
+ "Backblaze Error": "Errore di backblaze",
+ "BacklightCompensation": "Compensazione retroilluminazione",
+ "Backup": "Backup",
+ "Bandwidth": "Larghezza di banda",
+ "Base64 over Websocket": "Base64 su WebSocket",
+ "Basic Authentication": "Autenticazione di base",
+ "Batch": "Lotto",
+ "Before": "Prima",
+ "Bind Credentials": "Bind Credentials (password)",
+ "BitrateLimit": "Limite bitrate",
+ "Blank for No Change": "Vuoto per nessun cambiamento",
+ "Bottom Left": "In basso a sinistra",
+ "Bottom Right": "In basso a destra",
+ "Brightness": "Luminosità",
+ "Browser Console Log": "Registro della console del browser",
+ "Bucket": "Benna",
+ "Buffer Preview": "Anteprima del buffer",
+ "Build": "Costruire",
+ "Build Video": "Costruisci video",
+ "Building": "Costruzione",
+ "CPU": "processore",
+ "CPU indicator will not work. Continuing...": "L'indicatore della CPU non funzionerà. Continuando ...",
+ "CPU used by this stream": "CPU utilizzata da questo flusso",
+ "CSS": "CSS stile la tua dashboard. ",
+ "Calendar": "Calendario",
+ "Call Method": "Metodo di chiamata",
+ "Camera Password": "Password della fotocamera",
+ "Camera Username": "Nome utente della fotocamera",
+ "Camera is not recording": "La fotocamera non sta registrando",
+ "Camera is not running": "La fotocamera non funziona",
+ "Camera is not streaming": "La fotocamera non è in streaming",
+ "CameraNotRecordingText": "Le impostazioni possono essere incompatibili. Controlla gli encoder. Riavviamento ...",
+ "Can Authenticate Websocket": "Può autenticare WebSocket",
+ "Can Change User Settings": "Può modificare le impostazioni dell'utente",
+ "Can Control Monitors": "Può controllare i monitor",
+ "Can Create and Delete Monitors": "Può creare ed eliminare i monitor",
+ "Can Delete Videos": "Può eliminare i video",
+ "Can Delete Videos and Events": "Può eliminare video ed eventi",
+ "Can Edit Monitor": "Può modificare il monitor",
+ "Can Get Logs": "Può ottenere tronchi",
+ "Can Get Monitors": "Può ottenere monitor",
+ "Can View Logs": "Può visualizzare i registri",
+ "Can View Monitor": "Può visualizzare il monitor",
+ "Can View Snapshots": "Può visualizzare le istantanee",
+ "Can View Streams": "Può visualizzare i flussi",
+ "Can View Videos": "Può visualizzare i video",
+ "Can View Videos and Events": "Può visualizzare video ed eventi",
+ "Can edit Max Days": "Può modificare i giorni massimi",
+ "Can edit Max Storage": "Può modificare l'archiviazione massima",
+ "Can edit how long to keep Events": "Può modificare quanto tempo per mantenere gli eventi",
+ "Can edit how long to keep Logs": "Può modificare quanto tempo per mantenere i registri",
+ "Can use Admin Panel": "Può utilizzare il pannello di amministrazione",
+ "Can use Amazon S3": "Può usare Amazon S3",
+ "Can use Discord Bot": "Può usare Discord Bot",
+ "Can use LDAP": "Può usare LDAP",
+ "Can use SFTP": "Può usare SFTP",
+ "Can use Wasabi Hot Cloud Storage": "Può utilizzare Wasabi Hot Cloud Storage",
+ "Can use WebDAV": "Può utilizzare WebDav",
+ "Can't Connect": "Non posso connettersi",
+ "Cannot watch a monitor that isn't running.": "Non riesco a guardare un monitor che non funziona.",
+ "Cards": "Carte",
+ "Carousel in Background": "Giostra in background",
+ "Center": "Center Indirizzo URL ",
+ "Channel": "Canale",
+ "Channel ID": "Canale ID",
+ "Chat on Discord": "Chatta su discordia",
+ "Check": "Dai un'occhiata",
+ "Check Signal Interval": "Controllare l'intervallo del segnale in minuti ",
+ "Check for Motion First": "Controlla prima il movimento",
+ "Check the Channel ID": "Controlla l'ID canale",
+ "Check the Recipient ID": "Controlla l'ID destinatario",
+ "Clear": "Chiara",
+ "Clear Recorder Process": "Processo di registratore chiaro",
+ "Close": "Chiudere",
+ "Close All Monitors": "Chiudi tutti i monitor",
+ "Closed": "Chiusa",
+ "Cloud": "Nube",
+ "Codec Mismatch": "Codec Mismatch",
+ "Color Threshold": "Soglia di colore",
+ "ColorSaturation": "Saturazione del colore",
+ "Command": "Comando",
+ "Command on Trigger": "Comando su trigger",
+ "Common Objects": "Oggetti comuni",
+ "Complete Stream URL": "URL di flusso completo",
+ "Conditions": "Condizioni",
+ "Confidence": "Fiducia",
+ "Confidence of Detection": "Fiducia di rilevamento",
+ "Configuration": "Configurazione",
+ "Confirm": "Confermare",
+ "Connected": "Collegata",
+ "Connected Users": "Utenti connessi",
+ "Connection": "Connessione",
+ "Connection Type": "Tipo di connessione",
+ "Connection timed out": "Connessione Scaduta",
+ "Contains": "Contiene",
+ "Contrast": "Contrasto",
+ "Control": "Controllo",
+ "Control Error": "Errore di controllo",
+ "Control Trigger Started": "Trigger di controllo avviato",
+ "Control Triggered": "Controllo attivato",
+ "ControlErrorText1": "Il controllo non è abilitato",
+ "ControlErrorText2": "Controlla i dettagli della tua connessione. Potrebbe essere necessario puntare l'URL di base alla porta 8000 o 80. Controllare le informazioni di autenticazione.",
+ "Controllable": "Controllabile",
+ "Controls and Logs": "Controlli e registri",
+ "Copied": "Copiato",
+ "Copied to Clipboard": "Copiato negli appunti",
+ "Copy": "copia",
+ "Copy Connection Settings": "Copia impostazioni di connessione",
+ "Copy Custom Settings": "Copia impostazioni personalizzate",
+ "Copy Detector Settings": "Impostazioni del rivelatore di copia",
+ "Copy Group Settings": "Copia impostazioni del gruppo",
+ "Copy Input Settings": "Copia impostazioni di input",
+ "Copy JPEG API Settings": "Copia le impostazioni dell'API JPEG",
+ "Copy Logging Settings": "Copia impostazioni di registrazione",
+ "Copy Mode": "Modalità copia",
+ "Copy Recording Settings": "Copia impostazioni di registrazione",
+ "Copy Remote Link": "Copia link remoto",
+ "Copy Settings": "Copia impostazioni",
+ "Copy Stream Channel Settings": "Copia le impostazioni del canale del flusso",
+ "Copy Stream Channels": "Copia canali di streaming",
+ "Copy Stream Settings": "Copia le impostazioni del flusso",
+ "Copy Stream URL": "Copia URL in streaming",
+ "Copy Timelapse Settings": "Copia impostazioni timelapse",
+ "Copy to Settings": "Copia alle impostazioni",
+ "Cores": "Core",
+ "Could not create Bucket.": "Impossibile creare un secchio.",
+ "Count Objects": "Contare gli oggetti",
+ "Count Objects only inside Regions": "Contare gli oggetti solo all'interno delle regioni",
+ "Country of Plates": "Paese di piatti",
+ "Counts of Motion": "Conteggi di movimento",
+ "Create Sub-Accounts at /admin": "Crea sottoconunti a /admin",
+ "Creating New Account": "Creazione di un nuovo account",
+ "Creation Interval": "Intervallo di creazione",
+ "Current": "Attuale",
+ "Currently Active": "Attualmente attivo",
+ "Currently viewing": "Attualmente visualizzazione",
+ "Custom": "Costume",
+ "Custom Auto Load": "Carica automatica personalizzata",
+ "Custom Base URL": "URL di base personalizzato lascia vuoto per utilizzare l'URL host ",
+ "Custom Endpoint": "Endpoint personalizzato",
+ "DB Lost.. Retrying..": "Database perso .. Retring ..",
+ "DESC": "Desc",
+ "DHCP": "Dhcp",
+ "DNS": "DNS",
+ "Daily Events": "Eventi quotidiani",
+ "Dashboard": "Pannello di controllo",
+ "Dashboard Language": "Linguaggio della dashboard",
+ "Dashcam": "Dashcam",
+ "Dashcam (Streamer v2)": "DashCam (Streamer V2)",
+ "Database": "Banca dati",
+ "Database Not Found": "Database non trovato",
+ "Database row does not exist": "La riga del database non esiste",
+ "Date": "Data",
+ "Date Added": "Data aggiunta",
+ "Date Range": "Intervallo di date",
+ "Date Updated": "Data aggiornata",
+ "Date and Time": "Data e ora",
+ "DateTimeType": "Gestione della data",
+ "DaylightSavings": "Risparmio diurno",
+ "Days": "Giorni",
+ "Debug": "Debug",
+ "December": "Dicembre",
+ "Default": "Predefinita",
+ "Delay for Snapshot": "Ritardo per l'istantanea",
+ "Delete": "Elimina",
+ "Delete Camera": "Elimina la fotocamera",
+ "Delete Filter": "Elimina filtro",
+ "Delete Logs": "Elimina i registri",
+ "Delete Matches": "Elimina le partite",
+ "Delete Monitor": "Elimina il monitor",
+ "Delete Monitor State?": "Elimina lo stato di monitor",
+ "Delete Monitor States Preset": "Elimina gli stati del monitor preimpostazione",
+ "Delete Monitors and Files": "Elimina monitor e file",
+ "Delete Motionless Video": "Elimina video immobili",
+ "Delete Motionless Videos (Record)": "Elimina video immobili (record)",
+ "Delete Region": "Elimina la regione",
+ "Delete Schedule": "Elimina il programma",
+ "Delete Selected Videos": "Elimina video selezionati",
+ "Delete Timelapse Frame": "Elimina il telaio timelapse",
+ "Delete Video": "Elimina video",
+ "Delete selected": "Elimina selezionato",
+ "DeleteMonitorText": "Vuoi eliminare questo monitor? Non puoi recuperarlo. È possibile scegliere che i file rimangano nel filesystem. Se si sceglie di ricreare un monitor con lo stesso ID, i video e gli eventi diventeranno visibili nella dashboard.",
+ "DeleteMonitorsText": "Vuoi eliminare questi monitor? Non puoi recuperarli. Puoi scegliere di conservare i file per questi ID nel filesystem. Se si sceglie di ricreare un monitor con uno degli ID, i video e gli eventi diventeranno visibili nella dashboard.",
+ "DeleteSelectedVideosMsg": "Vuoi eliminare questi video? Non puoi recuperarli.",
+ "DeleteThisMsg": "Vuoi eliminare questo? Non puoi recuperarlo.",
+ "DeleteVideoMsg": "Vuoi eliminare questo video? Non puoi recuperarlo.",
+ "Deleted": "Cancellato",
+ "Deleted Schedule Configuration": "Configurazione del programma eliminato",
+ "Deleted State Configuration": "Configurazione dello stato eliminato",
+ "Detect Objects": "Rileva oggetti Quando vengono mostrate le caselle di larghezza e altezza, dovresti impostarle su 640x480 o inferiore. Ciò ottimizzerà la velocità di lettura dei frame.
", + "Died": "Morta", + "Digest Authentication": "Digest Autentication", + "Disable": "disattivare", + "Disable Night Vision": "Disabilita visione notturna Indirizzo URL ", + "Disable Nightvision": "Disabilita NightVision", + "Disabled": "Disabilitata", + "Discord": "Discordia", + "Discord Alert on Trigger": "Discord Alert sul grilletto", + "Discord Bot": "Discord Bot", + "Discord on No Motion": "Discord su \"No Motion\"", + "DiscordErrorText": "L'invio a discord ha causato un errore", + "DiscordFailedText": "L'invio a discord non è riuscito", + "DiscordLoggedIn": "Discord Bot autenticato", + "DiscordNotEnabledText": "Discord Bot non abilitato, abilitalo nelle impostazioni dell'account.", + "Documentation": "Documentazione", + "Does Not Contain": "Non contiene", + "Don't Show for 1 Week": "Non mostrare per 1 settimana", + "Don't Stretch Monitors": "Non allungare i monitor", + "Don't show this anymore": "Non mostrarlo più", + "DontAddToPreset": "Non aggiungere a preimpostazione", + "Double Quote Directory": "Directory a doppia citazione Alcune directory hanno spazi. Usando questo può crash alcune telecamere. ", + "Down": "DOWN Indirizzo URL ", + "Down Stop": "STOP DOWN Indirizzo URL ", + "Download": "Scarica", + "Download Bandwidth": "Scarica la larghezza di banda", + "Downloading Videos": "Download di video", + "Downloading...": "Download ...", + "Duplicate": "Duplicare", + "EU": "Unione Europea", + "Easy Remote Access (P2P)": "Facile accesso remoto (P2P)", + "Edit": "Modificare", + "Edit Configuration": "Modifica configurazione", + "Edit Selected": "Modifica selezionata", + "Edited Schedule Configuration": "Configurazione del programma modificato", + "Edited State Configuration": "Configurazione dello stato modificato", + "Email": "E-mail", + "Email Details": "Dettagli e -mail", + "Email address is in use.": "L'indirizzo e -mail è in uso.", + "Email and Password fields cannot be empty": "I campi e -mail e password non possono essere vuoti", + "Email on No Motion": "Email su \"No Motion\"", + "Email on Trigger": "Invia un'e -mail su trigger", + "Emotion": "Emozione", + "Emotion Average": "Media emozione", + "Enable": "Abilitare", + "Enable Night Vision": "Abilita visione notturna Indirizzo URL ", + "Enable Nightvision": "Abilita NightVision", + "Enabled": "Abilitata", + "Encoding": "Codifica", + "EncodingInterval": "I-frame", + "End": "Fine", + "End Time": "Tempo scaduto", + "Ended": "Conclusa", + "Endpoint": "Endpoint", + "Endpoint Address": "Indirizzo endpoint", + "Enlarge": "Ingrandire", + "Enter at least one IP": "Immettere almeno un IP", + "Enter this code to proceed": "Immettere questo codice per procedere", + "Equal to": "Uguale a", + "Error Connecting": "Connessione all'errore", + "Error While Decoding": "Errore durante la decodifica", + "ErrorWhileDecodingText": "L'hardware potrebbe avere una connessione instabile alla rete. Controlla le connessioni di rete.", + "ErrorWhileDecodingTextAudio": "La fotocamera fornisce dati rotti. Prova a disabilitare l'audio nelle impostazioni interne della fotocamera.", + "Event": "Evento", + "Event Counts": "Conta degli eventi", + "Event Filter Error": "Errore del filtro dell'evento", + "Event Filters": "Filtri degli eventi", + "Event Limit": "Limite di eventi", + "Event Occurred": "Si è verificato un evento", + "Event Rules": "Regole degli eventi", + "Event Webhook Error": "ERRORE WebHook Event", + "EventText1": "Ha innescato un evento a", + "EventText2": "Non poteva e -mail immagine, il file non era accessibile", + "Events": "Eventi", + "Events Found": "Eventi trovati", + "Example": "Esempio", + "Execute Command": "Eseguire il comando", + "Executed": "Eseguito", + "Export": "Esportare", + "Export Selected Videos": "Esporta video selezionati", + "Export Video": "Video di esportazione", + "ExportSelectedVideosMsg": "Vuoi esportare questi video? Potrebbe volerci del tempo per chiudere e scaricare.", + "Exposure": "Esposizione", + "FFmpegCantStart": "FFMPEG non poteva iniziare", + "FFmpegCantStartText": "Il motore di registrazione per questa fotocamera non è stato possibile avviarsi. Potrebbe esserci qualcosa di sbagliato nella configurazione della fotocamera. Se ci sono registri diversi da questo, pubblicali nei problemi su GitHub.", + "FFmpegTip": "FFPROBE è un semplice analizzatore di flussi multimediali. È possibile utilizzarlo per produrre tutti i tipi di informazioni su un input che include durata, velocità del frame, dimensioni del frame, ecc.", + "FFprobe": "Sonda", + "FLV": "Flv", + "FLV Stream Type": "Tipo di flusso FLV", + "FactorAuthText1": "Il codice sarà attivo solo per 15 minuti. Se si accede di nuovo, il timer verrà ripristinato a 15 minuti con lo stesso codice.", + "Failed to Edit Account": "Impossibile modificare l'account", + "Fatal": "Fatale", + "Fatal Maximum Reached": "La fotocamera di arresto massimo fatale, di arresto.", + "FatalMaximumReachedText": "L'errore JPEG è stato fatale.", + "February": "febbraio", + "Feed-in Image Height": "Altezza dell'immagine alimentare", + "Feed-in Image Width": "Larghezza dell'immagine di alimentazione", + "Female": "Femmina", + "Field Missing Value": "Valore mancante del campo", + "Fields cannot be empty": "I campi non possono essere vuoti", + "File Delete Error": "Errore di eliminazione del file", + "File Not Exist": "Il file non esiste", + "File Not Found": "File non trovato", + "File Not Found in Database": "File non trovato nel database", + "File Not Found in Filesystem": "File non trovato nel filesystem", + "File Type": "Tipo di file", + "FileBin Share": "FileBin Share", + "FileNotExistText": "Impossibile salvare file non esistenti. Qualcosa è andato storto.", + "Filename": "Nome del file", + "Filesize": "Dimensione del file", + "Filter ID": "Filtro ID", + "Filter Matches": "Filtro corrispondenze", + "Filter Name": "Nome filtro", + "Filter for Objects only": "Filtrare solo gli oggetti", + "FilterMatchesText1": "Questo filtro ha incontrato le condizioni.", + "FilterMatchesText2": "video trovati.", + "Filters": "Filtri", + "Filters Updated": "Filtri aggiornati", + "FiltersUpdatedText": "Le tue modifiche sono state salvate e applicate.", + "Find Where": "Trova dove", + "First stream in feed": "Primo flusso in feed", + "Fix": "Aggiustare", + "Fix Video": "Correggi video", + "FixVideoMsg": "Vuoi sistemare questo video? Non puoi annullare questa azione.", + "Flush PM2 Logs": "Flush Logs PM2", + "Font Path": "PATURA DI FONT", + "Font Size": "Dimensione del font", + "For Group": "Per il gruppo", + "Force Monitors Per Row": "Monitor di forza per riga", + "Force Port": "Porta di forza", + "Form Data Not Found": "Modulo dati non trovati", + "Found Devices": "Dispositivi trovati", + "Frame Rate": "Frequenza dei fotogrammi", + "FrameRateLimit": "Limite di rate fotogrammi (FPS)", + "Frames": "Cornici", + "Friday": "Venerdì", + "Frigate": "Fregata", + "Full Frame Detection": "Rilevamento completo del telaio", + "Full Stream URL": "URL flusso completo", + "Full URL Path": "Percorso URL completo", + "Fullscreen": "A schermo intero", + "Gateway": "Gateway", + "Gender": "Genere", + "Generate Subtitles": "Generare sottotitoli", + "Get Code": "Ottenere il codice", + "Get Logs to Client": "Ottieni tronchi al client", + "Global Detector Settings": "Impostazioni del rivelatore globale", + "Google Drive": "Google Drive", + "GovLength": "Gov", + "Greater Than": "Più grande di", + "Greater Than or Equal to": "Maggiore o uguale a", + "Grid": "Griglia", + "Group Key": "Chiave di gruppo", + "Group Key is in use.": "La chiave di gruppo è in uso.", + "Group Name": "Nome del gruppo", + "Grouping": "Raggruppamento", + "H.264 / H.265 / H.265+": "H.264 / H.265 / H.265+", + "H264Profile": "Profilo H264", + "HEVC (H.265)": "HEVC (H.265)", + "HLS (.m3u8)": "HLS (.M3U8)", + "HLS (includes Audio)": "HLS (include audio)", + "HLS Audio Encoder": "Encoder audio", + "HLS List Size": "Dimensione dell'elenco", + "HLS Live Start Index": "HLS Live Start Index", + "HLS Preset": "Modello preimpostata", + "HLS Segment Length": "Lunghezza del segmento in secondi ", + "HLS Start Number": "Numero di inizio HLS", + "HLS Video Encoder": "Encoder video", + "HTTP": "Http", + "HTTPS": "Https", + "Hardware Accelerated": "Hardware accelerato", + "Height": "Altezza", + "Help": "Aiuto", + "Hide List": "Nascondi elenco", + "Hide Notes": "Nascondere le note", + "Home": "Casa", + "Host": "Ospite", + "Host Type": "Tipo host", + "Hostname": "Nome host", + "Hotswap Modes (Watch-Only)": "Modalità Hotswap (solo watch)", + "How to Record": "Come registrare", + "IP Address": "Indirizzo IP", + "Identity": "Identità", + "IdentityText1": "Ecco come il sistema identificherà i dati per questo flusso. Non è possibile modificare l'ID monitor una volta premuto Salva. Se vuoi puoi rendere l'ID Monitor più leggibile umano prima di continuare.", + "IdentityText2": "È possibile duplicare un monitor modificando l'ID monitor , quindi premendo Salva. non è possibile utilizzare l'ID di un monitor che esiste già o risparmierà le informazioni sul database di quel monitor.", + "Idle": "Oziare", + "Image Height": "Altezza dell'immagine", + "Image Location": "Posizione dell'immagine", + "Image Position": "Posizione dell'immagine", + "Image Width": "Larghezza dell'immagine", + "Imaging": "Imaging", + "Import": "Importare", + "Import Monitor Configuration": "Configurazione del monitor di importazione", + "ImportMonitorConfigurationText": "In questo modo si scriverà eventuali modifiche attualmente non salvate. Le modifiche importate verranno applicate solo quando si preme Salva .", + "ImportMultiMonitorConfigurationText": "In questo modo si scruterà eventuali monitor con ID esistenti nel file di importazione.", + "In": "Nella", + "Incorrect Settings Chosen": "Impostazioni errate scelte", + "Indifference": "Indifferenza", + "Info": "Informazioni", + "Information": "Informazione", + "Input": "Ingresso", + "Input Feed": "Feed di ingresso", + "Input Feeds Selected": "Feed di ingresso selezionato", + "Input Flags": "Flag di input", + "Input Map": "Mappa di input", + "Input Selector": "Selettore di input", + "Input Settings": "Impostazioni di input", + "Input Type": "Tipo di input", + "InputText1": "Questa sezione dice a Shinobi come consumare un flusso. Per prestazioni ottimali, provare a sintonizzare le impostazioni interne della fotocamera. Per trovare la fotocamera puoi utilizzare incorporato scanner Onvif di shinobi. Per aprire lo scanner Onvif fai clic sul nome utente in alto a sinistra e quindi onvif.", + "InputText2": "Scopri la configurazione e la sintonizzazione delle telecamere qui .", + "InputText3": "Se hai bisogno di aiuto per capire quale tipo di input è la tua fotocamera, puoi dare un'occhiata nell'elenco URLS della fotocamera Il sito web Shinobi.", + "Inserted Schedule Configuration": "Configurazione del programma inserita", + "Inserted State Configuration": "Configurazione dello stato inserito", + "Install": "Installare", + "Interface": "Interfaccia", + "Invalid Data": "Dati non validi", + "Invalid JSON": "JSON non valido", + "Invalid Settings": "Impostazioni non valide", + "InvalidJSONText": "Assicurati che questa sia una stringa JSON valida per la configurazione del monitor Shinobi.", + "Inverse Trigger": "Trigger inverso", + "Invert Y-Axis": "Inverti asse y", + "IrCutFilter": "Visione notturna", + "JPEG": "Jpeg", + "JPEG (Auto Enables JPEG API)": "JPEG (Auto abilita JPEG API)", + "JPEG API": "JPEG API", + "JPEG Error": "Errore JPEG", + "JPEG Mode": "Modalità jpeg", + "JPEGErrorText": "C'è stato un problema per ottenere dati dalla fotocamera.", + "January": "Gennaio", + "July": "Luglio", + "June": "Giugno", + "LDAP": "Ldap", + "LDAP Success": "Successo di LDAP", + "LDAP User Authenticated": "Utente LDAP autenticato", + "LDAP User is New": "L'utente LDAP è nuovo", + "Landing Page": "Pagina di destinazione", + "Last": "Scorsa", + "Last Modified": "Ultima modifica", + "Launch in New Window": "Avvia nella nuova finestra", + "Leave blank for random.": "Lasciare vuoto per casuale.", + "Leave blank for unlimited": "Lascia vuoto per illimitato", + "Left": "A sinistra indirizzo URL ", + "Left Stop": "Stop sinistro Indirizzo URL ", + "Legacy Webhook": "Legacy Webhook", + "Less Than": "Meno di", + "Less Than or Equal to": "Minore o uguale a", + "License Activated": "Licenza attivata", + "License Activation": "Attivazione della licenza", + "License Activation Failed": "L'attivazione della licenza non è riuscita", + "License Key": "Chiave di licenza", + "License Plate Detector": "Rilevatore di targhe", + "Like": "Piace", + "Limited": "Limitata", + "Link Google Account": "Link Account Google", + "Link LDAP Account": "Link Account LDAP", + "Link Shinobi": "Link Shinobi", + "List Toggle": "Elenco azionamento", + "List of Videos Delete Error": "Elenco dei video Elimina errore", + "Live Grid": "Griglia dal vivo", + "Live Stream Toggle": "Live Stream azionario", + "Live View": "Dal vivo", + "Local": "Locale", + "Log Level": "Livello di registro", + "Log Signal Event": "Evento del segnale di registro solo lato client ", + "Log Stream": "Flusso di registro", + "Logging": "Registrazione", + "Login": "Login", + "Logout": "Disconnettersi", + "Logs": "Tronchi", + "Loop Stream": "Flusso di loop", + "MB": "MB", + "MJPEG": "Mjpeg", + "MP4 (copy, libx264, libx265)": "MP4 (copia, libx264, libx265)", + "MPEG-4 (.mp4 / .ts)": "MPEG-4 (.mp4 / .ts)", + "MPEG-DASH (includes Audio)": "MPEG-DASH (include audio)", + "MQTT Client": "Client MQTT", + "MQTT Error": "Errore MQTT", + "MQTT Inbound": "MQTT INBOUND", + "MQTT Outbound": "MQTT Outbound", + "MailError": "Errore di posta: non è stato possibile inviare e -mail, controllare conf.json. Saltare qualsiasi funzionalità che si basa sulla mailing.", + "Main": "Principale", + "Male": "Maschia", + "Manual": "Manuale", + "Map": "Carta geografica", + "March": "Marzo", + "Matches": "Corrispondenze", + "Matrices": "Matrici", + "Max Indifference": "Max Indifferenza", + "Max Latency": "Latenza massima", + "Max Number of Cameras": "Numero massimo di telecamere", + "Max Storage Amount": "Importo di archiviazione massimo", + "MaxExposureTime": "Tempo di esposizione massima", + "MaxGain": "Guadagno massimo", + "Maximum Change": "Modifica massima", + "Maximum dB": "DB massimo", + "May": "Maggio", + "Merge Selected Videos": "Unisci video selezionati", + "Merge Video": "Unisci video", + "Merge and Download": "Unire e download", + "MergeSelectedVideosMsg": "Vuoi unire questi video? Potrebbe volerci del tempo per unire e scaricare. Nel momento in cui la connessione viene chiusa, il file verrà eliminato. Assicurati di tenere aperto il browser fino al completamento.", + "Methods": "Metodi", + "Migrator": "Migratore", + "MinExposureTime": "Tempo di esposizione minima", + "MinGain": "Guadagno minimo", + "Minimum Change": "Cambiamento minimo", + "Minimum dB": "DB minimo", + "Minutes": "Minuti", + "Mode": "Modalità", + "Monday": "Lunedi", + "Monitor": "Tenere sotto controllo", + "Monitor Added by user": "Monitor aggiunto dall'utente.", + "Monitor Capture Rate": "Monitora la velocità di acquisizione (fps) ", + "Monitor Died": "Monitor è morto", + "Monitor Edit": "Monitorare la modifica", + "Monitor Groups": "Monitorare i gruppi", + "Monitor ID": "Monitor ID", + "Monitor Idling": "Monitorare il minimo", + "Monitor Name": "Nome monitor", + "Monitor Settings": "Monitorare le impostazioni", + "Monitor Start": "Monitor Avvia", + "Monitor States": "Monitorare gli stati", + "Monitor States and Schedules": "Monitorare stati e programmi", + "Monitor Stop": "Monitor Stop", + "Monitor Stopped": "Monitor si è fermato", + "Monitor Updated by user": "Monitorare aggiornato dall'utente.", + "Monitor is now Disabled": "Monitor è ora disabilitato", + "Monitor is now Idle": "Monitor è ora inattivo", + "Monitor is now Recording": "Monitor sta ora registrando", + "Monitor is now Watching": "Monitor sta ora guardando", + "Monitor mode changed": "Modalità monitor modificata", + "Monitor mode is already": "La modalità Monitor è già", + "Monitor or Key does not exist.": "Monitoraggio o chiave non esiste.", + "MonitorIdlingText": "La sessione di monitoraggio è stata ordinata al minimo.", + "MonitorStatesText": "Puoi imparare come usare questo qui su shinobihub .", + "MonitorStoppedText": "La sessione di monitoraggio è stata ordinata di interrompere.", + "Monitors": "Monitor", + "Monitors per row": "Monitor per riga per montaggio ", + "Monitors to Copy to": "Monitora da copiare a", + "Montage": "Montaggio", + "Motion": "Movimento", + "Motion Detection": "Rilevamento del movimento", + "Motion GUI": "GUI di movimento", + "Motion Meter": "METTORE", + "Motion Threshold": "Soglia di movimento", + "Mp4Frag": "Mp4frag", + "Must be atleast one row": "Deve essere almeno una fila", + "Mute Audio": "Audio muto", + "NTP": "Ntp", + "NTP Servers": "Server NTP", + "NVIDIA": "Nvidia", + "Name": "Nome", + "Name cannot be empty.": "Il nome non può essere vuoto.", + "Nameservers": "Nameservers", + "Network": "Rete", + "Network Manager": "Responsabile del network", + "Never": "Mai", + "New Authentication Token": "Nuovo token di autenticazione", + "New Monitor": "Nuovo monitor", + "Newest": "Più recente", + "Next Video": "Video successivo", + "No": "No", + "No API Key": "Nessuna chiave API", + "No Audio": "Nessun audio", + "No Data": "Nessun dato", + "No Events found for this video": "Nessun evento trovato per questo video", + "No Group with this key exists": "Non esiste alcun gruppo con questa chiave", + "No Monitor Exists with this ID.": "Non esiste alcun monitor con questo ID.", + "No Monitor Found, Ignoring Request": "Nessun monitor trovato, ignorando la richiesta", + "No Monitor ID Present in Form": "Nessun ID monitor presente nel modulo", + "No Monitors Selected": "Nessun monitor selezionato", + "No Region": "Nessuna regione", + "No Rotation": "Nessuna rotazione", + "No Sound": "Nessun suono", + "No Trigger": "Nessun grilletto", + "No Videos Found": "Nessun video trovato", + "No such file": "Nessun file del genere", + "NoLogsFoundForDateRange": "Nessun registro trovato in questo intervallo di date. Prova ad ampliare l'intervallo di date.", + "NoMotionEmailText1": "Nessuna mozione per", + "NoMotionEmailText2": "Non è stato rilevato alcun movimento sulla fotocamera", + "NoVideosFoundForDateRange": "Nessun video trovato in questo intervallo di date. Prova a impostare la data di inizio più indietro.", + "Noise Filter": "Filtro del rumore", + "Noise Filter Range": "Gamma di filtri di rumore", + "Non-Standard ONVIF": "Non standard onvif", + "Not Activated": "Non attivato", + "Not Authorized": "Non autorizzato", + "Not Connected": "Non collegata", + "Not Equal to": "Non uguale a", + "Not Found": "Non trovato", + "Not In": "Non in", + "Not Matches": "Non corrisponde", + "Not Permitted": "Non consentito", + "Not Saved": "Non salvato", + "Not an Administrator Account": "Non un account amministratore", + "NotAuthorizedText1": "Non autorizzato, invia comando init con \"autenticazione\", \"Ke\" e \"uid\"", + "Notes": "Appunti", + "NotesPlacholder": "Commenti che vuoi lasciare per le impostazioni delle telecamere.", + "Nothing exists": "Niente esiste", + "Notice": "Avviso", + "Notification Sound": "Suono di notifica", + "Notification Video Length": "Lunghezza del video di notifica", + "Notifications": "Notifiche", + "NotifyErrorText": "L'invio della notifica ha causato un errore", + "November": "novembre", + "Number of Days to keep": "Numero di giorni da mantenere", + "Numeric criteria unsupported for Region tests, Ignoring Conditional": "Criteri numerici non supportati per i test della regione, ignorando il condizionale", + "OAuth Code": "Codice OAuth", + "OAuth Credentials": "Credenziali di oauth", + "ONVIF": "Onvif", + "ONVIF Device Manager": "OnVif Device Manager", + "ONVIF Port": "Porta onvif", + "ONVIF Scanner": "Scanner Onvif", + "ONVIFErr400": "Trovato la porta Onvif ma l'autorizzazione non è riuscita quando si è recuperato l'URL del flusso. Controlla il nome utente e la password utilizzati per la scansione. Assicurati che il tempo della fotocamera e il tempo del server siano sincronizzati.", + "ONVIFErr404": "Non trovato. Questo potrebbe essere solo il pannello Web per un dispositivo di rete.", + "ONVIFErr405": "Operazione non permessa. Controlla il nome utente e la password utilizzati per la scansione.", + "ONVIFEventsNotAvailable": "Eventi Onvif non disponibili", + "ONVIFEventsNotAvailableText1": "Questo servizio potrebbe non essere disponibile per questa fotocamera o ONVIF non ha ancora inizializzato.", + "ONVIFnotCompliantProfileT": "La fotocamera non è conforme al profilo T.", + "ONVIFnote": "Scopri i dispositivi ONVIF su reti al di fuori del proprio o lascialo vuoto per scansionare la tua rete attuale.Questa sezione designerà il metodo principale di streaming e le sue impostazioni. Questo flusso verrà visualizzato nella dashboard. Se si sceglie di utilizzare HLS, JPEG o MJPEG, è possibile consumare il flusso attraverso altri programmi.
Usando il flusso JPEG essenzialmente disattiva il flusso primario e utilizza il bidone di Snapshot per ottenere frame.
", + "Streamed Logs": "Registri in streaming", + "Streamer": "Streamer", + "Streams": "Flussi", + "Sub-Accounts": "Sottoconti", + "Subdivision": "Suddivisione", + "Substream": "Substream", + "Substream Process": "Processo substream", + "SubstreamNotConfigured": "Substream non configurato. Apri le impostazioni del monitor e configurale.", + "Subtitle": "Sottotitolo", + "Success": "Successo", + "Sunday": "Domenica", + "Superuser": "Superutente", + "Superuser Logs": "Registri superutenti", + "Switch on for Still Image": "Accendi per l'immagine fissa", + "System": "Sistema", + "System Level": "Livello di sistema", + "TCP": "TCP", + "TV Channel": "Canale tv", + "TV Channel Group": "Gruppo di canali televisivi", + "TV Channel ID": "ID canale TV", + "Telegram": "Telegramma", + "Text Box Color": "Colore della casella di testo", + "Text Color": "Colore del testo", + "Text criteria unsupported for Object Count tests, Ignoring Conditional": "Criteri di testo non supportati per i test del conteggio degli oggetti, ignorando il condizionale", + "Themes": "Temi", + "There are no monitors that you can view with this account.": "Non ci sono monitor che puoi visualizzare con questo account.", + "Threads": "Discussioni", + "Thumbnail": "Miniatura", + "Thursday": "Giovedì", + "Time": "Volta", + "Time Created": "Tempo creato", + "Time Left": "Tempo rimasto", + "Time Occurred": "Il tempo si è verificato", + "Time-lapse": "Lasso di tempo", + "Time-lapse Tool": "Strumento time-lapse", + "TimeZone": "Fuso orario", + "Timelapse": "Lasso di tempo", + "Timelapse Frames Share": "Condividi delle cornici timelapse", + "Timelapse Watermark": "Filigrana timelapse", + "Timeout": "Tempo scaduto", + "Timeout Reset on Next Event": "Timeout Reset al prossimo evento", + "Timeout Reset on Next Motion": "Timeout Ripristina il prossimo movimento", + "Timezone": "Fuso orario", + "Timezone Offset": "Offset del fuso orario", + "Title": "Titolo", + "Today": "In data odierna", + "Toggle Sidebar": "Attiva a disattivazione della barra laterale", + "Toggle Substream": "Attiva il substream", + "Token": "Gettone", + "Top Left": "In alto a sinistra", + "Top Right": "In alto a destra", + "Traditional (Watch-Only, Includes Buffer)": "Tradizionale (solo orologio, include buffer)", + "Traditional Recording": "Registrazione tradizionale", + "Traditional Recording Flags": "Bandiere di registrazione tradizionali", + "Train": "Treno", + "TrainConfirm": "Sei sicuro di voler iniziare ad allenarti? Questo può richiedere più di 12 ore con oltre 500 immagini. Ciò consumerà una grande quantità di risorse, come RAM e/o CPU.", + "TrainConfirmStop": "Sei sicuro di voler smettere di allenarsi?", + "Trainer Engine": "Motore del trainer", + "Trigger Blocked": "Grilletto bloccato", + "Trigger Camera Groups": "Gruppi di telecamere trigger", + "Trigger Event": "Evento scatenante", + "Trigger Group to Record": "Trigger Group da registrare", + "Trigger Record": "Record di grilletto", + "Trigger Successful": "Innescare successo", + "Trigger Threshold": "Soglia di grilletto", + "Tuesday": "Martedì", + "Turn Speed": "Girare la velocità", + "Type": "Tipa", + "UDP": "UDP", + "URL": "URL", + "URL Stop Timeout": "URL Stop timeout run stop URL dopo x millisecondi ", + "US": "NOI", + "UTCDateTime": "Data", + "Unable to Launch": "Impossibile avviare", + "UnabletoLaunchText": "Si prega di salvare prima un nuovo monitor. Quindi tenta di lanciare l'editor della regione.", + "Uncommon Objects": "Oggetti non comuni", + "Uniform": "Uniforme", + "Unlink": "Unkin", + "Unlink Login": "Accesso di accesso?", + "Unlinked": "Non incazzato", + "Up": "UP Indirizzo URL ", + "Up Stop": "STOP UP Indirizzo URL ", + "Update": "Aggiornare", + "Update to Development": "Aggiornamento allo sviluppo", + "Update to Master": "Aggiornamento a Master", + "Upload Bandwidth": "Carica larghezza di banda", + "Upload File": "Caricare un file", + "Uploaded Only": "Solo caricato", + "Uploaders": "Uploader", + "Use Built-In": "Usa integrato", + "Use Camera Timestamps": "Utilizzare i timestamp della fotocamera", + "Use Global Amazon S3 Video Storage": "Usa l'archiviazione video Global Amazon S3", + "Use Global Backblaze B2 Video Storage": "Utilizzare l'archiviazione video globale BAINTLAZE B2", + "Use Global Wasabi Hot Cloud Storage Video Storage": "Utilizzare l'archiviazione video di archiviazione cloud hot globale wasabi", + "Use Global WebDAV Video Storage": "Usa l'archiviazione video WebDav globale", + "Use HTML5 Play Method": "Usa il metodo di gioco HTML5", + "Use Max Storage Amount": "Utilizzare l'importo di archiviazione massimo", + "Use Raw Snapshot": "Usa l'istantanea grezza", + "Use Substream": "Usa il substream", + "Use coProcessor": "Usa il coprocessore", + "UseCount": "Usecount", + "User Log": "Registro utente", + "User Not Found": "Utente non trovato", + "Username": "Nome utente", + "VA-API": "Va-api", + "Value": "Valore", + "Video": "video", + "Video Bit Rate": "Bitrate video", + "Video Codec": "Video codec", + "Video Configuration": "Configurazione video", + "Video Filter": "Filtro video", + "Video Finished": "Video finito", + "Video Length (minutes) and Motion Count per video": "Lunghezza del video (minuti) e conteggio dei movimenti per video", + "Video Limit": "Limite video", + "Video Record Rate": "Velocità di registrazione video", + "Video Set": "Set video", + "Video Share": "Condivisione video", + "Video Status": "Stato video", + "Video and Time Span (Minutes)": "Video e tempo (minuti)", + "Video stream only from first feed": "Stream video solo dal primo feed", + "Video streams only": "Solo flussi video", + "Videos": "Video", + "Videos List": "Elenco video", + "Videos Merge": "I video si uniscono", + "Viewing Server Stats": "Visualizzazione delle statistiche del server", + "Warning": "Avvertimento", + "Wasabi Hot Cloud Storage": "Wasabi Hot Cloud Storage", + "Wasabi Hot Cloud Storage Upload Error": "Wasabi Hot Cloud Storage Caricamento Errore", + "Watch": "Guadare", + "Watch Only": "Guarda solo", + "Watch-Only": "Watchy", + "Watching": "Guardando", + "Web Page": "Pagina web", + "WebDAV": "Webdav", + "WebM (libvpx)": "Webm (libvpx)", + "Webdav Error": "Errore WebDAV", + "WebdavErrorTextCreatingDir": "Impossibile creare la directory.", + "WebdavErrorTextTryCreatingDir": "Non può salvare. Cercando di creare la directory.", + "Webhook": "Webhook", + "Webhook URL": "Webhook URL", + "Websocket": "WebSocket", + "Websocket Connected": "WebSocket connesso", + "Websocket Disconnected": "WebSocket disconnesso", + "Wednesday": "Mercoledì", + "Welcome": "Benarrivata!", + "When Detector is Off": "Quando il rivelatore è spento", + "When Detector is On": "Quando il rivelatore è acceso", + "WhiteBalance": "Bilanciamento del bianco", + "WideDynamicRange": "Ampia gamma dinamica", + "Width": "Larghezza", + "X Point": "X punto", + "Y Point": "Y punto", + "Yes": "sì", + "Zip and Download": "Zip e download", + "Zipping Videos": "Video zipping", + "Zones": "Zone", + "Zoom In": "Ingrandire", + "Zoom In Stop": "Indirizzo URL di zoom in stop ", + "Zoom Out": "Zoom out", + "Zoom Out Stop": "Zoom out stop indirizzo URL ", + "a day": "un giorno", + "a few seconds": "pochi secondi", + "a minute": "un minuto", + "a month": "un mese", + "a year": "un anno", + "aac": "AAC", + "aac (Default)": "AAC (impostazione predefinita)", + "ac3": "AC3", + "accountActionFailed": "Azione dell'account non riuscita", + "accountAdded": "Account aggiunto", + "accountAddedText": "L'account è stato aggiunto.", + "accountDeleted": "Account cancellato", + "accountDeletedText": "L'account è stato eliminato.", + "accountId": "Account ID", + "accountSettingsDescription": "Gestisci il tuo profilo e imposta opzioni come un importo di archiviazione massimo e il numero massimo di giorni per conservare i video.", + "accountSettingsError": "Errore di impostazioni dell'account", + "activatedText": "La tua installazione è stata attivata.", + "ago": "fa", + "airplane": "aereo", + "alreadyLinked": "Già collegato a un account", + "an hour": "un'ora", + "apple": "mela", + "applicationKey": "Chiave dell'applicazione", + "aws_accessKeyId": "ID chiave di accesso", + "aws_secretAccessKey": "Chiave di accesso segreto", + "backpack": "zaino", + "banana": "Banana", + "baseball bat": "Mazza da baseball", + "baseball glove": "guanto da baseball", + "bear": "orsa", + "bed": "letta", + "bench": "panca", + "bicycle": "bicicletta", + "bindDN": "binddn", + "bird": "uccello", + "blankPassword": "Lascia vuoto per mantenere la stessa password", + "boat": "barca", + "book": "prenotare", + "bottle": "bottiglia", + "bowl": "ciotola", + "broccoli": "broccole", + "bus": "autobus", + "cake": "torta", + "car": "macchina", + "carrot": "carota", + "cat": "gatta", + "cell phone": "cellulare", + "chair": "sedia", + "clientStreamFailedattemptingReconnect": "Controllo del flusso lato client non riuscito, tentando di riconnettersi.", + "clock": "orologio", + "coProcess Crashed for Monitor": "Il coprocesso si è schiantato per il monitor", + "coProcess Unexpected Exit": "Coprocess uscita inaspettata", + "coProcessor": "coprocessore", + "coProcessor Started": "Il coprocessore è iniziato", + "coProcessor Stopped": "Il coprocessore si fermò", + "coProcessorTextStarted": "Il coprocessore ha iniziato solo per le uscite della CPU.", + "coProcessorTextStopped": "Il coprocessore è finito.", + "codecMismatchText1": "La fotocamera fornisce dati di streaming H.265 (HEVC) e si utilizza Copy come codec video per la sezione Stream. Il tuo flusso di Shinobi potrebbe non apparire su dispositivi che non possono utilizzare questo codec. L'app mobile Shinobi può visualizzare questi flussi.", + "codecMismatchText2": "Il codec video selezionato non è applicabile. La fotocamera fornisce dati di flusso MJPEG e si utilizza Copy come codec video per la sezione Stream. Modificato il tipo di flusso in MJPEG.", + "codecMismatchText3": "Il codec video selezionato non è applicabile. La fotocamera fornisce dati di flusso MJPEG e si utilizza Copy come codec video per la sezione di registrazione. Modificato il codec video in libx264.", + "confirmDeleteFilter": "Vuoi eliminare questo filtro? Non puoi recuperarlo.", + "contactAdmin": "Contatta il manutentore dell'installazione di Shinobi.", + "copy": "copia", + "couch": "divano", + "cow": "mucca", + "cuda": "CUDA (Nvidia NVENC)", + "cup": "tazza", + "cuvid": "Cuvid (Nvidia Nvenc)", + "days": "giorni", + "deleteApiKey": "Elimina la chiave API", + "deleteApiKeyText": "Vuoi eliminare questa chiave API? Non puoi recuperarlo.", + "deleteMonitorStateText1": "Vuoi eliminare questo preimpostazione degli stati del monitor? Le configurazioni del monitor non possono essere recuperate.", + "deleteMonitorStateText2": "Vuoi eliminare il preimpostazione di questo monitor?", + "deleteScheduleText": "Vuoi eliminare questo programma? Monitorare i preset associati non verranno modificati ..", + "deleteSubAccount": "Elimina sotto-conto", + "deleteSubAccountText": "Vuoi eliminare questo sotto-conto? Non puoi recuperarlo.", + "dining table": "tavolo da pranzo", + "dog": "cagna", + "donut": "ciambella", + "drm": "Condivisione di oggetti DRM", + "dropBoxSuccess": "Successo! File salvati sul tuo dropbox.", + "dxva2": "DXVA2 (Video DirectX, Windows)", + "elephant": "elefantessa", + "eventFilterActionText": "Queste sono le azioni che si verificano dalle condizioni del filtro che sono riuscite. La \"scelta originale\" si riferisce all'opzione che hai scelto nelle impostazioni del monitor.", + "eventFilterErrorBrackets": "Hai un numero non uniforme di parentesi. Vengono ignorati.", + "eventFiltersDescription": "Filtri di configurazione per quando si verificano eventi.", + "failedLoginText1": "Non sei riuscito a accedere troppe volte. Devi aspettare 15 minuti prima di riprovare.", + "failedLoginText2": "Si prega di controllare le credenziali di accesso.", + "fieldMissingValueText1": "La fotocamera fornisce dati di flusso MJPEG. È necessario impostare il tasso di acquisizione del monitor. Shinobi tenterà di rilevarlo e riempirlo automaticamente.", + "fieldTextAccelerator": "Accelerazione hardware (HWACCEL) per i flussi di decodifica.", + "fieldTextAcodec": "Codec audio per la registrazione.", + "fieldTextActionsCommand": "È possibile utilizzarlo per attivare uno script su comando.", + "fieldTextActionsHalt": "Fai non fare all'evento nulla, come se non fosse mai successo.", + "fieldTextActionsIndifference": "Modifica l'indifferenza minima richiesta per l'evento.", + "fieldTextActionsRecord": "Utilizzare la registrazione tradizionale, HotSwap o Elimina immobile con le loro opzioni attualmente impostate nella sezione Impostazioni di rilevamento globali.", + "fieldTextAduration": "Specificare quanti microsecondi vengono analizzati per sondare l'ingresso. Impostare su 100000 se si utilizza RTSP e hai problemi di streaming.", + "fieldTextAudioAlert": "Suono quando si verifica un evento.", + "fieldTextAudioDelay": "Ritardare fino alla prossima volta un evento può iniziare un avviso. Misurato in secondi.", + "fieldTextAudioNote": "Suono quando appare la bolla di informazioni.", + "fieldTextAutoHost": "L'URL a flusso completo.", + "fieldTextAutoHostEnable": "Nutri i singoli pezzi necessari per costruire un URL di flusso o fornire l'intero URL e consentire a Shinobi di analizzarlo per te.", + "fieldTextChannelHlsListSize": "Il numero di segmenti massimo prima di eliminare automaticamente i vecchi segmenti.", + "fieldTextChannelHlsTime": "Per quanto tempo dovrebbe essere ogni segmento video, in pochi minuti. Ogni segmento verrà disegnato dal client tramite un file M3U8. I segmenti più corti assumono meno spazio.", + "fieldTextChannelPresetStream": "Flag preimpostata per alcuni coder video. Se scopri che la tua fotocamera si blocca ogni pochi secondi: prova a lasciarla vuota.", + "fieldTextChannelStreamAcodec": "Codec audio per lo streaming.", + "fieldTextChannelStreamAcodecAac": "Utilizzato per il video MP4.", + "fieldTextChannelStreamAcodecAc3": "Utilizzato per il video MP4.", + "fieldTextChannelStreamAcodecAuto": "Lascia che FFMPEG scelga.", + "fieldTextChannelStreamAcodecCopy": "Utilizzato per il video MP4. Ha un utilizzo della CPU molto basso, ma alcuni codec audio necessitano di flag personalizzati come -strict 2 per AAC.",
+ "fieldTextChannelStreamAcodecLibmp3lame": "Utilizzato per il video MP4.",
+ "fieldTextChannelStreamAcodecLibopus": "Utilizzato per il video WebM.",
+ "fieldTextChannelStreamAcodecLibvorbis": "Utilizzato per il video WebM.",
+ "fieldTextChannelStreamAcodecNoAudio": "Nessun audio, questa è un'opzione che deve essere impostata in alcune parti del mondo per motivi legali.",
+ "fieldTextChannelStreamFps": "La velocità in cui i frame vengono visualizzati ai client, in frame al secondo. Essere consapevoli che non ci sono impostazioni predefinite. Questo può portare ad un elevato utilizzo della larghezza di banda.",
+ "fieldTextChannelStreamQuality": "Numero basso significa qualità superiore. Numero più alto significa meno qualità.",
+ "fieldTextChannelStreamRotate": "Cambia l'angolo di visualizzazione del flusso video.",
+ "fieldTextChannelStreamScaleX": "Larghezza dell'immagine del flusso che viene emessa dopo l'elaborazione.",
+ "fieldTextChannelStreamScaleY": "Altezza dell'immagine del flusso che viene utilizzata dopo l'elaborazione.",
+ "fieldTextChannelStreamType": "Il metodo utilizzato per consumare il flusso video.",
+ "fieldTextChannelStreamTypeFLV": "Invio di frame codificati FLV su WebSocket.",
+ "fieldTextChannelStreamTypeHLS(includesAudio)": "Metodo simile ai flussi live di Facebook. Include audio Se l'input lo fornisce. C'è un ritardo di circa 4-6 secondi perché questo metodo registra i segmenti li spinge al cliente piuttosto che spingere come mentre li crea.",
+ "fieldTextChannelStreamTypeMJPEG": "MOVIMENTO STANDARD JPEG Immagine. Nessun audio.",
+ "fieldTextChannelStreamTypePoseidon": "Poseidon è costruito sul codice di elaborazione MP4 di Kevin Godell. Simula un file MP4 in streaming ma utilizzando i dati di un flusso live. Include audio. Alcuni browser possono riprodurlo come un normale file MP4. Flussi su HTTP o WebSocket.",
+ "fieldTextChannelStreamVcodec": "Codec video per lo streaming.",
+ "fieldTextChannelStreamVcodecAuto": "Lascia che FFMPEG scelga.",
+ "fieldTextChannelStreamVcodecCopy": "Utilizzato per il video MP4. Ha un utilizzo della CPU molto basso, ma non è possibile utilizzare filtri video e file file possono essere giganteschi. Meglio impostare le impostazioni MP4 sul lato della fotocamera quando si utilizza questa opzione.",
+ "fieldTextChannelStreamVcodecLibx264": "Utilizzato per il video MP4.",
+ "fieldTextChannelStreamVcodecLibx265": "Utilizzato per il video MP4.",
+ "fieldTextChannelSvf": "Posizionare i filtri video FFMPEG in questa casella per influenzare la porzione di streaming. No spazi.",
+ "fieldTextControlInvertY": "Per quando la fotocamera è montata a testa in giù o utilizza controlli verticali invertiti.",
+ "fieldTextCrf": "Numero basso significa qualità superiore. Numero più alto significa meno qualità.",
+ "fieldTextCustDetect": "Flag personalizzati che si legano al rivelatore del flusso utilizza per l'analisi.",
+ "fieldTextCustDetectObject": "Flag personalizzati che si legano al rivelatore del flusso utilizza per l'analisi.",
+ "fieldTextCustInput": "Flag personalizzati che si legano all'input del processo FFMPEG.",
+ "fieldTextCustRecord": "Flag personalizzati che si legano alla registrazione del processo FFMPEG.",
+ "fieldTextCustSipRecord": "Flag personalizzati che si legano all'output da cui le registrazioni basate su eventi sifon.",
+ "fieldTextCustSnap": "Flag personalizzati che si legano alle istantanee.",
+ "fieldTextCustStream": "Flag personalizzati che si legano al flusso (vista laterale client) del processo FFMPEG.",
+ "fieldTextCustomOutput": "Aggiungi un output personalizzato come frame JPEG o invia i dati direttamente a un altro server.",
+ "fieldTextCutoff": "In pochi minuti. Quando tagliare e avviare un nuovo file video.",
+ "fieldTextDays": "Il numero di giorni per mantenere i video prima di spurgare.",
+ "fieldTextDetailSubstreamInputRtspTransportAuto": "Lascia decidere FFMPEG. Normalmente proverà prima UDP.",
+ "fieldTextDetailSubstreamInputRtspTransportTCP": "Impostalo su questo se UDP inizia a dare risultati indesiderati.",
+ "fieldTextDetailSubstreamInputRtspTransportUDP": "FFMPEG ci prova prima.",
+ "fieldTextDetailSubstreamOutputHlsListSize": "Il numero di segmenti massimo prima di eliminare automaticamente i vecchi segmenti.",
+ "fieldTextDetailSubstreamOutputHlsTime": "Per quanto tempo dovrebbe essere ogni segmento video, in pochi minuti. Ogni segmento verrà disegnato dal client tramite un file M3U8. I segmenti più corti assumono meno spazio.",
+ "fieldTextDetailSubstreamOutputPresetStream": "Flag preimpostata per alcuni coder video. Se scopri che la tua fotocamera si blocca ogni pochi secondi: prova a lasciarla vuota.",
+ "fieldTextDetailSubstreamOutputStreamAcodec": "Codec audio per lo streaming.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAac": "Utilizzato per il video MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAc3": "Utilizzato per il video MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAuto": "Lascia che FFMPEG scelga.",
+ "fieldTextDetailSubstreamOutputStreamAcodecCopy": "Utilizzato per il video MP4. Ha un utilizzo della CPU molto basso, ma alcuni codec audio necessitano di flag personalizzati come -strict 2 per AAC.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibmp3lame": "Utilizzato per il video MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibopus": "Utilizzato per il video WebM.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibvorbis": "Utilizzato per il video WebM.",
+ "fieldTextDetailSubstreamOutputStreamAcodecNoAudio": "Nessun audio, questa è un'opzione che deve essere impostata in alcune parti del mondo per motivi legali.",
+ "fieldTextDetailSubstreamOutputStreamFps": "La velocità in cui i frame vengono visualizzati ai client, in frame al secondo. Essere consapevoli che non ci sono impostazioni predefinite. Questo può portare ad un elevato utilizzo della larghezza di banda.",
+ "fieldTextDetailSubstreamOutputStreamQuality": "Numero basso significa qualità superiore. Numero più alto significa meno qualità.",
+ "fieldTextDetailSubstreamOutputStreamRotate": "Cambia l'angolo di visualizzazione del flusso video.",
+ "fieldTextDetailSubstreamOutputStreamScaleX": "Larghezza dell'immagine del flusso che viene emessa dopo l'elaborazione.",
+ "fieldTextDetailSubstreamOutputStreamScaleY": "Altezza dell'immagine del flusso che viene utilizzata dopo l'elaborazione.",
+ "fieldTextDetailSubstreamOutputStreamType": "Il metodo utilizzato per consumare il flusso video.",
+ "fieldTextDetailSubstreamOutputStreamVcodec": "Codec video per lo streaming.",
+ "fieldTextDetailSubstreamOutputStreamVcodecAuto": "Lascia che FFMPEG scelga.",
+ "fieldTextDetailSubstreamOutputStreamVcodecCopy": "Utilizzato per il video MP4. Ha un utilizzo della CPU molto basso, ma non è possibile utilizzare filtri video e file file possono essere giganteschi. Meglio impostare le impostazioni MP4 sul lato della fotocamera quando si utilizza questa opzione.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx264": "Utilizzato per il video MP4.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx265": "Utilizzato per il video MP4.",
+ "fieldTextDetailSubstreamOutputSvf": "Posizionare i filtri video FFMPEG in questa casella per influenzare la porzione di streaming. No spazi.",
+ "fieldTextDetector": "Ciò aggiungerà un altro output nel comando FFMPEG per il rilevatore di movimento.",
+ "fieldTextDetectorAudio": "Controlla se l'audio si è verificato con una certificazione. La lettura decisa potrebbe non essere accurata alla misurazione del mondo reale.",
+ "fieldTextDetectorBufferHlsListSize": "Il numero di segmenti massimo prima di eliminare automaticamente i vecchi segmenti.",
+ "fieldTextDetectorBufferHlsTime": "Per quanto tempo dovrebbe essere ogni segmento video, in pochi secondi. Ogni segmento verrà disegnato dal client tramite un file M3U8. I segmenti più corti assumono meno spazio.",
+ "fieldTextDetectorColorThreshold": "La quantità di differenza consentita in un pixel prima che sia considerato moto.",
+ "fieldTextDetectorCommand": "Il comando che verrà eseguito. Questo è l'equivalente dell'esecuzione di un comando shell dal terminale.",
+ "fieldTextDetectorCommandTimeout": "Questo valore è un timer per consentire la prossima esecuzione del tuo script. Questo valore è in pochi minuti.",
+ "fieldTextDetectorFps": "Quanti cornici un secondo da inviare al rilevatore di movimento; 2 è il valore predefinito.",
+ "fieldTextDetectorFrame": "Questo leggerà l'intero fotogramma per le differenze di pixel. Questo è lo stesso della creazione di una regione che copre l'intero schermo.",
+ "fieldTextDetectorHttpApi": "Vuoi consentire i trigger HTTP a questa fotocamera?",
+ "fieldTextDetectorLisencePlate": "Abilita il riconoscimento della targa. Il plugin OpenAlPR ha sempre abilitato.",
+ "fieldTextDetectorLisencePlateCountry": "Scegli il tipo di piastre da riconoscere. Solo noi e l'UE sono supportati in questo momento.",
+ "fieldTextDetectorLockTimeout": "Lockout per quando è consentito il trigger successivo, per evitare di sovraccaricare il database e ricevere client. Misurato in millisecondi.",
+ "fieldTextDetectorMaxSensitivity": "La valutazione della fiducia del movimento deve essere inferiore a questo valore da vedere come un trigger. Lascia vuoto per nessun massimo. Questa opzione era precedentemente denominata \"Max Indifference\".",
+ "fieldTextDetectorNoiseFilter": "Tentare di filtrare il grano o un movimento ripetuto a una particolare indifferenza.",
+ "fieldTextDetectorNoiseFilterRange": "La quantità di differenza consentita in un pixel prima che sia considerato moto.",
+ "fieldTextDetectorNotrigger": "Controllare se si è verificato un movimento su un intervallo. Se si è verificato il movimento, il controllo verrà ripristinato.",
+ "fieldTextDetectorNotriggerCommand": "Il comando che verrà eseguito. Questo è l'equivalente dell'esecuzione di un comando shell dal terminale.",
+ "fieldTextDetectorNotriggerCommandTimeout": "Questo valore è un timer per consentire la prossima esecuzione del tuo script. Questo valore è in pochi minuti.",
+ "fieldTextDetectorNotriggerDiscord": "Se il movimento non è stato rilevato dopo il periodo di timeout, riceverai una notifica di discordia.",
+ "fieldTextDetectorNotriggerTimeout": "Il timeout è calcolato in pochi minuti.",
+ "fieldTextDetectorNotriggerWebhook": "Invia una richiesta GET a un URL con alcuni valori dall'evento.",
+ "fieldTextDetectorObjCount": "Contare gli oggetti rilevati.",
+ "fieldTextDetectorObjCountInRegion": "Contare gli oggetti solo all'interno delle regioni.",
+ "fieldTextDetectorPam": "Usa il rilevatore di movimento di Kevin Godell. Questo è integrato in Shinobi e non richiede altra configurazione per attivare.",
+ "fieldTextDetectorPtzFollow": "Seguire l'oggetto più grande rilevato con PTZ? Richiede un rilevatore di oggetti in esecuzione o matrici fornite di eventi.",
+ "fieldTextDetectorRecordMethod": "Esistono diversi modi per iniziare a registrare quando si verifica un evento, come il movimento. La registrazione tradizionale è la più facile da usare.",
+ "fieldTextDetectorSave": "Salva eventi di movimento in SQL. Ciò consentirà la visualizzazione del movimento rispetto ai video durante i tempi che gli eventi si sono verificati in Power Viewer.",
+ "fieldTextDetectorScaleX": "Larghezza dell'immagine rilevata. Le dimensioni più piccole richiedono meno CPU.",
+ "fieldTextDetectorScaleY": "Altezza dell'immagine rilevata. Le dimensioni più piccole richiedono meno CPU.",
+ "fieldTextDetectorSendFrames": "Spingere i frame al plug -in collegato da analizzare.",
+ "fieldTextDetectorSendFramesObject": "Spingere i frame al plug -in collegato da analizzare.",
+ "fieldTextDetectorSendVideoLength": "In secondi. La durata del video che viene inviato al servizio di notifica, come e -mail o discordia.",
+ "fieldTextDetectorSensitivity": "La valutazione della fiducia del movimento deve superare questo valore per essere visto come un trigger. Questo numero è correlato direttamente alla valutazione della confidenza restituita dal rilevatore di movimento. Questa opzione era precedentemente denominata \"indifferenza\".",
+ "fieldTextDetectorThreshold": "Numero minimo di rilevamenti per licenziare un evento di movimento. I rilevamenti devono essere all'interno del rilevatore la soglia divisa per i secondi del rivelatore FPS. Ad esempio, se il rivelatore FPS è 2 e la soglia del trigger è 3, è necessario che tre rilevamenti devono verificarsi entro 1,5 secondi per attivare un evento di movimento. Questa soglia è per regione di rilevamento.",
+ "fieldTextDetectorTimeout": "Il periodo di tempo \"record di trigger\" verrà eseguito per. Questo è letto in pochi minuti.",
+ "fieldTextDetectorTrigger": "Ciò ordinerà alla fotocamera di registrare se è impostato su \"solo guardia\" quando viene rilevato un evento.",
+ "fieldTextDetectorUseDetectObject": "Crea frame per l'invio a qualsiasi plug -in connesso.",
+ "fieldTextDetectorWebhook": "Invia una richiesta GET a un URL con alcuni valori dall'evento.",
+ "fieldTextDetectorWebhookTimeout": "Questo valore è un timer per consentire la prossima esecuzione del tuo webhook. Questo valore è in pochi minuti.",
+ "fieldTextDir": "Posizione di dove verranno salvati i file registrati. È possibile configurare più posizioni con la variabile AddStorage .",
+ "fieldTextEventDays": "Il numero di giorni per mantenere gli eventi prima dello spurgo.",
+ "fieldTextEventMonPop": "Quando si verifica un evento popolare il flusso di monitor.",
+ "fieldTextEventRecordScaleX": "Larghezza dell'immagine di registrazione basata su eventi che viene emessa dopo l'elaborazione.",
+ "fieldTextEventRecordScaleY": "Altezza dell'immagine di registrazione basata su eventi che viene utilizzata dopo l'elaborazione.",
+ "fieldTextExt": "Il tipo di file per il file video registrato.",
+ "fieldTextExtMP4": "Questo tipo di file è giocabile è quasi tutti i browser Web moderni, che includono dispositivi mobili. Il filesize tende ad essere più grande a meno che non si abbassa la qualità.",
+ "fieldTextExtWebM": "Piccolo fileszeze, bassa compatibilità client. Buono per caricare su siti come YouTube.",
+ "fieldTextFactorAuth": "Abilita un requisito secondario per l'accesso attraverso uno dei metodi abilitati.",
+ "fieldTextFatalMax": "Il numero di volte da riprovare per la connessione di rete tra il server e la fotocamera prima di impostare il monitor su disabilitato. Nessun decimali. Impostato su 0 per riprovare per sempre.",
+ "fieldTextFps": "La velocità in cui i frame sono registrati in file, frame al secondo. Essere consapevoli che non ci sono impostazioni predefinite. Questo può portare a file di grandi dimensioni. Meglio impostare questo lato della fotocamera.",
+ "fieldTextHeight": "Altezza dell'immagine del flusso.",
+ "fieldTextHlsListSize": "Il numero di segmenti massimo prima di eliminare automaticamente i vecchi segmenti.",
+ "fieldTextHlsTime": "Per quanto tempo dovrebbe essere ogni segmento video, in pochi minuti. Ogni segmento verrà disegnato dal client tramite un file M3U8. I segmenti più corti assumono meno spazio.",
+ "fieldTextHost": "Indirizzo di connessione",
+ "fieldTextHwaccel": "Motore di decodifica",
+ "fieldTextHwaccelVcodec": "Motore di decodifica",
+ "fieldTextInverseTrigger": "Per innescare le regioni esterne specifiche. Non si attiverà con il rilevamento del frame completo abilitato.",
+ "fieldTextIp": "Gamma o singolo",
+ "fieldTextIrCutFilterAuto": "Il filtro Cut IR viene attivato automaticamente dal dispositivo.",
+ "fieldTextIrCutFilterOff": "Disabilita IR Cut Fiter. In genere modalità notturna.",
+ "fieldTextIrCutFilterOn": "Abilita IR Cut Fiter. In genere modalità giorno.",
+ "fieldTextIsOnvif": "È una fotocamera conforme a Onvif?",
+ "fieldTextLang": "La lingua principale degli elementi di testo. Per una traduzione completa Aggiungi la tua lingua in conf.json E.G: \"lingua\": \"en_ca\", ",
+ "fieldTextLogDays": "Il numero di giorni per mantenere i tronchi prima dello spurgo.",
+ "fieldTextLoglevel": "La quantità di dati da fornire durante il lavoro.",
+ "fieldTextLoglevelAllWarnings": "Mostra tutti gli avvertimenti. Usalo se non riesci a scoprire cosa c'è che non va nella tua fotocamera.",
+ "fieldTextLoglevelFatal": "Visualizza solo errori fatali.",
+ "fieldTextLoglevelOnError": "Visualizza tutti gli errori importanti. Nota: questo non mostra sempre informazioni importanti.",
+ "fieldTextLoglevelSilent": "Nessuno. Questo silenzio di tutte le registrazioni.",
+ "fieldTextMail": "L'accesso per gli account. L'indirizzo e -mail del titolare dell'account principale riceverà notifiche.",
+ "fieldTextMapRtspTransportAuto": "Lascia decidere FFMPEG. Normalmente proverà prima UDP.",
+ "fieldTextMapRtspTransportTCP": "Impostalo su questo se UDP inizia a dare risultati indesiderati.",
+ "fieldTextMapRtspTransportUDP": "FFMPEG ci prova prima.",
+ "fieldTextMaxKeepDays": "Il numero di giorni per conservare i video prima di spurgare in modo specifico questo monitor.",
+ "fieldTextMid": "Questo è un identificatore non variabile per il monitor. È possibile duplicare un monitor facendo doppio clic sull'ID monitor e modificandolo.",
+ "fieldTextMode": "Questo è il compito principale del monitor.",
+ "fieldTextModeDisabled": "Monitor inattivo, non verrà creato alcun processo in questa modalità.",
+ "fieldTextModeRecord": "Registrazione continua. I segmenti vengono realizzati ogni 15 minuti per impostazione predefinita.",
+ "fieldTextModeWatchOnly": "Monitor si trasformerà solo in streaming, non si verificherà alcuna registrazione se non diversamente ordinato dall'API o dal rivelatore.",
+ "fieldTextMpass": "La password per la tua fotocamera",
+ "fieldTextMuser": "L'utente di accesso per la fotocamera",
+ "fieldTextName": "Questo è il nome visualizzato leggibile dall'uomo per il monitor.",
+ "fieldTextNotes": "Commenti che vuoi lasciare per questa fotocamera.",
+ "fieldTextOnvifNonStandard": "È una fotocamera Onvif non standard?",
+ "fieldTextOnvifPort": "ONVIF viene generalmente eseguito sulla porta 8000 . Questo può essere 80 anche a seconda del modello della fotocamera.",
+ "fieldTextPass": "Lasciare vuoto per mantenere la stessa password durante la modifica delle impostazioni.",
+ "fieldTextPasswordAgain": "Devi abbinare il campo della password se desideri cambiarlo.",
+ "fieldTextPath": "Il percorso per la tua fotocamera",
+ "fieldTextPort": "Separato da virgole o un intervallo",
+ "fieldTextPortForce": "L'uso della porta Web predefinita può consentire l'interruttore automatico ad altre porte per flussi come RTSP.",
+ "fieldTextPresetRecord": "Flag preimpostata per alcuni coder video. Se scopri che la tua fotocamera si blocca ogni pochi secondi: prova a lasciarla vuota.",
+ "fieldTextPresetStream": "Flag preimpostata per alcuni coder video. Se scopri che la tua fotocamera si blocca ogni pochi secondi: prova a lasciarla vuota.",
+ "fieldTextProbesize": "Specificare quanto è grande per creare la sonda di analisi per l'input. Impostare su 100000 se si utilizza RTSP e hai problemi di streaming.",
+ "fieldTextProtocol": "Il protocollo che consumerà il flusso video.",
+ "fieldTextRecordScaleX": "Larghezza dell'immagine del flusso.",
+ "fieldTextRecordScaleY": "Altezza dell'immagine del flusso.",
+ "fieldTextRecordTimelapse": "Crea un timelapse basato su JPEG.",
+ "fieldTextRecordTimelapseMp4": "Creare un file MP4 alla fine di ogni giorno per il timelapse.",
+ "fieldTextRecordTimelapseWatermark": "Un'immagine che viene bruciata sui fotogrammi del video registrato.",
+ "fieldTextRecordTimelapseWatermarkLocation": "Posizione dell'immagine che verrà utilizzata come filigrana.",
+ "fieldTextRecordTimelapseWatermarkPosition": "Un'immagine che viene bruciata sui fotogrammi del video registrato.",
+ "fieldTextRotate": "Cambia l'angolo di registrazione del flusso video.",
+ "fieldTextRtmpKey": "Tasto di streaming per flussi in arrivo sulla porta RTMP.",
+ "fieldTextRtspTransport": "Il protocollo di trasporto utilizzerà la fotocamera. TCP è di solito la scelta migliore.",
+ "fieldTextRtspTransportAuto": "Lascia decidere FFMPEG. Normalmente proverà prima UDP.",
+ "fieldTextRtspTransportHTTP": "Metodo di connessione standard.",
+ "fieldTextRtspTransportTCP": "Impostalo su questo se UDP inizia a dare risultati indesiderati.",
+ "fieldTextRtspTransportUDP": "FFMPEG ci prova prima.",
+ "fieldTextSfps": "Specificare il frame rate (FPS) in cui la fotocamera fornisce il suo flusso.",
+ "fieldTextSignalCheck": "Quante volte il tuo cliente controllerà lo streaming per vedere se è vivo. Questo è calcolato in minuti.",
+ "fieldTextSignalCheckLog": "Questo è solo per lato client. Verrà visualizzato nel thread di registro quando si verificano controlli del segnale lato client.",
+ "fieldTextSize": "La quantità di spazio su disco Shinobi consentirà di essere consumato prima dello spurgo. Questo valore viene letto in Megabyte.",
+ "fieldTextSizeFilebinPercent": "Percentuale dell'importo di archiviazione massimo L'archivio filebin può utilizzare.",
+ "fieldTextSizeTimelapsePercent": "Percentuale dell'importo di archiviazione massimo a cui i frame timelapse possono registrare.",
+ "fieldTextSizeVideoPercent": "Percentuale di importo di archiviazione massimo a cui i video possono registrare.",
+ "fieldTextSkipPing": "Scegli se è richiesto un ping riuscito prima di iniziare un processo di monitoraggio.",
+ "fieldTextSnap": "Ottieni l'ultimo fotogramma in JPEG.",
+ "fieldTextSnapSecondsInward": "in secondi",
+ "fieldTextSqllog": "Usalo con cautela poiché a FFMPEG piace lanciare dati superflui a volte, il che può portare a molte righe del database.",
+ "fieldTextSqllogNo": "No è il valore predefinito.",
+ "fieldTextSqllogYes": "Fallo se hai solo problemi ricorrenti.",
+ "fieldTextStreamAcodec": "Codec audio per lo streaming.",
+ "fieldTextStreamAcodecAac": "Utilizzato per il video MP4.",
+ "fieldTextStreamAcodecAc3": "Utilizzato per il video MP4.",
+ "fieldTextStreamAcodecAuto": "Lascia che FFMPEG scelga.",
+ "fieldTextStreamAcodecCopy": "Utilizzato per il video MP4. Ha un utilizzo della CPU molto basso, ma alcuni codec audio necessitano di flag personalizzati come -strict 2 per AAC.",
+ "fieldTextStreamAcodecLibmp3lame": "Utilizzato per il video MP4.",
+ "fieldTextStreamAcodecLibopus": "Utilizzato per il video WebM.",
+ "fieldTextStreamAcodecLibvorbis": "Utilizzato per il video WebM.",
+ "fieldTextStreamAcodecNoAudio": "Nessun audio, questa è un'opzione che deve essere impostata in alcune parti del mondo per motivi legali.",
+ "fieldTextStreamFlvType": "Questo è solo per il cruscotto Shinobi. Entrambi i metodi di flusso sono ancora attivi e pronti per l'uso.",
+ "fieldTextStreamFps": "La velocità in cui i frame vengono visualizzati ai client, in frame al secondo. Essere consapevoli che non ci sono impostazioni predefinite. Questo può portare ad un elevato utilizzo della larghezza di banda.",
+ "fieldTextStreamLoop": "Loop un file statico in modo che il flusso di file si comporti come un live streaming.",
+ "fieldTextStreamQuality": "Numero basso significa qualità superiore. Numero più alto significa meno qualità.",
+ "fieldTextStreamRotate": "Cambia l'angolo di visualizzazione del flusso video.",
+ "fieldTextStreamScaleX": "Larghezza dell'immagine del flusso che viene emessa dopo l'elaborazione.",
+ "fieldTextStreamScaleY": "Altezza dell'immagine del flusso che viene utilizzata dopo l'elaborazione.",
+ "fieldTextStreamTimestamp": "Un orologio che viene bruciato sui fotogrammi del flusso video.",
+ "fieldTextStreamTimestampBoxColor": "TimStamp Donkrop Color.",
+ "fieldTextStreamTimestampColor": "Timstamp Testo Colore.",
+ "fieldTextStreamTimestampFont": "FILE FONT per modellare il tuo timestamp.",
+ "fieldTextStreamTimestampFontSize": "Dimensione del carattere in Pt.",
+ "fieldTextStreamTimestampX": "Posizione orizztonale del timestamp",
+ "fieldTextStreamTimestampY": "Posizione verticale del timestamp",
+ "fieldTextStreamType": "Il metodo utilizzato per consumare il flusso video.",
+ "fieldTextStreamTypeBase64OverWebsocket": "Invio di frame codificati Base64 su WebSocket. Questo evita la memorizzazione nella cache ma non c'è audio.",
+ "fieldTextStreamTypeFLV": "Invio di frame codificati FLV su WebSocket.",
+ "fieldTextStreamTypeHLS(includesAudio)": "Metodo simile ai flussi live di Facebook. Include audio Se l'input lo fornisce. C'è un ritardo di circa 4-6 secondi perché questo metodo registra i segmenti li spinge al cliente piuttosto che spingere come mentre li crea.",
+ "fieldTextStreamTypeMJPEG": "MOVIMENTO STANDARD JPEG Immagine. Nessun audio.",
+ "fieldTextStreamTypePoseidon": "Poseidon è costruito sul codice di elaborazione MP4 di Kevin Godell. Simula un file MP4 in streaming ma utilizzando i dati di un flusso live. Include audio. Alcuni browser possono riprodurlo come un normale file MP4. Flussi su HTTP o WebSocket.",
+ "fieldTextStreamVcodec": "Codec video per lo streaming.",
+ "fieldTextStreamVcodecAuto": "Lascia che FFMPEG scelga.",
+ "fieldTextStreamVcodecCopy": "Utilizzato per il video MP4. Ha un utilizzo della CPU molto basso, ma non è possibile utilizzare filtri video e file file possono essere giganteschi. Meglio impostare le impostazioni MP4 sul lato della fotocamera quando si utilizza questa opzione.",
+ "fieldTextStreamVcodecLibx264": "Utilizzato per il video MP4.",
+ "fieldTextStreamVcodecLibx265": "Utilizzato per il video MP4.",
+ "fieldTextStreamVf": "Posizionare i filtri video FFMPEG in questa casella per influenzare la porzione di streaming. No spazi.",
+ "fieldTextStreamWatermark": "Un'immagine che viene bruciata sui frame del flusso video.",
+ "fieldTextStreamWatermarkLocation": "Posizione dell'immagine che verrà utilizzata come filigrana.",
+ "fieldTextStreamWatermarkPosition": "Un'immagine che viene bruciata sui frame del flusso video.",
+ "fieldTextTimestamp": "Un orologio che viene bruciato sui fotogrammi del video registrato.",
+ "fieldTextTimestampBoxColor": "TimStamp Donkrop Color.",
+ "fieldTextTimestampColor": "Timstamp Testo Colore.",
+ "fieldTextTimestampFont": "FILE FONT per modellare il tuo timestamp.",
+ "fieldTextTimestampFontSize": "Dimensione del carattere in Pt.",
+ "fieldTextTimestampX": "Posizione orizztonale del timestamp",
+ "fieldTextTimestampY": "Posizione verticale del timestamp",
+ "fieldTextTvChannel": "Questo monitor avrà abilitato le funzionalità del canale TV. Sarai in grado di visualizzarlo nell'elenco dei canali TV.",
+ "fieldTextTvChannelGroupTitle": "Un gruppo personalizzato per il canale.",
+ "fieldTextTvChannelId": "Un ID personalizzato per il canale.",
+ "fieldTextType": "Il metodo utilizzato per consumare il flusso video.",
+ "fieldTextTypeDashcam(StreamerV2)": "WebSocket Stream P2P basato su WebM.",
+ "fieldTextTypeH.264/H.265/H.265+": "Leggendo un video di alta qualità che a volte include audio.",
+ "fieldTextTypeHLS(.m3u8)": "Leggendo un video di alta qualità che a volte include audio.",
+ "fieldTextTypeJPEG": "Leggere le istantanee da un URL e fare un flusso e/o un video da loro.",
+ "fieldTextTypeLocal": "Lettura di schede di acquisizione, webcam o telecamere integrate.",
+ "fieldTextTypeMJPEG": "Simile a JPEG tranne che la maneggevolezza del telaio viene eseguita da FFMPEG, non da Shinobi.",
+ "fieldTextTypeMPEG4(.mp4/.ts)": "Un file statico. Leggi a un ritmo inferiore e non dovrebbe essere utilizzato per un effettivo streaming live.",
+ "fieldTextTypeMxPEG": "MoboTix MJPEG Stream",
+ "fieldTextTypeRTMP": "Impara a connetterti qui: articolo: Come spingere i flussi tramite RTMP a Shinobi ",
+ "fieldTextTypeShinobiStreamer": "Stream P2P basato su JPEG di WebSocket.",
+ "fieldTextVcodec": "Codec video per la registrazione.",
+ "fieldTextVf": "Posizionare i filtri video FFMPEG in questa casella per influenzare la parte di registrazione. No spazi.",
+ "fieldTextWallClockTimestampIgnore": "Basare tutti i dati della fotocamera in arrivo nel tempo della fotocamera anziché nel tempo del server.",
+ "fieldTextWatchdogReset": "Se c'è una sovrapposizione nel record di trigger in caso di ripristino.",
+ "fieldTextWatermark": "Un'immagine che viene bruciata sui fotogrammi del video registrato.",
+ "fieldTextWatermarkLocation": "Posizione dell'immagine che verrà utilizzata come filigrana.",
+ "fieldTextWatermarkPosition": "Un'immagine che viene bruciata sui fotogrammi del video registrato.",
+ "fieldTextWidth": "Larghezza dell'immagine del flusso.",
+ "fire hydrant": "idrante",
+ "flv": "flv",
+ "for Global Access": "per accesso globale",
+ "fork": "forchetta",
+ "frisbee": "frisbee",
+ "getAMonitor": "Ottieni un monitor",
+ "getATvChannel": "Ottieni canali TV per il monitor",
+ "getATvChannelText": "Ottieni i flussi H.264 disponibili di un singolo monitor in una playlist .M3U8.",
+ "getAllMonitors": "Ottieni tutti i monitor",
+ "getAllTvChannels": "Ottieni tutti i canali TV",
+ "getAllTvChannelsText": "Ottieni tutti i flussi H.264 in una playlist .M3U8. Abilita l'opzione del canale TV nelle impostazioni del monitor per vedere i loro flussi in questo elenco.",
+ "getUserInfo": "Ottieni informazioni sull'utente",
+ "getVideos": "Ottieni video",
+ "getVideosForMonitor": "Ottieni video per Monitor",
+ "giraffe": "giraffa",
+ "h264_cuvid": "H.264 Cuvid",
+ "h264_mmal": "H.264 (Raspberry Pi)",
+ "h264_nvenc": "H.264 NVENC (NVIDIA HW Accel)",
+ "h264_omx": "H.264 OpenMax (Raspberry Pi)",
+ "h264_qsv": "H.264 (video di sincronizzazione rapida)",
+ "h264_vaapi": "H.264 VA-API (Intel HW Accel)",
+ "h265BrowserText1": "Se stai cercando di riprodurre un file H.265, potrebbe essere necessario scaricarlo e aprirlo in un'altra applicazione come VLC.",
+ "hair drier": "asciugacapelli",
+ "handbag": "borsetta",
+ "hevc_cuvid": "H.265 Cuvid",
+ "hevc_nvenc": "H.265 NVENC (NVIDIA HW Accel)",
+ "hevc_qsv": "H.265 (video di sincronizzazione rapida)",
+ "hevc_vaapi": "H.265 VA-API (Intel HW Accel)",
+ "hlsOptions": "Opzioni HLS",
+ "hlsOptionsInvalid": "Le opzioni HLS non sono valide",
+ "horse": "cavalla",
+ "hot dog": "hot dog",
+ "hour": "ora",
+ "hours": "ore",
+ "hwaccel": "Motore di accelerazione",
+ "hwaccel_device": "Dispositivo HWACCEL",
+ "hwaccel_vcodec": "Decodificatore video",
+ "in": "nella",
+ "in Days": "in giorni",
+ "in seconds": "in secondi",
+ "keyId": "ID chiave",
+ "keyboard": "tastiera del computer",
+ "kite": "aquilone",
+ "knife": "coltello",
+ "laptop": "computer portatile",
+ "lastLogin": "Ultimo accesso",
+ "libmp3lame": "Libmp3lamy",
+ "libopus": "libopus",
+ "libvorbis (Default)": "libvorbis (impostazione predefinita)",
+ "libvpx (Default)": "libvpx (impostazione predefinita)",
+ "libvpx-vp9": "libvpx-vp9",
+ "libx264": "libx264",
+ "libx264 (Default)": "libx264 (impostazione predefinita)",
+ "libx265": "libx265",
+ "liveGridDescription": "Live Grid è il display a flusso multiplo per Shinobi. Questo metodo di visualizzazione è progettato principalmente per il desktop.",
+ "loginHandleUnbound": "Il login è stato non collegato da questo account.",
+ "microwave": "microonde",
+ "migrateText1": " Tipo di input Non è stato possibile analizzare. Si prega di impostarlo manualmente.",
+ "minute": "minuta",
+ "minutes": "minuti",
+ "mjpeg_cuvid": "Mjpeg cuvid",
+ "modifyVideoText1": "Il metodo non esiste. Controlla per assicurarsi che l'ultimo valore dell'URL non sia vuoto.",
+ "monSavedButNotCopied": "Il monitor è stato salvato ma non copiato su nessun altro monitor.",
+ "monitorConfigFinderDescription": "Questo strumento ti aiuterà a cercare configurazioni per le telecamere pubblicate dalla comunità. Tutti ospitati su shinobihub . Puoi pubblicare anche il tuo, aiuterebbe davvero la comunità :)",
+ "monitorEditFailedMaxReached": "Il tuo account ha raggiunto il numero massimo di telecamere che possono essere create. Parla con un amministratore se desideri che questo sia cambiato.",
+ "monitorEditText1": "Dati non validi, verificare che questa sia una stringa di importazione valida.",
+ "monitorEditText2": "Stringa di dettagli non validi. Controlla per vedere che è una stringa JSON e non un oggetto normale che viene superato.",
+ "monitorGetText1": "Richiesta incompleta, rimuovere l'ultima barra nell'URL o mettere un valore accettabile.",
+ "monitorStateNotEnoughChanges": "È necessario apportare una modifica alla configurazione del monitor prima di tentare di aggiungerla a un preimpostazione.",
+ "monitorStatesError": "Monitorare l'errore dei preset",
+ "months": "mesi",
+ "motorcycle": "motociclo",
+ "mouse": "topo",
+ "mpeg2_mmal": "MPEG-2 (Raspberry Pi)",
+ "mpeg2_qsv": "MPEG2 (video di sincronizzazione rapida)",
+ "mpeg4_cuvid": "MPEG4 CUVID",
+ "mpeg4_mmal": "MPEG-4 (Raspberry Pi)",
+ "noLoginTokensAdded": "Non ci sono accessi alternativi associati a questo account.",
+ "noSpecialCharacters": "Niente spazi o caratteri speciali.",
+ "noTriggerText": "Se il movimento non è stato rilevato dopo il periodo di timeout, riceverai una notifica di discordia.",
+ "noUndoForAction": "Non puoi annullare questa azione.",
+ "notActivatedText": "La tua installazione ha fallito l'attivazione.",
+ "notEnoughFramesText1": "Non abbastanza cornici per la compilazione.",
+ "notPermitted1": "Questa azione non è consentita dall'amministratore del tuo account. \"",
+ "on": "Su",
+ "on Error": "su errore",
+ "on Event": "su evento",
+ "onvifdeviceManagerGlobalTip": "ONVIF consente di modificare le impostazioni interne della fotocamera. OnVif è in qualche modo un termine ombrello, può significare molte cose sfortunatamente. In questo caso potresti vedere un'opzione in questo strumento, ma potrebbe non essere modificabile. Ciò è di solito dovuto al fatto che il fornitore della fotocamera non ha aggiunto questo metodo o si è deviato dall'uso previsto. In questi casi dovrai inserire la configurazione della fotocamera tramite il metodo prescritto del fornitore della fotocamera, questo sta generalmente aprendo l'indirizzo IP della fotocamera nel browser Web.",
+ "onvifdeviceSavedFoundErrorText": "Alcune impostazioni potrebbero essere tornate a un valore precedente. È possibile che l'opzione modificata non sia disponibile con questa fotocamera tramite ONVIF.",
+ "onvifdeviceSavedText": "Le impostazioni interne della fotocamera sono state salvate. Potrebbe essere necessario riavviare la fotocamera per avere queste modifiche.",
+ "openImagesDownloadConfirm": "Sei sicuro di voler iniziare a scaricare immagini e scatole di delimitazione (matrici preimpostate) da OpenImages?",
+ "openImagesDownloadConfirmStop": "Sei sicuro di voler smettere di allenarsi?",
+ "opencl": "OpenCl",
+ "opencvCascadesText": "Se non vedi nulla qui, basta scaricare questo pacchetto di cascades . Rilasciali in plugins/opencv/cascades Quindi premi aggiorna .",
+ "orange": "arancia",
+ "oven": "forno",
+ "p2pServerNotSelectedText": "Seleziona un server dall'elenco e premi Salva. Aspetta 10 secondi, quindi prova ad aprire la dashboard in remoto.",
+ "p2pSettingsText1": "Dovrai aggiornare questa pagina per applicare le modifiche.",
+ "parking meter": "parchimetro",
+ "performanceOptimizeText1": "La fotocamera fornisce dati sul flusso H.264. È possibile impostare il tipo di streaming su HLS, Poseidon e Codec video da copiare.",
+ "person": "persona",
+ "pizza": "Pizza",
+ "possibleInternalError": "Possibile errore interno",
+ "postDataBroken": "Controlla il formato del JSON. Assicurarsi che sia stringa e definito in \"dati\"",
+ "potted plant": "pianta in vaso",
+ "powerVideoEventLimit": "Hai impostato un limite di evento elevato. Sei sicuro di voler fare questa richiesta?",
+ "privateKey": "Chiave privata",
+ "qsv": "QSV",
+ "rebootingCamera": "Riavvia la fotocamera",
+ "refrigerator": "frigorifero",
+ "remote": "a distanza",
+ "restartRequired": "Il riavvio di Shinobi Core è necessario affinché le modifiche abbiano effetto.",
+ "sandwich": "Sandwich",
+ "scissors": "forbici",
+ "separateByCommasOrRange": "Separato da virgole o un intervallo",
+ "setMaxStorageAmountText": "Dovresti impostare l'importo di archiviazione massimo nelle impostazioni dell'account situate a sinistra. Trova l'opzione nella sezione Profilo. L'impostazione predefinita è di 10 GB.",
+ "sheep": "pecora",
+ "sink": "lavello",
+ "sizePurgeLockedText": "Il blocco di spurgo delle dimensioni (elimina OVERMAX) sembra non essere sbloccato. Sblocco ora ...",
+ "skateboard": "skateboard",
+ "skipPingText1": "Prova a impostare \"salta ping\" su sì.",
+ "skis": "sci",
+ "snowboard": "snowboard",
+ "sorryNo": "Scusa no",
+ "sorryNothingWasFound": "Scusa, niente è stato trovato.",
+ "spoon": "cucchiaio",
+ "sports ball": "palla sportiva",
+ "startUpText0": "Controllo del disco usato ..",
+ "startUpText1": "Disco di controllo completato utilizzato.",
+ "startUpText2": "Tutti gli utenti controllati, attendi di chiudere i file aperti e rimuovere i file oltre il limite dell'utente",
+ "startUpText3": "in attesa di dare video incompiuti controlla un po 'di tempo. 3 secondi.",
+ "startUpText4": "Monitor di inizio ... per favore aspetta ...",
+ "startUpText5": "Shinobi è pronto.",
+ "startUpText6": "Video orfani trovati e inseriti",
+ "stop sign": "segnale di stop",
+ "subAccountManager": "Manager di sotto-conto",
+ "substreamConnectionText": "È possibile lasciare il dettaglio della connessione così com'è se si desidera utilizzare le informazioni di connessione principali impostate sopra.",
+ "substreamOutputText": "Qui puoi impostare la configurazione del flusso su richiesta. Scopri latenza dei tipi di flusso qui. ",
+ "substreamText": "Questo è un metodo su richiesta per visualizzare il live streaming. Puoi farlo in modo che il processo di visualizzazione sia disponibile solo quando qualcuno sta guardando o da utilizzare per passare da una risoluzione bassa e alta.",
+ "suitcase": "valigia",
+ "superAdminText": "\"Super.json\" non esiste. Si prega di rinominare \"Super.sample.json\" in \"Super.json\".",
+ "superAdminTitle": "Shinobi: Super Admin",
+ "surfboard": "tavola da surf",
+ "teddy bear": "orsacchiotto di peluche",
+ "tennis racket": "racchetta da tennis",
+ "tie": "legare",
+ "toaster": "tostapane",
+ "toilet": "gabinetto",
+ "tokenNotUserBound": "Questa handle di accesso non è collegata a un utente su questo server!",
+ "tokenNotUserBoundPt2": "Digita le credenziali, quindi utilizza il pulsante di accesso Google per collegarsi rapidamente.",
+ "toothbrush": "spazzolino",
+ "total": "totale",
+ "traffic light": "semaforo",
+ "train": "treno",
+ "truck": "camion",
+ "tv": "tv",
+ "umbrella": "ombrello",
+ "undoAllUnsaveChanges": "Sei sicuro di volerlo fare? Ciò annulla tutti i cambiamenti non salvati.",
+ "unexpectedExitText": "Le informazioni su questa uscita saranno trovate prima di questo registro. Inoltre, ecco il comando FFMPEG che è stato utilizzato quando il processo si è schiantato.",
+ "updateCamerasInternalSettings": "Aggiornare le impostazioni interne della fotocamera?",
+ "updateKeyText1": "\"UpdateKey\" manca da \"Conf.json\", non può fare aggiornamenti in questo modo fino a quando non lo aggiungi.",
+ "updateKeyText2": "\"UpdateKey\" non è corretto.",
+ "updateNotice1": "L'aggiornamento di Shinobi significa sovrascrivere i file. Se hai modificato i file da solo, dovresti aggiornare shinobi manualmente. Le configurazioni e i file video non saranno modificati.",
+ "useSubStreamOnlyWhenWatching": "Solo quando si guarda, usa il substream",
+ "vaapi": "Vaapi (Va-api)",
+ "vase": "vaso",
+ "vda": "VDA (Apple VDA Hardware Acceleration)",
+ "vdpau": "vdpau",
+ "videoBuildingText1": "Il video sta attualmente costruendo. Controlla di nuovo più tardi.",
+ "videotoolbox": "Videotoolbox",
+ "vp8_cuvid": "VP8 NVENC (NVIDIA HW Accel)",
+ "vp8_qsv": "VP8 (video di sincronizzazione rapida)",
+ "vp9_cuvid": "VP9 NVENC (NVIDIA HW Accel)",
+ "wannaReset": "Vuoi ripristinare?",
+ "willTriggerAnEvent": "attiverà un evento",
+ "wine glass": "bicchiere di vino",
+ "years": "anni",
+ "zebra": "zebra"
+}
\ No newline at end of file
diff --git a/languages/ru.json b/languages/ru.json
index bd7866ec..8b171527 100644
--- a/languages/ru.json
+++ b/languages/ru.json
@@ -1,497 +1,1650 @@
{
- "\"No Motion\" Detector": "Детектор \"Нет Движения\"",
- "# of Allow MJPEG Clients": "Кол-во разрешённых MJPEG клиентов 0 для неограниченного",
- "180 Degrees": "180 градусов",
- "2-Factor Authentication": "Двухфакторная аутентификация",
- "90 Clockwise": "90 по часовой стрелке",
- "90 Clockwise and Vertical Flip": "90 по часовой стрелке и отражение по вертикали",
- "90 Counter Clockwise and Vertical Flip (default)": "90 против часовой стрелки и отражение по вертикали (по умолчанию)",
- "API": "API-интерфейс",
- "API Key Added": "API-ключ добавлен",
- "API Key Deleted": "API-ключ удалён",
- "API Keys": "Ключи API",
- "APIKeyAddedText": "Ключ добалвен. Вы уже можете использовать этот ключ.",
- "APIKeyDeletedText": "Ключ был удален. Он больше не будет работать.",
- "ASC": "Возрастание",
- "Account Info": "Информация об аккаунте",
- "AccountEditText1": "Невозможно изменить. Обновить страницу, если проблема повторяется.",
- "Accounts": "Аккаунты",
- "Action for Selected": "Действие для выбранных",
- "Add": "Добавить",
- "Add Monitor": "Добавить Монитор",
- "Add New": "Добавить Новый",
- "Admin": "Админ",
- "Advanced": "Расширенный",
- "Again": "Снова",
- "All Monitors": "Все Мониторы",
- "All Monitors and Privileges": "Все мониторы и привилегии",
- "All Warnings": "Все Предупреждения",
- "Allow Next Command": "Разрешить следующую команды в минутах",
- "Allow Next Email": "Разрешить следующую электронную почту в минутах",
- "Allow Next Trigger": "Разрешить следующий триггер в миллисекундах",
- "Allowed IPs": "Допустимые IP-адреса",
- "Analyzation Duration": "Продолжительность анализа",
- "Archive": "Архив",
- "Audio Codec": "Аудио-кодек",
- "Authenticate": "Аутентифицировать",
- "Auto": "Авто",
- "Autosave": "Автосохранение",
- "Base64 over Websocket": "Base64 через websocket",
- "Bottom Left": "Внизу Слева",
- "Bottom Right": "Внизу Справа",
- "Browser Console Log": "Журнал консоли браузера",
- "CPU": "ЦП",
- "CPU indicator will not work. Continuing...": "Индикатор ЦП не будет работать. Продолжение...",
- "CSS": "CSS Стиль вашей приборной панели.",
- "Calendar": "Календарь",
- "Camera Password": "Пароль камеры",
- "Camera Username": "Логин камеры",
- "Camera is not recording": "Камера не записывает",
- "CameraNotRecordingText": "Параметры могут быть несовместимы. Проверьте кодировку потока. Перезапуск...",
- "Can Control Monitors": "Может управлять мониторами",
- "Can Delete Videos": "Может удалять видео",
- "Can Delete Videos and Events": "Может удалять видео и события",
- "Can Edit Monitor": "Может редактировать мониторы",
- "Can Get Logs": "Может получать журнал",
- "Can Get Monitors": "Может получать мониторы",
- "Can View Monitor": "Может смотреть мониторы",
- "Can View Snapshots": "Может смотрет снимки",
- "Can View Streams": "Может смотреть потоки",
- "Can View Videos": "Может смотреть видео",
- "Can View Videos and Events": "Может смотреть видео и события",
- "Can't Connect": "Не удалось подключиться",
- "Center": "Центр URL-Адрес",
- "Chat on Discord": "Чат в Discord",
- "Check": "Проверить",
- "Check Signal Interval": "Интервал проверки сигнала в минутах",
- "Check for Motion First": "Сначала проверить движение",
- "Close": "Закрыть",
- "Closed": "Закрытые",
- "Command": "Команда",
- "Command on Trigger": "Команда на триггер",
- "Complete Stream URL": "Полный URL потока",
- "Confirm": "Подтвердить",
- "Connected": "Подключен",
- "Connection Type": "Тип Подключения",
- "Control": "Управление",
- "Control Error": "Ошибка управления",
- "ControlErrorText1": "Управление не включено",
- "Controllable": "Управляемая",
- "Country of Plates": "Страна номерного знака",
- "Counts of Motion": "Количество движений",
- "Current": "Текущий",
- "Currently viewing": "Сейчас просматривают",
- "Custom": "Пользовательский",
- "Custom Base URL": "Базовый пользовательский URL оставьте пустым, чтобы использовать URL узла",
- "DB Lost.. Retrying..": "Подключение к БД утеряно. Повтор...",
- "DESC": "Убывание",
- "Dashboard": "Приборной панели",
- "Dashboard Language": "Язык Приборной Панели",
- "Dashcam": "Видеорегистратор",
- "Dashcam (Streamer v2)": "Видеорегистратор (Streamer v2)",
- "Date Range": "Диапазон дат",
- "Debug": "Отладка",
- "Default": "По умолчанию",
- "Delete": "Удалить",
- "Delete Filter": "Удалить фильтр",
- "Delete Matches": "Удалить совпадения",
- "Delete Monitor": "Удалить монитор",
- "Delete Motionless Video": "Удалить видео без движения",
- "Delete Motionless Videos (Record)": "Удалить видео без движения (запись)",
- "Delete Selected Videos": "Удалить выбранные видео",
- "Delete Video": "Удалить видео",
- "Delete selected": "Удалить выбранные",
- "DeleteMonitorText": "Вы действительно хотите удалить этот монитор? Это действие нельзя отменить. Файлы для этого ID останутся в файловой системе. Если позже вы создадите монитор с таким-же ID, события станут видны в личном кабинете.",
- "DeleteSelectedVideosMsg": "Вы действительно хотите удалить эти видео? Это действие нельзя отменить.",
- "DeleteVideoMsg": "Вы действительно хотите удалить это видео? Это действие нельзя отменить.",
- "Deleted": "Удален",
- "Detect Objects": "Обнаруживать объекты ниже",
- "Detector": "Детектор",
- "Detector Flags": "Флаги детектора",
- "Detector Rate": "Скорость детектора (кадры в секунду)",
- "DetectorText": "Когда показана ширина и высота области, установите её размер в 640х480 или меньше. Это позволит оптимизировать скорость чтения кадров.
", - "Disable Night Vision": "Отключение ночного видения URL", - "Disable Nightvision": "Отключить ночное видение", - "Disabled": "Отключен", - "Documentation": "Документация", - "Don't show this anymore": "Больше не показывать это", - "Double Quote Directory": "Двойные кавычки в имени каталога у некоторых каталогов есть пробелы в имени. Из-за этого может произойти сбой некоторых камер.", - "Down": "Движение вниз URL", - "Down Stop": "Остановка движения вниз URL", - "Download": "Скачать", - "EU": "Европа", - "Edit": "Редактировать", - "Email": "Электронная почта", - "Email Details": "Детали на электронную почту", - "Email on No Motion": "Письмо при отсутствии движения", - "Email on Trigger": "Письмо при срабатывании триггера письма будут приходить на почту, утсановленную как логин для аккаунта", - "Enable Night Vision": "Включение ночного видения URL", - "Enable Nightvision": "Включение ночного видения", - "Enabled": "Включен", - "End": "Конец", - "End Time": "Время окончания", - "Ended": "Закончился", - "Enlarge": "Увеличить", - "Enter this code to proceed": "Введите этот код, чтобы продолжить", - "Equal to": "Равно", - "Error Connecting": "Ошибка подключения", - "Event": "Событие", - "Event Limit": "Ограничение событий", - "EventText1": "Сработало событие движения в", - "EventText2": "Не удалось отправить изображение по электронной почте - файл не был доступен", - "Events": "События", - "Example": "Пример", - "Execute Command": "Выполнить команду", - "Executed": "Выполнен", - "Export": "Экспорт", - "FFmpegCantStart": "Не удалось запустить FFmpeg", - "FFmpegCantStartText": "Ядро записи для этой камеры не удалось запустить. Это может быть вызвано ошибочной настройкой камеры. Если, помимо этой, есть другие записи в журнале, пожалуйста, пришлите их в Issues на GitLab.", - "FFmpegTip": "FFprobe - это простой анализатор мултимедийных потоков. Вы можете использовать его для вывода всевозможной информации о потоке, включая продолжительность, частоту кадров, размер кадра и т. д.", - "FFprobe": "Зонд FFprobe", - "FactorAuthText1": "Код будет активен только в течение 15 минут. Если вы войдёте снова, таймер будет сброшен до 15 минут с тем же кодом.", - "Fatal": "Фатальная", - "Fatal Maximum Reached": "Фатальный максимум достигнут, камера останавливается.", - "FatalMaximumReachedText": "Ошибка JPEG была фатальной.", - "Feed-in Image Height": "Входная высота изображения", - "Feed-in Image Width": "Входная ширина изображения", - "Fields cannot be empty": "Поля не могут быть пустыми", - "File Not Exist": "Файл не существует", - "File Not Found": "Файл не найден", - "File Type": "Тип файла", - "FileNotExistText": "Невозможно сохранить несуществущий файл. Что-то пошло не так.", - "Filename": "Имя файла", - "Filesize": "Размер файла", - "Filter ID": "ID фильтра", - "Filter Matches": "Совпадение фильтра", - "Filter Name": "Название фильтра", - "FilterMatchesText1": "Этот фильтр удовлетворил условия.", - "FilterMatchesText2": "видео найдено.", - "Filters": "Фильтры", - "Filters Updated": "Фильтры обновлены", - "FiltersUpdatedText": "Ваши изменения были сохранены и применены.", - "Find Where": "Найти где", - "Fix": "Исправить", - "Fix Video": "Исправить видео", - "FixVideoMsg": "Вы действительно хотите исправить это видео? Это действие нельзя отменить.", - "Font Path": "Путь к шрифту", - "Font Size": "Размер шрифта", - "Force Port": "Порт (принудительно)", - "Found Devices": "Найдено устройств", - "Frame Rate": "Частота кадров (кадров / с)", - "Full Frame Detection": "Обнаружение в полном Кадре", - "Fullscreen": "Полноэкранный", - "Greater Than": "Больше", - "Greater Than or Equal to": "Больше или равно", - "Group Key": "Ключ группы", - "Group Name": "Название группы", - "Grouping": "Группировка ", - "H.264 / H.265 / H.265+": "Формат H.264 / H.265 / H.265+", - "HLS (.m3u8)": "Формат HLS (.m3u8)", - "HLS (includes Audio)": "Формат HLS со звуком", - "HLS Audio Encoder": "HLS Аудио кодировщик", - "HLS List Size": "Размер списка HLS", - "HLS Preset": "Предустановка HLS", - "HLS Segment Length": "Длина сегмента HLS в секундах", - "HLS Video Encoder": "HLS Видео кодировщик", - "HTTP": "Протокол http", - "HTTPS": "Протоколу https", - "Height": "Высота", - "Help": "Помощь", - "Hide List": "Скрыть список", - "Hide Notes": "Скрыть заметки", - "Host": "Хост", - "Hotswap Modes (Watch-Only)": "Режим горячей замены (только просмотр)", - "How to Record": "Как записать", - "IP Address": "IP-адрес", - "Identity": "Идентификация", - "IdentityText1": "Здесь определяется, как система будет идентифийироват данные этого потока. После сохранения вы не сможете изменить ID монитора. Если хотите, можете сделать ID монитора понятным для людей перед прожолжением.", - "IdentityText2": "Вы можете сделать дубликат монитора, изменив ID монитора и нажав \"Сохранить\". Запрещено использовать ID существующего монитора - в противном случае его информация в БД будет перезаписана.", - "Idle": "Простаивающий", - "Image Height": "Высота изображения", - "Image Location": "Путь к изображению абсолютный путь к файлу, или оставьте поле пустым для использования глобального", - "Image Position": "Положение изображения", - "Image Width": "Ширина изображения", - "Import": "Импорт", - "Import Monitor Configuration": "Импорт конфигурации монитора", - "ImportMonitorConfigurationText": "Это перезапишет любые не сохранённые изменения. Импортированные настройки будут применены только при нажатии Сохранить.", - "In": "В", - "Incorrect Settings Chosen": "Выбраны неправильные установки", - "Indifference": "Безразличие", - "Input": "Ввод", - "Input Flags": "Флаги ввода", - "Input Type": "Тип ввода", - "InputText1": "Этот раздел описывает, как использовать поток. Для оптимальной производительности, измените внутренние настройки камеры. Найдите соответствующие настройки и установите их, как показано ниже. Чтобы обнаружить вашу камеру, вы можете использовать встроенный ONVIF-сканер. Некоторые ONVIF камеры требуют использования специального инструмента управления для изменения внутренних настроек. Если Вы не можете обнаружить ваши, попробуйте ONVIF Device Manager для Windows.", - "InputText2": "Этот раздел описывает основной метод вещания и его настройки. Трансляция будет отображена на приборной доске. Если вы выберете HLS, JPEG, или MJPEG, то вы сможете просматривать поток в других программах.
Использование JPEG-потока по сути выключит основную трансляцию и приведёт к использованию корзины снимков для получения кадров.
", - "Streamer": "Вещатель", - "Streams": "Потоки", - "Superuser": "Суперпользователь", - "Switch on for Still Image": "Переключиться на неподвижное изображение", - "TCP": "Протокол TCP", - "Text Box Color": "Цвет текстового поля", - "Text Color": "Цвет текста", - "Time-lapse": "Интервальная съёмка", - "Time-lapse Tool": "Инструменты интервальной съёмки", - "Timeout": "Таймаут", - "Timeout Reset on Next Motion": "Сброс таймаута при следующем движении", - "Toggle Sidebar": "Переключение боковой панели", - "Top Left": "Верхний Левый", - "Top Right": "Верхний Правый", - "Trigger Record": "Триггер записи", - "Trigger Successful": "Триггер успешен", - "UDP": "Протокол UDP", - "URL": "URL-адрес", - "URL Stop Timeout": "Таймаут URL остановки вызвать URL после X миллисекунд", - "US": "США", - "Unable to Launch": "Не удалось запустить", - "UnabletoLaunchText": "Пожалуйста, сначала сохраните монитор а затем используйте редактор областей.", - "Up": "Вверх URL", - "Up Stop": "Остановить вверх URL", - "Username": "Логин", - "Value": "Значение", - "Video": "Видео", - "Video Codec": "Видео-кодек", - "Video Filter": "Видео-фильтр", - "Video Finished": "Видео окончено", - "Video Length (minutes) and Motion Count per video": "Продолжительность видео (минут) и кол-во движений на видео", - "Video Record Rate": "Скорость видео (кадров в секунду)", - "Video Status": "Статус видео", - "Video and Time Span (Minutes)": "Видео и отрезок времени (минут)", - "Videos": "Видео", - "Videos List": "Список видео", - "Watch": "Смотреть", - "Watch Only": "Только просмотр", - "WebDAV": "Протокол WebDAV", - "WebM (libvpx)": "WebM (libvpx)", - "Webdav Error": "Ошибка WebDAV", - "WebdavErrorText": "Не удается сохранить. Попробуйте сначала создать каталог.", - "Webhook": "Вебхук", - "Webhook URL": "URL вебхука", - "Width": "Ширина", - "Yes": "Да", - "Zoom In": "Увеличение URL", - "Zoom In Stop": "Остановка увеличениеURL", - "Zoom Out": "Уменьшить URL", - "Zoom Out Stop": "Остановка уменьшения URL-Адрес", - "a day": "день", - "a few seconds": "несколько секунд", - "a minute": "минуту", - "a month": "месяц", - "a year": "год", - "aac": "ААС", - "aac (Default)": "ААС (по умолчанию)", - "ac3": "АС3", - "ago": "назад", - "an hour": "час", - "blankPassword": "Оставьте поле пустым, чтобы сохранить текущий пароль", - "calendar": "календарь", - "clientStreamFailedattemptingReconnect": "Проверка потока на стороне клиента провалилась, попытка переподключения", - "confirmDeleteFilter": "Вы действительно хотите удалить этот фильтр? Это действие нельзя отменить.", - "copy": "копия", - "days": "дней", - "dropBoxSuccess": "Успех! Файлы сохранены в Dropbox.", - "for Global Access": "для глобального доступа", - "hours": "часов", - "in": "в", - "libmp3lame": "libmp3lame", - "libopus": "libopus", - "libvorbis (Default)": "libvorbis (по умолчанию)", - "libvpx (Default)": "libvpx (по умолчанию)", - "libvpx-vp9": "libvpx-vp9", - "libx264": "libx264", - "libx264 (Default)": "libx264 (по умолчанию)", - "libx265": "libx265", - "minutes": "минут", - "modifyVideoText1": "Метод не существует. Убкдитесь, что последнее значение URL не пустое.", - "monitorEditFailedMaxReached": "Достигнт предел создаваемых камер для вашего аккаунта. Свяжитесь с вашим администратором для изменения предела.", - "monitorEditText1": "Неверные данные - убедитесь, что введена корректная строка для импорта", - "monitorEditText2": "Неверная строка деталей. Убедитесь, что введа корректная JSON-строка вместо обычного объекта.", - "monitorGetText1": "неполный запрос, уберите слэш в конце URL или введите приемлимое значение.", - "months": "месяцев", - "noSpecialCharacters": "Без пробелов или специальных символов.", - "on": "на", - "on Error": "в случае ошибки", - "startUpText0": "Проверка используемого диска...", - "startUpText1": "Проверка используемого диска окончена.", - "startUpText2": "все пользователи проверены, ожидаю закрытия открытых файлов и удаления файлов, превышающих ограничения пользователей", - "startUpText3": "даю немного времени на завершение проверки видео. 3 секунды.", - "startUpText4": "Запуск мониторов... Пожалуйста, ожидайте...", - "startUpText5": "Shinobi готов.", - "startUpText6": "Несвязанные видео найдены и добавлены", - "superAdminText": "Файл \"super.json\" не существует. Переименуйте \"super.sample.json\" в \"super.json\".", - "superAdminTitle": "Shinobi: Супер Админ", - "total": "итого", - "updateKeyText1": "\"updateKey\" отсутствует в \"conf.json\", невозможно провести обновление, пока он не будет добавлен.", - "updateKeyText2": "\"updateKey\" неверный.", - "years": "лет" -} + "\"No Motion\" Detector": "Детектор \"Нет Движения\"", + "# of Allow MJPEG Clients": "Кол-во разрешённых MJPEG клиентов 0 для неограниченного", + "'Already Installing...'": "'Уже установка ...'", + "180 Degrees": "180 градусов", + "2-Factor Authentication": "Двухфакторная аутентификация", + "90 Clockwise": "90 по часовой стрелке", + "90 Clockwise and Vertical Flip": "90 по часовой стрелке и отражение по вертикали", + "90 Counter Clockwise and Vertical Flip (default)": "90 против часовой стрелки и отражение по вертикали (по умолчанию)", + "AND": "И", + "API": "API-интерфейс", + "API Key": "API -ключ", + "API Key Action Failed": "Действие ключа API не удалось", + "API Key Added": "API-ключ добавлен", + "API Key Deleted": "API-ключ удалён", + "API Keys": "Ключи API", + "APIKeyAddedText": "Ключ добалвен. Вы уже можете использовать этот ключ.", + "APIKeyDeletedText": "Ключ был удален. Он больше не будет работать.", + "ASC": "Возрастание", + "Accelerator": "Ускоритель", + "Account Info": "Информация об аккаунте", + "Account Information": "Информация об учетной записи", + "Account Privileges": "Привилегии учетной записи", + "Account Save": "Сохранить учетную запись", + "Account Settings": "Настройки учетной записи", + "AccountEditText1": "Невозможно изменить. Обновить страницу, если проблема повторяется.", + "Accounts": "Аккаунты", + "Action for Selected": "Действие для выбранных", + "Activated": "Активирован", + "Active Monitors": "Активные мониторы", + "Add": "Добавить", + "Add All": "Добавить все", + "Add Camera": "Добавить камеру", + "Add Cameras": "Добавить камеры", + "Add Channel": "Добавить канал", + "Add Input Feed": "Добавьте входной подачу", + "Add Map": "Добавить карту", + "Add Monitor": "Добавить Монитор", + "Add New": "Добавить Новый", + "AddToPreset": "Добавить в предустановку", + "Additional Inputs": "Дополнительные входы", + "Admin": "Админ", + "Advanced": "Расширенный", + "After": "После", + "Again": "Снова", + "Age": "Возраст", + "Alert Sound": "Предупреждающий звук", + "Alert Sound Delay": "Предупреждение задержки звука", + "All Logs": "Все журналы", + "All Monitors": "Все Мониторы", + "All Monitors and Privileges": "Все мониторы и привилегии", + "All Privileges": "Все привилегии", + "All Warnings": "Все Предупреждения", + "All streams in first feed": "Все потоки в первом корме", + "Allow API Trigger": "Разрешить API триггер", + "Allow Next Alert": "Разрешить следующее предупреждение", + "Allow Next Command": "Разрешить следующую команды в минутах", + "Allow Next Discord Alert": "Разрешить следующее предупреждение о раздорахКогда показана ширина и высота области, установите её размер в 640х480 или меньше. Это позволит оптимизировать скорость чтения кадров.
", + "Died": "Умер", + "Digest Authentication": "Аутентификация переваривания", + "Disable": "Запрещать", + "Disable Night Vision": "Отключение ночного видения URL", + "Disable Nightvision": "Отключить ночное видение", + "Disabled": "Отключен", + "Discord": "Раздор", + "Discord Alert on Trigger": "Предупреждение о раздорах на триггер", + "Discord Bot": "Discord Bot", + "Discord on No Motion": "Раздор на \"Нет движения\"", + "DiscordErrorText": "Отправка на раздор вызвала ошибку", + "DiscordFailedText": "Отправка в Discord не удалась", + "DiscordLoggedIn": "Discord Bot завершил подлинность", + "DiscordNotEnabledText": "Discord Bot не включен, включите его в настройки учетной записи.", + "Documentation": "Документация", + "Does Not Contain": "Не содержит", + "Don't Show for 1 Week": "Не показывай 1 неделю", + "Don't show this anymore": "Больше не показывать это", + "DontAddToPreset": "Не добавляй к предустановке", + "Double Quote Directory": "Двойные кавычки в имени каталога у некоторых каталогов есть пробелы в имени. Из-за этого может произойти сбой некоторых камер.", + "Down": "Движение вниз URL", + "Down Stop": "Остановка движения вниз URL", + "Download": "Скачать", + "Download Bandwidth": "Скачать полосу пропускания", + "Downloading Videos": "Загрузка видео", + "Downloading...": "Загрузка ...", + "Duplicate": "Дублировать", + "EU": "Европа", + "Easy Remote Access (P2P)": "Легкий удаленный доступ (P2P)", + "Edit": "Редактировать", + "Edit Configuration": "Редактировать конфигурацию", + "Edit Selected": "Редактировать выбранный", + "Edited Schedule Configuration": "Отредактированная конфигурация расписания", + "Edited State Configuration": "Отредактированная конфигурация состояния", + "Email": "Электронная почта", + "Email Details": "Детали на электронную почту", + "Email address is in use.": "Адрес электронной почты используется.", + "Email and Password fields cannot be empty": "Поля по электронной почте и пароля не могут быть пустыми", + "Email on No Motion": "Письмо при отсутствии движения", + "Email on Trigger": "Письмо при срабатывании триггера письма будут приходить на почту, утсановленную как логин для аккаунта", + "Emotion": "Эмоция", + "Emotion Average": "Эмоции средний", + "Enable": "Давать возможность", + "Enable Night Vision": "Включение ночного видения URL", + "Enable Nightvision": "Включение ночного видения", + "Enabled": "Включен", + "Encoding": "Кодирование", + "EncodingInterval": "I-Frame", + "End": "Конец", + "End Time": "Время окончания", + "Ended": "Закончился", + "Endpoint": "Конечная точка", + "Endpoint Address": "Адрес конечной точки", + "Enlarge": "Увеличить", + "Enter at least one IP": "Введите хотя бы один IP", + "Enter this code to proceed": "Введите этот код, чтобы продолжить", + "Equal to": "Равно", + "Error Connecting": "Ошибка подключения", + "Error While Decoding": "Ошибка при расшифровании", + "ErrorWhileDecodingText": "Ваше оборудование может иметь нестабильное соединение с сетью. Проверьте свои сетевые подключения.", + "ErrorWhileDecodingTextAudio": "Ваша камера предоставляет разбитые данные. Попробуйте отключить звук во внутренних настройках камеры.", + "Event": "Событие", + "Event Counts": "Количество событий", + "Event Filter Error": "Ошибка фильтра событий", + "Event Filters": "Фильтры событий", + "Event Limit": "Ограничение событий", + "Event Occurred": "Событие произошло", + "Event Rules": "Правила события", + "Event Webhook Error": "СОБЫТАЯ Ошибка Webhook", + "EventText1": "Сработало событие движения в", + "EventText2": "Не удалось отправить изображение по электронной почте - файл не был доступен", + "Events": "События", + "Events Found": "События найдены", + "Example": "Пример", + "Execute Command": "Выполнить команду", + "Executed": "Выполнен", + "Export": "Экспорт", + "Export Selected Videos": "Экспорт выбранных видео", + "Export Video": "Экспортное видео", + "ExportSelectedVideosMsg": "Вы хотите экспортировать эти видео? Это может занять некоторое время, чтобы застегнуть и загрузить.", + "Exposure": "Экспозиция", + "FFmpegCantStart": "Не удалось запустить FFmpeg", + "FFmpegCantStartText": "Ядро записи для этой камеры не удалось запустить. Это может быть вызвано ошибочной настройкой камеры. Если, помимо этой, есть другие записи в журнале, пожалуйста, пришлите их в Issues на GitLab.", + "FFmpegTip": "FFprobe - это простой анализатор мултимедийных потоков. Вы можете использовать его для вывода всевозможной информации о потоке, включая продолжительность, частоту кадров, размер кадра и т. д.", + "FFprobe": "Зонд FFprobe", + "FLV": "FLV", + "FLV Stream Type": "FLV -поток тип", + "FactorAuthText1": "Код будет активен только в течение 15 минут. Если вы войдёте снова, таймер будет сброшен до 15 минут с тем же кодом.", + "Fatal": "Фатальная", + "Fatal Maximum Reached": "Фатальный максимум достигнут, камера останавливается.", + "FatalMaximumReachedText": "Ошибка JPEG была фатальной.", + "February": "Февраль", + "Feed-in Image Height": "Входная высота изображения", + "Feed-in Image Width": "Входная ширина изображения", + "Female": "женский", + "Field Missing Value": "Поле отсутствующее значение", + "Fields cannot be empty": "Поля не могут быть пустыми", + "File Delete Error": "Файл Удалить ошибку", + "File Not Exist": "Файл не существует", + "File Not Found": "Файл не найден", + "File Not Found in Database": "Файл не найден в базе данных", + "File Not Found in Filesystem": "Файл не найден в файловой системе", + "File Type": "Тип файла", + "FileBin Share": "Filebin Share", + "FileNotExistText": "Невозможно сохранить несуществущий файл. Что-то пошло не так.", + "Filename": "Имя файла", + "Filesize": "Размер файла", + "Filter ID": "ID фильтра", + "Filter Matches": "Совпадение фильтра", + "Filter Name": "Название фильтра", + "Filter for Objects only": "Фильтр только для объектов", + "FilterMatchesText1": "Этот фильтр удовлетворил условия.", + "FilterMatchesText2": "видео найдено.", + "Filters": "Фильтры", + "Filters Updated": "Фильтры обновлены", + "FiltersUpdatedText": "Ваши изменения были сохранены и применены.", + "Find Where": "Найти где", + "First stream in feed": "Первый поток в подаче", + "Fix": "Исправить", + "Fix Video": "Исправить видео", + "FixVideoMsg": "Вы действительно хотите исправить это видео? Это действие нельзя отменить.", + "Flush PM2 Logs": "Flush PM2 журналы", + "Font Path": "Путь к шрифту", + "Font Size": "Размер шрифта", + "For Group": "Для группы", + "Force Monitors Per Row": "Силовые мониторы в ряду", + "Force Port": "Порт (принудительно)", + "Form Data Not Found": "Форма данных не найдены", + "Found Devices": "Найдено устройств", + "Frame Rate": "Частота кадров (кадров / с)", + "FrameRateLimit": "Предел скорости кадров (FPS)", + "Frames": "Рамы", + "Friday": "Пятница", + "Frigate": "Фрегат", + "Full Frame Detection": "Обнаружение в полном Кадре", + "Full Stream URL": "Полный поток URL", + "Full URL Path": "Полный URL -пути", + "Fullscreen": "Полноэкранный", + "Gateway": "Ворота", + "Gender": "Пол", + "Generate Subtitles": "Генерировать субтитры", + "Get Code": "Получить код", + "Get Logs to Client": "Получить журналы к клиенту", + "Global Detector Settings": "Глобальные настройки детектора", + "Google Drive": "Гугл Диск", + "GovLength": "Губернатор", + "Greater Than": "Больше", + "Greater Than or Equal to": "Больше или равно", + "Grid": "Сетка", + "Group Key": "Ключ группы", + "Group Key is in use.": "Групповой ключ используется.", + "Group Name": "Название группы", + "Grouping": "Группировка ", + "H.264 / H.265 / H.265+": "Формат H.264 / H.265 / H.265+", + "H264Profile": "H264 Профиль", + "HEVC (H.265)": "HEVC (H.265)", + "HLS (.m3u8)": "Формат HLS (.m3u8)", + "HLS (includes Audio)": "Формат HLS со звуком", + "HLS Audio Encoder": "HLS Аудио кодировщик", + "HLS List Size": "Размер списка HLS", + "HLS Live Start Index": "HLS Live Start Index", + "HLS Preset": "Предустановка HLS", + "HLS Segment Length": "Длина сегмента HLS в секундах", + "HLS Start Number": "HLS Start Number", + "HLS Video Encoder": "HLS Видео кодировщик", + "HTTP": "Протокол http", + "HTTPS": "Протоколу https", + "Hardware Accelerated": "Аппаратное ускорение", + "Height": "Высота", + "Help": "Помощь", + "Hide List": "Скрыть список", + "Hide Notes": "Скрыть заметки", + "Home": "Дом", + "Host": "Хост", + "Host Type": "Тип хоста", + "Hostname": "Имя хоста", + "Hotswap Modes (Watch-Only)": "Режим горячей замены (только просмотр)", + "How to Record": "Как записать", + "IP Address": "IP-адрес", + "Identity": "Идентификация", + "IdentityText1": "Здесь определяется, как система будет идентифийироват данные этого потока. После сохранения вы не сможете изменить ID монитора. Если хотите, можете сделать ID монитора понятным для людей перед прожолжением.", + "IdentityText2": "Вы можете сделать дубликат монитора, изменив ID монитора и нажав \"Сохранить\". Запрещено использовать ID существующего монитора - в противном случае его информация в БД будет перезаписана.", + "Idle": "Простаивающий", + "Image Height": "Высота изображения", + "Image Location": "Путь к изображению абсолютный путь к файлу, или оставьте поле пустым для использования глобального", + "Image Position": "Положение изображения", + "Image Width": "Ширина изображения", + "Imaging": "Визуализация", + "Import": "Импорт", + "Import Monitor Configuration": "Импорт конфигурации монитора", + "ImportMonitorConfigurationText": "Это перезапишет любые не сохранённые изменения. Импортированные настройки будут применены только при нажатии Сохранить.", + "ImportMultiMonitorConfigurationText": "Это будет перебросить любые мониторы с идентификаторами, существующими в файле импорта.", + "In": "В", + "Incorrect Settings Chosen": "Выбраны неправильные установки", + "Indifference": "Безразличие", + "Info": "Информация", + "Information": "Информация", + "Input": "Ввод", + "Input Feed": "Входной податок", + "Input Feeds Selected": "Входной подачу выбран", + "Input Flags": "Флаги ввода", + "Input Map": "Входная карта", + "Input Selector": "Селектор ввода", + "Input Settings": "Настройки ввода", + "Input Type": "Тип ввода", + "InputText1": "Этот раздел описывает, как использовать поток. Для оптимальной производительности, измените внутренние настройки камеры. Найдите соответствующие настройки и установите их, как показано ниже. Чтобы обнаружить вашу камеру, вы можете использовать встроенный ONVIF-сканер. Некоторые ONVIF камеры требуют использования специального инструмента управления для изменения внутренних настроек. Если Вы не можете обнаружить ваши, попробуйте ONVIF Device Manager для Windows.", + "InputText2": "Этот раздел описывает основной метод вещания и его настройки. Трансляция будет отображена на приборной доске. Если вы выберете HLS, JPEG, или MJPEG, то вы сможете просматривать поток в других программах.
Использование JPEG-потока по сути выключит основную трансляцию и приведёт к использованию корзины снимков для получения кадров.
", + "Streamed Logs": "Потоковые журналы", + "Streamer": "Вещатель", + "Streams": "Потоки", + "Sub-Accounts": "Суб-аккаунт", + "Subdivision": "Подразделение", + "Substream": "Подстег", + "Substream Process": "Процесс подстежки", + "SubstreamNotConfigured": "Подстег не настроен. Откройте настройки монитора и настройте его.", + "Subtitle": "Подзаголовок", + "Success": "Успех", + "Sunday": "Воскресенье", + "Superuser": "Суперпользователь", + "Superuser Logs": "Журналы суперпользователя", + "Switch on for Still Image": "Переключиться на неподвижное изображение", + "System": "Система", + "System Level": "Системный уровень", + "TCP": "Протокол TCP", + "TV Channel": "Телевизионный канал", + "TV Channel Group": "Группа телеканалов", + "TV Channel ID": "Телеканал идентификатор канала", + "Telegram": "Телеграмма", + "Text Box Color": "Цвет текстового поля", + "Text Color": "Цвет текста", + "Text criteria unsupported for Object Count tests, Ignoring Conditional": "Текстовые критерии, не поддерживаемые для тестов подсчета объектов, игнорируя условные", + "Themes": "Темы", + "There are no monitors that you can view with this account.": "Нет мониторов, которые вы можете просмотреть с помощью этой учетной записи.", + "Threads": "Потоки", + "Thumbnail": "Миниатюра", + "Thursday": "Четверг", + "Time": "Время", + "Time Created": "Время создано", + "Time Left": "Время вышло", + "Time Occurred": "Время произошло", + "Time-lapse": "Интервальная съёмка", + "Time-lapse Tool": "Инструменты интервальной съёмки", + "TimeZone": "Часовой пояс", + "Timelapse": "Промежуток времени", + "Timelapse Frames Share": "Рамовые рамки с временем делится", + "Timelapse Watermark": "Timelapse Watermark", + "Timeout": "Таймаут", + "Timeout Reset on Next Event": "Тайм -аут сброс на следующем мероприятии", + "Timeout Reset on Next Motion": "Сброс таймаута при следующем движении", + "Timezone": "Часовой пояс", + "Timezone Offset": "Смещение часового пояса", + "Title": "Заголовок", + "Today": "Сегодня", + "Toggle Sidebar": "Переключение боковой панели", + "Toggle Substream": "Переключатель подстежки", + "Token": "Токен", + "Top Left": "Верхний Левый", + "Top Right": "Верхний Правый", + "Traditional (Watch-Only, Includes Buffer)": "Традиционный (только для часа, включает буфер)", + "Traditional Recording": "Традиционная запись", + "Traditional Recording Flags": "Традиционные флаги записи", + "Train": "Тренироваться", + "TrainConfirm": "Вы уверены, что хотите начать тренировку? Это может занять более 12 часов с более чем 500 изображениями. Это будет потреблять большое количество ресурсов, таких как RAM и/или CPU.", + "TrainConfirmStop": "Вы уверены, что хотите перестать тренироваться?", + "Trainer Engine": "Двигатель тренера", + "Trigger Blocked": "Триггер заблокирован", + "Trigger Camera Groups": "Запустить группы камер", + "Trigger Event": "Триггер событие", + "Trigger Group to Record": "Запустить группу для записи", + "Trigger Record": "Триггер записи", + "Trigger Successful": "Триггер успешен", + "Trigger Threshold": "Триггерный порог", + "Tuesday": "Вторник", + "Turn Speed": "Поверните скорость", + "Type": "Тип", + "UDP": "Протокол UDP", + "URL": "URL-адрес", + "URL Stop Timeout": "Таймаут URL остановки вызвать URL после X миллисекунд", + "US": "США", + "UTCDateTime": "Дата", + "Unable to Launch": "Не удалось запустить", + "UnabletoLaunchText": "Пожалуйста, сначала сохраните монитор а затем используйте редактор областей.", + "Uncommon Objects": "Необычные объекты", + "Uniform": "Униформа", + "Unlink": "Неопределенность", + "Unlink Login": "Unlink Login?", + "Unlinked": "Не связанный", + "Up": "Вверх URL", + "Up Stop": "Остановить вверх URL", + "Update": "Обновлять", + "Update to Development": "Обновление до разработки", + "Update to Master": "Обновление для мастера", + "Upload Bandwidth": "Загрузить полосу пропускания", + "Upload File": "Загрузить файл", + "Uploaded Only": "Загружен только", + "Uploaders": "Загрузчики", + "Use Built-In": "Используйте встроенный", + "Use Camera Timestamps": "Используйте временные метки камеры", + "Use Global Amazon S3 Video Storage": "Используйте Global Amazon S3 Video Storage", + "Use Global Backblaze B2 Video Storage": "Используйте Global Backblaze B2 Video", + "Use Global Wasabi Hot Cloud Storage Video Storage": "Используйте глобальное хранение видео с хранением горячих облаков Wasabi", + "Use Global WebDAV Video Storage": "Используйте Global Webdav Video Storage", + "Use HTML5 Play Method": "Используйте метод игры HTML5", + "Use Max Storage Amount": "Используйте максимальную сумму хранения", + "Use Raw Snapshot": "Используйте необработанный снимок", + "Use Substream": "Используйте подстрим", + "Use coProcessor": "Используйте копроцессор", + "UseCount": "Использование", + "User Log": "Пользовательский журнал", + "User Not Found": "Пользователь не найден", + "Username": "Логин", + "VA-API": "Va-api", + "Value": "Значение", + "Video": "Видео", + "Video Bit Rate": "Видеобиточная скорость", + "Video Codec": "Видео-кодек", + "Video Configuration": "Видео конфигурация", + "Video Filter": "Видео-фильтр", + "Video Finished": "Видео окончено", + "Video Length (minutes) and Motion Count per video": "Продолжительность видео (минут) и кол-во движений на видео", + "Video Limit": "Видеоминку", + "Video Record Rate": "Скорость видео (кадров в секунду)", + "Video Set": "Видео набор", + "Video Share": "Видео обмена", + "Video Status": "Статус видео", + "Video and Time Span (Minutes)": "Видео и отрезок времени (минут)", + "Video stream only from first feed": "Видеопоток только от первого канала", + "Video streams only": "Только видеопотоки", + "Videos": "Видео", + "Videos List": "Список видео", + "Videos Merge": "Видео слияние", + "Viewing Server Stats": "Просмотр статистики сервера", + "Warning": "Предупреждение", + "Wasabi Hot Cloud Storage": "Wasabi Hot Cloud Storage", + "Wasabi Hot Cloud Storage Upload Error": "Ошибка загрузки хранения Hot Cloud Wasabi", + "Watch": "Смотреть", + "Watch Only": "Только просмотр", + "Watch-Only": "Только для часа", + "Watching": "Наблюдая", + "Web Page": "Страница интернета", + "WebDAV": "Протокол WebDAV", + "WebM (libvpx)": "Webm (libvpx)", + "Webdav Error": "Ошибка WebDAV", + "WebdavErrorText": "Не удается сохранить. Попробуйте сначала создать каталог.", + "WebdavErrorTextCreatingDir": "Не может создать каталог.", + "WebdavErrorTextTryCreatingDir": "Не может спасти. Попытка создать каталог.", + "Webhook": "Вебхук", + "Webhook URL": "URL вебхука", + "Websocket": "WebSocket", + "Websocket Connected": "WebSocket подключен", + "Websocket Disconnected": "WebSocket отключен", + "Wednesday": "Среда", + "Welcome": "Добро пожаловать!", + "When Detector is Off": "Когда детектор отключен", + "When Detector is On": "Когда детектор включен", + "WhiteBalance": "Баланс белого", + "WideDynamicRange": "Широкий динамический диапазон", + "Width": "Ширина", + "X Point": "X точка", + "Y Point": "Y точка", + "Yes": "Да", + "Zip and Download": "Zip и скачать", + "Zipping Videos": "Застегивание видео", + "Zones": "Зоны", + "Zoom In": "Увеличение URL", + "Zoom In Stop": "Остановка увеличениеURL", + "Zoom Out": "Уменьшить URL", + "Zoom Out Stop": "Остановка уменьшения URL-Адрес", + "a day": "день", + "a few seconds": "несколько секунд", + "a minute": "минуту", + "a month": "месяц", + "a year": "год", + "aac": "ААС", + "aac (Default)": "ААС (по умолчанию)", + "ac3": "АС3", + "accountActionFailed": "Действие аккаунта не удалось", + "accountAdded": "Учетная запись добавлена", + "accountAddedText": "Учетная запись была добавлена.", + "accountDeleted": "Удалена", + "accountDeletedText": "Учетная запись была удалена.", + "accountId": "Идентификатор учетной записи", + "accountSettingsDescription": "Управляйте своим профилем и установите такие параметры, как Max Hore Summ и Max -количество дней для хранения видео.", + "accountSettingsError": "Ошибка настроек учетной записи", + "activatedText": "Ваша установка была активирована.", + "ago": "назад", + "airplane": "самолет", + "alreadyLinked": "Уже связан с учетной записью", + "an hour": "час", + "apple": "яблоко", + "applicationKey": "Ключ приложения", + "aws_accessKeyId": "Доступ к ключе", + "aws_secretAccessKey": "Секретный ключ доступа", + "backpack": "рюкзак", + "banana": "банан", + "baseball bat": "бейсбольная бита", + "baseball glove": "бейсбольная перчатка", + "bear": "нести", + "bed": "кровать", + "bench": "скамья", + "bicycle": "велосипед", + "bindDN": "binddn", + "bird": "птица", + "blankPassword": "Оставьте поле пустым, чтобы сохранить текущий пароль", + "boat": "лодка", + "book": "книга", + "bottle": "бутылка", + "bowl": "чаша", + "broccoli": "брокколи", + "bus": "автобус", + "cake": "торт", + "calendar": "календарь", + "car": "автомобиль", + "carrot": "морковь", + "cat": "Кот", + "cell phone": "сотовый телефон", + "chair": "стул", + "clientStreamFailedattemptingReconnect": "Проверка потока на стороне клиента провалилась, попытка переподключения", + "clock": "Часы", + "coProcess Crashed for Monitor": "Coprocess разбился для монитора", + "coProcess Unexpected Exit": "Coprocess неожиданный выход", + "coProcessor": "Коконсор", + "coProcessor Started": "Коконсор начал", + "coProcessor Stopped": "Коконсор остановился", + "coProcessorTextStarted": "Копроцессор начал только для результатов процессора.", + "coProcessorTextStopped": "Коконсор закончился.", + "codecMismatchText1": "Ваша камера предоставляет потоковые данные H.265 (HEVC), и вы используете копию в качестве видеокодека для раздела потока. Ваш поток от Shinobi может не отображаться на устройствах, которые не могут использовать этот кодек. Мобильное приложение Shinobi может просмотреть эти потоки.", + "codecMismatchText2": "Вы выбранный видеокодек не применим. Ваша камера предоставляет данные потока MJPEG, и вы используете копию в качестве видеокодека для раздела потока. Изменил тип потока на MJPEG.", + "codecMismatchText3": "Вы выбранный видеокодек не применим. Ваша камера предоставляет данные потока MJPEG, и вы используете копию в качестве видеокодека для раздела записи. Изменил видеокодек на Libx264.", + "confirmDeleteFilter": "Вы действительно хотите удалить этот фильтр? Это действие нельзя отменить.", + "contactAdmin": "Свяжитесь с сопровождающей установкой Shinobi.", + "copy": "копия", + "couch": "диван", + "cow": "корова", + "cuda": "cuda (nvidia nvenc)", + "cup": "кружка", + "cuvid": "cuvid (nvidia nvenc)", + "days": "дней", + "deleteApiKey": "Удалить ключ API", + "deleteApiKeyText": "Вы хотите удалить этот ключ API? Вы не можете восстановить это.", + "deleteMonitorStateText1": "Вы хотите удалить этот монитор состояний? Связанные конфигурации монитора не могут быть восстановлены.", + "deleteMonitorStateText2": "Вы хотите удалить предустановку этого монитора?", + "deleteScheduleText": "Вы хотите удалить это расписание? Связанные предустановки не будут изменены.", + "deleteSubAccount": "Удалить суб-аккунт", + "deleteSubAccountText": "Вы хотите удалить этот суб-аккунт? Вы не можете восстановить это.", + "dining table": "обеденный стол", + "dog": "собака", + "donut": "пончик", + "drm": "Обмен объектом DRM", + "dropBoxSuccess": "Успех! Файлы сохранены в Dropbox.", + "dxva2": "dxva2 (видео DirectX, Windows)", + "elephant": "слон", + "eventFilterActionText": "Это действия, которые происходят из условий фильтра, которые преуспели. «Оригинальный выбор» относится к опции, которую вы выбрали в настройках вашего монитора.", + "eventFilterErrorBrackets": "У вас есть не более чем количество кронштейнов. Их игнорируют.", + "eventFiltersDescription": "Настройка фильтров для того, когда происходят события.", + "failedLoginText1": "Вы не смогли войти слишком много раз. Вы должны подождать 15 минут, прежде чем попробовать снова.", + "failedLoginText2": "Пожалуйста, проверьте свои учетные данные для входа.", + "fieldMissingValueText1": "Ваша камера предоставляет данные потока MJPEG. Вам нужно установить скорость захвата монитора. Шиноби попытается обнаружить его и заполнить его автоматически.", + "fieldTextAccelerator": "Аппаратное ускорение (HWACCEL) для декодирования потоков.", + "fieldTextAcodec": "Аудиокодек для записи.", + "fieldTextActionsCommand": "Вы можете использовать это, чтобы запустить сценарий по команде.", + "fieldTextActionsHalt": "Заставьте мероприятие ничего не делать, как будто этого никогда не было.", + "fieldTextActionsIndifference": "Измените минимальное безразличие, необходимое для события.", + "fieldTextActionsRecord": "Используйте традиционную запись, HotSwap или удалить неподвижно с помощью их в настоящее время установленных параметров в разделе «Настройки глобальных обнаружений».", + "fieldTextAduration": "Укажите, сколько микросекунд анализируется, чтобы исследовать вход. Установите 100000, если вы используете RTSP и имеете проблемы с потоком.", + "fieldTextAudioAlert": "Звук, когда происходит событие.", + "fieldTextAudioDelay": "Задержка до следующего раза, когда событие может начать предупреждение. Измеряется в секундах.", + "fieldTextAudioNote": "Звук, когда появляется информационный пузырь.", + "fieldTextAutoHost": "Полный URL.", + "fieldTextAutoHostEnable": "Поправьте отдельные части, необходимые для создания URL -адреса потока, или обеспечить полный URL и позволить Shinobi проанализировать его для вас.", + "fieldTextChannelHlsListSize": "Количество максимальных сегментов перед автоматическим удалением старых сегментов.", + "fieldTextChannelHlsTime": "Как долго должен быть каждый видео -сегмент, за считанные минуты. Каждый сегмент будет нарисован клиентом через файл M3U8. Более короткие сегменты занимают меньше места.", + "fieldTextChannelPresetStream": "Предустающий флаг для определенных видеокодеров. Если вы обнаружите, что ваша камера сбивается каждые несколько секунд: попробуйте оставить ее пустой.", + "fieldTextChannelStreamAcodec": "Аудиокодек для потоковой передачи.", + "fieldTextChannelStreamAcodecAac": "Используется для видео MP4.", + "fieldTextChannelStreamAcodecAc3": "Используется для видео MP4.", + "fieldTextChannelStreamAcodecAuto": "Пусть ffmpeg выберет.", + "fieldTextChannelStreamAcodecCopy": "Используется для видео MP4. Имеет очень низкое использование процессора, но некоторые аудиокодеки нуждаются в пользовательских флагах, таких как -strict 2 для AAC.",
+ "fieldTextChannelStreamAcodecLibmp3lame": "Используется для видео MP4.",
+ "fieldTextChannelStreamAcodecLibopus": "Используется для видео WebM.",
+ "fieldTextChannelStreamAcodecLibvorbis": "Используется для видео WebM.",
+ "fieldTextChannelStreamAcodecNoAudio": "Нет звука, это вариант, который должен быть установлен в некоторых частях света по юридическим причинам.",
+ "fieldTextChannelStreamFps": "Скорость, с которой кадры отображаются для клиентов, в кадрах в секунду. Имейте в виду, что нет дефолта. Это может привести к использованию высокой пропускной способности.",
+ "fieldTextChannelStreamQuality": "Низкое число означает более высокое качество. Более высокое число означает меньшее качество.",
+ "fieldTextChannelStreamRotate": "Измените угол просмотра видеопотока.",
+ "fieldTextChannelStreamScaleX": "Ширина изображения потока, которое выводит после обработки.",
+ "fieldTextChannelStreamScaleY": "Высота изображения потока, которое выводит после обработки.",
+ "fieldTextChannelStreamType": "Метод, который будет использовать для потребления видеопотока.",
+ "fieldTextChannelStreamTypeFLV": "Отправка FLV -кодированных кадров за WebSocket.",
+ "fieldTextChannelStreamTypeHLS(includesAudio)": "Аналогичный метод Facebook Live Streams. включает в себя Audio , если вход предоставляет его. Существует задержка около 4-6 секунд, потому что этот метод записывает сегменты, а затем подталкивает их к клиенту, а не толкает, пока он их создает.",
+ "fieldTextChannelStreamTypeMJPEG": "Стандартное движение JPEG Изображение. Нет аудио.",
+ "fieldTextChannelStreamTypePoseidon": "Посейдон построен на коде обработки MP4 Кевина Годелла. Он имитирует потоковой файл MP4, но используя данные живого потока. Включает аудио. Некоторые браузеры могут воспроизводить его как обычный файл MP4. Поток через HTTP или WebSocket.",
+ "fieldTextChannelStreamVcodec": "Видеодек для потоковой передачи.",
+ "fieldTextChannelStreamVcodecAuto": "Пусть ffmpeg выберет.",
+ "fieldTextChannelStreamVcodecCopy": "Используется для видео MP4. Имеет очень низкое использование процессора, но не может использовать видеофильтры, и файлы, может быть гигантским. Лучше всего настроить настройки MP4 на стороне камеры при использовании этой опции.",
+ "fieldTextChannelStreamVcodecLibx264": "Используется для видео MP4.",
+ "fieldTextChannelStreamVcodecLibx265": "Используется для видео MP4.",
+ "fieldTextChannelSvf": "Поместите видеофильтры FFMPEG в этом поле, чтобы повлиять на потоковую часть. Нет мест.",
+ "fieldTextControlInvertY": "Когда ваша камера установлена вверх ногами или использует инвертированные вертикальные элементы управления.",
+ "fieldTextCrf": "Низкое число означает более высокое качество. Более высокое число означает меньшее качество.",
+ "fieldTextCustDetect": "Пользовательские флаги, которые связываются с потоковым детектором, используют для анализа.",
+ "fieldTextCustDetectObject": "Пользовательские флаги, которые связываются с потоковым детектором, используют для анализа.",
+ "fieldTextCustInput": "Пользовательские флаги, которые связываются с вводом процесса FFMPEG.",
+ "fieldTextCustRecord": "Пользовательские флаги, которые связываются с записи процесса FFMPEG.",
+ "fieldTextCustSipRecord": "Пользовательские флаги, которые связываются с выводом, от которых Siphon на основе событий.",
+ "fieldTextCustSnap": "Пользовательские флаги, которые связываются с снимками.",
+ "fieldTextCustStream": "Пользовательские флаги, которые связываются с потоком (вид клиента) процесса FFMPEG.",
+ "fieldTextCustomOutput": "Добавьте пользовательский выход, такой как кадры JPEG, или отправьте данные прямо на другой сервер.",
+ "fieldTextCutoff": "В считанные минуты. Когда отрезать и запустить новый видеофайл.",
+ "fieldTextDays": "Количество дней для хранения видео перед очисткой.",
+ "fieldTextDetailSubstreamInputRtspTransportAuto": "Пусть FFMPEG решит. Обычно он сначала попробует UDP.",
+ "fieldTextDetailSubstreamInputRtspTransportTCP": "Установите это, если UDP начнет давать нежелательные результаты.",
+ "fieldTextDetailSubstreamInputRtspTransportUDP": "FFMPEG пытается это сначала.",
+ "fieldTextDetailSubstreamOutputHlsListSize": "Количество максимальных сегментов перед автоматическим удалением старых сегментов.",
+ "fieldTextDetailSubstreamOutputHlsTime": "Как долго должен быть каждый видео -сегмент, за считанные минуты. Каждый сегмент будет нарисован клиентом через файл M3U8. Более короткие сегменты занимают меньше места.",
+ "fieldTextDetailSubstreamOutputPresetStream": "Предустающий флаг для определенных видеокодеров. Если вы обнаружите, что ваша камера сбивается каждые несколько секунд: попробуйте оставить ее пустой.",
+ "fieldTextDetailSubstreamOutputStreamAcodec": "Аудиокодек для потоковой передачи.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAac": "Используется для видео MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAc3": "Используется для видео MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAuto": "Пусть ffmpeg выберет.",
+ "fieldTextDetailSubstreamOutputStreamAcodecCopy": "Используется для видео MP4. Имеет очень низкое использование процессора, но некоторые аудиокодеки нуждаются в пользовательских флагах, таких как -strict 2 для AAC.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibmp3lame": "Используется для видео MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibopus": "Используется для видео WebM.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibvorbis": "Используется для видео WebM.",
+ "fieldTextDetailSubstreamOutputStreamAcodecNoAudio": "Нет звука, это вариант, который должен быть установлен в некоторых частях света по юридическим причинам.",
+ "fieldTextDetailSubstreamOutputStreamFps": "Скорость, с которой кадры отображаются для клиентов, в кадрах в секунду. Имейте в виду, что нет дефолта. Это может привести к использованию высокой пропускной способности.",
+ "fieldTextDetailSubstreamOutputStreamQuality": "Низкое число означает более высокое качество. Более высокое число означает меньшее качество.",
+ "fieldTextDetailSubstreamOutputStreamRotate": "Измените угол просмотра видеопотока.",
+ "fieldTextDetailSubstreamOutputStreamScaleX": "Ширина изображения потока, которое выводит после обработки.",
+ "fieldTextDetailSubstreamOutputStreamScaleY": "Высота изображения потока, которое выводит после обработки.",
+ "fieldTextDetailSubstreamOutputStreamType": "Метод, который будет использовать для потребления видеопотока.",
+ "fieldTextDetailSubstreamOutputStreamVcodec": "Видеодек для потоковой передачи.",
+ "fieldTextDetailSubstreamOutputStreamVcodecAuto": "Пусть ffmpeg выберет.",
+ "fieldTextDetailSubstreamOutputStreamVcodecCopy": "Используется для видео MP4. Имеет очень низкое использование процессора, но не может использовать видеофильтры, и файлы, может быть гигантским. Лучше всего настроить настройки MP4 на стороне камеры при использовании этой опции.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx264": "Используется для видео MP4.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx265": "Используется для видео MP4.",
+ "fieldTextDetailSubstreamOutputSvf": "Поместите видеофильтры FFMPEG в этом поле, чтобы повлиять на потоковую часть. Нет мест.",
+ "fieldTextDetector": "Это добавит еще один вывод в команде FFMPEG для детектора движения.",
+ "fieldTextDetectorAudio": "Проверьте, произошло ли звук в Decible Certiain. Декабкое чтение может быть не точным для реального измерения.",
+ "fieldTextDetectorBufferHlsListSize": "Количество максимальных сегментов перед автоматическим удалением старых сегментов.",
+ "fieldTextDetectorBufferHlsTime": "Как долго должен быть каждый видео -сегмент, в считанные секунды. Каждый сегмент будет нарисован клиентом через файл M3U8. Более короткие сегменты занимают меньше места.",
+ "fieldTextDetectorColorThreshold": "Количество различий, допускаемое в пикселе до того, как оно будет считаться движением.",
+ "fieldTextDetectorCommand": "Команда, которая будет работать. Это эквивалент запуска команды оболочки из терминала.",
+ "fieldTextDetectorCommandTimeout": "Это значение является таймером, позволяющим следующему запуску вашего сценария. Это значение в минутах.",
+ "fieldTextDetectorFps": "Сколько кадров в секунду отправит детектору движения; 2 - дефолт.",
+ "fieldTextDetectorFrame": "Это будет читать весь кадр для различий в пикселях. Это то же самое, что создание области, которая охватывает весь экран.",
+ "fieldTextDetectorHttpApi": "Вы хотите разрешить HTTP -триггеры на эту камеру?",
+ "fieldTextDetectorLisencePlate": "Включить распознавание номерного знака. Плагин OpenALPR имеет это всегда включено.",
+ "fieldTextDetectorLisencePlateCountry": "Выберите тип пластин для распознавания. Только мы и ЕС поддерживаются в это время.",
+ "fieldTextDetectorLockTimeout": "Блокировка, когда разрешен следующий триггер, чтобы избежать перегрузки базы данных и приема клиентов. Измеряется в миллисекундах.",
+ "fieldTextDetectorMaxSensitivity": "Рейтинг достоверности движения должен быть ниже, чем это значение, которое следует рассматривать как триггер. Оставьте пустым без максимума. Этот вариант был ранее назван «максимальным безразличием».",
+ "fieldTextDetectorNoiseFilter": "Попытка отфильтровать зерно или повторное движение при определенном безразличие.",
+ "fieldTextDetectorNoiseFilterRange": "Количество различий, допускаемое в пикселе до того, как оно будет считаться движением.",
+ "fieldTextDetectorNotrigger": "Проверьте, произошло ли движение в интервале. Если произошло движение, чек будет сброшен.",
+ "fieldTextDetectorNotriggerCommand": "Команда, которая будет работать. Это эквивалент запуска команды оболочки из терминала.",
+ "fieldTextDetectorNotriggerCommandTimeout": "Это значение является таймером, позволяющим следующему запуску вашего сценария. Это значение в минутах.",
+ "fieldTextDetectorNotriggerDiscord": "Если движение не было обнаружено после периода времени ожидания, вы получите уведомление о Discord.",
+ "fieldTextDetectorNotriggerTimeout": "Тайм -аут рассчитывается за считанные минуты.",
+ "fieldTextDetectorNotriggerWebhook": "Отправьте запрос на получение URL -адреса с некоторыми значениями с мероприятия.",
+ "fieldTextDetectorObjCount": "Граф обнаруженных объектов.",
+ "fieldTextDetectorObjCountInRegion": "Считайте объекты только внутри регионов.",
+ "fieldTextDetectorPam": "Используйте детектор движения Кевина Годелла. Это встроено в Shinobi и не требует другой конфигурации для активации.",
+ "fieldTextDetectorPtzFollow": "Следуйте по самым большим обнаруженным объектам с PTZ? Требуется детектор объекта или матрицы, обеспечиваемые событиями.",
+ "fieldTextDetectorRecordMethod": "Есть несколько способов начать запись, когда происходит событие, например, движение. Традиционная запись является самой удобной.",
+ "fieldTextDetectorSave": "Сохранить события движения в SQL. Это позволит отображать движение над видео во время событий движения времени, произошедших в зрителе Power.",
+ "fieldTextDetectorScaleX": "Ширина обнаруженного изображения. Меньшие размеры принимают меньше процессора.",
+ "fieldTextDetectorScaleY": "Высота обнаруженного изображения. Меньшие размеры принимают меньше процессора.",
+ "fieldTextDetectorSendFrames": "Протолкните рамки к подключенному плагину, который будет проанализирован.",
+ "fieldTextDetectorSendFramesObject": "Протолкните рамки к подключенному плагину, который будет проанализирован.",
+ "fieldTextDetectorSendVideoLength": "В секундах. Продолжительность видео, которое отправляется в вашу службу уведомлений, например, по электронной почте или раздор.",
+ "fieldTextDetectorSensitivity": "Рейтинг достоверности движения должен превышать это значение, чтобы рассматриваться как триггер. Это число коррелирует непосредственно с рейтингом доверия, возвращенным детектором движения. Этот вариант был ранее назван «безразличием».",
+ "fieldTextDetectorThreshold": "Минимальное количество обнаружений, чтобы запустить событие движения. Обнаружения должны быть внутри детектора. Порог, разделенный на детектор FPS секунд. Например, если FPS детектора составляет 2, а порог триггера - 3, то в течение 1,5 секунды должны произойти три обнаружения, чтобы запустить событие движения. Этот порог - это область обнаружения.",
+ "fieldTextDetectorTimeout": "Продолжительность «Запись запуска» будет работать. Это читается за считанные минуты.",
+ "fieldTextDetectorTrigger": "Это закажет камеру для записи, если она установлена на «только для наблюдения», когда обнаруживается событие.",
+ "fieldTextDetectorUseDetectObject": "Создайте кадры для отправки в любой подключенный плагин.",
+ "fieldTextDetectorWebhook": "Отправьте запрос на получение URL -адреса с некоторыми значениями с мероприятия.",
+ "fieldTextDetectorWebhookTimeout": "Это значение является таймером, позволяющим следующему запуску вашего веб -крючка. Это значение в минутах.",
+ "fieldTextDir": "Расположение там, где будут сохранены записанные файлы. Вы можете настроить больше местоположений с переменной addStorage .",
+ "fieldTextEventDays": "Количество дней, чтобы сохранить события перед очисткой.",
+ "fieldTextEventMonPop": "Когда событие происходит всплывающе, поток монитора.",
+ "fieldTextEventRecordScaleX": "Ширина изображения записи на основе событий, которое выводит после обработки.",
+ "fieldTextEventRecordScaleY": "Высота изображения записи на основе событий, которое выводится после обработки.",
+ "fieldTextExt": "Тип файла для вашего записанного видеофайла.",
+ "fieldTextExtMP4": "Этот тип файла воспроизводится почти все современные веб -браузеры, которые включают мобильные. Размер файлов, как правило, больше, если вы не снижаете качество.",
+ "fieldTextExtWebM": "Маленький размер файлов, низкая совместимость с клиентами. Хорошо для загрузки на сайты, такие как YouTube.",
+ "fieldTextFactorAuth": "Включите вторичное требование для входа через один из включенных методов.",
+ "fieldTextFatalMax": "Количество раз повторно повторно для сетевого соединения между сервером и камерой перед установкой монитора для отключения. Нет десятичных десятиков. Установите на 0, чтобы повторно повторить навсегда.",
+ "fieldTextFps": "Скорость, в которой кадры записываются в файлы, кадры в секунду. Имейте в виду, что нет дефолта. Это может привести к большим файлам. Лучше всего установить эту камеру.",
+ "fieldTextHeight": "Высота изображения потока.",
+ "fieldTextHlsListSize": "Количество максимальных сегментов перед автоматическим удалением старых сегментов.",
+ "fieldTextHlsTime": "Как долго должен быть каждый видео -сегмент, за считанные минуты. Каждый сегмент будет нарисован клиентом через файл M3U8. Более короткие сегменты занимают меньше места.",
+ "fieldTextHost": "Адрес соединения",
+ "fieldTextHwaccel": "Декодирование двигателя",
+ "fieldTextHwaccelVcodec": "Декодирование двигателя",
+ "fieldTextInverseTrigger": "Запустить внешние указанные регионы. Не будет запускать с включенным полным кадром.",
+ "fieldTextIp": "Диапазон или холост",
+ "fieldTextIrCutFilterAuto": "IR Cut Filter автоматически активируется устройством.",
+ "fieldTextIrCutFilterOff": "Отключить IR Cut Fiter. Обычно ночной режим.",
+ "fieldTextIrCutFilterOn": "Включить IR Cut Fiter. Обычно дневной режим.",
+ "fieldTextIsOnvif": "Это камера, соответствующая Onvif?",
+ "fieldTextLang": "Основной язык текстовых элементов. Для получения полного перевода добавить свой язык в conf.json, например: \"Язык\": \"en_ca\", ",
+ "fieldTextLogDays": "Количество дней, чтобы сохранить журналы перед очисткой.",
+ "fieldTextLoglevel": "Объем данных для предоставления при выполнении работы.",
+ "fieldTextLoglevelAllWarnings": "Показать все предупреждения. Используйте это, если вы не можете узнать, что не так с вашей камерой.",
+ "fieldTextLoglevelFatal": "Отображать только смертельные ошибки.",
+ "fieldTextLoglevelOnError": "Отображать все важные ошибки. Примечание: это не всегда показывает важную информацию.",
+ "fieldTextLoglevelSilent": "Никто. Это заставит замолчать все регистрации.",
+ "fieldTextMail": "Вход для учетных записей. Адрес электронной почты основного владельца учетной записи получит уведомления.",
+ "fieldTextMapRtspTransportAuto": "Пусть FFMPEG решит. Обычно он сначала попробует UDP.",
+ "fieldTextMapRtspTransportTCP": "Установите это, если UDP начнет давать нежелательные результаты.",
+ "fieldTextMapRtspTransportUDP": "FFMPEG пытается это сначала.",
+ "fieldTextMaxKeepDays": "Количество дней для хранения видео до очистки этого монитора.",
+ "fieldTextMid": "Это непасный идентификатор для монитора. Вы можете дублировать монитор, дважды щелкнув идентификатор монитора и изменив его.",
+ "fieldTextMode": "Это основная задача монитора.",
+ "fieldTextModeDisabled": "Неактивный монитор, в этом режиме не будет создан процесс.",
+ "fieldTextModeRecord": "Непрерывная запись. Сегменты производятся каждые 15 минут по умолчанию.",
+ "fieldTextModeWatchOnly": "Монитор будет транслироваться только, никакой записи не будет происходить, если не будет упорядочено API или детектор.",
+ "fieldTextMpass": "Пароль для вашей камеры",
+ "fieldTextMuser": "Вход пользователя для вашей камеры",
+ "fieldTextName": "Это имя, читаемое на человеке для монитора.",
+ "fieldTextNotes": "Комментарии, которые вы хотите оставить для этой камеры.",
+ "fieldTextOnvifNonStandard": "Это нестандартная камера Onvif?",
+ "fieldTextOnvifPort": "ONVIF обычно запускается на порте 8000 . Это может быть 80 , а также в зависимости от модели вашей камеры.",
+ "fieldTextPass": "Оставьте пустой, чтобы сохранить тот же пароль во время изменения настроек.",
+ "fieldTextPasswordAgain": "Необходимо сопоставить поле пароля, если вы хотите изменить его.",
+ "fieldTextPath": "Путь к вашей камере",
+ "fieldTextPort": "Разделять запятыми или диапазоном",
+ "fieldTextPortForce": "Использование веб -порта по умолчанию может разрешить автоматический переключатель на другие порты для потоков, таких как RTSP.",
+ "fieldTextPresetRecord": "Предустающий флаг для определенных видеокодеров. Если вы обнаружите, что ваша камера сбивается каждые несколько секунд: попробуйте оставить ее пустой.",
+ "fieldTextPresetStream": "Предустающий флаг для определенных видеокодеров. Если вы обнаружите, что ваша камера сбивается каждые несколько секунд: попробуйте оставить ее пустой.",
+ "fieldTextProbesize": "Укажите, насколько велик, чтобы сделать анализ датчика для ввода. Установите 100000, если вы используете RTSP и имеете проблемы с потоком.",
+ "fieldTextProtocol": "Протокол, который будет использовать для потребления видеопотока.",
+ "fieldTextRecordScaleX": "Ширина изображения потока.",
+ "fieldTextRecordScaleY": "Высота изображения потока.",
+ "fieldTextRecordTimelapse": "Создайте временную шлюпу на основе JPEG.",
+ "fieldTextRecordTimelapseMp4": "Создайте файл MP4 в конце каждого дня для TimeLapse.",
+ "fieldTextRecordTimelapseWatermark": "Изображение, которое сжигается на кадрах записанного видео.",
+ "fieldTextRecordTimelapseWatermarkLocation": "Расположение изображения, которое будет использоваться в качестве водяного знака.",
+ "fieldTextRecordTimelapseWatermarkPosition": "Изображение, которое сжигается на кадрах записанного видео.",
+ "fieldTextRotate": "Измените угол записи видеопотока.",
+ "fieldTextRtmpKey": "Клавиша потока для входящих потоков на порту RTMP.",
+ "fieldTextRtspTransport": "Транспортный протокол, которую будет использовать ваша камера. TCP, как правило, лучший выбор.",
+ "fieldTextRtspTransportAuto": "Пусть FFMPEG решит. Обычно он сначала попробует UDP.",
+ "fieldTextRtspTransportHTTP": "Стандартный метод соединения.",
+ "fieldTextRtspTransportTCP": "Установите это, если UDP начнет давать нежелательные результаты.",
+ "fieldTextRtspTransportUDP": "FFMPEG пытается это сначала.",
+ "fieldTextSfps": "Укажите частоту кадров (FPS), в которой камера предоставляет свой поток.",
+ "fieldTextSignalCheck": "Как часто ваш клиент проверяет поток, чтобы увидеть, жив ли он. Это рассчитывается за считанные минуты.",
+ "fieldTextSignalCheckLog": "Это только для клиента. Он будет отображаться в потоке журнала, когда будут проходить проверки сигнала клиента.",
+ "fieldTextSize": "Количество дискового пространства, шиноби, позволит потреблять до очистки. Это значение читается в мегабайтах.",
+ "fieldTextSizeFilebinPercent": "Процент максимальной суммы хранилища, которую архив файлов может использовать.",
+ "fieldTextSizeTimelapsePercent": "Процент максимальной суммы хранения. Крамы с временем могут записаться.",
+ "fieldTextSizeVideoPercent": "Процент максимальной суммы хранения, к которым видео могут записать.",
+ "fieldTextSkipPing": "Выберите, требуется ли успешный пинг до начала процесса монитора.",
+ "fieldTextSnap": "Получите последний кадр в JPEG.",
+ "fieldTextSnapSecondsInward": "в секундах",
+ "fieldTextSqllog": "Используйте это с осторожностью, так как FFMPEG любит собирать излишние данные по временам, что может привести к множеству строк баз данных.",
+ "fieldTextSqllogNo": "Нет, это дефолт.",
+ "fieldTextSqllogYes": "Сделайте это, если у вас есть только повторяющиеся проблемы.",
+ "fieldTextStreamAcodec": "Аудиокодек для потоковой передачи.",
+ "fieldTextStreamAcodecAac": "Используется для видео MP4.",
+ "fieldTextStreamAcodecAc3": "Используется для видео MP4.",
+ "fieldTextStreamAcodecAuto": "Пусть ffmpeg выберет.",
+ "fieldTextStreamAcodecCopy": "Используется для видео MP4. Имеет очень низкое использование процессора, но некоторые аудиокодеки нуждаются в пользовательских флагах, таких как -strict 2 для AAC.",
+ "fieldTextStreamAcodecLibmp3lame": "Используется для видео MP4.",
+ "fieldTextStreamAcodecLibopus": "Используется для видео WebM.",
+ "fieldTextStreamAcodecLibvorbis": "Используется для видео WebM.",
+ "fieldTextStreamAcodecNoAudio": "Нет звука, это вариант, который должен быть установлен в некоторых частях света по юридическим причинам.",
+ "fieldTextStreamFlvType": "Это только для приборной панели Shinobi. Оба потоковых метода все еще активны и готовы к использованию.",
+ "fieldTextStreamFps": "Скорость, с которой кадры отображаются для клиентов, в кадрах в секунду. Имейте в виду, что нет дефолта. Это может привести к использованию высокой пропускной способности.",
+ "fieldTextStreamLoop": "Целью статического файла, чтобы поток файлов ведет себя как прямой поток.",
+ "fieldTextStreamQuality": "Низкое число означает более высокое качество. Более высокое число означает меньшее качество.",
+ "fieldTextStreamRotate": "Измените угол просмотра видеопотока.",
+ "fieldTextStreamScaleX": "Ширина изображения потока, которое выводит после обработки.",
+ "fieldTextStreamScaleY": "Высота изображения потока, которое выводит после обработки.",
+ "fieldTextStreamTimestamp": "Часы, которые сжигаются на кадрах видеопотока.",
+ "fieldTextStreamTimestampBoxColor": "Timstamp Facdrop Color.",
+ "fieldTextStreamTimestampColor": "Timstamp Text Color.",
+ "fieldTextStreamTimestampFont": "Файл шрифта, чтобы укрепить свою временную метку.",
+ "fieldTextStreamTimestampFontSize": "Размер шрифта в Pt.",
+ "fieldTextStreamTimestampX": "Горизтональное положение временной метки",
+ "fieldTextStreamTimestampY": "Вертикальное положение временной метки",
+ "fieldTextStreamType": "Метод, который будет использовать для потребления видеопотока.",
+ "fieldTextStreamTypeBase64OverWebsocket": "Отправка кодированных рам BASE64 через WebSocket. Это избегает кэширования, но нет звука.",
+ "fieldTextStreamTypeFLV": "Отправка FLV -кодированных кадров за WebSocket.",
+ "fieldTextStreamTypeHLS(includesAudio)": "Аналогичный метод Facebook Live Streams. включает в себя Audio , если вход предоставляет его. Существует задержка около 4-6 секунд, потому что этот метод записывает сегменты, а затем подталкивает их к клиенту, а не толкает, пока он их создает.",
+ "fieldTextStreamTypeMJPEG": "Стандартное движение JPEG Изображение. Нет аудио.",
+ "fieldTextStreamTypePoseidon": "Посейдон построен на коде обработки MP4 Кевина Годелла. Он имитирует потоковой файл MP4, но используя данные живого потока. Включает аудио. Некоторые браузеры могут воспроизводить его как обычный файл MP4. Поток через HTTP или WebSocket.",
+ "fieldTextStreamVcodec": "Видеодек для потоковой передачи.",
+ "fieldTextStreamVcodecAuto": "Пусть ffmpeg выберет.",
+ "fieldTextStreamVcodecCopy": "Используется для видео MP4. Имеет очень низкое использование процессора, но не может использовать видеофильтры, и файлы, может быть гигантским. Лучше всего настроить настройки MP4 на стороне камеры при использовании этой опции.",
+ "fieldTextStreamVcodecLibx264": "Используется для видео MP4.",
+ "fieldTextStreamVcodecLibx265": "Используется для видео MP4.",
+ "fieldTextStreamVf": "Поместите видеофильтры FFMPEG в этом поле, чтобы повлиять на потоковую часть. Нет мест.",
+ "fieldTextStreamWatermark": "Изображение, которое сжигается на кадрах видеопотока.",
+ "fieldTextStreamWatermarkLocation": "Расположение изображения, которое будет использоваться в качестве водяного знака.",
+ "fieldTextStreamWatermarkPosition": "Изображение, которое сжигается на кадрах видеопотока.",
+ "fieldTextTimestamp": "Часы, которые сжигаются на кадрах записанного видео.",
+ "fieldTextTimestampBoxColor": "Timstamp Facdrop Color.",
+ "fieldTextTimestampColor": "Timstamp Text Color.",
+ "fieldTextTimestampFont": "Файл шрифта, чтобы укрепить свою временную метку.",
+ "fieldTextTimestampFontSize": "Размер шрифта в Pt.",
+ "fieldTextTimestampX": "Горизтональное положение временной метки",
+ "fieldTextTimestampY": "Вертикальное положение временной метки",
+ "fieldTextTvChannel": "Этот монитор будет иметь включенные функции телевизионного канала. Вы сможете просмотреть его в списке телеканалов.",
+ "fieldTextTvChannelGroupTitle": "Пользовательская группа для канала.",
+ "fieldTextTvChannelId": "Пользовательский идентификатор для канала.",
+ "fieldTextType": "Метод, который будет использовать для потребления видеопотока.",
+ "fieldTextTypeDashcam(StreamerV2)": "WebSocket Webm на основе P2P Stream.",
+ "fieldTextTypeH.264/H.265/H.265+": "Чтение высококачественной видео -стрелки, которая иногда включает в себя аудио.",
+ "fieldTextTypeHLS(.m3u8)": "Чтение высококачественной видео -стрелки, которая иногда включает в себя аудио.",
+ "fieldTextTypeJPEG": "Чтение снимков из URL и создание потока и/или видео от них.",
+ "fieldTextTypeLocal": "Чтение карт захвата, веб -камеры или интегрированные камеры.",
+ "fieldTextTypeMJPEG": "Подобно JPEG, за исключением того, что обработка кадров выполняется FFMPEG, а не Shinobi.",
+ "fieldTextTypeMPEG4(.mp4/.ts)": "Статический файл. Читать с более низкой скоростью и не следует использовать для реального живого потока.",
+ "fieldTextTypeMxPEG": "MOBOTIX MJPEG Stream",
+ "fieldTextTypeRTMP": "Научитесь подключаться здесь: статья: Как толкать потоки через RTMP к Shinobi ",
+ "fieldTextTypeShinobiStreamer": "WebSocket JPEG на основе P2P Stream.",
+ "fieldTextVcodec": "Видеодек для записи.",
+ "fieldTextVf": "Поместите видеофильтры FFMPEG в этом поле, чтобы повлиять на часть записи. Нет мест.",
+ "fieldTextWallClockTimestampIgnore": "Основыте все входящие данные камеры во время камеры вместо времени на сервере.",
+ "fieldTextWatchdogReset": "Если в записи триггера есть перекрытие.",
+ "fieldTextWatermark": "Изображение, которое сжигается на кадрах записанного видео.",
+ "fieldTextWatermarkLocation": "Расположение изображения, которое будет использоваться в качестве водяного знака.",
+ "fieldTextWatermarkPosition": "Изображение, которое сжигается на кадрах записанного видео.",
+ "fieldTextWidth": "Ширина изображения потока.",
+ "fire hydrant": "пожарный гидрант",
+ "flv": "FLV",
+ "for Global Access": "для глобального доступа",
+ "fork": "вилка",
+ "frisbee": "Фрисби",
+ "getAMonitor": "Получите монитор",
+ "getATvChannel": "Получить телевизионные каналы для монитора",
+ "getATvChannelText": "Получите доступные потоки H.264 в одиночном мониторе в списке воспроизведения .m3u8.",
+ "getAllMonitors": "Получите все мониторы",
+ "getAllTvChannels": "Получите все телеканалы",
+ "getAllTvChannelsText": "Получите все доступные потоки H.264 в плейлисте .m3u8. Включите опцию TV Channel в настройках вашего монитора, чтобы увидеть их потоки в этом списке.",
+ "getUserInfo": "Получите информацию пользователя",
+ "getVideos": "Получите видео",
+ "getVideosForMonitor": "Получите видео для монитора",
+ "giraffe": "жирафа",
+ "h264_cuvid": "H.264 Cuvid",
+ "h264_mmal": "H.264 (Raspberry Pi)",
+ "h264_nvenc": "H.264 NVENC (NVIDIA HW ACCEL)",
+ "h264_omx": "H.264 OpenMax (Raspberry Pi)",
+ "h264_qsv": "H.264 (Quick Sync Video)",
+ "h264_vaapi": "H.264 VA-API (Intel HW Accel)",
+ "h265BrowserText1": "Если вы пытаетесь воспроизводить файл H.265, вам может потребоваться загрузить его и открыть его в другом приложении, таком как VLC.",
+ "hair drier": "фен",
+ "handbag": "сумочка",
+ "hevc_cuvid": "H.265 Cuvid",
+ "hevc_nvenc": "H.265 NVENC (NVIDIA HW ACCEL)",
+ "hevc_qsv": "H.265 (Quick Sync Video)",
+ "hevc_vaapi": "H.265 VA-API (Intel HW Accel)",
+ "hlsOptions": "Варианты HLS",
+ "hlsOptionsInvalid": "Варианты HLS недействительны",
+ "horse": "лошадь",
+ "hot dog": "хотдог",
+ "hour": "час",
+ "hours": "часов",
+ "hwaccel": "Двигатель ускорения",
+ "hwaccel_device": "Устройство Hwaccel",
+ "hwaccel_vcodec": "Видео декодер",
+ "in": "в",
+ "in Days": "в дни",
+ "in seconds": "в секундах",
+ "keyId": "Идентификатор ключа",
+ "keyboard": "клавиатура",
+ "kite": "воздушный змей",
+ "knife": "нож",
+ "laptop": "ноутбук",
+ "lastLogin": "Последний Войти",
+ "libmp3lame": "libmp3lame",
+ "libopus": "Либоп",
+ "libvorbis (Default)": "libvorbis (по умолчанию)",
+ "libvpx (Default)": "libvpx (по умолчанию)",
+ "libvpx-vp9": "libvpx-vp9",
+ "libx264": "libx264",
+ "libx264 (Default)": "libx264 (по умолчанию)",
+ "libx265": "libx265",
+ "liveGridDescription": "Live Grid - это дисплей с несколькими потоками для Shinobi. Этот метод просмотра в основном предназначен для настольных компьютеров.",
+ "loginHandleUnbound": "Логин был не связан с этой учетной записи.",
+ "microwave": "Микроволновая печь",
+ "migrateText1": " входной тип не может быть проанализирован. Пожалуйста, установите вручную.",
+ "minute": "минута",
+ "minutes": "минут",
+ "mjpeg_cuvid": "MJPEG CUVID",
+ "modifyVideoText1": "Метод не существует. Убкдитесь, что последнее значение URL не пустое.",
+ "monSavedButNotCopied": "Ваш монитор был сохранен, но не скопирован на любой другой монитор.",
+ "monitorConfigFinderDescription": "Этот инструмент поможет вам найти конфигурации для камер, опубликованных сообществом. Все размещены на shinobihub . Вы тоже можете опубликовать свой, это действительно поможет сообществу :)",
+ "monitorEditFailedMaxReached": "Достигнт предел создаваемых камер для вашего аккаунта. Свяжитесь с вашим администратором для изменения предела.",
+ "monitorEditText1": "Неверные данные - убедитесь, что введена корректная строка для импорта",
+ "monitorEditText2": "Неверная строка деталей. Убедитесь, что введа корректная JSON-строка вместо обычного объекта.",
+ "monitorGetText1": "неполный запрос, уберите слэш в конце URL или введите приемлимое значение.",
+ "monitorStateNotEnoughChanges": "Вам необходимо внести изменения в конфигурацию вашего монитора, прежде чем попытаться добавить ее в предустановку.",
+ "monitorStatesError": "Мониторинг ошибки пресетов",
+ "months": "месяцев",
+ "motorcycle": "мотоцикл",
+ "mouse": "мыши",
+ "mpeg2_mmal": "MPEG-2 (Raspberry Pi)",
+ "mpeg2_qsv": "MPEG2 (Quick Sync Video)",
+ "mpeg4_cuvid": "MPEG4 CUVID",
+ "mpeg4_mmal": "MPEG-4 (Raspberry Pi)",
+ "noLoginTokensAdded": "Там нет альтернативных логин, связанных с этой учетной записью.",
+ "noSpecialCharacters": "Без пробелов или специальных символов.",
+ "noTriggerText": "Если движение не было обнаружено после периода времени ожидания, вы получите уведомление о Discord.",
+ "noUndoForAction": "Вы не можете отменить это действие.",
+ "notActivatedText": "Ваша установка не выполнила активацию.",
+ "notEnoughFramesText1": "Недостаточно кадров для компиляции.",
+ "notPermitted1": "Это действие не разрешено администратором вашей учетной записи.",
+ "on": "на",
+ "on Error": "в случае ошибки",
+ "on Event": "на мероприятии",
+ "onvifdeviceManagerGlobalTip": "ONVIF позволяет изменять внутренние настройки камеры. Onvif - это нечто, к сожалению, это может означать много вещей. При этом вы можете увидеть вариант в этом инструменте, но он не может быть редактируемым. Обычно это связано с тем, что поставщик камеры не добавил этот метод или отклонился от своего предполагаемого использования. В этих случаях вам нужно будет ввести конфигурацию камеры с помощью предписанного метода поставщика камеры, это обычно открывает IP -адрес камеры в вашем веб -браузере.",
+ "onvifdeviceSavedFoundErrorText": "Некоторые настройки могли вернуться к предыдущему значению. Возможно, что модифицированный вариант недоступен с этой камерой через ONVIF.",
+ "onvifdeviceSavedText": "Внутренние настройки камеры были сохранены. Вам может потребоваться перезагрузить камеру, чтобы эти изменения вступили в силу.",
+ "openImagesDownloadConfirm": "Вы уверены, что хотите начать загружать изображения и ограничивающие коробки (предустановленные матрицы) с OpenImages?",
+ "openImagesDownloadConfirmStop": "Вы уверены, что хотите перестать тренироваться?",
+ "opencl": "Opencl",
+ "opencvCascadesText": "Если вы ничего не видите здесь, просто загрузите этот пакет Каскады . Оставьте их в plugins/opencv/cascades , затем нажмите обновить .",
+ "orange": "апельсин",
+ "oven": "духовой шкаф",
+ "p2pServerNotSelectedText": "Выберите сервер из списка и нажмите «Сохранить». Подождите 10 секунд, а затем попытайтесь открыть приборную панель удаленно.",
+ "p2pSettingsText1": "Вам нужно будет обновить эту страницу для применения изменений.",
+ "parking meter": "счетчик на стоянке",
+ "performanceOptimizeText1": "Ваша камера предоставляет данные потока H.264. Вы можете установить тип потока на HLS, Poseidon и видеокодек для копирования.",
+ "person": "человек",
+ "pizza": "пицца",
+ "possibleInternalError": "Возможная внутренняя ошибка",
+ "postDataBroken": "Проверьте формат JSON. Убедитесь, что он строка и определен в разделе «Данные»",
+ "potted plant": "растение в горшке",
+ "powerVideoEventLimit": "Вы установили высокий предел события. Вы уверены, что хотите сделать этот запрос?",
+ "privateKey": "Частный ключ",
+ "qsv": "QSV",
+ "rebootingCamera": "Перезагрузить камеру",
+ "refrigerator": "холодильник",
+ "remote": "дистанционный пульт",
+ "restartRequired": "Перезапуск ядра Shinobi требуется для вступления в силу.",
+ "sandwich": "сэндвич",
+ "scissors": "ножницы",
+ "separateByCommasOrRange": "Разделять запятыми или диапазоном",
+ "setMaxStorageAmountText": "Вы должны установить максимальную сумму хранения в настройках учетной записи, расположенные слева. Найдите опцию в разделе профиля. По умолчанию 10 ГБ.",
+ "sheep": "овца",
+ "sink": "раковина",
+ "sizePurgeLockedText": "Похоже, что блокировка чистки размера (deleteoverMax) не удалось разблокировать. Разблокируется сейчас ...",
+ "skateboard": "скейтборд",
+ "skipPingText1": "Попробуйте установить «пропустить пинг» на да.",
+ "skis": "лыжи",
+ "snowboard": "сноуборд",
+ "sorryNo": "Извините но нет",
+ "sorryNothingWasFound": "Извините, ничего не было найдено.",
+ "spoon": "ложка",
+ "sports ball": "Спортивный мяч",
+ "startUpText0": "Проверка используемого диска...",
+ "startUpText1": "Проверка используемого диска окончена.",
+ "startUpText2": "все пользователи проверены, ожидаю закрытия открытых файлов и удаления файлов, превышающих ограничения пользователей",
+ "startUpText3": "даю немного времени на завершение проверки видео. 3 секунды.",
+ "startUpText4": "Запуск мониторов... Пожалуйста, ожидайте...",
+ "startUpText5": "Shinobi готов.",
+ "startUpText6": "Несвязанные видео найдены и добавлены",
+ "stop sign": "знак СТОП",
+ "subAccountManager": "Менеджер суб-аккунтирования",
+ "substreamConnectionText": "Вы можете оставить детали подключения как есть, если вы хотите, чтобы оно использовало основную информацию о подключении, установленную выше.",
+ "substreamOutputText": "Здесь вы можете установить конфигурацию потока по требованию. Узнайте о задержка типов потоков здесь. ",
+ "substreamText": "Это метод просмотра живого потока по требованию. Вы можете сделать так, чтобы процесс просмотра был доступен только тогда, когда кто -то смотрит или будет использоваться для переключения между низким и высоким разрешением.",
+ "suitcase": "чемодан",
+ "superAdminText": "Файл \"super.json\" не существует. Переименуйте \"super.sample.json\" в \"super.json\".",
+ "superAdminTitle": "Shinobi: Супер Админ",
+ "surfboard": "доска для серфинга",
+ "teddy bear": "плюшевый медведь",
+ "tennis racket": "теннисная ракетка",
+ "tie": "галстук",
+ "toaster": "тостер",
+ "toilet": "туалет",
+ "tokenNotUserBound": "Эта ручка для входа не связана с пользователем на этом сервере!",
+ "tokenNotUserBoundPt2": "Введите свои учетные данные, затем используйте кнопку входа в Google, чтобы быстро ссылаться.",
+ "toothbrush": "зубная щетка",
+ "total": "итого",
+ "traffic light": "светофор",
+ "train": "тренироваться",
+ "truck": "грузовая машина",
+ "tv": "телевидение",
+ "umbrella": "зонтик",
+ "undoAllUnsaveChanges": "Вы уверены, что хотите это сделать? Это отменит все неспасенные изменения.",
+ "unexpectedExitText": "Информация об этом выходе будет найдена до этого журнала. Кроме того, вот команда FFMPEG, которая использовалась, когда процесс разбился.",
+ "updateCamerasInternalSettings": "Обновить внутренние настройки камеры?",
+ "updateKeyText1": "\"updateKey\" отсутствует в \"conf.json\", невозможно провести обновление, пока он не будет добавлен.",
+ "updateKeyText2": "\"updateKey\" неверный.",
+ "updateNotice1": "Обновление Shinobi означает перезапись файлов. Если вы самостоятельно изменили какие -либо файлы, вы должны обновить Shinobi вручную. Ваши конфигурации и видеофайлы не будут изменены.",
+ "useSubStreamOnlyWhenWatching": "Только при просмотре используйте подстрим",
+ "vaapi": "Ваапи (VA-API)",
+ "vase": "ваза",
+ "vda": "VDA (аппаратное ускорение Apple VDA)",
+ "vdpau": "Vdpau",
+ "videoBuildingText1": "Видео в настоящее время строит. Проверьте еще раз позже.",
+ "videotoolbox": "VideoToolbox",
+ "vp8_cuvid": "VP8 NVENC (NVIDIA HW ACCEL)",
+ "vp8_qsv": "VP8 (Quick Sync Video)",
+ "vp9_cuvid": "VP9 NVENC (NVIDIA HW ACCEL)",
+ "wannaReset": "Вы хотите сбросить?",
+ "willTriggerAnEvent": "запустит событие",
+ "wine glass": "бокал для вина",
+ "years": "лет",
+ "zebra": "зебра"
+}
\ No newline at end of file
diff --git a/languages/tr.json b/languages/tr.json
new file mode 100644
index 00000000..0350c1be
--- /dev/null
+++ b/languages/tr.json
@@ -0,0 +1,1649 @@
+{
+ "\"No Motion\" Detector": "\"Hareket yok\" dedektör",
+ "# of Allow MJPEG Clients": "Sonsuz için MJPEG istemcilerinin için",
+ "'Already Installing...'": "'Zaten yükleme ...'",
+ "180 Degrees": "180 derece",
+ "2-Factor Authentication": "2 faktörlü kimlik doğrulama",
+ "90 Clockwise": "90 saat yönünde",
+ "90 Clockwise and Vertical Flip": "90 saat yönünde ve dikey flip",
+ "90 Counter Clockwise and Vertical Flip (default)": "90 Saat yönünde ve dikey flip (varsayılan)",
+ "AND": "VE",
+ "API": "API",
+ "API Key": "API Anahtarı",
+ "API Key Action Failed": "API anahtar eylemi başarısız oldu",
+ "API Key Added": "API Anahtarı Eklendi",
+ "API Key Deleted": "API anahtarı silindi",
+ "API Keys": "API anahtarları",
+ "APIKeyAddedText": "Bu anahtarı şimdi kullanabilirsiniz.",
+ "APIKeyDeletedText": "Anahtar silindi. Artık işe yaramayacak.",
+ "ASC": "ASC",
+ "Accelerator": "Gaz pedalı",
+ "Account Info": "Hesap bilgisi",
+ "Account Information": "Hesap Bilgileri",
+ "Account Privileges": "Hesap ayrıcalıkları",
+ "Account Save": "Hesap Kaydet",
+ "Account Settings": "Hesap ayarları",
+ "AccountEditText1": "Düzenleyemedi. Sorun devam ederse sayfasını yenileyin.",
+ "Accounts": "Hesaplar",
+ "Action for Selected": "Seçilen için Eylem",
+ "Activated": "Aktif",
+ "Active Monitors": "Aktif monitörler",
+ "Add": "Ekle",
+ "Add All": "Hepsini ekle",
+ "Add Camera": "Kamera ekle",
+ "Add Cameras": "Kamera ekle",
+ "Add Channel": "Kanal ekle",
+ "Add Input Feed": "Giriş beslemesi ekle",
+ "Add Map": "Harita ekle",
+ "Add Monitor": "Monitör Ekle",
+ "Add New": "Yeni ekle",
+ "AddToPreset": "Ön ayara ekle",
+ "Additional Inputs": "Ek Girişler",
+ "Admin": "Yönetici",
+ "Advanced": "ileri",
+ "After": "Sonrasında",
+ "Again": "Tekrar",
+ "Age": "Yaş",
+ "Alert Sound": "Uyarı sesi",
+ "Alert Sound Delay": "Uyarı Ses Gecikmesi",
+ "All Logs": "Tüm günlükler",
+ "All Monitors": "Tüm monitörler",
+ "All Monitors and Privileges": "Tüm monitörler ve ayrıcalıklar",
+ "All Privileges": "Tüm ayrıcalıklar",
+ "All Warnings": "Tüm uyarılar",
+ "All streams in first feed": "İlk yemdeki tüm akışlar",
+ "Allow API Trigger": "API tetikleyicisine izin ver",
+ "Allow Next Alert": "Sonraki Uyarıya İzin Verin",
+ "Allow Next Command": "Sonraki Komuta İzin Ver",
+ "Allow Next Discord Alert": "Sonraki Discord uyarısına izin verin küçük>",
+ "Allow Next Email": "Sonraki e -postaya izin verin ",
+ "Allow Next Trigger": "Sonraki tetikleyiciye izin ver",
+ "Allow Next Webhook": "Sonraki webhook'a izin ver",
+ "Allowed IPs": "İzin verilen IPS",
+ "Already Processing": "Zaten işleme",
+ "Already exists": "Zaten var",
+ "Alternate Logins": "Alternatif girişler",
+ "Always": "Her zaman",
+ "Amazon S3": "Amazon S3",
+ "Amazon S3 Upload Error": "Amazon S3 Yükleme Hatası",
+ "Analyzation Duration": "Analiz süresi",
+ "AppNotEnabledText": "Uygulama etkin değil, hesap ayarlarınızda etkinleştirin.",
+ "April": "Nisan",
+ "Archive": "Arşiv",
+ "Are you sure?": "Emin misin?",
+ "Attach Snapshot": "Anlık görüntü ekleyin",
+ "Attach Video Clip": "Video klip ekleyin",
+ "Audio": "Ses",
+ "Audio Bit Rate": "Ses bit hızı",
+ "Audio Codec": "Ses Kodek",
+ "Audio Detection": "Ses tespiti",
+ "Audio Detector": "Ses dedektörü",
+ "Audio stream only from first feed": "Yalnızca ilk yemden ses akışı",
+ "Audio streams only": "Yalnızca ses akışları",
+ "August": "Ağustos",
+ "Authenticate": "Doğrulamak",
+ "Authenticated": "Doğrulanmış",
+ "Authentication Failed": "Kimlik doğrulama başarısız oldu",
+ "Auto": "Oto",
+ "Automatic": "Otomatik",
+ "Automatic Checking Cancelled": "Otomatik çek iptal edildi",
+ "Automatic Codec Repair": "Otomatik Codec onarımı",
+ "Automatic Field Fill": "Otomatik alan dolgusu",
+ "Autosave": "Otomobil",
+ "Back": "Geri",
+ "Backblaze B2": "Backblaze B2",
+ "Backblaze Error": "Backblaze Hatası",
+ "BacklightCompensation": "Arka ışık telafisi",
+ "Backup": "Destek olmak",
+ "Bandwidth": "Bant genişliği",
+ "Base64 over Websocket": "Base64 WebSocket üzerinden",
+ "Basic Authentication": "Temel kimlik doğrulama",
+ "Batch": "Grup",
+ "Before": "Önceki",
+ "Bind Credentials": "Bağlantı Kimlik Bilgileri (Parola)",
+ "BitrateLimit": "Bit hızı sınırı",
+ "Blank for No Change": "Değişiklik için boş",
+ "Bottom Left": "Sol alt",
+ "Bottom Right": "Sağ alt",
+ "Brightness": "Parlaklık",
+ "Browser Console Log": "Tarayıcı Konsolu Günlüğü",
+ "Bucket": "Kova",
+ "Buffer Preview": "Arabellek önizlemesi",
+ "Build": "İnşa etmek",
+ "Build Video": "Video Oluştur",
+ "Building": "Bina",
+ "CPU": "İşlemci",
+ "CPU indicator will not work. Continuing...": "CPU göstergesi çalışmaz. Devam ediyor...",
+ "CPU used by this stream": "Bu akış tarafından kullanılan CPU",
+ "CSS": "CSS Gösterge Tablonuzu Stil. ",
+ "Calendar": "Takvim",
+ "Call Method": "Çağrı yöntemi",
+ "Camera Password": "Kamera şifresi",
+ "Camera Username": "Kamera Kullanıcı Adı",
+ "Camera is not recording": "Kamera kayıt değil",
+ "Camera is not running": "Kamera çalışmıyor",
+ "Camera is not streaming": "Kamera akış değil",
+ "CameraNotRecordingText": "Ayarlar uyumsuz olabilir. Kodlayıcıları kontrol edin. Yeniden başlatma...",
+ "Can Authenticate Websocket": "WebSocket'i doğrulayabilir",
+ "Can Change User Settings": "Kullanıcı ayarlarını değiştirebilir",
+ "Can Control Monitors": "Monitörleri kontrol edebilir",
+ "Can Create and Delete Monitors": "Monitörler oluşturabilir ve silebilir",
+ "Can Delete Videos": "Videoları silebilir",
+ "Can Delete Videos and Events": "Videoları ve etkinlikleri silebilir",
+ "Can Edit Monitor": "Monitörü Düzenleyebilir",
+ "Can Get Logs": "Günlük alabilir",
+ "Can Get Monitors": "Monitör alabilir",
+ "Can View Logs": "Günlükleri görüntüleyebilir",
+ "Can View Monitor": "Monitörü görüntüleyebilir",
+ "Can View Snapshots": "Anlık görüntüleri görüntüleyebilir",
+ "Can View Streams": "Akışları görüntüleyebilir",
+ "Can View Videos": "Videoları görüntüleyebilir",
+ "Can View Videos and Events": "Videoları ve etkinlikleri görüntüleyebilir",
+ "Can edit Max Days": "Maksimum günleri düzenleyebilir",
+ "Can edit Max Storage": "Max depolamayı düzenleyebilir",
+ "Can edit how long to keep Events": "Etkinlikleri ne kadar tutacağınızı düzenleyebilir",
+ "Can edit how long to keep Logs": "Günlükleri ne kadar tutacağınızı düzenleyebilir",
+ "Can use Admin Panel": "Yönetici panelini kullanabilir",
+ "Can use Amazon S3": "Amazon S3 kullanabilir",
+ "Can use Discord Bot": "Discord bot kullanabilir",
+ "Can use LDAP": "LDAP kullanabilir",
+ "Can use SFTP": "SFTP kullanabilir",
+ "Can use Wasabi Hot Cloud Storage": "Wasabi Sıcak Bulut Depolama Kullanabilir",
+ "Can use WebDAV": "Webdav kullanabilir",
+ "Can't Connect": "Bağlanamıyorum",
+ "Cannot watch a monitor that isn't running.": "Koşmayan bir monitör izleyemiyorum.",
+ "Cards": "Kart",
+ "Carousel in Background": "Arka planda atlıkarınca",
+ "Center": "Center URL adresi küçük>",
+ "Channel": "Kanal",
+ "Channel ID": "Kanal kimliği",
+ "Chat on Discord": "Discord hakkında sohbet edin",
+ "Check": "Kontrol etmek",
+ "Check Signal Interval": "Sinyal aralığını kontrol edin ",
+ "Check for Motion First": "Önce Hareketi Kontrol Edin",
+ "Check the Channel ID": "Kanal Kimliğini Kontrol Edin",
+ "Check the Recipient ID": "Alıcı Kimliğini Kontrol Edin",
+ "Clear": "Açık",
+ "Clear Recorder Process": "Kayıt cihazı işlemi",
+ "Close": "Kapat",
+ "Close All Monitors": "Tüm monitörleri kapatın",
+ "Closed": "Kapalı",
+ "Cloud": "Bulut",
+ "Codec Mismatch": "Kodek uyuşmazlığı",
+ "Color Threshold": "Renk eşiği",
+ "ColorSaturation": "Renk doygunluğu",
+ "Command": "Emretmek",
+ "Command on Trigger": "Tetikte komut",
+ "Common Objects": "Ortak nesneler",
+ "Complete Stream URL": "Tam Akış URL'si",
+ "Conditions": "Koşullar",
+ "Confidence": "Kendinden emin",
+ "Confidence of Detection": "Tespitin güveni",
+ "Configuration": "Konfigürasyon",
+ "Confirm": "Onaylamak",
+ "Connected": "Bağlı",
+ "Connected Users": "Bağlı Kullanıcılar",
+ "Connection": "Bağ",
+ "Connection Type": "Bağlantı türü",
+ "Connection timed out": "Bağlantı zaman aşımı",
+ "Contains": "İçermek",
+ "Contrast": "Zıtlık",
+ "Control": "Kontrol",
+ "Control Error": "Kontrol hatası",
+ "Control Trigger Started": "Kontrol Tetikleyicisi başladı",
+ "Control Triggered": "Kontrol Tetiklendi",
+ "ControlErrorText1": "Kontrol etkin değil",
+ "ControlErrorText2": "Bağlantı ayrıntılarınızı kontrol edin. Temel URL'yi 8000 veya 80 bağlantı noktasına yönlendirmeniz gerekebilir. Kimlik doğrulama bilgilerinizi kontrol edin.",
+ "Controllable": "Kontrol edilebilir",
+ "Controls and Logs": "Kontroller ve günlükler",
+ "Copied": "Kopyalanmış",
+ "Copied to Clipboard": "Panoya kopyalandı",
+ "Copy": "Kopya",
+ "Copy Connection Settings": "Bağlantı Ayarlarını Kopyala",
+ "Copy Custom Settings": "Özel Ayarları Kopyala",
+ "Copy Detector Settings": "Dedektör Ayarlarını Kopyala",
+ "Copy Group Settings": "Grup Ayarlarını Kopyala",
+ "Copy Input Settings": "Giriş Ayarlarını Kopyala",
+ "Copy JPEG API Settings": "JPEG API ayarlarını kopyala",
+ "Copy Logging Settings": "Günlük ayarlarını kopyala",
+ "Copy Mode": "Kopya Modu",
+ "Copy Recording Settings": "Kayıt Ayarlarını Kopyala",
+ "Copy Remote Link": "Uzaktan bağlantıyı kopyala",
+ "Copy Settings": "Ayarları Kopyala",
+ "Copy Stream Channel Settings": "Akış Kanalı Ayarlarını Kopyala",
+ "Copy Stream Channels": "Akış kanallarını kopyala",
+ "Copy Stream Settings": "Akış Ayarlarını Kopyala",
+ "Copy Stream URL": "Akış URL'sini Kopyala",
+ "Copy Timelapse Settings": "Timelapse ayarlarını kopyala",
+ "Copy to Settings": "Ayarlara Kopyala",
+ "Cores": "Çekirdek",
+ "Could not create Bucket.": "Kova oluşturamadı.",
+ "Count Objects": "Nesneleri Say",
+ "Count Objects only inside Regions": "Nesneleri yalnızca bölgelerde say",
+ "Country of Plates": "Tabak ülkesi",
+ "Counts of Motion": "Hareket sayıları",
+ "Create Sub-Accounts at /admin": "/Admin atında alt hesap oluştur",
+ "Creating New Account": "Yeni Hesap Oluşturma",
+ "Creation Interval": "Yaratılış aralığı",
+ "Current": "Akım",
+ "Currently Active": "Şu anda aktif",
+ "Currently viewing": "Şu anda görüntülüyor",
+ "Custom": "Gelenek",
+ "Custom Auto Load": "Özel Otomatik Yük",
+ "Custom Base URL": "Özel Taban URL Ana bilgisayar url'ini kullanmak için boş bırakın küçük>",
+ "Custom Endpoint": "Özel uç nokta",
+ "DB Lost.. Retrying..": "Veritabanı kayıp .. yeniden deneme ..",
+ "DESC": "Desc",
+ "DHCP": "DHCP",
+ "DNS": "DNS",
+ "Daily Events": "Günlük Etkinlikler",
+ "Dashboard": "Gösterge Paneli",
+ "Dashboard Language": "Gösterge paneli dili",
+ "Dashcam": "Şık",
+ "Dashcam (Streamer v2)": "Dashcam (Salam V2)",
+ "Database": "Veri tabanı",
+ "Database Not Found": "Veritabanı bulunamadı",
+ "Database row does not exist": "Veritabanı satırı yok",
+ "Date": "Tarih",
+ "Date Added": "Ekleme Tarihi",
+ "Date Range": "Tarih aralığı",
+ "Date Updated": "Tarih Güncellendi",
+ "Date and Time": "Tarih ve saat",
+ "DateTimeType": "Tarih yönetimi",
+ "DaylightSavings": "Gün ışığından yararlanma",
+ "Days": "Günler",
+ "Debug": "Ayıklamak",
+ "December": "Aralık",
+ "Default": "Varsayılan",
+ "Delay for Snapshot": "Anlık görüntü için gecikme",
+ "Delete": "Silmek",
+ "Delete Camera": "Kamerayı sil",
+ "Delete Filter": "Filtreyi sil",
+ "Delete Logs": "Günlükleri Sil",
+ "Delete Matches": "Maçları Sil",
+ "Delete Monitor": "Monitörü Sil",
+ "Delete Monitor State?": "Monitör Durumunu Sil",
+ "Delete Monitor States Preset": "Monitör Durumlarını Sil Ön ayar",
+ "Delete Monitors and Files": "Monitörleri ve dosyaları sil",
+ "Delete Motionless Video": "Hareketsiz videoyu sil",
+ "Delete Motionless Videos (Record)": "Hareketsiz videoları sil (kayıt)",
+ "Delete Region": "Bölge Sil",
+ "Delete Schedule": "Programı Sil",
+ "Delete Selected Videos": "Seçilen videoları sil",
+ "Delete Timelapse Frame": "Timelapse çerçevesini sil",
+ "Delete Video": "Videoyu sil",
+ "Delete selected": "Silme seçildi",
+ "DeleteMonitorText": "Bu monitörü silmek istiyor musunuz? Onu kurtaramazsın. Dosyaların dosya sisteminde kalmasını seçebilirsiniz. Aynı kimliğe sahip bir monitörü yeniden oluşturmayı seçerseniz, videolar ve etkinlikler gösterge tablosunda görünür olacaktır.",
+ "DeleteMonitorsText": "Bu monitörleri silmek ister misiniz? Onları kurtaramazsın. Bu kimliklerin dosyalarını dosya sisteminde tutmayı seçebilirsiniz. Kimliklerden biriyle bir monitörü yeniden oluşturmayı seçerseniz, videolar ve etkinlikler gösterge tablosunda görünür olacaktır.",
+ "DeleteSelectedVideosMsg": "Bu videoları silmek ister misiniz? Onları kurtaramazsın.",
+ "DeleteThisMsg": "Bunu silmek ister misin? Onu kurtaramazsın.",
+ "DeleteVideoMsg": "Bu videoyu silmek ister misiniz? Onu kurtaramazsın.",
+ "Deleted": "silindi",
+ "Deleted Schedule Configuration": "Silinmiş Program Yapılandırması",
+ "Deleted State Configuration": "Silinmiş durum yapılandırması",
+ "Detect Objects": "Nesneleri algılayın Aşağıya bakın küçük>",
+ "Detection": "Tespit etme",
+ "Detection Engine": "Algılama motoru",
+ "Detection Event": "Tespit olayı",
+ "Detector": "Dedektör",
+ "Detector Buffer": "Dedektör tamponu",
+ "Detector Filters": "Dedektör filtreleri",
+ "Detector Flags": "Dedektör bayrakları",
+ "Detector Grouping": "Dedektör Gruplama Ayarları küçük> 'de grup ekleyin",
+ "Detector Rate": "Dedektör oranı Genişlik ve yükseklik kutuları gösterildiğinde, bunları 640x480 veya daha altına ayarlamanız gerekir. Bu, çerçevelerin okuma hızını optimize edecektir.
", + "Died": "Öldü", + "Digest Authentication": "Sindirim Kimlik Doğrulaması", + "Disable": "Devre dışı bırakmak", + "Disable Night Vision": "Gece Görüşünü Devre Dışı Bırak URL Adresi ", + "Disable Nightvision": "NightVision'u devre dışı bırakın", + "Disabled": "Engelli", + "Discord": "Anlaşmazlık", + "Discord Alert on Trigger": "Tetikte uyumsuzluk uyarısı", + "Discord Bot": "Anlaşmazlık botu", + "Discord on No Motion": "\"Hareket yok\"", + "DiscordErrorText": "Anlaşmazlığa göndermek bir hataya neden oldu", + "DiscordFailedText": "Anlaşmazlığa göndermek başarısız oldu", + "DiscordLoggedIn": "Discord bot kimlik doğrulandı", + "DiscordNotEnabledText": "Discord bot etkinleştirilmez, hesap ayarlarınızda etkinleştirin.", + "Documentation": "Belgeler", + "Does Not Contain": "İçermiyor", + "Don't Show for 1 Week": "1 Hafta Gösterme", + "Don't show this anymore": "Bunu artık gösterme", + "DontAddToPreset": "Ön ayara eklemeyin", + "Double Quote Directory": "Çift Alıntı Dizini Bazı dizinlerin boşlukları vardır. Bunu kullanmak bazı kameraları çökertebilir. küçük>", + "Down": "AşağıBu bölüm, akış ve ayarlarının birincil yöntemini belirleyecektir. Bu akış gösterge tablosunda görüntülenecektir. HLS, JPEG veya MJPEG kullanmayı seçerseniz, akışı diğer programlar aracılığıyla tüketebilirsiniz. çerçeveler.
", + "Streamed Logs": "Akışlı günlükler", + "Streamer": "Flama", + "Streams": "Canlı Yayınlar", + "Sub-Accounts": "Alt Hesaplar", + "Subdivision": "Alt bölüm", + "Substream": "Alt akım", + "Substream Process": "Alt akım işlemi", + "SubstreamNotConfigured": "Alt akım yapılandırılmadı. Monitör ayarlarınızı açın ve yapılandırın.", + "Subtitle": "Alt yazı", + "Success": "Başarı", + "Sunday": "Pazar", + "Superuser": "Süper", + "Superuser Logs": "Süper kullanıcı günlükleri", + "Switch on for Still Image": "Hareketsiz görüntü için aç", + "System": "Sistem", + "System Level": "Sistem seviyesi", + "TCP": "TCP", + "TV Channel": "TV kanalı", + "TV Channel Group": "TV kanalı grubu", + "TV Channel ID": "TV kanalı kimliği", + "Telegram": "Telgraf", + "Text Box Color": "Metin kutusu rengi", + "Text Color": "Metin rengi", + "Text criteria unsupported for Object Count tests, Ignoring Conditional": "Nesne sayımı testleri için desteklenmeyen metin kriterleri, koşullu göz ardı ederek", + "Themes": "Temalar", + "There are no monitors that you can view with this account.": "Bu hesapla görüntüleyebileceğiniz monitör yoktur.", + "Threads": "İş Parçacığı", + "Thumbnail": "Küçük resim", + "Thursday": "Perşembe", + "Time": "Zaman", + "Time Created": "Zaman yaratıldı", + "Time Left": "Kalan zaman", + "Time Occurred": "Zaman meydana geldi", + "Time-lapse": "Hızlandırılmış", + "Time-lapse Tool": "Hızlandırılmış aracı", + "TimeZone": "Saat dilimi", + "Timelapse": "Timelapse", + "Timelapse Frames Share": "Timelapse Frames Paylaşım", + "Timelapse Watermark": "Timelapse filigran", + "Timeout": "Zaman aşımı", + "Timeout Reset on Next Event": "Bir sonraki etkinlikte zaman aşımı sıfırlaması", + "Timeout Reset on Next Motion": "Sonraki Harekette Zaman Aşımı Sıfırlama", + "Timezone": "Saat dilimi", + "Timezone Offset": "Timezone ofseti", + "Title": "Başlık", + "Today": "Bugün", + "Toggle Sidebar": "Kenar çubuğu", + "Toggle Substream": "Alt akışı değiştirin", + "Token": "Jeton", + "Top Left": "Sol üst", + "Top Right": "Sağ üst", + "Traditional (Watch-Only, Includes Buffer)": "Geleneksel (yalnızca izleme, tampon içerir)", + "Traditional Recording": "Geleneksel kayıt", + "Traditional Recording Flags": "Geleneksel kayıt bayrakları", + "Train": "Tren", + "TrainConfirm": "Eğitime başlamak istediğinden emin misiniz? Bu, 500'den fazla görüntü ile 12 saatten fazla sürebilir. Bu, RAM ve/veya CPU gibi büyük miktarda kaynak tüketecektir.", + "TrainConfirmStop": "Eğitimi durdurmak istediğinden emin misiniz?", + "Trainer Engine": "Eğitmen motoru", + "Trigger Blocked": "Tetikle engellendi", + "Trigger Camera Groups": "Tetik Kamera Grupları", + "Trigger Event": "Tetikleyici olay", + "Trigger Group to Record": "Kayıt için tetikleme grubu", + "Trigger Record": "Tetik kaydı", + "Trigger Successful": "Tetikleyici Başarılı", + "Trigger Threshold": "Tetik eşiği", + "Tuesday": "Salı", + "Turn Speed": "Dönüş hızı", + "Type": "Tip", + "UDP": "UDP", + "URL": "Url", + "URL Stop Timeout": "URL Stop Timeout X milisaniye sonrası URL'yi çalıştırın küçük>", + "US": "BİZ", + "UTCDateTime": "Tarih", + "Unable to Launch": "Başlatılamıyor", + "UnabletoLaunchText": "Lütfen önce yeni monitör kaydedin. Ardından bölge editörünü başlatmaya çalışın.", + "Uncommon Objects": "Nadir Nesneler", + "Uniform": "Üniforma", + "Unlink": "Bağırmak", + "Unlink Login": "Bağlantı girişi?", + "Unlinked": "Bağlantısız", + "Up": "Yukarı -Strict 2 gibi özel bayraklara ihtiyaç duyar.",
+ "fieldTextChannelStreamAcodecLibmp3lame": "MP4 video için kullanılır.",
+ "fieldTextChannelStreamAcodecLibopus": "WebM videosu için kullanılır.",
+ "fieldTextChannelStreamAcodecLibvorbis": "WebM videosu için kullanılır.",
+ "fieldTextChannelStreamAcodecNoAudio": "Ses yok, bu yasal nedenlerden dolayı dünyanın bazı bölgelerinde ayarlanması gereken bir seçenektir.",
+ "fieldTextChannelStreamFps": "Çerçevelerin saniyede kareler halinde müşterilere görüntülenme hızı. Varsayılan olmadığını unutmayın. Bu, yüksek bant genişliği kullanımına yol açabilir.",
+ "fieldTextChannelStreamQuality": "Düşük sayı daha yüksek kalite anlamına gelir. Daha yüksek sayı daha az kalite anlamına gelir.",
+ "fieldTextChannelStreamRotate": "Video akışının görüntüleme açısını değiştirin.",
+ "fieldTextChannelStreamScaleX": "İşlemden sonra çıktı olan akış görüntüsünün genişliği.",
+ "fieldTextChannelStreamScaleY": "İşlemden sonra çıktı olan akış görüntüsünün yüksekliği.",
+ "fieldTextChannelStreamType": "Video akışını tüketmek için kullanılacak yöntem.",
+ "fieldTextChannelStreamTypeFLV": "WebSocket üzerinden FLV kodlu çerçeveler gönderme.",
+ "fieldTextChannelStreamTypeHLS(includesAudio)": "Facebook canlı akışlarına benzer bir yöntem. Ses içerir Giriş sağlarsa. Yaklaşık 4-6 saniyelik bir gecikme vardır, çünkü bu yöntem segmentleri kaydeder, daha sonra onları oluştururken itmek yerine müşteriye iter.",
+ "fieldTextChannelStreamTypeMJPEG": "Standart hareket JPEG görüntüsü. Ses yok.",
+ "fieldTextChannelStreamTypePoseidon": "Poseidon, Kevin Godell'ın MP4 işleme kodu üzerine kurulmuştur. Bir akış MP4 dosyasını simüle eder, ancak canlı akışın verilerini kullanarak. Ses içerir. Bazı tarayıcılar normal bir MP4 dosyası gibi oynayabilir. HTTP veya WebSocket üzerinden akışlar.",
+ "fieldTextChannelStreamVcodec": "Akış için video kodek.",
+ "fieldTextChannelStreamVcodecAuto": "FFMPEG'nin seçmesine izin verin.",
+ "fieldTextChannelStreamVcodecCopy": "MP4 video için kullanılır. Çok düşük CPU kullanımı vardır, ancak video filtreleri kullanamaz ve dosya boyutları devasa olabilir. Bu seçeneği kullanırken MP4 ayarları kamera tarafınızı ayarlamak için en iyisi.",
+ "fieldTextChannelStreamVcodecLibx264": "MP4 video için kullanılır.",
+ "fieldTextChannelStreamVcodecLibx265": "MP4 video için kullanılır.",
+ "fieldTextChannelSvf": "Akış bölümünü etkilemek için FFMPEG video filtrelerini bu kutuya yerleştirin. Boşluksuz.",
+ "fieldTextControlInvertY": "Çünkü kameranız baş aşağı monte edildiğinde veya ters dikey kontroller kullandığında.",
+ "fieldTextCrf": "Düşük sayı daha yüksek kalite anlamına gelir. Daha yüksek sayı daha az kalite anlamına gelir.",
+ "fieldTextCustDetect": "Analizasyon için akarsu dedektörüne bağlanan özel bayraklar.",
+ "fieldTextCustDetectObject": "Analizasyon için akarsu dedektörüne bağlanan özel bayraklar.",
+ "fieldTextCustInput": "FFMPEG işleminin girişine bağlanan özel bayraklar.",
+ "fieldTextCustRecord": "FFMPEG işleminin kaydına bağlanan özel bayraklar.",
+ "fieldTextCustSipRecord": "Olay tabanlı kayıtların sifonundan kaynaklanan çıktıya bağlanan özel bayraklar.",
+ "fieldTextCustSnap": "Anlık görüntülere bağlanan özel bayraklar.",
+ "fieldTextCustStream": "FFMPEG işleminin akışına (istemci taraf görünümü) bağlanan özel bayraklar.",
+ "fieldTextCustomOutput": "JPEG çerçeveleri gibi özel bir çıktı ekleyin veya doğrudan başka bir sunucuya veri gönderin.",
+ "fieldTextCutoff": "Dakikalar içinde. Ne zaman dilimlenir ve yeni bir video dosyası başlatılır.",
+ "fieldTextDays": "Temizlemeden önce videoları saklamak için gün sayısı.",
+ "fieldTextDetailSubstreamInputRtspTransportAuto": "FFMPEG'nin karar vermesine izin verin. Normalde önce UDP'yi deneyecektir.",
+ "fieldTextDetailSubstreamInputRtspTransportTCP": "UDP istenmeyen sonuçlar vermeye başlarsa buna ayarlayın.",
+ "fieldTextDetailSubstreamInputRtspTransportUDP": "FFMPEG bunu önce deniyor.",
+ "fieldTextDetailSubstreamOutputHlsListSize": "Eski segmentleri otomatik olarak silmeden önce maksimum segment sayısı.",
+ "fieldTextDetailSubstreamOutputHlsTime": "Her video segmentinin ne kadar süre olması gerektiği, dakikalar içinde. Her segment istemci tarafından bir M3U8 dosyası aracılığıyla çizilecektir. Daha kısa segmentler daha az yer alır.",
+ "fieldTextDetailSubstreamOutputPresetStream": "Belirli video kodlayıcıları için önceden ayarlanmış bayrak. Kameranızın birkaç saniyede bir çöktüğünü görürseniz: boş bırakmayı deneyin.",
+ "fieldTextDetailSubstreamOutputStreamAcodec": "Akış için ses kodek.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAac": "MP4 video için kullanılır.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAc3": "MP4 video için kullanılır.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAuto": "FFMPEG'nin seçmesine izin verin.",
+ "fieldTextDetailSubstreamOutputStreamAcodecCopy": "MP4 video için kullanılır. Çok düşük CPU kullanımı vardır, ancak bazı ses kodekleri AAC için -Strict 2 gibi özel bayraklara ihtiyaç duyar.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibmp3lame": "MP4 video için kullanılır.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibopus": "WebM videosu için kullanılır.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibvorbis": "WebM videosu için kullanılır.",
+ "fieldTextDetailSubstreamOutputStreamAcodecNoAudio": "Ses yok, bu yasal nedenlerden dolayı dünyanın bazı bölgelerinde ayarlanması gereken bir seçenektir.",
+ "fieldTextDetailSubstreamOutputStreamFps": "Çerçevelerin saniyede kareler halinde müşterilere görüntülenme hızı. Varsayılan olmadığını unutmayın. Bu, yüksek bant genişliği kullanımına yol açabilir.",
+ "fieldTextDetailSubstreamOutputStreamQuality": "Düşük sayı daha yüksek kalite anlamına gelir. Daha yüksek sayı daha az kalite anlamına gelir.",
+ "fieldTextDetailSubstreamOutputStreamRotate": "Video akışının görüntüleme açısını değiştirin.",
+ "fieldTextDetailSubstreamOutputStreamScaleX": "İşlemden sonra çıktı olan akış görüntüsünün genişliği.",
+ "fieldTextDetailSubstreamOutputStreamScaleY": "İşlemden sonra çıktı olan akış görüntüsünün yüksekliği.",
+ "fieldTextDetailSubstreamOutputStreamType": "Video akışını tüketmek için kullanılacak yöntem.",
+ "fieldTextDetailSubstreamOutputStreamVcodec": "Akış için video kodek.",
+ "fieldTextDetailSubstreamOutputStreamVcodecAuto": "FFMPEG'nin seçmesine izin verin.",
+ "fieldTextDetailSubstreamOutputStreamVcodecCopy": "MP4 video için kullanılır. Çok düşük CPU kullanımı vardır, ancak video filtreleri kullanamaz ve dosya boyutları devasa olabilir. Bu seçeneği kullanırken MP4 ayarları kamera tarafınızı ayarlamak için en iyisi.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx264": "MP4 video için kullanılır.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx265": "MP4 video için kullanılır.",
+ "fieldTextDetailSubstreamOutputSvf": "Akış bölümünü etkilemek için FFMPEG video filtrelerini bu kutuya yerleştirin. Boşluksuz.",
+ "fieldTextDetector": "Bu, hareket dedektörü için FFMPEG komutuna başka bir çıktı ekleyecektir.",
+ "fieldTextDetectorAudio": "Bir certiain kararsızında ses olup olmadığını kontrol edin. Düzeltme okuması gerçek dünya ölçümü için doğru olmayabilir.",
+ "fieldTextDetectorBufferHlsListSize": "Eski segmentleri otomatik olarak silmeden önce maksimum segment sayısı.",
+ "fieldTextDetectorBufferHlsTime": "Her video segmentinin saniyeler içinde ne kadar olması gerektiği. Her segment istemci tarafından bir M3U8 dosyası aracılığıyla çizilecektir. Daha kısa segmentler daha az yer alır.",
+ "fieldTextDetectorColorThreshold": "Hareket olarak kabul edilmeden önce bir pikselde izin verilen fark miktarı.",
+ "fieldTextDetectorCommand": "Çalışacak komut. Bu, terminalden bir kabuk komutu çalıştırmanın eşdeğeridir.",
+ "fieldTextDetectorCommandTimeout": "Bu değer, betiğinizin bir sonraki çalışmasına izin veren bir zamanlayıcıdır. Bu değer dakikalar halinde.",
+ "fieldTextDetectorFps": "Hareket dedektörüne göndermek için saniyede kaç kare; 2 varsayılandır.",
+ "fieldTextDetectorFrame": "Bu, piksel farklılıkları için tüm çerçeveyi okuyacaktır. Bu, tüm ekranı kapsayan bir bölge oluşturmakla aynıdır.",
+ "fieldTextDetectorHttpApi": "Bu kameraya HTTP tetikleyicilerine izin vermek ister misiniz?",
+ "fieldTextDetectorLisencePlate": "Plaka tanımasını etkinleştirin. OpenAlpr eklentisi her zaman etkinleştirildi.",
+ "fieldTextDetectorLisencePlateCountry": "Tanıma plaka türünü seçin. Şu anda sadece biz ve AB destekleniyor.",
+ "fieldTextDetectorLockTimeout": "Veritabanının aşırı yüklenmesini ve istemcileri almayı önlemek için bir sonraki tetikleyiciye izin verildiğinde kilitleme. Milisaniye cinsinden ölçülür.",
+ "fieldTextDetectorMaxSensitivity": "Hareket güven derecesi, tetikleyici olarak görülmek için bu değerden daha düşük olmalıdır. Maksimum olmadan boş bırakın. Bu seçenek daha önce \"Max Kayıtsızlık\" olarak adlandırılmıştır.",
+ "fieldTextDetectorNoiseFilter": "Belirli bir kayıtsızlıkta tahıl veya tekrarlanan hareketi filtrelemeye çalışın.",
+ "fieldTextDetectorNoiseFilterRange": "Hareket olarak kabul edilmeden önce bir pikselde izin verilen fark miktarı.",
+ "fieldTextDetectorNotrigger": "Hareketin bir aralıkta meydana gelip gelmediğini kontrol edin. Hareket meydana geldiyse, çek sıfırlanacaktır.",
+ "fieldTextDetectorNotriggerCommand": "Çalışacak komut. Bu, terminalden bir kabuk komutu çalıştırmanın eşdeğeridir.",
+ "fieldTextDetectorNotriggerCommandTimeout": "Bu değer, betiğinizin bir sonraki çalışmasına izin veren bir zamanlayıcıdır. Bu değer dakikalar halinde.",
+ "fieldTextDetectorNotriggerDiscord": "Zaman aşımı döneminden sonra hareket tespit edilmediyse, bir anlaşmazlık bildirimi alacaksınız.",
+ "fieldTextDetectorNotriggerTimeout": "Zaman aşımı dakikalar içinde hesaplanır.",
+ "fieldTextDetectorNotriggerWebhook": "Etkinlikten bazı değerlerle bir URL'ye GET isteği gönderin.",
+ "fieldTextDetectorObjCount": "Tespit edilen nesneleri say.",
+ "fieldTextDetectorObjCountInRegion": "Nesneleri yalnızca bölgelerde say.",
+ "fieldTextDetectorPam": "Kevin Godell'ın hareket dedektörünü kullanın. Bu, Shinobi'ye yerleştirilmiştir ve etkinleştirmek için başka bir yapılandırma gerektirmez.",
+ "fieldTextDetectorPtzFollow": "PTZ ile tespit edilen en büyük nesneyi takip edin? Olaylarla birlikte çalışan bir nesne dedektörü veya matris gerektirir.",
+ "fieldTextDetectorRecordMethod": "Hareket gibi bir olay meydana geldiğinde kayda başlamanın birçok yolu vardır. Geleneksel kayıt en kullanıcı dostudur.",
+ "fieldTextDetectorSave": "SQL'de hareket etkinliklerini kaydedin. Bu, güç görüntüleyicisinde meydana gelen zaman hareketi olayları sırasında video üzerinden hareketin görüntülenmesine izin verecektir.",
+ "fieldTextDetectorScaleX": "Tespit edilen görüntünün genişliği. Daha küçük boyutlar daha az CPU alır.",
+ "fieldTextDetectorScaleY": "Tespit edilen görüntünün yüksekliği. Daha küçük boyutlar daha az CPU alır.",
+ "fieldTextDetectorSendFrames": "Analiz edilecek çerçeveleri bağlı eklentiye itin.",
+ "fieldTextDetectorSendFramesObject": "Analiz edilecek çerçeveleri bağlı eklentiye itin.",
+ "fieldTextDetectorSendVideoLength": "Saniyeler içinde. E -posta veya anlaşmazlık gibi bildirim hizmetinize gönderilen videonun uzunluğu.",
+ "fieldTextDetectorSensitivity": "Hareket güven derecesi bir tetikleyici olarak görülmek için bu değeri aşmalıdır. Bu sayı, hareket dedektörü tarafından döndürülen güven derecesi ile doğrudan ilişkilidir. Bu seçenek daha önce \"kayıtsızlık\" olarak adlandırılmıştır.",
+ "fieldTextDetectorThreshold": "Bir hareket olayını ateşlemek için minimum algılama sayısı. Tespitler dedektörün içinde eşiğin dedektör fps saniyesine bölünmesi gerekir. Örneğin, dedektör FPS 2 ise ve tetik eşiği 3 ise, bir hareket olayını tetiklemek için 1.5 saniye içinde üç algılama yapılmalıdır. Bu eşik algılama bölgesidir.",
+ "fieldTextDetectorTimeout": "\"Tetik kaydı\" süresinin uzunluğu için çalışacaktır. Bu dakikalar içinde okunur.",
+ "fieldTextDetectorTrigger": "Bu, bir olay algılandığında kameraya \"sadece izleme\" olarak ayarlanırsa kaydedilmesini emreder.",
+ "fieldTextDetectorUseDetectObject": "Bağlı eklentiye göndermek için çerçeveler oluşturun.",
+ "fieldTextDetectorWebhook": "Etkinlikten bazı değerlerle bir URL'ye GET isteği gönderin.",
+ "fieldTextDetectorWebhookTimeout": "Bu değer, webhook'unuzun bir sonraki çalışmasına izin veren bir zamanlayıcıdır. Bu değer dakikalar halinde.",
+ "fieldTextDir": "Kaydedilen dosyaların kaydedileceği yeri. addStorage değişkeniyle daha fazla konum yapılandırabilirsiniz.",
+ "fieldTextEventDays": "Temizlemeden önce olayları saklamak için gün sayısı.",
+ "fieldTextEventMonPop": "Bir olay oluştuğunda monitör akışını açın.",
+ "fieldTextEventRecordScaleX": "İşlemden sonra çıktı olan olay tabanlı kayıt görüntüsünün genişliği.",
+ "fieldTextEventRecordScaleY": "İşlemden sonra çıktı olan olay tabanlı kayıt görüntüsünün yüksekliği.",
+ "fieldTextExt": "Kaydedilen video dosyanız için dosya türü.",
+ "fieldTextExtMP4": "Bu dosya türü oynanabilir, mobil içeren neredeyse tüm modern web tarayıcılarıdır. Kaliteyi düşürmediğiniz sürece dosya boyutu daha büyük olma eğilimindedir.",
+ "fieldTextExtWebM": "Küçük dosya boyutu, düşük istemci uyumluluğu. YouTube gibi sitelere yükleme için iyi.",
+ "fieldTextFactorAuth": "Etkin yöntemlerden biri aracılığıyla giriş için ikincil bir gereksinimi etkinleştirin.",
+ "fieldTextFatalMax": "Monitörü devre dışı bırakmadan önce sunucu ve kamera arasındaki ağ bağlantısı için yeniden deneme sayısı. Ondalık değil. Sonsuza dek yeniden denemek için 0 olarak ayarlayın.",
+ "fieldTextFps": "Çerçevelerin dosyalara, saniyede çerçevelere kaydedilme hızı. Varsayılan olmadığını unutmayın. Bu büyük dosyalara yol açabilir. Bu kamerayı ayarlamak için en iyisi.",
+ "fieldTextHeight": "Akış görüntüsünün yüksekliği.",
+ "fieldTextHlsListSize": "Eski segmentleri otomatik olarak silmeden önce maksimum segment sayısı.",
+ "fieldTextHlsTime": "Her video segmentinin ne kadar süre olması gerektiği, dakikalar içinde. Her segment istemci tarafından bir M3U8 dosyası aracılığıyla çizilecektir. Daha kısa segmentler daha az yer alır.",
+ "fieldTextHost": "Bağlantı adresi",
+ "fieldTextHwaccel": "Kod çözme motoru",
+ "fieldTextHwaccelVcodec": "Kod çözme motoru",
+ "fieldTextInverseTrigger": "Belirtilen bölgelerin dışını tetiklemek için. Tam çerçeve algılama etkinken tetiklenmez.",
+ "fieldTextIp": "Menzil veya tek",
+ "fieldTextIrCutFilterAuto": "IR kesme filtresi cihaz tarafından otomatik olarak etkinleştirilir.",
+ "fieldTextIrCutFilterOff": "IR Kesme Fiter'i devre dışı bırakın. Tipik olarak gece modu.",
+ "fieldTextIrCutFilterOn": "IR Kesme Fiter'i etkinleştirin. Tipik olarak gündüz modu.",
+ "fieldTextIsOnvif": "Bu ONVIF uyumlu bir kamera mı?",
+ "fieldTextLang": "Metin öğelerinin birincil dili. Tam çeviri için dilinizi conf.json'a ekleyin.",
+ "fieldTextLogDays": "Tasfiye etmeden önce günlükleri saklamak için gün sayısı.",
+ "fieldTextLoglevel": "İşi yaparken sağlanacak veri miktarı.",
+ "fieldTextLoglevelAllWarnings": "Tüm uyarıları görüntüleyin. Kameranızda neyin yanlış olduğunu bulamıyorsanız bunu kullanın.",
+ "fieldTextLoglevelFatal": "Yalnızca ölümcül hatalar görüntüleyin.",
+ "fieldTextLoglevelOnError": "Tüm önemli hataları görüntüleyin. Not: Bu her zaman önemli bilgileri göstermez.",
+ "fieldTextLoglevelSilent": "Hiçbiri. Bu tüm günlüğü susturur.",
+ "fieldTextMail": "Hesaplar için giriş. Ana hesap sahibinin e -posta adresi bildirim alacaktır.",
+ "fieldTextMapRtspTransportAuto": "FFMPEG'nin karar vermesine izin verin. Normalde önce UDP'yi deneyecektir.",
+ "fieldTextMapRtspTransportTCP": "UDP istenmeyen sonuçlar vermeye başlarsa buna ayarlayın.",
+ "fieldTextMapRtspTransportUDP": "FFMPEG bunu önce deniyor.",
+ "fieldTextMaxKeepDays": "Özellikle bu monitör için temizlenmeden önce videoları saklamak için gün sayısı.",
+ "fieldTextMid": "Bu, monitör için değiştirilemez bir tanımlayıcıdır. Monitör kimliğini çift tıklayıp değiştirerek bir monitörü çoğaltabilirsiniz.",
+ "fieldTextMode": "Bu, monitörün birincil görevidir.",
+ "fieldTextModeDisabled": "Aktif olmayan monitör, bu modda hiçbir işlem oluşturulmayacaktır.",
+ "fieldTextModeRecord": "Sürekli kayıt. Segmentler varsayılan olarak her 15 dakikada bir yapılır.",
+ "fieldTextModeWatchOnly": "Monitör yalnızca akış yapar, API veya dedektör tarafından aksi belirtilmedikçe kayıt gerçekleşmez.",
+ "fieldTextMpass": "Kameranızın şifresi",
+ "fieldTextMuser": "Kameranız için kullanıcı girişi",
+ "fieldTextName": "Bu, monitör için insan tarafından okunabilir görüntüleme adıdır.",
+ "fieldTextNotes": "Bu kamera için ayrılmak istediğiniz yorumlar.",
+ "fieldTextOnvifNonStandard": "Bu standart olmayan bir onvif kamera mı?",
+ "fieldTextOnvifPort": "ONVIF genellikle 8000 bağlantı noktasında çalıştırılır. Bu, kamera modelinize bağlı olarak 80 olabilir.",
+ "fieldTextPass": "Ayarlar değişikliği sırasında aynı şifreyi tutmak için boş bırakın.",
+ "fieldTextPasswordAgain": "Değiştirmek istiyorsanız şifre alanını eşleştirmelidir.",
+ "fieldTextPath": "Kameranıza giden yol",
+ "fieldTextPort": "Virgül veya menzil ile ayrı",
+ "fieldTextPortForce": "Varsayılan Web bağlantı noktasını kullanmak, RTSP gibi akışlar için otomatik geçiş yapmasına izin verebilir.",
+ "fieldTextPresetRecord": "Belirli video kodlayıcıları için önceden ayarlanmış bayrak. Kameranızın birkaç saniyede bir çöktüğünü görürseniz: boş bırakmayı deneyin.",
+ "fieldTextPresetStream": "Belirli video kodlayıcıları için önceden ayarlanmış bayrak. Kameranızın birkaç saniyede bir çöktüğünü görürseniz: boş bırakmayı deneyin.",
+ "fieldTextProbesize": "Giriş için analizleme probunun ne kadar büyük yapabileceğini belirtin. RTSP kullanıyorsanız ve akış sorunları varsa 100000'e ayarlayın.",
+ "fieldTextProtocol": "Video akışını tüketmek için kullanılacak protokol.",
+ "fieldTextRecordScaleX": "Akış görüntüsünün genişliği.",
+ "fieldTextRecordScaleY": "Akış görüntüsünün yüksekliği.",
+ "fieldTextRecordTimelapse": "JPEG tabanlı bir Timelapse oluşturun.",
+ "fieldTextRecordTimelapseMp4": "Timelapse için her günün sonunda bir MP4 dosyası oluşturun.",
+ "fieldTextRecordTimelapseWatermark": "Kaydedilen videonun çerçevelerine yakılan bir görüntü.",
+ "fieldTextRecordTimelapseWatermarkLocation": "Filigran olarak kullanılacak görüntü konumu.",
+ "fieldTextRecordTimelapseWatermarkPosition": "Kaydedilen videonun çerçevelerine yakılan bir görüntü.",
+ "fieldTextRotate": "Video akışının kayıt açısını değiştirin.",
+ "fieldTextRtmpKey": "RTMP bağlantı noktasında gelen akışlar için akış anahtarı.",
+ "fieldTextRtspTransport": "Kameranızın kullanacağı taşıma protokolü. TCP genellikle en iyi seçimdir.",
+ "fieldTextRtspTransportAuto": "FFMPEG'nin karar vermesine izin verin. Normalde önce UDP'yi deneyecektir.",
+ "fieldTextRtspTransportHTTP": "Standart bağlantı yöntemi.",
+ "fieldTextRtspTransportTCP": "UDP istenmeyen sonuçlar vermeye başlarsa buna ayarlayın.",
+ "fieldTextRtspTransportUDP": "FFMPEG bunu önce deniyor.",
+ "fieldTextSfps": "Kameranın akışını sağladığı kare hızını (FPS) belirtin.",
+ "fieldTextSignalCheck": "Müşteriniz, canlı olup olmadığını görmek için akışı ne sıklıkla kontrol eder. Bu dakikalar içinde hesaplanır.",
+ "fieldTextSignalCheckLog": "Bu sadece müşteri tarafı içindir. İstemci tarafı sinyal kontrolleri meydana geldiğinde günlük iş parçacığında görüntülenir.",
+ "fieldTextSize": "SHINOBI disk alanı miktarı, temizlemeden önce tüketilmesine izin verecektir. Bu değer megabaytlarda okunur.",
+ "fieldTextSizeFilebinPercent": "FileBin Arşivinin kullanabileceği maksimum depolama miktarının yüzdesi.",
+ "fieldTextSizeTimelapsePercent": "Timelapse çerçevelerinin kaydedilebileceği maksimum depolama miktarının yüzdesi.",
+ "fieldTextSizeVideoPercent": "Videoların kaydedebileceği maksimum depolama miktarının yüzdesi.",
+ "fieldTextSkipPing": "Bir monitör işlemi başlatılmadan önce başarılı bir ping gerekli olup olmadığını seçin.",
+ "fieldTextSnap": "JPEG'deki en son çerçeveyi alın.",
+ "fieldTextSnapSecondsInward": "saniyeler içinde",
+ "fieldTextSqllog": "FFMPEG, çok fazla veritabanı satırına yol açabilecek gereksiz verileri atmayı sevdiği için bunu dikkatle kullanın.",
+ "fieldTextSqllogNo": "Hayır varsayılandır.",
+ "fieldTextSqllogYes": "Bunu sadece tekrar eden sorunlar yaşıyorsanız yapın.",
+ "fieldTextStreamAcodec": "Akış için ses kodek.",
+ "fieldTextStreamAcodecAac": "MP4 video için kullanılır.",
+ "fieldTextStreamAcodecAc3": "MP4 video için kullanılır.",
+ "fieldTextStreamAcodecAuto": "FFMPEG'nin seçmesine izin verin.",
+ "fieldTextStreamAcodecCopy": "MP4 video için kullanılır. Çok düşük CPU kullanımı vardır, ancak bazı ses kodekleri AAC için -Strict 2 gibi özel bayraklara ihtiyaç duyar.",
+ "fieldTextStreamAcodecLibmp3lame": "MP4 video için kullanılır.",
+ "fieldTextStreamAcodecLibopus": "WebM videosu için kullanılır.",
+ "fieldTextStreamAcodecLibvorbis": "WebM videosu için kullanılır.",
+ "fieldTextStreamAcodecNoAudio": "Ses yok, bu yasal nedenlerden dolayı dünyanın bazı bölgelerinde ayarlanması gereken bir seçenektir.",
+ "fieldTextStreamFlvType": "Bu sadece Shinobi Gösterge Paneli içindir. Her iki akış yöntemi hala aktif ve kullanıma hazır.",
+ "fieldTextStreamFps": "Çerçevelerin saniyede kareler halinde müşterilere görüntülenme hızı. Varsayılan olmadığını unutmayın. Bu, yüksek bant genişliği kullanımına yol açabilir.",
+ "fieldTextStreamLoop": "Dosya akışı canlı bir akış gibi davranması için statik bir dosyayı döndürün.",
+ "fieldTextStreamQuality": "Düşük sayı daha yüksek kalite anlamına gelir. Daha yüksek sayı daha az kalite anlamına gelir.",
+ "fieldTextStreamRotate": "Video akışının görüntüleme açısını değiştirin.",
+ "fieldTextStreamScaleX": "İşlemden sonra çıktı olan akış görüntüsünün genişliği.",
+ "fieldTextStreamScaleY": "İşlemden sonra çıktı olan akış görüntüsünün yüksekliği.",
+ "fieldTextStreamTimestamp": "Video akışının çerçevelerine yakılan bir saat.",
+ "fieldTextStreamTimestampBoxColor": "Timstamp zemin rengi.",
+ "fieldTextStreamTimestampColor": "Timstamp metin rengi.",
+ "fieldTextStreamTimestampFont": "Zaman damganızı şekillendirmek için yazı tipi dosyası.",
+ "fieldTextStreamTimestampFontSize": "Pt.",
+ "fieldTextStreamTimestampX": "Zaman damgasının ufiztonal konumu",
+ "fieldTextStreamTimestampY": "Zaman damgasının dikey konumu",
+ "fieldTextStreamType": "Video akışını tüketmek için kullanılacak yöntem.",
+ "fieldTextStreamTypeBase64OverWebsocket": "Base64 kodlu çerçeveleri WebSocket üzerinden gönderme. Bu önbelleklemeyi önler, ancak ses yoktur.",
+ "fieldTextStreamTypeFLV": "WebSocket üzerinden FLV kodlu çerçeveler gönderme.",
+ "fieldTextStreamTypeHLS(includesAudio)": "Facebook canlı akışlarına benzer bir yöntem. Ses içerir Giriş sağlarsa. Yaklaşık 4-6 saniyelik bir gecikme vardır, çünkü bu yöntem segmentleri kaydeder, daha sonra onları oluştururken itmek yerine müşteriye iter.",
+ "fieldTextStreamTypeMJPEG": "Standart hareket JPEG görüntüsü. Ses yok.",
+ "fieldTextStreamTypePoseidon": "Poseidon, Kevin Godell'ın MP4 işleme kodu üzerine kurulmuştur. Bir akış MP4 dosyasını simüle eder, ancak canlı akışın verilerini kullanarak. Ses içerir. Bazı tarayıcılar normal bir MP4 dosyası gibi oynayabilir. HTTP veya WebSocket üzerinden akışlar.",
+ "fieldTextStreamVcodec": "Akış için video kodek.",
+ "fieldTextStreamVcodecAuto": "FFMPEG'nin seçmesine izin verin.",
+ "fieldTextStreamVcodecCopy": "MP4 video için kullanılır. Çok düşük CPU kullanımı vardır, ancak video filtreleri kullanamaz ve dosya boyutları devasa olabilir. Bu seçeneği kullanırken MP4 ayarları kamera tarafınızı ayarlamak için en iyisi.",
+ "fieldTextStreamVcodecLibx264": "MP4 video için kullanılır.",
+ "fieldTextStreamVcodecLibx265": "MP4 video için kullanılır.",
+ "fieldTextStreamVf": "Akış bölümünü etkilemek için FFMPEG video filtrelerini bu kutuya yerleştirin. Boşluksuz.",
+ "fieldTextStreamWatermark": "Video akışının çerçevelerine yakılan bir görüntü.",
+ "fieldTextStreamWatermarkLocation": "Filigran olarak kullanılacak görüntü konumu.",
+ "fieldTextStreamWatermarkPosition": "Video akışının çerçevelerine yakılan bir görüntü.",
+ "fieldTextTimestamp": "Kaydedilen videonun çerçevelerine yakılan bir saat.",
+ "fieldTextTimestampBoxColor": "Timstamp zemin rengi.",
+ "fieldTextTimestampColor": "Timstamp metin rengi.",
+ "fieldTextTimestampFont": "Zaman damganızı şekillendirmek için yazı tipi dosyası.",
+ "fieldTextTimestampFontSize": "Pt.",
+ "fieldTextTimestampX": "Zaman damgasının ufiztonal konumu",
+ "fieldTextTimestampY": "Zaman damgasının dikey konumu",
+ "fieldTextTvChannel": "Bu monitör TV kanalı özellikleri etkin olacaktır. TV kanalı listenizde görüntüleyebileceksiniz.",
+ "fieldTextTvChannelGroupTitle": "Kanal için özel bir grup.",
+ "fieldTextTvChannelId": "Kanal için özel bir kimlik.",
+ "fieldTextType": "Video akışını tüketmek için kullanılacak yöntem.",
+ "fieldTextTypeDashcam(StreamerV2)": "WebSocket WebM tabanlı P2P akışı.",
+ "fieldTextTypeH.264/H.265/H.265+": "Bazen ses içeren yüksek kaliteli bir video telini okumak.",
+ "fieldTextTypeHLS(.m3u8)": "Bazen ses içeren yüksek kaliteli bir video telini okumak.",
+ "fieldTextTypeJPEG": "Bir URL'den anlık görüntüleri okumak ve onlardan bir akış ve/veya video yapmak.",
+ "fieldTextTypeLocal": "Yakalama kartlarını, web kameralarını veya entegre kameraları okumak.",
+ "fieldTextTypeMJPEG": "Çerçeve kullanımı hariç JPEG'ye benzer şekilde Shinobi değil FFMPEG tarafından yapılır.",
+ "fieldTextTypeMPEG4(.mp4/.ts)": "Statik bir dosya. Daha düşük bir oranda okuyun ve gerçek bir canlı akış için kullanılmamalıdır.",
+ "fieldTextTypeMxPEG": "Mobotix Mjpeg akışı",
+ "fieldTextTypeRTMP": "Buraya bağlanmayı öğrenin: Makale: RTMP aracılığıyla akışları Shinobi'ye nasıl itilir ",
+ "fieldTextTypeShinobiStreamer": "WebSocket JPEG tabanlı P2P akışı.",
+ "fieldTextVcodec": "Kayıt için video kodek.",
+ "fieldTextVf": "Kayıt bölümünü etkilemek için FFMPEG video filtrelerini bu kutuya yerleştirin. Boşluksuz.",
+ "fieldTextWallClockTimestampIgnore": "Gelen tüm kamera verilerini sunucu süresi yerine kamera zamanına dayandırın.",
+ "fieldTextWatchdogReset": "Tetik kaydında bir örtüşme varsa, sıfırlaması gerekir.",
+ "fieldTextWatermark": "Kaydedilen videonun çerçevelerine yakılan bir görüntü.",
+ "fieldTextWatermarkLocation": "Filigran olarak kullanılacak görüntü konumu.",
+ "fieldTextWatermarkPosition": "Kaydedilen videonun çerçevelerine yakılan bir görüntü.",
+ "fieldTextWidth": "Akış görüntüsünün genişliği.",
+ "fire hydrant": "yangın hidrant",
+ "flv": "flv",
+ "for Global Access": "Global erişim için",
+ "fork": "çatal",
+ "frisbee": "frizbi",
+ "getAMonitor": "Bir Monitör Alın",
+ "getATvChannel": "Monitör için TV kanalları alın",
+ "getATvChannelText": "Bir .m3u8 çalma listesinde tek bir monitörün kullanılabilir H.264 akışlarını alın.",
+ "getAllMonitors": "Tüm Monitörleri Alın",
+ "getAllTvChannels": "Tüm TV kanallarını alın",
+ "getAllTvChannelsText": "Tüm H.264 akışlarını .m3u8 çalma listesinde alın. Bu listede akışlarını görmek için monitörünüzün ayarlarındaki TV kanalı seçeneğini etkinleştirin.",
+ "getUserInfo": "Kullanıcı Bilgileri Al",
+ "getVideos": "Videolar Alın",
+ "getVideosForMonitor": "Monitör için videolar alın",
+ "giraffe": "zürafa",
+ "h264_cuvid": "H.264 Cuvid",
+ "h264_mmal": "H.264 (Raspberry Pi)",
+ "h264_nvenc": "H.264 NVENC (Nvidia HW Accel)",
+ "h264_omx": "H.264 Openmax (Raspberry Pi)",
+ "h264_qsv": "H.264 (Hızlı Senkronizasyon Videosu)",
+ "h264_vaapi": "H.264 VA-API (Intel HW Accel)",
+ "h265BrowserText1": "Bir H.265 dosyası oynamaya çalışıyorsanız, indirmeniz ve VLC gibi başka bir uygulamada açmanız gerekebilir.",
+ "hair drier": "saç kurutma makinesi",
+ "handbag": "el çantası",
+ "hevc_cuvid": "H.265 Cuvid",
+ "hevc_nvenc": "H.265 NVENC (Nvidia HW Accel)",
+ "hevc_qsv": "H.265 (Hızlı Senkronizasyon Videosu)",
+ "hevc_vaapi": "H.265 VA-API (Intel HW Accel)",
+ "hlsOptions": "HLS Seçenekleri",
+ "hlsOptionsInvalid": "HLS seçenekleri geçersiz",
+ "horse": "at",
+ "hot dog": "sosisli sandviç",
+ "hour": "saat",
+ "hours": "saat",
+ "hwaccel": "İvme motoru",
+ "hwaccel_device": "HWACCEL Cihazı",
+ "hwaccel_vcodec": "Video kod çözücü",
+ "in": "içinde",
+ "in Days": "Günlerde",
+ "in seconds": "saniyeler içinde",
+ "keyId": "Anahtar kimlik",
+ "keyboard": "tuş takımı",
+ "kite": "uçurtma",
+ "knife": "bıçak",
+ "laptop": "dizüstü bilgisayar",
+ "lastLogin": "Son giriş",
+ "libmp3lame": "Libmp3Lame",
+ "libopus": "Libopus",
+ "libvorbis (Default)": "libvorbis (varsayılan)",
+ "libvpx (Default)": "libvpx (varsayılan)",
+ "libvpx-vp9": "libvpx-vp9",
+ "libx264": "libx264",
+ "libx264 (Default)": "libx264 (varsayılan)",
+ "libx265": "libx265",
+ "liveGridDescription": "Canlı ızgara, Shinobi için çoklu akış ekranıdır. Bu görüntüleme yöntemi esas olarak masaüstü için tasarlanmıştır.",
+ "loginHandleUnbound": "Oturum açma bu hesaptan bağlantısız.",
+ "microwave": "mikrodalga",
+ "migrateText1": " Giriş Türü ayrıştırılamadı. Lütfen manuel olarak ayarlayın.",
+ "minute": "dakika",
+ "minutes": "dakikalar",
+ "mjpeg_cuvid": "Mjpeg cuvid",
+ "modifyVideoText1": "Yöntem yoktur. URL'nin son değerinin boş olmadığından emin olun.",
+ "monSavedButNotCopied": "Monitörünüz kaydedildi, ancak başka bir monitöre kopyalanmadı.",
+ "monitorConfigFinderDescription": "Bu araç, topluluk tarafından yayınlanan kameralar için yapılandırmalar aramanıza yardımcı olacaktır. Hepsi shinobihub . Sizinkini de gönderebilirsiniz, topluluğa gerçekten yardımcı olur :)",
+ "monitorEditFailedMaxReached": "Hesabınız oluşturulabilecek maksimum kamera sayısına ulaştı. Bunun değişmesini istiyorsanız bir yöneticiyle konuşun.",
+ "monitorEditText1": "Geçersiz veriler, bunun geçerli bir içe aktarma dizesi olduğunu kontrol edin.",
+ "monitorEditText2": "Geçersiz ayrıntılar dizesi. Bir JSON dizesi olduğunu görmek için kontrol edin ve normal bir nesne değil.",
+ "monitorGetText1": "Eksik istek, son eğik çizgi URL'de kaldırın veya kabul edilebilir bir değer koyun.",
+ "monitorStateNotEnoughChanges": "Bir ön ayara eklemeye çalışmadan önce monitör yapılandırmanızda bir değişiklik yapmanız gerekir.",
+ "monitorStatesError": "Ön Ayarlar Hatasını Monitör",
+ "months": "aylar",
+ "motorcycle": "motosiklet",
+ "mouse": "fare",
+ "mpeg2_mmal": "MPEG-2 (Raspberry Pi)",
+ "mpeg2_qsv": "MPEG2 (Hızlı Senkronizasyon Videosu)",
+ "mpeg4_cuvid": "Mpeg4 cuvid",
+ "mpeg4_mmal": "MPEG-4 (Raspberry Pi)",
+ "noLoginTokensAdded": "Bu hesapla ilişkili alternatif girişler yoktur.",
+ "noSpecialCharacters": "Hiçbir boşluk veya özel karakterler.",
+ "noTriggerText": "Zaman aşımı döneminden sonra hareket tespit edilmediyse, bir anlaşmazlık bildirimi alacaksınız.",
+ "noUndoForAction": "Bu eylemi geri alamazsınız.",
+ "notActivatedText": "Kurulumunuz başarısız olmuştur.",
+ "notEnoughFramesText1": "Derleme için yeterli çerçeve yok.",
+ "notPermitted1": "Bu eyleme hesabınızın yöneticisi tarafından izin verilmez. '",
+ "on": "üzerinde",
+ "on Error": "hatalı",
+ "on Event": "olayda",
+ "onvifdeviceManagerGlobalTip": "ONVIF, kameranın dahili ayarlarının değiştirilmesine izin verir. OnVIF bir şemsiye terimidir, maalesef birçok şey anlamına gelebilir. Bu durumda, bu araçta bir seçenek görebilirsiniz, ancak düzenlenebilir olmayabilir. Bunun nedeni genellikle kamera satıcısının bu yöntemi eklememesi veya amaçlanan kullanımından sapmasıdır. Bu durumlarda, kamera satıcısının öngörülen yöntemi aracılığıyla kameranın yapılandırmasını girmeniz gerekecektir, bu genellikle web tarayıcınızdaki kameranın IP adresini açar.",
+ "onvifdeviceSavedFoundErrorText": "Bazı ayarlar önceki bir değere dönmüş olabilir. OnVIF aracılığıyla bu kamera ile değiştirilmiş seçeneğin mevcut olmaması mümkündür.",
+ "onvifdeviceSavedText": "Kameranın dahili ayarları kaydedildi. Bu değişikliklerin geçerli olması için kamerayı yeniden başlatmanız gerekebilir.",
+ "openImagesDownloadConfirm": "OpenImages'ten resimleri ve sınırlayıcı kutuları (önceden ayarlanmış matrisler) indirmeye başlamak istediğinizden emin misiniz?",
+ "openImagesDownloadConfirmStop": "Eğitimi durdurmak istediğinden emin misiniz?",
+ "opencl": "Opencl",
+ "opencvCascadesText": "Burada hiçbir şey görmezseniz, bu paketin Cascades paketini indirin. Bunları eklentileri/opencv/cascades içine bırakın, ardından Yenileme tuşuna basın.",
+ "orange": "Portakal",
+ "oven": "fırın",
+ "p2pServerNotSelectedText": "Listeden bir sunucu seçin ve Kaydet'e basın. 10 saniye bekleyin, ardından gösterge tablosuna uzaktan açmaya çalışın.",
+ "p2pSettingsText1": "Uygulanacak değişiklikler için bu sayfayı yenilemeniz gerekecektir.",
+ "parking meter": "parkmetre",
+ "performanceOptimizeText1": "Kameranız H.264 akış verileri sağlıyor. Akış türünü kopyalamak için HLS, Poseidon ve Video Codec olarak ayarlayabilirsiniz.",
+ "person": "kişi",
+ "pizza": "Pizza",
+ "possibleInternalError": "Olası dahili hata",
+ "postDataBroken": "JSON formatını kontrol edin. 'Veriler' altında yayılmış ve tanımlandığından emin olun",
+ "potted plant": "saksı bitkisi",
+ "powerVideoEventLimit": "Yüksek bir olay sınırı belirlediniz. Bu isteği yapmak istediğinden emin misiniz?",
+ "privateKey": "Özel anahtar",
+ "qsv": "QSV",
+ "rebootingCamera": "Kamerayı yeniden başlatma",
+ "refrigerator": "buzdolabı",
+ "remote": "uzak",
+ "restartRequired": "Değişikliklerin yürürlüğe girmesi için Shinobi çekirdeğinin yeniden başlatılması gereklidir.",
+ "sandwich": "sandviç",
+ "scissors": "makas",
+ "separateByCommasOrRange": "Virgül veya menzil ile ayrı",
+ "setMaxStorageAmountText": "Maksimum depolama miktarınızı soldaki hesap ayarlarınızda ayarlamanız gerekir. Profil bölümünün altındaki seçeneği bulun. Varsayılan 10 GB'dır.",
+ "sheep": "koyun",
+ "sink": "lavabo",
+ "sizePurgeLockedText": "Boyut temizleme kilidi (deleteOvermax) kilidini açamadı. Şimdi kilidini açmak ...",
+ "skateboard": "kaykay",
+ "skipPingText1": "\"Ping at\" yı evet olarak ayarlamayı deneyin.",
+ "skis": "kayaklar",
+ "snowboard": "snowboard",
+ "sorryNo": "Üzgünüm hayır",
+ "sorryNothingWasFound": "Üzgünüm, hiçbir şey bulunamadı.",
+ "spoon": "kaşık",
+ "sports ball": "spor topu",
+ "startUpText0": "KONTROL DISK İSTİYOR ..",
+ "startUpText1": "Kullanılan disk tamamlandı.",
+ "startUpText2": "Tüm kullanıcılar kontrol etti, dosyaları açmak ve dosyaları kullanıcı limiti üzerinden kaldırmayı bekleyin",
+ "startUpText3": "Bitmemiş video vermek için bir süre kontrol edin. 3 saniye.",
+ "startUpText4": "Monitörler Başlat ... Lütfen bekleyin ...",
+ "startUpText5": "Shinobi hazır.",
+ "startUpText6": "Bulunan ve eklenen yetim videolar",
+ "stop sign": "dur işareti",
+ "subAccountManager": "Alt Hesap Yöneticisi",
+ "substreamConnectionText": "Yukarıda ayarlanan ana bağlantı bilgilerini kullanmasını istiyorsanız bağlantı detayını olduğu gibi bırakabilirsiniz.",
+ "substreamOutputText": "Burada isteğe bağlı akışın yapılandırmasını ayarlayabilirsiniz. akış türlerinin gecikmesi. ",
+ "substreamText": "Bu, canlı akışı görüntülemek için isteğe bağlı bir yöntemdir. Görüntüleme işleminin yalnızca birisi izlediğinde veya düşük ve yüksek çözünürlük arasında geçiş yapmak için kullanılacak şekilde kullanılabilir şekilde yapabilirsiniz.",
+ "suitcase": "bavul",
+ "superAdminText": "\"Super.json\" yok. Lütfen \"Super.smon\" adını \"Super.json\" olarak yeniden adlandırın.",
+ "superAdminTitle": "Shinobi: Süper Yönetici",
+ "surfboard": "sörf tahtası",
+ "teddy bear": "Oyuncak ayı",
+ "tennis racket": "Tenis raketi",
+ "tie": "bağlamak",
+ "toaster": "tost makinası",
+ "toilet": "tuvalet",
+ "tokenNotUserBound": "Bu giriş kolu bu sunucudaki bir kullanıcıya bağlı değildir!",
+ "tokenNotUserBoundPt2": "Kimlik bilgilerinizi yazın ve ardından hızlı bir şekilde bağlantı kurmak için Google Oturum Aç düğmesini kullanın.",
+ "toothbrush": "diş fırçası",
+ "total": "Toplam",
+ "traffic light": "trafik ışığı",
+ "train": "tren",
+ "truck": "kamyon",
+ "tv": "televizyon",
+ "umbrella": "şemsiye",
+ "undoAllUnsaveChanges": "Bunu yapmak istediğinden emin misin? Bu, kaydedilmemiş tüm değişiklikleri geri alacaktır.",
+ "unexpectedExitText": "Bu çıkış hakkında bilgi bu kütükten önce bulunacaktır. Ayrıca burada işlem çöktüğünde kullanılan FFMPEG komutu.",
+ "updateCamerasInternalSettings": "Kameranın dahili ayarlarını güncelleyin?",
+ "updateKeyText1": "\"UpdateKey\" \"Conf.json\" da eksik, siz ekleyene kadar bu şekilde güncellemeler yapamaz.",
+ "updateKeyText2": "\"UpdateKey\" yanlış.",
+ "updateNotice1": "Shinobi'nin güncellenmesi, dosyaların üzerine yazma anlamına gelir. Herhangi bir dosyayı kendiniz değiştirdiyseniz, Shinobi'yi manuel olarak güncellemelisiniz. Yapılandırmalarınız ve video dosyalarınız değiştirilmeyecektir.",
+ "useSubStreamOnlyWhenWatching": "Yalnızca izlerken alt akışı kullanın",
+ "vaapi": "Vaapi (VA-api)",
+ "vase": "vazo",
+ "vda": "VDA (Apple VDA Donanım Hızlanması)",
+ "vdpau": "VDPAU",
+ "videoBuildingText1": "Video şu anda oluşturuluyor. Daha sonra tekrar kontrol edin.",
+ "videotoolbox": "video kutusu",
+ "vp8_cuvid": "VP8 NVENC (Nvidia HW Accel)",
+ "vp8_qsv": "VP8 (Hızlı Senkronizasyon Videosu)",
+ "vp9_cuvid": "VP9 NVENC (Nvidia HW Accel)",
+ "wannaReset": "Sıfırlamak ister misiniz?",
+ "willTriggerAnEvent": "Bir etkinliği tetikleyecek",
+ "wine glass": "şarap bardağı",
+ "years": "yıllar",
+ "zebra": "zebra"
+}
\ No newline at end of file
diff --git a/languages/vi.json b/languages/vi.json
new file mode 100644
index 00000000..22d638ba
--- /dev/null
+++ b/languages/vi.json
@@ -0,0 +1,1647 @@
+{
+ "\"No Motion\" Detector": "Máy dò \"Không chuyển động\"",
+ "# of Allow MJPEG Clients": "# cho phép các máy khách MJPEG 0 cho vô hạn ",
+ "'Already Installing...'": "'Đã cài đặt ...'",
+ "180 Degrees": "180 độ",
+ "2-Factor Authentication": "Xác thực 2 yếu tố",
+ "90 Clockwise": "90 theo chiều kim đồng hồ",
+ "90 Clockwise and Vertical Flip": "90 theo chiều kim đồng hồ và lật dọc",
+ "90 Counter Clockwise and Vertical Flip (default)": "90 ngược chiều kim đồng hồ và lật dọc (mặc định)",
+ "AND": "VÀ",
+ "API": "API",
+ "API Key": "Mã API",
+ "API Key Action Failed": "Hành động khóa API không thành công",
+ "API Key Added": "Khóa API được thêm vào",
+ "API Key Deleted": "Khóa API bị xóa",
+ "API Keys": "Khóa API",
+ "APIKeyAddedText": "Bạn có thể sử dụng khóa này ngay bây giờ.",
+ "APIKeyDeletedText": "Key đã bị xóa. Nó sẽ không còn hoạt động nữa.",
+ "ASC": "TĂNG DẦN",
+ "Accelerator": "Máy gia tốc",
+ "Account Info": "Thông tin tài khoản",
+ "Account Information": "thông tin tài khoản",
+ "Account Privileges": "Đặc quyền tài khoản",
+ "Account Save": "Tài khoản lưu",
+ "Account Settings": "Cài đặt tài khoản",
+ "AccountEditText1": "Không thể chỉnh sửa. Làm mới trang nếu vấn đề tiếp tục.",
+ "Accounts": "Tài khoản",
+ "Action for Selected": "Hành động cho đã chọn",
+ "Activated": "Kích hoạt",
+ "Active Monitors": "Màn hình hoạt động",
+ "Add": "Thêm vào",
+ "Add All": "Thêm tất cả",
+ "Add Camera": "Thêm camera",
+ "Add Cameras": "Thêm máy ảnh",
+ "Add Channel": "Thêm kênh",
+ "Add Input Feed": "Thêm nguồn cấp dữ liệu đầu vào",
+ "Add Map": "Thêm bản đồ",
+ "Add Monitor": "Thêm màn hình",
+ "Add New": "Thêm mới",
+ "AddToPreset": "Thêm vào đặt trước",
+ "Additional Inputs": "Đầu vào bổ sung",
+ "Admin": "Quản trị viên",
+ "Advanced": "Trình độ cao",
+ "After": "Sau",
+ "Again": "Lần nữa",
+ "Age": "Già đi",
+ "Alert Sound": "Âm thanh cảnh báo",
+ "Alert Sound Delay": "Cảnh báo độ trễ âm thanh",
+ "All Logs": "Tất cả các bản ghi",
+ "All Monitors": "Tất cả các màn hình",
+ "All Monitors and Privileges": "Tất cả các màn hình và đặc quyền",
+ "All Privileges": "Tất cả các đặc quyền",
+ "All Warnings": "Tất cả các cảnh báo",
+ "All streams in first feed": "Tất cả các luồng trong nguồn cấp dữ liệu đầu tiên",
+ "Allow API Trigger": "Cho phép kích hoạt API",
+ "Allow Next Alert": "Cho phép cảnh báo tiếp theo",
+ "Allow Next Command": "Cho phép lệnh tiếp theo",
+ "Allow Next Discord Alert": "Cho phép cảnh báo Discrord tiếp theo trong vài phút ",
+ "Allow Next Email": "Cho phép email tiếp theo trong vài phút ",
+ "Allow Next Trigger": "Cho phép kích hoạt tiếp theo",
+ "Allow Next Webhook": "Cho phép webhook tiếp theo",
+ "Allowed IPs": "Cho phép IPS",
+ "Already Processing": "Đã xử lý",
+ "Already exists": "Đã tồn tại",
+ "Alternate Logins": "Đăng nhập thay thế",
+ "Always": "Luôn luôn",
+ "Amazon S3": "Amazon S3",
+ "Amazon S3 Upload Error": "Amazon S3 Lỗi tải lên",
+ "Analyzation Duration": "Thời gian phân tích",
+ "AppNotEnabledText": "Ứng dụng không được bật, bật nó trong cài đặt tài khoản của bạn.",
+ "April": "Tháng tư",
+ "Archive": "Lưu trữ",
+ "Are you sure?": "Bạn có chắc không?",
+ "Attach Snapshot": "Đính kèm ảnh chụp nhanh",
+ "Attach Video Clip": "Đính kèm video clip",
+ "Audio": "Âm thanh",
+ "Audio Bit Rate": "Tốc độ âm thanh",
+ "Audio Codec": "Audio codec",
+ "Audio Detection": "Phát hiện âm thanh",
+ "Audio Detector": "Máy dò âm thanh",
+ "Audio stream only from first feed": "Chỉ luồng âm thanh từ nguồn cấp dữ liệu đầu tiên",
+ "Audio streams only": "Chỉ luồng âm thanh",
+ "August": "Tháng tám",
+ "Authenticate": "Xác thực",
+ "Authenticated": "Xác thực",
+ "Authentication Failed": "Quá trình xác thực đã thất bại",
+ "Auto": "Tự động",
+ "Automatic": "Tự động",
+ "Automatic Checking Cancelled": "Kiểm tra tự động bị hủy",
+ "Automatic Codec Repair": "Sửa chữa codec tự động",
+ "Automatic Field Fill": "Lĩnh vực tự động điền",
+ "Autosave": "Tự động",
+ "Back": "Mặt sau",
+ "Backblaze B2": "Backblaze B2",
+ "Backblaze Error": "Lỗi backblaze",
+ "BacklightCompensation": "Thay đổi đèn nền",
+ "Backup": "Sao lưu",
+ "Bandwidth": "Băng thông",
+ "Base64 over Websocket": "Base64 trên WebSocket",
+ "Basic Authentication": "Xác thực cơ bản",
+ "Batch": "Lô hàng",
+ "Before": "Trước",
+ "Bind Credentials": "BIND Thông tin đăng nhập (Mật khẩu)",
+ "BitrateLimit": "Giới hạn bitrate",
+ "Blank for No Change": "Trống không thay đổi",
+ "Bottom Left": "Phía dưới bên trái",
+ "Bottom Right": "Góc phải ở phía dưới",
+ "Brightness": "độ sáng",
+ "Browser Console Log": "Nhật ký bảng điều khiển trình duyệt",
+ "Bucket": "Gầu múc",
+ "Buffer Preview": "Xem trước bộ đệm",
+ "Build": "Xây dựng",
+ "Build Video": "Xây dựng video",
+ "Building": "Xây dựng",
+ "CPU": "CPU",
+ "CPU indicator will not work. Continuing...": "Chỉ báo CPU sẽ không hoạt động. Tiếp tục ...",
+ "CPU used by this stream": "CPU được sử dụng bởi luồng này",
+ "CSS": "CSS Kiểu bảng điều khiển của bạn. ",
+ "Calendar": "Lịch",
+ "Call Method": "Phương thức gọi",
+ "Camera Password": "Mật khẩu camera",
+ "Camera Username": "Tên người dùng máy ảnh",
+ "Camera is not recording": "Camera không ghi",
+ "Camera is not running": "Camera không chạy",
+ "Camera is not streaming": "Camera không phát trực tuyến",
+ "CameraNotRecordingText": "Cài đặt có thể không tương thích. Kiểm tra bộ mã hóa. Khởi động lại ...",
+ "Can Authenticate Websocket": "Có thể xác thực WebSocket",
+ "Can Change User Settings": "Có thể thay đổi cài đặt người dùng",
+ "Can Control Monitors": "Có thể kiểm soát màn hình",
+ "Can Create and Delete Monitors": "Có thể tạo và xóa màn hình",
+ "Can Delete Videos": "Có thể xóa video",
+ "Can Delete Videos and Events": "Có thể xóa video và sự kiện",
+ "Can Edit Monitor": "Có thể chỉnh sửa giám sát",
+ "Can Get Logs": "Có thể nhận được nhật ký",
+ "Can Get Monitors": "Có thể có màn hình",
+ "Can View Logs": "Có thể xem nhật ký",
+ "Can View Monitor": "Có thể xem màn hình",
+ "Can View Snapshots": "Có thể xem ảnh chụp nhanh",
+ "Can View Streams": "Có thể xem các luồng",
+ "Can View Videos": "Có thể xem video",
+ "Can View Videos and Events": "Có thể xem video và sự kiện",
+ "Can edit Max Days": "Có thể chỉnh sửa ngày tối đa",
+ "Can edit Max Storage": "Có thể chỉnh sửa lưu trữ tối đa không",
+ "Can edit how long to keep Events": "Có thể chỉnh sửa thời gian để giữ các sự kiện",
+ "Can edit how long to keep Logs": "Có thể chỉnh sửa thời gian để giữ nhật ký",
+ "Can use Admin Panel": "Có thể sử dụng bảng quản trị",
+ "Can use Amazon S3": "Có thể sử dụng Amazon S3",
+ "Can use Discord Bot": "Có thể sử dụng bot discord",
+ "Can use LDAP": "Có thể sử dụng LDAP",
+ "Can use SFTP": "Có thể sử dụng SFTP",
+ "Can use Wasabi Hot Cloud Storage": "Có thể sử dụng lưu trữ đám mây nóng wasabi",
+ "Can use WebDAV": "Có thể sử dụng webdav",
+ "Can't Connect": "Không thể kết nối",
+ "Cannot watch a monitor that isn't running.": "Không thể xem một màn hình không chạy.",
+ "Cards": "thẻ",
+ "Carousel in Background": "Băng chuyền trong nền",
+ "Center": "Trung tâm Địa chỉ URL ",
+ "Channel": "Kênh",
+ "Channel ID": "kênh ID",
+ "Chat on Discord": "Trò chuyện về Discord",
+ "Check": "Kiểm tra",
+ "Check Signal Interval": "Kiểm tra khoảng thời gian tín hiệu tính bằng phút ",
+ "Check for Motion First": "Kiểm tra chuyển động trước",
+ "Check the Channel ID": "Kiểm tra ID kênh",
+ "Check the Recipient ID": "Kiểm tra ID người nhận",
+ "Clear": "Thông thoáng",
+ "Clear Recorder Process": "Xóa quy trình ghi",
+ "Close": "Đóng",
+ "Close All Monitors": "Đóng tất cả các màn hình",
+ "Closed": "Đóng",
+ "Cloud": "Mây",
+ "Codec Mismatch": "Codec không phù hợp",
+ "Color Threshold": "Ngưỡng màu",
+ "ColorSaturation": "Độ bão hòa màu",
+ "Command": "Yêu cầu",
+ "Command on Trigger": "Lệnh trên kích hoạt",
+ "Common Objects": "Những vật thông thường",
+ "Complete Stream URL": "Hoàn thành URL luồng",
+ "Conditions": "Điều kiện",
+ "Confidence": "Sự tự tin",
+ "Confidence of Detection": "Tự tin phát hiện",
+ "Configuration": "Cấu hình",
+ "Confirm": "Xác nhận",
+ "Connected": "Kết nối",
+ "Connected Users": "Người dùng được kết nối",
+ "Connection": "Sự liên quan",
+ "Connection Type": "Kiểu kết nối",
+ "Connection timed out": "Kết nối quá hạn",
+ "Contains": "Chứa",
+ "Contrast": "Tương phản",
+ "Control": "Điều khiển",
+ "Control Error": "Lỗi kiểm soát",
+ "ControlErrorText1": "Điều khiển không được bật",
+ "ControlErrorText2": "Kiểm tra chi tiết kết nối của bạn. Bạn có thể cần phải trỏ URL cơ sở tại cổng 8000 hoặc 80. Kiểm tra thông tin xác thực của bạn.",
+ "Controllable": "Có thể kiểm soát được",
+ "Controls and Logs": "Điều khiển và nhật ký",
+ "Copied": "Đã sao chép",
+ "Copied to Clipboard": "Sao chép vào clipboard",
+ "Copy": "Sao chép",
+ "Copy Connection Settings": "Sao chép cài đặt kết nối",
+ "Copy Custom Settings": "Sao chép cài đặt tùy chỉnh",
+ "Copy Detector Settings": "Cài đặt máy dò sao chép",
+ "Copy Group Settings": "Sao chép cài đặt nhóm",
+ "Copy Input Settings": "Sao chép cài đặt đầu vào",
+ "Copy JPEG API Settings": "Sao chép cài đặt API JPEG",
+ "Copy Logging Settings": "Sao chép cài đặt ghi nhật ký",
+ "Copy Mode": "Chế độ sao chép",
+ "Copy Recording Settings": "Sao chép cài đặt ghi âm",
+ "Copy Remote Link": "Sao chép liên kết từ xa",
+ "Copy Settings": "Cài đặt sao chép",
+ "Copy Stream Channel Settings": "Sao chép cài đặt kênh phát trực tuyến",
+ "Copy Stream Channels": "Sao chép các kênh phát trực tuyến",
+ "Copy Stream Settings": "Sao chép cài đặt luồng",
+ "Copy Stream URL": "Sao chép url luồng",
+ "Copy Timelapse Settings": "Sao chép cài đặt Timelapse",
+ "Copy to Settings": "Sao chép vào Cài đặt",
+ "Cores": "Lõi",
+ "Could not create Bucket.": "Không thể tạo ra xô.",
+ "Count Objects": "Đếm đối tượng",
+ "Count Objects only inside Regions": "Chỉ đếm các đối tượng bên trong các vùng",
+ "Country of Plates": "Đất nước của tấm",
+ "Counts of Motion": "Đếm chuyển động",
+ "Create Sub-Accounts at /admin": "Tạo các tài khoản phụ AT /admin",
+ "Creating New Account": "Tạo tài khoản mới",
+ "Creation Interval": "Khoảng thời gian sáng tạo",
+ "Current": "Hiện hành",
+ "Currently Active": "Hiện đang hoạt động",
+ "Currently viewing": "Hiện đang xem",
+ "Custom": "Tập quán",
+ "Custom Auto Load": "Tải tự động tùy chỉnh",
+ "Custom Base URL": "URL cơ sở tùy chỉnh Để trống để sử dụng URL máy chủ ",
+ "Custom Endpoint": "Điểm cuối tùy chỉnh",
+ "DB Lost.. Retrying..": "Cơ sở dữ liệu bị mất .. thử lại ..",
+ "DESC": "Desc",
+ "DHCP": "DHCP",
+ "DNS": "DNS",
+ "Daily Events": "Sự kiện hàng ngày",
+ "Dashboard": "bảng điều khiển",
+ "Dashboard Language": "Ngôn ngữ bảng điều khiển",
+ "Dashcam": "Dashcam",
+ "Dashcam (Streamer v2)": "Dashcam (Streamer v2)",
+ "Database": "Cơ sở dữ liệu",
+ "Database Not Found": "Cơ sở dữ liệu không tìm thấy",
+ "Database row does not exist": "Hàng cơ sở dữ liệu không tồn tại",
+ "Date": "Ngày tháng",
+ "Date Added": "Ngày thêm",
+ "Date Range": "Phạm vi ngày",
+ "Date Updated": "Ngày cập nhật",
+ "Date and Time": "Ngày và giờ",
+ "DateTimeType": "Quản lý ngày",
+ "DaylightSavings": "Tiết kiệm ánh sáng ban ngày",
+ "Days": "Ngày",
+ "Debug": "Gỡ lỗi",
+ "December": "Tháng 12",
+ "Default": "Mặc định",
+ "Delay for Snapshot": "Trì hoãn cho ảnh chụp nhanh",
+ "Delete": "Xóa bỏ",
+ "Delete Camera": "Xóa camera",
+ "Delete Filter": "Xóa bộ lọc",
+ "Delete Logs": "Xóa nhật ký",
+ "Delete Matches": "Xóa các trận đấu",
+ "Delete Monitor": "Xóa màn hình",
+ "Delete Monitor State?": "Xóa trạng thái giám sát",
+ "Delete Monitor States Preset": "Xóa các trạng thái màn hình được đặt trước",
+ "Delete Monitors and Files": "Xóa màn hình và tệp",
+ "Delete Motionless Video": "Xóa video bất động",
+ "Delete Motionless Videos (Record)": "Xóa video bất động (ghi)",
+ "Delete Region": "Xóa khu vực",
+ "Delete Schedule": "Xóa lịch trình",
+ "Delete Selected Videos": "Xóa các video đã chọn",
+ "Delete Timelapse Frame": "Xóa khung thời gian",
+ "Delete Video": "Xóa video",
+ "Delete selected": "Xóa đã chọn",
+ "DeleteMonitorText": "Bạn có muốn xóa màn hình này không? Bạn không thể phục hồi nó. Bạn có thể chọn cho các tệp để ở trong hệ thống tập tin. Nếu bạn chọn tạo lại một màn hình có cùng ID, các video và sự kiện sẽ được hiển thị trong bảng điều khiển.",
+ "DeleteMonitorsText": "Bạn có muốn xóa các màn hình này? Bạn không thể phục hồi chúng. Bạn có thể chọn giữ các tệp cho các ID này trong hệ thống tập tin. Nếu bạn chọn tạo lại một màn hình với một trong các ID, các video và sự kiện sẽ được hiển thị trong bảng điều khiển.",
+ "DeleteSelectedVideosMsg": "Bạn có muốn xóa những video này không? Bạn không thể phục hồi chúng.",
+ "DeleteThisMsg": "Bạn có muốn xóa cái này không? Bạn không thể phục hồi nó.",
+ "DeleteVideoMsg": "Bạn có muốn xóa video này không? Bạn không thể phục hồi nó.",
+ "Deleted": "Đã xóa",
+ "Deleted Schedule Configuration": "Xóa cấu hình lịch trình",
+ "Deleted State Configuration": "Xóa cấu hình trạng thái",
+ "Detect Objects": "Phát hiện các đối tượng Xem bên dưới ",
+ "Detection": "Phát hiện",
+ "Detection Engine": "Động cơ phát hiện",
+ "Detection Event": "Sự kiện phát hiện",
+ "Detector": "Máy dò",
+ "Detector Buffer": "Bộ đệm máy dò",
+ "Detector Filters": "Bộ lọc máy dò",
+ "Detector Flags": "Cờ phát hiện",
+ "Detector Grouping": "Nhóm phát hiện Thêm các nhóm trong Cài đặt ",
+ "Detector Rate": "Tỷ lệ máy dò Khi các hộp chiều rộng và chiều cao được hiển thị, bạn nên đặt chúng thành 640x480 trở xuống. Điều này sẽ tối ưu hóa tốc độ đọc của các khung.
", + "Died": "Chết", + "Digest Authentication": "Xác thực tiêu hóa", + "Disable": "Vô hiệu", + "Disable Night Vision": "Tắt tầm nhìn ban đêm Địa chỉ URL ", + "Disable Nightvision": "Tắt ban đêm", + "Disabled": "Vô hiệu hóa", + "Discord": "Bất hòa", + "Discord Alert on Trigger": "Cảnh báo bất hòa trên kích hoạt", + "Discord Bot": "Discord bot", + "Discord on No Motion": "Discord về \"Không chuyển động\"", + "DiscordErrorText": "Gửi đến Discord gây ra lỗi", + "DiscordFailedText": "Gửi đến Discord không thành công", + "DiscordLoggedIn": "Discord bot xác thực", + "DiscordNotEnabledText": "Discord bot không được bật, bật nó trong cài đặt tài khoản của bạn.", + "Documentation": "Tài liệu", + "Does Not Contain": "Không chứa", + "Don't Show for 1 Week": "Không hiển thị trong 1 tuần", + "Don't show this anymore": "Đừng hiển thị điều này nữa", + "DontAddToPreset": "Đừng thêm vào cài đặt trước", + "Double Quote Directory": "Double Trích dẫn thư mục Một số thư mục có không gian. Sử dụng điều này có thể bị sập một số máy ảnh. ", + "Down": "Xuống Địa chỉ URL ", + "Down Stop": "Down Stop Địa chỉ URL ", + "Download": "Tải xuống", + "Download Bandwidth": "Tải xuống băng thông", + "Downloading Videos": "Tải xuống video", + "Downloading...": "Tải xuống ...", + "Duplicate": "Nhân bản", + "EU": "EU", + "Easy Remote Access (P2P)": "Truy cập từ xa dễ dàng (P2P)", + "Edit": "Biên tập", + "Edit Configuration": "Chỉnh sửa cấu hình", + "Edit Selected": "Chỉnh sửa đã chọn", + "Edited Schedule Configuration": "Cấu hình lịch trình chỉnh sửa", + "Edited State Configuration": "Cấu hình trạng thái chỉnh sửa", + "Email": "E-mail", + "Email Details": "Chi tiết email", + "Email address is in use.": "Địa chỉ email đang được sử dụng.", + "Email and Password fields cannot be empty": "Các trường email và mật khẩu không thể trống", + "Email on No Motion": "Email về \"Không chuyển động\"", + "Email on Trigger": "Email trên Trigger", + "Emotion": "Cảm xúc", + "Emotion Average": "Cảm xúc trung bình", + "Enable": "Cho phép", + "Enable Night Vision": "Bật tầm nhìn ban đêm Địa chỉ URL ", + "Enable Nightvision": "Bật Nightvision", + "Enabled": "Đã bật", + "Encoding": "Mã hóa", + "EncodingInterval": "I-frame", + "End": "Chấm dứt", + "End Time": "Thời gian kết thúc", + "Ended": "Đã kết thúc", + "Endpoint": "Điểm cuối", + "Endpoint Address": "Địa chỉ điểm cuối", + "Enlarge": "Phóng to", + "Enter at least one IP": "Nhập ít nhất một IP", + "Enter this code to proceed": "Nhập mã này để tiếp tục", + "Equal to": "Tương đương với", + "Error Connecting": "Lỗi kết nối", + "Error While Decoding": "Lỗi trong khi giải mã", + "ErrorWhileDecodingText": "Phần cứng của bạn có thể có kết nối không ổn định với mạng. Kiểm tra kết nối mạng của bạn.", + "ErrorWhileDecodingTextAudio": "Máy ảnh của bạn đang cung cấp dữ liệu bị hỏng. Hãy thử vô hiệu hóa âm thanh trong cài đặt nội bộ của máy ảnh.", + "Event": "Biến cố", + "Event Counts": "Số lượng sự kiện", + "Event Filter Error": "Lỗi bộ lọc sự kiện", + "Event Filters": "Bộ lọc sự kiện", + "Event Limit": "Giới hạn sự kiện", + "Event Occurred": "Sự kiện xảy ra", + "Event Rules": "Quy tắc sự kiện", + "Event Webhook Error": "Sự kiện lỗi webhook", + "EventText1": "Đã kích hoạt một sự kiện tại", + "EventText2": "Không thể gửi email hình ảnh, tệp không thể truy cập được", + "Events": "Sự kiện", + "Events Found": "Các sự kiện được tìm thấy", + "Example": "Ví dụ", + "Execute Command": "Thực thi lệnh", + "Executed": "Thực thi", + "Export": "Xuất khẩu", + "Export Selected Videos": "Xuất các video được chọn", + "Export Video": "Xuất video", + "ExportSelectedVideosMsg": "Bạn có muốn xuất các video này không? Có thể mất một thời gian để zip và tải xuống.", + "Exposure": "Phơi bày", + "FFmpegCantStart": "FFMPEG không thể bắt đầu", + "FFmpegCantStartText": "Công cụ ghi âm cho máy ảnh này không thể khởi động. Có thể có điều gì đó sai với cấu hình máy ảnh của bạn. Nếu có bất kỳ nhật ký nào khác ngoài cái này, vui lòng đăng chúng trong các vấn đề trên GitHub.", + "FFmpegTip": "FFProbe là một máy phân tích dòng đa phương tiện đơn giản. Bạn có thể sử dụng nó để xuất tất cả các loại thông tin về đầu vào bao gồm thời lượng, tốc độ khung, kích thước khung, v.v.", + "FFprobe": "Thăm dò", + "FLV": "Flv", + "FLV Stream Type": "Loại luồng FLV", + "FactorAuthText1": "Mã sẽ chỉ hoạt động trong 15 phút. Nếu bạn đăng nhập lại, bộ hẹn giờ sẽ được đặt lại thành 15 phút với cùng một mã.", + "Fatal": "Gây tử vong", + "Fatal Maximum Reached": "Tối đa gây tử vong, dừng camera.", + "FatalMaximumReachedText": "Lỗi JPEG gây tử vong.", + "February": "tháng 2", + "Feed-in Image Height": "Chiều cao hình ảnh thức ăn", + "Feed-in Image Width": "Chiều rộng hình ảnh của nguồn cấp dữ liệu", + "Female": "Giống cái", + "Field Missing Value": "Trường bị thiếu giá trị", + "Fields cannot be empty": "Các trường không thể trống", + "File Delete Error": "Tệp xóa lỗi", + "File Not Exist": "Tệp không tồn tại", + "File Not Found": "Tệp không tìm thấy", + "File Not Found in Database": "Tệp không tìm thấy trong cơ sở dữ liệu", + "File Not Found in Filesystem": "Tệp không tìm thấy trong hệ thống tập tin", + "File Type": "Loại tệp", + "FileBin Share": "FileBin chia sẻ", + "FileNotExistText": "Không thể lưu tệp không tồn tại. Có gì đó đã sai.", + "Filename": "Tên tệp", + "Filesize": "Kích thước tập tin", + "Filter ID": "ID lọc", + "Filter Matches": "Bộ lọc phù hợp", + "Filter Name": "Tên lọc", + "Filter for Objects only": "Chỉ lọc cho các đối tượng", + "FilterMatchesText1": "Bộ lọc này có điều kiện đáp ứng.", + "FilterMatchesText2": "Video được tìm thấy.", + "Filters": "Bộ lọc", + "Filters Updated": "Bộ lọc được cập nhật", + "FiltersUpdatedText": "Những thay đổi của bạn đã được lưu và áp dụng.", + "Find Where": "Tìm ở đâu", + "First stream in feed": "Luồng đầu tiên trong thức ăn", + "Fix": "Khắc phục", + "Fix Video": "Khắc phục video", + "FixVideoMsg": "Bạn có muốn sửa video này không? Bạn không thể hoàn tác hành động này.", + "Flush PM2 Logs": "Nhật ký pm2", + "Font Path": "Đường dẫn phông chữ", + "Font Size": "Cỡ chữ", + "For Group": "Cho nhóm", + "Force Monitors Per Row": "Lực lượng màn hình mỗi hàng", + "Force Port": "Cổng lực", + "Form Data Not Found": "Không tìm thấy dữ liệu hình thức", + "Found Devices": "Tìm thấy thiết bị", + "Frame Rate": "Tỷ lệ khung hình", + "FrameRateLimit": "Giới hạn tốc độ khung hình (FPS)", + "Frames": "Khung", + "Friday": "Thứ sáu", + "Frigate": "Tàu khu trục", + "Full Frame Detection": "Phát hiện khung đầy đủ", + "Full Stream URL": "URL luồng đầy đủ", + "Full URL Path": "Đường dẫn URL đầy đủ", + "Fullscreen": "Toàn màn hình", + "Gateway": "Cổng", + "Gender": "Giới tính", + "Generate Subtitles": "Tạo phụ đề", + "Get Code": "Nhận được mã", + "Get Logs to Client": "Nhận nhật ký cho khách hàng", + "Global Detector Settings": "Cài đặt máy dò toàn cầu", + "Google Drive": "Google Drive", + "GovLength": "Chính phủ", + "Greater Than": "Lớn hơn", + "Greater Than or Equal to": "Lớn hơn hoặc bằng", + "Grid": "Lưới", + "Group Key": "Khóa nhóm", + "Group Key is in use.": "Khóa nhóm đang được sử dụng.", + "Group Name": "Tên nhóm", + "Grouping": "Nhóm", + "H.264 / H.265 / H.265+": "H.264 / H.265 / H.265+", + "H264Profile": "H264 Hồ sơ", + "HEVC (H.265)": "HEVC (H.265)", + "HLS (.m3u8)": "HLS (.M3U8)", + "HLS (includes Audio)": "HLS (bao gồm âm thanh)", + "HLS Audio Encoder": "Bộ mã hóa âm thanh", + "HLS List Size": "Kích thước danh sách", + "HLS Live Start Index": "Chỉ số bắt đầu trực tiếp HLS", + "HLS Preset": "Mẫu đặt trước", + "HLS Segment Length": "Chiều dài phân đoạn tính bằng giây ", + "HLS Start Number": "Số bắt đầu của HLS", + "HLS Video Encoder": "Bộ mã hóa video", + "HTTP": "HTTP", + "HTTPS": "HTTPS", + "Hardware Accelerated": "Phần cứng tăng tốc", + "Height": "Chiều cao", + "Help": "Giúp đỡ", + "Hide List": "Danh sách ẩn", + "Hide Notes": "Ẩn ghi chú", + "Home": "Nhà", + "Host": "Chủ nhà", + "Host Type": "Loại máy chủ", + "Hostname": "Tên máy chủ", + "Hotswap Modes (Watch-Only)": "Chế độ HotSwap (chỉ có đồng hồ)", + "How to Record": "Làm thế nào để ghi lại", + "IP Address": "Địa chỉ IP", + "Identity": "Xác thực", + "IdentityText1": "Đây là cách hệ thống sẽ xác định dữ liệu cho luồng này. Bạn không thể thay đổi ID màn hình khi bạn đã nhấn Lưu. Nếu bạn muốn, bạn có thể tạo ID ID theo dõi có thể đọc được nhiều hơn trước khi bạn tiếp tục.", + "IdentityText2": "Bạn có thể sao chép một màn hình bằng cách sửa đổi ID Màn hình sau đó nhấn Save. Bạn không thể Sử dụng ID của màn hình đã tồn tại hoặc nó sẽ lưu qua thông tin cơ sở dữ liệu của màn hình đó.", + "Idle": "Nhàn rỗi", + "Image Height": "Chiều cao hình ảnh", + "Image Location": "Vị trí hình ảnh", + "Image Position": "Vị trí hình ảnh", + "Image Width": "Chiều rộng hình ảnh", + "Imaging": "Hình ảnh", + "Import": "Nhập khẩu", + "Import Monitor Configuration": "Nhập cấu hình màn hình", + "ImportMonitorConfigurationText": "Làm điều này sẽ ghi đè bất kỳ thay đổi hiện không được lưu. Thay đổi đã nhập sẽ chỉ được áp dụng khi bạn nhấn lưu .", + "ImportMultiMonitorConfigurationText": "Làm điều này sẽ ghi đè bất kỳ màn hình nào có ID hiện có trong tệp nhập.", + "In": "Trong", + "Incorrect Settings Chosen": "Cài đặt không chính xác được chọn", + "Indifference": "Thờ ơ", + "Info": "Thông tin", + "Information": "Thông tin", + "Input": "Đầu vào", + "Input Feed": "Nguồn cấp dữ liệu đầu vào", + "Input Feeds Selected": "Nguồn cấp dữ liệu đầu vào được chọn", + "Input Flags": "Cờ đầu vào", + "Input Map": "Bản đồ đầu vào", + "Input Selector": "Bộ chọn đầu vào", + "Input Settings": "Nhập vào cài đặt", + "Input Type": "Kiểu đầu vào", + "InputText1": "Phần này cho Shinobi biết cách tiêu thụ một luồng. Để có hiệu suất tối ưu, hãy, hãy, hãy thử điều chỉnh cài đặt nội bộ của máy ảnh của bạn. Để tìm máy ảnh của bạn, bạn có thể sử dụng được tích hợp trong máy quét ONVIF của shinobi. Để mở trình quét ONVIF, nhấp vào tên người dùng của bạn ở phía trên bên trái và sau đó là ONVIF.", + "InputText2": "Tìm hiểu về việc định cấu hình và điều chỉnh máy ảnh của bạn tại đây .", + "InputText3": "Nếu bạn cần trợ giúp để tìm ra loại máy ảnh đầu vào của bạn, bạn có thể xem trong ", + "Left Stop": "Dừng trái Địa chỉ URL ", + "Legacy Webhook": "Di sản Webhook", + "Less Than": "Ít hơn", + "Less Than or Equal to": "Ít hơn hoặc bằng", + "License Activated": "Giấy phép được kích hoạt", + "License Activation": "Kích hoạt giấy phép", + "License Activation Failed": "Kích hoạt giấy phép không thành công", + "License Key": "Mã bản quyền", + "License Plate Detector": "Máy dò biển số", + "Like": "Như", + "Limited": "Giới hạn", + "Link Google Account": "Liên kết tài khoản Google", + "Link LDAP Account": "Liên kết Tài khoản LDAP", + "Link Shinobi": "Liên kết Shinobi", + "List Toggle": "Danh sách chuyển đổi", + "List of Videos Delete Error": "Danh sách các video xóa lỗi", + "Live Grid": "Lưới trực tiếp", + "Live Stream Toggle": "Chuyển đổi luồng trực tiếp", + "Live View": "Xem trực tiếp", + "Local": "Địa phương", + "Log Level": "Mức đăng nhập", + "Log Signal Event": "Sự kiện tín hiệu nhật ký Chỉ phía máy khách ", + "Log Stream": "Nhật ký luồng", + "Logging": "Đăng nhập", + "Login": "Đăng nhập", + "Logout": "Đăng xuất", + "Logs": "Nhật ký", + "Loop Stream": "Luồng vòng lặp", + "MB": "MB", + "MJPEG": "Mjpeg", + "MP4 (copy, libx264, libx265)": "Mp4 (sao chép, libx264, libx265)", + "MPEG-4 (.mp4 / .ts)": "MPEG-4 (.mp4 / .ts)", + "MPEG-DASH (includes Audio)": "MPEG-Dash (bao gồm âm thanh)", + "MQTT Client": "Máy khách MQTT", + "MQTT Error": "Lỗi MQTT", + "MQTT Inbound": "MQTT trong nước", + "MQTT Outbound": "MQTT đi ra ngoài", + "MailError": "Lỗi thư: Không thể gửi email, kiểm tra conf.json. Bỏ qua bất kỳ tính năng nào dựa vào gửi thư.", + "Main": "Chủ yếu", + "Male": "Nam giới", + "Manual": "Thủ công", + "Map": "Bản đồ", + "March": "Bước đều", + "Matches": "Diêm", + "Matrices": "Ma trận", + "Max Indifference": "Sự thờ ơ tối đa", + "Max Latency": "Độ trễ tối đa", + "Max Number of Cameras": "Số lượng máy ảnh tối đa", + "Max Storage Amount": "Số lượng lưu trữ tối đa", + "MaxExposureTime": "Thời gian phơi sáng tối đa", + "MaxGain": "Tăng tối đa", + "Maximum Change": "Thay đổi tối đa", + "Maximum dB": "DB tối đa", + "May": "Có thể", + "Merge Selected Videos": "Hợp nhất các video đã chọn", + "Merge Video": "Hợp nhất video", + "Merge and Download": "Hợp nhất và tải xuống", + "MergeSelectedVideosMsg": "Bạn có muốn hợp nhất các video này không? Có thể mất một thời gian để hợp nhất và tải xuống. Thời điểm kết nối được đóng, tệp sẽ bị xóa. Đảm bảo bạn giữ cho trình duyệt mở cho đến khi hoàn thành.", + "Methods": "Phương pháp", + "Migrator": "Người di cư", + "MinExposureTime": "Thời gian phơi nhiễm tối thiểu", + "MinGain": "Tăng tối thiểu", + "Minimum Change": "Thay đổi tối thiểu", + "Minimum dB": "DB tối thiểu", + "Minutes": "Phút", + "Mode": "Cách thức", + "Monday": "Thứ hai", + "Monitor": "Màn hình", + "Monitor Added by user": "Giám sát được thêm bởi người dùng.", + "Monitor Capture Rate": "Giám sát tỷ lệ bắt giữ (FPS) ", + "Monitor Died": "Màn hình chết", + "Monitor Edit": "Giám sát chỉnh sửa", + "Monitor Groups": "Giám sát các nhóm", + "Monitor ID": "Giám sát ID", + "Monitor Idling": "Giám sát không hoạt động", + "Monitor Name": "Tên giám sát", + "Monitor Settings": "Cài đặt giám sát", + "Monitor Start": "Giám sát bắt đầu", + "Monitor States": "Giám sát trạng thái", + "Monitor States and Schedules": "Giám sát các trạng thái và lịch trình", + "Monitor Stop": "Theo dõi điểm dừng", + "Monitor Stopped": "Màn hình dừng lại", + "Monitor Updated by user": "Giám sát được cập nhật bởi người dùng.", + "Monitor is now Disabled": "Màn hình hiện bị vô hiệu hóa", + "Monitor is now Idle": "Màn hình hiện đang nhàn rỗi", + "Monitor is now Recording": "Màn hình hiện đang ghi", + "Monitor is now Watching": "Màn hình hiện đang theo dõi", + "Monitor mode changed": "Chế độ giám sát đã thay đổi", + "Monitor mode is already": "Chế độ giám sát đã", + "Monitor or Key does not exist.": "Giám sát hoặc khóa không tồn tại.", + "MonitorIdlingText": "Phiên giám sát đã được yêu cầu nhàn rỗi.", + "MonitorStatesText": "Bạn có thể tìm hiểu về cách sử dụng ở đây trên shinobihub .", + "MonitorStoppedText": "Phiên giám sát đã được yêu cầu dừng lại.", + "Monitors": "Màn hình", + "Monitors per row": "Màn hình trên mỗi hàng cho Montage ", + "Monitors to Copy to": "Màn hình để sao chép vào", + "Montage": "Dựng phim", + "Motion": "Cử động", + "Motion Detection": "Phát hiện chuyển động", + "Motion GUI": "GUI chuyển động", + "Motion Meter": "Đồng hồ đo chuyển động", + "Motion Threshold": "Ngưỡng chuyển động", + "Mp4Frag": "Mp4frag", + "Must be atleast one row": "Phải ít nhất một hàng", + "Mute Audio": "Tắt tiếng", + "NTP": "NTP", + "NTP Servers": "Máy chủ NTP", + "NVIDIA": "Nvidia", + "Name": "Tên", + "Name cannot be empty.": "Tên không thể trống.", + "Nameservers": "Máy chủ tên", + "Network": "Mạng", + "Network Manager": "Quản lý mạng", + "Never": "Không bao giờ", + "New Authentication Token": "Mã thông báo xác thực mới", + "New Monitor": "Màn hình mới", + "Newest": "Mới nhất", + "Next Video": "Video tiếp theo", + "No": "Không", + "No API Key": "Không có khóa API", + "No Audio": "Không có âm thanh", + "No Data": "Không có dữ liệu", + "No Events found for this video": "Không tìm thấy sự kiện nào cho video này", + "No Group with this key exists": "Không có nhóm nào có chìa khóa này tồn tại", + "No Monitor Exists with this ID.": "Không có màn hình tồn tại với ID này.", + "No Monitor Found, Ignoring Request": "Không tìm thấy màn hình, bỏ qua yêu cầu", + "No Monitor ID Present in Form": "Không có ID màn hình có hình thức", + "No Monitors Selected": "Không có màn hình được chọn", + "No Region": "Không có khu vực", + "No Rotation": "Không xoay vòng", + "No Sound": "Không có âm thanh", + "No Trigger": "Không có kích hoạt", + "No Videos Found": "Không tìm thấy video nào", + "No such file": "Không có tệp như vậy", + "NoLogsFoundForDateRange": "Không có nhật ký nào được tìm thấy trong phạm vi ngày này. Hãy thử mở rộng phạm vi ngày.", + "NoMotionEmailText1": "Không có chuyển động cho", + "NoMotionEmailText2": "Không có bất kỳ chuyển động nào được phát hiện trên máy ảnh cho", + "NoVideosFoundForDateRange": "Không có video tìm thấy trong phạm vi ngày này. Hãy thử đặt ngày bắt đầu trở lại.", + "Noise Filter": "Máy lọc tiếng ồn", + "Noise Filter Range": "Phạm vi bộ lọc nhiễu", + "Non-Standard ONVIF": "Onvif không chuẩn", + "Not Activated": "Chưa kích hoạt", + "Not Authorized": "Không được ủy quyền", + "Not Connected": "Không kết nối", + "Not Equal to": "Không bằng", + "Not Found": "Không tìm thấy", + "Not In": "Không phải vào", + "Not Matches": "Không phù hợp", + "Not Permitted": "Không được phép", + "Not Saved": "Chưa được lưu", + "Not an Administrator Account": "Không phải tài khoản quản trị viên", + "NotAuthorizedText1": "Không được ủy quyền, gửi lệnh init với \"auth\", \"ke\" và \"uid\"", + "Notes": "Ghi chú", + "NotesPlacholder": "Nhận xét bạn muốn để lại cho cài đặt máy ảnh này.", + "Nothing exists": "Không có gì tồn tại", + "Notice": "Lưu ý", + "Notification Sound": "Âm thanh thông báo", + "Notification Video Length": "Độ dài video thông báo", + "Notifications": "Thông báo", + "NotifyErrorText": "Gửi thông báo gây ra lỗi", + "November": "Tháng mười một", + "Number of Days to keep": "Số ngày để giữ", + "Numeric criteria unsupported for Region tests, Ignoring Conditional": "Tiêu chí số không được hỗ trợ cho các bài kiểm tra vùng, bỏ qua điều kiện", + "OAuth Code": "Mã OAuth", + "OAuth Credentials": "Thông tin OAuth", + "ONVIF": "Onvif", + "ONVIF Device Manager": "Trình quản lý thiết bị ONVIF", + "ONVIF Port": "Cổng Onvif", + "ONVIF Scanner": "Máy quét ONVIF", + "ONVIFErr400": "Tìm thấy cổng ONVIF nhưng ủy quyền không thành công khi truy xuất URL luồng. Kiểm tra tên người dùng và mật khẩu được sử dụng để quét. Đảm bảo thời gian máy ảnh và thời gian máy chủ của bạn được đồng bộ hóa.", + "ONVIFErr404": "Không tìm thấy. Đây có thể chỉ là bảng web cho một thiết bị mạng.", + "ONVIFErr405": "Phương pháp không được phép. Kiểm tra tên người dùng và mật khẩu được sử dụng để quét.", + "ONVIFEventsNotAvailable": "Sự kiện ONVIF không có sẵn", + "ONVIFEventsNotAvailableText1": "Dịch vụ này có thể không có sẵn cho máy ảnh này hoặc ONVIF chưa được khởi tạo.", + "ONVIFnotCompliantProfileT": "Camera không tuân thủ hồ sơ ONVIF", + "ONVIFnote": "Khám phá các thiết bị ONVIF trên các mạng bên ngoài hoặc để trống để quét mạng hiện tại của bạn.Phần này sẽ chỉ định phương pháp chính để phát trực tuyến và cài đặt của nó. Luồng này sẽ được hiển thị trong bảng điều khiển. Nếu bạn chọn sử dụng HLS, JPEG hoặc MJPEG thì bạn có thể tiêu thụ luồng qua các chương trình khác. khung.
", + "Streamed Logs": "Nhật ký phát trực tuyến", + "Streamer": "Streamer", + "Streams": "Dòng", + "Sub-Accounts": "Tài khoản phụ", + "Subdivision": "Phân khu", + "Substream": "Chất nền", + "Substream Process": "Quá trình con", + "SubstreamNotConfigured": "Subream không được cấu hình. Mở cài đặt màn hình của bạn và định cấu hình nó.", + "Subtitle": "Phụ đề", + "Success": "Sự thành công", + "Sunday": "Chủ nhật", + "Superuser": "Superuser", + "Superuser Logs": "Nhật ký Superuser", + "Switch on for Still Image": "Bật hình ảnh tĩnh", + "System": "Hệ thống", + "System Level": "Cấp độ hệ thống", + "TCP": "TCP", + "TV Channel": "Kênh truyền hình", + "TV Channel Group": "Nhóm kênh truyền hình", + "TV Channel ID": "ID kênh truyền hình", + "Telegram": "Telegram", + "Text Box Color": "Màu hộp văn bản", + "Text Color": "Văn bản màu", + "Text criteria unsupported for Object Count tests, Ignoring Conditional": "Tiêu chí văn bản không được hỗ trợ cho các bài kiểm tra số đối tượng, bỏ qua điều kiện", + "Themes": "Chủ đề", + "There are no monitors that you can view with this account.": "Không có màn hình nào mà bạn có thể xem với tài khoản này.", + "Threads": "Chủ đề", + "Thumbnail": "Hình nhỏ", + "Thursday": "thứ năm", + "Time": "Thời gian", + "Time Created": "Thời gian được tạo ra", + "Time Left": "Thời gian còn lại", + "Time Occurred": "Thời gian xảy ra", + "Time-lapse": "Thời gian trôi qua", + "Time-lapse Tool": "Công cụ thời gian trôi đi", + "TimeZone": "Múi giờ", + "Timelapse": "Dòng thời gian", + "Timelapse Frames Share": "Khung thời gian chia sẻ", + "Timelapse Watermark": "Hình mờ theo thời gian", + "Timeout": "Hết giờ", + "Timeout Reset on Next Event": "Đặt lại thời gian chờ vào sự kiện tiếp theo", + "Timeout Reset on Next Motion": "Đặt lại thời gian chờ trên chuyển động tiếp theo", + "Timezone": "Múi giờ", + "Timezone Offset": "Thời gian bù", + "Title": "Tiêu đề", + "Today": "Hôm nay", + "Toggle Sidebar": "Chuyển đổi thanh bên", + "Toggle Substream": "Chuyển đổi chuỗi con", + "Token": "Mã thông báo", + "Top Left": "Trên cùng bên trái", + "Top Right": "Trên cùng bên phải", + "Traditional (Watch-Only, Includes Buffer)": "Truyền thống (chỉ xem, bao gồm bộ đệm)", + "Traditional Recording": "Ghi âm truyền thống", + "Traditional Recording Flags": "Cờ ghi âm truyền thống", + "Train": "Tàu hỏa", + "TrainConfirm": "Bạn có chắc là bạn muốn bắt đầu đào tạo? Điều này có thể mất hơn 12 giờ với hơn 500 hình ảnh. Điều này sẽ tiêu thụ một lượng lớn tài nguyên, như RAM và/hoặc CPU.", + "TrainConfirmStop": "Bạn có chắc là bạn muốn ngừng đào tạo?", + "Trainer Engine": "Động cơ huấn luyện", + "Trigger Blocked": "Kích hoạt bị chặn", + "Trigger Camera Groups": "Kích hoạt nhóm camera", + "Trigger Event": "Sự kiện kích hoạt", + "Trigger Group to Record": "Nhóm kích hoạt để ghi lại", + "Trigger Record": "Kích hoạt hồ sơ", + "Trigger Successful": "Kích hoạt thành công", + "Trigger Threshold": "Ngưỡng kích hoạt", + "Tuesday": "Thứ ba", + "Turn Speed": "Tốc độ quay", + "Type": "Gõ phím", + "UDP": "UDP", + "URL": "URL", + "URL Stop Timeout": "Dừng thời gian chờ URL Chạy URL dừng sau X Milliseconds ", + "US": "CHÚNG TA", + "UTCDateTime": "Ngày tháng", + "Unable to Launch": "Không thể ra mắt", + "UnabletoLaunchText": "Vui lòng lưu màn hình mới trước. Sau đó cố gắng khởi chạy biên tập viên khu vực.", + "Uncommon Objects": "Đối tượng không phổ biến", + "Uniform": "Đồng dạng", + "Unlink": "Hủy bỏ", + "Unlink Login": "Hủy liên kết đăng nhập?", + "Unlinked": "Không liên kết", + "Up": "UP Địa chỉ URL ", + "Up Stop": "Dừng lại Địa chỉ URL ", + "Update": "Cập nhật", + "Update to Development": "Cập nhật để phát triển", + "Update to Master": "Cập nhật để làm chủ", + "Upload Bandwidth": "Tải băng thông", + "Upload File": "Cập nhật dử liệu", + "Uploaded Only": "Chỉ tải lên", + "Uploaders": "Người tải lên", + "Use Built-In": "Sử dụng tích hợp", + "Use Camera Timestamps": "Sử dụng dấu thời gian camera", + "Use Global Amazon S3 Video Storage": "Sử dụng lưu trữ video toàn cầu Amazon S3", + "Use Global Backblaze B2 Video Storage": "Sử dụng lưu trữ video B2 toàn cầu B2", + "Use Global Wasabi Hot Cloud Storage Video Storage": "Sử dụng lưu trữ video lưu trữ đám mây nóng toàn cầu Wasabi", + "Use Global WebDAV Video Storage": "Sử dụng lưu trữ video WebDav toàn cầu", + "Use HTML5 Play Method": "Sử dụng phương thức chơi HTML5", + "Use Max Storage Amount": "Sử dụng số lượng lưu trữ tối đa", + "Use Raw Snapshot": "Sử dụng ảnh chụp nhanh", + "Use Substream": "Sử dụng chuỗi con", + "Use coProcessor": "Sử dụng bộ đồng xử lý", + "UseCount": "Usecount", + "User Log": "Nhật ký người dùng", + "User Not Found": "Người dùng không tìm thấy", + "Username": "tên tài khoản", + "VA-API": "Va-api", + "Value": "Giá trị", + "Video": "Video", + "Video Bit Rate": "Tỷ lệ bit video", + "Video Codec": "Codec video", + "Video Configuration": "Cấu hình video", + "Video Filter": "Bộ lọc video", + "Video Finished": "Video kết thúc", + "Video Length (minutes) and Motion Count per video": "Chiều dài video (phút) và số lượng chuyển động cho mỗi video", + "Video Limit": "Giới hạn video", + "Video Record Rate": "Tỷ lệ bản ghi video", + "Video Set": "VIDEO SET", + "Video Share": "Chia sẻ video", + "Video Status": "Trạng thái video", + "Video and Time Span (Minutes)": "Video và khoảng thời gian (phút)", + "Video stream only from first feed": "Chỉ phát video từ nguồn cấp dữ liệu đầu tiên", + "Video streams only": "Chỉ các luồng video", + "Videos": "Video", + "Videos List": "Danh sách video", + "Videos Merge": "Video hợp nhất", + "Viewing Server Stats": "Xem số liệu thống kê máy chủ", + "Warning": "Cảnh báo", + "Wasabi Hot Cloud Storage": "Lưu trữ đám mây nóng Wasabi", + "Wasabi Hot Cloud Storage Upload Error": "Wasabi Hot Cloud lưu trữ lên lỗi tải lên", + "Watch": "Sự canh gác", + "Watch Only": "Chỉ xem", + "Watch-Only": "Chỉ xem", + "Watching": "Xem", + "Web Page": "Trang web", + "WebDAV": "Webdav", + "WebM (libvpx)": "Webm (libvpx)", + "Webdav Error": "Lỗi webDAV", + "WebdavErrorTextCreatingDir": "Không thể tạo thư mục.", + "WebdavErrorTextTryCreatingDir": "Không thể tiết kiệm. Cố gắng tạo thư mục.", + "Webhook": "Webhook", + "Webhook URL": "URL Webhook", + "Websocket": "WebSocket", + "Websocket Connected": "WebSocket kết nối", + "Websocket Disconnected": "WebSocket bị ngắt kết nối", + "Wednesday": "Thứ Tư", + "Welcome": "Chào mừng!", + "When Detector is Off": "Khi máy dò tắt", + "When Detector is On": "Khi máy dò được bật", + "WhiteBalance": "Cân bằng trắng", + "WideDynamicRange": "Phạm vi động rộng", + "Width": "Chiều rộng", + "X Point": "Điểm x", + "Y Point": "Y điểm", + "Yes": "Đúng", + "Zip and Download": "Zip và tải xuống", + "Zipping Videos": "Video zipping", + "Zones": "Khu vực", + "Zoom In": "Phóng to", + "Zoom In Stop": "Thu phóng trong Stop Địa chỉ URL ", + "Zoom Out": "Thu nhỏ", + "Zoom Out Stop": "Thu phóng Stop Địa chỉ URL ", + "a day": "một ngày", + "a few seconds": "một vài giây", + "a minute": "một phút", + "a month": "một tháng", + "a year": "một năm", + "aac": "AAC", + "aac (Default)": "AAC (mặc định)", + "ac3": "AC3", + "accountActionFailed": "Hành động tài khoản không thành công", + "accountAdded": "Tài khoản được thêm vào", + "accountAddedText": "Tài khoản đã được thêm vào.", + "accountDeleted": "Tài khoản đã bị xóa", + "accountDeletedText": "Tài khoản đã bị xóa.", + "accountId": "ID tài khoản", + "accountSettingsDescription": "Quản lý hồ sơ của bạn và đặt các tùy chọn như số lượng lưu trữ tối đa và số ngày tối đa để giữ video.", + "accountSettingsError": "Lỗi cài đặt tài khoản", + "activatedText": "Cài đặt của bạn đã được kích hoạt.", + "ago": "trước kia", + "airplane": "Máy bay", + "alreadyLinked": "Đã được liên kết với một tài khoản", + "an hour": "một giờ", + "apple": "táo", + "applicationKey": "Khóa ứng dụng", + "aws_accessKeyId": "Truy cập ID khóa", + "aws_secretAccessKey": "Khóa truy cập bí mật", + "backpack": "balo", + "banana": "trái chuối", + "baseball bat": "gậy bóng chày", + "baseball glove": "găng tay bóng chày", + "bear": "con gấu", + "bed": "Giường", + "bench": "Băng ghế", + "bicycle": "Xe đạp", + "bindDN": "Binddn", + "bird": "chim", + "blankPassword": "Để trống để giữ cùng một mật khẩu", + "boat": "thuyền", + "book": "sách", + "bottle": "chai", + "bowl": "bát", + "broccoli": "bông cải xanh", + "bus": "xe buýt", + "cake": "bánh", + "car": "xe ô tô", + "carrot": "Cà rốt", + "cat": "con mèo", + "cell phone": "điện thoại di động", + "chair": "cái ghế", + "clientStreamFailedattemptingReconnect": "Kiểm tra luồng phía máy khách không thành công, cố gắng kết nối lại.", + "clock": "đồng hồ", + "coProcess Crashed for Monitor": "Coprocess bị sập cho màn hình", + "coProcess Unexpected Exit": "Coprocess Lối ra bất ngờ", + "coProcessor": "Coprocessor", + "coProcessor Started": "Coprocessor bắt đầu", + "coProcessor Stopped": "Coprocessor dừng lại", + "coProcessorTextStarted": "Coprocessor đã bắt đầu cho CPU chỉ đầu ra.", + "coProcessorTextStopped": "Coprocessor đã kết thúc.", + "codecMismatchText1": "Máy ảnh của bạn đang cung cấp dữ liệu luồng H.265 (HEVC) và bạn đang sử dụng sao chép làm codec video cho phần luồng. Luồng của bạn từ Shinobi có thể không xuất hiện trên các thiết bị không thể sử dụng codec này. Ứng dụng di động Shinobi có thể xem các luồng này.", + "codecMismatchText2": "Codec video được chọn của bạn không được áp dụng. Máy ảnh của bạn đang cung cấp dữ liệu luồng MJPEG và bạn đang sử dụng sao chép làm codec video cho phần luồng. Thay đổi loại luồng thành MJPEG.", + "codecMismatchText3": "Codec video được chọn của bạn không được áp dụng. Máy ảnh của bạn đang cung cấp dữ liệu luồng MJPEG và bạn đang sử dụng sao chép làm codec video cho phần ghi. Đã thay đổi codec video thành libx264.", + "confirmDeleteFilter": "Bạn có muốn xóa bộ lọc này không? Bạn không thể phục hồi nó.", + "contactAdmin": "Liên hệ với người bảo trì cài đặt Shinobi của bạn.", + "copy": "sao chép", + "couch": "đi văng", + "cow": "bò", + "cuda": "Cuda (Nvidia NVENC)", + "cup": "tách", + "cuvid": "CUVID (NVIDIA NVENC)", + "days": "ngày", + "deleteApiKey": "Xóa khóa API", + "deleteApiKeyText": "Bạn có muốn xóa khóa API này không? Bạn không thể phục hồi nó.", + "deleteMonitorStateText1": "Bạn có muốn xóa các trạng thái màn hình này trước đây không? Các cấu hình màn hình liên quan không thể được phục hồi.", + "deleteMonitorStateText2": "Bạn có muốn xóa cài đặt trước của màn hình này không?", + "deleteScheduleText": "Bạn có muốn xóa lịch trình này không? Các cài đặt trước giám sát được liên kết sẽ không được sửa đổi ..", + "deleteSubAccount": "Xóa tài khoản phụ", + "deleteSubAccountText": "Bạn có muốn xóa tài khoản phụ này không? Bạn không thể phục hồi nó.", + "dining table": "bàn ăn", + "dog": "chú chó", + "donut": "bánh vòng", + "drm": "Chia sẻ đối tượng DRM", + "dropBoxSuccess": "Sự thành công! Các tập tin được lưu vào Dropbox của bạn.", + "dxva2": "DXVA2 (Video DirectX, Windows)", + "elephant": "con voi", + "eventFilterActionText": "Đây là những hành động xảy ra từ các điều kiện lọc đã thành công. \"Lựa chọn ban đầu\" đề cập đến tùy chọn bạn đã chọn trong cài đặt của màn hình.", + "eventFilterErrorBrackets": "Bạn có một số lượng dấu ngoặc. Họ đang bị bỏ qua.", + "eventFiltersDescription": "Bộ lọc thiết lập cho khi các sự kiện xảy ra.", + "failedLoginText1": "Bạn đã không đăng nhập quá nhiều lần. Bạn phải đợi 15 phút trước khi thử lại.", + "failedLoginText2": "Vui lòng kiểm tra thông tin đăng nhập của bạn.", + "fieldMissingValueText1": "Máy ảnh của bạn đang cung cấp dữ liệu luồng MJPEG. Bạn cần đặt tốc độ chụp màn hình. Shinobi sẽ cố gắng phát hiện nó và điền vào nó tự động.", + "fieldTextAccelerator": "Tăng tốc phần cứng (HWACCEL) để giải mã các luồng.", + "fieldTextAcodec": "Codec âm thanh để ghi.", + "fieldTextActionsCommand": "Bạn có thể sử dụng điều này để kích hoạt một tập lệnh trên lệnh.", + "fieldTextActionsHalt": "Làm cho sự kiện không làm gì cả, như thể nó không bao giờ xảy ra.", + "fieldTextActionsIndifference": "Sửa đổi sự thờ ơ tối thiểu cần thiết cho sự kiện.", + "fieldTextActionsRecord": "Sử dụng ghi âm truyền thống, hotswap hoặc xóa bất động với các tùy chọn hiện đang được đặt trong phần Cài đặt phát hiện toàn cầu.", + "fieldTextAduration": "Chỉ định có bao nhiêu micro giây được phân tích để thăm dò đầu vào. Đặt thành 100000 nếu bạn đang sử dụng RTSP và gặp sự cố luồng.", + "fieldTextAudioAlert": "Âm thanh khi sự kiện xảy ra.", + "fieldTextAudioDelay": "Trì hoãn cho đến lần tiếp theo một sự kiện có thể bắt đầu một cảnh báo. Đo bằng giây.", + "fieldTextAudioNote": "Âm thanh khi bong bóng thông tin xuất hiện.", + "fieldTextAutoHost": "URL luồng đầy đủ.", + "fieldTextAutoHostEnable": "Cho các mảnh riêng lẻ cần thiết để xây dựng một URL luồng hoặc cung cấp URL đầy đủ và cho phép Shinobi phân tích nó cho bạn.", + "fieldTextChannelHlsListSize": "Số lượng phân đoạn tối đa trước khi tự động xóa các phân đoạn cũ.", + "fieldTextChannelHlsTime": "Bao lâu mỗi phân đoạn video, trong vài phút. Mỗi phân đoạn sẽ được khách hàng rút ra thông qua một tệp M3U8. Các phân đoạn ngắn hơn mất ít không gian hơn.", + "fieldTextChannelPresetStream": "Cờ đặt trước cho một số bộ mã hóa video nhất định. Nếu bạn thấy máy ảnh của bạn bị sập cứ sau vài giây: hãy thử để trống.", + "fieldTextChannelStreamAcodec": "Codec âm thanh cho phát trực tuyến.", + "fieldTextChannelStreamAcodecAac": "Được sử dụng cho video MP4.", + "fieldTextChannelStreamAcodecAc3": "Được sử dụng cho video MP4.", + "fieldTextChannelStreamAcodecAuto": "Hãy để ffmpeg chọn.", + "fieldTextChannelStreamAcodecCopy": "Được sử dụng cho video MP4. Có cách sử dụng CPU rất thấp nhưng một số codec âm thanh cần các cờ tùy chỉnh như -strict 2 cho AAC.",
+ "fieldTextChannelStreamAcodecLibmp3lame": "Được sử dụng cho video MP4.",
+ "fieldTextChannelStreamAcodecLibopus": "Được sử dụng cho video webm.",
+ "fieldTextChannelStreamAcodecLibvorbis": "Được sử dụng cho video webm.",
+ "fieldTextChannelStreamAcodecNoAudio": "Không có âm thanh, đây là một lựa chọn phải được đặt ở một số nơi trên thế giới vì lý do pháp lý.",
+ "fieldTextChannelStreamFps": "Tốc độ mà các khung được hiển thị cho máy khách, tính theo khung mỗi giây. Hãy nhận biết không có mặc định. Điều này có thể dẫn đến việc sử dụng băng thông cao.",
+ "fieldTextChannelStreamQuality": "Số lượng thấp có nghĩa là chất lượng cao hơn. Số lượng cao hơn có nghĩa là ít chất lượng hơn.",
+ "fieldTextChannelStreamRotate": "Thay đổi góc xem của luồng video.",
+ "fieldTextChannelStreamScaleX": "Chiều rộng của hình ảnh luồng là đầu ra sau khi xử lý.",
+ "fieldTextChannelStreamScaleY": "Chiều cao của hình ảnh luồng là đầu ra sau khi xử lý.",
+ "fieldTextChannelStreamType": "Phương pháp sẽ được sử dụng để tiêu thụ luồng video.",
+ "fieldTextChannelStreamTypeFLV": "Gửi các khung được mã hóa FLV qua WebSocket.",
+ "fieldTextChannelStreamTypeHLS(includesAudio)": "Phương pháp tương tự với các luồng trực tiếp Facebook. bao gồm âm thanh nếu đầu vào cung cấp nó. Có độ trễ khoảng 4-6 giây vì phương pháp này ghi lại các phân đoạn sau đó đẩy chúng đến khách hàng thay vì đẩy như trong khi nó tạo ra chúng.",
+ "fieldTextChannelStreamTypeMJPEG": "Hình ảnh JPEG chuyển động tiêu chuẩn. Không có âm thanh.",
+ "fieldTextChannelStreamTypePoseidon": "Poseidon được xây dựng trên mã xử lý MP4 của Kevin Godell. Nó mô phỏng một tệp MP4 phát trực tuyến nhưng sử dụng dữ liệu của luồng trực tiếp. Bao gồm âm thanh. Một số trình duyệt có thể phát nó giống như một tệp MP4 thông thường. Các luồng qua HTTP hoặc WebSocket.",
+ "fieldTextChannelStreamVcodec": "Video codec để phát trực tuyến.",
+ "fieldTextChannelStreamVcodecAuto": "Hãy để ffmpeg chọn.",
+ "fieldTextChannelStreamVcodecCopy": "Được sử dụng cho video MP4. Có cách sử dụng CPU rất thấp nhưng không thể sử dụng các bộ lọc video và tập tin tệp có thể là khổng lồ. Tốt nhất để thiết lập phía camera cài đặt MP4 của bạn khi sử dụng tùy chọn này.",
+ "fieldTextChannelStreamVcodecLibx264": "Được sử dụng cho video MP4.",
+ "fieldTextChannelStreamVcodecLibx265": "Được sử dụng cho video MP4.",
+ "fieldTextChannelSvf": "Đặt các bộ lọc video FFMPEG vào hộp này để ảnh hưởng đến phần phát trực tuyến. Không có khoảng trắng.",
+ "fieldTextControlInvertY": "Vì khi máy ảnh của bạn được gắn lộn ngược hoặc sử dụng các điều khiển dọc đảo ngược.",
+ "fieldTextCrf": "Số lượng thấp có nghĩa là chất lượng cao hơn. Số lượng cao hơn có nghĩa là ít chất lượng hơn.",
+ "fieldTextCustDetect": "Các cờ tùy chỉnh liên kết với máy dò luồng sử dụng để phân tích.",
+ "fieldTextCustDetectObject": "Các cờ tùy chỉnh liên kết với máy dò luồng sử dụng để phân tích.",
+ "fieldTextCustInput": "Các cờ tùy chỉnh liên kết với đầu vào của quy trình FFMPEG.",
+ "fieldTextCustRecord": "Các cờ tùy chỉnh liên kết với việc ghi lại quy trình FFMPEG.",
+ "fieldTextCustSipRecord": "Các cờ tùy chỉnh liên kết với đầu ra mà các bản ghi dựa trên sự kiện hút từ.",
+ "fieldTextCustSnap": "Cờ tùy chỉnh liên kết với các ảnh chụp nhanh.",
+ "fieldTextCustStream": "Các cờ tùy chỉnh liên kết với luồng (chế độ xem phía máy khách) của quy trình FFMPEG.",
+ "fieldTextCustomOutput": "Thêm đầu ra tùy chỉnh như khung JPEG hoặc gửi dữ liệu thẳng đến máy chủ khác.",
+ "fieldTextCutoff": "Trong vài phút. Khi nào nên cắt bỏ và bắt đầu một tệp video mới.",
+ "fieldTextDays": "Số ngày để giữ video trước khi thanh trừng.",
+ "fieldTextDetailSubstreamInputRtspTransportAuto": "Hãy để FFMPEG quyết định. Thông thường nó sẽ thử UDP trước.",
+ "fieldTextDetailSubstreamInputRtspTransportTCP": "Đặt nó thành điều này nếu UDP bắt đầu cho kết quả không mong muốn.",
+ "fieldTextDetailSubstreamInputRtspTransportUDP": "FFMPEG thử điều này trước.",
+ "fieldTextDetailSubstreamOutputHlsListSize": "Số lượng phân đoạn tối đa trước khi tự động xóa các phân đoạn cũ.",
+ "fieldTextDetailSubstreamOutputHlsTime": "Bao lâu mỗi phân đoạn video, trong vài phút. Mỗi phân đoạn sẽ được khách hàng rút ra thông qua một tệp M3U8. Các phân đoạn ngắn hơn mất ít không gian hơn.",
+ "fieldTextDetailSubstreamOutputPresetStream": "Cờ đặt trước cho một số bộ mã hóa video nhất định. Nếu bạn thấy máy ảnh của bạn bị sập cứ sau vài giây: hãy thử để trống.",
+ "fieldTextDetailSubstreamOutputStreamAcodec": "Codec âm thanh cho phát trực tuyến.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAac": "Được sử dụng cho video MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAc3": "Được sử dụng cho video MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecAuto": "Hãy để ffmpeg chọn.",
+ "fieldTextDetailSubstreamOutputStreamAcodecCopy": "Được sử dụng cho video MP4. Có cách sử dụng CPU rất thấp nhưng một số codec âm thanh cần các cờ tùy chỉnh như -strict 2 cho AAC.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibmp3lame": "Được sử dụng cho video MP4.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibopus": "Được sử dụng cho video webm.",
+ "fieldTextDetailSubstreamOutputStreamAcodecLibvorbis": "Được sử dụng cho video webm.",
+ "fieldTextDetailSubstreamOutputStreamAcodecNoAudio": "Không có âm thanh, đây là một lựa chọn phải được đặt ở một số nơi trên thế giới vì lý do pháp lý.",
+ "fieldTextDetailSubstreamOutputStreamFps": "Tốc độ mà các khung được hiển thị cho máy khách, tính theo khung mỗi giây. Hãy nhận biết không có mặc định. Điều này có thể dẫn đến việc sử dụng băng thông cao.",
+ "fieldTextDetailSubstreamOutputStreamQuality": "Số lượng thấp có nghĩa là chất lượng cao hơn. Số lượng cao hơn có nghĩa là ít chất lượng hơn.",
+ "fieldTextDetailSubstreamOutputStreamRotate": "Thay đổi góc xem của luồng video.",
+ "fieldTextDetailSubstreamOutputStreamScaleX": "Chiều rộng của hình ảnh luồng là đầu ra sau khi xử lý.",
+ "fieldTextDetailSubstreamOutputStreamScaleY": "Chiều cao của hình ảnh luồng là đầu ra sau khi xử lý.",
+ "fieldTextDetailSubstreamOutputStreamType": "Phương pháp sẽ được sử dụng để tiêu thụ luồng video.",
+ "fieldTextDetailSubstreamOutputStreamVcodec": "Video codec để phát trực tuyến.",
+ "fieldTextDetailSubstreamOutputStreamVcodecAuto": "Hãy để ffmpeg chọn.",
+ "fieldTextDetailSubstreamOutputStreamVcodecCopy": "Được sử dụng cho video MP4. Có cách sử dụng CPU rất thấp nhưng không thể sử dụng các bộ lọc video và tập tin tệp có thể là khổng lồ. Tốt nhất để thiết lập phía camera cài đặt MP4 của bạn khi sử dụng tùy chọn này.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx264": "Được sử dụng cho video MP4.",
+ "fieldTextDetailSubstreamOutputStreamVcodecLibx265": "Được sử dụng cho video MP4.",
+ "fieldTextDetailSubstreamOutputSvf": "Đặt các bộ lọc video FFMPEG vào hộp này để ảnh hưởng đến phần phát trực tuyến. Không có khoảng trắng.",
+ "fieldTextDetector": "Điều này sẽ thêm một đầu ra khác trong lệnh FFMPEG cho máy dò chuyển động.",
+ "fieldTextDetectorAudio": "Kiểm tra xem âm thanh đã xảy ra tại một certiain có thể giải quyết được. Đọc có thể phân giải có thể không chính xác để đo lường trong thế giới thực.",
+ "fieldTextDetectorBufferHlsListSize": "Số lượng phân đoạn tối đa trước khi tự động xóa các phân đoạn cũ.",
+ "fieldTextDetectorBufferHlsTime": "Bao lâu mỗi phân đoạn video, trong vài giây. Mỗi phân đoạn sẽ được khách hàng rút ra thông qua một tệp M3U8. Các phân đoạn ngắn hơn mất ít không gian hơn.",
+ "fieldTextDetectorColorThreshold": "Lượng chênh lệch cho phép trong một pixel trước khi nó được coi là chuyển động.",
+ "fieldTextDetectorCommand": "Lệnh sẽ chạy. Đây là tương đương với việc chạy một lệnh shell từ thiết bị đầu cuối.",
+ "fieldTextDetectorCommandTimeout": "Giá trị này là một bộ đếm thời gian để cho phép chạy tập lệnh tiếp theo của bạn. Giá trị này là trong vài phút.",
+ "fieldTextDetectorFps": "Có bao nhiêu khung hình một giây để gửi đến máy dò chuyển động; 2 là mặc định.",
+ "fieldTextDetectorFrame": "Điều này sẽ đọc toàn bộ khung cho sự khác biệt pixel. Điều này giống như tạo ra một khu vực bao gồm toàn bộ màn hình.",
+ "fieldTextDetectorHttpApi": "Bạn có muốn cho phép kích hoạt HTTP cho máy ảnh này không?",
+ "fieldTextDetectorLisencePlate": "Cho phép nhận dạng biển số xe. Plugin OpenalPR có điều này luôn được bật.",
+ "fieldTextDetectorLisencePlateCountry": "Chọn loại tấm để nhận ra. Chỉ có chúng tôi và EU được hỗ trợ tại thời điểm này.",
+ "fieldTextDetectorLockTimeout": "Khóa khi được cho phép kích hoạt tiếp theo, để tránh quá tải cơ sở dữ liệu và nhận máy khách. Được đo bằng mili giây.",
+ "fieldTextDetectorMaxSensitivity": "Xếp hạng tự tin chuyển động phải thấp hơn giá trị này được coi là một kích hoạt. Để trống mà không có tối đa. Tùy chọn này trước đây đã được đặt tên là \"Max DeDiferference\".",
+ "fieldTextDetectorNoiseFilter": "Cố gắng lọc hạt hoặc chuyển động lặp đi lặp lại ở một sự thờ ơ cụ thể.",
+ "fieldTextDetectorNoiseFilterRange": "Lượng chênh lệch cho phép trong một pixel trước khi nó được coi là chuyển động.",
+ "fieldTextDetectorNotrigger": "Kiểm tra nếu chuyển động đã xảy ra trong một khoảng thời gian. Nếu chuyển động đã xảy ra, séc sẽ được đặt lại.",
+ "fieldTextDetectorNotriggerCommand": "Lệnh sẽ chạy. Đây là tương đương với việc chạy một lệnh shell từ thiết bị đầu cuối.",
+ "fieldTextDetectorNotriggerCommandTimeout": "Giá trị này là một bộ đếm thời gian để cho phép chạy tập lệnh tiếp theo của bạn. Giá trị này là trong vài phút.",
+ "fieldTextDetectorNotriggerDiscord": "Nếu chuyển động chưa được phát hiện sau khoảng thời gian chờ, bạn sẽ nhận được thông báo bất hòa.",
+ "fieldTextDetectorNotriggerTimeout": "Thời gian chờ được tính trong vài phút.",
+ "fieldTextDetectorNotriggerWebhook": "Gửi yêu cầu nhận đến URL với một số giá trị từ sự kiện.",
+ "fieldTextDetectorObjCount": "Đếm các đối tượng được phát hiện.",
+ "fieldTextDetectorObjCountInRegion": "Đếm các đối tượng chỉ bên trong các vùng.",
+ "fieldTextDetectorPam": "Sử dụng máy dò chuyển động của Kevin Godell. Điều này được tích hợp vào Shinobi và không yêu cầu cấu hình khác để kích hoạt.",
+ "fieldTextDetectorPtzFollow": "Thực hiện theo đối tượng được phát hiện lớn nhất với PTZ? Yêu cầu một máy dò đối tượng chạy hoặc ma trận được cung cấp với các sự kiện.",
+ "fieldTextDetectorRecordMethod": "Có nhiều cách để bắt đầu ghi lại khi một sự kiện xảy ra, như chuyển động. Ghi âm truyền thống là thân thiện nhất với người dùng.",
+ "fieldTextDetectorSave": "Lưu các sự kiện chuyển động trong SQL. Điều này sẽ cho phép hiển thị chuyển động qua video trong thời gian các sự kiện chuyển động xảy ra trong trình xem Power.",
+ "fieldTextDetectorScaleX": "Chiều rộng của hình ảnh được phát hiện. Kích thước nhỏ hơn mất ít CPU hơn.",
+ "fieldTextDetectorScaleY": "Chiều cao của hình ảnh được phát hiện. Kích thước nhỏ hơn mất ít CPU hơn.",
+ "fieldTextDetectorSendFrames": "Đẩy khung vào plugin được kết nối để phân tích.",
+ "fieldTextDetectorSendFramesObject": "Đẩy khung vào plugin được kết nối để phân tích.",
+ "fieldTextDetectorSendVideoLength": "Trong vài giây. Độ dài của video được gửi đến dịch vụ thông báo của bạn, như email hoặc Discord.",
+ "fieldTextDetectorSensitivity": "Xếp hạng tự tin chuyển động phải vượt quá giá trị này để được coi là một kích hoạt. Số này tương quan trực tiếp với xếp hạng độ tin cậy được trả về bởi máy dò chuyển động. Tùy chọn này trước đây được đặt tên là \"Sự thờ ơ\".",
+ "fieldTextDetectorThreshold": "Số lượng phát hiện tối thiểu để bắn một sự kiện chuyển động. Phát hiện phải nằm trong máy dò, ngưỡng chia cho máy dò giây FPS. Ví dụ: nếu FPS của máy dò là 2 và ngưỡng kích hoạt là 3, thì ba phát hiện phải xảy ra trong vòng 1,5 giây để kích hoạt sự kiện chuyển động. Ngưỡng này là khu vực phát hiện.",
+ "fieldTextDetectorTimeout": "Khoảng thời gian của \"bản ghi kích hoạt\" sẽ chạy cho. Điều này được đọc trong vài phút.",
+ "fieldTextDetectorTrigger": "Điều này sẽ đặt hàng máy ảnh để ghi nếu nó được đặt thành \"chỉ xem\" khi phát hiện sự kiện.",
+ "fieldTextDetectorUseDetectObject": "Tạo khung để gửi đến bất kỳ plugin được kết nối.",
+ "fieldTextDetectorWebhook": "Gửi yêu cầu nhận đến URL với một số giá trị từ sự kiện.",
+ "fieldTextDetectorWebhookTimeout": "Giá trị này là một bộ đếm thời gian để cho phép chạy webhook tiếp theo của bạn. Giá trị này là trong vài phút.",
+ "fieldTextDir": "Vị trí của nơi các tập tin được ghi sẽ được lưu. Bạn có thể định cấu hình nhiều vị trí hơn với biến addStorage .",
+ "fieldTextEventDays": "Số ngày để giữ các sự kiện trước khi thanh trừng.",
+ "fieldTextEventMonPop": "Khi một sự kiện xảy ra bật lên luồng màn hình.",
+ "fieldTextEventRecordScaleX": "Chiều rộng của hình ảnh ghi dựa trên sự kiện là đầu ra sau khi xử lý.",
+ "fieldTextEventRecordScaleY": "Chiều cao của hình ảnh ghi dựa trên sự kiện là đầu ra sau khi xử lý.",
+ "fieldTextExt": "Loại tệp cho tệp video được ghi của bạn.",
+ "fieldTextExtMP4": "Loại tệp này có thể chơi được gần như tất cả các trình duyệt web hiện đại, bao gồm cả thiết bị di động. Các tập tin chỉ có xu hướng lớn hơn trừ khi bạn giảm chất lượng.",
+ "fieldTextExtWebM": "Tệp nhỏ, khả năng tương thích của khách hàng thấp. Tốt để tải lên các trang web như YouTube.",
+ "fieldTextFactorAuth": "Kích hoạt yêu cầu thứ cấp để đăng nhập thông qua một trong các phương thức đã bật.",
+ "fieldTextFatalMax": "Số lần thử lại cho kết nối mạng giữa máy chủ và máy ảnh trước khi đặt màn hình thành bị vô hiệu hóa. Không có số thập phân. Đặt thành 0 thành thử lại mãi mãi.",
+ "fieldTextFps": "Tốc độ trong đó các khung được ghi vào các tệp, khung mỗi giây. Hãy nhận biết không có mặc định. Điều này có thể dẫn đến các tập tin lớn. Tốt nhất để đặt phía máy ảnh này.",
+ "fieldTextHeight": "Chiều cao của hình ảnh luồng.",
+ "fieldTextHlsListSize": "Số lượng phân đoạn tối đa trước khi tự động xóa các phân đoạn cũ.",
+ "fieldTextHlsTime": "Bao lâu mỗi phân đoạn video, trong vài phút. Mỗi phân đoạn sẽ được khách hàng rút ra thông qua một tệp M3U8. Các phân đoạn ngắn hơn mất ít không gian hơn.",
+ "fieldTextHost": "Địa chỉ kết nối",
+ "fieldTextHwaccel": "Công cụ giải mã",
+ "fieldTextHwaccelVcodec": "Công cụ giải mã",
+ "fieldTextInverseTrigger": "Để kích hoạt các vùng được chỉ định bên ngoài. Sẽ không kích hoạt với phát hiện khung đầy đủ được bật.",
+ "fieldTextIp": "Phạm vi hoặc đơn",
+ "fieldTextIrCutFilterAuto": "Bộ lọc cắt IR được kích hoạt tự động bởi thiết bị.",
+ "fieldTextIrCutFilterOff": "Tắt ir cắt fiter. Thông thường chế độ ban đêm.",
+ "fieldTextIrCutFilterOn": "Bật IR Cut Fiter. Thông thường chế độ ban ngày.",
+ "fieldTextIsOnvif": "Đây có phải là một máy ảnh tuân thủ ONVIF?",
+ "fieldTextLang": "Ngôn ngữ chính của các yếu tố văn bản. Để dịch hoàn chỉnh, hãy thêm ngôn ngữ của bạn trong conf.json, ví dụ: \"ngôn ngữ\": \"en_ca\", ",
+ "fieldTextLogDays": "Số ngày để giữ nhật ký trước khi thanh trừng.",
+ "fieldTextLoglevel": "Số lượng dữ liệu để cung cấp trong khi thực hiện công việc.",
+ "fieldTextLoglevelAllWarnings": "Hiển thị tất cả các cảnh báo. Sử dụng điều này nếu bạn không thể tìm ra điều gì sai với máy ảnh của mình.",
+ "fieldTextLoglevelFatal": "Chỉ hiển thị các lỗi gây tử vong.",
+ "fieldTextLoglevelOnError": "Hiển thị tất cả các lỗi quan trọng. Lưu ý: Điều này không phải lúc nào cũng hiển thị thông tin quan trọng.",
+ "fieldTextLoglevelSilent": "Không có. Điều này sẽ im lặng tất cả ghi nhật ký.",
+ "fieldTextMail": "Đăng nhập cho tài khoản. Địa chỉ email của chủ tài khoản chính sẽ nhận được thông báo.",
+ "fieldTextMapRtspTransportAuto": "Hãy để FFMPEG quyết định. Thông thường nó sẽ thử UDP trước.",
+ "fieldTextMapRtspTransportTCP": "Đặt nó thành điều này nếu UDP bắt đầu cho kết quả không mong muốn.",
+ "fieldTextMapRtspTransportUDP": "FFMPEG thử điều này trước.",
+ "fieldTextMaxKeepDays": "Số ngày để giữ video trước khi thanh lọc cho màn hình này cụ thể.",
+ "fieldTextMid": "Đây là một định danh không thể thay đổi cho màn hình. Bạn có thể sao chép một màn hình bằng cách nhấp đúp vào ID màn hình và thay đổi nó.",
+ "fieldTextMode": "Đây là nhiệm vụ chính của màn hình.",
+ "fieldTextModeDisabled": "Màn hình không hoạt động, không có quy trình nào sẽ được tạo trong chế độ này.",
+ "fieldTextModeRecord": "Ghi âm liên tục. Các phân đoạn được thực hiện cứ sau 15 phút theo mặc định.",
+ "fieldTextModeWatchOnly": "Màn hình sẽ chỉ phát trực tuyến, không có bản ghi nào sẽ xảy ra trừ khi API hoặc máy dò được đặt hàng khác.",
+ "fieldTextMpass": "Mật khẩu cho máy ảnh của bạn",
+ "fieldTextMuser": "Người dùng đăng nhập cho máy ảnh của bạn",
+ "fieldTextName": "Đây là tên hiển thị có thể đọc được cho con người cho màn hình.",
+ "fieldTextNotes": "Nhận xét bạn muốn để lại cho máy ảnh này.",
+ "fieldTextOnvifNonStandard": "Đây có phải là một máy ảnh ONVIF không chuẩn?",
+ "fieldTextOnvifPort": "ONVIF thường được chạy trên cổng 8000 . Điều này có thể là 80 cũng như tùy thuộc vào mô hình camera của bạn.",
+ "fieldTextPass": "Để trống để giữ cùng một mật khẩu trong quá trình sửa đổi cài đặt.",
+ "fieldTextPasswordAgain": "Phải phù hợp với trường mật khẩu nếu bạn muốn thay đổi nó.",
+ "fieldTextPath": "Đường dẫn đến máy ảnh của bạn",
+ "fieldTextPort": "Tách biệt bằng dấu phẩy hoặc một phạm vi",
+ "fieldTextPortForce": "Sử dụng cổng web mặc định có thể cho phép chuyển đổi tự động sang các cổng khác cho các luồng như RTSP.",
+ "fieldTextPresetRecord": "Cờ đặt trước cho một số bộ mã hóa video nhất định. Nếu bạn thấy máy ảnh của bạn bị sập cứ sau vài giây: hãy thử để trống.",
+ "fieldTextPresetStream": "Cờ đặt trước cho một số bộ mã hóa video nhất định. Nếu bạn thấy máy ảnh của bạn bị sập cứ sau vài giây: hãy thử để trống.",
+ "fieldTextProbesize": "Chỉ định mức độ lớn để làm cho đầu dò phân tích cho đầu vào. Đặt thành 100000 nếu bạn đang sử dụng RTSP và gặp sự cố luồng.",
+ "fieldTextProtocol": "Giao thức sẽ được sử dụng để tiêu thụ luồng video.",
+ "fieldTextRecordScaleX": "Chiều rộng của hình ảnh luồng.",
+ "fieldTextRecordScaleY": "Chiều cao của hình ảnh luồng.",
+ "fieldTextRecordTimelapse": "Tạo một thời gian dựa trên JPEG.",
+ "fieldTextRecordTimelapseMp4": "Tạo một tệp MP4 vào cuối mỗi ngày cho Timelapse.",
+ "fieldTextRecordTimelapseWatermark": "Một hình ảnh được ghi vào khung của video được ghi.",
+ "fieldTextRecordTimelapseWatermarkLocation": "Vị trí hình ảnh sẽ được sử dụng làm hình mờ.",
+ "fieldTextRecordTimelapseWatermarkPosition": "Một hình ảnh được ghi vào khung của video được ghi.",
+ "fieldTextRotate": "Thay đổi góc ghi của luồng video.",
+ "fieldTextRtmpKey": "Khóa luồng cho các luồng đến trên cổng RTMP.",
+ "fieldTextRtspTransport": "Giao thức vận chuyển máy ảnh của bạn sẽ sử dụng. TCP thường là sự lựa chọn tốt nhất.",
+ "fieldTextRtspTransportAuto": "Hãy để FFMPEG quyết định. Thông thường nó sẽ thử UDP trước.",
+ "fieldTextRtspTransportHTTP": "Phương pháp kết nối tiêu chuẩn.",
+ "fieldTextRtspTransportTCP": "Đặt nó thành điều này nếu UDP bắt đầu cho kết quả không mong muốn.",
+ "fieldTextRtspTransportUDP": "FFMPEG thử điều này trước.",
+ "fieldTextSfps": "Chỉ định tốc độ khung hình (FPS) trong đó máy ảnh đang cung cấp luồng của nó.",
+ "fieldTextSignalCheck": "Tần suất khách hàng của bạn sẽ kiểm tra luồng để xem nó có còn sống không. Điều này được tính toán trong vài phút.",
+ "fieldTextSignalCheckLog": "Điều này chỉ dành cho phía khách hàng. Nó sẽ hiển thị trong luồng nhật ký khi kiểm tra tín hiệu phía máy khách xảy ra.",
+ "fieldTextSize": "Lượng không gian đĩa shinobi sẽ cho phép tiêu thụ trước khi thanh lọc. Giá trị này được đọc trong megabyte.",
+ "fieldTextSizeFilebinPercent": "Phần trăm số lượng lưu trữ tối đa mà kho lưu trữ FileBin có thể sử dụng.",
+ "fieldTextSizeTimelapsePercent": "Phần trăm số lượng lưu trữ tối đa mà các khung thời gian có thể ghi lại.",
+ "fieldTextSizeVideoPercent": "Phần trăm số lượng lưu trữ tối đa mà các video có thể ghi lại.",
+ "fieldTextSkipPing": "Chọn nếu một ping thành công được yêu cầu trước khi quá trình giám sát được bắt đầu.",
+ "fieldTextSnap": "Nhận khung mới nhất trong JPEG.",
+ "fieldTextSnapSecondsInward": "trong vài giây",
+ "fieldTextSqllog": "Sử dụng điều này một cách thận trọng vì FFMPEG thích ném dữ liệu thừa vào những thời điểm có thể dẫn đến rất nhiều hàng cơ sở dữ liệu.",
+ "fieldTextSqllogNo": "Không là mặc định.",
+ "fieldTextSqllogYes": "Làm điều này nếu bạn chỉ gặp vấn đề định kỳ.",
+ "fieldTextStreamAcodec": "Codec âm thanh cho phát trực tuyến.",
+ "fieldTextStreamAcodecAac": "Được sử dụng cho video MP4.",
+ "fieldTextStreamAcodecAc3": "Được sử dụng cho video MP4.",
+ "fieldTextStreamAcodecAuto": "Hãy để ffmpeg chọn.",
+ "fieldTextStreamAcodecCopy": "Được sử dụng cho video MP4. Có cách sử dụng CPU rất thấp nhưng một số codec âm thanh cần các cờ tùy chỉnh như -strict 2 cho AAC.",
+ "fieldTextStreamAcodecLibmp3lame": "Được sử dụng cho video MP4.",
+ "fieldTextStreamAcodecLibopus": "Được sử dụng cho video webm.",
+ "fieldTextStreamAcodecLibvorbis": "Được sử dụng cho video webm.",
+ "fieldTextStreamAcodecNoAudio": "Không có âm thanh, đây là một lựa chọn phải được đặt ở một số nơi trên thế giới vì lý do pháp lý.",
+ "fieldTextStreamFlvType": "Đây chỉ dành cho bảng điều khiển Shinobi. Cả hai phương thức luồng vẫn đang hoạt động và sẵn sàng để sử dụng.",
+ "fieldTextStreamFps": "Tốc độ mà các khung được hiển thị cho máy khách, tính theo khung mỗi giây. Hãy nhận biết không có mặc định. Điều này có thể dẫn đến việc sử dụng băng thông cao.",
+ "fieldTextStreamLoop": "Viết một tệp tĩnh để luồng tệp hoạt động giống như một luồng trực tiếp.",
+ "fieldTextStreamQuality": "Số lượng thấp có nghĩa là chất lượng cao hơn. Số lượng cao hơn có nghĩa là ít chất lượng hơn.",
+ "fieldTextStreamRotate": "Thay đổi góc xem của luồng video.",
+ "fieldTextStreamScaleX": "Chiều rộng của hình ảnh luồng là đầu ra sau khi xử lý.",
+ "fieldTextStreamScaleY": "Chiều cao của hình ảnh luồng là đầu ra sau khi xử lý.",
+ "fieldTextStreamTimestamp": "Một chiếc đồng hồ được ghi vào khung của luồng video.",
+ "fieldTextStreamTimestampBoxColor": "Màu sắc thời gian Timstamp.",
+ "fieldTextStreamTimestampColor": "Màu văn bản Timstamp.",
+ "fieldTextStreamTimestampFont": "Tệp phông chữ để tạo kiểu thời gian của bạn.",
+ "fieldTextStreamTimestampFontSize": "Kích thước phông chữ trong Pt.",
+ "fieldTextStreamTimestampX": "Vị trí thời gian của thời gian",
+ "fieldTextStreamTimestampY": "Vị trí dọc của dấu thời gian",
+ "fieldTextStreamType": "Phương pháp sẽ được sử dụng để tiêu thụ luồng video.",
+ "fieldTextStreamTypeBase64OverWebsocket": "Gửi các khung được mã hóa Base64 qua WebSocket. Điều này tránh bộ nhớ đệm nhưng không có âm thanh.",
+ "fieldTextStreamTypeFLV": "Gửi các khung được mã hóa FLV qua WebSocket.",
+ "fieldTextStreamTypeHLS(includesAudio)": "Phương pháp tương tự với các luồng trực tiếp Facebook. bao gồm âm thanh nếu đầu vào cung cấp nó. Có độ trễ khoảng 4-6 giây vì phương pháp này ghi lại các phân đoạn sau đó đẩy chúng đến khách hàng thay vì đẩy như trong khi nó tạo ra chúng.",
+ "fieldTextStreamTypeMJPEG": "Hình ảnh JPEG chuyển động tiêu chuẩn. Không có âm thanh.",
+ "fieldTextStreamTypePoseidon": "Poseidon được xây dựng trên mã xử lý MP4 của Kevin Godell. Nó mô phỏng một tệp MP4 phát trực tuyến nhưng sử dụng dữ liệu của luồng trực tiếp. Bao gồm âm thanh. Một số trình duyệt có thể phát nó giống như một tệp MP4 thông thường. Các luồng qua HTTP hoặc WebSocket.",
+ "fieldTextStreamVcodec": "Video codec để phát trực tuyến.",
+ "fieldTextStreamVcodecAuto": "Hãy để ffmpeg chọn.",
+ "fieldTextStreamVcodecCopy": "Được sử dụng cho video MP4. Có cách sử dụng CPU rất thấp nhưng không thể sử dụng các bộ lọc video và tập tin tệp có thể là khổng lồ. Tốt nhất để thiết lập phía camera cài đặt MP4 của bạn khi sử dụng tùy chọn này.",
+ "fieldTextStreamVcodecLibx264": "Được sử dụng cho video MP4.",
+ "fieldTextStreamVcodecLibx265": "Được sử dụng cho video MP4.",
+ "fieldTextStreamVf": "Đặt các bộ lọc video FFMPEG vào hộp này để ảnh hưởng đến phần phát trực tuyến. Không có khoảng trắng.",
+ "fieldTextStreamWatermark": "Một hình ảnh được ghi vào khung của luồng video.",
+ "fieldTextStreamWatermarkLocation": "Vị trí hình ảnh sẽ được sử dụng làm hình mờ.",
+ "fieldTextStreamWatermarkPosition": "Một hình ảnh được ghi vào khung của luồng video.",
+ "fieldTextTimestamp": "Một chiếc đồng hồ được ghi vào các khung của video được ghi lại.",
+ "fieldTextTimestampBoxColor": "Màu sắc thời gian Timstamp.",
+ "fieldTextTimestampColor": "Màu văn bản Timstamp.",
+ "fieldTextTimestampFont": "Tệp phông chữ để tạo kiểu thời gian của bạn.",
+ "fieldTextTimestampFontSize": "Kích thước phông chữ trong Pt.",
+ "fieldTextTimestampX": "Vị trí thời gian của thời gian",
+ "fieldTextTimestampY": "Vị trí dọc của dấu thời gian",
+ "fieldTextTvChannel": "Màn hình này sẽ có các tính năng kênh TV được bật. Bạn sẽ có thể xem nó trong danh sách kênh TV của bạn.",
+ "fieldTextTvChannelGroupTitle": "Một nhóm tùy chỉnh cho kênh.",
+ "fieldTextTvChannelId": "Một ID tùy chỉnh cho kênh.",
+ "fieldTextType": "Phương pháp sẽ được sử dụng để tiêu thụ luồng video.",
+ "fieldTextTypeDashcam(StreamerV2)": "Luồng P2P dựa trên webm Webm.",
+ "fieldTextTypeH.264/H.265/H.265+": "Đọc một video video chất lượng cao đôi khi bao gồm âm thanh.",
+ "fieldTextTypeHLS(.m3u8)": "Đọc một video video chất lượng cao đôi khi bao gồm âm thanh.",
+ "fieldTextTypeJPEG": "Đọc ảnh chụp nhanh từ URL và tạo luồng và/hoặc video từ chúng.",
+ "fieldTextTypeLocal": "Đọc thẻ chụp, webcam hoặc camera tích hợp.",
+ "fieldTextTypeMJPEG": "Tương tự như JPEG ngoại trừ việc xử lý khung được thực hiện bởi FFMPEG, không phải shinobi.",
+ "fieldTextTypeMPEG4(.mp4/.ts)": "Một tập tin tĩnh. Đọc với tốc độ thấp hơn và không nên được sử dụng cho một luồng trực tiếp thực tế.",
+ "fieldTextTypeMxPEG": "Mobotix MJPEG Stream",
+ "fieldTextTypeRTMP": "Tìm hiểu để kết nối tại đây: bài viết: Cách đẩy các luồng qua RTMP đến Shinobi ",
+ "fieldTextTypeShinobiStreamer": "Luồng P2P dựa trên WebSocket JPEG.",
+ "fieldTextVcodec": "Video codec để ghi.",
+ "fieldTextVf": "Đặt các bộ lọc video FFMPEG vào hộp này để ảnh hưởng đến phần ghi. Không có khoảng trắng.",
+ "fieldTextWallClockTimestampIgnore": "Dựa trên tất cả dữ liệu camera đến trong thời gian camera thay vì thời gian máy chủ.",
+ "fieldTextWatchdogReset": "Nếu có sự chồng chéo trong bản ghi kích hoạt nên đặt lại.",
+ "fieldTextWatermark": "Một hình ảnh được ghi vào khung của video được ghi.",
+ "fieldTextWatermarkLocation": "Vị trí hình ảnh sẽ được sử dụng làm hình mờ.",
+ "fieldTextWatermarkPosition": "Một hình ảnh được ghi vào khung của video được ghi.",
+ "fieldTextWidth": "Chiều rộng của hình ảnh luồng.",
+ "fire hydrant": "vòi chữa cháy",
+ "flv": "flv",
+ "for Global Access": "để truy cập toàn cầu",
+ "fork": "cái nĩa",
+ "frisbee": "chiếc dĩa nhựa ném",
+ "getAMonitor": "Nhận một màn hình",
+ "getATvChannel": "Nhận các kênh truyền hình cho màn hình",
+ "getATvChannelText": "Nhận một màn hình H.264 có sẵn trong danh sách phát .M3U8.",
+ "getAllMonitors": "Nhận tất cả các màn hình",
+ "getAllTvChannels": "Nhận tất cả các kênh TV",
+ "getAllTvChannelsText": "Nhận tất cả các luồng H.264 có sẵn trong danh sách phát .M3U8. Kích hoạt tùy chọn kênh TV trong cài đặt màn hình của bạn để xem các luồng của chúng trong danh sách này.",
+ "getUserInfo": "Nhận thông tin người dùng",
+ "getVideos": "Nhận video",
+ "getVideosForMonitor": "Nhận video cho màn hình",
+ "giraffe": "hươu cao cổ",
+ "h264_cuvid": "H.264 CUVID",
+ "h264_mmal": "H.264 (Raspberry Pi)",
+ "h264_nvenc": "H.264 NVENC (Nvidia HW Accel)",
+ "h264_omx": "H.264 OpenMax (Raspberry Pi)",
+ "h264_qsv": "H.264 (Video đồng bộ nhanh)",
+ "h264_vaapi": "H.264 VA-API (Intel HW Accel)",
+ "h265BrowserText1": "Nếu bạn đang cố gắng phát một tệp H.265, bạn có thể cần tải xuống và mở nó trong một ứng dụng khác như VLC.",
+ "hair drier": "máy sấy tóc",
+ "handbag": "túi xách tay",
+ "hevc_cuvid": "H.265 CUVID",
+ "hevc_nvenc": "H.265 NVENC (Nvidia HW Accel)",
+ "hevc_qsv": "H.265 (Video đồng bộ nhanh)",
+ "hevc_vaapi": "H.265 VA-API (Intel HW Accel)",
+ "hlsOptions": "Tùy chọn HLS",
+ "hlsOptionsInvalid": "Tùy chọn HLS không hợp lệ",
+ "horse": "con ngựa",
+ "hot dog": "bánh mì kẹp xúc xích",
+ "hour": "giờ",
+ "hours": "giờ",
+ "hwaccel": "Động cơ tăng tốc",
+ "hwaccel_device": "Thiết bị Hwaccel",
+ "hwaccel_vcodec": "Bộ giải mã video",
+ "in": "trong",
+ "in Days": "trong những ngày",
+ "in seconds": "trong vài giây",
+ "keyId": "ID chính",
+ "keyboard": "bàn phím",
+ "kite": "cánh diều",
+ "knife": "dao",
+ "laptop": "máy tính xách tay",
+ "lastLogin": "Lân đăng nhập cuôi",
+ "libmp3lame": "libmp3lame",
+ "libopus": "Libopus",
+ "libvorbis (Default)": "libvorbis (mặc định)",
+ "libvpx (Default)": "libvpx (mặc định)",
+ "libvpx-vp9": "libvpx-vp9",
+ "libx264": "libx264",
+ "libx264 (Default)": "libx264 (mặc định)",
+ "libx265": "libx265",
+ "liveGridDescription": "Live Grid là màn hình nhiều luồng cho Shinobi. Phương pháp xem này chủ yếu được thiết kế cho máy tính để bàn.",
+ "loginHandleUnbound": "Đăng nhập đã được liên kết từ tài khoản này.",
+ "microwave": "lò vi sóng",
+ "migrateText1": " Loại đầu vào không thể được phân tích cú pháp. Vui lòng đặt nó theo cách thủ công.",
+ "minute": "phút",
+ "minutes": "phút",
+ "mjpeg_cuvid": "MJPEG CUVID",
+ "modifyVideoText1": "Phương pháp không tồn tại. Kiểm tra để đảm bảo rằng giá trị cuối cùng của URL không trống.",
+ "monSavedButNotCopied": "Màn hình của bạn đã được lưu nhưng không được sao chép vào bất kỳ màn hình nào khác.",
+ "monitorConfigFinderDescription": "Công cụ này sẽ giúp bạn tìm kiếm cấu hình cho máy ảnh được đăng bởi cộng đồng. Tất cả được lưu trữ trên shinobihub . Bạn cũng có thể đăng của bạn, nó thực sự sẽ giúp cộng đồng :)",
+ "monitorEditFailedMaxReached": "Tài khoản của bạn đã đạt đến số lượng máy ảnh tối đa có thể được tạo. Nói chuyện với một quản trị viên nếu bạn muốn điều này đã thay đổi.",
+ "monitorEditText1": "Dữ liệu không hợp lệ, kiểm tra xem đây là một chuỗi nhập hợp lệ.",
+ "monitorEditText2": "Chuỗi chi tiết không hợp lệ. Kiểm tra xem đó là một chuỗi JSON và không phải là một đối tượng thông thường được truyền.",
+ "monitorGetText1": "Yêu cầu không đầy đủ, xóa dấu gạch chéo cuối cùng trong URL hoặc đặt giá trị chấp nhận được.",
+ "monitorStateNotEnoughChanges": "Bạn cần thực hiện thay đổi trong cấu hình màn hình của mình trước khi cố gắng thêm nó vào cài đặt trước.",
+ "monitorStatesError": "Giám sát lỗi đặt trước",
+ "months": "tháng",
+ "motorcycle": "xe máy",
+ "mouse": "con chuột",
+ "mpeg2_mmal": "MPEG-2 (Raspberry Pi)",
+ "mpeg2_qsv": "MPEG2 (Video đồng bộ nhanh)",
+ "mpeg4_cuvid": "MPEG4 CUVID",
+ "mpeg4_mmal": "MPEG-4 (Raspberry Pi)",
+ "noLoginTokensAdded": "Không có thông tin đăng nhập thay thế liên quan đến tài khoản này.",
+ "noSpecialCharacters": "Không có không gian hoặc ký tự đặc biệt.",
+ "noTriggerText": "Nếu chuyển động chưa được phát hiện sau khoảng thời gian chờ, bạn sẽ nhận được thông báo bất hòa.",
+ "noUndoForAction": "Bạn không thể hoàn tác hành động này.",
+ "notActivatedText": "Cài đặt của bạn đã không kích hoạt.",
+ "notEnoughFramesText1": "Không đủ khung để biên dịch.",
+ "notPermitted1": "Hành động này không được quản trị viên của tài khoản của bạn cho phép. '",
+ "on": "trên",
+ "on Error": "về lỗi",
+ "on Event": "về sự kiện",
+ "onvifdeviceManagerGlobalTip": "ONVIF cho phép sửa đổi cài đặt nội bộ của máy ảnh. Onvif là một phần của một thuật ngữ ô, nó có thể có nghĩa là nhiều điều không may. Với trường hợp đó, bạn có thể thấy một tùy chọn trong công cụ này nhưng nó có thể không thể chỉnh sửa được. Điều này thường là do nhà cung cấp máy ảnh không thêm phương pháp này hoặc đã đi chệch khỏi mức sử dụng dự định của nó. Trong những trường hợp đó, bạn sẽ cần nhập cấu hình của máy ảnh thông qua phương thức quy định của nhà cung cấp máy ảnh, đây thường là mở địa chỉ IP của máy ảnh trong trình duyệt web của bạn.",
+ "onvifdeviceSavedFoundErrorText": "Một số cài đặt có thể đã hoàn nguyên về một giá trị trước đó. Có thể tùy chọn sửa đổi không có sẵn với máy ảnh này thông qua ONVIF.",
+ "onvifdeviceSavedText": "Cài đặt nội bộ của máy ảnh đã được lưu. Bạn có thể cần phải khởi động lại máy ảnh để có những thay đổi này có hiệu lực.",
+ "openImagesDownloadConfirm": "Bạn có chắc là bạn muốn bắt đầu tải xuống hình ảnh và hộp giới hạn (ma trận đặt trước) từ OpenImages?",
+ "openImagesDownloadConfirmStop": "Bạn có chắc là bạn muốn ngừng đào tạo?",
+ "opencl": "Opencl",
+ "opencvCascadesText": "Nếu bạn không thấy gì ở đây thì chỉ cần tải xuống gói Cascades . Thả chúng vào plugin/opencv/cascades sau đó nhấn làm mới .",
+ "orange": "trái cam",
+ "oven": "lò",
+ "p2pServerNotSelectedText": "Chọn một máy chủ từ danh sách và nhấn Lưu. Đợi 10 giây sau đó cố gắng mở bảng điều khiển từ xa.",
+ "p2pSettingsText1": "Bạn sẽ cần phải làm mới trang này để các thay đổi được áp dụng.",
+ "parking meter": "Đồng hồ đỗ xe",
+ "performanceOptimizeText1": "Máy ảnh của bạn đang cung cấp dữ liệu luồng H.264. Bạn có thể đặt loại luồng thành HLS, Poseidon và video codec để sao chép.",
+ "person": "người",
+ "pizza": "pizza",
+ "possibleInternalError": "Có thể xảy ra lỗi nội bộ",
+ "postDataBroken": "Kiểm tra định dạng của JSON. Đảm bảo nó được xâu chuỗi và được xác định trong 'dữ liệu'",
+ "potted plant": "cây chậu",
+ "powerVideoEventLimit": "Bạn đã đặt giới hạn sự kiện cao. Bạn có chắc là bạn muốn thực hiện yêu cầu này?",
+ "privateKey": "Khóa riêng",
+ "qsv": "QSV",
+ "rebootingCamera": "Khởi động lại camera",
+ "refrigerator": "tủ lạnh",
+ "remote": "Xa xôi",
+ "restartRequired": "Khởi động lại lõi Shinobi là cần thiết cho các thay đổi để có hiệu lực.",
+ "sandwich": "bánh mì sandwich",
+ "scissors": "cây kéo",
+ "separateByCommasOrRange": "Tách biệt bằng dấu phẩy hoặc một phạm vi",
+ "setMaxStorageAmountText": "Bạn nên đặt số tiền lưu trữ tối đa của mình trong cài đặt tài khoản ở bên trái. Tìm tùy chọn trong phần Hồ sơ. Mặc định là 10 GB.",
+ "sheep": "con cừu",
+ "sink": "bồn rửa chén",
+ "sizePurgeLockedText": "Khóa thanh lọc kích thước (DeleteOverMax) dường như không mở khóa. Mở khóa ngay bây giờ ...",
+ "skateboard": "ván trượt",
+ "skipPingText1": "Hãy thử đặt \"Bỏ qua ping\" thành Có.",
+ "skis": "ván trượt",
+ "snowboard": "trượt tuyết",
+ "sorryNo": "Xin lỗi, không",
+ "sorryNothingWasFound": "Xin lỗi, không có gì được tìm thấy.",
+ "spoon": "cái thìa",
+ "sports ball": "bóng thể thao",
+ "startUpText0": "Kiểm tra đĩa được sử dụng ..",
+ "startUpText1": "Hoàn thành kiểm tra đĩa được sử dụng.",
+ "startUpText2": "Tất cả người dùng đã kiểm tra, đợi để đóng các tệp mở và xóa các tệp qua giới hạn người dùng",
+ "startUpText3": "Chờ đợi để cung cấp video chưa hoàn thành kiểm tra một thời gian. 3 giây.",
+ "startUpText4": "Bắt đầu màn hình ... Vui lòng đợi ...",
+ "startUpText5": "Shinobi đã sẵn sàng.",
+ "startUpText6": "Video mồ côi được tìm thấy và chèn",
+ "stop sign": "biển báo dừng",
+ "subAccountManager": "Quản lý tài khoản phụ",
+ "substreamConnectionText": "Bạn có thể để lại chi tiết kết nối nếu bạn muốn nó sử dụng thông tin kết nối chính ở trên.",
+ "substreamOutputText": "Tại đây bạn có thể đặt cấu hình của luồng theo yêu cầu. Tìm hiểu về độ trễ của các loại luồng tại đây. ",
+ "substreamText": "Đây là một phương pháp theo yêu cầu để xem luồng trực tiếp. Bạn có thể làm cho nó để quá trình xem chỉ có sẵn khi ai đó đang xem hoặc được sử dụng để chuyển đổi giữa độ phân giải thấp và cao.",
+ "suitcase": "chiếc vali",
+ "superAdminText": "\"Super.json\" không tồn tại. Vui lòng đổi tên \"Super.sample.json\" thành \"Super.json\".",
+ "superAdminTitle": "Shinobi: Super Admin",
+ "surfboard": "ván lướt sóng",
+ "teddy bear": "gấu bông",
+ "tennis racket": "vợt tennis",
+ "tie": "cà vạt",
+ "toaster": "Máy nướng bánh mì",
+ "toilet": "phòng vệ sinh",
+ "tokenNotUserBound": "Tay cầm đăng nhập này không được liên kết với người dùng trên máy chủ này!",
+ "tokenNotUserBoundPt2": "Nhập thông tin đăng nhập của bạn sau đó sử dụng nút Đăng nhập Google để liên kết nhanh chóng.",
+ "toothbrush": "Bàn chải đánh răng",
+ "total": "toàn bộ",
+ "traffic light": "đèn giao thông",
+ "train": "tàu hỏa",
+ "truck": "xe tải",
+ "tv": "TV",
+ "umbrella": "ô",
+ "undoAllUnsaveChanges": "Bạn có chắc chắn muốn làm điều này? Điều này sẽ hoàn tác tất cả các thay đổi chưa được lưu.",
+ "unexpectedExitText": "Thông tin về lối ra này sẽ được tìm thấy trước nhật ký này. Ngoài ra, đây là lệnh FFMPEG đã được sử dụng khi quá trình bị sập.",
+ "updateCamerasInternalSettings": "Cập nhật cài đặt nội bộ của camera?",
+ "updateKeyText1": "\"UpdateKey\" bị thiếu trong \"Conf.json\", không thể cập nhật theo cách này cho đến khi bạn thêm nó.",
+ "updateKeyText2": "\"UpdateKey\" không chính xác.",
+ "updateNotice1": "Cập nhật Shinobi có nghĩa là ghi đè các tập tin. Nếu bạn đã tự sửa đổi bất kỳ tệp nào, bạn nên cập nhật Shinobi theo cách thủ công. Cấu hình và tệp video của bạn sẽ không được sửa đổi.",
+ "useSubStreamOnlyWhenWatching": "Chỉ khi xem, hãy sử dụng Substream",
+ "vaapi": "Vaapi (VA-API)",
+ "vase": "lọ cắm hoa",
+ "vda": "VDA (gia tốc phần cứng của Apple VDA)",
+ "vdpau": "vdpau",
+ "videoBuildingText1": "Video hiện đang xây dựng. Kiểm tra lại sau.",
+ "videotoolbox": "Videotoolbox",
+ "vp8_cuvid": "VP8 NVENC (NVIDIA HW Accel)",
+ "vp8_qsv": "VP8 (Video đồng bộ nhanh)",
+ "vp9_cuvid": "VP9 NVENC (NVIDIA HW Accel)",
+ "wannaReset": "Bạn có muốn thiết lập lại?",
+ "willTriggerAnEvent": "sẽ kích hoạt một sự kiện",
+ "wine glass": "ly rượu",
+ "years": "năm",
+ "zebra": "ngựa rằn"
+}
\ No newline at end of file
diff --git a/libs/auth.js b/libs/auth.js
index c70355f7..79df5326 100644
--- a/libs/auth.js
+++ b/libs/auth.js
@@ -78,24 +78,21 @@ module.exports = function(s,config,lang){
var isSessionKey = false
if(apiKey){
var sessionKey = params.auth
- createSession(apiKey,{
- auth: sessionKey,
- permissions: s.parseJSON(apiKey.details),
- details: {}
- })
getUserByUid(apiKey,'mail,details',function(err,user){
if(user){
- try{
- editSession({
- auth: sessionKey
- },{
- mail: user.mail,
- details: s.parseJSON(user.details),
- lang: s.getLanguageFile(user.details.lang)
- })
- }catch(er){
- console.log('FAILED TO EDIT',er)
- }
+ createSession(apiKey,{
+ auth: sessionKey,
+ permissions: s.parseJSON(apiKey.details),
+ mail: user.mail,
+ details: s.parseJSON(user.details),
+ lang: s.getLanguageFile(user.details.lang)
+ })
+ }else{
+ createSession(apiKey,{
+ auth: sessionKey,
+ permissions: s.parseJSON(apiKey.details),
+ details: {}
+ })
}
callback(err,s.api[params.auth])
})
@@ -132,9 +129,7 @@ module.exports = function(s,config,lang){
var editSession = function(user,additionalData){
if(user){
if(!additionalData)additionalData = {}
- Object.keys(additionalData).forEach(function(value,key){
- s.api[user.auth][key] = value
- })
+ Object.assign(s.api[user.auth], additionalData)
}
}
var failHttpAuthentication = function(res,req,message){
@@ -190,39 +185,37 @@ module.exports = function(s,config,lang){
activeSession.lang = s.copySystemDefaultLanguage()
}
onSuccessComplete(activeSession)
- }else{
- if(s.api[params.auth] && s.api[params.auth].details){
- var activeSession = s.api[params.auth]
- onSuccess(activeSession)
- if(activeSession.timeout){
- resetActiveSessionTimer(activeSession)
- }
- }else{
- if(params.username && params.username !== '' && params.password && params.password !== ''){
- loginWithUsernameAndPassword(params,'*',function(err,user){
- if(user){
- params.auth = user.auth
- createSession(user)
- resetActiveSessionTimer(s.api[params.auth])
- onSuccess(user)
- }else{
- onFail()
- }
- })
- }else{
- loginWithApiKey(params,function(err,user,isSessionKey){
- if(isSessionKey)resetActiveSessionTimer(s.api[params.auth])
- if(user){
- createSession(user,{
- auth: params.auth
- })
- onSuccess(s.api[params.auth])
- }else{
- onFail()
- }
- })
- }
+ }else if(s.api[params.auth] && s.api[params.auth].details){
+ var activeSession = s.api[params.auth]
+ onSuccess(activeSession)
+ if(activeSession.timeout){
+ resetActiveSessionTimer(activeSession)
}
+ }else if(params.username && params.username !== '' && params.password && params.password !== ''){
+ loginWithUsernameAndPassword(params,'*',function(err,user){
+ if(user){
+ params.auth = user.auth
+ createSession(user)
+ resetActiveSessionTimer(s.api[params.auth])
+ onSuccess(user)
+ }else{
+ onFail()
+ }
+ })
+ }else if(params.auth && params.ke){
+ loginWithApiKey(params,function(err,user,isSessionKey){
+ if(isSessionKey)resetActiveSessionTimer(s.api[params.auth])
+ if(user){
+ createSession(user,{
+ auth: params.auth
+ })
+ onSuccess(s.api[params.auth])
+ }else{
+ onFail()
+ }
+ })
+ } else {
+ onFail()
}
}
//super user authentication handler
diff --git a/libs/basic.js b/libs/basic.js
index bcfe4b15..1c427714 100644
--- a/libs/basic.js
+++ b/libs/basic.js
@@ -57,12 +57,15 @@ module.exports = function(s,config){
splitted[1] = user + ':' + pass + '@' + splitted[1]
return splitted.join('://')
}
- s.checkCorrectPathEnding = function(x){
- var length=x.length
- if(x.charAt(length-1)!=='/'){
- x=x+'/'
+ s.checkCorrectPathEnding = function(x,reverse){
+ var newString = `${x}`
+ var length = x.length
+ if(reverse && x.charAt(length-1) === '/'){
+ newString = x.slice(0, -1)
+ }else if(x.charAt(length-1) !== '/'){
+ newString = x + '/'
}
- return x.replace('__DIR__',s.mainDirectory)
+ return newString.replace('__DIR__',s.mainDirectory)
}
s.mergeDeep = function(...objects) {
const isObject = obj => obj && typeof obj === 'object';
diff --git a/libs/basic/utils.js b/libs/basic/utils.js
index 3a99c0da..73bff949 100644
--- a/libs/basic/utils.js
+++ b/libs/basic/utils.js
@@ -1,7 +1,9 @@
const fs = require('fs');
const path = require('path');
const moment = require('moment');
-const request = require('request');
+const fetch = require('node-fetch');
+const { AbortController } = require('node-abort-controller')
+const DigestFetch = require('digest-fetch')
module.exports = (processCwd,config) => {
const parseJSON = (string) => {
var parsed
@@ -28,6 +30,11 @@ module.exports = (processCwd,config) => {
if(toLowerCase)newString = newString.toLowerCase()
return newString.indexOf(find) > -1
}
+ function getFileDirectory(filePath){
+ const fileParts = filePath.split('/')
+ fileParts.pop();
+ return fileParts.join('/') + '/';
+ }
const checkCorrectPathEnding = (x) => {
var length=x.length
if(x.charAt(length-1)!=='/'){
@@ -79,6 +86,50 @@ module.exports = (processCwd,config) => {
if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};
return moment(e).format(x);
}
+ const fetchTimeout = (url, ms, { signal, ...options } = {}) => {
+ const controller = new AbortController();
+ const promise = fetch(url, { signal: controller.signal, ...options });
+ if (signal) signal.addEventListener("abort", () => controller.abort());
+ const timeout = setTimeout(() => controller.abort(), ms);
+ return promise.finally(() => clearTimeout(timeout));
+ }
+ async function fetchDownloadAndWrite(downloadUrl,outputPath,readFileAfterWrite,options){
+ const writeStream = fs.createWriteStream(outputPath);
+ const downloadBuffer = await fetch(downloadUrl,options).then((res) => res.buffer());
+ writeStream.write(downloadBuffer);
+ writeStream.end();
+ if(readFileAfterWrite === 1){
+ return fs.createReadStream(outputPath)
+ }else if(readFileAfterWrite === 2){
+ return downloadBuffer
+ }
+ return null
+ }
+ function fetchWithAuthentication(requestUrl,options,callback){
+ let hasDigestAuthEnabled = options.digestAuth;
+ let theRequester;
+ const hasUsernameAndPassword = options.username && typeof options.password === 'string'
+ const requestOptions = {
+ method : options.method || 'GET'
+ }
+ if(typeof options.postData === 'object'){
+ const formData = new fetch.FormData()
+ const formKeys = Object.keys(options.postData)
+ formKeys.forEach(function(key){
+ const value = formKeys[key]
+ formData.set(key, value)
+ })
+ requestOptions.body = formData
+ }
+ if(hasUsernameAndPassword && hasDigestAuthEnabled){
+ theRequester = (new DigestFetch(options.username, options.password)).fetch
+ }else if(hasUsernameAndPassword){
+ theRequester = (new DigestFetch(options.username, options.password, { basic: true })).fetch
+ }else{
+ theRequester = fetch
+ }
+ return theRequester(requestUrl,requestOptions)
+ }
const checkSubscription = (subscriptionId,callback) => {
function subscriptionFailed(){
console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
@@ -92,12 +143,12 @@ module.exports = (processCwd,config) => {
if(subscriptionId && subscriptionId !== 'sub_XXXXXXXXXXXX' && !config.disableOnlineSubscriptionCheck){
var url = 'https://licenses.shinobi.video/subscribe/check?subscriptionId=' + subscriptionId
var hasSubcribed = false
- request(url,{
+ fetchTimeout(url,30000,{
method: 'GET',
- timeout: 30000
- }, function(err,resp,body){
+ })
+ .then(response => response.text())
+ .then(function(body){
var json = s.parseJSON(body)
- if(err)console.log(err,json)
hasSubcribed = json && !!json.ok
var i;
for (i = 0; i < s.onSubscriptionCheckExtensions.length; i++) {
@@ -113,8 +164,12 @@ module.exports = (processCwd,config) => {
}else{
subscriptionFailed()
}
+ }).catch((err) => {
+ if(err)console.log(err)
+ subscriptionFailed()
})
}else{
+ var i;
for (i = 0; i < s.onSubscriptionCheckExtensions.length; i++) {
const extender = s.onSubscriptionCheckExtensions[i]
hasSubcribed = extender(false,{},subscriptionId)
@@ -125,10 +180,53 @@ module.exports = (processCwd,config) => {
callback(hasSubcribed)
}
}
+ function isEven(value) {
+ if (value%2 == 0)
+ return true;
+ else
+ return false;
+ }
+ function asyncSetTimeout(timeoutAmount) {
+ return new Promise((resolve,reject) => {
+ setTimeout(function(){
+ resolve()
+ },timeoutAmount)
+ })
+ }
+ function copyFile(inputFilePath,outputFilePath) {
+ const response = {ok: true}
+ return new Promise((resolve,reject) => {
+ function failed(err){
+ response.ok = false
+ response.err = err
+ resolve(response)
+ }
+ const readStream = fs.createReadStream(inputFilePath)
+ const writeStream = fs.createWriteStream(outputFilePath)
+ writeStream.on('finish', () => {
+ resolve(response)
+ })
+ writeStream.on('error', failed)
+ readStream.on('error', failed)
+ readStream.pipe(writeStream)
+ })
+ }
+ function hmsToSeconds(str) {
+ var p = str.split(':'),
+ s = 0, m = 1;
+
+ while (p.length > 0) {
+ s += m * parseFloat(p.pop(), 10);
+ m *= 60;
+ }
+
+ return s;
+ }
return {
parseJSON: parseJSON,
stringJSON: stringJSON,
stringContains: stringContains,
+ getFileDirectory: getFileDirectory,
checkCorrectPathEnding: checkCorrectPathEnding,
nameToTime: nameToTime,
mergeDeep: mergeDeep,
@@ -137,5 +235,12 @@ module.exports = (processCwd,config) => {
localToUtc: localToUtc,
formattedTime: formattedTime,
checkSubscription: checkSubscription,
+ isEven: isEven,
+ fetchTimeout: fetchTimeout,
+ fetchDownloadAndWrite: fetchDownloadAndWrite,
+ fetchWithAuthentication: fetchWithAuthentication,
+ asyncSetTimeout: asyncSetTimeout,
+ copyFile: copyFile,
+ hmsToSeconds,
}
}
diff --git a/libs/basic/websocketTools.js b/libs/basic/websocketTools.js
new file mode 100644
index 00000000..2c8c86dc
--- /dev/null
+++ b/libs/basic/websocketTools.js
@@ -0,0 +1,32 @@
+const WebSocket = require('cws');
+function createWebSocketServer(options){
+ const theWebSocket = new WebSocket.Server(options ? options : {
+ noServer: true
+ });
+ theWebSocket.broadcast = function(data) {
+ theWebSocket.clients.forEach((client) => {
+ try{
+ client.sendData(data)
+ }catch(err){
+ // console.log(err)
+ }
+ })
+ };
+ return theWebSocket
+}
+function createWebSocketClient(connectionHost,options){
+ const clientConnection = new WebSocket(connectionHost, options.engineOptions);
+ if(options.onMessage){
+ const onMessage = options.onMessage;
+ clientConnection.on('message', message => {
+ const data = JSON.parse(message);
+ onMessage(data);
+ });
+ }
+ return clientConnection
+}
+
+module.exports = {
+ createWebSocketServer,
+ createWebSocketClient,
+}
diff --git a/libs/branding.js b/libs/branding.js
index 982153c6..db7da985 100644
--- a/libs/branding.js
+++ b/libs/branding.js
@@ -1,15 +1,46 @@
module.exports = function(s,config,lang,app,io){
if(config.showPoweredByShinobi === undefined){config.showPoweredByShinobi=true}
if(config.poweredByShinobi === undefined){config.poweredByShinobi='Powered by Shinobi.Systems'}
- if(config.poweredByShinobiClass === undefined){config.poweredByShinobiClass='margin:15px 0 0 0;text-align:center;color:#777;font-family: sans-serif;text-transform: uppercase;letter-spacing: 3;font-size: 8pt;'}
- if(config.webPageTitle === undefined){config.webPageTitle='Shinobi'}
if(config.showLoginCardHeader === undefined){config.showLoginCardHeader=true}
- if(config.webFavicon === undefined){config.webFavicon='libs/img/icon/favicon.ico'}
- if(config.logoLocation76x76 === undefined){config.logoLocation76x76='libs/img/icon/apple-touch-icon-76x76.png'}
+ if(config.webFavicon === undefined){config.webFavicon = 'libs/img/icon/favicon.ico'}
+ if(!config.logoLocationAppleTouchIcon)config.logoLocationAppleTouchIcon = 'libs/img/icon/apple-touch-icon.png';
+ if(!config.logoLocation57x57)config.logoLocation57x57 = 'libs/img/icon/apple-touch-icon-57x57.png';
+ if(!config.logoLocation72x72)config.logoLocation72x72 = 'libs/img/icon/apple-touch-icon-72x72.png';
+ if(!config.logoLocation76x76)config.logoLocation76x76 = 'libs/img/icon/apple-touch-icon-76x76.png';
+ if(!config.logoLocation114x114)config.logoLocation114x114 = 'libs/img/icon/apple-touch-icon-114x114.png';
+ if(!config.logoLocation120x120)config.logoLocation120x120 = 'libs/img/icon/apple-touch-icon-120x120.png';
+ if(!config.logoLocation144x144)config.logoLocation144x144 = 'libs/img/icon/apple-touch-icon-144x144.png';
+ if(!config.logoLocation152x152)config.logoLocation152x152 = 'libs/img/icon/apple-touch-icon-152x152.png';
+ if(!config.logoLocation196x196)config.logoLocation196x196 = 'libs/img/icon/favicon-196x196.png';
if(config.logoLocation76x76Link === undefined){config.logoLocation76x76Link='https://shinobi.video'}
if(config.logoLocation76x76Style === undefined){config.logoLocation76x76Style='border-radius:50%'}
+ if(config.loginScreenBackground === undefined){config.loginScreenBackground='assets/img/splash.avif'}
if(config.showLoginSelector === undefined){config.showLoginSelector=true}
-
+ if(config.defaultTheme === undefined)config.defaultTheme = 'Ice-v3';
+ if(config.socialLinks === undefined){
+ config.socialLinks = [
+ {
+ icon: 'home',
+ href: 'https://shinobi.video',
+ title: 'Homepage'
+ },
+ {
+ icon: 'facebook',
+ href: 'https://www.facebook.com/ShinobiCCTV',
+ title: 'Facebook'
+ },
+ {
+ icon: 'twitter',
+ href: 'https://twitter.com/ShinobiCCTV',
+ title: 'Twitter'
+ },
+ {
+ icon: 'youtube',
+ href: 'https://www.youtube.com/channel/UCbgbBLTK-koTyjOmOxA9msQ',
+ title: 'YouTube'
+ }
+ ]
+ }
s.getConfigWithBranding = function(domain){
var configCopy = Object.assign({},config)
if(config.brandingConfig && config.brandingConfig[domain]){
diff --git a/libs/cameraThread/detector.js b/libs/cameraThread/detector.js
index 02f9102d..537044e9 100644
--- a/libs/cameraThread/detector.js
+++ b/libs/cameraThread/detector.js
@@ -1,30 +1,36 @@
module.exports = function(jsonData,pamDiffResponder){
const {
- // see libs/detectorUtils.js for more parameters and functions
- //
- config,
+ completeMonitorConfig,
groupKey,
monitorId,
+ monitorName,
monitorDetails,
- completeMonitorConfig,
+ } = require('./libs/monitorUtils.js')(jsonData)
+ const {
+ convertRegionsToTiles,
+ } = require('./libs/tileCutter.js')
+ const loadDetectorUtils = require('./libs/detectorUtils.js')
+ let detectorUtils
+ let onMotionData = null
+ if(monitorDetails.detector_motion_tile_mode === '1'){
+ const {
+ originalCords,
+ newRegionsBySquares,
+ } = convertRegionsToTiles(monitorDetails)
+ jsonData.rawMonitorConfig.details.cords = newRegionsBySquares;
+ detectorUtils = loadDetectorUtils(jsonData,pamDiffResponder)
+ detectorUtils.originalCords = originalCords;
+ onMotionData = detectorUtils.getTileMotionEvent()
+ }else{
+ detectorUtils = loadDetectorUtils(jsonData,pamDiffResponder)
+ }
+ const {
pamDetectorIsEnabled,
- //
attachPamPipeDrivers,
- //
- getAcceptedTriggers,
- getRegionsWithMinimumChange,
- getRegionsBelowMaximumChange,
- getRegionsWithThresholdMet,
- filterTheNoise,
- filterTheNoiseFromMultipleRegions,
- //
- buildDetectorObject,
- buildTriggerEvent,
- sendDetectedData,
- } = require('./libs/detectorUtils.js')(jsonData,pamDiffResponder)
+ } = detectorUtils;
return function(cameraProcess){
if(pamDetectorIsEnabled){
- attachPamPipeDrivers(cameraProcess)
+ attachPamPipeDrivers(cameraProcess,onMotionData)
}
}
}
diff --git a/libs/cameraThread/libs/dataPortConnection.js b/libs/cameraThread/libs/dataPortConnection.js
new file mode 100644
index 00000000..c5a0a4c4
--- /dev/null
+++ b/libs/cameraThread/libs/dataPortConnection.js
@@ -0,0 +1,10 @@
+module.exports = function(jsonData,onConnected,onError,onClose){
+ const config = jsonData.globalInfo.config;
+ const dataPortToken = jsonData.dataPortToken;
+ const CWS = require('cws');
+ const client = new CWS(`ws://localhost:${config.port}/dataPort`);
+ if(onError)client.on('error',onError);
+ if(onClose)client.on('close',onClose);
+ client.on('open',onConnected);
+ return client;
+}
diff --git a/libs/cameraThread/libs/detectorUtils.js b/libs/cameraThread/libs/detectorUtils.js
index ed7f2764..abaf519c 100644
--- a/libs/cameraThread/libs/detectorUtils.js
+++ b/libs/cameraThread/libs/detectorUtils.js
@@ -1,5 +1,8 @@
const P2P = require('pipe2pam')
let PamDiff = require('pam-diff')
+const {
+ makeBigMatricesFromSmallOnes,
+} = require('./tileCutter.js')
module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
if(alternatePamDiff)PamDiff = alternatePamDiff;
const noiseFilterArray = {};
@@ -7,6 +10,7 @@ module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
const completeMonitorConfig = jsonData.rawMonitorConfig
const groupKey = completeMonitorConfig.ke
const monitorId = completeMonitorConfig.mid
+ const monitorName = completeMonitorConfig.name
const monitorDetails = completeMonitorConfig.details
const triggerTimer = {}
let regionJson
@@ -71,12 +75,12 @@ module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
pamDiffResponder(detectorObject)
}
}else{
- var sendDetectedData = function(detectorObject){
- pamDiffResponder.write(Buffer.from(JSON.stringify(detectorObject)))
- }
+ var sendDetectedData = function(detectorObject){
+ pamDiffResponder.write(Buffer.from(JSON.stringify(detectorObject)))
+ }
}
function logData(...args){
- process.logData(JSON.stringify(args))
+ process.logData(args)
}
function getRegionsWithMinimumChange(data){
try{
@@ -240,6 +244,28 @@ module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
pamDiff.on('diff',pamAnalyzer)
cameraProcess.stdio[3].pipe(p2p).pipe(pamDiff)
}
+ function getTileMotionEvent(){
+ let pamAnalyzer = function(){}
+ if(monitorDetails.detector_noise_filter === '1'){
+ pamAnalyzer = async (data) => {
+ const acceptedTriggers = getAcceptedTriggers(data.trigger)
+ const passedFilter = await filterTheNoiseFromMultipleRegions(acceptedTriggers)
+ if(passedFilter){
+ const mergedTriggers = mergePamTriggers(acceptedTriggers)
+ mergedTriggers.matrices = makeBigMatricesFromSmallOnes(mergedTriggers.matrices)
+ buildTriggerEvent(mergedTriggers)
+ }
+ }
+ }else{
+ pamAnalyzer = (data) => {
+ const acceptedTriggers = getAcceptedTriggers(data.trigger)
+ const mergedTriggers = mergePamTriggers(acceptedTriggers)
+ mergedTriggers.matrices = makeBigMatricesFromSmallOnes(mergedTriggers.matrices)
+ buildTriggerEvent(mergedTriggers)
+ }
+ }
+ return pamAnalyzer
+ }
function createPamDiffRegionArray(regions,globalColorThreshold,globalSensitivity,fullFrame){
var pamDiffCompliantArray = [],
arrayForOtherStuff = [],
@@ -256,6 +282,7 @@ module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
if(!region)return false;
region.polygon = [];
region.points.forEach(function(points){
+ if(!points || isNaN(points[0]) || isNaN(points[1]))return;
var x = parseFloat(points[0]);
var y = parseFloat(points[1]);
if(x < 0)x = 0;
@@ -265,6 +292,7 @@ module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
y: y
})
})
+ if(region.polygon.length < 4)return logData(`Failed to Create Region : ${monitorName} : ${region.name}`,region.points);
if(region.sensitivity===''){
region.sensitivity = globalSensitivity
}else{
@@ -369,6 +397,7 @@ module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
getPropertiesFromBlob,
createMatricesFromBlobs,
logData,
+ getTileMotionEvent,
// parameters
pamDetectorIsEnabled,
noiseFilterArray,
diff --git a/libs/cameraThread/libs/monitorUtils.js b/libs/cameraThread/libs/monitorUtils.js
new file mode 100644
index 00000000..7417b7d2
--- /dev/null
+++ b/libs/cameraThread/libs/monitorUtils.js
@@ -0,0 +1,14 @@
+module.exports = (jsonData) => {
+ const completeMonitorConfig = jsonData.rawMonitorConfig
+ const groupKey = completeMonitorConfig.ke
+ const monitorId = completeMonitorConfig.mid
+ const monitorName = completeMonitorConfig.name
+ const monitorDetails = completeMonitorConfig.details
+ return {
+ completeMonitorConfig,
+ groupKey,
+ monitorId,
+ monitorName,
+ monitorDetails,
+ }
+}
diff --git a/libs/cameraThread/libs/tileCutter.js b/libs/cameraThread/libs/tileCutter.js
new file mode 100644
index 00000000..3c8fe8fa
--- /dev/null
+++ b/libs/cameraThread/libs/tileCutter.js
@@ -0,0 +1,185 @@
+const SAT = require('sat')
+const V = SAT.Vector;
+const P = SAT.Polygon;
+const B = SAT.Box;
+
+function intersectionY(edge, y) {
+ const [[x1, y1], [x2, y2]] = edge;
+ const dir = Math.sign(y2 - y1);
+ if (dir && (y1 - y)*(y2 - y) <= 0) return { x: x1 + (y-y1)/(y2-y1) * (x2-x1), dir };
+}
+
+
+function tilePolygon(points, tileSize){
+ // https://stackoverflow.com/questions/56827208/spilliting-polygon-into-square
+ const minY = Math.min(...points.map(p => p[1]));
+ const maxY = Math.max(...points.map(p => p[1]));
+ const minX = Math.min(...points.map(p => p[0]));
+ const gridPoints = [];
+ for (let y = minY; y <= maxY; y += tileSize) {
+ // Collect x-coordinates where polygon crosses this horizontal line (y)
+ const cuts = [];
+ let prev = null;
+ for (let i = 0; i < points.length; i++) {
+ const cut = intersectionY([points[i], points[(i+1)%points.length]], y);
+ if (!cut) continue;
+ if (!prev || prev.dir !== cut.dir) cuts.push(cut);
+ prev = cut;
+ }
+ if (prev && prev.dir === cuts[0].dir) cuts.pop();
+ // Now go through those cuts from left to right toggling whether we are in/out the polygon
+ let dirSum = 0;
+ let startX = null;
+ for (let cut of cuts.sort((a, b) => a.x - b.x)) {
+ dirSum += cut.dir;
+ if (dirSum % 2) { // Entering polygon
+ if (startX === null) startX = cut.x;
+ } else if (startX !== null) { // Exiting polygon
+ // Genereate grid points on this horizontal line segement
+ for (let x = minX + Math.ceil((startX - minX) / tileSize)*tileSize; x <= cut.x; x += tileSize) {
+ gridPoints.push([x, y]);
+ }
+ startX = null;
+ }
+ }
+ }
+ return gridPoints;
+}
+
+function convertStringPoints(oldPoints){
+ // [["0","0"],["0","150"],["300","150"],["300","0"]]
+ var newPoints = []
+ oldPoints.forEach((point) => {
+ newPoints.push([parseInt(point[0]),parseInt(point[1])])
+ })
+ return newPoints
+}
+
+function createSquares(gridPoints,imgWidth,imgHeight){
+ var rows = [];
+ var n = 0;
+ var curentLine = gridPoints[0][1]
+ gridPoints.forEach((point) => {
+ if(!rows[n])rows[n] = []
+ rows[n].push(point)
+ if(curentLine !== point[1]){
+ curentLine = point[1];
+ ++n;
+ }
+ });
+ var squares = [];
+ rows.forEach((row,n) => {
+ for (let i = 0; i < row.length; i += 2) {
+ if(!rows[n + 1] || !row[i + 1])return;
+ var square = [row[i],row[i + 1],rows[n + 1][i],rows[n + 1][i + 1]]
+ squares.push(square)
+ }
+ })
+ return squares
+}
+const getAllSquaresTouchingRegion = function(region,squares){
+ var matrixPoints = []
+ var collisions = []
+ var polyPoints = []
+ region.points.forEach(function(point){
+ polyPoints.push(new V(parseInt(point[0]),parseInt(point[1])))
+ })
+ var regionPoly = new P(new V(0,0), polyPoints)
+ squares.forEach(function(squarePoints){
+ var firstPoint = squarePoints[0]
+ var thirdPoint = squarePoints[2]
+ var squareX = firstPoint[0]
+ var squareY = firstPoint[1]
+ var squareWidth = thirdPoint[0] - firstPoint[0]
+ var squareHeight = thirdPoint[1] - firstPoint[1]
+ var squarePoly = new B(new V(squareX, squareY), squareWidth, squareHeight).toPolygon()
+ var response = new SAT.Response()
+ var collided = SAT.testPolygonPolygon(squarePoly, regionPoly, response)
+ if(collided === true){
+ collisions.push(squarePoints)
+ }
+ })
+ return collisions
+}
+function makeBigMatricesFromSmallOnes(matrices){
+ var bigMatrices = {}
+ matrices.forEach(function(matrix,n){
+ const regionName = matrix.tag
+ if(!bigMatrices[regionName]){
+ bigMatrices[regionName] = {
+ tag: regionName,
+ x: 9999999999,
+ y: 9999999999,
+ width: matrices.length > 1 ? matrices[0].width : 0,
+ height: matrices.length > 1 ? matrices[0].height : 0,
+ tilesCounted: 0,
+ confidence: 0,
+ }
+ }
+ var bigMatrix = bigMatrices[regionName];
+ bigMatrix.x = bigMatrix.x > matrix.x ? matrix.x : bigMatrix.x;
+ bigMatrix.y = bigMatrix.y > matrix.y ? matrix.y : bigMatrix.y;
+ const newWidth = matrix.x - bigMatrix.x
+ const newHeight = matrix.y - bigMatrix.y
+ bigMatrix.width = bigMatrix.width < matrix.x ? newWidth === 0 ? matrix.width : newWidth : bigMatrix.width;
+ bigMatrix.height = bigMatrix.height < matrix.y ? newHeight === 0 ? matrix.height : newHeight : bigMatrix.height;
+ // bigMatrix.tag = matrix.tag;
+ bigMatrix.confidence += matrix.confidence;
+ bigMatrix.tilesCounted += 1;
+ })
+ let allBigMatrices = Object.values(bigMatrices)
+ allBigMatrices.forEach(function(matrix,n){
+ let bigMatrix = allBigMatrices[n]
+ bigMatrix.averageConfidence = bigMatrix.confidence / bigMatrix.tilesCounted;
+ })
+ return allBigMatrices
+}
+function convertRegionsToTiles(monitorDetails){
+ let originalCords;
+ //force full frame detection to be use for tracking blobs
+ monitorDetails.detector_frame = '1'
+ monitorDetails.detector_sensitivity = '1'
+ monitorDetails.detector_color_threshold = monitorDetails.detector_color_threshold || '7'
+ try{
+ monitorDetails.cords = JSON.parse(monitorDetails.cords)
+ }catch(err){
+
+ }
+ originalCords = Object.values(monitorDetails.cords)
+ const regionKeys = Object.keys(monitorDetails.cords);
+ const newRegionsBySquares = {}
+ try{
+ regionKeys.forEach(function(regionKey){
+ const region = monitorDetails.cords[regionKey]
+ const tileSize = parseInt(region.detector_tile_size) || 20;
+ const gridPoints = tilePolygon([
+ [0,0],
+ [0,height],
+ [width,height],
+ [width,0]
+ ],tileSize)
+ const squares = createSquares(gridPoints,width,height)
+ const squaresInRegion = getAllSquaresTouchingRegion(region,squares)
+ squaresInRegion.forEach((square,n) => {
+ newRegionsBySquares[`${regionKey}_${n}`] = Object.assign({},region,{
+ "points": square
+ })
+ })
+ })
+ // jsonData.rawMonitorConfig.details.cords = newRegionsBySquares;
+ }catch(err){
+ process.logData(err)
+ }
+ // detectorUtils.originalCords = originalCords;
+ return {
+ originalCords,
+ newRegionsBySquares,
+ }
+}
+module.exports = {
+ tilePolygon,
+ createSquares,
+ convertRegionsToTiles,
+ getAllSquaresTouchingRegion,
+ makeBigMatricesFromSmallOnes,
+}
diff --git a/libs/cameraThread/singleCamera.js b/libs/cameraThread/singleCamera.js
index 1976942d..28217840 100644
--- a/libs/cameraThread/singleCamera.js
+++ b/libs/cameraThread/singleCamera.js
@@ -1,5 +1,4 @@
const fs = require('fs')
-const request = require('request')
const exec = require('child_process').exec
const spawn = require('child_process').spawn
const isWindows = (process.platform === 'win32' || process.platform === 'win64')
@@ -14,9 +13,34 @@ const stdioPipes = jsonData.pipes || []
var newPipes = []
var stdioWriters = [];
-var writeToStderr = function(text){
+const { fetchTimeout } = require('../basic/utils.js')(process.cwd(),config)
+const dataPort = require('./libs/dataPortConnection.js')(jsonData,
+// onConnected
+() => {
+ dataPort.send(jsonData.dataPortToken)
+},
+// onError
+(err) => {
+ writeToStderr([
+ 'dataPort:Connection:Error',
+ err
+ ])
+},
+// onClose
+(e) => {
+ writeToStderr([
+ 'dataPort:Connection:Closed',
+ e
+ ])
+})
+
+var writeToStderr = function(argsAsArray){
try{
- process.stderr.write(Buffer.from(`${text}`, 'utf8' ))
+ process.stderr.write(Buffer.from(`${JSON.stringify(argsAsArray)}`, 'utf8' ))
+ // dataPort.send({
+ // f: 'debugLog',
+ // data: argsAsArray,
+ // })
// stdioWriters[2].write(Buffer.from(`${new Error('writeToStderr').stack}`, 'utf8' ))
}catch(err){
}
@@ -111,7 +135,9 @@ writeToStderr('Thread Opening')
if(rawMonitorConfig.details.detector === '1' && rawMonitorConfig.details.detector_pam === '1'){
try{
- const attachPamDetector = require(config.monitorDetectorDaemonPath ? config.monitorDetectorDaemonPath : __dirname + '/detector.js')(jsonData,stdioWriters[3])
+ const attachPamDetector = require(config.monitorDetectorDaemonPath ? config.monitorDetectorDaemonPath : __dirname + '/detector.js')(jsonData,(detectorObject) => {
+ dataPort.send(JSON.stringify(detectorObject))
+ })
attachPamDetector(cameraProcess)
}catch(err){
writeToStderr(err.stack)
@@ -119,7 +145,6 @@ if(rawMonitorConfig.details.detector === '1' && rawMonitorConfig.details.detecto
}
if(rawMonitorConfig.type === 'jpeg'){
- var recordingSnapRequest
var recordingSnapper
var errorTimeout
var errorCount = 0
@@ -137,16 +162,11 @@ if(rawMonitorConfig.type === 'jpeg'){
setTimeout(() => {
if(!cameraProcess.stdio[0])return writeToStderr('No Camera Process Found for Snapper');
const captureOne = function(f){
- recordingSnapRequest = request({
- url: buildMonitorUrl(rawMonitorConfig),
+ fetchTimeout(buildMonitorUrl(rawMonitorConfig),15000,{
method: 'GET',
- encoding: null,
- timeout: 15000
- },function(err,data){
- if(err){
- writeToStderr(JSON.stringify(err))
- return;
- }
+ })
+ .then(response => response.text())
+ .then(function(body){
// writeToStderr(data.body.length)
cameraProcess.stdio[0].write(data.body)
recordingSnapper = setTimeout(function(){
@@ -159,7 +179,7 @@ if(rawMonitorConfig.type === 'jpeg'){
delete(errorTimeout)
},3000)
}
- }).on('error', function(err){
+ }).catch(function(err){
++errorCount
clearTimeout(errorTimeout)
errorTimeout = null
diff --git a/libs/childNode.js b/libs/childNode.js
index eaf3ba4a..6d1d9879 100644
--- a/libs/childNode.js
+++ b/libs/childNode.js
@@ -1,215 +1,147 @@
-var fs = require('fs');
-var http = require('http');
-var https = require('https');
-var express = require('express');
+const fs = require('fs');
+const url = require('url');
+const http = require('http');
+const https = require('https');
+const express = require('express');
+const { createWebSocketServer, createWebSocketClient } = require('./basic/websocketTools.js')
module.exports = function(s,config,lang,app,io){
- const { cameraDestroy } = require('./monitor/utils.js')(s,config,lang)
//setup Master for childNodes
- if(config.childNodes.enabled === true && config.childNodes.mode === 'master'){
+ if(
+ config.childNodes.enabled === true &&
+ config.childNodes.mode === 'master'
+ ){
+ const {
+ getIpAddress,
+ initiateDataConnection,
+ initiateVideoTransferConnection,
+ onWebSocketDataFromChildNode,
+ onDataConnectionDisconnect,
+ initiateVideoWriteFromChildNode,
+ initiateTimelapseFrameWriteFromChildNode,
+ } = require('./childNode/utils.js')(s,config,lang,app,io)
s.childNodes = {};
- var childNodeHTTP = express();
- var childNodeServer = http.createServer(app);
- var childNodeWebsocket = new (require('socket.io'))()
- childNodeServer.listen(config.childNodes.port,config.bindip,function(){
- console.log(lang.Shinobi+' - CHILD NODE PORT : '+config.childNodes.port);
- });
- s.debugLog('childNodeWebsocket.attach(childNodeServer)')
- childNodeWebsocket.attach(childNodeServer,{
- path:'/socket.io',
- transports: ['websocket']
- });
- //send data to child node function (experimental)
- s.cx = function(z,y,x){
- if(!z.mid && !z.d){
- console.error('Missing ID')
- }else if(x){
- x.broadcast.to(y).emit('c',z)
- }else{
- childNodeWebsocket.to(y).emit('c',z)
+ const childNodesConnectionIndex = {};
+ const childNodeHTTP = express();
+ const childNodeServer = http.createServer(app);
+ const childNodeWebsocket = createWebSocketServer();
+ const childNodeFileRelay = createWebSocketServer();
+ childNodeServer.on('upgrade', function upgrade(request, socket, head) {
+ const pathname = url.parse(request.url).pathname;
+ if (pathname === '/childNode') {
+ childNodeWebsocket.handleUpgrade(request, socket, head, function done(ws) {
+ childNodeWebsocket.emit('connection', ws, request)
+ })
+ } else if (pathname === '/childNodeFileRelay') {
+ childNodeFileRelay.handleUpgrade(request, socket, head, function done(ws) {
+ childNodeFileRelay.emit('connection', ws, request)
+ })
+ } else {
+ socket.destroy();
}
+ });
+ const childNodeBindIP = config.childNodes.ip || config.bindip;
+ childNodeServer.listen(config.childNodes.port,childNodeBindIP,function(){
+ console.log(lang.Shinobi+' - CHILD NODE SERVER : ' + config.childNodes.port);
+ });
+ //send data to child node function
+ s.cx = function(data,connectionId){
+ childNodesConnectionIndex[connectionId].sendJson(data)
}
//child Node Websocket
- childNodeWebsocket.on('connection', function (cn) {
+ childNodeWebsocket.on('connection', function (client, req) {
//functions for dispersing work to child servers;
- var ipAddress
- cn.on('c',function(d){
- if(config.childNodes.key.indexOf(d.socketKey) > -1){
- if(!cn.shinobi_child&&d.f=='init'){
- ipAddress = cn.request.connection.remoteAddress.replace('::ffff:','')+':'+d.port
- cn.ip = ipAddress
- cn.shinobi_child = 1
- cn.tx = function(z){
- cn.emit('c',z)
- }
- if(!s.childNodes[cn.ip]){
- s.childNodes[cn.ip] = {}
- };
- s.childNodes[cn.ip].dead = false
- s.childNodes[cn.ip].cnid = cn.id
- s.childNodes[cn.ip].cpu = 0
- s.childNodes[cn.ip].ip = ipAddress
- s.childNodes[cn.ip].activeCameras = {}
- d.availableHWAccels.forEach(function(accel){
- if(config.availableHWAccels.indexOf(accel) === -1)config.availableHWAccels.push(accel)
- })
- cn.tx({
- f : 'init_success',
- childNodes : s.childNodes
- })
- s.childNodes[cn.ip].coreCount = d.coreCount
- }else{
- switch(d.f){
- case'cpu':
- s.childNodes[ipAddress].cpu = d.cpu;
- break;
- case'sql':
- s.sqlQuery(d.query,d.values,function(err,rows){
- cn.emit('c',{f:'sqlCallback',rows:rows,err:err,callbackId:d.callbackId});
- });
- break;
- case'knex':
- s.knexQuery(d.options,function(err,rows){
- cn.emit('c',{f:'sqlCallback',rows:rows,err:err,callbackId:d.callbackId});
- });
- break;
- case'clearCameraFromActiveList':
- if(s.childNodes[ipAddress])delete(s.childNodes[ipAddress].activeCameras[d.ke + d.id])
- break;
- case'camera':
- s.camera(d.mode,d.data)
- break;
- case's.tx':
- s.tx(d.data,d.to)
- break;
- case's.userLog':
- if(!d.mon || !d.data)return console.log('LOG DROPPED',d.mon,d.data);
- s.userLog(d.mon,d.data)
- break;
- case'open_timelapse_file_transfer':
- var location = s.getTimelapseFrameDirectory(d.d) + `${d.currentDate}/`
- if(!fs.existsSync(location)){
- fs.mkdirSync(location)
- }
- break;
- case'created_timelapse_file_chunk':
- if(!s.group[d.ke].activeMonitors[d.mid].childNodeStreamWriters[d.filename]){
- var dir = s.getTimelapseFrameDirectory(d.d) + `${d.currentDate}/`
- s.group[d.ke].activeMonitors[d.mid].childNodeStreamWriters[d.filename] = fs.createWriteStream(dir+d.filename)
- }
- s.group[d.ke].activeMonitors[d.mid].childNodeStreamWriters[d.filename].write(d.chunk)
- break;
- case'created_timelapse_file':
- if(!s.group[d.ke].activeMonitors[d.mid].childNodeStreamWriters[d.filename]){
- return console.log('FILE NOT EXIST')
- }
- s.group[d.ke].activeMonitors[d.mid].childNodeStreamWriters[d.filename].end()
- cn.tx({
- f: 'deleteTimelapseFrame',
- file: d.filename,
- currentDate: d.currentDate,
- d: d.d, //monitor config
- ke: d.ke,
- mid: d.mid
- })
- s.insertTimelapseFrameDatabaseRow({
- ke: d.ke
- },d.queryInfo)
- break;
- case'created_file_chunk':
- if(!s.group[d.ke].activeMonitors[d.mid].childNodeStreamWriters[d.filename]){
- d.dir = s.getVideoDirectory(s.group[d.ke].rawMonitorConfigurations[d.mid])
- if (!fs.existsSync(d.dir)) {
- fs.mkdirSync(d.dir, {recursive: true}, (err) => {s.debugLog(err)})
- }
- s.group[d.ke].activeMonitors[d.mid].childNodeStreamWriters[d.filename] = fs.createWriteStream(d.dir+d.filename)
- }
- s.group[d.ke].activeMonitors[d.mid].childNodeStreamWriters[d.filename].write(d.chunk)
- break;
- case'created_file':
- if(!s.group[d.ke].activeMonitors[d.mid].childNodeStreamWriters[d.filename]){
- return console.log('FILE NOT EXIST')
- }
- s.group[d.ke].activeMonitors[d.mid].childNodeStreamWriters[d.filename].end();
- cn.tx({
- f:'delete',
- file:d.filename,
- ke:d.ke,
- mid:d.mid
- })
- s.txWithSubPermissions({
- f:'video_build_success',
- hrefNoAuth:'/videos/'+d.ke+'/'+d.mid+'/'+d.filename,
- filename:d.filename,
- mid:d.mid,
- ke:d.ke,
- time:d.time,
- size:d.filesize,
- end:d.end
- },'GRP_'+d.ke,'video_view')
- //save database row
- var insert = {
- startTime : d.time,
- filesize : d.filesize,
- endTime : d.end,
- dir : s.getVideoDirectory(d.d),
- file : d.filename,
- filename : d.filename,
- filesizeMB : parseFloat((d.filesize/1048576).toFixed(2))
- }
- s.insertDatabaseRow(d.d,insert)
- s.insertCompletedVideoExtensions.forEach(function(extender){
- extender(d.d,insert)
- })
- //purge over max
- s.purgeDiskForGroup(d.ke)
- //send new diskUsage values
- s.setDiskUsedForGroup(d.ke,insert.filesizeMB)
- clearTimeout(s.group[d.ke].activeMonitors[d.mid].recordingChecker)
- clearTimeout(s.group[d.ke].activeMonitors[d.mid].streamChecker)
- break;
- }
- }
+ const ipAddress = getIpAddress(req)
+ const connectionId = s.gid(10);
+ s.debugLog('Child Node Connection!',new Date(),ipAddress)
+ client.id = connectionId;
+ function onAuthenticate(d){
+ const data = JSON.parse(d);
+ const childNodeKeyAccepted = config.childNodes.key.indexOf(data.socketKey) > -1;
+ if(!client.shinobiChildAlreadyRegistered && data.f === 'init' && childNodeKeyAccepted){
+ initiateDataConnection(client,req,data,connectionId);
+ childNodesConnectionIndex[connectionId] = client;
+ client.removeListener('message',onAuthenticate)
+ client.on('message',(d) => {
+ const data = JSON.parse(d);
+ onWebSocketDataFromChildNode(client,data)
+ })
+ }else{
+ s.debugLog('Child Node Force Disconnected!',new Date(),ipAddress)
+ client.destroy()
}
+ }
+ client.on('message',onAuthenticate)
+ client.on('close',() => {
+ onDataConnectionDisconnect(client, req)
})
- cn.on('disconnect',function(){
- console.log('childNodeWebsocket.disconnect',ipAddress)
- if(s.childNodes[ipAddress]){
- var monitors = Object.values(s.childNodes[ipAddress].activeCameras)
- if(monitors && monitors[0]){
- var loadCompleted = 0
- var loadMonitor = function(monitor){
- setTimeout(function(){
- var mode = monitor.mode + ''
- var cleanMonitor = s.cleanMonitorObject(monitor)
- s.camera('stop',Object.assign(cleanMonitor,{}))
- delete(s.group[monitor.ke].activeMonitors[monitor.mid].childNode)
- delete(s.group[monitor.ke].activeMonitors[monitor.mid].childNodeId)
- setTimeout(function(){
- s.camera(mode,cleanMonitor)
- ++loadCompleted
- if(monitors[loadCompleted]){
- loadMonitor(monitors[loadCompleted])
- }
- },1000)
- },2000)
- }
- loadMonitor(monitors[loadCompleted])
+ })
+ childNodeFileRelay.on('connection', function (client, req) {
+ function onAuthenticate(d){
+ const data = JSON.parse(d);
+ const childNodeKeyAccepted = config.childNodes.key.indexOf(data.socketKey) > -1;
+ if(!client.alreadyInitiated && data.fileType && childNodeKeyAccepted){
+ client.alreadyInitiated = true;
+ client.removeListener('message',onAuthenticate)
+ switch(data.fileType){
+ case'video':
+ initiateVideoWriteFromChildNode(client,data.options,data.connectionId)
+ break;
+ case'timelapseFrame':
+ initiateTimelapseFrameWriteFromChildNode(client,data.options,data.connectionId)
+ break;
}
- s.childNodes[ipAddress].activeCameras = {}
- s.childNodes[ipAddress].dead = true
+ }else{
+ client.destroy()
}
- })
+ }
+ client.on('message',onAuthenticate)
})
}else
//setup Child for childNodes
- if(config.childNodes.enabled === true && config.childNodes.mode === 'child' && config.childNodes.host){
- s.connected = false;
- childIO = require('socket.io-client')('ws://'+config.childNodes.host,{
- transports: ['websocket']
- });
- s.cx = function(x){x.socketKey = config.childNodes.key;childIO.emit('c',x)}
- s.tx = function(x,y){s.cx({f:'s.tx',data:x,to:y})}
- s.userLog = function(x,y){s.cx({f:'s.userLog',mon:x,data:y})}
+ if(
+ config.childNodes.enabled === true &&
+ config.childNodes.mode === 'child' &&
+ config.childNodes.host
+ ){
+ const {
+ initiateConnectionToMasterNode,
+ onDisconnectFromMasterNode,
+ onDataFromMasterNode,
+ } = require('./childNode/childUtils.js')(s,config,lang,app,io)
+ s.connectedToMasterNode = false;
+ let childIO;
+ function createChildNodeConnection(){
+ childIO = createWebSocketClient('ws://'+config.childNodes.host + '/childNode',{
+ onMessage: onDataFromMasterNode
+ })
+ childIO.on('open', function(){
+ console.error(new Date(),'Child Nodes : Connected to Master Node! Authenticating...');
+ initiateConnectionToMasterNode()
+ })
+ childIO.on('close',function(){
+ onDisconnectFromMasterNode()
+ setTimeout(() => {
+ console.error(new Date(),'Child Nodes : Connection to Master Node Closed. Attempting Reconnect...');
+ createChildNodeConnection()
+ },3000)
+ })
+ childIO.on('error',function(err){
+ console.error(new Date(),'Child Nodes ERROR : ', err.message);
+ childIO.close()
+ })
+ }
+ createChildNodeConnection()
+ function sendDataToMasterNode(data){
+ childIO.send(JSON.stringify(data))
+ }
+ s.cx = sendDataToMasterNode;
+ // replace internal functions with bridges to master node
+ s.tx = function(x,y){
+ sendDataToMasterNode({f:'s.tx',data:x,to:y})
+ }
+ s.userLog = function(x,y){
+ sendDataToMasterNode({f:'s.userLog',mon:x,data:y})
+ }
s.queuedSqlCallbacks = {}
s.sqlQuery = function(query,values,onMoveOn){
var callbackId = s.gid()
@@ -218,84 +150,13 @@ module.exports = function(s,config,lang,app,io){
var onMoveOn = values;
var values = [];
}
- if(typeof onMoveOn !== 'function'){onMoveOn=function(){}}
- s.queuedSqlCallbacks[callbackId] = onMoveOn
- s.cx({f:'sql',query:query,values:values,callbackId:callbackId});
+ if(typeof onMoveOn === 'function')s.queuedSqlCallbacks[callbackId] = onMoveOn;
+ sendDataToMasterNode({f:'sql',query:query,values:values,callbackId:callbackId});
}
s.knexQuery = function(options,onMoveOn){
var callbackId = s.gid()
- if(typeof onMoveOn !== 'function'){onMoveOn=function(){}}
- s.queuedSqlCallbacks[callbackId] = onMoveOn
- s.cx({f:'knex',options:options,callbackId:callbackId});
+ if(typeof onMoveOn === 'function')s.queuedSqlCallbacks[callbackId] = onMoveOn;
+ sendDataToMasterNode({f:'knex',options:options,callbackId:callbackId});
}
- setInterval(async () => {
- const cpu = await s.cpuUsage()
- s.cx({
- f: 'cpu',
- cpu: parseFloat(cpu)
- })
- },5000)
- childIO.on('connect', function(d){
- console.log('CHILD CONNECTION SUCCESS')
- s.cx({
- f : 'init',
- port : config.port,
- coreCount : s.coreCount,
- availableHWAccels : config.availableHWAccels
- })
- })
- childIO.on('c', function (d) {
- switch(d.f){
- case'sqlCallback':
- if(s.queuedSqlCallbacks[d.callbackId]){
- s.queuedSqlCallbacks[d.callbackId](d.err,d.rows)
- delete(s.queuedSqlCallbacks[d.callbackId])
- }
- break;
- case'init_success':
- s.connected=true;
- s.other_helpers=d.child_helpers;
- break;
- case'kill':
- s.initiateMonitorObject(d.d);
- cameraDestroy(d.d)
- var childNodeIp = s.group[d.d.ke].activeMonitors[d.d.id]
- break;
- case'sync':
- s.initiateMonitorObject(d.sync);
- Object.keys(d.sync).forEach(function(v){
- s.group[d.sync.ke].activeMonitors[d.sync.mid][v]=d.sync[v];
- });
- break;
- case'delete'://delete video
- s.file('delete',s.dir.videos+d.ke+'/'+d.mid+'/'+d.file)
- break;
- case'deleteTimelapseFrame'://delete video
- var filePath = s.getTimelapseFrameDirectory(d.d) + `${d.currentDate}/` + d.file
- s.file('delete',filePath)
- break;
- case'insertCompleted'://close video
- s.insertCompletedVideo(d.d,d.k)
- break;
- case'cameraStop'://start camera
- s.camera('stop',d.d)
- break;
- case'cameraStart'://start or record camera
- s.camera(d.mode,d.d)
- break;
- }
- })
- childIO.on('disconnect',function(d){
- s.connected = false;
- var groupKeys = Object.keys(s.group)
- groupKeys.forEach(function(groupKey){
- var activeMonitorKeys = Object.keys(s.group[groupKey].activeMonitors)
- activeMonitorKeys.forEach(function(monitorKey){
- var activeMonitor = s.group[groupKey].activeMonitors[monitorKey]
- if(activeMonitor && activeMonitor.spawn && activeMonitor.spawn.close)activeMonitor.spawn.close()
- if(activeMonitor && activeMonitor.spawn && activeMonitor.spawn.kill)activeMonitor.spawn.kill()
- })
- })
- })
}
}
diff --git a/libs/childNode/childUtils.js b/libs/childNode/childUtils.js
new file mode 100644
index 00000000..a2e70178
--- /dev/null
+++ b/libs/childNode/childUtils.js
@@ -0,0 +1,149 @@
+const fs = require('fs');
+const { createWebSocketClient } = require('../basic/websocketTools.js')
+module.exports = function(s,config,lang,app,io){
+ const { cameraDestroy } = require('../monitor/utils.js')(s,config,lang)
+ var checkHwInterval = null;
+ function onDataFromMasterNode(d) {
+ switch(d.f){
+ case'sqlCallback':
+ const callbackId = d.callbackId;
+ if(s.queuedSqlCallbacks[callbackId]){
+ s.queuedSqlCallbacks[callbackId](d.err,d.rows)
+ delete(s.queuedSqlCallbacks[callbackId])
+ }
+ break;
+ case'init_success':
+ console.error(new Date(),'Child Nodes : Authenticated with Master Node!');
+ s.connectedToMasterNode = true;
+ s.other_helpers = d.child_helpers;
+ s.childNodeIdOnMasterNode = d.connectionId
+ break;
+ case'kill':
+ s.initiateMonitorObject(d.d);
+ cameraDestroy(d.d)
+ break;
+ case'sync':
+ s.initiateMonitorObject(d.sync);
+ Object.keys(d.sync).forEach(function(v){
+ s.group[d.sync.ke].activeMonitors[d.sync.mid][v]=d.sync[v];
+ });
+ break;
+ case'delete'://delete video
+ s.file('delete',s.dir.videos+d.ke+'/'+d.mid+'/'+d.file)
+ break;
+ case'deleteTimelapseFrame'://delete timelapse frame
+ var filePath = s.getTimelapseFrameDirectory(d) + `${d.currentDate}/` + d.file
+ s.file('delete',filePath)
+ break;
+ case'cameraStop'://stop camera
+ // s.group[d.d.ke].activeMonitors[d.d.mid].masterSaysToStop = true
+ s.camera('stop',d.d)
+ break;
+ case'cameraStart'://start or record camera
+ s.camera(d.mode,d.d)
+ let activeMonitor = s.group[d.d.ke].activeMonitors[d.d.mid]
+ // activeMonitor.masterSaysToStop = false
+ clearTimeout(activeMonitor.recordingChecker);
+ clearTimeout(activeMonitor.streamChecker);
+ break;
+ }
+ }
+ function initiateConnectionToMasterNode(){
+ s.cx({
+ f: 'init',
+ port: config.port,
+ platform: s.platform,
+ coreCount: s.coreCount,
+ totalmem: s.totalmem / 1048576,
+ availableHWAccels: config.availableHWAccels,
+ socketKey: config.childNodes.key
+ })
+ clearInterval(checkHwInterval)
+ checkHwInterval = setInterval(() => {
+ sendCurrentCpuUsage()
+ sendCurrentRamUsage()
+ },5000)
+ }
+ function onDisconnectFromMasterNode(){
+ s.connectedToMasterNode = false;
+ destroyAllMonitorProcesses()
+ clearInterval(checkHwInterval)
+ }
+ function destroyAllMonitorProcesses(){
+ var groupKeys = Object.keys(s.group)
+ groupKeys.forEach(function(groupKey){
+ var activeMonitorKeys = Object.keys(s.group[groupKey].activeMonitors)
+ activeMonitorKeys.forEach(function(monitorKey){
+ var activeMonitor = s.group[groupKey].activeMonitors[monitorKey]
+ if(activeMonitor && activeMonitor.spawn && activeMonitor.spawn.close)activeMonitor.spawn.close()
+ if(activeMonitor && activeMonitor.spawn && activeMonitor.spawn.kill)activeMonitor.spawn.kill()
+ })
+ })
+ }
+ async function sendCurrentCpuUsage(){
+ const percent = await s.cpuUsage();
+ const use = s.coreCount * (percent / 100)
+ s.cx({
+ f: 'cpu',
+ used: use,
+ percent: percent
+ })
+ }
+ async function sendCurrentRamUsage(){
+ const ram = await s.ramUsage()
+ s.cx({
+ f: 'ram',
+ used: ram.used,
+ percent: ram.percent,
+ })
+ }
+ function createFileTransferToMasterNode(filePath,transferInfo,fileType){
+ const response = {ok: true}
+ return new Promise((resolve,reject) => {
+ const fileTransferConnection = createWebSocketClient('ws://'+config.childNodes.host + '/childNodeFileRelay',{
+ onMessage: () => {}
+ })
+ fileTransferConnection.on('open', function(){
+ fileTransferConnection.send(JSON.stringify({
+ fileType: fileType || 'video',
+ options: transferInfo,
+ socketKey: config.childNodes.key,
+ connectionId: s.childNodeIdOnMasterNode,
+ }))
+ setTimeout(() => {
+ fs.createReadStream(filePath)
+ .on('data',function(data){
+ fileTransferConnection.send(data)
+ })
+ .on('close',function(){
+ fileTransferConnection.close()
+ resolve(response)
+ })
+ },2000)
+ })
+ })
+ }
+ async function sendVideoToMasterNode(filePath,options){
+ const groupKey = options.ke
+ const monitorId = options.mid
+ const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
+ const response = await createFileTransferToMasterNode(filePath,options,'video');
+ clearTimeout(activeMonitor.recordingChecker);
+ clearTimeout(activeMonitor.streamChecker);
+ return response;
+ }
+ async function sendTimelapseFrameToMasterNode(filePath,options){
+ const response = await createFileTransferToMasterNode(filePath,options,'timelapseFrame');
+ return response;
+ }
+ return {
+ onDataFromMasterNode,
+ initiateConnectionToMasterNode,
+ onDisconnectFromMasterNode,
+ destroyAllMonitorProcesses,
+ sendCurrentCpuUsage,
+ sendCurrentRamUsage,
+ sendVideoToMasterNode,
+ sendTimelapseFrameToMasterNode,
+ }
+}
diff --git a/libs/childNode/utils.js b/libs/childNode/utils.js
new file mode 100644
index 00000000..cc51aef6
--- /dev/null
+++ b/libs/childNode/utils.js
@@ -0,0 +1,358 @@
+const fs = require('fs');
+module.exports = function(s,config,lang,app,io){
+ const masterDoWorkToo = config.childNodes.masterDoWorkToo;
+ const maxCpuPercent = config.childNodes.maxCpuPercent || 75;
+ const maxRamPercent = config.childNodes.maxRamPercent || 75;
+ function getIpAddress(req){
+ return (req.headers['cf-connecting-ip'] ||
+ req.headers["CF-Connecting-IP"] ||
+ req.headers["'x-forwarded-for"] ||
+ req.connection.remoteAddress).replace('::ffff:','');
+ }
+ function initiateDataConnection(client,req,options,connectionId){
+ const ipAddress = getIpAddress(req)
+ const webAddress = ipAddress + ':' + options.port
+ client.ip = webAddress;
+ client.shinobiChildAlreadyRegistered = true;
+ client.sendJson = (data) => {
+ client.send(JSON.stringify(data))
+ }
+ if(!s.childNodes[webAddress]){
+ s.childNodes[webAddress] = {}
+ };
+ const activeNode = s.childNodes[webAddress];
+ activeNode.dead = false
+ activeNode.cnid = client.id
+ activeNode.cpu = 0
+ activeNode.ip = webAddress
+ activeNode.activeCameras = {}
+ activeNode.platform = options.platform
+ activeNode.coreCount = options.coreCount
+ activeNode.totalmem = options.totalmem
+ options.availableHWAccels.forEach(function(accel){
+ if(config.availableHWAccels.indexOf(accel) === -1)config.availableHWAccels.push(accel)
+ })
+ client.sendJson({
+ f : 'init_success',
+ childNodes : s.childNodes,
+ connectionId: connectionId,
+ })
+ s.debugLog('Authenticated Child Node!',new Date(),webAddress)
+ return webAddress
+ }
+ function onWebSocketDataFromChildNode(client,data){
+ const activeMonitor = data.ke && data.mid && s.group[data.ke] ? s.group[data.ke].activeMonitors[data.mid] : null;
+ const webAddress = client.ip;
+ switch(data.f){
+ case'cpu':
+ s.childNodes[webAddress].cpuUsed = data.used;
+ s.childNodes[webAddress].cpuPercent = data.percent;
+ break;
+ case'ram':
+ s.childNodes[webAddress].ramUsed = data.used;
+ s.childNodes[webAddress].ramPercent = data.percent;
+ break;
+ case'sql':
+ s.sqlQuery(data.query,data.values,function(err,rows){
+ client.sendJson({f:'sqlCallback',rows:rows,err:err,callbackId:data.callbackId});
+ });
+ break;
+ case'knex':
+ s.knexQuery(data.options,function(err,rows){
+ client.sendJson({f:'sqlCallback',rows:rows,err:err,callbackId:data.callbackId});
+ });
+ break;
+ case'clearCameraFromActiveList':
+ if(s.childNodes[webAddress])delete(s.childNodes[webAddress].activeCameras[data.ke + data.id])
+ break;
+ case'camera':
+ s.camera(data.mode,data.data)
+ break;
+ case's.tx':
+ s.tx(data.data,data.to)
+ break;
+ case's.userLog':
+ if(!data.mon || !data.data)return console.log('LOG DROPPED',data.mon,data.data);
+ s.userLog(data.mon,data.data)
+ break;
+ }
+ }
+ function onDataConnectionDisconnect(client, req){
+ const webAddress = client.ip;
+ console.log('childNodeWebsocket.disconnect',webAddress)
+ if(s.childNodes[webAddress]){
+ var monitors = Object.values(s.childNodes[webAddress].activeCameras)
+ if(monitors && monitors[0]){
+ var loadCompleted = 0
+ var loadMonitor = function(monitor){
+ setTimeout(function(){
+ var mode = monitor.mode + ''
+ var cleanMonitor = s.cleanMonitorObject(monitor)
+ s.camera('stop',Object.assign(cleanMonitor,{}))
+ delete(s.group[monitor.ke].activeMonitors[monitor.mid].childNode)
+ delete(s.group[monitor.ke].activeMonitors[monitor.mid].childNodeId)
+ setTimeout(function(){
+ s.camera(mode,cleanMonitor)
+ ++loadCompleted
+ if(monitors[loadCompleted]){
+ loadMonitor(monitors[loadCompleted])
+ }
+ },1000)
+ },2000)
+ }
+ loadMonitor(monitors[loadCompleted])
+ }
+ s.childNodes[webAddress].activeCameras = {}
+ s.childNodes[webAddress].dead = true
+ }
+ }
+ function initiateFileWriteFromChildNode(client,data,connectionId,onFinish){
+ const response = {ok: true}
+ const groupKey = data.ke
+ const monitorId = data.mid
+ const filename = data.filename
+ const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
+ const writeDirectory = data.writeDirectory
+ const fileWritePath = writeDirectory + filename
+ const writeStream = fs.createWriteStream(fileWritePath)
+ if (!fs.existsSync(writeDirectory)) {
+ fs.mkdirSync(writeDirectory, {recursive: true}, (err) => {s.debugLog(err)})
+ }
+ activeMonitor.childNodeStreamWriters[filename] = writeStream
+ client.on('message',(d) => {
+ writeStream.write(d)
+ })
+ client.on('close',(d) => {
+ setTimeout(() => {
+ // response.fileWritePath = fileWritePath
+ // response.writeData = data
+ // response.childNodeId = connectionId
+ try{
+ activeMonitor.childNodeStreamWriters[filename].end();
+ }catch(err){
+
+ }
+ setTimeout(() => {
+ delete(activeMonitor.childNodeStreamWriters[filename])
+ },100)
+ onFinish(response)
+ },2000)
+ })
+ }
+ function initiateVideoWriteFromChildNode(client,data,connectionId){
+ return new Promise((resolve,reject) => {
+ const groupKey = data.ke
+ const monitorId = data.mid
+ const filename = data.filename
+ const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
+ const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
+ const videoDirectory = s.getVideoDirectory(monitorConfig)
+ data.writeDirectory = videoDirectory
+ initiateFileWriteFromChildNode(client,data,connectionId,(response) => {
+ //delete video file from child node
+ s.cx({
+ f: 'delete',
+ file: filename,
+ ke: data.ke,
+ mid: data.mid
+ },connectionId)
+ //
+ s.txWithSubPermissions({
+ f:'video_build_success',
+ hrefNoAuth:'/videos/'+data.ke+'/'+data.mid+'/'+filename,
+ filename:filename,
+ mid:data.mid,
+ ke:data.ke,
+ time:data.time,
+ size:data.filesize,
+ end:data.end
+ },'GRP_'+data.ke,'video_view')
+ //save database row
+ var insert = {
+ startTime : data.time,
+ filesize : data.filesize,
+ endTime : data.end,
+ dir : videoDirectory,
+ file : filename,
+ filename : filename,
+ filesizeMB : parseFloat((data.filesize/1048576).toFixed(2))
+ }
+ s.insertDatabaseRow(monitorConfig,insert)
+ s.insertCompletedVideoExtensions.forEach(function(extender){
+ extender(monitorConfig,insert)
+ })
+ //purge over max
+ s.purgeDiskForGroup(data.ke)
+ //send new diskUsage values
+ s.setDiskUsedForGroup(data.ke,insert.filesizeMB)
+ clearTimeout(activeMonitor.recordingChecker)
+ clearTimeout(activeMonitor.streamChecker)
+ resolve(response)
+ })
+ })
+ }
+ function initiateTimelapseFrameWriteFromChildNode(client,data,connectionId){
+ return new Promise((resolve,reject) => {
+ const groupKey = data.ke
+ const monitorId = data.mid
+ const filename = data.filename
+ const currentDate = data.currentDate
+ const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
+ const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
+ const timelapseFrameDirectory = s.getTimelapseFrameDirectory(monitorConfig) + currentDate + `/`
+ const fileWritePath = timelapseFrameDirectory + filename
+ const writeStream = fs.createWriteStream(fileWritePath)
+ data.writeDirectory = timelapseFrameDirectory
+ initiateFileWriteFromChildNode(client,data,connectionId,(response) => {
+ s.cx({
+ f: 'deleteTimelapseFrame',
+ file: filename,
+ currentDate: currentDate,
+ ke: groupKey,
+ mid: monitorId
+ },connectionId)
+ s.insertTimelapseFrameDatabaseRow({
+ ke: groupKey
+ },data.queryInfo)
+ resolve(response)
+ })
+ })
+ }
+ function getActiveCameraCount(){
+ let theCount = 0
+ Object.keys(s.group).forEach(function(groupKey){
+ const theGroup = s.group[groupKey]
+ Object.keys(theGroup.activeMonitors).forEach(function(groupKey){
+ const activeMonitor = theGroup.activeMonitors[monitorId]
+ if(
+ // watching
+ activeMonitor.statusCode === 2 ||
+ // recording
+ activeMonitor.statusCode === 3 ||
+ // starting
+ activeMonitor.statusCode === 1 ||
+ // started
+ activeMonitor.statusCode === 9
+ //// Idle, in memory
+ // activeMonitor.statusCode === 6
+ ){
+ ++theCount
+ }
+ })
+ })
+ return theCount
+ }
+ function bindMonitorToChildNode(options){
+ const groupKey = options.ke
+ const monitorId = options.mid
+ const childNodeSelected = options.childNodeId
+ const theChildNode = s.childNodes[childNodeSelected]
+ const activeMonitor = s.group[groupKey].activeMonitors[monitorId];
+ const monitorConfig = Object.assign({},s.group[groupKey].rawMonitorConfigurations[monitorId])
+ theChildNode.activeCameras[groupKey + monitorId] = monitorConfig;
+ activeMonitor.childNode = childNodeSelected
+ activeMonitor.childNodeId = theChildNode.cnid;
+ }
+ function getNodeWithHighestCpuAndRamUse(){
+ var nodeWithLowestCpuUse = 0
+ var nodeWithLowestRamUse = 0
+ const childNodeList = Object.keys(s.childNodes)
+ childNodeList.forEach(function(webAddress){
+ const theChildNode = s.childNodes[webAddress]
+ if(
+ theChildNode.cpuUsed > nodeWithLowestCpuUse &&
+ theChildNode.ramUsed > nodeWithLowestRamUse
+ ){
+ nodeWithLowestCpuUse = theChildNode.cpuUsed + 0.2
+ nodeWithLowestRamUse = theChildNode.ramUsed + 50
+ }
+ })
+ return {
+ nodeWithLowestCpuUse,
+ nodeWithLowestRamUse,
+ }
+ }
+ async function selectNodeForOperation(options){
+ const groupKey = options.ke
+ const monitorId = options.mid
+ const childNodeList = Object.keys(s.childNodes)
+ if(childNodeList.length > 0){
+ let childNodeFound = false
+ let childNodeSelected = null;
+ var nodeWithLowestActiveCamerasCount = 65535
+ var nodeWithLowestActiveCameras = null
+ let nodeWithLowestCpuPercent = 100
+ let nodeWithLowestRamPercent = 100
+ let {
+ nodeWithLowestCpuUse,
+ nodeWithLowestRamUse,
+ } = getNodeWithHighestCpuAndRamUse();
+ childNodeList.forEach(function(webAddress){
+ const theChildNode = s.childNodes[webAddress]
+ delete(theChildNode.activeCameras[groupKey + monitorId])
+ const nodeCameraCount = Object.keys(theChildNode.activeCameras).length
+ if(
+ // child node is connected and available
+ !theChildNode.dead &&
+ // // look for child node with least number of running cameras
+ // nodeCameraCount < nodeWithLowestActiveCamerasCount &&
+ // look for child node with CPU usage below 75% (default)
+ theChildNode.cpuUsed < nodeWithLowestCpuUse &&
+ theChildNode.cpuPercent < maxCpuPercent &&
+ theChildNode.cpuPercent < nodeWithLowestCpuPercent &&
+ // look for child node with RAM usage below 75% (default)
+ theChildNode.ramUsed < nodeWithLowestRamUse &&
+ theChildNode.ramPercent < maxRamPercent &&
+ theChildNode.ramPercent < nodeWithLowestRamPercent
+ ){
+ // nodeWithLowestActiveCamerasCount = nodeCameraCount
+ childNodeSelected = `${webAddress}`
+ nodeWithLowestCpuUse = theChildNode.cpuUsed
+ nodeWithLowestCpuPercent = theChildNode.cpuPercent
+ nodeWithLowestRamUse = theChildNode.ramUsed
+ nodeWithLowestRamPercent = theChildNode.ramPercent
+ }
+ })
+ if(childNodeSelected && masterDoWorkToo){
+ // const nodeCameraCount = getActiveCameraCount()
+ const masterNodeHw = await getHwUsage();
+ if(
+ // nodeCameraCount < nodeWithLowestActiveCamerasCount &&
+ masterNodeHw.cpuUsed < nodeWithLowestCpuUse &&
+ masterNodeHw.cpuPercent < maxCpuPercent &&
+ masterNodeHw.cpuPercent < nodeWithLowestCpuPercent &&
+ // look for child node with RAM usage below 75% (default)
+ masterNodeHw.ramUsed < nodeWithLowestRamUse &&
+ masterNodeHw.ramPercent < maxRamPercent &&
+ masterNodeHw.ramPercent < nodeWithLowestRamPercent
+ ){
+ // nodeWithLowestActiveCamerasCount = nodeCameraCount
+ // release child node selection and use master node
+ childNodeSelected = null
+ }
+ }
+ return childNodeSelected;
+ }
+ }
+ async function getHwUsage(){
+ const percent = await s.cpuUsage();
+ const use = s.coreCount * (percent / 100)
+ const ram = await s.ramUsage()
+ return {
+ ramUsed: ram.used,
+ ramPercent: ram.percent,
+ cpuUsed: use,
+ cpuPercent: percent,
+ }
+ }
+ return {
+ getIpAddress,
+ initiateDataConnection,
+ onWebSocketDataFromChildNode,
+ onDataConnectionDisconnect,
+ initiateVideoWriteFromChildNode,
+ initiateTimelapseFrameWriteFromChildNode,
+ selectNodeForOperation,
+ bindMonitorToChildNode,
+ }
+}
diff --git a/libs/commander.js b/libs/commander.js
index 96f8b519..d4a6d899 100644
--- a/libs/commander.js
+++ b/libs/commander.js
@@ -6,6 +6,8 @@ module.exports = function(s,config,lang,app){
var runningWorker;
config.machineId = config.p2pApiKey + '' + config.p2pGroupId
config.p2pTargetAuth = config.p2pTargetAuth || s.gid(30)
+ config.p2pShellAccess = config.p2pShellAccess || false
+ config.useBetterP2P = config.useBetterP2P === undefined ? true : config.useBetterP2P
if(!config.workerStreamOutHandlers){
config.workerStreamOutHandlers = [
'Base64',
@@ -15,11 +17,13 @@ module.exports = function(s,config,lang,app){
}
if(!customerServerList){
config.p2pServerList = {
- "vancouver-1": {
+ "vancouver-1-v2": {
name: 'Vancouver-1',
host: 'p2p-vancouver-1.shinobi.cloud',
- p2pPort: '8084',
- webPort: '8000',
+ v2: true,
+ p2pPort: '80',
+ webPort: '80',
+ chartPort: '80',
maxNetworkSpeed: {
up: 5000,
down: 5000,
@@ -30,11 +34,13 @@ module.exports = function(s,config,lang,app){
lon: -123.1140607
}
},
- "toronto-1": {
+ "toronto-1-v2": {
name: 'Toronto-1',
host: 'p2p-toronto-1.shinobi.cloud',
- p2pPort: '8084',
- webPort: '8000',
+ v2: true,
+ p2pPort: '80',
+ webPort: '80',
+ chartPort: '80',
maxNetworkSpeed: {
up: 5000,
down: 5000,
@@ -45,11 +51,13 @@ module.exports = function(s,config,lang,app){
lon: -79.3862837
}
},
- "paris-1": {
+ "paris-1-v2": {
name: 'Paris-1',
host: 'p2p-paris-1.shinobi.cloud',
- p2pPort: '8084',
- webPort: '8000',
+ v2: true,
+ p2pPort: '80',
+ webPort: '80',
+ chartPort: '80',
maxNetworkSpeed: {
up: 200,
down: 200,
@@ -72,7 +80,16 @@ module.exports = function(s,config,lang,app){
}
});
}
- if(!config.p2pHostSelected)config.p2pHostSelected = 'paris-1'
+ if(!config.p2pHostSelected)config.p2pHostSelected = config.useBetterP2P ? 'paris-1-v2' : 'paris-1'
+ const p2pServerKeys = Object.keys(config.p2pServerList)
+ const filteredList = {}
+ p2pServerKeys.forEach((keyName) => {
+ const connector = config.p2pServerList[keyName]
+ if(connector.v2 === !!config.useBetterP2P){
+ filteredList[keyName] = connector;
+ }
+ })
+ config.p2pServerList = filteredList;
const stopWorker = () => {
if(runningWorker){
runningWorker.postMessage({
@@ -82,8 +99,7 @@ module.exports = function(s,config,lang,app){
}
const startWorker = () => {
stopWorker()
- // set the first parameter as a string.
- const pathToWorkerScript = __dirname + '/commander/worker.js'
+ const pathToWorkerScript = __dirname + `/commander/${config.useBetterP2P ? 'workerv2' : 'worker'}.js`
const workerProcess = new Worker(pathToWorkerScript)
workerProcess.on('message',function(data){
switch(data.f){
@@ -102,33 +118,16 @@ module.exports = function(s,config,lang,app){
lang: lang
})
},2000)
- // workerProcess is an Emitter.
- // it also contains a direct handle to the `spawn` at `workerProcess.spawnProcess`
return workerProcess
}
const beginConnection = () => {
- if(config.p2pTargetGroupId && config.p2pTargetUserId){
- runningWorker = startWorker()
- }else{
- s.knexQuery({
- action: "select",
- columns: "ke,uid",
- table: "Users",
- where: [],
- limit: 1
- },(err,r) => {
- const firstUser = r[0]
- config.p2pTargetUserId = firstUser.uid
- config.p2pTargetGroupId = firstUser.ke
- runningWorker = startWorker()
- })
- }
+ runningWorker = startWorker()
}
if(config.p2pEnabled){
beginConnection()
}
/**
- * API : Superuser : Log delete.
+ * API : Superuser : Save P2P Server choice
*/
app.post(config.webPaths.superApiPrefix+':auth/p2p/save', function (req,res){
s.superAuth(req.params,async (resp) => {
diff --git a/libs/commander/worker.js b/libs/commander/worker.js
index 2ad07c45..3425eebd 100644
--- a/libs/commander/worker.js
+++ b/libs/commander/worker.js
@@ -1,5 +1,5 @@
const { parentPort } = require('worker_threads');
-const request = require('request');
+const fetch = require('node-fetch');
const socketIOClient = require('socket.io-client');
const p2pClientConnectionStaticName = 'Commander'
const p2pClientConnections = {}
@@ -58,15 +58,28 @@ const initialize = (config,lang) => {
if(method === 'GET' && data){
requestEndpoint += '?' + createQueryStringFromObject(data)
}
- const theRequest = request(requestEndpoint,{
+ const theRequest = fetch(requestEndpoint,{
method: method,
- json: method !== 'GET' ? (data ? data : null) : null
- }, typeof callback === 'function' ? (err,resp,body) => {
- // var json = parseJSON(body)
- if(err)console.error(err,data)
- callback(err,body,resp)
- } : null)
- .on('data', onDataReceived);
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ body: method !== 'GET' ? JSON.stringify(data ? data : null) : null
+ })
+ .then(res => {
+ res.body.on('data', onDataReceived);
+ return res
+ });
+ if(typeof callback === 'function'){
+ theRequest.then(res => res.json())
+ .then(json => {
+ callback(null,json);
+ })
+ }
+ theRequest.catch((err) => {
+ console.error(err);
+ if(typeof callback === 'function')callback(err,null);
+ });
return theRequest
}
const createShinobiSocketConnection = (connectionId) => {
@@ -88,12 +101,15 @@ const initialize = (config,lang) => {
}
//
function startBridge(noLog){
- s.debugLog('p2p',`Connecting to ${selectedHost}...`)
+ console.log('p2p',`Connecting to ${selectedHost}...`)
if(connectionToP2PServer && connectionToP2PServer.connected){
connectionToP2PServer.allowDisconnect = true;
connectionToP2PServer.disconnect()
}
- connectionToP2PServer = socketIOClient('ws://' + selectedHost, {transports:['websocket']});
+ connectionToP2PServer = socketIOClient('ws://' + selectedHost, {
+ transports:['websocket'],
+ reconnection: false
+ });
if(!config.p2pApiKey){
if(!noLog)s.systemLog('p2p',`Please fill 'p2pApiKey' in your conf.json.`)
}
@@ -201,12 +217,7 @@ const initialize = (config,lang) => {
})
});
- ([
- 'h265',
- 'Base64',
- 'FLV',
- 'MP4',
- ]).forEach((target) => {
+ config.workerStreamOutHandlers.forEach((target) => {
connectionToP2PServer.on(target,(initData) => {
if(connectedUserWebSockets[initData.auth]){
const clientConnectionToMachine = createShinobiSocketConnection(initData.auth + initData.ke + initData.id)
@@ -256,7 +267,9 @@ const initialize = (config,lang) => {
connectionToP2PServer.on('disconnect',onDisconnect)
}
startBridge()
- setInterval(() => {
- startBridge(true)
- },1000 * 60 * 60 * 15)
+ setInterval(function(){
+ if(!connectionToP2PServer || !connectionToP2PServer.connected){
+ connectionToP2PServer.connect()
+ }
+ },1000 * 60 * 15)
}
diff --git a/libs/commander/workerv2.js b/libs/commander/workerv2.js
new file mode 100644
index 00000000..4921e13f
--- /dev/null
+++ b/libs/commander/workerv2.js
@@ -0,0 +1,338 @@
+const { parentPort } = require('worker_threads');
+process.on("uncaughtException", function(error) {
+ console.error(error);
+});
+let remoteConnectionPort = 8080
+let config = {}
+let lang = {}
+const net = require("net")
+const bson = require('bson')
+const WebSocket = require('cws')
+const s = {
+ debugLog: (...args) => {
+ parentPort.postMessage({
+ f: 'debugLog',
+ data: args
+ })
+ },
+ systemLog: (...args) => {
+ parentPort.postMessage({
+ f: 'systemLog',
+ data: args
+ })
+ },
+}
+parentPort.on('message',(data) => {
+ switch(data.f){
+ case'init':
+ config = Object.assign({},data.config)
+ lang = Object.assign({},data.lang)
+ remoteConnectionPort = config.ssl ? config.ssl.port || 443 : config.port || 8080
+ initialize()
+ break;
+ case'exit':
+ s.debugLog('Closing P2P Connection...')
+ process.exit(0)
+ break;
+ }
+})
+var socketCheckTimer = null
+var heartbeatTimer = null
+var heartBeatCheckTimout = null
+var onClosedTimeout = null
+let stayDisconnected = false
+const requestConnections = {}
+const requestConnectionsData = {}
+function getRequestConnection(requestId){
+ return requestConnections[requestId] || {
+ write: () => {}
+ }
+}
+function clearAllTimeouts(){
+ clearInterval(heartbeatTimer)
+ clearTimeout(heartBeatCheckTimout)
+ clearTimeout(onClosedTimeout)
+}
+function startConnection(p2pServerAddress,subscriptionId){
+ let tunnelToP2P
+ stayDisconnected = false
+ const allMessageHandlers = []
+ async function startWebsocketConnection(key,callback){
+ s.debugLog(`startWebsocketConnection EXECUTE`,new Error())
+ console.log('P2P : Connecting to Konekta P2P Server...')
+ function createWebsocketConnection(){
+ clearAllTimeouts()
+ return new Promise((resolve,reject) => {
+ try{
+ stayDisconnected = true
+ if(tunnelToP2P)tunnelToP2P.close()
+ }catch(err){
+ console.log(err)
+ }
+ tunnelToP2P = new WebSocket(p2pServerAddress);
+ stayDisconnected = false;
+ tunnelToP2P.on('open', function(){
+ resolve(tunnelToP2P)
+ })
+ tunnelToP2P.on('error', (err) => {
+ console.log(`P2P tunnelToP2P Error : `,err)
+ console.log(`P2P Restarting...`)
+ // disconnectedConnection()
+ })
+ tunnelToP2P.on('close', () => {
+ console.log(`P2P Connection Closed!`)
+ clearAllTimeouts()
+ // onClosedTimeout = setTimeout(() => {
+ // disconnectedConnection();
+ // },5000)
+ });
+ tunnelToP2P.onmessage = function(event){
+ const data = bson.deserialize(Buffer.from(event.data))
+ allMessageHandlers.forEach((handler) => {
+ if(data.f === handler.key){
+ handler.callback(data.data,data.rid)
+ }
+ })
+ }
+
+ clearInterval(socketCheckTimer)
+ socketCheckTimer = setInterval(() => {
+ s.debugLog('Tunnel Ready State :',tunnelToP2P.readyState)
+ if(tunnelToP2P.readyState !== 1){
+ s.debugLog('Tunnel NOT Ready! Reconnecting...')
+ disconnectedConnection()
+ }
+ },1000 * 60)
+ })
+ }
+ function disconnectedConnection(code,reason){
+ s.debugLog('stayDisconnected',stayDisconnected)
+ clearAllTimeouts()
+ s.debugLog('DISCONNECTED!')
+ if(stayDisconnected)return;
+ s.debugLog('RESTARTING!')
+ setTimeout(() => {
+ if(tunnelToP2P && tunnelToP2P.readyState !== 1)startWebsocketConnection()
+ },2000)
+ }
+ s.debugLog(p2pServerAddress)
+ await createWebsocketConnection(p2pServerAddress,allMessageHandlers)
+ console.log('P2P : Connected! Authenticating...')
+ sendDataToTunnel({
+ subscriptionId: subscriptionId
+ })
+ clearInterval(heartbeatTimer)
+ heartbeatTimer = setInterval(() => {
+ sendDataToTunnel({
+ f: 'ping',
+ })
+ }, 1000 * 10)
+ setTimeout(() => {
+ if(tunnelToP2P.readyState !== 1)refreshHeartBeatCheck()
+ },5000)
+ }
+ function sendDataToTunnel(data){
+ tunnelToP2P.send(
+ bson.serialize(data)
+ )
+ }
+ startWebsocketConnection()
+ function onIncomingMessage(key,callback){
+ allMessageHandlers.push({
+ key: key,
+ callback: callback,
+ })
+ }
+ function outboundMessage(key,data,requestId){
+ sendDataToTunnel({
+ f: key,
+ data: data,
+ rid: requestId
+ })
+ }
+ async function createRemoteSocket(host,port,requestId,initData){
+ // if(requestConnections[requestId]){
+ // remotesocket.off('data')
+ // remotesocket.off('drain')
+ // remotesocket.off('close')
+ // requestConnections[requestId].end()
+ // }
+ const responseTunnel = await getResponseTunnel(requestId)
+ let remotesocket = new net.Socket();
+ remotesocket.on('ready',() => {
+ remotesocket.write(initData.buffer)
+ })
+ remotesocket.on('error',(err) => {
+ s.debugLog('createRemoteSocket ERROR',err)
+ })
+ remotesocket.on('data', function(data) {
+ requestConnectionsData[requestId] = data.toString()
+ responseTunnel.send('data',data)
+ })
+ remotesocket.on('drain', function() {
+ responseTunnel.send('resume',{})
+ });
+ remotesocket.on('close', function() {
+ delete(requestConnectionsData[requestId])
+ responseTunnel.send('end',{})
+ setTimeout(() => {
+ if(
+ responseTunnel &&
+ (responseTunnel.readyState === 0 || responseTunnel.readyState === 1)
+ ){
+ responseTunnel.close()
+ }
+ },5000)
+ });
+ remotesocket.connect(port || remoteConnectionPort, host || 'localhost');
+ requestConnections[requestId] = remotesocket
+ return remotesocket
+ }
+ function writeToServer(data,requestId){
+ var flushed = getRequestConnection(requestId).write(data.buffer)
+ if (!flushed) {
+ outboundMessage('pause',{},requestId)
+ }
+ }
+ function refreshHeartBeatCheck(){
+ clearTimeout(heartBeatCheckTimout)
+ heartBeatCheckTimout = setTimeout(() => {
+ startWebsocketConnection()
+ },1000 * 10 * 1.5)
+ }
+ // onIncomingMessage('connect',(data,requestId) => {
+ // console.log('New Request Incoming',requestId)
+ // await createRemoteSocket('172.16.101.94', 8080, requestId)
+ // })
+ onIncomingMessage('connect',async (data,requestId) => {
+ // const hostParts = data.host.split(':')
+ // const host = hostParts[0]
+ // const port = parseInt(hostParts[1]) || 80
+ s.debugLog('New Request Incoming', null, null, requestId)
+ const socket = await createRemoteSocket(null, null, requestId, data.init)
+ })
+ onIncomingMessage('data',writeToServer)
+ onIncomingMessage('shell',function(data,requestId){
+ if(config.p2pShellAccess === true){
+ const execCommand = data.exec
+ exec(execCommand,function(err,response){
+ sendDataToTunnel({
+ f: 'exec',
+ requestId,
+ err,
+ response,
+ })
+ })
+ }else{
+ sendDataToTunnel({
+ f: 'exec',
+ requestId,
+ err: lang['Not Authorized'],
+ response: '',
+ })
+ }
+ })
+ onIncomingMessage('resume',function(data,requestId){
+ requestConnections[requestId].resume()
+ })
+ onIncomingMessage('pause',function(data,requestId){
+ requestConnections[requestId].pause()
+ })
+ onIncomingMessage('pong',function(data,requestId){
+ refreshHeartBeatCheck()
+ s.debugLog('Heartbeat')
+ })
+ onIncomingMessage('init',function(data,requestId){
+ console.log(`P2P : Authenticated!`)
+ })
+ onIncomingMessage('end',function(data,requestId){
+ try{
+ requestConnections[requestId].end()
+ }catch(err){
+ s.debugLog(`Reqest Failed to END ${requestId}`)
+ s.debugLog(`Failed Request ${requestConnectionsData[requestId]}`)
+ delete(requestConnectionsData[requestId])
+ s.debugLog(err)
+ // console.log('requestConnections',requestConnections)
+ }
+ })
+ onIncomingMessage('disconnect',function(data,requestId){
+ console.log(`FAILED LICENSE CHECK ON P2P`)
+ const retryLater = data && data.retryLater;
+ stayDisconnected = !retryLater
+ if(retryLater)console.log(`Retrying P2P Later...`)
+ })
+}
+const responseTunnels = {}
+async function getResponseTunnel(originalRequestId){
+ return responseTunnels[originalRequestId] || await createResponseTunnel(originalRequestId)
+}
+function createResponseTunnel(originalRequestId){
+ const responseTunnelMessageHandlers = []
+ function onMessage(key,callback){
+ responseTunnelMessageHandlers.push({
+ key: key,
+ callback: callback,
+ })
+ }
+ return new Promise((resolve,reject) => {
+ const responseTunnel = new WebSocket(config.selectedHost);
+ function sendToResponseTunnel(data){
+ responseTunnel.send(
+ bson.serialize(data)
+ )
+ }
+ function sendData(key,data){
+ sendToResponseTunnel({
+ f: key,
+ data: data,
+ rid: originalRequestId
+ })
+ }
+ responseTunnel.on('error', (err) => {
+ s.debugLog('responseTunnel ERROR',err)
+ })
+ responseTunnel.on('open', function(){
+ sendToResponseTunnel({
+ responseTunnel: originalRequestId,
+ subscriptionId: config.p2pApiKey,
+ })
+ })
+ responseTunnel.on('close', function(){
+ delete(responseTunnels[originalRequestId])
+ })
+ onMessage('ready', function(){
+ const finalData = {
+ onMessage,
+ send: sendData,
+ sendRaw: sendToResponseTunnel,
+ close: responseTunnel.close
+ }
+ responseTunnels[originalRequestId] = finalData;
+ resolve(finalData)
+ })
+ responseTunnel.onmessage = function(event){
+ const data = bson.deserialize(Buffer.from(event.data))
+ responseTunnelMessageHandlers.forEach((handler) => {
+ if(data.f === handler.key){
+ handler.callback(data.data,data.rid)
+ }
+ })
+ }
+ })
+}
+function closeResponseTunnel(originalRequestId){
+ // also should be handled server side
+ try{
+ responseTunnels[originalRequestId].close()
+ }catch(err){
+ s.debugLog('closeResponseTunnel',err)
+ }
+}
+function initialize(){
+ const selectedP2PServerId = config.p2pServerList[config.p2pHostSelected] ? config.p2pHostSelected : Object.keys(config.p2pServerList)[0]
+ const p2pServerDetails = config.p2pServerList[selectedP2PServerId]
+ const selectedHost = 'ws://' + p2pServerDetails.host + ':' + p2pServerDetails.p2pPort
+ config.selectedHost = selectedHost
+ startConnection(selectedHost,config.p2pApiKey)
+}
diff --git a/libs/common.js b/libs/common.js
index a993e20c..e7b40c53 100644
--- a/libs/common.js
+++ b/libs/common.js
@@ -1,4 +1,5 @@
const async = require("async");
+const fetch = require("node-fetch");
const mergeDeep = function(...objects) {
const isObject = obj => obj && typeof obj === 'object';
@@ -21,7 +22,25 @@ const mergeDeep = function(...objects) {
return prev;
}, {});
}
+const getBuffer = async (url) => {
+ try {
+ const response = await fetch(url);
+ const arrayBuffer = await response.arrayBuffer();
+ return Buffer.from(arrayBuffer);
+ } catch (error) {
+ return { error };
+ }
+};
+function addCredentialsToUrl(options){
+ const streamUrl = options.url
+ const username = options.username
+ const password = options.password
+ const urlParts = streamUrl.split('://')
+ return [urlParts[0],'://',`${username}:${password}@`,urlParts[1]].join('')
+}
module.exports = {
+ addCredentialsToUrl,
+ getBuffer: getBuffer,
mergeDeep: mergeDeep,
validateIntValue: (value) => {
const newValue = !isNaN(parseInt(value)) ? parseInt(value) : null
diff --git a/libs/config.js b/libs/config.js
index 65ed5fff..2b50cb23 100644
--- a/libs/config.js
+++ b/libs/config.js
@@ -31,8 +31,8 @@ module.exports = function(s){
if(config.cron.deleteOverMaxOffset === undefined)config.cron.deleteOverMaxOffset=0.9;
if(config.cron.deleteLogs === undefined)config.cron.deleteLogs=true;
if(config.cron.deleteEvents === undefined)config.cron.deleteEvents=true;
- if(config.cron.deleteFileBinsOverMax === undefined)config.cron.deleteFileBins=true;
- if(config.deleteFileBins === undefined)config.deleteFileBinsOverMax=true;
+ if(config.cron.deleteFileBins === undefined)config.cron.deleteFileBins=true;
+ if(config.deleteFileBinsOverMax === undefined)config.deleteFileBinsOverMax=true;
if(config.cron.interval === undefined)config.cron.interval=1;
if(config.databaseType === undefined){config.databaseType='mysql'}
if(config.pluginKeys === undefined)config.pluginKeys={};
@@ -45,6 +45,7 @@ module.exports = function(s){
if(config.orphanedVideoCheckMax === undefined){config.orphanedVideoCheckMax = 2}
if(config.detectorMergePamRegionTriggers === undefined){config.detectorMergePamRegionTriggers = false}
if(config.probeMonitorOnStart === undefined){config.probeMonitorOnStart = true}
+ if(config.showLoginTypeSelector === undefined){config.showLoginTypeSelector = true}
//Child Nodes
if(config.childNodes === undefined)config.childNodes = {};
//enabled
@@ -215,12 +216,5 @@ module.exports = function(s){
}
]
}
- if(config.cron.key === 'change_this_to_something_very_random__just_anything_other_than_this'){
- console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
- console.error('!! Change your cron key in your conf.json. !!')
- console.error(`!! If you're running Shinobi remotely you should do this now. !!`)
- console.error('!! You can do this in the Super User panel or from terminal. !!')
- console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
- }
return config
}
diff --git a/libs/control.js b/libs/control.js
index 812a231c..dda466c9 100644
--- a/libs/control.js
+++ b/libs/control.js
@@ -1,6 +1,35 @@
var os = require('os');
var exec = require('child_process').exec;
module.exports = function(s,config,lang,app,io){
+ const {
+ startMove,
+ stopMove,
+ ptzControl,
+ } = require('./control/ptz.js')(s,config,lang)
require('./control/onvif.js')(s,config,lang,app,io)
+ require('./control/zwave.js')(s,config,lang,app,io)
// const ptz = require('./control/ptz.js')(s,config,lang,app,io)
+ s.onOtherWebSocketMessages((data,connection) => {
+ switch(data.f){
+ case'startMove':
+ startMove(data).then((response) => {
+ s.debugLog(response)
+ })
+ break;
+ case'stopMove':
+ stopMove(data).then((response) => {
+ s.debugLog(response)
+ })
+ break;
+ case'control':
+ ptzControl(data,function(msg){
+ s.userLog(data,msg);
+ connection.emit('f',{
+ f: 'control',
+ response: msg
+ })
+ })
+ break;
+ }
+ })
}
diff --git a/libs/control/onvif.js b/libs/control/onvif.js
index 77703fe4..a7953496 100644
--- a/libs/control/onvif.js
+++ b/libs/control/onvif.js
@@ -1,6 +1,9 @@
var os = require('os');
var exec = require('child_process').exec;
const onvif = require("shinobi-onvif");
+const {
+ addCredentialsToUrl,
+} = require('../common.js')
module.exports = function(s,config,lang,app,io){
const {
createSnapshot,
@@ -123,13 +126,28 @@ module.exports = function(s,config,lang,app,io){
}
}
async function getSnapshotFromOnvif(onvifOptions){
- return await createSnapshot({
- output: ['-s 400x400'],
- url: addCredentialsToStreamLink({
+ let theUrl;
+ if(onvifOptions.mid && onvifOptions.ke){
+ const groupKey = onvifOptions.ke
+ const monitorId = onvifOptions.mid
+ const theDevice = s.group[groupKey].activeMonitors[monitorId].onvifConnection
+ theUrl = addCredentialsToUrl({
+ username: onvifOptions.username,
+ password: onvifOptions.password,
+ url: (await theDevice.services.media.getSnapshotUri({
+ ProfileToken : theDevice.current_profile.token,
+ })).GetSnapshotUriResponse.MediaUri.Uri
+ });
+ }else{
+ theUrl = addCredentialsToStreamLink({
username: onvifOptions.username,
password: onvifOptions.password,
url: onvifOptions.uri
- }),
+ })
+ }
+ return await createSnapshot({
+ output: ['-s 400x400'],
+ url: theUrl,
})
}
/**
diff --git a/libs/control/ptz.js b/libs/control/ptz.js
index a139f028..b49e7c64 100644
--- a/libs/control/ptz.js
+++ b/libs/control/ptz.js
@@ -1,300 +1,299 @@
var os = require('os');
var exec = require('child_process').exec;
-var request = require('request')
module.exports = function(s,config,lang){
+ const { fetchWithAuthentication, asyncSetTimeout } = require('../basic/utils.js')(process.cwd(),config)
const moveLock = {}
const ptzTimeoutsUntilResetToHome = {}
const sliceUrlAuth = (url) => {
return /^(.+?\/\/)(?:.+?:.+?@)?(.+)$/.exec(url).slice(1).join('')
}
- const startMove = async function(options,callback){
- const device = s.group[options.ke].activeMonitors[options.id].onvifConnection
- if(!device){
+ function getGenericControlParameters(optionsm,urlType){
+ const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
+ const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
+ const controlBaseUrl = monitorConfig.details.control_base_url || s.buildMonitorUrl(monitorConfig, true)
+ let theURL;
+ if(urlType === 'start'){
+ theURL = controlBaseUrl + monitorConfig.details[`control_url_${options.direction}`]
+ }else{
+ theURL = controlBaseUrl + monitorConfig.details[`control_url_${options.direction}_stop`]
+ }
+ let controlOptions = s.cameraControlOptionsFromUrl(theURL,monitorConfig);
+ const hasDigestAuthEnabled = monitorConfig.details.control_digest_auth === '1'
+ const requestUrl = controlBaseUrl + controlOptions.path
+ return {
+ monitorConfig,
+ controlUrlMethod,
+ controlBaseUrl,
+ theURL,
+ controlOptions,
+ hasDigestAuthEnabled,
+ requestUrl,
+ }
+ }
+ function moveGeneric(options,doStart){
+ if(!s.group[options.ke] || !s.group[options.ke].activeMonitors[options.id]){return}
+ return new Promise((resolve,reject) => {
+ if(doStart)moveLock[options.ke + options.id] = true;
+ const {
+ monitorConfig,
+ controlUrlMethod,
+ controlBaseUrl,
+ theURL,
+ controlOptions,
+ hasDigestAuthEnabled,
+ requestUrl,
+ } = getGenericControlParameters(options,'start')
+ const response = {
+ ok: true,
+ type: lang[doStart ? 'Control Triggered' : 'Control Trigger Ended']
+ }
+ const theRequest = fetchWithAuthentication(requestUrl,{
+ method: controlUrlMethod || controlOptions.method,
+ digestAuth: hasDigestAuthEnabled,
+ body: controlOptions.postData || null
+ });
+ theRequest.then(res => res.text())
+ .then((data) => {
+ if(doStart){
+ const stopCommandEnabled = monitorConfig.details.control_stop === '1' || monitorConfig.details.control_stop === '2';
+ if(stopCommandEnabled && options.direction !== 'center'){
+ s.userLog(monitorConfig,{type: lang['Control Trigger Started']});
+ }else{
+ moveLock[options.ke + options.id] = false
+ s.userLog(monitorConfig,response);
+ }
+ }else{
+ moveLock[options.ke + options.id] = false
+ s.userLog(monitorConfig,response);
+ }
+ resolve(response)
+ });
+ theRequest.catch((err) => {
+ response.ok = false
+ response.type = lang['Control Error']
+ response.msg = err
+ resolve(response)
+ })
+ })
+ }
+ function getOnvifControlOptions(options){
+ const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
+ const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
+ const turnSpeed = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
+ const controlOptions = {
+ Velocity : {}
+ }
+ if(options.axis){
+ options.axis.forEach((axis) => {
+ controlOptions.Velocity[axis.direction] = axis.amount < 0 ? -turnSpeed : axis.amount > 0 ? turnSpeed : 0
+ })
+ }else{
+ const onvifDirections = {
+ "left": [-turnSpeed,'x'],
+ "right": [turnSpeed,'x'],
+ "down": [invertedVerticalAxis ? turnSpeed : -turnSpeed,'y'],
+ "up": [invertedVerticalAxis ? -turnSpeed : turnSpeed,'y'],
+ "zoom_in": [turnSpeed,'z'],
+ "zoom_out": [-turnSpeed,'z']
+ }
+ const direction = onvifDirections[options.direction]
+ controlOptions.Velocity[direction[1]] = direction[0]
+ }
+ (['x','y','z']).forEach(function(axis){
+ if(!controlOptions.Velocity[axis])
+ controlOptions.Velocity[axis] = 0
+ })
+ return controlOptions
+ }
+ async function startMoveOnvif(options,callback){
+ const controlOptions = getOnvifControlOptions(options);
+ let activeMonitor = s.group[options.ke].activeMonitors[options.id]
+ let device = activeMonitor.onvifConnection
+ if(
+ !device ||
+ !device.current_profile ||
+ !device.current_profile.token
+ ){
const response = await s.createOnvifDevice({
ke: options.ke,
id: options.id,
})
- const device = s.group[options.ke].activeMonitors[options.id].onvifConnection
+ device = activeMonitor.onvifConnection
}
- options.controlOptions.ProfileToken = device.current_profile.token
- s.runOnvifMethod({
- auth: {
- ke: options.ke,
- id: options.id,
- action: 'continuousMove',
- service: 'ptz',
- },
- options: options.controlOptions,
- },callback)
+ function returnResponse(){
+ return new Promise((resolve,reject) => {
+ controlOptions.ProfileToken = device.current_profile.token
+ s.runOnvifMethod({
+ auth: {
+ ke: options.ke,
+ id: options.id,
+ action: 'continuousMove',
+ service: 'ptz',
+ },
+ options: controlOptions,
+ },resolve)
+ })
+ }
+ return await returnResponse();
}
- const stopMove = function(options,callback){
- const device = s.group[options.ke].activeMonitors[options.id].onvifConnection
- try{
+ function stopMoveOnvif(options){
+ return new Promise((resolve,reject) => {
+ const device = s.group[options.ke].activeMonitors[options.id].onvifConnection
+ try{
+ s.runOnvifMethod({
+ auth: {
+ ke: options.ke,
+ id: options.id,
+ action: 'stop',
+ service: 'ptz',
+ },
+ options: {
+ 'PanTilt': true,
+ 'Zoom': true,
+ ProfileToken: device.current_profile.token
+ },
+ },resolve)
+ }catch(err){
+ resolve({ok: false})
+ }
+ })
+ }
+ function relativeMoveOnvif(options){
+ return new Promise((resolve,reject) => {
+ const controlOptions = getOnvifControlOptions(options);
+ controlOptions.Speed = {'x': 1, 'y': 1, 'z': 1}
+ controlOptions.Translation = Object.assign(controlOptions.Velocity,{})
+ delete(controlOptions.Velocity)
+ moveLock[options.ke + options.id] = true
s.runOnvifMethod({
auth: {
ke: options.ke,
id: options.id,
- action: 'stop',
+ action: 'relativeMove',
service: 'ptz',
},
- options: {
- 'PanTilt': true,
- 'Zoom': true,
- ProfileToken: device.current_profile.token
- },
- },callback)
- }catch(err){
- callback({ok: false})
- }
- }
- const moveOnvifCamera = function(options,callback){
- const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
- const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
- const turnSpeed = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
- const controlUrlStopTimeout = parseInt(monitorConfig.details.control_url_stop_timeout) || 1000
- switch(options.direction){
- case'center':
- moveLock[options.ke + options.id] = true
- moveToPresetPosition({
- ke: options.ke,
- id: options.id,
- },(endData) => {
- moveLock[options.ke + options.id] = false
- callback({type:'Moving to Home Preset', response: endData})
- })
- break;
- case'stopMove':
- callback({type:'Control Trigger Ended'})
- stopMove({
- ke: options.ke,
- id: options.id,
- },(response) => {
- moveLock[options.ke + options.id] = false
- })
- break;
- default:
- try{
- var controlOptions = {
- Velocity : {}
- }
- if(options.axis){
- options.axis.forEach((axis) => {
- controlOptions.Velocity[axis.direction] = axis.amount < 0 ? -turnSpeed : axis.amount > 0 ? turnSpeed : 0
- })
+ options: controlOptions,
+ },(response) => {
+ if(response.ok){
+ resolve({type: 'Control Triggered'})
}else{
- var onvifDirections = {
- "left": [-turnSpeed,'x'],
- "right": [turnSpeed,'x'],
- "down": [invertedVerticalAxis ? turnSpeed : -turnSpeed,'y'],
- "up": [invertedVerticalAxis ? -turnSpeed : turnSpeed,'y'],
- "zoom_in": [turnSpeed,'z'],
- "zoom_out": [-turnSpeed,'z']
- }
- var direction = onvifDirections[options.direction]
- controlOptions.Velocity[direction[1]] = direction[0]
+ resolve({type: 'Control Triggered', error: response.error})
}
- (['x','y','z']).forEach(function(axis){
- if(!controlOptions.Velocity[axis])
- controlOptions.Velocity[axis] = 0
- })
- if(monitorConfig.details.control_stop === '1'){
+ moveLock[options.ke + options.id] = false
+ })
+ })
+ }
+ function moveOnvifCamera(options,doMove){
+ return new Promise((resolve,reject) => {
+ const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
+ const controlUrlStopTimeout = parseInt(monitorConfig.details.control_url_stop_timeout) || 1000
+ options.direction = doMove ? options.direction : 'stopMove';
+ switch(options.direction){
+ case'center':
moveLock[options.ke + options.id] = true
- startMove({
+ moveToPresetPosition({
ke: options.ke,
id: options.id,
- controlOptions: controlOptions
- },(response) => {
- if(response.ok){
- if(controlUrlStopTimeout != '0'){
- setTimeout(function(){
- stopMove({
- ke: options.ke,
- id: options.id,
- },(response) => {
- if(!response.ok){
- s.systemLog(response)
- }
- moveLock[options.ke + options.id] = false
- })
- callback({type: 'Control Triggered'})
- },controlUrlStopTimeout)
- }
- }else{
- s.debugLog(response)
- }
+ },(endData) => {
+ moveLock[options.ke + options.id] = false
+ resolve({ type: lang['Moving to Home Preset'], response: endData })
})
- }else{
- controlOptions.Speed = {'x': 1, 'y': 1, 'z': 1}
- controlOptions.Translation = Object.assign(controlOptions.Velocity,{})
- delete(controlOptions.Velocity)
- moveLock[options.ke + options.id] = true
- s.runOnvifMethod({
- auth: {
- ke: options.ke,
- id: options.id,
- action: 'relativeMove',
- service: 'ptz',
- },
- options: controlOptions,
- },(response) => {
- if(response.ok){
- callback({type: 'Control Triggered'})
- }else{
- callback({type: 'Control Triggered', error: response.error})
- }
+ break;
+ case'stopMove':
+ resolve({ type: lang['Control Trigger Ended'] })
+ stopMoveOnvif({
+ ke: options.ke,
+ id: options.id,
+ }).then((response) => {
moveLock[options.ke + options.id] = false
})
- }
- }catch(err){
- console.log(err)
- console.log(new Error())
+ break;
+ default:
+ try{
+ moveLock[options.ke + options.id] = true
+ startMoveOnvif(options).then((moveResponse) => {
+ if(!moveResponse.ok){
+ s.debugLog('ONVIF Move Error',moveResponse)
+ }
+ resolve(moveResponse)
+ })
+ }catch(err){
+ console.log(err)
+ console.log(new Error())
+ }
+ break;
}
- break;
+ })
+ }
+ async function startMove(options){
+ const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
+ const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
+ if(controlUrlMethod === 'ONVIF'){
+ return await moveOnvifCamera(options,true);
+ }else{
+ return await moveGeneric(options,true);
}
}
- const ptzControl = async function(options,callback){
+ async function stopMove(options){
+ const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
+ const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
+ if(controlUrlMethod === 'ONVIF'){
+ return await moveOnvifCamera(options,false);
+ }else{
+ return await moveGeneric(options,false);
+ }
+ }
+ async function ptzControl(options,callback){
if(!s.group[options.ke] || !s.group[options.ke].activeMonitors[options.id]){return}
const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
- const controlBaseUrl = monitorConfig.details.control_base_url || s.buildMonitorUrl(monitorConfig, true)
+ const controlUrlStopTimeout = options.moveTimeout || parseInt(monitorConfig.details.control_url_stop_timeout) || 1000
+ const stopCommandEnabled = monitorConfig.details.control_stop === '1' || monitorConfig.details.control_stop === '2';
if(monitorConfig.details.control !== "1"){
- s.userLog(monitorConfig,{type:lang['Control Error'],msg:lang.ControlErrorText1});
- return
+ s.userLog(monitorConfig,{
+ type: lang['Control Error'],
+ msg: lang.ControlErrorText1
+ });
+ return {
+ ok: false,
+ msg: lang.ControlErrorText1
+ }
}
- if(monitorConfig.details.control_url_stop_timeout === '0' && monitorConfig.details.control_stop === '1' && s.group[options.ke].activeMonitors[options.id].ptzMoving === true){
- options.direction = 'stopMove'
- s.group[options.ke].activeMonitors[options.id].ptzMoving = false
- }else{
- s.group[options.ke].activeMonitors[options.id].ptzMoving = true
+ let response = {
+ direction: options.direction,
}
if(controlUrlMethod === 'ONVIF'){
- try{
- //create onvif connection
- if(
- !s.group[options.ke].activeMonitors[options.id].onvifConnection ||
- !s.group[options.ke].activeMonitors[options.id].onvifConnection.current_profile ||
- !s.group[options.ke].activeMonitors[options.id].onvifConnection.current_profile.token
- ){
- const response = await s.createOnvifDevice({
- ke: options.ke,
- id: options.id,
- })
- if(response.ok){
- moveOnvifCamera({
- ke: options.ke,
- id: options.id,
- direction: options.direction,
- axis: options.axis,
- },(msg) => {
- msg.msg = options.direction
- callback(msg)
- })
- }else{
- s.userLog(options,{type:lang['Control Error'],msg:response.error})
- }
+ if(options.direction === 'center'){
+ response.moveResponse = await moveOnvifCamera(options,true)
+ }else if(stopCommandEnabled){
+ response.moveResponse = await moveOnvifCamera(options,true)
+ if(options.direction !== 'stopMove' && options.direction !== 'center'){
+ await asyncSetTimeout(controlUrlStopTimeout)
+ response.stopMoveResponse = await moveOnvifCamera(options,false)
+ response.ok = response.moveResponse.ok && response.stopMoveResponse.ok;
}else{
- moveOnvifCamera({
- ke: options.ke,
- id: options.id,
- direction: options.direction,
- axis: options.axis,
- },(msg) => {
- if(!msg.msg)msg.msg = {direction: options.direction}
- callback(msg)
- })
+ response.ok = response.moveResponse.ok;
}
- }catch(err){
- s.debugLog(err)
- callback({
- type: lang['Control Error'],
- msg: {
- msg: lang.ControlErrorText2,
- error: err,
- direction: options.direction
- }
- })
+ }else{
+ response = await relativeMoveOnvif(options);
}
}else{
- const controlUrlStopTimeout = parseInt(monitorConfig.details.control_url_stop_timeout) || 1000
- var stopCamera = function(){
- let stopURL = controlBaseUrl + monitorConfig.details[`control_url_${options.direction}_stop`]
- let controlOptions = s.cameraControlOptionsFromUrl(stopURL,monitorConfig)
- let requestOptions = {
- url : controlBaseUrl + controlOptions.path,
- method : controlOptions.method
- }
- if(controlOptions.username && controlOptions.password){
- requestOptions.auth = {
- user: controlOptions.username,
- pass: controlOptions.password
- }
- }
- if(controlOptions.postData){
- requestOptions.form = controlOptions.postData
- }
- if(monitorConfig.details.control_digest_auth === '1'){
- requestOptions.uri = sliceUrlAuth(requestOptions.url);
- delete requestOptions.url;
- requestOptions.auth.sendImmediately = false;
- }
- request(requestOptions,function(err,data){
- const msg = {
- ok: true,
- type:'Control Trigger Ended'
- }
- if(err){
- msg.ok = false
- msg.type = 'Control Error'
- msg.msg = err
- }
- moveLock[options.ke + options.id] = false
- callback(msg)
- s.userLog(monitorConfig,msg);
- })
- }
if(options.direction === 'stopMove'){
- stopCamera()
+ response = await moveGeneric(options,false)
}else{
- let controlURL = controlBaseUrl + monitorConfig.details[`control_url_${options.direction}`]
- let controlOptions = s.cameraControlOptionsFromUrl(controlURL,monitorConfig)
- let requestOptions = {
- url: controlBaseUrl + controlOptions.path,
- method: controlOptions.method
+ // left, right, up, down, center
+ response.moveResponse = await moveGeneric(options,true)
+ if(stopCommandEnabled){
+ await asyncSetTimeout(controlUrlStopTimeout)
+ response.stopMoveResponse = await moveGeneric(options,false)
+ response.ok = response.moveResponse.ok && response.stopMoveResponse.ok;
+ }else{
+ response.ok = response.moveResponse.ok;
}
- if(controlOptions.username && controlOptions.password){
- requestOptions.auth = {
- user: controlOptions.username,
- pass: controlOptions.password
- }
- }
- if(controlOptions.postData){
- requestOptions.form = controlOptions.postData
- }
- if(monitorConfig.details.control_digest_auth === '1'){
- requestOptions.uri = sliceUrlAuth(requestOptions.url);
- delete requestOptions.url;
- requestOptions.auth.sendImmediately = false;
- }
- moveLock[options.ke + options.id] = true
- request(requestOptions,function(err,data){
- if(err){
- callback({ok:false,type:'Control Error',msg:err})
- return
- }
- if(monitorConfig.details.control_stop == '1' && options.direction !== 'center' ){
- s.userLog(monitorConfig,{type:'Control Triggered Started'});
- if(controlUrlStopTimeout > 0){
- setTimeout(function(){
- stopCamera()
- },controlUrlStopTimeout)
- }
- }else{
- moveLock[options.ke + options.id] = false
- callback({ok:true,type:'Control Triggered'})
- }
- })
}
}
+ if(callback)callback(response);
+ return response;
}
const getPresetPositions = (options,callback) => {
const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
@@ -313,7 +312,6 @@ module.exports = function(s,config,lang){
const setPresetForCurrentPosition = (options,callback) => {
const nonStandardOnvif = s.group[options.ke].rawMonitorConfigurations[options.id].details.onvif_non_standard === '1'
const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
- console.log(options.PresetToken)
s.runOnvifMethod({
auth: {
ke: options.ke,
@@ -380,7 +378,6 @@ module.exports = function(s,config,lang){
const imageCenterY = imgHeight / 2
const matrices = event.details.matrices || []
const largestMatrix = getLargestMatrix(matrices.filter(matrix => trackingTarget.indexOf(matrix.tag) > -1))
- // console.log(matrices.find(matrix => matrix.tag === 'person'))
if(!largestMatrix)return;
const monitorConfig = s.group[event.ke].rawMonitorConfigurations[event.id]
const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
@@ -389,6 +386,10 @@ module.exports = function(s,config,lang){
const matrixCenterY = largestMatrix.y + (largestMatrix.height / 2)
const rawDistanceX = (matrixCenterX - imageCenterX)
const rawDistanceY = (matrixCenterY - imageCenterY)
+ const percentX = parseFloat((rawDistanceX / imgWidth).toFixed(2));
+ const percentY = parseFloat((rawDistanceY / imgHeight).toFixed(2));
+ const turnSpeedX = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
+ const turnSpeedY = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
const distanceX = imgWidth / rawDistanceX
const distanceY = imgHeight / rawDistanceY
const axisX = rawDistanceX > thresholdX || rawDistanceX < -thresholdX ? distanceX : 0
@@ -396,11 +397,11 @@ module.exports = function(s,config,lang){
if(axisX !== 0 || axisY !== 0){
ptzControl({
axis: [
- {direction: 'x', amount: axisX === 0 ? 0 : axisX > 0 ? turnSpeed : -turnSpeed},
- {direction: 'y', amount: axisY === 0 ? 0 : axisY > 0 ? invertedVerticalAxis ? -turnSpeed : turnSpeed : invertedVerticalAxis ? turnSpeed : -turnSpeed},
+ {direction: 'x', amount: axisX === 0 ? 0 : axisX > 0 ? turnSpeedX : -turnSpeedX},
+ {direction: 'y', amount: axisY === 0 ? 0 : axisY > 0 ? invertedVerticalAxis ? -turnSpeedY : turnSpeedY : invertedVerticalAxis ? turnSpeedY : -turnSpeedY},
{direction: 'z', amount: 0},
],
- // axis: [{direction: 'x', amount: 1.0}],
+ moveTimeout: 500,
id: event.id,
ke: event.ke
},(msg) => {
@@ -413,12 +414,12 @@ module.exports = function(s,config,lang){
}
}
return {
- ptzControl: ptzControl,
- startMove: startMove,
- stopMove: stopMove,
- getPresetPositions: getPresetPositions,
- setPresetForCurrentPosition: setPresetForCurrentPosition,
- moveToPresetPosition: moveToPresetPosition,
- moveCameraPtzToMatrix: moveCameraPtzToMatrix
+ startMove,
+ stopMove,
+ ptzControl,
+ getPresetPositions,
+ setPresetForCurrentPosition,
+ moveToPresetPosition,
+ moveCameraPtzToMatrix,
}
}
diff --git a/libs/control/zwave.js b/libs/control/zwave.js
new file mode 100644
index 00000000..9a84b5c5
--- /dev/null
+++ b/libs/control/zwave.js
@@ -0,0 +1,134 @@
+const zWaveAPI = require('shinobi-zwave')
+module.exports = async (s,config,lang,app,io) => {
+ const addCredentialsToHostLink = (url,username,password) => {
+ if(url.indexOf('@') > -1){
+ return url
+ }else if(username){
+ const urlParts = url.split('://')
+ return [urlParts[0],'://',`${username}:${password || ''}@`,urlParts[1]].join('')
+ }else{
+ return url
+ }
+ }
+ function loadApplicationForGroup(user){
+ const userDetails = s.parseJSON(user.details);
+ s.debugLog('Z-Wave','Loading API',user.ke)
+ if(
+ !s.group[user.ke].zwave &&
+ userDetails.zwave === '1' &&
+ userDetails.zwave_host
+ ){
+ const zWaveHost = addCredentialsToHostLink(
+ userDetails.zwave_host || config.zWaveHost,
+ userDetails.zwave_user,
+ userDetails.zwave_pass
+ ).replace(/\/$/, '');
+ s.group[user.ke].zwave = zWaveAPI(zWaveHost,config.debugLogZwave || false)
+ s.debugLog('Z-Wave','Loaded API',zWaveHost)
+ }
+ }
+ function unloadApplicationForGroup(user){
+ s.group[user.ke].zwave = null
+ }
+ /**
+ * API : Z-Wave HTTP Handler
+ */
+ function httpApiHandler(req,res){
+ s.auth(req.params,async (user) => {
+ const endData = {ok: true}
+ const actionFunction = req.params.action
+ const arguments = s.getPostData(req) || []
+ try{
+ const groupKey = req.params.ke
+ endData.response = await s.group[groupKey].zwave[actionFunction](...arguments)
+ }catch(err){
+ endData.ok = false
+ endData.err = err
+ s.debugLog(err)
+ }
+ s.closeJsonResponse(res,endData)
+ },res,req);
+ }
+ app.get(config.webPaths.apiPrefix+':auth/zwave/:ke/:action',httpApiHandler)
+ app.post(config.webPaths.apiPrefix+':auth/zwave/:ke/:action',httpApiHandler)
+ app.get(config.webPaths.apiPrefix+':auth/zwaveRaw/:ke',(req,res) => {
+ s.auth(req.params,async (user) => {
+ const groupKey = req.params.ke
+ const pathString = s.getPostData(req,'path')
+ if(!pathString){
+ res.end(lang['Not Found'])
+ return
+ }
+ s.group[groupKey].zwave.httpRequest(pathString).pipe(res)
+ },res,req);
+ })
+ s.definitions["Account Settings"].blocks["Z-Wave"] = {
+ "evaluation": "$user.details.use_zwave !== '0'",
+ "name": lang['Z-Wave'],
+ "id":"accSectionZwave",
+ "isSection": true,
+ "color": "blue",
+ "info": [
+ {
+ "name": "detail=zwave",
+ "selector":"u_zwave_bot",
+ "field": lang.Enabled,
+ "default": "0",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ hidden: true,
+ "name": "detail=zwave_host",
+ "placeholder": "https://localhost:8083",
+ "field": lang.Host,
+ "form-group-class":"u_zwave_bot_input u_zwave_bot_1",
+ },
+ {
+ hidden: true,
+ "name": "detail=zwave_user",
+ "placeholder": lang["Username"],
+ "field": lang["Username"],
+ "form-group-class":"u_zwave_bot_input u_zwave_bot_1",
+ },
+ {
+ hidden: true,
+ "name": "detail=zwave_pass",
+ "placeholder": lang["Password"],
+ "fieldType": "password",
+ "field": lang["Password"],
+ "form-group-class":"u_zwave_bot_input u_zwave_bot_1",
+ }
+ ]
+ }
+ s.definitions["Z-Wave Manager"] = {
+ "name": lang["Z-Wave Manager"],
+ blocks: {
+ "Container1": {
+ "evaluation": "$user.details.use_zwave !== '0'",
+ noHeader: true,
+ noDefaultSectionClasses: true,
+ "color": "green",
+ "section-pre-class": "col-md-8 search-parent",
+ "info": [
+ {
+ "id": "zwaveDevices",
+ "fieldType": "div",
+ },
+ ]
+ }
+ }
+ }
+ s.loadGroupAppExtender(loadApplicationForGroup)
+ s.unloadGroupAppExtender(unloadApplicationForGroup)
+}
diff --git a/libs/cron.js b/libs/cron.js
new file mode 100644
index 00000000..b6b60b4a
--- /dev/null
+++ b/libs/cron.js
@@ -0,0 +1,63 @@
+const { Worker } = require('worker_threads');
+const moment = require('moment');
+module.exports = (s,config,lang) => {
+ const {
+ legacyFilterEvents
+ } = require('./events/utils.js')(s,config,lang)
+ if(config.doCronAsWorker===undefined)config.doCronAsWorker = true;
+ const startWorker = () => {
+ const pathToWorkerScript = __dirname + `/cron/worker.js`
+ const workerProcess = new Worker(pathToWorkerScript)
+ workerProcess.on('message',function(data){
+ if(data.time === 'moment()')data.time = moment();
+ switch(data.f){
+ case'debugLog':
+ s.debugLog(...data.data)
+ break;
+ case'systemLog':
+ s.systemLog(...data.data)
+ break;
+ case'filters':
+ legacyFilterEvents(data.ff,data)
+ break;
+ case's.tx':
+ s.tx(data.data,data.to)
+ break;
+ case's.deleteVideo':
+ s.deleteVideo(data.file)
+ break;
+ case's.deleteFileBinEntry':
+ s.deleteFileBinEntry(data.file)
+ break;
+ case's.setDiskUsedForGroup':
+ function doOnMain(){
+ s.setDiskUsedForGroup(data.ke,data.size,data.target || undefined)
+ }
+ if(data.videoRow){
+ let storageIndex = s.getVideoStorageIndex(data.videoRow);
+ if(storageIndex){
+ s.setDiskUsedForGroupAddStorage(data.ke,{
+ size: data.size,
+ storageIndex: storageIndex
+ })
+ }else{
+ doOnMain()
+ }
+ }else{
+ doOnMain()
+ }
+ break;
+ default:
+ s.systemLog('CRON.js MESSAGE : ',data)
+ break;
+ }
+ })
+ setTimeout(() => {
+ workerProcess.postMessage({
+ f: 'init',
+ })
+ },2000)
+ return workerProcess
+ }
+ if(config.doCronAsWorker === true)startWorker()
+}
diff --git a/libs/cron/worker.js b/libs/cron/worker.js
new file mode 100644
index 00000000..e7a08a58
--- /dev/null
+++ b/libs/cron/worker.js
@@ -0,0 +1,612 @@
+const fs = require('fs');
+const path = require('path');
+const moment = require('moment');
+const exec = require('child_process').exec;
+const spawn = require('child_process').spawn;
+const { parentPort, isMainThread } = require('worker_threads');
+const config = require(process.cwd() + '/conf.json')
+process.on('uncaughtException', function (err) {
+ errorLog('uncaughtException',err);
+});
+if(isMainThread){
+ console.log(`Shinobi now runs cron.js as child process.`)
+ console.error(`Shinobi now runs cron.js as child process.`)
+ setInterval(() => {
+ // console.log(`Please turn off cron.js process.`)
+ },1000 * 60 * 60 * 24 * 7)
+ return;
+}
+function setDefaultConfigOptions(){
+ if(config.cron===undefined)config.cron={};
+ if(config.cron.deleteOld===undefined)config.cron.deleteOld=true;
+ if(config.cron.deleteOrphans===undefined)config.cron.deleteOrphans=false;
+ if(config.cron.deleteNoVideo===undefined)config.cron.deleteNoVideo=true;
+ if(config.cron.deleteNoVideoRecursion===undefined)config.cron.deleteNoVideoRecursion=false;
+ if(config.cron.deleteOverMax===undefined)config.cron.deleteOverMax=true;
+ if(config.cron.deleteLogs===undefined)config.cron.deleteLogs=true;
+ if(config.cron.deleteTimelpaseFrames===undefined)config.cron.deleteTimelpaseFrames=true;
+ if(config.cron.deleteEvents===undefined)config.cron.deleteEvents=true;
+ if(config.cron.deleteFileBins===undefined)config.cron.deleteFileBins=true;
+ if(config.cron.interval===undefined)config.cron.interval=1;
+ if(config.databaseType===undefined){config.databaseType='mysql'}
+ if(config.databaseLogs===undefined){config.databaseLogs=false}
+ if(config.debugLog===undefined){config.debugLog=false}
+
+ if(!config.ip||config.ip===''||config.ip.indexOf('0.0.0.0')>-1)config.ip='localhost';
+ if(!config.videosDir)config.videosDir = process.cwd() + '/videos/';
+ if(!config.binDir){config.binDir = process.cwd() + '/fileBin/'}
+}
+parentPort.on('message',(data) => {
+ switch(data.f){
+ case'init':
+ setDefaultConfigOptions()
+ beginProcessing()
+ break;
+ case'start':case'restart':
+ setIntervalForCron()
+ break;
+ case'stop':
+ clearCronInterval()
+ break;
+ }
+})
+function debugLog(...args){
+ if(config.debugLog === true){
+ console.log(...([`CRON.js DEBUG LOG ${new Date()}`].concat(args)))
+ }
+}
+function normalLog(...args){
+ console.log(...([`CRON.js LOG ${new Date()}`].concat(args)))
+}
+function errorLog(...args){
+ console.error(...([`CRON.js ERROR LOG ${new Date()}`].concat(args)))
+}
+const s = {
+ debugLog,
+}
+function beginProcessing(){
+ normalLog(`Worker Processing!`)
+ const {
+ checkCorrectPathEnding,
+ generateRandomId,
+ formattedTime,
+ localToUtc,
+ } = require('../basic/utils.js')(process.cwd())
+ const {
+ sqlDate,
+ knexQuery,
+ knexQueryPromise,
+ initiateDatabaseEngine
+ } = require('../sql/utils.js')(s,config)
+ var theCronInterval = null
+ const overlapLocks = {}
+ const alreadyDeletedRowsWithNoVideosOnStart = {}
+ const videoDirectory = checkCorrectPathEnding(config.videosDir)
+ const fileBinDirectory = checkCorrectPathEnding(config.binDir)
+ const postMessage = (data) => {
+ parentPort.postMessage(data)
+ }
+ const sendToWebSocket = (x,y) => {
+ //emulate master socket emitter
+ postMessage({f:'s.tx',data:x,to:y})
+ }
+ const deleteVideo = (x) => {
+ postMessage({f:'s.deleteVideo',file:x})
+ }
+ const deleteFileBinEntry = (x) => {
+ postMessage({f:'s.deleteFileBinEntry',file:x})
+ }
+ const setDiskUsedForGroup = (groupKey,size,target,videoRow) => {
+ postMessage({f:'s.setDiskUsedForGroup', ke: groupKey, size: size, target: target, videoRow: videoRow})
+ }
+ const getVideoDirectory = function(e){
+ if(e.mid&&!e.id){e.id=e.mid};
+ if(e.details&&(e.details instanceof Object)===false){
+ try{e.details=JSON.parse(e.details)}catch(err){}
+ }
+ if(e.details.dir&&e.details.dir!==''){
+ return checkCorrectPathEnding(e.details.dir)+e.ke+'/'+e.id+'/'
+ }else{
+ return videoDirectory + e.ke + '/' + e.id + '/'
+ }
+ }
+ const getTimelapseFrameDirectory = function(e){
+ if(e.mid&&!e.id){e.id=e.mid}
+ if(e.details&&(e.details instanceof Object)===false){
+ try{e.details=JSON.parse(e.details)}catch(err){}
+ }
+ if(e.details&&e.details.dir&&e.details.dir!==''){
+ return checkCorrectPathEnding(e.details.dir)+e.ke+'/'+e.id+'_timelapse/'
+ }else{
+ return videoDirectory + e.ke + '/' + e.id + '_timelapse/'
+ }
+ }
+ const getFileBinDirectory = function(e){
+ if(e.mid && !e.id){e.id = e.mid}
+ return fileBinDirectory + e.ke + '/' + e.id + '/'
+ }
+ //filters set by the user in their dashboard
+ //deleting old videos is part of the filter - config.cron.deleteOld
+ const checkFilterRules = function(v){
+ return new Promise((resolve,reject) => {
+ //filters
+ v.d.filters = v.d.filters ? v.d.filters : {}
+ debugLog('Checking Basic Filters...')
+ var keys = Object.keys(v.d.filters)
+ if(keys.length>0){
+ keys.forEach(function(m,current){
+ // b = filter
+ var b = v.d.filters[m];
+ debugLog(b)
+ if(b.enabled==="1"){
+ const whereQuery = [
+ ['ke','=',v.ke],
+ ['status','!=',"0"],
+ ['archive','!=',`1`],
+ ]
+ b.where.forEach(function(condition){
+ if(condition.p1 === 'ke'){condition.p3 = v.ke}
+ whereQuery.push([condition.p1,condition.p2 || '=',condition.p3])
+ })
+ knexQuery({
+ action: "select",
+ columns: "*",
+ table: "Videos",
+ where: whereQuery,
+ orderBy: [b.sort_by,b.sort_by_direction.toLowerCase()],
+ limit: b.limit
+ },(err,r) => {
+ if(r && r[0]){
+ if(r.length > 0 || config.debugLog === true){
+ postMessage({f:'filterMatch',msg:r.length+' SQL rows match "'+m+'"',ke:v.ke,time:'moment()'})
+ }
+ b.cx={
+ f:'filters',
+ name:b.name,
+ videos:r,
+ time:'moment()',
+ ke:v.ke,
+ id:b.id
+ };
+ if(b.archive==="1"){
+ postMessage({f:'filters',ff:'archive',videos:r,time:'moment()',ke:v.ke,id:b.id});
+ }else if(b.delete==="1"){
+ postMessage({f:'filters',ff:'delete',videos:r,time:'moment()',ke:v.ke,id:b.id});
+ }
+ if(b.email==="1"){
+ b.cx.ff='email';
+ b.cx.delete=b.delete;
+ b.cx.mail=v.mail;
+ b.cx.execute=b.execute;
+ b.cx.query=b.sql;
+ postMessage(b.cx);
+ }
+ if(b.execute&&b.execute!==""){
+ postMessage({f:'filters',ff:'execute',execute:b.execute,time:'moment()'});
+ }
+ }
+ })
+
+ }
+ if(current===keys.length-1){
+ //last filter
+ resolve()
+ }
+ })
+ }else{
+ //no filters
+ resolve()
+ }
+ })
+ }
+ const deleteVideosByDays = async (v,days,addedQueries) => {
+ const groupKey = v.ke;
+ const whereQuery = [
+ ['ke','=',v.ke],
+ ['archive','!=',`1`],
+ ['time','<', sqlDate(days+' DAY')],
+ addedQueries
+ ]
+ const selectResponse = await knexQueryPromise({
+ action: "select",
+ columns: "*",
+ table: "Videos",
+ where: whereQuery
+ })
+ const videoRows = selectResponse.rows
+ let affectedRows = 0
+ if(videoRows.length > 0){
+ let clearSize = 0;
+ var i;
+ for (i = 0; i < videoRows.length; i++) {
+ const row = videoRows[i];
+ const dir = getVideoDirectory(row)
+ const filename = formattedTime(row.time) + '.' + row.ext
+ try{
+ await fs.promises.unlink(dir + filename)
+ const fileSizeMB = row.size / 1048576;
+ setDiskUsedForGroup(groupKey,-fileSizeMB,null,row)
+ sendToWebSocket({
+ f: 'video_delete',
+ filename: filename + '.' + row.ext,
+ mid: row.mid,
+ ke: row.ke,
+ time: row.time,
+ end: formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
+ },'GRP_' + row.ke)
+ }catch(err){
+ normalLog('Video Delete Error',row)
+ normalLog(err)
+ }
+ }
+ const deleteResponse = await knexQueryPromise({
+ action: "delete",
+ table: "Videos",
+ where: whereQuery
+ })
+ affectedRows = deleteResponse.rows || 0
+ }
+ return {
+ ok: true,
+ affectedRows: affectedRows,
+ }
+ }
+ const deleteOldVideos = async (v) => {
+ // v = group, admin user
+ if(config.cron.deleteOld === true){
+ const daysOldForDeletion = v.d.days && !isNaN(v.d.days) ? parseFloat(v.d.days) : 5
+ const monitorsIgnored = []
+ const monitorsResponse = await knexQueryPromise({
+ action: "select",
+ columns: "*",
+ table: "Monitors",
+ where: [
+ ['ke','=',v.ke],
+ ]
+ })
+ const monitorRows = monitorsResponse.rows
+ var i;
+ for (i = 0; i < monitorRows.length; i++) {
+ const monitor = monitorRows[i]
+ const monitorId = monitor.mid
+ const details = JSON.parse(monitor.details);
+ const monitorsMaxDaysToKeep = !isNaN(details.max_keep_days) ? parseFloat(details.max_keep_days) : null
+ if(monitorsMaxDaysToKeep){
+ const { affectedRows } = await deleteVideosByDays(v,monitorsMaxDaysToKeep,['mid','=',monitorId])
+ const hasDeletedRows = affectedRows && affectedRows.length > 0;
+ if(hasDeletedRows || config.debugLog === true){
+ postMessage({
+ f: 'deleteOldVideosByMonitorId',
+ msg: `${affectedRows} SQL rows older than ${monitorsMaxDaysToKeep} days deleted`,
+ ke: v.ke,
+ mid: monitorId,
+ time: 'moment()',
+ })
+ }
+ monitorsIgnored.push(['mid','!=',monitorId])
+ }
+ }
+ const { affectedRows } = await deleteVideosByDays(v,daysOldForDeletion,monitorsIgnored)
+ const hasDeletedRows = affectedRows && affectedRows.length > 0;
+ if(hasDeletedRows || config.debugLog === true){
+ postMessage({
+ f: 'deleteOldVideos',
+ msg: `${affectedRows} SQL rows older than ${daysOldForDeletion} days deleted`,
+ ke: v.ke,
+ time: 'moment()',
+ })
+ }
+ }
+ }
+ //database rows with no videos in the filesystem
+ const deleteRowsWithNoVideo = function(v){
+ return new Promise((resolve,reject) => {
+ if(
+ config.cron.deleteNoVideo===true&&(
+ config.cron.deleteNoVideoRecursion===true||
+ (config.cron.deleteNoVideoRecursion===false&&!alreadyDeletedRowsWithNoVideosOnStart[v.ke])
+ )
+ ){
+ alreadyDeletedRowsWithNoVideosOnStart[v.ke]=true;
+ knexQuery({
+ action: "select",
+ columns: "*",
+ table: "Videos",
+ where: [
+ ['ke','=',v.ke],
+ ['status','!=','0'],
+ ['archive','!=',`1`],
+ ['time','<', sqlDate('10 MINUTE')],
+ ]
+ },(err,evs) => {
+ if(evs && evs[0]){
+ const videosToDelete = [];
+ evs.forEach(function(ev){
+ var filename
+ var details
+ try{
+ details = JSON.parse(ev.details)
+ }catch(err){
+ if(details instanceof Object){
+ details = ev.details
+ }else{
+ details = {}
+ }
+ }
+ var dir = getVideoDirectory(ev)
+ filename = formattedTime(ev.time)+'.'+ev.ext
+ fileExists = fs.existsSync(dir+filename)
+ if(fileExists !== true){
+ deleteVideo(ev)
+ sendToWebSocket({f:'video_delete',filename:filename+'.'+ev.ext,mid:ev.mid,ke:ev.ke,time:ev.time,end: formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')},'GRP_'+ev.ke);
+ }
+ });
+ if(videosToDelete.length > 0 || config.debugLog === true){
+ postMessage({f:'deleteNoVideo',msg:videosToDelete.length+' SQL rows with no file deleted',ke:v.ke,time:'moment()'})
+ }
+ }
+ setTimeout(function(){
+ resolve()
+ },3000)
+ })
+ }else{
+ resolve()
+ }
+ })
+ }
+ //info about what the application is doing
+ const deleteOldLogs = function(v){
+ return new Promise((resolve,reject) => {
+ const daysOldForDeletion = v.d.log_days && !isNaN(v.d.log_days) ? parseFloat(v.d.log_days) : 10
+ if(config.cron.deleteLogs === true && daysOldForDeletion !== 0){
+ knexQuery({
+ action: "delete",
+ table: "Logs",
+ where: [
+ ['ke','=',v.ke],
+ ['time','<', sqlDate(daysOldForDeletion + ' DAY')],
+ ]
+ },(err,rrr) => {
+ resolve()
+ if(err)return errorLog(err);
+ if(rrr && rrr > 0 || config.debugLog === true){
+ postMessage({f:'deleteLogs',msg: rrr + ' SQL rows older than ' + daysOldForDeletion + ' days deleted',ke:v.ke,time:'moment()'})
+ }
+ })
+ }else{
+ resolve()
+ }
+ })
+ }
+ //still images saved
+ const deleteOldTimelapseFrames = async function(v){
+ const daysOldForDeletion = v.d.timelapseFrames_days && !isNaN(v.d.timelapseFrames_days) ? parseFloat(v.d.timelapseFrames_days) : 60
+ if(config.cron.deleteTimelpaseFrames === true && daysOldForDeletion !== 0){
+ const groupKey = v.ke;
+ const whereQuery = [
+ ['ke','=',v.ke],
+ ['archive','!=',`1`],
+ ['time','<', sqlDate(daysOldForDeletion+' DAY')],
+ ]
+ const selectResponse = await knexQueryPromise({
+ action: "select",
+ columns: "*",
+ table: "Timelapse Frames",
+ where: whereQuery
+ })
+ const videoRows = selectResponse.rows
+ let affectedRows = 0
+ if(videoRows.length > 0){
+ const foldersDeletedFrom = [];
+ let clearSize = 0;
+ var i;
+ for (i = 0; i < videoRows.length; i++) {
+ const row = videoRows[i];
+ const dir = getTimelapseFrameDirectory(row)
+ const filename = row.filename
+ const theDate = filename.split('T')[0]
+ const enclosingFolder = `${dir}/${theDate}/`
+ try{
+ const fileSizeMB = row.size / 1048576;
+ setDiskUsedForGroup(groupKey,-fileSizeMB,null,row)
+ sendToWebSocket({
+ f: 'timelapse_frame_delete',
+ filename: filename,
+ mid: row.mid,
+ ke: groupKey,
+ time: row.time,
+ details: row.details,
+ },'GRP_' + groupKey)
+ await fs.promises.unlink(`${enclosingFolder}${filename}`)
+ if(foldersDeletedFrom.indexOf(enclosingFolder) === -1)foldersDeletedFrom.push(enclosingFolder);
+ }catch(err){
+ normalLog('Timelapse Frame Delete Error',row)
+ normalLog(err)
+ }
+ }
+ for (i = 0; i < foldersDeletedFrom.length; i++) {
+ const folderPath = foldersDeletedFrom[i];
+ const folderIsEmpty = (await fs.promises.readdir(folderPath)).filter(file => file.indexOf('.jpg') > -1).length === 0;
+ if(folderIsEmpty){
+ await fs.promises.rm(folderPath, { recursive: true, force: true })
+ }
+ }
+ const deleteResponse = await knexQueryPromise({
+ action: "delete",
+ table: "Timelapse Frames",
+ where: whereQuery
+ })
+ affectedRows = deleteResponse.rows || 0
+ }
+ return {
+ ok: true,
+ affectedRows: affectedRows,
+ }
+ }
+ return {
+ ok: false
+ }
+ }
+ //events - motion, object, etc. detections
+ const deleteOldEvents = function(v){
+ return new Promise((resolve,reject) => {
+ const daysOldForDeletion = v.d.event_days && !isNaN(v.d.event_days) ? parseFloat(v.d.event_days) : 10
+ if(config.cron.deleteEvents === true && daysOldForDeletion !== 0){
+ knexQuery({
+ action: "delete",
+ table: "Events",
+ where: [
+ ['ke','=',v.ke],
+ ['archive','!=',`1`],
+ ['time','<', sqlDate(daysOldForDeletion + ' DAY')],
+ ]
+ },(err,rrr) => {
+ resolve()
+ if(err)return errorLog(err);
+ if(rrr && rrr > 0 || config.debugLog === true){
+ postMessage({f:'deleteEvents',msg:rrr + ' SQL rows older than ' + daysOldForDeletion + ' days deleted',ke:v.ke,time:'moment()'})
+ }
+ })
+ }else{
+ resolve()
+ }
+ })
+ }
+ //event counts
+ const deleteOldEventCounts = function(v){
+ return new Promise((resolve,reject) => {
+ const daysOldForDeletion = v.d.event_days && !isNaN(v.d.event_days) ? parseFloat(v.d.event_days) : 10
+ if(config.cron.deleteEvents === true && daysOldForDeletion !== 0){
+ knexQuery({
+ action: "delete",
+ table: "Events Counts",
+ where: [
+ ['ke','=',v.ke],
+ ['time','<', sqlDate(daysOldForDeletion + ' DAY')],
+ ]
+ },(err,rrr) => {
+ resolve()
+ if(err && err.code !== 'ER_NO_SUCH_TABLE')return errorLog(err);
+ if(rrr && rrr > 0 || config.debugLog === true){
+ postMessage({f:'deleteEvents',msg:rrr + ' SQL rows older than ' + daysOldForDeletion + ' days deleted',ke:v.ke,time:'moment()'})
+ }
+ })
+ }else{
+ resolve()
+ }
+ })
+ }
+ //check for temporary files (special archive)
+ const deleteOldFileBins = function(v){
+ return new Promise((resolve,reject) => {
+ const daysOldForDeletion = v.d.fileBin_days && !isNaN(v.d.fileBin_days) ? parseFloat(v.d.fileBin_days) : 10
+ if(config.cron.deleteFileBins === true && daysOldForDeletion !== 0){
+ var fileBinQuery = " FROM Files WHERE ke=? AND `time` < ?";
+ knexQuery({
+ action: "select",
+ columns: "*",
+ table: "Files",
+ where: [
+ ['ke','=',v.ke],
+ ['archive','!=',`1`],
+ ['time','<', sqlDate(daysOldForDeletion + ' DAY')],
+ ]
+ },(err,files) => {
+ if(files && files[0]){
+ //delete the files
+ files.forEach(function(file){
+ deleteFileBinEntry(file)
+ })
+ if(config.debugLog === true){
+ postMessage({
+ f: 'deleteFileBins',
+ msg: files.length + ' files older than ' + daysOldForDeletion + ' days deleted',
+ ke: v.ke,
+ time: 'moment()'
+ })
+ }
+ }
+ resolve()
+ })
+ }else{
+ resolve()
+ }
+ })
+ }
+ //user processing function
+ const processUser = async (v) => {
+ if(!v){
+ //no user object given, end of group list
+ return
+ }
+ debugLog(`Group Key : ${v.ke}`)
+ debugLog(`Owner : ${v.mail}`)
+ if(!overlapLocks[v.ke]){
+ debugLog(`Checking...`)
+ overlapLocks[v.ke] = true
+ v.d = JSON.parse(v.details);
+ try{
+ await deleteOldVideos(v)
+ debugLog('--- deleteOldVideos Complete')
+ await deleteOldTimelapseFrames(v)
+ debugLog('--- deleteOldTimelapseFrames Complete')
+ await deleteOldLogs(v)
+ debugLog('--- deleteOldLogs Complete')
+ await deleteOldFileBins(v)
+ debugLog('--- deleteOldFileBins Complete')
+ await deleteOldEvents(v)
+ debugLog('--- deleteOldEvents Complete')
+ await deleteOldEventCounts(v)
+ debugLog('--- deleteOldEventCounts Complete')
+ await checkFilterRules(v)
+ debugLog('--- checkFilterRules Complete')
+ await deleteRowsWithNoVideo(v)
+ debugLog('--- deleteRowsWithNoVideo Complete')
+ }catch(err){
+ normalLog(`Failed to Complete User : ${v.mail}`)
+ normalLog(err)
+ }
+ //done user, unlock current, and do next
+ overlapLocks[v.ke] = false;
+ debugLog(`Complete Checking... ${v.mail}`)
+ }else{
+ debugLog(`Locked, Skipped...`)
+ }
+ }
+ //recursive function
+ const setIntervalForCron = function(){
+ clearCronInterval()
+ // theCronInterval = setInterval(doCronJobs,1000 * 10)
+ theCronInterval = setInterval(doCronJobs,parseFloat(config.cron.interval)*60000*60)
+ }
+ const clearCronInterval = function(){
+ clearInterval(theCronInterval)
+ }
+ const doCronJobs = function(){
+ postMessage({
+ f: 'start',
+ time: 'moment()'
+ })
+ knexQuery({
+ action: "select",
+ columns: "ke,uid,details,mail",
+ table: "Users",
+ where: [
+ ['details','NOT LIKE','%"sub"%'],
+ ]
+ }, async (err,rows) => {
+ if(err){
+ errorLog(err)
+ }
+ if(rows.length > 0){
+ var i;
+ for (i = 0; i < rows.length; i++) {
+ await processUser(rows[i])
+ }
+ }
+ })
+ }
+ initiateDatabaseEngine()
+ setIntervalForCron()
+ doCronJobs()
+}
diff --git a/libs/customAutoLoad.js b/libs/customAutoLoad.js
index 5075c22c..97026eeb 100644
--- a/libs/customAutoLoad.js
+++ b/libs/customAutoLoad.js
@@ -1,10 +1,10 @@
const fs = require('fs-extra');
const express = require('express')
-const request = require('request')
const unzipper = require('unzipper')
const fetch = require("node-fetch")
const spawn = require('child_process').spawn
module.exports = async (s,config,lang,app,io) => {
+ const { fetchDownloadAndWrite } = require('./basic/utils.js')(process.cwd(),config)
s.debugLog(`+++++++++++CustomAutoLoad Modules++++++++++++`)
const runningInstallProcesses = {}
const modulesBasePath = __dirname + '/customAutoLoad/'
@@ -18,7 +18,7 @@ module.exports = async (s,config,lang,app,io) => {
}
const getModule = (moduleName) => {
s.debugLog(`+++++++++++++++++++++++`)
- s.debugLog(`Loading : ${moduleName}`)
+ s.debugLog(`Getting Module : ${moduleName}`)
const modulePath = modulesBasePath + moduleName
const stats = fs.lstatSync(modulePath)
const isDirectory = stats.isDirectory()
@@ -65,10 +65,9 @@ module.exports = async (s,config,lang,app,io) => {
fs.mkdirSync(downloadPath)
return new Promise(async (resolve, reject) => {
fs.mkdir(downloadPath, () => {
- request(downloadUrl).pipe(fs.createWriteStream(downloadPath + '.zip'))
- .on('finish',() => {
- zip = fs.createReadStream(downloadPath + '.zip')
- .pipe(unzipper.Parse())
+ fetchDownloadAndWrite(downloadUrl,downloadPath + '.zip', 1)
+ .then((readStream) => {
+ readStream.pipe(unzipper.Parse())
.on('entry', async (file) => {
if(file.type === 'Directory'){
try{
@@ -176,7 +175,9 @@ module.exports = async (s,config,lang,app,io) => {
}
}
const loadModule = (shinobiModule) => {
+ s.debugLog(`+++++++++++++++++++++++`)
const moduleName = shinobiModule.name
+ s.debugLog(`Loading Module : ${moduleName}`)
s.customAutoLoadModules[moduleName] = {}
var customModulePath = modulesBasePath + '/' + moduleName
s.debugLog(customModulePath)
@@ -208,8 +209,13 @@ module.exports = async (s,config,lang,app,io) => {
app.use('/libs',express.static(webFolder + '/libs'))
}
app.use(s.checkCorrectPathEnding(config.webPaths.home)+'libs',express.static(webFolder + '/libs'))
- app.use(s.checkCorrectPathEnding(config.webPaths.admin)+'libs',express.static(webFolder + '/libs'))
app.use(s.checkCorrectPathEnding(config.webPaths.super)+'libs',express.static(webFolder + '/libs'))
+ }else if(name === 'assets'){
+ if(config.webPaths.home !== '/'){
+ app.use('/assets',express.static(webFolder + '/assets'))
+ }
+ app.use(s.checkCorrectPathEnding(config.webPaths.home)+'assets',express.static(webFolder + '/assets'))
+ app.use(s.checkCorrectPathEnding(config.webPaths.super)+'assets',express.static(webFolder + '/assets'))
}
var libFolder = webFolder + name + '/'
fs.readdir(libFolder,function(err,webFolderContents){
@@ -225,23 +231,34 @@ module.exports = async (s,config,lang,app,io) => {
var fullPath = thirdLevelName + '/' + filename
var blockPrefix = ''
switch(true){
- case filename.contains('super.'):
+ case filename.indexOf('super.') > -1:
blockPrefix = 'super'
break;
- case filename.contains('admin.'):
- blockPrefix = 'admin'
- break;
}
- switch(libName){
- case'js':
- s.customAutoLoadTree[blockPrefix + 'LibsJs'].push(filename)
- break;
- case'css':
- s.customAutoLoadTree[blockPrefix + 'LibsCss'].push(filename)
- break;
- case'blocks':
- s.customAutoLoadTree[blockPrefix + 'PageBlocks'].push(fullPath)
- break;
+ if(name === 'libs'){
+ switch(libName){
+ case'js':
+ s.customAutoLoadTree[blockPrefix + 'LibsJs'].push(filename)
+ break;
+ case'css':
+ s.customAutoLoadTree[blockPrefix + 'LibsCss'].push(filename)
+ break;
+ case'blocks':
+ s.customAutoLoadTree[blockPrefix + 'PageBlocks'].push(fullPath)
+ break;
+ }
+ }else if(name === 'assets'){
+ switch(libName){
+ case'js':
+ s.customAutoLoadTree[blockPrefix + 'AssetsJs'].push(filename)
+ break;
+ case'css':
+ s.customAutoLoadTree[blockPrefix + 'AssetsCss'].push(filename)
+ break;
+ case'blocks':
+ s.customAutoLoadTree[blockPrefix + 'PageBlocks'].push(fullPath)
+ break;
+ }
}
})
})
@@ -278,22 +295,24 @@ module.exports = async (s,config,lang,app,io) => {
})
break;
case'definitions':
- var definitionsFolder = s.checkCorrectPathEnding(customModulePath) + 'definitions/'
- fs.readdir(definitionsFolder,function(err,files){
- if(err)return console.log(err);
- files.forEach(function(filename){
- var fileData = require(definitionsFolder + filename)
- var rule = filename.replace('.json','').replace('.js','')
- if(config.language === rule){
- s.definitions = s.mergeDeep(s.definitions,fileData)
- }
- if(s.loadedDefinitons[rule]){
- s.loadedDefinitons[rule] = s.mergeDeep(s.loadedDefinitons[rule],fileData)
- }else{
- s.loadedDefinitons[rule] = s.mergeDeep(s.copySystemDefaultDefinitions(),fileData)
- }
- })
- })
+ console.error('This Method has been deprecated. Could not load : ', customModulePath + 'defintions/')
+ console.error('Make your module\'s index.js file make the changes directly.')
+ // var definitionsFolder = s.checkCorrectPathEnding(customModulePath) + 'definitions/'
+ // fs.readdir(definitionsFolder,function(err,files){
+ // if(err)return console.log(err);
+ // files.forEach(function(filename){
+ // var fileData = require(definitionsFolder + filename)
+ // var rule = filename.replace('.json','').replace('.js','')
+ // if(config.language === rule){
+ // s.definitions = s.mergeDeep(s.definitions,fileData)
+ // }
+ // if(s.loadedDefinitons[rule]){
+ // s.loadedDefinitons[rule] = s.mergeDeep(s.loadedDefinitons[rule],fileData)
+ // }else{
+ // s.loadedDefinitons[rule] = s.mergeDeep(s.copySystemDefaultDefinitions(),fileData)
+ // }
+ // })
+ // })
break;
}
})
@@ -327,13 +346,14 @@ module.exports = async (s,config,lang,app,io) => {
PageBlocks: [],
LibsJs: [],
LibsCss: [],
- adminPageBlocks: [],
- adminLibsJs: [],
- adminLibsCss: [],
+ AssetsJs: [],
+ AssetsCss: [],
superPageBlocks: [],
superLibsJs: [],
superRawJs: [],
- superLibsCss: []
+ superLibsCss: [],
+ superAssetsJs: [],
+ superAssetsCss: []
}
fs.readdir(modulesBasePath,function(err,folderContents){
if(!err && folderContents.length > 0){
@@ -342,6 +362,8 @@ module.exports = async (s,config,lang,app,io) => {
return;
}
loadModule(shinobiModule)
+ s.reloadLanguages()
+ s.reloadDefinitions()
})
}else{
fs.mkdir(modulesBasePath,() => {})
diff --git a/libs/dataPort.js b/libs/dataPort.js
new file mode 100644
index 00000000..a20c5143
--- /dev/null
+++ b/libs/dataPort.js
@@ -0,0 +1,62 @@
+const { createWebSocketServer } = require('./basic/websocketTools.js')
+module.exports = function(s,config,lang,app,io){
+ const {
+ triggerEvent,
+ } = require('./events/utils.js')(s,config,lang)
+ s.dataPortTokens = {}
+ const theWebSocket = createWebSocketServer()
+ function setClientKillTimerIfNotAuthenticatedInTime(client){
+ client.killTimer = setTimeout(function(){
+ client.terminate()
+ },10000)
+ }
+ function clearKillTimer(client){
+ clearTimeout(client.killTimer)
+ }
+ theWebSocket.on('connection',(client) => {
+ // client.send(someDataToSendAsStringOrBinary)
+ setClientKillTimerIfNotAuthenticatedInTime(client)
+ function onAuthenticate(data){
+ clearKillTimer(client)
+ if(data in s.dataPortTokens){
+ client.removeListener('message', onAuthenticate);
+ client.on('message', onAuthenticatedData)
+ delete(s.dataPortTokens[data]);
+ }else{
+ client.terminate()
+ }
+ }
+ function onAuthenticatedData(jsonData){
+ const data = JSON.parse(jsonData);
+ switch(data.f){
+ case'trigger':
+ triggerEvent(data)
+ break;
+ case's.tx':
+ s.tx(data.data,data.to)
+ break;
+ case'debugLog':
+ s.debugLog(data.data)
+ break;
+ default:
+ console.log(data)
+ break;
+ }
+ s.onDataPortMessageExtensions.forEach(function(extender){
+ extender(data)
+ })
+ }
+ client.on('message', onAuthenticate)
+ client.on('close', () => {
+ clearTimeout(client.killTimer)
+ })
+ client.on('disconnect', () => {
+ clearTimeout(client.killTimer)
+ })
+ })
+ s.onHttpRequestUpgrade('/dataPort',(request, socket, head) => {
+ theWebSocket.handleUpgrade(request, socket, head, function done(ws) {
+ theWebSocket.emit('connection', ws, request)
+ })
+ })
+}
diff --git a/libs/database/utils.js b/libs/database/utils.js
index 296e4407..8b5f91cd 100644
--- a/libs/database/utils.js
+++ b/libs/database/utils.js
@@ -87,11 +87,11 @@ module.exports = function(s,config){
const knexError = (dbQuery,options,err) => {
console.error('knexError----------------------------------- START')
if(config.debugLogVerbose && config.debugLog === true){
- s.debugLog('s.knexQuery QUERY',JSON.stringify(options,null,3))
s.debugLog('STACK TRACE, NOT AN ',new Error())
}
console.error(err)
- console.error(dbQuery.toString())
+ // CANNOT USE `dbQuery.toString()` because it breaks the query
+ console.error(options)
console.error('knexError----------------------------------- END')
}
const knexQuery = (options,callback) => {
@@ -140,7 +140,7 @@ module.exports = function(s,config){
if(options.groupBy){
dbQuery.groupBy(options.groupBy)
}
- if(options.limit){
+ if(options.limit && options.limit !== '0'){
if(`${options.limit}`.indexOf(',') === -1){
dbQuery.limit(options.limit)
}else{
@@ -149,7 +149,8 @@ module.exports = function(s,config){
}
}
if(config.debugLog === true){
- console.log(dbQuery.toString())
+ // CANNOT USE `dbQuery.toString()` because it breaks the query
+ console.log(options)
}
if(callback || options.update || options.insert || options.action === 'delete'){
dbQuery.asCallback(function(err,r) {
@@ -177,6 +178,7 @@ module.exports = function(s,config){
]
const monitorRestrictions = options.monitorRestrictions
var frameLimit = options.limit
+ const noLimit = options.noLimit === '1'
const endIsStartTo = options.endIsStartTo
const chosenDate = options.date
const startDate = options.startDate ? stringToSqlTime(options.startDate) : null
@@ -211,12 +213,13 @@ module.exports = function(s,config){
whereQuery.push(monitorRestrictions)
}
if(options.archived){
- whereQuery.push(['details','LIKE',`%"archived":"1"%`])
+ whereQuery.push(['archive','=',`1`])
}
if(options.filename){
whereQuery.push(['filename','=',options.filename])
frameLimit = "1";
}
+ if(noLimit)frameLimit = '0';
options.orderBy = options.orderBy ? options.orderBy : ['time','desc']
if(options.count)options.groupBy = options.groupBy ? options.groupBy : options.orderBy[0]
knexQuery({
@@ -328,7 +331,10 @@ module.exports = function(s,config){
var endTimeOperator = options.endTimeOperator
var startTime = options.startTime
var limitString = `${options.limit}`
- const monitorRestrictions = s.getMonitorRestrictions(options.user.details,monitorId)
+ const {
+ monitorPermissions,
+ monitorRestrictions,
+ } = s.getMonitorsPermitted(user.details,monitorId)
getDatabaseRows({
monitorRestrictions: monitorRestrictions,
table: theTableSelected,
@@ -337,7 +343,7 @@ module.exports = function(s,config){
endDate: endTime,
startOperator: startTimeOperator,
endOperator: endTimeOperator,
- limit: options.limit,
+ limit: options.noLimit === '1' ? '0' : options.limit,
archived: archived,
rowType: rowName,
endIsStartTo: endIsStartTo
diff --git a/libs/definitions.js b/libs/definitions.js
index a58a6e24..c29802e6 100644
--- a/libs/definitions.js
+++ b/libs/definitions.js
@@ -1,25 +1,19 @@
var fs = require('fs')
var express = require('express')
+const {
+ mergeDeep
+} = require('./common.js')
+const frameworkBase = require(`../definitions/base.js`)
module.exports = function(s,config,lang,app,io){
- s.location.definitions = s.mainDirectory+'/definitions'
- try{
- var definitions = require(s.location.definitions+'/'+config.language+'.js')(s,config,lang)
- }catch(er){
- console.error(er)
- console.log('There was an error loading your definition file.')
- try{
- var definitions = require(s.location.definitions+'/en_CA.js')(s,config,lang)
- }catch(er){
- console.error(er)
- console.log('There was an error loading your definition file.')
- var definitions = require(s.location.definitions+'/en_CA.json');
- }
+ function getFramework(languageFile){
+ return frameworkBase(s,config,languageFile)
}
+ const defaultFramework = getFramework(lang)
//load defintions dynamically
- s.definitions = definitions
+ s.definitions = defaultFramework
s.copySystemDefaultDefinitions = function(){
//en_CA
- return Object.assign(s.definitions,{})
+ return Object.assign({},defaultFramework)
}
s.loadedDefinitons={}
s.loadedDefinitons[config.language] = s.copySystemDefaultDefinitions()
@@ -28,10 +22,15 @@ module.exports = function(s,config,lang,app,io){
var file = s.loadedDefinitons[rule]
if(!file){
try{
- s.loadedDefinitons[rule] = require(s.location.definitions+'/'+rule+'.js')(s,config,lang)
- s.loadedDefinitons[rule] = Object.assign(s.copySystemDefaultDefinitions(),s.loadedDefinitons[rule])
+ // console.log(getFramework(lang))
+ s.loadedDefinitons[rule] = Object.assign(
+ {},
+ s.copySystemDefaultDefinitions(),
+ getFramework(s.getLanguageFile(rule))
+ );
file = s.loadedDefinitons[rule]
}catch(err){
+ console.error(err)
file = s.copySystemDefaultDefinitions()
}
}
@@ -40,5 +39,9 @@ module.exports = function(s,config,lang,app,io){
}
return file
}
- return definitions
+ s.reloadDefinitions = function(){
+ s.loadedDefinitons = {};
+ s.loadedDefinitons[config.language] = s.copySystemDefaultDefinitions()
+ }
+ return defaultFramework
}
diff --git a/libs/dropInEvents.js b/libs/dropInEvents.js
index 98860d56..f952fd33 100644
--- a/libs/dropInEvents.js
+++ b/libs/dropInEvents.js
@@ -188,14 +188,21 @@ module.exports = function(s,config,lang,app,io){
createDropInEventsDirectory()
if(!config.ftpServerPort)config.ftpServerPort = 21
if(!config.ftpServerUrl)config.ftpServerUrl = `ftp://0.0.0.0:${config.ftpServerPort}`
+ if(!config.ftpServerPasvUrl)config.ftpServerPasvUrl = config.ftpServerUrl.replace(/.*:\/\//, '').replace(/:.*/, '');
+ if(!config.ftpServerPasvMinPort)config.ftpServerPasvMinPort = 10050;
+ if(!config.ftpServerPasvMaxPort)config.ftpServerPasvMaxPort = 10100;
config.ftpServerUrl = config.ftpServerUrl.replace('{{PORT}}',config.ftpServerPort)
const FtpSrv = require('ftp-srv')
+
const ftpServer = new FtpSrv({
url: config.ftpServerUrl,
// pasv_url must be set to enable PASV; ftp-srv uses its known IP if given 127.0.0.1,
// and smart clients will ignore the IP anyway. Some Dahua IP cams require PASV mode.
// ftp-srv just wants an IP only (no protocol or port)
- pasv_url: config.ftpServerUrl.replace(/.*:\/\//, '').replace(/:.*/, ''),
+ pasv_url: config.ftpServerPasvUrl,
+ pasv_min: config.ftpServerPasvMinPort,
+ pasv_max: config.ftpServerPasvMaxPort,
+ greeting: "Shinobi FTP dropInEvent Server says hello!",
log: require('bunyan').createLogger({
name: 'ftp-srv',
level: 100
@@ -331,4 +338,5 @@ module.exports = function(s,config,lang,app,io){
s.systemLog(`SMTP Server running on port ${config.smtpServerPort}...`)
})
}
+ require('./dropInEvents/mqtt.js')(s,config,lang,app,io)
}
diff --git a/libs/dropInEvents/mqtt.js b/libs/dropInEvents/mqtt.js
new file mode 100644
index 00000000..4c77a402
--- /dev/null
+++ b/libs/dropInEvents/mqtt.js
@@ -0,0 +1,230 @@
+module.exports = (s,config,lang,app,io) => {
+ if(config.mqttClient === true){
+ console.log('Loading MQTT Inbound Connectivity...')
+ const mqtt = require('mqtt')
+ const {
+ triggerEvent,
+ } = require('../events/utils.js')(s,config,lang)
+ function sendPlainEvent(options){
+ const groupKey = options.ke
+ const monitorId = options.mid || options.id
+ const subKey = options.subKey
+ const endpoint = options.host
+ triggerEvent({
+ id: monitorId,
+ ke: groupKey,
+ details: {
+ confidence: 100,
+ name: 'mqtt',
+ plug: endpoint,
+ reason: subKey
+ },
+ },config.mqttEventForceSaveEvent)
+ }
+ function sendFrigateEvent(data,options){
+ const groupKey = options.ke
+ const monitorId = options.mid || options.id
+ const subKey = options.subKey
+ const endpoint = options.host
+ const frigateMatrix = data.after || data.before
+ const confidenceScore = frigateMatrix.top_score * 100
+ const activeZones = frigateMatrix.entered_zones.join(', ')
+ const shinobiMatrix = {
+ x: frigateMatrix.box[0],
+ y: frigateMatrix.box[1],
+ width: frigateMatrix.box[2],
+ height: frigateMatrix.box[3],
+ tag: frigateMatrix.label,
+ confidence: confidenceScore,
+ }
+ triggerEvent({
+ id: monitorId,
+ ke: groupKey,
+ details: {
+ confidence: confidenceScore,
+ name: 'mqtt-'+endpoint,
+ plug: subKey,
+ reason: activeZones,
+ matrices: [shinobiMatrix]
+ },
+ },config.mqttEventForceSaveEvent)
+ }
+ function createMqttSubscription(options){
+ const mqttEndpoint = options.host
+ const subKey = options.subKey
+ const groupKey = options.ke
+ const onData = options.onData || function(){}
+ s.debugLog('Connecting.... mqtt://' + mqttEndpoint)
+ const client = mqtt.connect('mqtt://' + mqttEndpoint)
+ client.on('connect', function () {
+ s.debugLog('Connected! mqtt://' + mqttEndpoint)
+ client.subscribe(subKey, function (err) {
+ if (err) {
+ s.debugLog(err)
+ s.userLog({
+ ke: groupKey,
+ mid: '$USER'
+ },{
+ type: lang['MQTT Error'],
+ msg: err
+ })
+ }else{
+ client.on('message', function (topic, message) {
+ const data = s.parseJSON(message.toString())
+ onData(data)
+ })
+ }
+ })
+ })
+ return client
+ }
+ // const onEventTrigger = async () => {}
+ // const onMonitorUnexpectedExit = (monitorConfig) => {}
+ const loadMqttListBotForUser = function(user){
+ const groupKey = user.ke
+ const userDetails = s.parseJSON(user.details);
+ if(userDetails.mqttclient === '1'){
+ const mqttClientList = userDetails.mqttclient_list || []
+ if(!s.group[groupKey].mqttSubscriptions)s.group[groupKey].mqttSubscriptions = {};
+ const mqttSubs = s.group[groupKey].mqttSubscriptions
+ mqttClientList.forEach(function(row,n){
+ try{
+ const mqttSubId = `${row.host} ${row.subKey}`
+ const messageConversionTypes = row.type || []
+ const monitorsToTrigger = row.monitors || []
+ const triggerAllMonitors = monitorsToTrigger.indexOf('$all') > -1
+ const doActions = []
+ const onData = (data) => {
+ doActions.forEach(function(theAction){
+ theAction(data)
+ })
+ s.debugLog('MQTT Data',row,data)
+ }
+ if(mqttSubs[mqttSubId]){
+ mqttSubs[mqttSubId].end()
+ delete(mqttSubs[mqttSubId])
+ }
+ messageConversionTypes.forEach(function(type){
+ switch(type){
+ case'plain':
+ doActions.push(function(data){
+ // data is unused for plain event.
+ let listOfMonitors = monitorsToTrigger
+ if(triggerAllMonitors){
+ const activeMonitors = Object.keys(s.group[groupKey].activeMonitors)
+ listOfMonitors = activeMonitors
+ }
+ listOfMonitors.forEach(function(monitorId){
+ sendPlainEvent({
+ host: row.host,
+ subKey: row.subKey,
+ ke: groupKey,
+ mid: monitorId
+ })
+ })
+ })
+ break;
+ case'frigate':
+ // https://docs.frigate.video/integrations/mqtt/#frigateevents
+ doActions.push(function(data){
+ // this handler requires using frigate/events
+ // only "new" events will be captured.
+ if(data.type === 'new'){
+ let listOfMonitors = monitorsToTrigger
+ if(triggerAllMonitors){
+ const activeMonitors = Object.keys(s.group[groupKey].activeMonitors)
+ listOfMonitors = activeMonitors
+ }
+ listOfMonitors.forEach(function(monitorId){
+ sendFrigateEvent(data,{
+ host: row.host,
+ subKey: row.subKey,
+ ke: groupKey,
+ mid: monitorId
+ })
+ })
+ }
+ })
+ break;
+ }
+ })
+ mqttSubs[mqttSubId] = createMqttSubscription({
+ host: row.host,
+ subKey: row.subKey,
+ ke: groupKey,
+ onData: onData,
+ })
+ }catch(err){
+ s.debugLog(err)
+ // s.systemLog(err)
+ }
+ })
+ }
+ }
+ const unloadMqttListBotForUser = function(user){
+ const groupKey = user.ke
+ const mqttSubs = s.group[groupKey].mqttSubscriptions || {}
+ Object.keys(mqttSubs).forEach(function(mqttSubId){
+ try{
+ mqttSubs[mqttSubId].end()
+ }catch(err){
+ s.debugLog(err)
+ // s.userLog({
+ // ke: groupKey,
+ // mid: '$USER'
+ // },{
+ // type: lang['MQTT Error'],
+ // msg: err
+ // })
+ }
+ delete(mqttSubs[mqttSubId])
+ })
+ }
+ const onBeforeAccountSave = function(data){
+ data.d.mqttclient_list = []
+ }
+ s.loadGroupAppExtender(loadMqttListBotForUser)
+ s.unloadGroupAppExtender(unloadMqttListBotForUser)
+ s.beforeAccountSave(onBeforeAccountSave)
+ // s.onEventTrigger(onEventTrigger)
+ // s.onMonitorUnexpectedExit(onMonitorUnexpectedExit)
+ s.definitions["Account Settings"].blocks["MQTT Inbound"] = {
+ "evaluation": "$user.details.use_mqttclient !== '0'",
+ "name": lang['MQTT Inbound'],
+ "color": "green",
+ "info": [
+ {
+ "name": "detail=mqttclient",
+ "selector":"u_mqttclient",
+ "field": lang.Enabled,
+ "default": "0",
+ "example": "",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ },
+ {
+ "fieldType": "btn",
+ "class": `btn-success mqtt-add-row`,
+ "btnContent": ` ${lang['Add']}`,
+ },
+ {
+ "id": "mqttclient_list",
+ "fieldType": "div",
+ },
+ {
+ "fieldType": "script",
+ "src": "assets/js/bs5.mqtt.js",
+ }
+ ]
+ }
+ }
+}
diff --git a/libs/events.js b/libs/events.js
index fae6f351..9bfca61a 100644
--- a/libs/events.js
+++ b/libs/events.js
@@ -1,3 +1,3 @@
module.exports = function(s,config,lang){
- // all contents moved to libs/events/utils.js
+ require('./events/onvif.js')(s,config,lang)
}
diff --git a/libs/events/onvif.js b/libs/events/onvif.js
new file mode 100644
index 00000000..6f177fe8
--- /dev/null
+++ b/libs/events/onvif.js
@@ -0,0 +1,109 @@
+module.exports = function(s,config,lang){
+ const {
+ triggerEvent,
+ } = require('./utils.js')(s,config,lang)
+ const onvifEvents = require("node-onvif-events");
+ const onvifEventIds = []
+ const onvifEventControllers = {}
+ const startMotion = async (onvifId,monitorConfig) => {
+ const groupKey = monitorConfig.ke
+ const monitorId = monitorConfig.mid
+ const onvifIdKey = `${monitorConfig.mid}${monitorConfig.ke}`
+ const controlBaseUrl = monitorConfig.details.control_base_url || s.buildMonitorUrl(monitorConfig, true)
+ const controlURLOptions = s.cameraControlOptionsFromUrl(controlBaseUrl,monitorConfig)
+ const onvifPort = parseInt(monitorConfig.details.onvif_port) || 8000
+ let options = {
+ id: onvifId,
+ hostname: controlURLOptions.host,
+ username: controlURLOptions.username,
+ password: controlURLOptions.password,
+ port: onvifPort,
+ };
+ const detector = onvifEventControllers[onvifIdKey] || await onvifEvents.MotionDetector.create(options.id, options);
+ function onvifEventLog(type,data){
+ s.userLog({
+ ke: groupKey,
+ mid: monitorId
+ },{
+ type: type,
+ msg: data
+ })
+ }
+ onvifEventLog(`ONVIF Event Detection Listening!`)
+ try {
+ detector.listen((motion) => {
+ if (motion) {
+ // onvifEventLog(`ONVIF Event Detected!`)
+ triggerEvent({
+ f: 'trigger',
+ id: monitorId,
+ ke: groupKey,
+ details:{
+ plug: 'onvifEvent',
+ name: 'onvifEvent',
+ reason: 'motion',
+ confidence: 100,
+ // reason: 'object',
+ // matrices: [matrix],
+ // imgHeight: img.height,
+ // imgWidth: img.width,
+ }
+ })
+ // } else {
+ // onvifEventLog(`ONVIF Event Stopped`)
+ }
+ });
+ } catch(e) {
+ onvifEventLog(`ONVIF Event Error`,e)
+ }
+ return detector
+ }
+ function initializeOnvifEvents(monitorConfig){
+ const monitorMode = monitorConfig.mode
+ const groupKey = monitorConfig.ke
+ const monitorId = monitorConfig.mid
+ const hasOnvifEventsEnabled = monitorConfig.details.is_onvif === '1' && monitorConfig.details.onvif_events === '1';
+ if(hasOnvifEventsEnabled){
+ const onvifIdKey = `${monitorConfig.mid}${monitorConfig.ke}`
+ let onvifId = onvifEventIds.indexOf(onvifIdKey)
+ if(onvifEventIds.indexOf(onvifIdKey) === -1){
+ onvifId = onvifEventIds.length;
+ onvifEventIds.push(onvifIdKey);
+ }
+ try{
+ onvifEventControllers[onvifIdKey].close()
+ s.debugLog('ONVIF Event Module Warning : This could cause a memory leak?')
+ }catch(err){
+ s.debugLog('ONVIF Event Module Error', err);
+ }
+ delete(onvifEventControllers[onvifIdKey])
+ if(monitorMode !== 'stop'){
+ startMotion(onvifId,monitorConfig).then((detector) => {
+ onvifEventControllers[onvifIdKey] = detector;
+ })
+ }
+ }
+ }
+ s.onMonitorStart((monitorConfig) => {
+ initializeOnvifEvents(monitorConfig)
+ })
+ const connectionInfoArray = s.definitions["Monitor Settings"].blocks["Detector"].info
+ connectionInfoArray.splice(2, 0, {
+ "name": "detail=onvif_events",
+ "field": lang['ONVIF Events'],
+ "default": "0",
+ "form-group-class": "h_onvif_input h_onvif_1",
+ "form-group-class-pre-layer": "h_det_input h_det_1",
+ "fieldType": "select",
+ "possible": [
+ {
+ "name": lang.No,
+ "value": "0"
+ },
+ {
+ "name": lang.Yes,
+ "value": "1"
+ }
+ ]
+ });
+}
diff --git a/libs/events/utils.js b/libs/events/utils.js
index 9a3a0ab3..34be5838 100644
--- a/libs/events/utils.js
+++ b/libs/events/utils.js
@@ -3,7 +3,6 @@ const moment = require('moment');
const execSync = require('child_process').execSync;
const exec = require('child_process').exec;
const spawn = require('child_process').spawn;
-const request = require('request');
const imageSaveEventLock = {};
// Matrix In Region Libs >
const SAT = require('sat')
@@ -12,6 +11,9 @@ const P = SAT.Polygon;
const B = SAT.Box;
// Matrix In Region Libs />
module.exports = (s,config,lang,app,io) => {
+ // Event Filters >
+ const acceptableOperators = ['indexOf','!indexOf','===','!==','>=','>','<','<=']
+ // Event Filters />
const {
splitForFFPMEG
} = require('../ffmpeg/utils.js')(s,config,lang)
@@ -19,8 +21,13 @@ module.exports = (s,config,lang,app,io) => {
moveCameraPtzToMatrix
} = require('../control/ptz.js')(s,config,lang)
const {
- cutVideoLength
+ cutVideoLength,
+ reEncodeVideoAndBinOriginalAddToQueue
} = require('../video/utils.js')(s,config,lang)
+ const {
+ isEven,
+ fetchTimeout,
+ } = require('../basic/utils.js')(process.cwd(),config)
async function saveImageFromEvent(options,frameBuffer){
const monitorId = options.mid || options.id
const groupKey = options.ke
@@ -64,6 +71,9 @@ module.exports = (s,config,lang,app,io) => {
var newString = string + ''
var d = Object.assign(eventData,addOps)
var detailString = s.stringJSON(d.details)
+ var tag = detailString.matrices
+ && detailString.matrices[0]
+ && detailString.matrices[0].tag;
newString = newString
.replace(/{{TIME}}/g,d.currentTimestamp)
.replace(/{{REGION_NAME}}/g,d.details.name)
@@ -71,7 +81,10 @@ module.exports = (s,config,lang,app,io) => {
.replace(/{{MONITOR_ID}}/g,d.id)
.replace(/{{MONITOR_NAME}}/g,s.group[d.ke].rawMonitorConfigurations[d.id].name)
.replace(/{{GROUP_KEY}}/g,d.ke)
- .replace(/{{DETAILS}}/g,detailString)
+ .replace(/{{DETAILS}}/g,detailString);
+ if(tag){
+ newString = newString.replace(/{{TAG}}/g,tag)
+ }
if(d.details.confidence){
newString = newString
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
@@ -146,7 +159,6 @@ module.exports = (s,config,lang,app,io) => {
}
const addToEventCounter = (eventData) => {
const eventsCounted = s.group[eventData.ke].activeMonitors[eventData.id].detector_motion_count
- s.debugLog(`addToEventCounter`,eventData,eventsCounted.length)
eventsCounted.push(eventData)
}
const clearEventCounter = (groupKey,monitorId) => {
@@ -187,8 +199,20 @@ module.exports = (s,config,lang,app,io) => {
Object.keys(filters).forEach(function(key){
var conditionChain = {}
var dFilter = filters[key]
+ if(dFilter.enabled === '0')return;
+ var numberOfOpenAndCloseBrackets = 0
dFilter.where.forEach(function(condition,place){
- conditionChain[place] = {ok:false,next:condition.p4,matrixCount:0}
+ const hasOpenBracket = condition.openBracket === '1';
+ const hasCloseBracket = condition.closeBracket === '1';
+ conditionChain[place] = {
+ ok: false,
+ next: condition.p4,
+ matrixCount: 0,
+ openBracket: hasOpenBracket,
+ closeBracket: hasCloseBracket,
+ }
+ if(hasOpenBracket)++numberOfOpenAndCloseBrackets;
+ if(hasCloseBracket)++numberOfOpenAndCloseBrackets;
if(d.details.matrices)conditionChain[place].matrixCount = d.details.matrices.length
var modifyFilters = function(toCheck,matrixPosition){
var param = toCheck[condition.p1]
@@ -210,7 +234,12 @@ module.exports = (s,config,lang,app,io) => {
pass()
}
break;
- default:
+ case'===':
+ case'!==':
+ case'>=':
+ case'>':
+ case'<':
+ case'<=':
if(eval('param '+condition.p2+' "'+condition.p3.replace(/"/g,'\\"')+'"')){
pass()
}
@@ -243,7 +272,7 @@ module.exports = (s,config,lang,app,io) => {
var atSecond = parseInt(doAtTime[2]) - 1 || timeNow.getSeconds()
var nowAddedInSeconds = atHourNow * 60 * 60 + atMinuteNow * 60 + atSecondNow
var conditionAddedInSeconds = atHour * 60 * 60 + atMinute * 60 + atSecond
- if(eval('nowAddedInSeconds '+condition.p2+' conditionAddedInSeconds')){
+ if(acceptableOperators.indexOf(condition.p2) > -1 && eval('nowAddedInSeconds '+condition.p2+' conditionAddedInSeconds')){
conditionChain[place].ok = true
}
}
@@ -254,20 +283,29 @@ module.exports = (s,config,lang,app,io) => {
}
})
var conditionArray = Object.values(conditionChain)
- var validationString = ''
+ var validationString = []
+ var allowBrackets = false;
+ if (numberOfOpenAndCloseBrackets === 0 || isEven(numberOfOpenAndCloseBrackets)){
+ allowBrackets = true;
+ }else{
+ s.userLog(d,{type:lang["Event Filter Error"],msg:lang.eventFilterErrorBrackets})
+ }
conditionArray.forEach(function(condition,number){
- validationString += condition.ok+' '
+ validationString.push(`${allowBrackets && condition.openBracket ? '(' : ''}${condition.ok}${allowBrackets && condition.closeBracket ? ')' : ''}`);
if(conditionArray.length-1 !== number){
- validationString += condition.next+' '
+ validationString.push(condition.next)
}
})
- if(eval(validationString)){
+ if(eval(validationString.join(' '))){
if(dFilter.actions.halt !== '1'){
delete(dFilter.actions.halt)
Object.keys(dFilter.actions).forEach(function(key){
var value = dFilter.actions[key]
filter[key] = parseValue(key,value)
})
+ if(dFilter.actions.record === '1'){
+ filter.forceRecord = true
+ }
}else{
filter.halt = true
}
@@ -362,7 +400,7 @@ module.exports = (s,config,lang,app,io) => {
matrices: eventDetails.matrices || [],
},d.frame)
}
- if(forceSave || (filter.save && monitorDetails.detector_save === '1')){
+ if(forceSave || (filter.save || monitorDetails.detector_save === '1')){
s.knexQuery({
action: "insert",
table: "Events",
@@ -370,7 +408,7 @@ module.exports = (s,config,lang,app,io) => {
ke: d.ke,
mid: d.id,
details: detailString,
- time: eventTime,
+ time: s.formattedTime(eventTime),
}
})
}
@@ -384,12 +422,12 @@ module.exports = (s,config,lang,app,io) => {
detector_timeout = parseFloat(monitorDetails.detector_timeout)
}
if(
- filter.record &&
+ (filter.forceRecord || (filter.record && monitorDetails.detector_trigger === '1')) &&
monitorConfig.mode === 'start' &&
- monitorDetails.detector_trigger === '1' &&
(monitorDetails.detector_record_method === 'sip' || monitorDetails.detector_record_method === 'hot')
){
- createEventBasedRecording(d,moment(eventTime).subtract(5,'seconds').format('YYYY-MM-DDTHH-mm-ss'))
+ const secondBefore = (parseInt(monitorDetails.detector_buffer_seconds_before) || 5) + 1
+ createEventBasedRecording(d,moment(eventTime).subtract(secondBefore,'seconds').format('YYYY-MM-DDTHH-mm-ss'))
}
d.currentTime = eventTime
d.currentTimestamp = s.timeObject(d.currentTime).format()
@@ -401,14 +439,17 @@ module.exports = (s,config,lang,app,io) => {
var detector_webhook_url = addEventDetailsToString(d,monitorDetails.detector_webhook_url)
var webhookMethod = monitorDetails.detector_webhook_method
if(!webhookMethod || webhookMethod === '')webhookMethod = 'GET'
- request(detector_webhook_url,{method: webhookMethod,encoding:null},function(err,data){
- if(err){
- s.userLog(d,{type:lang["Event Webhook Error"],msg:{error:err,data:data}})
- }
+ fetchTimeout(detector_webhook_url,10000,{
+ method: webhookMethod
+ }).catch((err) => {
+ s.userLog(d,{type:lang["Event Webhook Error"],msg:{error:err,data:data}})
})
}
- if(filter.command && monitorDetails.detector_command_enable === '1' && !s.group[d.ke].activeMonitors[d.id].detector_command){
+ if(
+ filter.command ||
+ (monitorDetails.detector_command_enable === '1' && !s.group[d.ke].activeMonitors[d.id].detector_command)
+ ){
s.group[d.ke].activeMonitors[d.id].detector_command = s.createTimeout('detector_command',s.group[d.ke].activeMonitors[d.id],monitorDetails.detector_command_timeout,10)
var detector_command = addEventDetailsToString(d,monitorDetails.detector_command)
if(detector_command === '')return
@@ -431,23 +472,27 @@ module.exports = (s,config,lang,app,io) => {
if(activeMonitor && activeMonitor.eventBasedRecording && activeMonitor.eventBasedRecording.process){
const eventBasedRecording = activeMonitor.eventBasedRecording
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
- const videoLength = monitorConfig.details.detector_send_video_length
+ const videoLength = parseInt(monitorConfig.details.detector_send_video_length) || 10
const recordingDirectory = s.getVideoDirectory(monitorConfig)
const fileTime = eventBasedRecording.lastFileTime
const filename = `${fileTime}.mp4`
response.filename = `${filename}`
response.filePath = `${recordingDirectory}${filename}`
- eventBasedRecording.process.on('close',function(){
+ eventBasedRecording.process.on('exit',function(){
setTimeout(async () => {
if(!isNaN(videoLength)){
const cutResponse = await cutVideoLength({
ke: groupKey,
mid: monitorId,
filePath: response.filePath,
- cutLength: parseInt(videoLength),
+ cutLength: videoLength,
})
- response.filename = cutResponse.filename
- response.filePath = cutResponse.filePath
+ if(cutResponse.ok){
+ response.filename = cutResponse.filename
+ response.filePath = cutResponse.filePath
+ }else{
+ s.debugLog('cutResponse',cutResponse)
+ }
}
resolve(response)
},1000)
@@ -489,14 +534,26 @@ module.exports = (s,config,lang,app,io) => {
var ffmpegError = ''
var error
var filename = fileTime + '.mp4'
+ let outputMap = `-map 0:0 `
+ const analyzeDuration = parseInt(monitorDetails.event_record_aduration) || 1000
+ const probeSize = parseInt(monitorDetails.event_record_probesize) || 32
s.userLog(d,{
type: logTitleText,
msg: lang["Started"]
})
//-t 00:'+s.timeObject(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+'
+ if(
+ monitorDetails.detector_buffer_acodec &&
+ monitorDetails.detector_buffer_acodec !== 'no' &&
+ monitorDetails.detector_buffer_acodec !== 'auto'
+ ){
+ outputMap += `-map 0:1 `
+ }
+ const ffmpegCommand = `-loglevel warning -live_start_index -99999 -analyzeduration ${analyzeDuration} -probesize ${probeSize} -re -i "${s.dir.streams+d.ke+'/'+d.id}/detectorStream.m3u8" ${outputMap}-movflags faststart+frag_keyframe+empty_moov -fflags +igndts -c:v copy -c:a aac -strict -2 -strftime 1 -y "${s.getVideoDirectory(monitorConfig) + filename}"`
+ s.debugLog(ffmpegCommand)
activeMonitor.eventBasedRecording.process = spawn(
config.ffmpegDir,
- splitForFFPMEG(('-loglevel warning -analyzeduration 1000000 -probesize 1000000 -re -i "'+s.dir.streams+d.ke+'/'+d.id+'/detectorStream.m3u8" -movflags faststart+frag_keyframe+empty_moov -fflags +igndts -c:v copy -strftime 1 "'+s.getVideoDirectory(monitorConfig) + filename + '"'))
+ splitForFFPMEG(ffmpegCommand)
)
activeMonitor.eventBasedRecording.process.stderr.on('data',function(data){
s.userLog(d,{
@@ -515,7 +572,24 @@ module.exports = (s,config,lang,app,io) => {
}
s.insertCompletedVideo(monitorConfig,{
file : filename,
- })
+ },function(err,response){
+ const autoCompressionEnabled = monitorDetails.auto_compress_videos === '1';
+ if(autoCompressionEnabled){
+ reEncodeVideoAndBinOriginalAddToQueue({
+ video: response.insertQuery,
+ targetVideoCodec: 'vp9',
+ targetAudioCodec: 'libopus',
+ targetQuality: '-q:v 1 -q:a 1',
+ targetExtension: 'webm',
+ doSlowly: false,
+ automated: true,
+ }).then((encodeResponse) => {
+ s.debugLog('Complete Automatic Compression',encodeResponse)
+ }).catch((err) => {
+ console.log(err)
+ })
+ }
+ });
s.userLog(d,{
type: logTitleText,
msg: lang["Detector Recording Complete"]
@@ -594,12 +668,13 @@ module.exports = (s,config,lang,app,io) => {
halt : false,
addToMotionCounter : true,
useLock : true,
- save : true,
- webhook : true,
- command : true,
+ save : false,
+ webhook : false,
+ command : false,
record : true,
+ forceRecord : false,
indifference : false,
- countObjects : true
+ countObjects : false
}
if(!s.group[d.ke] || !s.group[d.ke].activeMonitors[d.id]){
return s.systemLog(lang['No Monitor Found, Ignoring Request'])
@@ -608,14 +683,14 @@ module.exports = (s,config,lang,app,io) => {
if(!monitorConfig){
return s.systemLog(lang['No Monitor Found, Ignoring Request'])
}
+ const activeMonitor = s.group[d.ke].activeMonitors[d.id]
const monitorDetails = monitorConfig.details
s.onEventTriggerBeforeFilterExtensions.forEach(function(extender){
extender(d,filter)
})
const eventDetails = d.details
- const passedEventFilters = checkEventFilters(d,monitorDetails,filter)
- if(!passedEventFilters)return
- const detailString = JSON.stringify(eventDetails)
+ const passedEventFilters = checkEventFilters(d,activeMonitor.details,filter)
+ if(!passedEventFilters)return;
const eventTime = new Date()
if(
filter.addToMotionCounter &&
@@ -638,8 +713,7 @@ module.exports = (s,config,lang,app,io) => {
addToEventCounter(d)
}
if(
- filter.countObjects &&
- monitorDetails.detector_obj_count === '1' &&
+ (filter.countObjects || monitorDetails.detector_obj_count === '1') &&
monitorDetails.detector_obj_count_in_region !== '1'
){
didCountingAlready = true
diff --git a/libs/extenders.js b/libs/extenders.js
index 89a887be..be305680 100644
--- a/libs/extenders.js
+++ b/libs/extenders.js
@@ -1,186 +1,66 @@
module.exports = function(s,config){
- ////// USER //////
- s.onSocketAuthenticationExtensions = []
- s.onSocketAuthentication = function(callback){
- s.onSocketAuthenticationExtensions.push(callback)
- }
- //
- s.onUserLogExtensions = []
- s.onUserLog = function(callback){
- s.onUserLogExtensions.push(callback)
- }
- //
- s.loadGroupExtensions = []
- s.loadGroupExtender = function(callback){
- s.loadGroupExtensions.push(callback)
- }
- //
- s.loadGroupAppExtensions = []
- s.loadGroupAppExtender = function(callback){
- s.loadGroupAppExtensions.push(callback)
- }
- //
- s.unloadGroupAppExtensions = []
- s.unloadGroupAppExtender = function(callback){
- s.unloadGroupAppExtensions.push(callback)
- }
- //
- s.cloudDisksLoaded = []
- s.cloudDisksLoader = function(storageType){
- s.cloudDisksLoaded.push(storageType)
- }
- //
- s.onAccountSaveExtensions = []
- s.onAccountSave = function(callback){
- s.onAccountSaveExtensions.push(callback)
- }
- //
- s.beforeAccountSaveExtensions = []
- s.beforeAccountSave = function(callback){
- s.beforeAccountSaveExtensions.push(callback)
- }
- //
- s.onTwoFactorAuthCodeNotificationExtensions = []
- s.onTwoFactorAuthCodeNotification = function(callback){
- s.onTwoFactorAuthCodeNotificationExtensions.push(callback)
- }
- //
- s.onStalePurgeLockExtensions = []
- s.onStalePurgeLock = function(callback){
- s.onStalePurgeLockExtensions.push(callback)
- }
- //
s.cloudDiskUseStartupExtensions = {}
s.cloudDiskUseOnGetVideoDataExtensions = {}
-
+ function createExtension(nameOfExtension,nameOfExtensionContainer,objective){
+ nameOfExtensionContainer = nameOfExtensionContainer || `${nameOfExtension}Extensions`
+ if(objective){
+ s[nameOfExtensionContainer] = []
+ s[nameOfExtension] = function(nameOfCallback,callback){
+ s[nameOfExtensionContainer][nameOfCallback] = callback
+ }
+ }else{
+ s[nameOfExtensionContainer] = []
+ s[nameOfExtension] = function(callback){
+ s[nameOfExtensionContainer].push(callback)
+ }
+ }
+ }
+ ////// USER //////
+ createExtension(`onSocketAuthentication`)
+ createExtension(`onUserLog`)
+ createExtension(`loadGroupExtender`,`loadGroupExtensions`)
+ createExtension(`loadGroupAppExtender`,`loadGroupAppExtensions`)
+ createExtension(`unloadGroupAppExtender`,`unloadGroupAppExtensions`)
+ createExtension(`cloudDisksLoader`,`cloudDisksLoaded`)
+ createExtension(`onAccountSave`)
+ createExtension(`beforeAccountSave`)
+ createExtension(`onTwoFactorAuthCodeNotification`)
+ createExtension(`onStalePurgeLock`)
////// EVENTS //////
- s.onEventTriggerExtensions = []
- s.onEventTrigger = function(callback){
- s.onEventTriggerExtensions.push(callback)
- }
- s.onEventTriggerBeforeFilterExtensions = []
- s.onEventTriggerBeforeFilter = function(callback){
- s.onEventTriggerBeforeFilterExtensions.push(callback)
- }
- s.onFilterEventExtensions = []
- s.onFilterEvent = function(callback){
- s.onFilterEventExtensions.push(callback)
- }
-
+ createExtension(`onEventTrigger`)
+ createExtension(`onEventTriggerBeforeFilter`)
+ createExtension(`onFilterEvent`)
////// MONITOR //////
- s.onMonitorInitExtensions = []
- s.onMonitorInit = function(callback){
- s.onMonitorInitExtensions.push(callback)
- }
- //
- s.onMonitorStartExtensions = []
- s.onMonitorStart = function(callback){
- s.onMonitorStartExtensions.push(callback)
- }
- //
- s.onMonitorStopExtensions = []
- s.onMonitorStop = function(callback){
- s.onMonitorStopExtensions.push(callback)
- }
- //
- s.onMonitorSaveExtensions = []
- s.onMonitorSave = function(callback){
- s.onMonitorSaveExtensions.push(callback)
- }
- //
- s.onMonitorUnexpectedExitExtensions = []
- s.onMonitorUnexpectedExit = function(callback){
- s.onMonitorUnexpectedExitExtensions.push(callback)
- }
- //
- s.onDetectorNoTriggerTimeoutExtensions = []
- s.onDetectorNoTriggerTimeout = function(callback){
- s.onDetectorNoTriggerTimeoutExtensions.push(callback)
- }
- //
- s.onFfmpegCameraStringCreationExtensions = []
- s.onFfmpegCameraStringCreation = function(callback){
- s.onFfmpegCameraStringCreationExtensions.push(callback)
- }
- //
- s.onMonitorPingFailedExtensions = []
- s.onMonitorPingFailed = function(callback){
- s.onMonitorPingFailedExtensions.push(callback)
- }
- //
- s.onMonitorDiedExtensions = []
- s.onMonitorDied = function(callback){
- s.onMonitorDiedExtensions.push(callback)
- }
-
+ createExtension(`onMonitorInit`)
+ createExtension(`onMonitorStart`)
+ createExtension(`onMonitorStop`)
+ createExtension(`onMonitorSave`)
+ createExtension(`onMonitorUnexpectedExit`)
+ createExtension(`onDetectorNoTriggerTimeout`)
+ createExtension(`onFfmpegCameraStringCreation`)
+ createExtension(`onFfmpegBuildMainStream`)
+ createExtension(`onFfmpegBuildStreamChannel`)
+ createExtension(`onMonitorPingFailed`)
+ createExtension(`onMonitorDied`)
+ createExtension(`onMonitorCreateStreamPipe`)
///////// SYSTEM ////////
- s.onProcessReadyExtensions = []
- s.onProcessReady = function(callback){
- s.onProcessReadyExtensions.push(callback)
- }
- //
- s.onProcessExitExtensions = []
- s.onProcessExit = function(callback){
- s.onProcessExitExtensions.push(callback)
- }
- //
- s.onBeforeDatabaseLoadExtensions = []
- s.onBeforeDatabaseLoad = function(callback){
- s.onBeforeDatabaseLoadExtensions.push(callback)
- }
- //
- s.onFFmpegLoadedExtensions = []
- s.onFFmpegLoaded = function(callback){
- s.onFFmpegLoadedExtensions.push(callback)
- }
- //
- s.beforeMonitorsLoadedOnStartupExtensions = []
- s.beforeMonitorsLoadedOnStartup = function(callback){
- s.beforeMonitorsLoadedOnStartupExtensions.push(callback)
- }
- //
- s.onWebSocketConnectionExtensions = []
- s.onWebSocketConnection = function(callback){
- s.onWebSocketConnectionExtensions.push(callback)
- }
- //
- s.onWebSocketDisconnectionExtensions = []
- s.onWebSocketDisconnection = function(callback){
- s.onWebSocketDisconnectionExtensions.push(callback)
- }
- //
- s.onWebsocketMessageSendExtensions = []
- s.onWebsocketMessageSend = function(callback){
- s.onWebsocketMessageSendExtensions.push(callback)
- }
- //
- s.onGetCpuUsageExtensions = []
- s.onGetCpuUsage = function(callback){
- s.onGetCpuUsageExtensions.push(callback)
- }
- //
- s.onGetRamUsageExtensions = []
- s.onGetRamUsage = function(callback){
- s.onGetRamUsageExtensions.push(callback)
- }
- //
- s.onSubscriptionCheckExtensions = []
- s.onSubscriptionCheck = function(callback){
- s.onSubscriptionCheckExtensions.push(callback)
- }
- //
+ createExtension(`onProcessReady`)
+ createExtension(`onProcessExit`)
+ createExtension(`onBeforeDatabaseLoad`)
+ createExtension(`onFFmpegLoaded`)
+ createExtension(`beforeMonitorsLoadedOnStartup`)
+ createExtension(`onWebSocketConnection`)
+ createExtension(`onWebSocketDisconnection`)
+ createExtension(`onWebsocketMessageSend`)
+ createExtension(`onOtherWebSocketMessages`)
+ createExtension(`onGetCpuUsage`)
+ createExtension(`onGetRamUsage`)
+ createExtension(`onSubscriptionCheck`)
+ createExtension(`onDataPortMessage`)
+ createExtension(`onHttpRequestUpgrade`,null,true)
/////// VIDEOS ////////
- s.insertCompletedVideoExtensions = []
- s.insertCompletedVideoExtender = function(callback){
- s.insertCompletedVideoExtensions.push(callback)
- }
- s.onBeforeInsertCompletedVideoExtensions = []
- s.onBeforeInsertCompletedVideo = function(callback){
- s.onBeforeInsertCompletedVideoExtensions.push(callback)
- }
+ createExtension(`insertCompletedVideoExtender`,`insertCompletedVideoExtensions`)
+ createExtension(`onBeforeInsertCompletedVideo`)
/////// TIMELAPSE ////////
- s.onInsertTimelapseFrameExtensions = []
- s.onInsertTimelapseFrame = function(callback){
- s.onInsertTimelapseFrameExtensions.push(callback)
- }
+ createExtension(`onInsertTimelapseFrame`)
}
diff --git a/libs/ffmpeg.js b/libs/ffmpeg.js
index dd29f08c..4a4502fa 100644
--- a/libs/ffmpeg.js
+++ b/libs/ffmpeg.js
@@ -26,9 +26,15 @@ module.exports = async (s,config,lang,onFinish) => {
s.ffmpeg = function(e){
try{
+ const activeMonitor = s.group[e.ke].activeMonitors[e.mid];
+ const dataPortToken = s.gid(10);
+ s.dataPortTokens[dataPortToken] = {
+ type: 'cameraThread',
+ ke: e.ke,
+ mid: e.mid,
+ }
const ffmpegCommand = [`-progress pipe:5`];
- ([
- buildMainInput(e),
+ const allOutputs = [
buildMainStream(e),
buildJpegApiOutput(e),
buildMainRecording(e),
@@ -36,45 +42,53 @@ module.exports = async (s,config,lang,onFinish) => {
buildMainDetector(e),
buildEventRecordingOutput(e),
buildTimelapseOutput(e),
- ]).forEach(function(commandStringPart){
- ffmpegCommand.push(commandStringPart)
- })
- s.onFfmpegCameraStringCreationExtensions.forEach(function(extender){
- extender(e,ffmpegCommand)
- })
- const stdioPipes = createPipeArray(e)
- const ffmpegCommandString = ffmpegCommand.join(' ')
- //hold ffmpeg command for log stream
- s.group[e.ke].activeMonitors[e.mid].ffmpeg = sanitizedFfmpegCommand(e,ffmpegCommandString)
- //clean the string of spatial impurities and split for spawn()
- const ffmpegCommandParsed = splitForFFPMEG(ffmpegCommandString)
- try{
- fs.unlinkSync(e.sdir + 'cmd.txt')
- }catch(err){
-
- }
- fs.writeFileSync(e.sdir + 'cmd.txt',JSON.stringify({
- cmd: ffmpegCommandParsed,
- pipes: stdioPipes.length,
- rawMonitorConfig: s.group[e.ke].rawMonitorConfigurations[e.id],
- globalInfo: {
- config: config,
- isAtleatOneDetectorPluginConnected: s.isAtleatOneDetectorPluginConnected
- }
- },null,3),'utf8')
- var cameraCommandParams = [
- config.monitorDaemonPath ? config.monitorDaemonPath : __dirname + '/cameraThread/singleCamera.js',
- config.ffmpegDir,
- e.sdir + 'cmd.txt'
- ]
- const cameraProcess = spawn('node',cameraCommandParams,{detached: true,stdio: stdioPipes})
- if(config.debugLog === true){
- cameraProcess.stderr.on('data',(data) => {
- console.log(`${e.ke} ${e.mid}`)
- console.log(data.toString())
+ ];
+ if(allOutputs.filter(output => !!output).length > 0){
+ ([
+ buildMainInput(e),
+ ]).concat(allOutputs).forEach(function(commandStringPart){
+ ffmpegCommand.push(commandStringPart)
})
+ s.onFfmpegCameraStringCreationExtensions.forEach(function(extender){
+ extender(e,ffmpegCommand)
+ })
+ const stdioPipes = createPipeArray(e)
+ const ffmpegCommandString = ffmpegCommand.join(' ')
+ //hold ffmpeg command for log stream
+ activeMonitor.ffmpeg = sanitizedFfmpegCommand(e,ffmpegCommandString)
+ //clean the string of spatial impurities and split for spawn()
+ const ffmpegCommandParsed = splitForFFPMEG(ffmpegCommandString)
+ try{
+ fs.unlinkSync(e.sdir + 'cmd.txt')
+ }catch(err){
+
+ }
+ fs.writeFileSync(e.sdir + 'cmd.txt',JSON.stringify({
+ dataPortToken: dataPortToken,
+ cmd: ffmpegCommandParsed,
+ pipes: stdioPipes.length,
+ rawMonitorConfig: s.group[e.ke].rawMonitorConfigurations[e.id],
+ globalInfo: {
+ config: config,
+ isAtleatOneDetectorPluginConnected: s.isAtleatOneDetectorPluginConnected
+ }
+ },null,3),'utf8')
+ var cameraCommandParams = [
+ config.monitorDaemonPath ? config.monitorDaemonPath : __dirname + '/cameraThread/singleCamera.js',
+ config.ffmpegDir,
+ e.sdir + 'cmd.txt'
+ ]
+ const cameraProcess = spawn('node',cameraCommandParams,{detached: true,stdio: stdioPipes})
+ if(config.debugLog === true && config.debugLogMonitors === true){
+ cameraProcess.stderr.on('data',(data) => {
+ console.log(`${e.ke} ${e.mid}`)
+ console.log(data.toString())
+ })
+ }
+ return cameraProcess
+ }else{
+ return null
}
- return cameraProcess
}catch(err){
s.systemLog(err)
return null
diff --git a/libs/ffmpeg/builders.js b/libs/ffmpeg/builders.js
index b2711e79..8583de25 100644
--- a/libs/ffmpeg/builders.js
+++ b/libs/ffmpeg/builders.js
@@ -9,6 +9,8 @@ module.exports = (s,config,lang) => {
const {
validateDimensions,
} = require('./utils.js')(s,config,lang)
+ if(!config.outputsWithAudio)config.outputsWithAudio = ['hls','flv','mp4','rtmp'];
+ if(!config.outputsNotCapableOfPresets)config.outputsNotCapableOfPresets = [];
const hasCudaEnabled = (monitor) => {
return monitor.details.accelerator === '1' && monitor.details.hwaccel === 'cuvid' && monitor.details.hwaccel_vcodec === ('h264_cuvid' || 'hevc_cuvid' || 'mjpeg_cuvid' || 'mpeg4_cuvid')
}
@@ -183,7 +185,7 @@ module.exports = (s,config,lang) => {
const createStreamChannel = function(e,number,channel){
//`e` is the monitor object
//`x` is an object used to contain temporary values.
- const channelStreamDirectory = !isNaN(parseInt(number)) ? `${e.sdir}channel${number}/` : e.sdir
+ const channelStreamDirectory = !isNaN(parseInt(number)) ? `${e.sdir || s.getStreamsDirectory(e)}channel${number}/` : e.sdir
if(channelStreamDirectory !== e.sdir && !fs.existsSync(channelStreamDirectory)){
try{
fs.mkdirSync(channelStreamDirectory)
@@ -203,7 +205,7 @@ module.exports = (s,config,lang) => {
const streamType = channel.stream_type ? channel.stream_type : 'hls'
const videoFps = !isNaN(parseFloat(channel.stream_fps)) && channel.stream_fps !== '0' ? parseFloat(channel.stream_fps) : streamType === 'rtmp' ? '30' : null
const inputMap = buildInputMap(e,e.details.input_map_choices[`stream_channel-${channelNumber}`])
- const outputCanHaveAudio = (streamType === 'hls' || streamType === 'mp4' || streamType === 'flv' || streamType === 'h265' || streamType === 'rtmp')
+ const outputCanHaveAudio = config.outputsWithAudio.indexOf(streamType) > -1;
const outputRequiresEncoding = streamType === 'mjpeg' || streamType === 'b64'
const outputIsPresetCapable = outputCanHaveAudio
const { videoWidth, videoHeight } = validateDimensions(channel.stream_scale_x,channel.stream_scale_y)
@@ -246,7 +248,7 @@ module.exports = (s,config,lang) => {
streamFilters.push(channel.stream_vf)
}
if(outputIsPresetCapable){
- const streamPreset = streamType !== 'h265' && channel.preset_stream ? channel.preset_stream : null
+ const streamPreset = config.outputsNotCapableOfPresets.indexOf(streamType) === -1 && channel.preset_stream ? channel.preset_stream : null
if(streamPreset){
streamFlags.push(`-preset ${streamPreset}`)
}
@@ -287,18 +289,18 @@ module.exports = (s,config,lang) => {
streamFlags.push(`-g 1`)
}
}
- streamFlags.push(`-f hls -hls_time ${hlsTime} -hls_list_size ${hlsListSize} -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "${channelStreamDirectory}s.m3u8"`)
+ streamFlags.push(`-f hls -hls_time ${hlsTime} -hls_list_size ${hlsListSize} -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist+discont_start "${channelStreamDirectory}s.m3u8"`)
break;
case'mjpeg':
streamFlags.push(`-an -c:v mjpeg -f mpjpeg -boundary_tag shinobi pipe:${number}`)
break;
- case'h265':
- streamFlags.push(`-movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Shinobi H.265 Stream" -reset_timestamps 1 -f hevc pipe:${number}`)
- break;
case'b64':case'':case undefined:case null://base64
streamFlags.push(`-an -c:v mjpeg -f image2pipe pipe:${number}`)
break;
}
+ s.onFfmpegBuildStreamChannelExtensions.forEach(function(extender){
+ extender(streamType,streamFlags,number,e)
+ });
return ' ' + streamFlags.join(' ')
}
const buildMainInput = function(e){
@@ -375,7 +377,7 @@ module.exports = (s,config,lang) => {
//x = temporary values
const streamFlags = []
const streamType = e.details.stream_type ? e.details.stream_type : 'hls'
- if(streamType !== 'jpeg'){
+ if(streamType !== 'jpeg' && streamType !== 'useSubstream'){
const isCudaEnabled = hasCudaEnabled(e)
const streamFilters = []
const videoCodecisCopy = e.details.stream_vcodec === 'copy'
@@ -384,7 +386,7 @@ module.exports = (s,config,lang) => {
const videoQuality = e.details.stream_quality ? e.details.stream_quality : '1'
const videoFps = !isNaN(parseFloat(e.details.stream_fps)) && e.details.stream_fps !== '0' ? parseFloat(e.details.stream_fps) : null
const inputMap = buildInputMap(e,e.details.input_map_choices.stream)
- const outputCanHaveAudio = (streamType === 'hls' || streamType === 'mp4' || streamType === 'flv' || streamType === 'h265')
+ const outputCanHaveAudio = config.outputsWithAudio.indexOf(streamType) > -1;
const outputRequiresEncoding = streamType === 'mjpeg' || streamType === 'b64'
const outputIsPresetCapable = outputCanHaveAudio
const { videoWidth, videoHeight } = validateDimensions(e.details.stream_scale_x,e.details.stream_scale_y)
@@ -429,7 +431,7 @@ module.exports = (s,config,lang) => {
streamFilters.push(e.details.stream_vf)
}
if(outputIsPresetCapable){
- const streamPreset = streamType !== 'h265' && e.details.preset_stream ? e.details.preset_stream : null
+ const streamPreset = config.outputsNotCapableOfPresets.indexOf(streamType) === -1 && e.details.preset_stream ? e.details.preset_stream : null
if(streamPreset){
streamFlags.push(`-preset ${streamPreset}`)
}
@@ -460,18 +462,18 @@ module.exports = (s,config,lang) => {
streamFlags.push(`-g 1`)
}
}
- streamFlags.push(`-f hls -hls_time ${hlsTime} -hls_list_size ${hlsListSize} -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "${e.sdir}s.m3u8"`)
+ streamFlags.push(`-f hls -hls_time ${hlsTime} -hls_list_size ${hlsListSize} -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist+discont_start "${e.sdir}s.m3u8"`)
break;
case'mjpeg':
streamFlags.push(`-an -c:v mjpeg -f mpjpeg -boundary_tag shinobi pipe:1`)
break;
- case'h265':
- streamFlags.push(`-movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Shinobi H.265 Stream" -reset_timestamps 1 -f hevc pipe:1`)
- break;
case'b64':case'':case undefined:case null://base64
streamFlags.push(`-an -c:v mjpeg -f image2pipe pipe:1`)
break;
}
+ s.onFfmpegBuildMainStreamExtensions.forEach(function(extender){
+ extender(streamType,streamFlags,e)
+ });
if(e.details.custom_output){
streamFlags.push(e.details.custom_output)
}
@@ -652,20 +654,20 @@ module.exports = (s,config,lang) => {
if(objectDetectorOutputIsEnabled){
addObjectDetectorInputMap()
addObjectDetectValues()
- detectorFlags.push('-an -f singlejpeg pipe:4')
+ detectorFlags.push('-an -f mjpeg pipe:4')
}
}else if(sendFramesToObjectDetector){
addObjectDetectorInputMap()
addObjectDetectValues()
- detectorFlags.push('-an -f singlejpeg pipe:4')
+ detectorFlags.push('-an -f mjpeg pipe:4')
}else{
addInputMap()
- detectorFlags.push('-an -f singlejpeg pipe:4')
+ detectorFlags.push('-an -f mjpeg pipe:4')
}
}else if(sendFramesToObjectDetector){
addObjectDetectorInputMap()
addObjectDetectValues()
- detectorFlags.push('-an -f singlejpeg pipe:4')
+ detectorFlags.push('-an -f mjpeg pipe:4')
}
return detectorFlags.join(' ')
}
@@ -677,13 +679,17 @@ module.exports = (s,config,lang) => {
const isCudaEnabled = hasCudaEnabled(e)
const outputFilters = []
var videoCodec = e.details.detector_buffer_vcodec
+ var liveStartIndex = e.details.detector_buffer_live_start_index || '-3'
var audioCodec = e.details.detector_buffer_acodec ? e.details.detector_buffer_acodec : 'no'
const videoCodecisCopy = videoCodec === 'copy'
const videoFps = !isNaN(parseFloat(e.details.stream_fps)) && e.details.stream_fps !== '0' ? parseFloat(e.details.stream_fps) : null
const inputMap = buildInputMap(e,e.details.input_map_choices.detector_sip_buffer)
const { videoWidth, videoHeight } = validateDimensions(e.details.event_record_scale_x,e.details.event_record_scale_y)
const hlsTime = !isNaN(parseInt(e.details.detector_buffer_hls_time)) ? `${parseInt(e.details.detector_buffer_hls_time)}` : '2'
- const hlsListSize = !isNaN(parseInt(e.details.detector_buffer_hls_list_size)) ? `${parseInt(e.details.detector_buffer_hls_list_size)}` : '4'
+ // const hlsListSize = !isNaN(parseInt(e.details.detector_buffer_hls_list_size)) ? `${parseInt(e.details.detector_buffer_hls_list_size)}` : '4'
+ const secondsBefore = parseInt(e.details.detector_buffer_seconds_before) || 5
+ let hlsListSize = parseInt(secondsBefore * 0.6)
+ hlsListSize = hlsListSize < 3 ? 3 : hlsListSize;
if(inputMap)outputFlags.push(inputMap)
if(e.details.cust_sip_record)outputFlags.push(e.details.cust_sip_record)
if(videoCodec === 'auto'){
@@ -737,7 +743,7 @@ module.exports = (s,config,lang) => {
outputFlags.push(`-g 1`)
}
}
- outputFlags.push(`-f hls -live_start_index -3 -hls_time ${hlsTime} -hls_list_size ${hlsListSize} -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "${e.sdir}detectorStream.m3u8"`)
+ outputFlags.push(`-f hls -live_start_index -3 -hls_time ${hlsTime} -hls_list_size ${hlsListSize} -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist+discont_start "${e.sdir}detectorStream.m3u8"`)
}
return outputFlags.join(' ')
}
@@ -757,11 +763,63 @@ module.exports = (s,config,lang) => {
if(videoFilters.length > 0){
videoFlags.push(`-vf "${videoFilters.join(',')}"`)
}
- videoFlags.push(`-f singlejpeg -an -q:v 1 pipe:7`)
+ videoFlags.push(`-f mjpeg -an -q:v 1 pipe:7`)
return videoFlags.join(' ')
}
return ``
}
+ const getDefaultSubstreamFields = function(monitor){
+ const subStreamFields = s.parseJSON(monitor.details.substream || {input:{},output:{}})
+ const inputAndConnectionFields = Object.assign({
+ "type":"h264",
+ "fulladdress":"",
+ "sfps":"",
+ "aduration":"",
+ "probesize":"",
+ "stream_loop":"0",
+ "rtsp_transport":"",
+ "accelerator":"0",
+ "hwaccel":"",
+ "hwaccel_vcodec":"auto",
+ "hwaccel_device":"",
+ "cust_input":""
+ },subStreamFields.input);
+ const outputFields = Object.assign({
+ "stream_type":"hls",
+ "rtmp_server_url":"",
+ "rtmp_stream_key":"",
+ "stream_mjpeg_clients":"",
+ "stream_vcodec":"copy",
+ "stream_acodec":"no",
+ "stream_fps":"",
+ "hls_time":"",
+ "preset_stream":"",
+ "hls_list_size":"",
+ "stream_quality":"",
+ "stream_v_br":"",
+ "stream_a_br":"",
+ "stream_scale_x":"",
+ "stream_scale_y":"",
+ "rotate_stream":"no",
+ "svf":"",
+ "cust_stream":""
+ },subStreamFields.output);
+ return {
+ inputAndConnectionFields,
+ outputFields,
+ }
+ }
+ const buildSubstreamString = function(channelNumber,monitor){
+ let ffmpegParts = []
+ const {
+ inputAndConnectionFields,
+ outputFields,
+ } = getDefaultSubstreamFields(monitor)
+ ffmpegParts.push(`-loglevel ${monitor.details.loglevel || 'warning'}`)
+ ffmpegParts.push(createInputMap(monitor,channelNumber,inputAndConnectionFields))
+ ffmpegParts.push(createStreamChannel(monitor,channelNumber,outputFields))
+ return ffmpegParts.join(' ')
+ }
return {
createStreamChannel: createStreamChannel,
buildMainInput: buildMainInput,
@@ -772,5 +830,7 @@ module.exports = (s,config,lang) => {
buildMainDetector: buildMainDetector,
buildEventRecordingOutput: buildEventRecordingOutput,
buildTimelapseOutput: buildTimelapseOutput,
+ getDefaultSubstreamFields: getDefaultSubstreamFields,
+ buildSubstreamString: buildSubstreamString,
}
}
diff --git a/libs/ffmpeg/subStream.js b/libs/ffmpeg/subStream.js
new file mode 100644
index 00000000..87740f34
--- /dev/null
+++ b/libs/ffmpeg/subStream.js
@@ -0,0 +1,16 @@
+function createStringForSubstreamProcess(options){
+ let options = {
+ "type":"h264",
+ "fulladdress":"",
+ "sfps":"",
+ "aduration":"",
+ "probesize":"",
+ "stream_loop":"0",
+ "rtsp_transport":"",
+ "accelerator":"0",
+ "hwaccel":"auto",
+ "hwaccel_vcodec":"auto",
+ "hwaccel_device":"",
+ "cust_input":""
+ }
+}
diff --git a/libs/ffmpeg/utils.js b/libs/ffmpeg/utils.js
index 432092a3..115576b3 100644
--- a/libs/ffmpeg/utils.js
+++ b/libs/ffmpeg/utils.js
@@ -14,48 +14,63 @@ module.exports = (s,config,lang) => {
var endData = {ok: false, result: {}}
if(!url){
endData.error = 'Missing URL'
- callback(endData)
- return
+ if(callback)callback(endData)
+ return {
+ result: {
+ format: {
+ duration: 1
+ }
+ }
+ }
}
if(!forceOverlap && activeProbes[auth]){
endData.error = 'Account is already probing'
- callback(endData)
- return
- }
- activeProbes[auth] = 1
- var stderr = ''
- var stdout = ''
- const probeCommand = splitForFFPMEG(`${customInput ? customInput + ' ' : ''}-analyzeduration 10000 -probesize 10000 -v quiet -print_format json -show_format -show_streams -i "${url}"`)
- var processTimeout = null
- var ffprobeLocation = config.ffmpegDir.split('/')
- ffprobeLocation[ffprobeLocation.length - 1] = 'ffprobe'
- ffprobeLocation = ffprobeLocation.join('/')
- const probeProcess = spawn(ffprobeLocation, probeCommand)
- const finishReponse = () => {
- delete(activeProbes[auth])
- if(!stdout){
- endData.error = stderr
- }else{
- endData.ok = true
- endData.result = s.parseJSON(stdout)
+ if(callback)callback(endData)
+ return {
+ result: {
+ format: {
+ duration: 1
+ }
+ }
}
- endData.probe = probeCommand
- callback(endData)
}
- probeProcess.stderr.on('data',function(data){
- stderr += data.toString()
+ return new Promise((resolve) => {
+ activeProbes[auth] = 1
+ var stderr = ''
+ var stdout = ''
+ const probeCommand = splitForFFPMEG(`${customInput ? customInput + ' ' : ''}-analyzeduration 10000 -probesize 10000 -v quiet -print_format json -show_format -show_streams -i "${url}"`)
+ var processTimeout = null
+ var ffprobeLocation = config.ffmpegDir.split('/')
+ ffprobeLocation[ffprobeLocation.length - 1] = 'ffprobe'
+ ffprobeLocation = ffprobeLocation.join('/')
+ const probeProcess = spawn(ffprobeLocation, probeCommand)
+ const finishReponse = () => {
+ delete(activeProbes[auth])
+ if(!stdout){
+ endData.error = stderr
+ }else{
+ endData.ok = true
+ endData.result = s.parseJSON(stdout)
+ }
+ endData.probe = probeCommand
+ if(callback)callback(endData)
+ resolve(endData)
+ }
+ probeProcess.stderr.on('data',function(data){
+ stderr += data.toString()
+ })
+ probeProcess.stdout.on('data',function(data){
+ stdout += data.toString()
+ })
+ probeProcess.on('close',function(){
+ clearTimeout(processTimeout)
+ finishReponse()
+ })
+ processTimeout = setTimeout(() => {
+ treekill(probeProcess.pid)
+ finishReponse()
+ },4000)
})
- probeProcess.stdout.on('data',function(data){
- stdout += data.toString()
- })
- probeProcess.on('close',function(){
- clearTimeout(processTimeout)
- finishReponse()
- })
- processTimeout = setTimeout(() => {
- treekill(probeProcess.pid)
- finishReponse()
- },4000)
}
const probeMonitor = (monitor,timeoutAmount,forceOverlap) => {
return new Promise((resolve,reject) => {
@@ -130,7 +145,11 @@ module.exports = (s,config,lang) => {
}else{
const details = s.parseJSON(configPartial.details)
Object.keys(details).forEach((key) => {
- activeMonitor.details[key] = details[key]
+ try{
+ activeMonitor.details[key] = details[key]
+ }catch(err){
+ console.log(err)
+ }
})
}
})
@@ -155,11 +174,11 @@ module.exports = (s,config,lang) => {
}
return sanitizedCmd
}
- const createPipeArray = function(e){
+ const createPipeArray = function(e, amountToAdd){
const stdioPipes = [];
- var times = config.pipeAddition;
- if(e.details.stream_channels){
- times+=e.details.stream_channels.length
+ var times = amountToAdd ? amountToAdd + config.pipeAddition : config.pipeAddition;
+ if(e.details && e.details.stream_channels){
+ times += e.details.stream_channels.length
}
for(var i=0; i < times; i++){
stdioPipes.push('pipe')
diff --git a/libs/fileBin.js b/libs/fileBin.js
index 455fc6d1..acac006e 100644
--- a/libs/fileBin.js
+++ b/libs/fileBin.js
@@ -163,11 +163,34 @@ module.exports = function(s,config,lang,app,io){
})
})
}
+ function archiveFileBinEntry(file,unarchive){
+ return new Promise((resolve) => {
+ s.knexQuery({
+ action: "update",
+ table: 'Files',
+ update: {
+ archive: unarchive ? '0' : 1
+ },
+ where: {
+ ke: file.ke,
+ mid: file.mid,
+ name: file.name,
+ }
+ },function(err){
+ resolve({
+ ok: !err,
+ err: err,
+ archived: !unarchive
+ })
+ })
+ })
+ }
s.getFileBinDirectory = getFileBinDirectory
s.getFileBinEntry = getFileBinEntry
s.insertFileBinEntry = insertFileBinEntry
s.updateFileBinEntry = updateFileBinEntry
s.deleteFileBinEntry = deleteFileBinEntry
+ s.archiveFileBinEntry = archiveFileBinEntry
/**
* API : Get fileBin file rows
*/
@@ -187,6 +210,7 @@ module.exports = function(s,config,lang,app,io){
startTimeOperator: req.query.startOperator,
endTimeOperator: req.query.endOperator,
limit: req.query.limit,
+ archived: req.query.archived,
endIsStartTo: true,
noFormat: true,
noCount: true,
@@ -196,7 +220,7 @@ module.exports = function(s,config,lang,app,io){
},(response) => {
response.forEach(function(v){
v.details = s.parseJSON(v.details)
- v.href = '/'+req.params.auth+'/fileBin/'+req.params.ke+'/'+req.params.id+'/'+v.details.year+'/'+v.details.month+'/'+v.details.day+'/'+v.name;
+ v.href = '/'+req.params.auth+'/fileBin/'+req.params.ke+'/'+req.params.id+'/'+v.name;
})
s.closeJsonResponse(res,{
ok: true,
@@ -208,55 +232,124 @@ module.exports = function(s,config,lang,app,io){
/**
* API : Get fileBin file
*/
- app.get([
- config.webPaths.apiPrefix+':auth/fileBin/:ke/:id/:file',
- config.webPaths.apiPrefix+':auth/fileBin/:ke/:id/:year/:month/:day/:file',
- ], async (req,res) => {
+ app.get(config.webPaths.apiPrefix+':auth/fileBin/:ke/:id/:file', async (req,res) => {
s.auth(req.params,function(user){
var failed = function(){
res.end(user.lang['File Not Found'])
}
- if (!s.group[req.params.ke].fileBin[req.params.id+'/'+req.params.file]){
- const groupKey = req.params.ke
- const monitorId = req.params.id
- const monitorRestrictions = s.getMonitorRestrictions(user.details,monitorId)
- if(user.details.sub && user.details.allmonitors === '0' && (user.permissions.get_monitors === "0" || monitorRestrictions.length === 0)){
- s.closeJsonResponse(res,{
- ok: false,
- msg: lang['Not Permitted']
- })
- return
- }
- s.knexQuery({
- action: "select",
- columns: "*",
- table: "Files",
- where: [
- ['ke','=',groupKey],
- ['mid','=',req.params.id],
- ['name','=',req.params.file],
- monitorRestrictions
- ]
- },(err,r) => {
- if(r && r[0]){
- r = r[0]
- r.details = JSON.parse(r.details)
- const filePath = s.dir.fileBin + req.params.ke + '/' + req.params.id + (r.details.year && r.details.month && r.details.day ? '/' + r.details.year + '/' + r.details.month + '/' + r.details.day : '') + '/' + req.params.file;
- fs.stat(filePath,function(err,stats){
- if(!err){
+ const groupKey = req.params.ke
+ const monitorId = req.params.id
+ const {
+ monitorPermissions,
+ monitorRestrictions,
+ } = s.getMonitorsPermitted(user.details,monitorId)
+ const {
+ isRestricted,
+ isRestrictedApiKey,
+ apiKeyPermissions,
+ } = s.checkPermission(user)
+ if(
+ isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
+ isRestricted && !monitorPermissions[`${monitorId}_video_view`]
+ ){
+ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
+ return
+ }
+ s.knexQuery({
+ action: "select",
+ columns: "*",
+ table: "Files",
+ where: [
+ ['ke','=',groupKey],
+ ['mid','=',req.params.id],
+ ['name','=',req.params.file],
+ monitorRestrictions
+ ]
+ },(err,r) => {
+ if(r && r[0]){
+ r = r[0]
+ r.details = JSON.parse(r.details)
+ const filename = req.params.file
+ const filePath = s.dir.fileBin + req.params.ke + '/' + req.params.id + (r.details.year && r.details.month && r.details.day ? '/' + r.details.year + '/' + r.details.month + '/' + r.details.day : '') + '/' + filename;
+ fs.stat(filePath,function(err,stats){
+ if(!err){
+ if(filename.endsWith('.mp4')){
+ s.streamMp4FileOverHttp(filePath,req,res,!!req.query.pureStream)
+ }else{
res.on('finish',function(){res.end()})
fs.createReadStream(filePath).pipe(res)
- }else{
- failed()
}
- })
- }else{
- failed()
- }
- })
- }else{
- res.end(user.lang['Please Wait for Completion'])
- }
+ }else{
+ failed()
+ }
+ })
+ }else{
+ failed()
+ }
+ })
},res,req);
});
+ /**
+ * API : Modify fileBin File
+ */
+ app.get(config.webPaths.apiPrefix+':auth/fileBin/:ke/:id/:file/:mode', function (req,res){
+ let response = { ok: false };
+ res.setHeader('Content-Type', 'application/json');
+ s.auth(req.params,function(user){
+ const monitorId = req.params.id
+ const groupKey = req.params.ke
+ const filename = req.params.file
+ const {
+ monitorPermissions,
+ monitorRestrictions,
+ } = s.getMonitorsPermitted(user.details,monitorId)
+ const {
+ isRestricted,
+ isRestrictedApiKey,
+ apiKeyPermissions,
+ } = s.checkPermission(user);
+ if(
+ isRestrictedApiKey && apiKeyPermissions.delete_videos_disallowed ||
+ isRestricted && !monitorPermissions[`${monitorId}_video_delete`]
+ ){
+ s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
+ return
+ }
+ s.knexQuery({
+ action: "select",
+ columns: "*",
+ table: 'Files',
+ where: [
+ ['ke','=',groupKey],
+ ['mid','=',monitorId],
+ ['name','=',filename]
+ ],
+ limit: 1
+ },async (err,r) => {
+ if(r && r[0]){
+ const file = r[0];
+ var details = s.parseJSON(r.details) || {}
+ switch(req.params.mode){
+ case'archive':
+ response.ok = true
+ const unarchive = s.getPostData(req,'unarchive') == '1';
+ const archiveResponse = await archiveFileBinEntry(file,unarchive)
+ response.ok = archiveResponse.ok
+ response.archived = archiveResponse.archived
+ break;
+ case'delete':
+ response.ok = true;
+ await s.deleteFileBinEntry(file)
+ break;
+ default:
+ response.msg = user.lang.modifyVideoText1;
+ break;
+ }
+ }else{
+ response.msg = user.lang['No such file'];
+ }
+ s.closeJsonResponse(res,response);
+ })
+ },res,req);
+ })
}
diff --git a/libs/health.js b/libs/health.js
index 31d2755e..a0ad9eb7 100644
--- a/libs/health.js
+++ b/libs/health.js
@@ -55,6 +55,9 @@ module.exports = function(s,config,lang,io){
exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
if(s.isWin===true){
d=d.replace(/(\r\n|\n|\r)/gm,"").replace(/%/g,"")
+ }else if(s.platform == 'darwin') {
+ // on macos the cpu% is per core, so a 10 core machine can go up to 1000%
+ d=parseFloat(d.trim()) / s.coreCount
}
resolve(d)
s.onGetCpuUsageExtensions.forEach(function(extender){
@@ -100,18 +103,31 @@ module.exports = function(s,config,lang,io){
k.cmd = "LANG=C free | grep Mem | awk '{print $7/$2 * 100.0}'";
break;
}
+ let percent = 0
+ let used = 0
if(k.cmd){
- exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
- if(s.isWin===true){
- d=(parseInt(d.split('=')[1])/(s.totalmem/1000))*100
- }
- resolve(d)
- s.onGetRamUsageExtensions.forEach(function(extender){
- extender(d)
- })
- })
+ exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
+ if(s.isWin===true){
+ const freeMb = parseInt(d.split('=')[1].trim()) / 1024
+ const totalMemInMb = s.totalmem/1024/1024
+ used = totalMemInMb - freeMb
+ percent=(used/totalMemInMb)*100
+ } else {
+ percent = d.trim()
+ }
+ resolve({
+ used,
+ percent
+ })
+ s.onGetRamUsageExtensions.forEach(function(extender){
+ extender(percent)
+ })
+ })
}else{
- resolve(0)
+ resolve({
+ used,
+ percent
+ })
}
})
}
diff --git a/libs/health/utils.js b/libs/health/utils.js
index 98eecdfc..07f36c99 100644
--- a/libs/health/utils.js
+++ b/libs/health/utils.js
@@ -16,14 +16,11 @@ const currentCPUInfo = {
total: 0,
active: 0
}
-const lastCPUInfo = {
+let lastCPUInfo = {
total: 0,
active: 0
}
exports.getCpuUsageOnLinux = () => {
- lastCPUInfo.active = currentCPUInfo.active;
- lastCPUInfo.idle = currentCPUInfo.idle;
- lastCPUInfo.total = currentCPUInfo.total;
return new Promise((resolve,reject) => {
const getUsage = function(callback){
fs.readFile("/proc/stat" ,'utf8', function(err, data){
@@ -36,6 +33,7 @@ exports.getCpuUsageOnLinux = () => {
}
currentCPUInfo.active = currentCPUInfo.total - currentCPUInfo.idle
currentCPUInfo.percentUsed = calculateCPUPercentage(lastCPUInfo, currentCPUInfo);
+ lastCPUInfo = Object.assign({},currentCPUInfo)
callback(currentCPUInfo.percentUsed)
})
}
diff --git a/libs/language.js b/libs/language.js
index ced395a0..512b4617 100644
--- a/libs/language.js
+++ b/libs/language.js
@@ -3,17 +3,23 @@ module.exports = function(s,config){
if(!config.language){
config.language='en_CA'
}
- try{
- var lang = require(s.location.languages+'/'+config.language+'.json');
- }catch(er){
- console.error(er)
- console.log('There was an error loading your language file.')
- var lang = require(s.location.languages+'/en_CA.json');
+ var lang = {};
+ function getLanguageData(choice){
+ let gotLang = {}
+ try{
+ eval(`gotLang = ${fs.readFileSync(s.location.languages+'/'+choice+'.json','utf8')}`)
+ }catch(er){
+ console.error(er)
+ console.log('There was an error loading your language file.')
+ eval(`gotLang = ${fs.readFileSync(s.location.languages+'/en_CA.json','utf8')}`)
+ }
+ return gotLang
}
+ lang = getLanguageData(config.language)
//load languages dynamically
s.copySystemDefaultLanguage = function(){
//en_CA
- return Object.assign(lang,{})
+ return Object.assign({},getLanguageData(config.language))
}
s.listOfPossibleLanguages = []
fs.readdirSync(s.mainDirectory + '/languages').forEach(function(filename){
@@ -28,12 +34,15 @@ module.exports = function(s,config){
s.getLanguageFile = function(rule){
if(rule && rule !== ''){
var file = s.loadedLanguages[file]
+ s.debugLog(file)
if(!file){
try{
- s.loadedLanguages[rule] = require(s.location.languages+'/'+rule+'.json')
- s.loadedLanguages[rule] = Object.assign(s.copySystemDefaultLanguage(),s.loadedLanguages[rule])
+ let newLang = {}
+ eval(`newLang = ${fs.readFileSync(s.location.languages+'/'+rule+'.json','utf8')}`)
+ s.loadedLanguages[rule] = Object.assign(s.copySystemDefaultLanguage(),newLang)
file = s.loadedLanguages[rule]
}catch(err){
+ console.error(err)
file = s.copySystemDefaultLanguage()
}
}
@@ -42,5 +51,9 @@ module.exports = function(s,config){
}
return file
}
+ s.reloadLanguages = function(){
+ s.loadedLanguages = {};
+ s.loadedLanguages[config.language] = s.copySystemDefaultLanguage()
+ }
return lang
}
diff --git a/libs/monitor.js b/libs/monitor.js
index 0d57dbb4..d7009374 100644
--- a/libs/monitor.js
+++ b/libs/monitor.js
@@ -5,16 +5,22 @@ const exec = require('child_process').exec;
const Mp4Frag = require('mp4frag');
const onvif = require("shinobi-onvif");
const treekill = require('tree-kill');
-const request = require('request');
const connectionTester = require('connection-tester')
const SoundDetection = require('shinobi-sound-detection')
-const async = require("async");
const URL = require('url')
const {
Worker
} = require('worker_threads');
const { copyObject, createQueue, queryStringToObject, createQueryStringFromObject } = require('./common.js')
module.exports = function(s,config,lang){
+ const { fetchTimeout } = require('./basic/utils.js')(process.cwd(),config)
+ const isMasterNode = (
+ (
+ config.childNodes.enabled === true &&
+ config.childNodes.mode === 'master'
+ ) ||
+ config.childNodes.enabled === false
+ );
const {
probeMonitor,
getStreamInfoFromProbe,
@@ -27,6 +33,11 @@ module.exports = function(s,config,lang){
processKill,
cameraDestroy,
monitorConfigurationMigrator,
+ attachStreamChannelHandlers,
+ setActiveViewer,
+ getActiveViewerCount,
+ destroySubstreamProcess,
+ attachMainProcessHandlers,
} = require('./monitor/utils.js')(s,config,lang)
const {
addEventDetailsToString,
@@ -37,15 +48,21 @@ module.exports = function(s,config,lang){
setPresetForCurrentPosition
} = require('./control/ptz.js')(s,config,lang)
const {
- scanForOrphanedVideos
+ scanForOrphanedVideos,
+ reEncodeVideoAndBinOriginalAddToQueue,
} = require('./video/utils.js')(s,config,lang)
+ const {
+ selectNodeForOperation,
+ bindMonitorToChildNode
+ } = require('./childNode/utils.js')(s,config,lang)
const startMonitorInQueue = createQueue(1, 3)
s.initiateMonitorObject = function(e){
if(!s.group[e.ke]){s.group[e.ke]={}};
if(!s.group[e.ke].activeMonitors){s.group[e.ke].activeMonitors={}}
if(!s.group[e.ke].activeMonitors[e.mid]){s.group[e.ke].activeMonitors[e.mid]={}}
const activeMonitor = s.group[e.ke].activeMonitors[e.mid]
-
+ activeMonitor.ke = e.ke
+ activeMonitor.mid = e.mid
if(!activeMonitor.streamIn){activeMonitor.streamIn={}};
if(!activeMonitor.emitterChannel){activeMonitor.emitterChannel={}};
if(!activeMonitor.mp4frag){activeMonitor.mp4frag={}};
@@ -53,7 +70,7 @@ module.exports = function(s,config,lang){
if(!activeMonitor.contentWriter){activeMonitor.contentWriter={}};
if(!activeMonitor.childNodeStreamWriters){activeMonitor.childNodeStreamWriters={}};
if(!activeMonitor.eventBasedRecording){activeMonitor.eventBasedRecording={}};
- if(!activeMonitor.watch){activeMonitor.watch={}};
+ if(!activeMonitor.watch){activeMonitor.watch = []};
if(!activeMonitor.fixingVideos){activeMonitor.fixingVideos={}};
// if(!activeMonitor.viewerConnection){activeMonitor.viewerConnection={}};
// if(!activeMonitor.viewerConnectionCount){activeMonitor.viewerConnectionCount=0};
@@ -138,7 +155,7 @@ module.exports = function(s,config,lang){
return x.ar;
}
s.getStreamsDirectory = (monitor) => {
- return s.dir.streams + monitor.ke + '/' + monitor.mid + '/'
+ return s.dir.streams + monitor.ke + '/' + (monitor.mid || monitor.id) + '/'
}
s.getRawSnapshotFromMonitor = function(monitor,options){
return new Promise((resolve,reject) => {
@@ -185,7 +202,7 @@ module.exports = function(s,config,lang){
var snapBuffer = []
var temporaryImageFile = streamDir + s.gid(5) + '.jpg'
var iconImageFile = streamDir + 'icon.jpg'
- var ffmpegCmd = splitForFFPMEG(`-loglevel warning -re -probesize 100000 -analyzeduration 100000 ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f image2 -an -vf "fps=1" -vframes 1 "${temporaryImageFile}"`)
+ var ffmpegCmd = splitForFFPMEG(`-y -loglevel warning -re ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f image2 -an -frames:v 1 "${temporaryImageFile}"`)
checkExists(streamDir, function(success) {
if (success === false) {
fs.mkdirSync(streamDir, {recursive: true}, (err) => {s.debugLog(err)})
@@ -500,20 +517,7 @@ module.exports = function(s,config,lang){
s.checkDetails(e)
if(e.ke && config.doSnapshot === true){
if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
- if(s.group[e.ke].activeMonitors[e.mid].onvifConnection){
- const screenShot = await s.getSnapshotFromOnvif({
- username: onvifUsername,
- password: onvifPassword,
- uri: cameraResponse.uri,
- });
- s.tx({
- f: 'monitor_snapshot',
- snapshot: screenShot.toString('base64'),
- snapshot_format: 'b64',
- mid: e.mid,
- ke: e.ke
- },'GRP_'+e.ke)
- }else{
+ async function getRaw(){
var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
if(screenShot){
@@ -527,7 +531,27 @@ module.exports = function(s,config,lang){
}else{
s.debugLog('Damaged Snapshot Data')
s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
- }
+ }
+ }
+ if(s.group[e.ke].activeMonitors[e.mid].onvifConnection){
+ try{
+ const screenShot = await s.getSnapshotFromOnvif({
+ ke: e.ke,
+ mid: e.mid,
+ });
+ s.tx({
+ f: 'monitor_snapshot',
+ snapshot: screenShot.toString('base64'),
+ snapshot_format: 'b64',
+ mid: e.mid,
+ ke: e.ke
+ },'GRP_'+e.ke)
+ }catch(err){
+ s.debugLog(err)
+ await getRaw()
+ }
+ }else{
+ await getRaw()
}
}else{
s.tx({f:'monitor_snapshot',snapshot:'Disabled',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
@@ -587,7 +611,7 @@ module.exports = function(s,config,lang){
}
const createTimelapseDirectory = function(e,callback){
var directory = s.getTimelapseFrameDirectory(e)
- fs.mkdir(directory,function(err){
+ fs.mkdir(directory,{ recursive: true },function(err){
s.handleFolderError(err)
callback(err,directory)
})
@@ -752,55 +776,8 @@ module.exports = function(s,config,lang){
code: e.wantedStatusCode
});
//on unexpected exit restart
- s.group[e.ke].activeMonitors[e.id].spawn_exit = function(){
- if(s.group[e.ke].activeMonitors[e.id].isStarted === true){
- if(e.details.loglevel!=='quiet'){
- s.userLog(e,{type:lang['Process Unexpected Exit'],msg:{msg:lang.unexpectedExitText,cmd:s.group[e.ke].activeMonitors[e.id].ffmpeg}});
- }
- fatalError(e,'Process Unexpected Exit');
- scanForOrphanedVideos(e,{
- forceCheck: true,
- checkMax: 2
- })
- s.onMonitorUnexpectedExitExtensions.forEach(function(extender){
- extender(Object.assign(s.group[e.ke].rawMonitorConfigurations[e.id],{}),e)
- })
- }
- }
- s.group[e.ke].activeMonitors[e.id].spawn.on('end',s.group[e.ke].activeMonitors[e.id].spawn_exit)
- s.group[e.ke].activeMonitors[e.id].spawn.on('exit',s.group[e.ke].activeMonitors[e.id].spawn_exit)
- s.group[e.ke].activeMonitors[e.id].spawn.on('error',function(er){
- s.userLog(e,{type:'Spawn Error',msg:er});fatalError(e,'Spawn Error')
- })
- s.userLog(e,{type:lang['Process Started'],msg:{cmd:s.group[e.ke].activeMonitors[e.id].ffmpeg}})
- if(s.isWin === false){
- var strippedHost = s.stripAuthFromHost(e)
- var sendProcessCpuUsage = function(){
- s.getMonitorCpuUsage(e,function(percent){
- s.group[e.ke].activeMonitors[e.id].currentCpuUsage = percent
- s.tx({
- f: 'camera_cpu_usage',
- ke: e.ke,
- id: e.id,
- percent: percent
- },'MON_STREAM_'+e.ke+e.id)
- })
- }
- clearInterval(s.group[e.ke].activeMonitors[e.id].getMonitorCpuUsage)
- s.group[e.ke].activeMonitors[e.id].getMonitorCpuUsage = setInterval(function(){
- if(e.details.skip_ping !== '1'){
- connectionTester.test(strippedHost,e.port,2000,function(err,response){
- if(response.success){
- sendProcessCpuUsage()
- }else{
- launchMonitorProcesses(e)
- }
- })
- }else{
- sendProcessCpuUsage()
- }
- },1000 * 60)
- }
+ if(s.group[e.ke].activeMonitors[e.id].spawn)attachMainProcessHandlers(e,fatalError)
+ return s.group[e.ke].activeMonitors[e.id].spawn
}
const createEventCounter = function(monitor){
if(monitor.details.detector_obj_count === '1'){
@@ -923,42 +900,16 @@ module.exports = function(s,config,lang){
//frames from motion detect
if(e.details.detector_pam === '1'){
// s.group[e.ke].activeMonitors[e.id].spawn.stdio[3].pipe(s.group[e.ke].activeMonitors[e.id].p2p).pipe(s.group[e.ke].activeMonitors[e.id].pamDiff)
- s.group[e.ke].activeMonitors[e.id].spawn.stdio[3].on('data',function(buf){
- let theJson
- try{
- buf.toString().split('}{').forEach((object,n)=>{
- theJson = object
- if(object.substr(object.length - 1) !== '}')theJson += '}'
- if(object.substr(0,1) !== '{')theJson = '{' + theJson
- try{
- var data = JSON.parse(theJson)
- }catch(err){
- var data = JSON.parse(theJson + '}')
- }
- switch(data.f){
- case'trigger':
- triggerEvent(data)
- break;
- case's.tx':
- s.tx(data.data,data.to)
- break;
- }
- })
- }catch(err){
- console.log(theJson)
- console.log('There was an error parsing a detector event')
- console.log(err)
- }
- })
+ // spawn.stdio[3] is deprecated and now motion events are handled by dataPort
if(e.details.detector_use_detect_object === '1' && e.details.detector_use_motion === '1' ){
s.group[e.ke].activeMonitors[e.id].spawn.stdio[4].on('data',function(data){
onDetectorJpegOutputSecondary(e,data)
})
}else{
- s.group[e.ke].activeMonitors[e.id].spawn.stdio[4].on('data',function(data){
+ s.group[e.ke].activeMonitors[e.id].spawn.stdio[4].on('data',function(data){
onDetectorJpegOutputAlone(e,data)
})
- }
+ }
}else if(e.details.detector_use_detect_object === '1' && e.details.detector_send_frames !== '1'){
s.group[e.ke].activeMonitors[e.id].spawn.stdio[4].on('data',function(data){
onDetectorJpegOutputSecondary(e,data)
@@ -970,8 +921,9 @@ module.exports = function(s,config,lang){
}
}
//frames to stream
- var frameToStreamPrimary
- switch(e.details.stream_type){
+ var frameToStreamPrimary;
+ const streamType = e.details.stream_type;
+ switch(streamType){
case'mp4':
delete(s.group[e.ke].activeMonitors[e.id].mp4frag['MAIN'])
if(!s.group[e.ke].activeMonitors[e.id].mp4frag['MAIN'])s.group[e.ke].activeMonitors[e.id].mp4frag['MAIN'] = new Mp4Frag()
@@ -996,12 +948,6 @@ module.exports = function(s,config,lang){
s.group[e.ke].activeMonitors[e.id].emitter.emit('data',d)
}
break;
- case'h265':
- frameToStreamPrimary = function(d){
- resetStreamCheck(e)
- s.group[e.ke].activeMonitors[e.id].emitter.emit('data',d)
- }
- break;
case'b64':case undefined:case null:case'':
var buffer
frameToStreamPrimary = function(d){
@@ -1018,50 +964,28 @@ module.exports = function(s,config,lang){
}
break;
}
+ s.onMonitorCreateStreamPipeExtensions.forEach(function(extender){
+ if(!frameToStreamPrimary)frameToStreamPrimary = extender(streamType,e,resetStreamCheck)
+ });
if(frameToStreamPrimary){
s.group[e.ke].activeMonitors[e.id].spawn.stdout.on('data',frameToStreamPrimary)
}
if(e.details.stream_channels && e.details.stream_channels !== ''){
- var createStreamEmitter = function(channel,number){
- var pipeNumber = number+config.pipeAddition;
- if(!s.group[e.ke].activeMonitors[e.id].emitterChannel[pipeNumber]){
- s.group[e.ke].activeMonitors[e.id].emitterChannel[pipeNumber] = new events.EventEmitter().setMaxListeners(0);
- }
- var frameToStreamAdded
- switch(channel.stream_type){
- case'mp4':
- delete(s.group[e.ke].activeMonitors[e.id].mp4frag[pipeNumber])
- if(!s.group[e.ke].activeMonitors[e.id].mp4frag[pipeNumber])s.group[e.ke].activeMonitors[e.id].mp4frag[pipeNumber] = new Mp4Frag();
- s.group[e.ke].activeMonitors[e.id].spawn.stdio[pipeNumber].pipe(s.group[e.ke].activeMonitors[e.id].mp4frag[pipeNumber],{ end: false })
- break;
- case'mjpeg':
- frameToStreamAdded = function(d){
- s.group[e.ke].activeMonitors[e.id].emitterChannel[pipeNumber].emit('data',d)
- }
- break;
- case'flv':
- frameToStreamAdded = function(d){
- if(!s.group[e.ke].activeMonitors[e.id].firstStreamChunk[pipeNumber])s.group[e.ke].activeMonitors[e.id].firstStreamChunk[pipeNumber] = d;
- frameToStreamAdded = function(d){
- s.group[e.ke].activeMonitors[e.id].emitterChannel[pipeNumber].emit('data',d)
- }
- frameToStreamAdded(d)
- }
- break;
- case'h264':
- frameToStreamAdded = function(d){
- s.group[e.ke].activeMonitors[e.id].emitterChannel[pipeNumber].emit('data',d)
- }
- break;
- }
- if(frameToStreamAdded){
- s.group[e.ke].activeMonitors[e.id].spawn.stdio[pipeNumber].on('data',frameToStreamAdded)
- }
- }
- e.details.stream_channels.forEach(createStreamEmitter)
+ e.details.stream_channels.forEach((fields,number) => {
+ attachStreamChannelHandlers({
+ ke: e.ke,
+ mid: e.id,
+ fields: fields,
+ number: number,
+ ffmpegProcess: s.group[e.ke].activeMonitors[e.id].spawn,
+ })
+ })
}
}
const catchNewSegmentNames = function(e){
+ const monitorConfig = s.group[e.ke].rawMonitorConfigurations[e.id]
+ const monitorDetails = monitorConfig.details
+ const autoCompressionEnabled = monitorDetails.auto_compress_videos === '1'
var checkLog = function(d,x){return d.indexOf(x)>-1}
s.group[e.ke].activeMonitors[e.id].spawn.stdio[8].on('data',function(d){
d=d.toString();
@@ -1070,7 +994,7 @@ module.exports = function(s,config,lang){
s.insertCompletedVideo(e,{
file: filename,
events: s.group[e.ke].activeMonitors[e.id].detector_motion_count
- },function(err){
+ },function(err,response){
s.userLog(e,{type:lang['Video Finished'],msg:{filename:d}})
if(
e.details.detector === '1' &&
@@ -1088,6 +1012,21 @@ module.exports = function(s,config,lang){
ke : e.ke,
id : e.id
})
+ }else if(autoCompressionEnabled){
+ s.debugLog('Queue Automatic Compression',response.insertQuery)
+ reEncodeVideoAndBinOriginalAddToQueue({
+ video: response.insertQuery,
+ targetVideoCodec: 'vp9',
+ targetAudioCodec: 'libopus',
+ targetQuality: '-q:v 1 -q:a 1',
+ targetExtension: 'webm',
+ doSlowly: false,
+ automated: true,
+ }).then((encodeResponse) => {
+ s.debugLog('Complete Automatic Compression',encodeResponse)
+ }).catch((err) => {
+ console.log(err)
+ })
}
s.group[e.ke].activeMonitors[e.id].detector_motion_count = []
})
@@ -1158,11 +1097,11 @@ module.exports = function(s,config,lang){
s.group[e.ke].activeMonitors[monitorId].detector_notrigger_webhook = s.createTimeout('detector_notrigger_webhook',s.group[e.ke].activeMonitors[monitorId],currentConfig.detector_notrigger_webhook_timeout,10)
var detector_notrigger_webhook_url = addEventDetailsToString(e,currentConfig.detector_notrigger_webhook_url)
var webhookMethod = currentConfig.detector_notrigger_webhook_method
- if(!webhookMethod || webhookMethod === '')webhookMethod = 'GET'
- request(detector_notrigger_webhook_url,{method: webhookMethod,encoding:null},function(err,data){
- if(err){
- s.userLog(d,{type:lang["Event Webhook Error"],msg:{error:err,data:data}})
- }
+ if(!webhookMethod || webhookMethod === '')webhookMethod = 'GET';
+ fetchTimeout(detector_notrigger_webhook_url,10000,{
+ method: webhookMethod
+ }).catch((err) => {
+ s.userLog(d,{type:lang["Event Webhook Error"],msg:{error:err,data:data}})
})
}
if(currentConfig.detector_notrigger_command_enable === '1' && !s.group[e.ke].activeMonitors[monitorId].detector_notrigger_command){
@@ -1278,27 +1217,29 @@ module.exports = function(s,config,lang){
if(pingResponse.success === true){
activeMonitor.isRecording = true
try{
- createCameraFfmpegProcess(e)
- createCameraStreamHandlers(e)
+ var mainProcess = createCameraFfmpegProcess(e)
createEventCounter(e)
- if(e.type === 'dashcam' || e.type === 'socket'){
- setTimeout(function(){
- activeMonitor.allowStdinWrite = true
- s.txToDashcamUsers({
- f : 'enable_stream',
- ke : e.ke,
- mid : e.id
- },e.ke)
- },30000)
- }
- if(
- e.functionMode === 'record' ||
- e.type === 'mjpeg' ||
- e.type === 'h264' ||
- e.type === 'local'
- ){
- catchNewSegmentNames(e)
- cameraFilterFfmpegLog(e)
+ if(mainProcess){
+ createCameraStreamHandlers(e)
+ if(e.type === 'dashcam' || e.type === 'socket'){
+ setTimeout(function(){
+ activeMonitor.allowStdinWrite = true
+ s.txToDashcamUsers({
+ f : 'enable_stream',
+ ke : e.ke,
+ mid : e.id
+ },e.ke)
+ },30000)
+ }
+ if(
+ e.functionMode === 'record' ||
+ e.type === 'mjpeg' ||
+ e.type === 'h264' ||
+ e.type === 'local'
+ ){
+ catchNewSegmentNames(e)
+ cameraFilterFfmpegLog(e)
+ }
}
clearTimeout(activeMonitor.onMonitorStartTimer)
activeMonitor.onMonitorStartTimer = setTimeout(() => {
@@ -1349,6 +1290,8 @@ module.exports = function(s,config,lang){
//data, options
d : s.group[e.ke].rawMonitorConfigurations[e.id]
},activeMonitor.childNodeId)
+ clearTimeout(activeMonitor.recordingChecker);
+ clearTimeout(activeMonitor.streamChecker);
}
if(
e.type !== 'socket' &&
@@ -1364,37 +1307,21 @@ module.exports = function(s,config,lang){
}
try{
if(config.childNodes.enabled === true && config.childNodes.mode === 'master'){
- var copiedMonitorObject = s.cleanMonitorObject(s.group[e.ke].rawMonitorConfigurations[e.id])
- var childNodeList = Object.keys(s.childNodes)
- if(childNodeList.length > 0){
- e.childNodeFound = false
- var selectNode = function(ip){
- e.childNodeFound = true
- e.childNodeSelected = ip
- }
- var nodeWithLowestActiveCamerasCount = 65535
- var nodeWithLowestActiveCameras = null
- childNodeList.forEach(function(ip){
- delete(s.childNodes[ip].activeCameras[e.ke+e.id])
- var nodeCameraCount = Object.keys(s.childNodes[ip].activeCameras).length
- if(!s.childNodes[ip].dead && nodeCameraCount < nodeWithLowestActiveCamerasCount && s.childNodes[ip].cpu < 75){
- nodeWithLowestActiveCamerasCount = nodeCameraCount
- nodeWithLowestActiveCameras = ip
- }
- })
- if(nodeWithLowestActiveCameras)selectNode(nodeWithLowestActiveCameras)
- if(e.childNodeFound === true){
- s.childNodes[e.childNodeSelected].activeCameras[e.ke+e.id] = copiedMonitorObject
- activeMonitor.childNode = e.childNodeSelected
- activeMonitor.childNodeId = s.childNodes[e.childNodeSelected].cnid;
- s.cx({f:'sync',sync:s.group[e.ke].rawMonitorConfigurations[e.id],ke:e.ke,mid:e.id},activeMonitor.childNodeId);
+ selectNodeForOperation({
+ ke: e.ke,
+ mid: e.id,
+ }).then((selectedNode) => {
+ if(selectedNode){
+ bindMonitorToChildNode({
+ ke: e.ke,
+ mid: e.id,
+ childNodeId: selectedNode,
+ })
doOnChildMachine()
}else{
startMonitorInQueue.push(doOnThisMachine,function(){})
}
- }else{
- startMonitorInQueue.push(doOnThisMachine,function(){})
- }
+ });
}else{
startMonitorInQueue.push(doOnThisMachine,function(){})
}
@@ -1568,26 +1495,23 @@ module.exports = function(s,config,lang){
s.initiateMonitorObject({ke:e.ke,mid:e.id})
switch(e.functionMode){
case'watch_on'://live streamers - join
- if(!cn.monitorsCurrentlyWatching){cn.monitorsCurrentlyWatching = {}}
- if(!cn.monitorsCurrentlyWatching[e.id]){cn.monitorsCurrentlyWatching[e.id]={ke:e.ke}}
- s.group[e.ke].activeMonitors[e.id].watch[cn.id]={};
- var numberOfViewers = Object.keys(s.group[e.ke].activeMonitors[e.id].watch).length
- s.tx({
- viewers: numberOfViewers,
- ke: e.ke,
- id: e.id
- },'MON_'+e.ke+e.id)
+ if(!cn.monitorsCurrentlyWatching){cn.monitorsCurrentlyWatching = {}}
+ if(!cn.monitorsCurrentlyWatching[e.id]){cn.monitorsCurrentlyWatching[e.id]={ke:e.ke}}
+ setActiveViewer(e.ke,e.id,cn.id,true)
+ s.group[e.ke].activeMonitors[e.id].allowDestroySubstream = false
+ clearTimeout(s.group[e.ke].activeMonitors[e.id].noViewerCountDisableSubstream)
break;
case'watch_off'://live streamers - leave
if(cn.monitorsCurrentlyWatching){delete(cn.monitorsCurrentlyWatching[e.id])}
- var numberOfViewers = 0
- delete(s.group[e.ke].activeMonitors[e.id].watch[cn.id]);
- numberOfViewers = Object.keys(s.group[e.ke].activeMonitors[e.id].watch).length
- s.tx({
- viewers: numberOfViewers,
- ke: e.ke,
- id: e.id
- },'MON_'+e.ke+e.id)
+ setActiveViewer(e.ke,e.id,cn.id,false)
+ clearTimeout(s.group[e.ke].activeMonitors[e.id].noViewerCountDisableSubstream)
+ s.group[e.ke].activeMonitors[e.id].noViewerCountDisableSubstream = setTimeout(async () => {
+ let currentCount = getActiveViewerCount(e.ke,e.id)
+ if(currentCount === 0 && s.group[e.ke].activeMonitors[e.id].subStreamProcess){
+ s.group[e.ke].activeMonitors[e.id].allowDestroySubstream = true
+ await destroySubstreamProcess(s.group[e.ke].activeMonitors[e.id])
+ }
+ },10000)
break;
case'restart'://restart monitor
s.sendMonitorStatus({
@@ -1605,13 +1529,13 @@ module.exports = function(s,config,lang){
if(!s.group[e.ke]||!s.group[e.ke].activeMonitors[e.id]){return}
if(config.childNodes.enabled === true && config.childNodes.mode === 'master' && s.group[e.ke].activeMonitors[e.id].childNode && s.childNodes[s.group[e.ke].activeMonitors[e.id].childNode].activeCameras[e.ke+e.id]){
s.group[e.ke].activeMonitors[e.id].isStarted = false
+ s.cx({f:'sync',sync:s.group[e.ke].rawMonitorConfigurations[e.id],ke:e.ke,mid:e.id},s.group[e.ke].activeMonitors[e.id].childNodeId);
s.cx({
//function
f : 'cameraStop',
//data, options
d : s.group[e.ke].rawMonitorConfigurations[e.id]
},s.group[e.ke].activeMonitors[e.id].childNodeId)
- s.cx({f:'sync',sync:s.group[e.ke].rawMonitorConfigurations[e.id],ke:e.ke,mid:e.id},s.group[e.ke].activeMonitors[e.id].childNodeId);
}else{
closeEventBasedRecording(e)
if(s.group[e.ke].activeMonitors[e.id].fswatch){s.group[e.ke].activeMonitors[e.id].fswatch.close();delete(s.group[e.ke].activeMonitors[e.id].fswatch)}
@@ -1653,15 +1577,17 @@ module.exports = function(s,config,lang){
status: wantedStatus,
code: wantedStatusCode,
})
- setTimeout(() => {
- scanForOrphanedVideos({
- ke: e.ke,
- mid: e.id,
- },{
- forceCheck: true,
- checkMax: 2
- })
- },2000)
+ if(isMasterNode){
+ setTimeout(() => {
+ scanForOrphanedVideos({
+ ke: e.ke,
+ mid: e.id,
+ },{
+ forceCheck: true,
+ checkMax: 2
+ })
+ },2000)
+ }
clearTimeout(s.group[e.ke].activeMonitors[e.id].onMonitorStartTimer)
s.onMonitorStopExtensions.forEach(function(extender){
extender(Object.assign(s.group[e.ke].rawMonitorConfigurations[e.id],{}),e)
@@ -1676,6 +1602,15 @@ module.exports = function(s,config,lang){
//stop action, monitor already started or recording
return
}
+ if(activeMonitor.masterSaysToStop === true){
+ s.sendMonitorStatus({
+ id: e.id,
+ ke: e.ke,
+ status: lang.Stopped,
+ code: 5,
+ })
+ return;
+ }
if(config.probeMonitorOnStart === true){
const probeResponse = await probeMonitor(s.group[e.ke].rawMonitorConfigurations[e.id],2000,true)
const probeStreams = getStreamInfoFromProbe(probeResponse.result)
@@ -1856,6 +1791,7 @@ module.exports = function(s,config,lang){
monitorRestrictions.push(['or','mid','=',v])
}
})
+ console.log(monitorRestrictions)
}catch(er){
}
}else if(
@@ -1873,20 +1809,104 @@ module.exports = function(s,config,lang){
){}
return monitorRestrictions
}
- // s.checkViewerConnectionsForMonitor = function(monitorObject){
- // var monitorConfig = s.group[monitorObject.ke].rawMonitorConfigurations[monitorObject.mid]
- // if(monitorConfig.mode === 'start'){
- //
- // }
- // }
- // s.addViewerConnectionForMonitor = function(monitorObject,viewerDetails){
- // s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnection[viewerDetails.viewerId] = viewerDetails
- // s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount += 1
- // return s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount
- // }
- // s.removeViewerConnectionForMonitor = function(monitorObject,viewerDetails){
- // delete(s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnection[viewerDetails.viewerId])
- // s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount -= 1
- // return s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount
- // }
+ s.checkPermission = (user) => {
+ // provide "user" object given from "s.auth"
+ const isSubAccount = !!user.details.sub
+ const response = {
+ isSubAccount,
+ hasAllPermissions: isSubAccount && user.details.allmonitors === '1',
+ isRestricted: isSubAccount && user.details.allmonitors !== '1',
+ isRestrictedApiKey: false,
+ apiKeyPermissions: {},
+ userPermissions: {},
+ }
+ const permissions = user.permissions
+ const details = user.details;
+ [
+ 'auth_socket',
+ 'get_monitors',
+ 'control_monitors',
+ 'get_logs',
+ 'watch_stream',
+ 'watch_snapshot',
+ 'watch_videos',
+ 'delete_videos',
+ ].forEach((key) => {
+ const permissionOff = permissions[key] === '0';
+ response.apiKeyPermissions[key] = permissions[key] === '1';
+ response.apiKeyPermissions[`${key}_disallowed`] = permissionOff;
+ response.isRestrictedApiKey = response.isRestrictedApiKey || permissionOff;
+ });
+ // Base Level Permissions
+ // allmonitors : All Monitors and Privileges
+ // monitor_create : Can Create and Delete Monitors
+ // user_change : Can Change User Settings
+ // view_logs : Can View Logs
+ [
+ 'allmonitors',
+ 'monitor_create',
+ 'user_change',
+ 'view_logs',
+ ].forEach((key) => {
+ response.userPermissions[key] = details[key] === '1' || !details[key];
+ response.userPermissions[`${key}_disallowed`] = details[key] === '0';
+ });
+ return response
+ }
+ s.getMonitorsPermitted = (userDetails,monitorId) => {
+ const monitorRestrictions = []
+ const monitors = {}
+ function setMonitorPermissions(mid){
+ // monitors : Can View Monitor
+ // monitor_edit : Can Edit Monitor (Delete as well)
+ // video_view : Can View Videos and Events
+ // video_delete : Can Delete Videos and Events
+ [
+ 'monitors',
+ 'monitor_edit',
+ 'video_view',
+ 'video_delete',
+ ].forEach((key) => {
+ monitors[`${mid}_${key}`] = userDetails[key] && userDetails[key].indexOf(mid) > -1 || false;
+ });
+ return true
+ }
+ function addToQuery(mid,n){
+ if(n === 0){
+ monitorRestrictions.push(['mid','=',mid])
+ }else{
+ monitorRestrictions.push(['or','mid','=',mid])
+ }
+ };
+ if(
+ !monitorId &&
+ userDetails.sub &&
+ userDetails.monitors &&
+ userDetails.allmonitors !== '1'
+ ){
+ try{
+ userDetails.monitors = s.parseJSON(userDetails.monitors)
+ userDetails.monitors.forEach(function(v,n){
+ setMonitorPermissions(v)
+ addToQuery(v,n)
+ })
+ }catch(err){
+ s.debugLog(err)
+ }
+ }else if(
+ monitorId && (
+ !userDetails.sub ||
+ userDetails.allmonitors !== '0' ||
+ userDetails.monitors.indexOf(monitorId) >- 1
+ )
+ ){
+ setMonitorPermissions(monitorId)
+ addToQuery(monitorId,0)
+ }
+ return {
+ monitorPermissions: monitors,
+ // queryConditions
+ monitorRestrictions: monitorRestrictions,
+ }
+ }
}
diff --git a/libs/monitor/utils.js b/libs/monitor/utils.js
index cfcf8446..4e66e162 100644
--- a/libs/monitor/utils.js
+++ b/libs/monitor/utils.js
@@ -1,21 +1,39 @@
const fs = require('fs');
const treekill = require('tree-kill');
const spawn = require('child_process').spawn;
+const events = require('events');
+const Mp4Frag = require('mp4frag');
+const streamViewerCountTimeouts = {}
module.exports = (s,config,lang) => {
const {
+ scanForOrphanedVideos
+ } = require('../video/utils.js')(s,config,lang)
+ const {
+ createPipeArray,
splitForFFPMEG,
+ sanitizedFfmpegCommand,
} = require('../ffmpeg/utils.js')(s,config,lang)
+ const {
+ buildSubstreamString,
+ getDefaultSubstreamFields,
+ } = require('../ffmpeg/builders.js')(s,config,lang)
const getUpdateableFields = require('./updatedFields.js')
const processKill = (proc) => {
const response = {ok: true}
return new Promise((resolve,reject) => {
+ if(!proc){
+ resolve(response)
+ return
+ }
function sendError(err){
response.ok = false
response.err = err
resolve(response)
}
try{
- proc.stdin.write("q\r\n")
+ if(proc && proc.stdin) {
+ proc.stdin.write("q\r\n");
+ }
setTimeout(() => {
if(proc && proc.kill){
if(s.isWin){
@@ -86,13 +104,17 @@ module.exports = (s,config,lang) => {
if(activeMonitor.onChildNodeExit){
activeMonitor.onChildNodeExit()
}
- activeMonitor.spawn.stdio.forEach(function(stdio){
- try{
- stdio.unpipe()
- }catch(err){
- console.log(err)
- }
- })
+ try{
+ activeMonitor.spawn.stdio.forEach(function(stdio){
+ try{
+ stdio.unpipe()
+ }catch(err){
+ console.log(err)
+ }
+ })
+ }catch(err){
+ // s.debugLog(err)
+ }
if(activeMonitor.mp4frag){
var mp4FragChannels = Object.keys(activeMonitor.mp4frag)
mp4FragChannels.forEach(function(channel){
@@ -108,6 +130,10 @@ module.exports = (s,config,lang) => {
}else{
processKill(proc).then((response) => {
s.debugLog(`cameraDestroy`,response)
+ activeMonitor.allowDestroySubstream = true
+ destroySubstreamProcess(activeMonitor).then((response) => {
+ if(response.hadSubStream)s.debugLog(`cameraDestroy`,response.closeResponse)
+ })
})
}
}
@@ -136,7 +162,7 @@ module.exports = (s,config,lang) => {
})
}
const temporaryImageFile = streamDir + s.gid(5) + '.jpg'
- const ffmpegCmd = splitForFFPMEG(`-loglevel warning -re -stimeout 30000000 -probesize 100000 -analyzeduration 100000 ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f image2 -an -vf "fps=1" -vframes 1 "${temporaryImageFile}"`)
+ const ffmpegCmd = splitForFFPMEG(`-y -loglevel warning -re ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f image2 -an -frames:v 1 "${temporaryImageFile}"`)
const snapProcess = spawn('ffmpeg',ffmpegCmd,{detached: true})
snapProcess.stderr.on('data',function(data){
// s.debugLog(data.toString())
@@ -195,11 +221,367 @@ module.exports = (s,config,lang) => {
}
})
}
+ const spawnSubstreamProcess = function(e){
+ // e = monitorConfig
+ try{
+ const groupKey = e.ke
+ const monitorId = e.mid
+ const monitorConfig = Object.assign({},s.group[groupKey].rawMonitorConfigurations[monitorId])
+ const monitorDetails = monitorConfig.details
+ const activeMonitor = s.group[e.ke].activeMonitors[e.mid]
+ const channelNumber = 1 + (monitorDetails.stream_channels || []).length
+ const ffmpegCommand = [`-progress pipe:5`];
+ const logLevel = monitorDetails.loglevel ? e.details.loglevel : 'warning'
+ const stdioPipes = createPipeArray({}, 2)
+ const substreamConfig = monitorConfig.details.substream
+ substreamConfig.input.type = !substreamConfig.input.fulladdress ? monitorConfig.type : substreamConfig.input.type || monitorConfig.details.rtsp_transport
+ substreamConfig.input.fulladdress = substreamConfig.input.fulladdress || s.buildMonitorUrl(monitorConfig)
+ substreamConfig.input.rtsp_transport = substreamConfig.input.rtsp_transport || monitorConfig.details.rtsp_transport
+ const {
+ inputAndConnectionFields,
+ outputFields,
+ } = getDefaultSubstreamFields(monitorConfig);
+ ([
+ buildSubstreamString(channelNumber + config.pipeAddition,e),
+ ]).forEach(function(commandStringPart){
+ ffmpegCommand.push(commandStringPart)
+ });
+ const ffmpegCommandString = ffmpegCommand.join(' ')
+ activeMonitor.ffmpegSubstream = sanitizedFfmpegCommand(e,ffmpegCommandString)
+ const ffmpegCommandParsed = splitForFFPMEG(ffmpegCommandString)
+ activeMonitor.subStreamChannel = channelNumber;
+ s.userLog({
+ ke: e.ke,
+ mid: e.mid,
+ },
+ {
+ type: lang["Substream Process"],
+ msg: {
+ msg: lang["Process Started"],
+ cmd: ffmpegCommandString,
+ },
+ });
+ const subStreamProcess = spawn(config.ffmpegDir,ffmpegCommandParsed,{detached: true,stdio: stdioPipes})
+ attachStreamChannelHandlers({
+ ke: e.ke,
+ mid: e.mid,
+ fields: Object.assign({},inputAndConnectionFields,outputFields),
+ number: activeMonitor.subStreamChannel,
+ ffmpegProcess: subStreamProcess,
+ })
+ if(config.debugLog === true){
+ subStreamProcess.stderr.on('data',(data) => {
+ console.log(`${e.ke} ${e.mid}`)
+ console.log(data.toString())
+ })
+ }
+ if(logLevel !== 'quiet'){
+ subStreamProcess.stderr.on('data',(data) => {
+ s.userLog({
+ ke: e.ke,
+ mid: e.mid,
+ },{
+ type: lang["Substream Process"],
+ msg: data.toString()
+ })
+ })
+ }
+ subStreamProcess.on('close',(data) => {
+ if(!activeMonitor.allowDestroySubstream){
+ subStreamProcess.stderr.on('data',(data) => {
+ s.userLog({
+ ke: e.ke,
+ mid: e.mid,
+ },
+ {
+ type: lang["Substream Process"],
+ msg: lang["Process Crashed for Monitor"],
+ })
+ })
+ setTimeout(() => {
+ spawnSubstreamProcess(e)
+ },2000)
+ }
+ })
+ activeMonitor.subStreamProcess = subStreamProcess
+ s.tx({
+ f: 'substream_start',
+ mid: e.mid,
+ ke: e.ke,
+ channel: activeMonitor.subStreamChannel
+ },'GRP_'+e.ke);
+ return subStreamProcess
+ }catch(err){
+ s.systemLog(err)
+ return null
+ }
+ }
+ const destroySubstreamProcess = async function(activeMonitor){
+ // e = monitorConfig.details.substream
+ const response = {
+ hadSubStream: false,
+ alreadyClosing: false
+ }
+ try{
+ if(activeMonitor.subStreamProcessClosing){
+ response.alreadyClosing = true
+ }else if(activeMonitor.subStreamProcess){
+ activeMonitor.subStreamProcessClosing = true
+ activeMonitor.subStreamChannel = null;
+ const closeResponse = await processKill(activeMonitor.subStreamProcess)
+ response.hadSubStream = true
+ response.closeResponse = closeResponse
+ delete(activeMonitor.subStreamProcess)
+ s.tx({
+ f: 'substream_end',
+ mid: activeMonitor.mid,
+ ke: activeMonitor.ke
+ },'GRP_'+activeMonitor.ke);
+ activeMonitor.subStreamProcessClosing = false
+ }
+ }catch(err){
+ s.debugLog('destroySubstreamProcess',err)
+ }
+ return response
+ }
+ function attachStreamChannelHandlers(options){
+ const fields = options.fields
+ const number = options.number
+ const ffmpegProcess = options.ffmpegProcess
+ const activeMonitor = s.group[options.ke].activeMonitors[options.mid]
+ const pipeNumber = number + config.pipeAddition;
+ if(!activeMonitor.emitterChannel[pipeNumber]){
+ activeMonitor.emitterChannel[pipeNumber] = new events.EventEmitter().setMaxListeners(0);
+ }
+ let frameToStreamAdded
+ switch(fields.stream_type){
+ case'mp4':
+ delete(activeMonitor.mp4frag[pipeNumber])
+ if(!activeMonitor.mp4frag[pipeNumber])activeMonitor.mp4frag[pipeNumber] = new Mp4Frag();
+ ffmpegProcess.stdio[pipeNumber].pipe(activeMonitor.mp4frag[pipeNumber],{ end: false })
+ break;
+ case'mjpeg':
+ frameToStreamAdded = function(d){
+ activeMonitor.emitterChannel[pipeNumber].emit('data',d)
+ }
+ break;
+ case'flv':
+ frameToStreamAdded = function(d){
+ if(!activeMonitor.firstStreamChunk[pipeNumber])activeMonitor.firstStreamChunk[pipeNumber] = d;
+ frameToStreamAdded = function(d){
+ activeMonitor.emitterChannel[pipeNumber].emit('data',d)
+ }
+ frameToStreamAdded(d)
+ }
+ break;
+ case'h264':
+ frameToStreamAdded = function(d){
+ activeMonitor.emitterChannel[pipeNumber].emit('data',d)
+ }
+ break;
+ }
+ if(frameToStreamAdded){
+ ffmpegProcess.stdio[pipeNumber].on('data',frameToStreamAdded)
+ }
+ }
+ function setActiveViewer(groupKey,monitorId,connectionId,isBeingAdded){
+ const viewerList = s.group[groupKey].activeMonitors[monitorId].watch;
+ if(isBeingAdded){
+ if(viewerList.indexOf(connectionId) > -1)viewerList.push(connectionId);
+ }else{
+ viewerList.splice(viewerList.indexOf(connectionId), 1)
+ }
+ const numberOfViewers = viewerList.length
+ s.tx({
+ f: 'viewer_count',
+ viewers: numberOfViewers,
+ ke: groupKey,
+ id: monitorId
+ },'MON_' + groupKey + monitorId)
+ return numberOfViewers;
+ }
+ function getActiveViewerCount(groupKey,monitorId){
+ const viewerList = s.group[groupKey].activeMonitors[monitorId].watch;
+ const numberOfViewers = viewerList.length
+ return numberOfViewers;
+ }
+ function setTimedActiveViewerForHttp(req){
+ const groupKey = req.params.ke
+ const connectionId = req.params.auth
+ const loggedInUser = s.group[groupKey].users[connectionId]
+ if(!loggedInUser){
+ const monitorId = req.params.id
+ const viewerList = s.group[groupKey].activeMonitors[monitorId].watch
+ const theViewer = viewerList[connectionId]
+ if(!theViewer){
+ setActiveViewer(groupKey,monitorId,connectionId,true)
+ }
+ clearTimeout(streamViewerCountTimeouts[req.originalUrl])
+ streamViewerCountTimeouts[req.originalUrl] = setTimeout(() => {
+ setActiveViewer(groupKey,monitorId,connectionId,false)
+ },5000)
+ }else{
+ s.debugLog(`User is Logged in, Don't add to viewer count`);
+ }
+ }
+ function attachMainProcessHandlers(e,fatalError){
+ s.group[e.ke].activeMonitors[e.id].spawn_exit = function(){
+ if(s.group[e.ke].activeMonitors[e.id].isStarted === true){
+ if(e.details.loglevel!=='quiet'){
+ s.userLog(e,{type:lang['Process Unexpected Exit'],msg:{msg:lang.unexpectedExitText,cmd:s.group[e.ke].activeMonitors[e.id].ffmpeg}});
+ }
+ fatalError(e,'Process Unexpected Exit');
+ scanForOrphanedVideos(e,{
+ forceCheck: true,
+ checkMax: 2
+ })
+ s.onMonitorUnexpectedExitExtensions.forEach(function(extender){
+ extender(Object.assign(s.group[e.ke].rawMonitorConfigurations[e.id],{}),e)
+ })
+ }
+ }
+ s.group[e.ke].activeMonitors[e.id].spawn.on('end',s.group[e.ke].activeMonitors[e.id].spawn_exit)
+ s.group[e.ke].activeMonitors[e.id].spawn.on('exit',s.group[e.ke].activeMonitors[e.id].spawn_exit)
+ s.group[e.ke].activeMonitors[e.id].spawn.on('error',function(er){
+ s.userLog(e,{type:'Spawn Error',msg:er});fatalError(e,'Spawn Error')
+ })
+ s.userLog(e,{type:lang['Process Started'],msg:{cmd:s.group[e.ke].activeMonitors[e.id].ffmpeg}})
+ // if(s.isWin === false){
+ // var strippedHost = s.stripAuthFromHost(e)
+ // var sendProcessCpuUsage = function(){
+ // s.getMonitorCpuUsage(e,function(percent){
+ // s.group[e.ke].activeMonitors[e.id].currentCpuUsage = percent
+ // s.tx({
+ // f: 'camera_cpu_usage',
+ // ke: e.ke,
+ // id: e.id,
+ // percent: percent
+ // },'MON_STREAM_'+e.ke+e.id)
+ // })
+ // }
+ // clearInterval(s.group[e.ke].activeMonitors[e.id].getMonitorCpuUsage)
+ // s.group[e.ke].activeMonitors[e.id].getMonitorCpuUsage = setInterval(function(){
+ // if(e.details.skip_ping !== '1'){
+ // connectionTester.test(strippedHost,e.port,2000,function(err,response){
+ // if(response.success){
+ // sendProcessCpuUsage()
+ // }else{
+ // launchMonitorProcesses(e)
+ // }
+ // })
+ // }else{
+ // sendProcessCpuUsage()
+ // }
+ // },1000 * 60)
+ // }
+ }
+ async function deleteMonitorData(groupKey,monitorId){
+ // deleteVideos
+ // deleteFileBinFiles
+ // deleteTimelapseFrames
+ async function deletePath(thePath){
+ try{
+ await fs.promises.stat(thePath)
+ await fs.promises.rm(thePath, {recursive: true})
+ }catch(err){
+
+ }
+ }
+ async function deleteFromTable(tableName){
+ await s.knexQueryPromise({
+ action: "delete",
+ table: tableName,
+ where: {
+ ke: groupKey,
+ mid: monitorId,
+ }
+ })
+ }
+ async function getSizeFromTable(tableName){
+ const response = await s.knexQueryPromise({
+ action: "select",
+ columns: "size",
+ table: tableName,
+ where: {
+ ke: groupKey,
+ mid: monitorId,
+ }
+ })
+ const rows = response.rows
+ let size = 0
+ for (let i = 0; i < rows.length; i++) {
+ const row = rows[i]
+ size += row.size
+ }
+ return size
+ }
+ async function adjustSpaceCounterForTableWithAddStorage(tableName,storageType){
+ // does normal videos and addStorage
+ const response = await s.knexQueryPromise({
+ action: "select",
+ columns: "ke,mid,details,size",
+ table: tableName || 'Videos',
+ where: {
+ ke: groupKey,
+ mid: monitorId,
+ }
+ })
+ const rows = response.rows
+ for (let i = 0; i < rows.length; i++) {
+ const video = rows[i]
+ const storageIndex = s.getVideoStorageIndex(video)
+ if(storageIndex){
+ s.setDiskUsedForGroupAddStorage(video.ke,{
+ size: -(video.size / 1048576),
+ storageIndex: storageIndex
+ },storageType)
+ }else{
+ s.setDiskUsedForGroup(video.ke,-(video.size / 1048576),storageType)
+ }
+ }
+ }
+ async function adjustSpaceCounter(tableName,storageType){
+ const amount = await getSizeFromTable(tableName)
+ s.setDiskUsedForGroup(groupKey,-amount,storageType)
+ }
+ const videosDir = s.dir.videos + `${groupKey}/${monitorId}`
+ const binDir = s.dir.fileBin + `${groupKey}/${monitorId}`
+
+ // videos and addStorage
+ await adjustSpaceCounterForTableWithAddStorage('Timelapse Frames','timelapeFrames')
+ await adjustSpaceCounterForTableWithAddStorage('Videos')
+ await deleteFromTable('Videos')
+ await deletePath(videosDir)
+ for (let i = 0; i < s.dir.addStorage.length; i++) {
+ const storage = s.dir.addStorage[i]
+ const addStorageDir = storage.path + groupKey + '/' + monitorId
+ await deletePath(addStorageDir)
+ await deletePath(addStorageDir + '_timelapse')
+ }
+
+ // timelapse frames
+ await adjustSpaceCounter('Timelapse Frames','timelapeFrames')
+ await deleteFromTable('Timelapse Frames')
+ await deletePath(videosDir + '_timelapse')
+
+ // fileBin
+ await adjustSpaceCounter('Files','fileBin')
+ await deleteFromTable('Files')
+ await deletePath(binDir)
+ }
return {
+ deleteMonitorData,
cameraDestroy: cameraDestroy,
createSnapshot: createSnapshot,
processKill: processKill,
addCredentialsToStreamLink: addCredentialsToStreamLink,
monitorConfigurationMigrator: monitorConfigurationMigrator,
+ spawnSubstreamProcess: spawnSubstreamProcess,
+ destroySubstreamProcess: destroySubstreamProcess,
+ attachStreamChannelHandlers: attachStreamChannelHandlers,
+ setActiveViewer: setActiveViewer,
+ getActiveViewerCount: getActiveViewerCount,
+ setTimedActiveViewerForHttp: setTimedActiveViewerForHttp,
+ attachMainProcessHandlers: attachMainProcessHandlers,
}
}
diff --git a/libs/notification.js b/libs/notification.js
index 3db2b604..296f462a 100644
--- a/libs/notification.js
+++ b/libs/notification.js
@@ -10,8 +10,18 @@ module.exports = function(s,config,lang){
d.screenshotBuffer = screenShot
}
}
- require('./notifications/email.js')(s,config,lang,getSnapshot)
+ if(
+ config.mail &&
+ config.mail.auth &&
+ config.mail.auth.user !== 'your_email@gmail.com' &&
+ config.mail.auth.pass !== 'your_password_or_app_specific_password'
+ ){
+ require('./notifications/email.js')(s,config,lang,getSnapshot)
+ }
+ require('./notifications/emailByUser.js')(s,config,lang,getSnapshot)
require('./notifications/discordBot.js')(s,config,lang,getSnapshot)
require('./notifications/telegram.js')(s,config,lang,getSnapshot)
require('./notifications/pushover.js')(s,config,lang,getSnapshot)
+ require('./notifications/webhook.js')(s,config,lang,getSnapshot)
+ require('./notifications/mqtt.js')(s,config,lang,getSnapshot)
}
diff --git a/libs/notifications/discordBot.js b/libs/notifications/discordBot.js
index 336177c5..0f39e60a 100644
--- a/libs/notifications/discordBot.js
+++ b/libs/notifications/discordBot.js
@@ -48,14 +48,14 @@ module.exports = function(s,config,lang,getSnapshot){
}
}
const onEventTriggerBeforeFilterForDiscord = function(d,filter){
- filter.discord = true
+ filter.discord = false
}
const onEventTriggerForDiscord = async (d,filter) => {
const monitorConfig = s.group[d.ke].rawMonitorConfigurations[d.id]
// d = event object
//discord bot
- const isEnabled = monitorConfig.details.detector_discordbot === '1' || monitorConfig.details.notify_discord === '1'
- if(filter.discord && s.group[d.ke].discordBot && isEnabled && !s.group[d.ke].activeMonitors[d.id].detector_discordbot){
+ const isEnabled = filter.discord || monitorConfig.details.detector_discordbot === '1' || monitorConfig.details.notify_discord === '1'
+ if(s.group[d.ke].discordBot && isEnabled && !s.group[d.ke].activeMonitors[d.id].detector_discordbot){
var detector_discordbot_timeout
if(!monitorConfig.details.detector_discordbot_timeout||monitorConfig.details.detector_discordbot_timeout===''){
detector_discordbot_timeout = 1000 * 60 * 10;
@@ -66,6 +66,28 @@ module.exports = function(s,config,lang,getSnapshot){
clearTimeout(s.group[d.ke].activeMonitors[d.id].detector_discordbot);
s.group[d.ke].activeMonitors[d.id].detector_discordbot = null
},detector_discordbot_timeout)
+ await getSnapshot(d,monitorConfig)
+ if(d.screenshotBuffer){
+ sendMessage({
+ author: {
+ name: s.group[d.ke].rawMonitorConfigurations[d.id].name,
+ icon_url: config.iconURL
+ },
+ title: lang.Event+' - '+d.screenshotName,
+ description: lang.EventText1+' '+d.currentTimestamp,
+ fields: [],
+ timestamp: d.currentTime,
+ footer: {
+ icon_url: config.iconURL,
+ text: "Shinobi Systems"
+ }
+ },[
+ {
+ attachment: d.screenshotBuffer,
+ name: d.screenshotName+'.jpg'
+ }
+ ],d.ke)
+ }
if(monitorConfig.details.detector_discordbot_send_video === '1'){
let videoPath = null
let videoName = null
@@ -102,28 +124,6 @@ module.exports = function(s,config,lang,getSnapshot){
],d.ke)
}
}
- await getSnapshot(d,monitorConfig)
- if(d.screenshotBuffer){
- sendMessage({
- author: {
- name: s.group[d.ke].rawMonitorConfigurations[d.id].name,
- icon_url: config.iconURL
- },
- title: lang.Event+' - '+d.screenshotName,
- description: lang.EventText1+' '+d.currentTimestamp,
- fields: [],
- timestamp: d.currentTime,
- footer: {
- icon_url: config.iconURL,
- text: "Shinobi Systems"
- }
- },[
- {
- attachment: d.screenshotBuffer,
- name: d.screenshotName+'.jpg'
- }
- ],d.ke)
- }
}
}
const onTwoFactorAuthCodeNotificationForDiscord = function(r){
@@ -366,6 +366,25 @@ module.exports = function(s,config,lang,getSnapshot){
}
]
}
+ s.definitions["Event Filters"].blocks["Action for Selected"].info.push({
+ "name": "actions=discord",
+ "field": lang['Discord'],
+ "fieldType": "select",
+ "form-group-class": "actions-row",
+ "default": "",
+ "example": "1",
+ "possible": [
+ {
+ "name": lang['Original Choice'],
+ "value": "",
+ "selected": true
+ },
+ {
+ "name": lang.Yes,
+ "value": "1",
+ }
+ ]
+ })
}catch(err){
console.log(err)
console.log('Could not start Discord bot, please run "npm install discord.js" inside the Shinobi folder.')
diff --git a/libs/notifications/email.js b/libs/notifications/email.js
index 6d9f6c70..172aee73 100644
--- a/libs/notifications/email.js
+++ b/libs/notifications/email.js
@@ -29,22 +29,22 @@ module.exports = function(s,config,lang,getSnapshot){
]
},(err,r) => {
r = r[0]
- var mailOptions = {
- from: config.mail.from, // sender address
- to: checkEmail(r.mail), // list of receivers
- subject: lang.NoMotionEmailText1+' '+e.name+' ('+e.id+')', // Subject line
- html: ''+lang.NoMotionEmailText2+' ' + (e.details.detector_notrigger_timeout || 10) + ' '+lang.minutes+'.',
+ var mailOptions = {
+ from: config.mail.from, // sender address
+ to: checkEmail(r.mail), // list of receivers
+ subject: lang.NoMotionEmailText1+' '+e.name+' ('+e.id+')', // Subject line
+ html: ''+lang.NoMotionEmailText2+' ' + (e.details.detector_notrigger_timeout || 10) + ' '+lang.minutes+'.',
+ }
+ mailOptions.html+='${row.code}
- ${moment(row.time).format(`DD-MM-YYYY hh:mm:ss A`)}
- ${row.ip}
- ${getHumanNamesForRowDetails(row.details || {})}
- | ${tag} | +${count} | +
${preset.name} ${presetMonitors.length} Monitor${presetMonitors.length > 1 ? 's' : ''}
- ${hasSelectedMonitor ? `${lang.Presets}