diff --git a/birdnet.conf-defaults b/birdnet.conf-defaults index 0513576..afe90b3 100644 --- a/birdnet.conf-defaults +++ b/birdnet.conf-defaults @@ -28,14 +28,6 @@ BIRDWEATHER_ID= CADDY_PWD= -#------------------------- MariaDB User Passwords ---------------------------# -#_____________The variable below sets the password for the_____________________# -#_______________________'birder' user on the MariaDB___________________________# - -## DB_PWD is for the 'birder' user. The shell substitution below sets a random -## password for you during installation. -DB_PWD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;) - #------------------------- Live Audio Stream --------------------------------# #_____________The variable below configures/enables the live___________________# #_____________________________audio stream.____________________________________# diff --git a/newinstaller.sh b/newinstaller.sh index e89c8e6..c84c6a5 100644 --- a/newinstaller.sh +++ b/newinstaller.sh @@ -2,7 +2,7 @@ # Simple new installer HOME=/home/pi USER=pi -branch=main +branch=sqlite if ! which git &> /dev/null;then sudo apt update sudo apt -y install git diff --git a/scripts/createdb.sh b/scripts/createdb.sh new file mode 100755 index 0000000..45ea2b4 --- /dev/null +++ b/scripts/createdb.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# This script performs the mysql_secure_installation +# Creates the birds database +# Creates the detections table +# Creates the birder user and grants them appropriate +# permissions +# If using this script to re-initialize (DROP then CREATE) +# the DB, be sure to run this as root or with sudo +source /etc/birdnet/birdnet.conf +sqlite3 /home/pi/BirdNET-Pi/scripts/birds.db << EOF +CREATE TABLE IF NOT EXISTS detections ( + Date DATE, + Time TIME, + Sci_Name VARCHAR(100) NOT NULL, + Com_Name VARCHAR(100) NOT NULL, + Confidence FLOAT, + Lat FLOAT, + Lon FLOAT, + Cutoff FLOAT, + Week INT, + Sens FLOAT, + Overlap FLOAT, + File_Name VARCHAR(100) NOT NULL); +EOF diff --git a/scripts/createdb_bullseye.sh b/scripts/createdb_bullseye.sh deleted file mode 100755 index 8453358..0000000 --- a/scripts/createdb_bullseye.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash -# This script performs the mysql_secure_installation -# Creates the birds database -# Creates the detections table -# Creates the birder user and grants them appropriate -# permissions -# If using this script to re-initialize (DROP then CREATE) -# the DB, be sure to run this as root or with sudo -source /etc/birdnet/birdnet.conf -mysql_secure_installation << EOF - -y -n -y -y -y -y -EOF - -mysql << EOF -DROP DATABASE IF EXISTS birds; -CREATE DATABASE IF NOT EXISTS birds; - -USE birds; - -CREATE TABLE IF NOT EXISTS detections ( - Date DATE, - Time TIME, - Sci_Name VARCHAR(100) NOT NULL, - Com_Name VARCHAR(100) NOT NULL, - Confidence FLOAT, - Lat FLOAT, - Lon FLOAT, - Cutoff FLOAT, - Week INT, - Sens FLOAT, - Overlap FLOAT); -GRANT ALL ON birds.* TO 'birder'@'localhost' IDENTIFIED BY '${DB_PWD}' WITH GRANT OPTION; -FLUSH PRIVILEGES; -exit -EOF -sudo -u${USER} sed -i "s/databasepassword/${DB_PWD}/g" /home/pi/BirdNET-Pi/scripts/server.py -sed -i "s/mysqli.default_host =.*/mysqli.default_host = localhost/g" /etc/php/7.4/fpm/php.ini -sed -i "s/mysqli.default_user =.*/mysqli.default_user = birder/g" /etc/php/7.4/fpm/php.ini -sed -i "s/mysqli.default_pw =.*/mysqli.default_pw = ${DB_PWD}/g" /etc/php/7.4/fpm/php.ini - diff --git a/scripts/install_birdnet.sh b/scripts/install_birdnet.sh index 16a821e..13df62c 100755 --- a/scripts/install_birdnet.sh +++ b/scripts/install_birdnet.sh @@ -20,27 +20,26 @@ fi sudo ./install_services.sh || exit 1 source /etc/birdnet/birdnet.conf -APT_DEPS=(swig ffmpeg wget unzip curl cmake make bc) -LIBS_MODULES=(libjpeg-dev zlib1g-dev python3-dev python3-pip python3-venv) +apt_deps=(swig ffmpeg wget unzip curl cmake make bc) +libs_modules=(libjpeg-dev zlib1g-dev python3-dev python3-pip python3-venv) install_deps() { - echo " Checking dependencies" - sudo apt update &> /dev/null - for i in "${LIBS_MODULES[@]}";do + echo "Checking dependencies" + for i in "${libs_modules[@]}";do if [ $(apt list --installed 2>/dev/null | grep "$i" | wc -l) -le 0 ];then echo " Installing $i" sudo apt -y install ${i} &> /dev/null else - echo " $i is installed!" + echo "$i is installed!" fi done - for i in "${APT_DEPS[@]}";do + for i in "${apt_deps[@]}";do if ! which $i &>/dev/null ;then - echo " Installing $i" + echo "Installing $i" sudo apt -y install ${i} &> /dev/null else - echo " $i is installed!" + echo "$i is installed!" fi done } @@ -51,21 +50,10 @@ install_birdnet() { python3 -m venv birdnet source ./birdnet/bin/activate echo "Upgrading pip, wheel, and setuptools" - pip3 install --upgrade pip~=21.0.0 wheel setuptools - python_version="$(awk -F. '{print $2}' <(ls -l $(which /usr/bin/python3)))" - echo "python_version=${python_version}" + pip3 install --upgrade pip wheel setuptools # TFLite Pre-built binaires from https://github.com/PINTO0309/TensorflowLite-bin - # Python 3.7 - if [[ "$python_version" == 7 ]];then - echo "Installing the TFLite bin wheel" - pip3 install --upgrade tflite_runtime-2.6.0-cp37-none-linux_aarch64.whl - fi - - # Python 3.9 - if [[ "$python_version" == 9 ]];then echo "Installing the TFLite bin wheel" pip3 install --upgrade tflite_runtime-2.6.0-cp39-none-linux_aarch64.whl - fi echo "Making sure everything else is installed" pip3 install -U -r /home/pi/BirdNET-Pi/requirements.txt } @@ -76,24 +64,4 @@ install_deps if [ ! -d ${VENV} ];then install_birdnet fi - -echo " BirdNet is installed!! - - To start the service manually, issue: - 'sudo systemctl start birdnet_analysis' - To monitor the service logs, issue: - 'journalctl -fu birdnet_analysis' - To stop the service manually, issue: - 'sudo systemctl stop birdnet_analysis' - To stop and disable the service, issue: - 'sudo systemctl disable --now birdnet_analysis.service' - - Visit - the BirdNET-Pi homepage at http://birdnetpi.local" - echo -case $YN in - [Yy] ) sudo systemctl start birdnet_analysis.service \ - && journalctl -fu birdnet_analysis;; -* ) echo " Thanks for installing BirdNET-Pi!! - I hope it was helpful!";; -esac +exit 0 diff --git a/scripts/install_config.sh b/scripts/install_config.sh index a2559e2..5c9fd16 100755 --- a/scripts/install_config.sh +++ b/scripts/install_config.sh @@ -6,240 +6,64 @@ trap 'exit 1' SIGINT SIGHUP my_dir=$(realpath $(dirname $0)) birdnetpi_dir=$(realpath $(dirname $my_dir)) -BIRDNET_CONF="$(dirname ${my_dir})/birdnet.conf" +birdnet_conf="$(dirname ${my_dir})/birdnet.conf" -get_RECS_DIR() { - read -p "What is the full path to your recordings directory (locally)? " RECS_DIR -} - -get_LATITUDE() { - read -p "What is the latitude where the recordings were made? " LATITUDE -} - -get_LONGITUDE() { - read -p "What is the longitude where the recordings were made? " LONGITUDE -} - -get_DO_EXTRACTIONS() { - while true; do - read -n1 -p "Do you want this device to perform the extractions? " DO_EXTRACTIONS - echo - case $DO_EXTRACTIONS in - [Yy] ) break;; - [Nn] ) break;; - * ) echo "You must answer with Yes or No (y or n)";; - esac - done -} - -get_DO_RECORDING() { - while true; do - read -n1 -p "Is this device also doing the recording? " DO_RECORDING - echo - case $DO_RECORDING in - [Yy] ) break;; - [Nn] ) break;; - * ) echo "You must answer with Yes or No (y or n)";; - esac - done -} - -get_BIRDNETPI_URL() { - while true;do - read -n1 -p "Would you like to access the extractions via a web browser? - - *Note: It is recommended, (but not required), that you run the web - server on the same host that does the extractions. If the extraction - service and web server are on different hosts, the \"By_Species\" and - \"Processed\" symbolic links won't work. The \"By-Date\" extractions, - however, will work as expected." CADDY_SERVICE - echo - case $CADDY_SERVICE in - [Yy] ) read -p "What URL would you like to publish the extractions to? - *Note: Set this to http://localhost if you do not want to make the - extractions publically available: " BIRDNETPI_URL - get_CADDY_PWD - get_ICE_PWD - break;; - [Nn] ) BIRDNETPI_URL= CADDY_PWD= ICE_PWD=;break;; - * ) echo "Please answer Yes or No";; - esac - done -} - -get_CADDY_PWD() { - if [ -z ${CADDY_PWD} ]; then - while true; do - read -p "Please set a password to protect your data: " CADDY_PWD - case $CADDY_PWD in - "" ) echo "The password cannot be empty. Please try again.";; - * ) break;; - esac - done - fi -} - -get_ICE_PWD() { - if [ ! -z ${CADDY_PWD} ] && [[ ${DO_RECORDING} =~ [Yy] ]];then - while true; do - read -n1 -p "Would you like to enable the live audio streaming service?" LIVE_STREAM - echo - case $LIVE_STREAM in - [Yy] ) - read -p "Please set the icecast password. Use only alphanumeric characters." ICE_PWD - echo - case ${ICE_PWD} in - "" ) echo "The password cannot be empty. Please try again.";; - *) break;; - esac - break;; - [Nn] ) break;; - * ) echo "You must answer Yes or No (y or n).";; - esac - done - fi -} - -get_DB_PWDS() { - if [ ! -z ${DB_PWD} ];then - read -p "Please set a password for your database: " DB_PWD - echo - fi -} - -get_PUSHED() { - while true; do - read -n1 -p "Do you have a free App key to receive mobile notifications via Pushed.co?" YN - echo - case $YN in - [Yy] ) read -p "Enter your Pushed.co App Key: " PUSHED_APP_KEY - read -p "Enter your Pushed.co App Key Secret: " PUSHED_APP_SECRET - break;; - [Nn] ) PUSHED_APP_KEY= - PUSHED_APP_SECRET= - break;; - * ) echo "A simple Yea or Nay will do";; - esac - done -} - -get_INSTALL_NOMACHINE() { - while true; do - read -n1 -p "Would you like to also install NoMachine for remote desktop access?" INSTALL_NOMACHINE - echo - case $INSTALL_NOMACHINE in - [Yy] ) break;; - [Nn] ) break;; - * ) echo "You must answer with Yes or No (y or n)";; - esac - done -} - -get_CHANNELS() { - REC_CARD="$(sudo -u pi aplay -L \ - | grep dsnoop \ - | cut -d, -f1 \ - | grep -ve 'vc4' -e 'Head' -e 'PCH' \ - | uniq)" - - [ -f $(dirname ${my_dir})/soundcard_params.txt ] || touch $(dirname ${my_dir})/soundcard_params.txt - SOUND_PARAMS=$(dirname ${my_dir})/soundcard_params.txt - SOUND_CARD="$(sudo -u ${USER} aplay -L \ - | awk -F, '/^hw:/ {print $1}' \ - | grep -ve 'vc4' -e 'Head' -e 'PCH' \ - | uniq)" - script -c "arecord -D ${SOUND_CARD} --dump-hw-params" -a "${SOUND_PARAMS}" &> /dev/null - CHANNELS=$(awk '/CHANN/ { print $2 }' "${SOUND_PARAMS}" | sed 's/\r$//') - [ -z REC_CARD ] && REC_CARD=default - [ -z CHANNELS ] && CHANNELS=2 - echo "REC_CARD variable set to ${REC_CARD}" - echo "Number of channels available: ${CHANNELS}" -} - - -configure() { - get_RECS_DIR - get_LATITUDE - get_LONGITUDE - get_DB_PWDS - get_DO_EXTRACTIONS - get_DO_RECORDING - get_BIRDNETPI_URL - get_PUSHED - get_INSTALL_NOMACHINE - get_CHANNELS -} - -install_birdnet_conf() { +install_config() { cat << EOF > $(dirname ${my_dir})/birdnet.conf ################################################################################ # Configuration settings for BirdNET-Pi # ################################################################################ -############ CHANGE THE LATITUDE AND LONGITUDE TO STATIC VALUES ################ +#--------------------- Required: Latitude, and Longitude ----------------------# + +## The shell substitution below guesses these based on your network. THESE NEED +## TO BE CHANGED TO STATIC VALUES +## Please only go to 4 decimal places. Example:43.3984 + +LATITUDE="$(curl -s4 ifconfig.co/json | awk '/lat/ {print $2}' | tr -d ',')" +LONGITUDE="$(curl -s4 ifconfig.co/json | awk '/lon/ {print $2}' | tr -d ',')" -## LATITUDE and LONGITUDE are self-explanatroy. Find them easily at -## maps.google.com. -## Example: these coordinates would indicate the Eiffel Tower in Paris, France. -## LATITUDE=48.858 -## LONGITUDE=2.294 +#--------------------- BirdWeather Station Information -----------------------# +#_____________The variable below can be set to have your BirdNET-Pi____________# +#__________________also act as a BirdWeather listening station_________________# -## These are input as shell substitutions so that this will work out of the box. -## It guesses your latitude and longitude based off of your network information. -## THESE SHOULD BE CHANGED TO STATIC NUMBERS!!!! -LATITUDE=${LATITUDE} -LONGITUDE=${LONGITUDE} - -## RECS_DIR is the location birdnet_analysis.service will look for the data-set -## it needs to analyze. Be sure this directory is readable and writable for -## the BIRDNET_USER. If you are going to be accessing a remote data-set, you -## still need to set this, as this will be where the remote directory gets -## mounted locally. - -RECS_DIR=${RECS_DIR} +BIRDWEATHER_ID= #----------------------- Web Interface User Password ------------------------# -#____________________The variable below sets the 'birdnet'_____________________# -#___________________user password for the live audio stream,___________________# -#_________________web tools, system info, and processed files__________________# +#____________________The variable below sets the 'birdnet'_____________________# +#___________________user password for the Live Audio Stream,___________________# +#_________________Tools, System Links, and the Processed files ________________# ## CADDY_PWD is the plaintext password (that will be hashed) and used to access ## certain parts of the web interface -CADDY_PWD=${CADDY_PWD} - -#------------------------- MariaDB User Passwords ---------------------------# -#_____________The variable below sets the password for the_____________________# -#_______________________'birder' user on the MariaDB___________________________# - -## DB_PWD is for the 'birder' user -DB_PWD=${DB_PWD} +CADDY_PWD= #------------------------- Live Audio Stream --------------------------------# -#_____________The variable below configures/enables the live___________________# +#_____________The variable below configures/enables the live___________________# #_____________________________audio stream.____________________________________# ## ICE_PWD is the password that icecast2 will use to authenticate ffmpeg as a ## trusted source for the stream. You will never need to enter this manually -## anywhere other than here. +## anywhere other than here and it stays on 'localhost.' -ICE_PWD=${ICE_PWD} +ICE_PWD=birdnetpi #----------------------- Web-hosting/Caddy File-server -----------------------# -#________The four variables below can be set to enable internet access_________# +#_______The three variables below can be set to enable internet access_________# #____________to your data,(e.g., extractions, raw data, live___________________# #______________audio stream, BirdNET.selection.txt files)______________________# ## BIRDNETPI_URL is the URL where the extractions, data-set, and live-stream -## will be web-hosted. If you do not own a domain, or would just prefer to keep +## will be web-hosted. If you do not own a domain, or would just prefer to keep ## the BirdNET-Pi on your local network, keep this EMPTY. -BIRDNETPI_URL=${BIRDNETPI_URL} -WEBTERMINAL_URL${WEBTERMINAL_URL} -BIRDNETLOG_URL=${BIRDNETLOG_URL} +BIRDNETPI_URL= +BIRDNETLOG_URL= +WEBTERMINAL_URL= #------------------- Mobile Notifications via Pushed.co ---------------------# @@ -251,34 +75,26 @@ BIRDNETLOG_URL=${BIRDNETLOG_URL} ## Pushed.co App Key and App Secret -PUSHED_APP_KEY=${PUSHED_APP_KEY} -PUSHED_APP_SECRET=${PUSHED_APP_SECRET} +PUSHED_APP_KEY= +PUSHED_APP_SECRET= ################################################################################ #-------------------------------- Defaults ----------------------------------# ################################################################################ -#------------------------------- NoMachine ----------------------------------# -#_____________The variable below can be set include NoMachine__________________# -#_________________remote desktop software to be installed._____________________# +## RECS_DIR is the location birdnet_analysis.service will look for the data-set +## it needs to analyze. Be sure this directory is readable and writable for +## the BIRDNET_USER. -# Keep this EMPTY if you do not want to install NoMachine. # +RECS_DIR=/home/pi/BirdSongs -## INSTALL_NOMACHINE is simply a setting that can be enabled to install -## NoMachine alongside the BirdNET-Pi for remote desktop access. This in- -## staller assumes personal use. Please reference the LICENSE file included -## in this repository for more information. -## Set this to Y or y to install NoMachine alongside the BirdNET-Lite -INSTALL_NOMACHINE=${INSTALL_NOMACHINE} - -# #------------------------------ Extraction Service ---------------------------# ## DO_EXTRACTIONS is simply a setting for enabling the extraction.service. ## Set this to Y or y to enable extractions. -DO_EXTRACTIONS=${DO_EXTRACTIONS} +DO_EXTRACTIONS=y #----------------------------- Recording Service ----------------------------# #____________________The variable below can be set to enable __________________# @@ -288,60 +104,49 @@ DO_EXTRACTIONS=${DO_EXTRACTIONS} ## birdnet_recording.service. ## Set this to Y or y to enable recording. -DO_RECORDING=${DO_RECORDING} +DO_RECORDING=y -## REC_CARD is the sound card you would want the birdnet_recording.service to -## use. This setting is irrelevant if you are not planning on doing data -## collection via recording on this machine. The command substitution below -## looks for a USB microphone's dsnoop alsa device. The dsnoop device lets -## birdnet_recording.service and livestream.service share the raw audio stream -## from the microphone. If you would like to use a different microphone than -## what this produces, or if your microphone does not support creating a -## dsnoop device, you can set this explicitly from a list of the available -## devices from the output of running 'aplay -L' +## REC_CARD is the sound card you would want the birdnet_recording.service to +## use. Leave this as "default" to use PulseAudio (recommended), or use +## the output from "aplay -L" to specify an ALSA device. -REC_CARD=${REC_CARD} +REC_CARD=default -## PROCESSED is the directory where the formerly 'Analyzed' files are moved -## after extractions have been made from them. This includes both WAVE and +## PROCESSED is the directory where the formerly 'Analyzed' files are moved +## after extractions have been made from them. This includes both WAVE and ## BirdNET.selection.txt files. -PROCESSED=${PROCESSED} +PROCESSED=${RECS_DIR}/Processed ## EXTRACTED is the directory where the extracted audio selections are moved. -EXTRACTED=${EXTRACTED} +EXTRACTED=${RECS_DIR}/Extracted ## IDFILE is the file that keeps a complete list of every spececies that -## BirdNET has identified from your data-set. It is persistent across -## data-sets, so would need to be whiped clean through deleting or renaming -## it. A backup is automatically made from this variable each time it is -## updated (structure: ${IDFILE}.bak), and would also need to be removed -## or renamed to start a new file between data-sets. Alternately, you can -## change this variable between data-sets to preserve records of disparate -## data-sets according to name. +## BirdNET has identified from your data-set. It is a relic and not really +## used anymore. -IDFILE=${IDFILE} +IDFILE=${HOME}/BirdNET-Pi/IdentifiedSoFar.txt ## OVERLAP is the value in seconds which BirdNET should use when analyzing ## the data. The values must be between 0.0-2.9. -OVERLAP=${OVERLAP} +OVERLAP=0.0 -## CONFIDENCE is the minimum confidence level from 0.0-1.0 BirdNET's analysis +## CONFIDENCE is the minimum confidence level from 0.0-1.0 BirdNET's analysis ## should reach before creating an entry in the BirdNET.selection.txt file. ## Don't set this to 1.0 or you won't have any results. -CONFIDENCE=${CONFIDENCE} +CONFIDENCE=0.7 ## SENSITIVITY is the detection sensitivity from 0.5-1.5. -SENSITIVITY=${SENSITIVITY} +SENSITIVITY=1.25 -## CHANNELS holds the variabel that corresponds to the number of channels the +## CHANNELS holds the variable that corresponds to the number of channels the ## sound card supports. -CHANNELS=${CHANNELS} +CHANNELS=2 ## FULL_DISK can be set to configure how the system reacts to a full disk ## purge = Remove the oldest day's worth of recordings @@ -349,46 +154,50 @@ CHANNELS=${CHANNELS} FULL_DISK=purge -## VENV is the virtual environment where the the BirdNET python build is found, -## i.e, VENV is the virtual environment miniforge built for BirdNET. +## VENV is the python virtual environment wherein all modules and site-packages +## have been installed. VENV=/home/pi/BirdNET-Pi/birdnet -## RECORDING_LENGTH sets the length of the recording that BirdNET-Lite will analyze. -RECORDING_LENGTH=${RECORDING_LENGTH} +## RECORDING_LENGTH sets the length of the recording that BirdNET-Lite will +## analyze. + +RECORDING_LENGTH=15 ## EXTRACTION_LENGTH sets the length of the audio extractions that will be made -## from each BirdNET-Lite detection. -EXTRACTION_LENGTH=${EXTRACTION_LENGTH} +## from each BirdNET-Lite detection. An empty value will use the default of 6 +## seconds. -## BIRDNET_USER should be the non-root user systemd should use to execute each +EXTRACTION_LENGTH= + +## AUDIOFMT set the audio format that sox should use for the extractions. +## The default is mp3. Available formats are: 8svx aif aifc aiff aiffc al amb +## amr-nb amr-wb anb au avr awb caf cdda cdr cvs cvsd cvu dat dvms f32 f4 f64 f8 +## fap flac fssd gsm gsrt hcom htk ima ircam la lpc lpc10 lu mat mat4 mat5 maud +## mp2 mp3 nist ogg paf prc pvf raw s1 s16 s2 s24 s3 s32 s4 s8 sb sd2 sds sf sl +## sln smp snd sndfile sndr sndt sou sox sph sw txw u1 u16 u2 u24 u3 u32 u4 u8 +## ub ul uw vms voc vorbis vox w64 wav wavpcm wv wve xa xi +## Note: Most have not been tested. + +AUDIOFMT=mp3 + +## BIRDNET_USER should be the non-root user systemd should use to execute each ## service. BIRDNET_USER=pi ## These are just for debugging -LAST_RUN=${LAST_RUN} -THIS_RUN=${THIS_RUN} +LAST_RUN= +THIS_RUN= + EOF } -# Checks for a birdnet.conf file in the BirdNET-Pi directory for a -# non-interactive installation. Otherwise,the installation is interactive. -if [ -f ${BIRDNET_CONF} ];then - source ${BIRDNET_CONF} - chmod g+w ${BIRDNET_CONF} - sed -i "s/LATITUDE=.*/LATITUDE=${LATITUDE}/g" ${BIRDNET_CONF} - sed -i "s/LONGITUDE=.*/LONGITUDE=${LONGITUDE}/g" ${BIRDNET_CONF} - sed -i "s/DB_PWD=.*/DB_PWD=${DB_PWD}/g" ${BIRDNET_CONF} - #install_birdnet_conf - [ -d /etc/birdnet ] || sudo mkdir /etc/birdnet - sudo ln -sf $(dirname ${my_dir})/birdnet.conf /etc/birdnet/birdnet.conf - grep -ve '^#' -e '^$' /etc/birdnet/birdnet.conf > ${birdnetpi_dir}/firstrun.ini -else - configure - install_birdnet_conf - chmod g+w ${BIRDNET_CONF} - [ -d /etc/birdnet ] || sudo mkdir /etc/birdnet - sudo ln -sf $(dirname ${my_dir})/birdnet.conf /etc/birdnet/birdnet.conf - grep -ve '^#' -e '^$' /etc/birdnet/birdnet.conf > ${birdnetpi_dir}/firstrun.ini +# Checks for a birdnet.conf file +if ! [ -f ${birdnet_conf} ];then + install_config fi +chmod g+w ${birdnet_conf} +[ -d /etc/birdnet ] || sudo mkdir /etc/birdnet +sudo ln -sf $(dirname ${my_dir})/birdnet.conf /etc/birdnet/birdnet.conf +grep -ve '^#' -e '^$' /etc/birdnet/birdnet.conf > ${birdnetpi_dir}/firstrun.ini diff --git a/scripts/install_services.sh b/scripts/install_services.sh index 447b573..2b40b0b 100755 --- a/scripts/install_services.sh +++ b/scripts/install_services.sh @@ -11,6 +11,11 @@ nomachine_url="https://download.nomachine.com/download/7.7/Arm/nomachine_7.7.4_1 gotty_url="https://github.com/yudai/gotty/releases/download/v1.0.1/gotty_linux_arm.tar.gz" config_file="$(dirname ${my_dir})/birdnet.conf" + +update_system() { + apt update && apt -y upgrade +} + set_hostname() { if [ "$(hostname)" == "raspberrypi" ];then echo "Setting hostname to 'birdnetpi'" @@ -31,10 +36,6 @@ install_ftpd() { fi } -update_system() { - apt update && apt -y upgrade -} - install_scripts() { echo "Installing BirdNET-Pi scripts to /usr/local/bin" ln -sf ${my_dir}/* /usr/local/bin/ @@ -42,20 +43,13 @@ install_scripts() { } install_mariadb() { - if ! which mysql &> /dev/null;then - echo "Installing MariaDB Server" - apt -qqy update - apt -qqy install mariadb-server - echo "MariaDB Installed" + if ! which sqlite3 &> /dev/null;then + echo "Installing SQLite3" + apt -qqy install sqlite3 php-sqlite3 + echo "SQLite Installed" fi echo "Initializing the database" - source /etc/os-release - if [[ "${VERSION_CODENAME}" == "buster" ]];then - USER=${USER} ${my_dir}/createdb_buster.sh - elif [[ "${VERSION_CODENAME}" == "bullseye" ]];then - USER=${USER} ${my_dir}/createdb_bullseye.sh - fi - systemctl restart php${php_version}-fpm + ${my_dir}/createdb.sh } install_birdnet_analysis() { @@ -186,7 +180,6 @@ create_necessary_dirs() { fi sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/spectrogram.php ${EXTRACTED} - sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/viewday.php ${EXTRACTED} sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/overview.php ${EXTRACTED} sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/stats.php ${EXTRACTED} sudo -u ${USER} ln -fs $(dirname ${my_dir})/scripts/viewdb.php ${EXTRACTED} @@ -212,7 +205,7 @@ generate_BirdDB() { sudo -u ${USER} sed -i '1 i\Date;Time;Sci_Name;Com_Name;Confidence;Lat;Lon;Cutoff;Week;Sens;Overlap' $(dirname ${my_dir})/BirdDB.txt fi ln -sf $(dirname ${my_dir})/BirdDB.txt ${my_dir}/BirdDB.txt && - chown pi:pi ${my_dir}/BirdDB.txt && chmod g+rw ${my_dir}/BirdDB.txt + chown pi:pi ${my_dir}/BirdDB.txt && chmod g+rw ${my_dir}/BirdDB.txt } install_alsa() { @@ -221,7 +214,6 @@ install_alsa() { echo "alsa-utils installed" else echo "Installing alsa-utils" - apt -qqq update apt install -qqy alsa-utils echo "alsa-utils installed" fi @@ -229,7 +221,6 @@ install_alsa() { echo "PulseAudio installed" else echo "Installing pulseaudio" - apt -qqq update apt install -qqy pulseaudio echo "PulseAudio installed" fi @@ -285,7 +276,6 @@ install_Caddyfile() { if [ -f /etc/caddy/Caddyfile ];then cp /etc/caddy/Caddyfile{,.original} fi - php_version="$(awk -F'php' '{print $3}' <(ls -l $(which /etc/alternatives/php)))" if ! [ -z ${CADDY_PWD} ];then HASHWORD=$(caddy hash-password -plaintext ${CADDY_PWD}) cat << EOF > /etc/caddy/Caddyfile @@ -305,7 +295,7 @@ http://localhost http://$(hostname).local ${BIRDNETPI_URL} { birdnet ${HASHWORD} } reverse_proxy /stream localhost:8000 - php_fastcgi unix//run/php/php${php_version}-fpm.sock + php_fastcgi unix//run/php/php7.4-fpm.sock } EOF else @@ -314,7 +304,7 @@ http://localhost http://$(hostname).local ${BIRDNETPI_URL} { root * ${EXTRACTED} file_server browse reverse_proxy /stream localhost:8000 - php_fastcgi unix//run/php/php${php_version}-fpm.sock + php_fastcgi unix//run/php/php7.4-fpm.sock } EOF fi @@ -453,7 +443,6 @@ install_sox() { echo "Sox is installed" else echo "Installing sox" - apt -qq update apt install -y sox libsox-fmt-mp3 echo "Sox installed" fi @@ -462,7 +451,6 @@ install_sox() { install_php() { if ! which php &> /dev/null || ! which php-fpm || ! apt list --installed | grep php-xml;then echo "Installing PHP modules" - apt -qq update apt install -qqy php php-fpm php-mysql php-xml php-zip else echo "PHP and PHP-FPM installed" @@ -485,7 +473,6 @@ EOF install_icecast() { if ! which icecast2;then echo "Installing IceCast2" - apt -qq update echo "icecast2 icecast2/icecast-setup boolean false" | debconf-set-selections apt install -qqy icecast2 config_icecast @@ -530,17 +517,6 @@ EOF systemctl enable livestream.service } -install_nomachine() { - if [ ! -d /usr/share/NX ] && [ -d /etc/lightdm ];then - echo "Installing NoMachine" - curl -s -o ${HOME}/nomachine.deb -O "${nomachine_url}" - apt install -y ${HOME}/nomachine.deb - rm -f ${HOME}/nomachine.deb - echo "Enabling VNC" - systemctl enable --now vncserver-x11-serviced.service - fi -} - install_cleanup_cron() { echo "Installing the cleanup.cron" cat $(dirname ${my_dir})/templates/cleanup.cron >> /etc/crontab @@ -552,37 +528,22 @@ install_selected_services() { install_scripts install_birdnet_analysis install_birdnet_server - - if [[ "${DO_EXTRACTIONS}" =~ [Yy] ]];then - install_extraction_service - fi - - if [[ "${DO_RECORDING}" =~ [Yy] ]];then - install_alsa - install_recording_service - fi - - install_php - install_caddy - install_Caddyfile - update_etc_hosts - install_avahi_aliases - install_gotty_logs - install_sox - install_mariadb - install_spectrogram_service - install_chart_viewer_service - install_pushed_notifications - - if [ ! -z "${ICE_PWD}" ];then - install_icecast - install_livestream_service - fi - - if [[ "${INSTALL_NOMACHINE}" =~ [Yy] ]];then - install_nomachine - fi - + install_extraction_service + install_alsa + install_recording_service + install_php + install_caddy + install_Caddyfile + update_etc_hosts + install_avahi_aliases + install_gotty_logs + install_sox + install_mariadb + install_spectrogram_service + install_chart_viewer_service + install_pushed_notifications + install_icecast + install_livestream_service create_necessary_dirs generate_BirdDB install_cleanup_cron diff --git a/scripts/server-lite.py b/scripts/server-lite.py deleted file mode 100755 index e1787d5..0000000 --- a/scripts/server-lite.py +++ /dev/null @@ -1,423 +0,0 @@ -#!/home/pi/BirdNET-Pi/birdnet/bin/python3 -import socket -import threading -import os -os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' -os.environ['CUDA_VISIBLE_DEVICES'] = '' - -try: - import tflite_runtime.interpreter as tflite -except: - from tensorflow import lite as tflite - -import argparse -import operator -import librosa -import numpy as np -import math -import time -from decimal import Decimal -import json -import requests -import sqlite3 -import datetime -from time import sleep -import pytz -from tzlocal import get_localzone -from pathlib import Path - - -HEADER = 64 -PORT = 5050 -SERVER = socket.gethostbyname(socket.gethostname()) -ADDR = (SERVER, PORT) -FORMAT = 'utf-8' -DISCONNECT_MESSAGE = "!DISCONNECT" - -server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -try: - server.bind(ADDR) -except: - print("Waiting on socket") - time.sleep(5) - - - -# Open most recent Configuration and grab DB_PWD as a python variable -with open('/home/pi/BirdNET-Pi/thisrun.txt', 'r') as f: - this_run = f.readlines() - db_pwd = str(str(str([i for i in this_run if i.startswith('DB_PWD')]).split('=')[1]).split('\\')[0]) - - -def loadModel(): - - global INPUT_LAYER_INDEX - global OUTPUT_LAYER_INDEX - global MDATA_INPUT_INDEX - global CLASSES - - print('LOADING TF LITE MODEL...', end=' ') - - # Load TFLite model and allocate tensors. - myinterpreter = tflite.Interpreter(model_path='/home/pi/BirdNET-Pi/model/BirdNET_6K_GLOBAL_MODEL.tflite',num_threads=2) - myinterpreter.allocate_tensors() - - # Get input and output tensors. - input_details = myinterpreter.get_input_details() - output_details = myinterpreter.get_output_details() - - # Get input tensor index - INPUT_LAYER_INDEX = input_details[0]['index'] - MDATA_INPUT_INDEX = input_details[1]['index'] - OUTPUT_LAYER_INDEX = output_details[0]['index'] - - # Load labels - CLASSES = [] - with open('/home/pi/BirdNET-Pi/model/labels.txt', 'r') as lfile: - for line in lfile.readlines(): - CLASSES.append(line.replace('\n', '')) - - print('DONE!') - - return myinterpreter - -def loadCustomSpeciesList(path): - - slist = [] - if os.path.isfile(path): - with open(path, 'r') as csfile: - for line in csfile.readlines(): - slist.append(line.replace('\r', '').replace('\n', '')) - - return slist - -def splitSignal(sig, rate, overlap, seconds=3.0, minlen=1.5): - - # Split signal with overlap - sig_splits = [] - for i in range(0, len(sig), int((seconds - overlap) * rate)): - split = sig[i:i + int(seconds * rate)] - - # End of signal? - if len(split) < int(minlen * rate): - break - - # Signal chunk too short? Fill with zeros. - if len(split) < int(rate * seconds): - temp = np.zeros((int(rate * seconds))) - temp[:len(split)] = split - split = temp - - sig_splits.append(split) - - return sig_splits - -def readAudioData(path, overlap, sample_rate=48000): - - print('READING AUDIO DATA...', end=' ', flush=True) - - # Open file with librosa (uses ffmpeg or libav) - sig, rate = librosa.load(path, sr=sample_rate, mono=True, res_type='kaiser_fast') - - # Split audio into 3-second chunks - chunks = splitSignal(sig, rate, overlap) - - print('DONE! READ', str(len(chunks)), 'CHUNKS.') - - return chunks - -def convertMetadata(m): - - # Convert week to cosine - if m[2] >= 1 and m[2] <= 48: - m[2] = math.cos(math.radians(m[2] * 7.5)) + 1 - else: - m[2] = -1 - - # Add binary mask - mask = np.ones((3,)) - if m[0] == -1 or m[1] == -1: - mask = np.zeros((3,)) - if m[2] == -1: - mask[2] = 0.0 - - return np.concatenate([m, mask]) - -def custom_sigmoid(x, sensitivity=1.0): - return 1 / (1.0 + np.exp(-sensitivity * x)) - -def predict(sample, sensitivity): - global INTERPRETER - # Make a prediction - INTERPRETER.set_tensor(INPUT_LAYER_INDEX, np.array(sample[0], dtype='float32')) - INTERPRETER.set_tensor(MDATA_INPUT_INDEX, np.array(sample[1], dtype='float32')) - INTERPRETER.invoke() - prediction = INTERPRETER.get_tensor(OUTPUT_LAYER_INDEX)[0] - - # Apply custom sigmoid - p_sigmoid = custom_sigmoid(prediction, sensitivity) - - # Get label and scores for pooled predictions - p_labels = dict(zip(CLASSES, p_sigmoid)) - - # Sort by score - p_sorted = sorted(p_labels.items(), key=operator.itemgetter(1), reverse=True) - - # Remove species that are on blacklist - for i in range(min(10, len(p_sorted))): - if p_sorted[i][0] in ['Human_Human', 'Non-bird_Non-bird', 'Noise_Noise']: - p_sorted[i] = (p_sorted[i][0], 0.0) - - # Only return first the top ten results - return p_sorted[:10] - -def analyzeAudioData(chunks, lat, lon, week, sensitivity, overlap,): - global INTERPRETER - - detections = {} - start = time.time() - print('ANALYZING AUDIO...', end=' ', flush=True) - - # Convert and prepare metadata - mdata = convertMetadata(np.array([lat, lon, week])) - mdata = np.expand_dims(mdata, 0) - - # Parse every chunk - pred_start = 0.0 - for c in chunks: - - # Prepare as input signal - sig = np.expand_dims(c, 0) - - # Make prediction - p = predict([sig, mdata], sensitivity) - - # Save result and timestamp - pred_end = pred_start + 3.0 - detections[str(pred_start) + ';' + str(pred_end)] = p - pred_start = pred_end - overlap - - print('DONE! Time', int((time.time() - start) * 10) / 10.0, 'SECONDS') - - return detections - -def writeResultsToFile(detections, min_conf, path): - - print('WRITING RESULTS TO', path, '...', end=' ') - rcnt = 0 - with open(path, 'w') as rfile: - rfile.write('Start (s);End (s);Scientific name;Common name;Confidence\n') - for d in detections: - for entry in detections[d]: - if entry[1] >= min_conf and ((entry[0] in INCLUDE_LIST or len(INCLUDE_LIST) == 0) and (entry[0] not in EXCLUDE_LIST or len(EXCLUDE_LIST) == 0) ): - rfile.write(d + ';' + entry[0].replace('_', ';') + ';' + str(entry[1]) + '\n') - rcnt += 1 - print('DONE! WROTE', rcnt, 'RESULTS.') - return - -def handle_client(conn, addr): - global INCLUDE_LIST - global EXCLUDE_LIST - print(f"[NEW CONNECTION] {addr} connected.") - - connected = True - while connected: - msg_length = conn.recv(HEADER).decode(FORMAT) - if msg_length: - msg_length = int(msg_length) - msg = conn.recv(msg_length).decode(FORMAT) - if msg == DISCONNECT_MESSAGE: - connected = False - else: - #print(f"[{addr}] {msg}") - - args = type('', (), {})() - - args.i = '/home/pi/test.wav' - args.o = '/home/pi/test.wav.csv' - args.birdweather_id = '99999' - args.include_list = 'null' - args.exclude_list = 'null' - args.overlap = 0.0 - args.week = -1 - args.sensitivity = 1.25 - args.min_conf = 0.70 - args.lat = -1 - args.lon = -1 - - - for line in msg.split('||'): - inputvars = line.split('=') - if inputvars[0] == 'i': - args.i = inputvars[1] - elif inputvars[0] == 'o': - args.o = inputvars[1] - elif inputvars[0] == 'birdweather_id': - args.birdweather_id = inputvars[1] - elif inputvars[0] == 'include_list': - args.include_list = inputvars[1] - elif inputvars[0] == 'exclude_list': - args.exclude_list = inputvars[1] - elif inputvars[0] == 'overlap': - args.overlap = float(inputvars[1]) - elif inputvars[0] == 'week': - args.week = int(inputvars[1]) - elif inputvars[0] == 'sensitivity': - args.sensitivity = float(inputvars[1]) - elif inputvars[0] == 'min_conf': - args.min_conf = float(inputvars[1]) - elif inputvars[0] == 'lat': - args.lat = float(inputvars[1]) - elif inputvars[0] == 'lon': - args.lon = float(inputvars[1]) - - - - # Load custom species lists - INCLUDED and EXCLUDED - if not args.include_list == 'null': - INCLUDE_LIST = loadCustomSpeciesList(args.include_list) - else: - INCLUDE_LIST = [] - - if not args.exclude_list == 'null': - EXCLUDE_LIST = loadCustomSpeciesList(args.exclude_list) - else: - EXCLUDE_LIST = [] - - birdweather_id = args.birdweather_id - - # Read audio data - audioData = readAudioData(args.i, args.overlap) - - # Get Date/Time from filename in case Pi gets behind - #now = datetime.now() - full_file_name = args.i - print('FULL FILENAME: -' + full_file_name + '-') - file_name = Path(full_file_name).stem - file_date = file_name.split('-birdnet-')[0] - file_time = file_name.split('-birdnet-')[1] - date_time_str = file_date + ' ' + file_time - date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S') - #print('Date:', date_time_obj.date()) - #print('Time:', date_time_obj.time()) - print('Date-time:', date_time_obj) - now = date_time_obj - current_date = now.strftime("%Y/%m/%d") - current_time = now.strftime("%H:%M:%S") - current_iso8601 = now.astimezone(get_localzone()).isoformat() - - week_number = int(now.strftime("%V")) - week = max(1, min(week_number, 48)) - - sensitivity = max(0.5, min(1.0 - (args.sensitivity - 1.0), 1.5)) - - # Process audio data and get detections - detections = analyzeAudioData(audioData, args.lat, args.lon, week, sensitivity, args.overlap) - - # Write detections to output file - min_conf = max(0.01, min(args.min_conf, 0.99)) - writeResultsToFile(detections, min_conf, args.o) - - ############################################################################### - ############################################################################### - - soundscape_uploaded = False - - # Write detections to Database - myReturn = '' - for i in detections: - myReturn += str(i) + '-' + str(detections[i][0]) + '\n' - - - with open('/home/pi/BirdNET-Pi/BirdDB.txt', 'a') as rfile: - for d in detections: - for entry in detections[d]: - if entry[1] >= min_conf and ((entry[0] in INCLUDE_LIST or len(INCLUDE_LIST) == 0) and (entry[0] not in EXCLUDE_LIST or len(EXCLUDE_LIST) == 0) ): - rfile.write(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';') + ';' \ - + str(entry[1]) +";" + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';' \ - + str(args.sensitivity) +';' + str(args.overlap) + '\n') - - Date = str(current_date) - Time = str(current_time) - species = entry[0] - Sci_Name,Com_Name = species.split('_') - score = entry[1] - Confidence = "{:.0%}".format(score) - Lat = str(args.lat) - Lon = str(args.lon) - Cutoff = str(args.min_conf) - Week = str(args.week) - Sens = str(args.sensitivity) - Overlap = str(args.overlap) - File_Name = Com_Name.replace(" ", "_") + '-' + Confidence + '-' + \ - Date.replace("/", "-") + '-birdnet-' + Time + '.mp3' - - #Connect to SQLite Database - con = sqlite3.connect('/home/pi/BirdNET-Pi/scripts/birds2.db') - cur = con.cursor() - cur.execute("INSERT INTO detections VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (Date, Time, Sci_Name, Com_Name, str(score), Lat, Lon, Cutoff, Week, Sens, Overlap, File_Name)) - - con.commit() - con.close() - print(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';') + ';' + str(entry[1]) + ';' + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';' + str(args.sensitivity) +';' + str(args.overlap) + Com_Name.replace(" ", "_") + '-' + str(score) + '-' + str(current_date) + '-birdnet-' + str(current_time) + '.mp3' + '\n') - - if birdweather_id != "99999": - - if soundscape_uploaded is False: - # POST soundscape to server - soundscape_url = "https://app.birdweather.com/api/v1/stations/" + birdweather_id + "/soundscapes" + "?timestamp=" + current_iso8601 - - with open(args.i, 'rb') as f: - wav_data = f.read() - response = requests.post(url=soundscape_url, data=wav_data, headers={'Content-Type': 'application/octet-stream'}) - print("Soundscape POST Response Status - ", response.status_code) - sdata = response.json() - soundscape_id = sdata['soundscape']['id'] - soundscape_uploaded = True - - # POST detection to server - detection_url = "https://app.birdweather.com/api/v1/stations/" + birdweather_id + "/detections" - start_time = d.split(';')[0] - end_time = d.split(';')[1] - post_begin = "{ " - now_p_start = now + datetime.timedelta(seconds=float(start_time)) - current_iso8601 = now_p_start.astimezone(get_localzone()).isoformat() - post_timestamp = "\"timestamp\": \"" + current_iso8601 + "\"," - post_lat = "\"lat\": " + str(args.lat) + "," - post_lon = "\"lon\": " + str(args.lon) + "," - post_soundscape_id = "\"soundscapeId\": " + str(soundscape_id) + "," - post_soundscape_start_time = "\"soundscapeStartTime\": " + start_time + "," - post_soundscape_end_time = "\"soundscapeEndTime\": " + end_time + "," - post_commonName = "\"commonName\": \"" + entry[0].split('_')[1] + "\"," - post_scientificName = "\"scientificName\": \"" + entry[0].split('_')[0] + "\"," - post_algorithm = "\"algorithm\": " + "\"alpha\"" + "," - post_confidence = "\"confidence\": " + str(entry[1]) - post_end = " }" - - post_json = post_begin + post_timestamp + post_lat + post_lon + post_soundscape_id + post_soundscape_start_time + post_soundscape_end_time + post_commonName + post_scientificName + post_algorithm + post_confidence + post_end - print(post_json) - response = requests.post(detection_url, json=json.loads(post_json)) - print("Detection POST Response Status - ", response.status_code) - - - conn.send(myReturn.encode(FORMAT)) - - #time.sleep(3) - - conn.close() - -def start(): - # Load model - global INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST - INTERPRETER = loadModel() - server.listen() - print(f"[LISTENING] Server is listening on {SERVER}") - while True: - conn, addr = server.accept() - thread = threading.Thread(target=handle_client, args=(conn, addr)) - thread.start() - print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}") - - -print("[STARTING] server is starting...") -start() diff --git a/scripts/server.py b/scripts/server.py index fdc4a0b..e1787d5 100755 --- a/scripts/server.py +++ b/scripts/server.py @@ -18,10 +18,8 @@ import math import time from decimal import Decimal import json -############################################################################### import requests -import mysql.connector -############################################################################### +import sqlite3 import datetime from time import sleep import pytz @@ -331,48 +329,37 @@ def handle_client(conn, addr): myReturn += str(i) + '-' + str(detections[i][0]) + '\n' - - with open('/home/pi/BirdNET-Pi/BirdDB.txt', 'a') as rfile: for d in detections: for entry in detections[d]: if entry[1] >= min_conf and ((entry[0] in INCLUDE_LIST or len(INCLUDE_LIST) == 0) and (entry[0] not in EXCLUDE_LIST or len(EXCLUDE_LIST) == 0) ): rfile.write(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';') + ';' \ + str(entry[1]) +";" + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';' \ - + str(sensitivity) +';' + str(args.overlap) + '\n') - - def insert_variables_into_table(Date, Time, Sci_Name, Com_Name, Confidence, Lat, Lon, Cutoff, Week, Sens, Overlap): - try: - connection = mysql.connector.connect(host='localhost', - database='birds', - user='birder', - password=db_pwd) - cursor = connection.cursor() - mySql_insert_query = """INSERT INTO detections (Date, Time, Sci_Name, Com_Name, Confidence, Lat, Lon, Cutoff, Week, Sens, Overlap) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ + + str(args.sensitivity) +';' + str(args.overlap) + '\n') - record = (Date, Time, Sci_Name, Com_Name, Confidence, Lat, Lon, Cutoff, Week, Sens, Overlap) - - cursor.execute(mySql_insert_query, record) - connection.commit() - print("Record inserted successfully into detections table") - - - except mysql.connector.Error as error: - print("Failed to insert record into detections table {}".format(error)) - - finally: - if connection.is_connected(): - connection.close() - print("MySQL connection is closed") - + Date = str(current_date) + Time = str(current_time) species = entry[0] - sci_name,com_name = species.split('_') - insert_variables_into_table(str(current_date), str(current_time), sci_name, com_name, \ - str(entry[1]), str(args.lat), str(args.lon), str(min_conf), str(week), \ - str(args.sensitivity), str(args.overlap)) + Sci_Name,Com_Name = species.split('_') + score = entry[1] + Confidence = "{:.0%}".format(score) + Lat = str(args.lat) + Lon = str(args.lon) + Cutoff = str(args.min_conf) + Week = str(args.week) + Sens = str(args.sensitivity) + Overlap = str(args.overlap) + File_Name = Com_Name.replace(" ", "_") + '-' + Confidence + '-' + \ + Date.replace("/", "-") + '-birdnet-' + Time + '.mp3' - print(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';') + ';' + str(entry[1]) +";" + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';' + str(args.sensitivity) +';' + str(args.overlap) + '\n') + #Connect to SQLite Database + con = sqlite3.connect('/home/pi/BirdNET-Pi/scripts/birds2.db') + cur = con.cursor() + cur.execute("INSERT INTO detections VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", (Date, Time, Sci_Name, Com_Name, str(score), Lat, Lon, Cutoff, Week, Sens, Overlap, File_Name)) + + con.commit() + con.close() + print(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';') + ';' + str(entry[1]) + ';' + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';' + str(args.sensitivity) +';' + str(args.overlap) + Com_Name.replace(" ", "_") + '-' + str(score) + '-' + str(current_date) + '-birdnet-' + str(current_time) + '.mp3' + '\n') if birdweather_id != "99999":