Merge branch 'sqlite' of github.com:mcguirepr89/BirdNET-Pi into sqlite

This commit is contained in:
mcguirepr89
2022-02-18 08:55:55 -05:00
9 changed files with 170 additions and 898 deletions
+24
View File
@@ -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
-46
View File
@@ -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
+10 -42
View File
@@ -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
+83 -274
View File
@@ -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
+29 -68
View File
@@ -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
-423
View File
@@ -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()
+23 -36
View File
@@ -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":