diff --git a/homepage/views.php b/homepage/views.php index 908d3ec..5a87e4b 100644 --- a/homepage/views.php +++ b/homepage/views.php @@ -297,14 +297,6 @@ if(isset($_GET['view'])){ 'sudo systemctl restart birdnet_log.service', 'sudo systemctl disable --now birdnet_log.service', 'sudo systemctl enable --now birdnet_log.service', - 'sudo systemctl stop extraction.service', - 'sudo systemctl restart extraction.service', - 'sudo systemctl disable --now extraction.service', - 'sudo systemctl enable --now extraction.service', - 'sudo systemctl stop birdnet_server.service', - 'sudo systemctl restart birdnet_server.service', - 'sudo systemctl disable --now birdnet_server.service', - 'sudo systemctl enable --now birdnet_server.service', 'sudo systemctl stop birdnet_analysis.service', 'sudo systemctl restart birdnet_analysis.service', 'sudo systemctl disable --now birdnet_analysis.service', diff --git a/scripts/analyze.py b/scripts/analyze.py deleted file mode 100755 index e224861..0000000 --- a/scripts/analyze.py +++ /dev/null @@ -1,123 +0,0 @@ -import argparse -import socket - -HEADER = 64 -PORT = 5050 -FORMAT = 'utf-8' -DISCONNECT_MESSAGE = "!DISCONNECT" -SERVER = "localhost" -ADDR = (SERVER, PORT) - -client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -client.connect(ADDR) - - -def send(msg): - message = msg.encode(FORMAT) - msg_length = len(message) - send_length = str(msg_length).encode(FORMAT) - send_length += b' ' * (HEADER - len(send_length)) - client.send(send_length) - client.send(message) - print(client.recv(2048).decode(FORMAT)) - - -def main(): - - global INCLUDE_LIST - global EXCLUDE_LIST - - # Parse passed arguments - parser = argparse.ArgumentParser() - parser.add_argument('--i', help='Path to input file.') - parser.add_argument( - '--o', - default='result.csv', - help='Path to output file. Defaults to result.csv.') - parser.add_argument( - '--lat', - type=float, - default=-1, - help='Recording location latitude. Set -1 to ignore.') - parser.add_argument( - '--lon', - type=float, - default=-1, - help='Recording location longitude. Set -1 to ignore.') - parser.add_argument( - '--week', - type=int, - default=-1, - help='Week of the year when the recording was made. Values in [1, 48] (4 weeks per month). Set -1 to ignore.') - parser.add_argument( - '--overlap', - type=float, - default=0.0, - help='Overlap in seconds between extracted spectrograms. Values in [0.0, 2.9]. Defaults tp 0.0.') - parser.add_argument( - '--sensitivity', - type=float, - default=1.0, - help='Detection sensitivity; Higher values result in higher sensitivity. Values in [0.5, 1.5]. Defaults to 1.0.') - parser.add_argument( - '--min_conf', - type=float, - default=0.1, - help='Minimum confidence threshold. Values in [0.01, 0.99]. Defaults to 0.1.') - parser.add_argument( - '--include_list', - default='null', - help='Path to text file containing a list of included species. Not used if not provided.') - parser.add_argument( - '--exclude_list', - default='null', - help='Path to text file containing a list of excluded species. Not used if not provided.') - parser.add_argument( - '--birdweather_id', - default='99999', - help='Private Station ID for BirdWeather.') - - args = parser.parse_args() - - sockParams = '' - if args.i: - sockParams += 'i=' + args.i + '||' - if args.o: - sockParams += 'o=' + args.o + '||' - if args.birdweather_id: - sockParams += 'birdweather_id=' + args.birdweather_id + '||' - if args.include_list: - sockParams += 'include_list=' + args.include_list + '||' - if args.exclude_list: - sockParams += 'exclude_list=' + args.exclude_list + '||' - if args.overlap: - sockParams += 'overlap=' + str(args.overlap) + '||' - if args.week: - sockParams += 'week=' + str(args.week) + '||' - if args.sensitivity: - sockParams += 'sensitivity=' + str(args.sensitivity) + '||' - if args.min_conf: - sockParams += 'min_conf=' + str(args.min_conf) + '||' - if args.lat: - sockParams += 'lat=' + str(args.lat) + '||' - if args.lon: - sockParams += 'lon=' + str(args.lon) + '||' - - send(sockParams) - - send(DISCONNECT_MESSAGE) - # time.sleep(3) - -############################################################################### -############################################################################### - - -if __name__ == '__main__': - - main() - - # Example calls - # python3 analyze.py --i 'example/XC558716 - Soundscape.mp3' --lat 35.4244 --lon -120.7463 --week 18 - # python3 analyze.py --i 'example/XC563936 - Soundscape.mp3' --lat 47.6766 - # --lon -122.294 --week 11 --overlap 1.5 --min_conf 0.25 --sensitivity - # 1.25 --custom_list 'example/custom_species_list.txt' diff --git a/scripts/birdnet_analysis.sh b/scripts/birdnet_analysis.sh deleted file mode 100755 index 633c1e7..0000000 --- a/scripts/birdnet_analysis.sh +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env -S --default-signal=PIPE bash -# Runs BirdNET-Lite -#set -x -source /etc/birdnet/birdnet.conf -# Document this run's birdnet.conf settings -# Make a temporary file to compare the current birdnet.conf with -# the birdnet.conf as it was the last time this script was called -my_dir=$HOME/BirdNET-Pi/scripts -if [ -z ${THIS_RUN} ];then THIS_RUN=$my_dir/thisrun.txt;fi -[ -f ${THIS_RUN} ] || touch ${THIS_RUN} && chmod g+w ${THIS_RUN} -if [ -z ${LAST_RUN} ];then LAST_RUN=$my_dir/lastrun.txt;fi -[ -z ${LATITUDE} ] && echo "LATITUDE not set, exiting 1" && exit 1 -[ -z ${LONGITUDE} ] && echo "LONGITUDE not set, exiting 1" && exit 1 -make_thisrun() { - sleep .4 - awk '!/#/ && !/^$/ {print}' /etc/birdnet/birdnet.conf \ - > >(tee "${THIS_RUN}") - sleep .5 -} -make_thisrun &> /dev/null -if ! diff ${LAST_RUN} ${THIS_RUN};then - echo "The birdnet.conf file has changed" - if grep REC <(diff $LAST_RUN $THIS_RUN);then - echo "Recording element changed -- restarting 'birdnet_recording.service'" - sudo systemctl stop birdnet_recording.service - sudo rm -rf ${RECS_DIR}/$(date +%B-%Y/%d-%A)/* - sudo systemctl start birdnet_recording.service - fi - cat ${THIS_RUN} > ${LAST_RUN} -fi - -INCLUDE_LIST="$HOME/BirdNET-Pi/include_species_list.txt" -EXCLUDE_LIST="$HOME/BirdNET-Pi/exclude_species_list.txt" -if [ ! -f ${INCLUDE_LIST} ];then - touch ${INCLUDE_LIST} && - chmod g+rw ${INCLUDE_LIST} -fi -if [ ! -f ${EXCLUDE_LIST} ];then - touch ${EXCLUDE_LIST} && - chmod g+rw ${EXCLUDE_LIST} -fi -if [ "$(du ${INCLUDE_LIST} | awk '{print $1}')" -lt 4 ];then - INCLUDE_LIST=null -fi -if [ "$(du ${EXCLUDE_LIST} | awk '{print $1}')" -lt 4 ];then - EXCLUDE_LIST=null -fi - -# Create an array of the audio files -# Takes one argument: -# - {DIRECTORY} -get_files() { - files=($( find ${1} -maxdepth 1 -name '*wav' ! -size 0\ - | sort \ - | head -n 20 \ - | awk -F "/" '{print $NF}' )) - [ -n "${files[1]}" ] && echo "Files loaded" -} - -# Move all files that have been analyzed already into newly created "Analyzed" -# directory -# Takes one argument: -# - {DIRECTORY} -move_analyzed() { - for i in "${files[@]}";do - j="${i}.csv" - if [ -f "${1}/${j}" ];then - if [ ! -d "${1}-Analyzed" ];then - mkdir -p "${1}-Analyzed" && echo "'Analyzed' directory created" - fi - mv "${1}/${i}" "${1}-Analyzed/" - mv "${1}/${j}" "${1}-Analyzed/" - fi - done -} - -# Run BirdNET-Lite on the WAVE files from get_files() -# Uses one argument: -# - {DIRECTORY} -run_analysis() { - PYTHON_VIRTUAL_ENV="$HOME/BirdNET-Pi/birdnet/bin/python3" - DIR="$HOME/BirdNET-Pi/scripts" - - sleep .5 - - ### TESTING NEW WEEK CALCULATION - WEEK_OF_YEAR="$(echo "($(date +%m)-1) * 4" | bc -l)" - DAY_OF_MONTH="$(date +%d)" - if [ ${DAY_OF_MONTH} -le 7 ];then - WEEK="$(echo "${WEEK_OF_YEAR} + 1" |bc -l)" - elif [ ${DAY_OF_MONTH} -le 14 ];then - WEEK="$(echo "${WEEK_OF_YEAR} + 2" |bc -l)" - elif [ ${DAY_OF_MONTH} -le 21 ];then - WEEK="$(echo "${WEEK_OF_YEAR} + 3" |bc -l)" - elif [ ${DAY_OF_MONTH} -ge 22 ];then - WEEK="$(echo "${WEEK_OF_YEAR} + 4" |bc -l)" - fi - - for i in "${files[@]}";do - [ ! -f ${1}/${i} ] && continue - echo "${1}/${i}" > $HOME/BirdNET-Pi/analyzing_now.txt - [ -z ${RECORDING_LENGTH} ] && RECORDING_LENGTH=15 - echo "RECORDING_LENGTH set to ${RECORDING_LENGTH}" - itr=0 - until [ -z "$(lsof -t ${1}/${i})" ];do - itr=$((itr+1)) - if [ $itr -eq $(($RECORDING_LENGTH * 3)) ]; then - echo "Maximum number of attempts exceeded. Exiting & restarting service." - exit - fi - sleep 2 - done - - if ! grep 5050 <(netstat -tulpn 2>&1) &> /dev/null 2>&1;then - echo "Waiting for socket" - until grep 5050 <(netstat -tulpn 2>&1) &> /dev/null 2>&1;do - sleep 1 - done - fi - # prepare optional parameters for analyze.py - if [ -f ${INCLUDE_LIST} ]; then - INCLUDEPARAM="--include_list ${INCLUDE_LIST}" - else - INCLUDEPARAM="" - fi - if [ -f ${EXCLUDE_LIST} ]; then - EXCLUDEPARAM="--exclude_list ${EXCLUDE_LIST}" - else - EXCLUDEPARAM="" - fi - if [ ! -z $BIRDWEATHER_ID ]; then - BIRDWEATHER_ID_PARAM="--birdweather_id ${BIRDWEATHER_ID}" - BIRDWEATHER_ID_LOG="--birdweather_id \"IN_USE\"" - else - BIRDWEATHER_ID_PARAM="" - BIRDWEATHER_ID_LOG="" - fi - echo $PYTHON_VIRTUAL_ENV "$DIR/analyze.py" \ ---i "${1}/${i}" \ ---o "${1}/${i}.csv" \ ---lat $(echo "${LATITUDE}" | awk '{print int($1+0.5)}').XX \ ---lon $(echo "${LONGITUDE}" | awk '{print int($1+0.5)}').XX \ ---week "${WEEK}" \ ---overlap "${OVERLAP}" \ ---sensitivity "${SENSITIVITY}" \ ---min_conf "${CONFIDENCE}" \ -${INCLUDEPARAM} \ -${EXCLUDEPARAM} \ -${BIRDWEATHER_ID_LOG} - $PYTHON_VIRTUAL_ENV $DIR/analyze.py \ - --i "${1}/${i}" \ - --o "${1}/${i}.csv" \ - --lat "${LATITUDE}" \ - --lon "${LONGITUDE}" \ - --week "${WEEK}" \ - --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ - --min_conf "${CONFIDENCE}" \ - ${INCLUDEPARAM} \ - ${EXCLUDEPARAM} \ - ${BIRDWEATHER_ID_PARAM} - if [ ! -z $HEARTBEAT_URL ]; then - echo "Performing Heartbeat" - IP=`curl -s ${HEARTBEAT_URL}` - echo "Heartbeat: $IP" - fi - done -} - -# The three main functions -# Takes one argument: -# - {DIRECTORY} -run_birdnet() { - get_files "${1}" - move_analyzed "${1}" - run_analysis "${1}" -} - -until grep 5050 <(netstat -tulpn 2>&1) &> /dev/null 2>&1;do - sleep 1 -done - -if [ $(find ${RECS_DIR}/StreamData -maxdepth 1 -name '*wav' 2>/dev/null| wc -l) -gt 0 ];then - find $RECS_DIR -maxdepth 1 -name '*wav' -type f -size 0 -delete - run_birdnet "${RECS_DIR}/StreamData" -fi - -YESTERDAY="$RECS_DIR/$(date --date="yesterday" "+%B-%Y/%d-%A")" -TODAY="$RECS_DIR/$(date "+%B-%Y/%d-%A")" -if [ $(find ${YESTERDAY} -name '*wav' 2>/dev/null | wc -l) -gt 0 ];then - find $YESTERDAY -name '*wav' -type f -size 0 -delete - run_birdnet "${YESTERDAY}" -elif [ $(find ${TODAY} -name '*wav' 2>/dev/null | wc -l) -gt 0 ];then - find $TODAY -name '*wav' -type f -size 0 -delete - run_birdnet "${TODAY}" -fi diff --git a/scripts/birdnet_log.sh b/scripts/birdnet_log.sh index 460d45d..e340292 100755 --- a/scripts/birdnet_log.sh +++ b/scripts/birdnet_log.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -journalctl --no-hostname -q -o short -fu birdnet_analysis -ubirdnet_server -uextraction | sed "s/$(date "+%b %d ")//g;s/${HOME//\//\\/}\///g;/Line/d;/find/d;/systemd/d;s/ .*\[.*\]: /---/" +journalctl --no-hostname -q -o short -fu birdnet_analysis | sed "s/$(date "+%b %d ")//g;s/${HOME//\//\\/}\///g;/Line/d;/find/d;/systemd/d;s/ .*\[.*\]: /---/" diff --git a/scripts/clear_all_data.sh b/scripts/clear_all_data.sh index 7108534..7ca511f 100755 --- a/scripts/clear_all_data.sh +++ b/scripts/clear_all_data.sh @@ -11,7 +11,6 @@ my_dir=${HOME}/BirdNET-Pi/scripts echo "Stopping services" sudo systemctl stop birdnet_recording.service sudo systemctl stop birdnet_analysis.service -sudo systemctl stop birdnet_server.service echo "Removing all data . . . " sudo rm -drf "${RECS_DIR}" sudo rm -f "${IDFILE}" diff --git a/scripts/extract_new_birdsounds.sh b/scripts/extract_new_birdsounds.sh deleted file mode 100755 index 0988f91..0000000 --- a/scripts/extract_new_birdsounds.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env bash -# Exit when any command fails -#set -x -set -e -# Keep track of the last executed command -#trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG -## Echo an error message before exiting -#trap 'echo "\"${last_command}\" command exited with code $?."' EXIT -# Remove temporary file -trap 'rm -f $TMPFILE' EXIT -source /etc/birdnet/birdnet.conf -[ -z ${RECORDING_LENGTH} ] && RECORDING_LENGTH=15 - -# Set Variables -TMPFILE=$(mktemp) -#SCAN_DIRS are all directories marked "Analyzed" -SCAN_DIRS=($(find $RECS_DIR -type d -name '*Analyzed' 2>/dev/null | sort )) - -for h in "${SCAN_DIRS[@]}";do - # The TMPFILE is created from each .csv file BirdNET creates - # within each "Analyzed" directory - # Field 1: Start (s) - # Field 2: End (s) - # Field 3: Scientific name - # Field 4: Common name - # Field 5: Confidence - - # Iterates over each "Analyzed" directory - for i in $(find ${h} -name '*csv' 2>/dev/null | sort );do - # Iterates over each '.csv' file found in each "Analyzed" directory - # to create the TMPFILE - echo "$(basename ${i})" >> ${TMPFILE} - sort -k1n -t\; "${i}" | awk '!/Start/{print}' >> ${TMPFILE} - done - - # The extraction reads each line of the TMPFILE and sets the variables ffmpeg - # will use. - while read -r line;do - echo "Line = $line" - DATE="$(echo "${line}" \ - | awk -F- '/birdnet/{print $1"-"$2"-"$3}')" - if [ ! -z ${DATE} ];then - OLDFILE="$(echo "${line}" | awk -F. '/birdnet/{print $1"."$2}')" ; continue - fi - - if [ -z ${DATE} ];then - DATE=$(date "+%F") - fi - START="$(echo "${line}" | awk -F\; '!/birdnet/{print $1}')" - END="$(echo "${line}" | awk -F\; '!/birdnet/{print $2}')" - COMMON_NAME=""$(echo ${line} \ - | awk -F\; '!/birdnet/{print $4}'|tr -d "'")"" - SCIENTIFIC_NAME=""$(echo ${line} \ - | awk -F\; '!/birdnet/{print $3}')"" - CONFIDENCE=""$(echo ${line} \ - | awk -F\; '{print $5}')"" - - #CONFIDENCE_SCORE="${CONFIDENCE:0:2}%" - locale_decimal=$(locale decimal_point) - if [[ $locale_decimal == "." ]]; then - CONFIDENCE_SCORE="$(printf %.0f "$(echo "scale=2; ${CONFIDENCE} * 100" | bc)")" - else - CONFIDENCE_SCORE="$(printf %.0f "$(echo "scale=2; "${CONFIDENCE}" * 100" | bc | tr '.' ',')")" - fi - NEWFILE="${COMMON_NAME// /_}-${CONFIDENCE_SCORE}-${OLDFILE//.wav/.${AUDIOFMT}}" - echo "NEWFILE=$NEWFILE" - NEWSPECIES_BYDATE="${EXTRACTED}/By_Date/${DATE}/${COMMON_NAME// /_}" - - # If the extracted file already exists, move on - if [[ -f "${NEWSPECIES_BYDATE}/${NEWFILE}" ]];then - echo "Extraction exists. Moving on" - continue - fi - - - # Before extracting the "Selection," the script checks to be sure the - # original WAVE file still exists. - [[ -f "${h}/${OLDFILE}" ]] || continue - - # If a directory does not already exist for the species (by date), - # it is created - [[ -d "${NEWSPECIES_BYDATE}" ]] || mkdir -p "${NEWSPECIES_BYDATE}" - - # If there are already 20 extracted entries for a given species - # for today, remove the oldest file and create the new one. - # if [[ "$(find ${NEWSPECIES_BYDATE} | wc -l)" -ge 20 ]];then - # echo "20 ${SPECIES}s, already! Removing the oldest by-date and making a new one" - # cd ${NEWSPECIES_BYDATE} || exit 1 - # ls -1t . | tail -n +20 | xargs -r rm -vv - # fi - - # If the above tests have passed, then the extraction happens. - # After creating the extracted files by-date, and a directory tree - # structured by-species, symbolic links are made to populate the new - # directory. - - # This section sets the SPACER that will be used to pad the audio clip with - # context. If EXTRACTION_LENGTH is 10, for instance, 3 seconds are removed - # from that value and divided by 2, so that the 3 seconds of the call are - # within 3.5 seconds of audio context before and after. - [ -z ${EXTRACTION_LENGTH} ] && EXTRACTION_LENGTH=6 - SPACER=$(echo "scale=1;(${EXTRACTION_LENGTH} - 3 )/2" |bc -l) - START=$(echo "scale=1;${START} - ${SPACER}"|bc -l) - END=$(echo "scale=1;${END} + ${SPACER}"|bc -l) - - # If the SPACER would have the START value less that 0, start at the - # beginning of the audio file. If the SPACER would make the END value - # exceed the end of the audio file, end the extraction at the end of the - # audio file. - if (( $(echo "${START} < 1" | bc -l) ));then START=0;fi - if (( $(echo "${END} > ${RECORDING_LENGTH}" | bc -l) ));then END=${RECORDING_LENGTH};fi - - if [ "${RECORDING_LENGTH}" == "${EXTRACTION_LENGTH}" ];then - START=0 - END=${RECORDING_LENGTH} - fi - - sox -V1 "${h}/${OLDFILE}" "${NEWSPECIES_BYDATE}/${NEWFILE}" \ - trim ="${START}" ="${END}" - - RAW_SPECTROGRAM=${RAW_SPECTROGRAM} - # Check if RAW_SPECTROGRAM is 1 - if [ "$RAW_SPECTROGRAM" == "1" ]; then - # If it is, add "-r" as an argument to the SOX command - sox -V1 "${NEWSPECIES_BYDATE}/${NEWFILE}" -n remix 1 rate 24k spectrogram \ - -t "${COMMON_NAME}" \ - -c "${NEWSPECIES_BYDATE//$HOME\/}/${NEWFILE}" \ - -o "${NEWSPECIES_BYDATE}/${NEWFILE}.png" \ - -r - else - # If it's not, run the SOX command without the "-r" argument - sox -V1 "${NEWSPECIES_BYDATE}/${NEWFILE}" -n remix 1 rate 24k spectrogram \ - -t "$(echo "${COMMON_NAME}" | iconv -f utf8 -t ascii//TRANSLIT)" \ - -c "${NEWSPECIES_BYDATE//$HOME\/}/${NEWFILE}" \ - -o "${NEWSPECIES_BYDATE}/${NEWFILE}.png" - fi - - done < "${TMPFILE}" - - # Once each line of the TMPFILE has been processed, the TMPFILE is emptied - # for the next iteration of the for loop. - >"${TMPFILE}" - - # Rename files that have been processed so that they are not processed on the - # next extraction. - [[ -d "${PROCESSED}" ]] || mkdir "${PROCESSED}" - #echo "Moving processed files to ${PROCESSED}" - mv ${h}/* ${PROCESSED} &> /dev/null || true - - # Removes old directories - if echo "${h}" | grep $(date --date="-2 day" "+%A") &> /dev/null;then - echo "Removing old directories" - rm -drf "${h}" - rm -drf "$(echo ${h} | cut -d'-' -f1-3)" - fi -done - -#echo "Linking Processed files to "${EXTRACTED}/Processed" web directory" -# After all audio extractions have taken place, a directory is created to house -# the original WAVE and .txt files used for this extraction processs. -if [[ ! -L ${EXTRACTED}/Processed ]] || [[ ! -e ${EXTRACTED}/Processed ]];then - ln -sf ${PROCESSED} ${EXTRACTED}/Processed -fi diff --git a/scripts/restart_services.sh b/scripts/restart_services.sh index aba21d8..9ce33d3 100755 --- a/scripts/restart_services.sh +++ b/scripts/restart_services.sh @@ -5,14 +5,13 @@ set -x my_dir=$HOME/BirdNET-Pi/scripts -sudo systemctl stop birdnet_server.service sudo systemctl stop birdnet_recording.service services=(chart_viewer.service spectrogram_viewer.service icecast2.service - extraction.service birdnet_recording.service + birdnet_analysis.service birdnet_log.service birdnet_stats.service) @@ -20,31 +19,11 @@ for i in "${services[@]}";do sudo systemctl restart "${i}" done -sudo systemctl start birdnet_server.service -sleep 5 - for i in {1..5}; do - # We want to loop here (5*5seconds) until the server is running and listening on its port - systemctl is-active --quiet birdnet_server.service \ - && grep 5050 <(netstat -tulpn 2>&1) \ - && logger "[$0] birdnet_server.service is running" \ + # We want to loop here (5*5seconds) until the birdnet_analysis.service is running + systemctl is-active --quiet birdnet_analysis.service \ + && logger "[$0] birdnet_analysis.service is running" \ && break sleep 5 done - -# Let's check a final time to ensure the server is running -systemctl is-active --quiet birdnet_server.service && grep 5050 <(netstat -tulpn 2>&1) -status=$? - -if (( status != 0 )); then - logger "[$0] Unable to start birdnet_server.service... Looping until it start properly" - - until grep 5050 <(netstat -tulpn 2>&1);do - sudo systemctl restart birdnet_server.service - sleep 45 - done -fi - -# Finally start the birdnet_analysis.service -sudo systemctl restart birdnet_analysis.service diff --git a/scripts/server.py b/scripts/server.py index 248dc5e..5a2cbbd 100755 --- a/scripts/server.py +++ b/scripts/server.py @@ -1,23 +1,13 @@ -import re -from pathlib import Path -from tzlocal import get_localzone import datetime -import sqlite3 -import requests -import json -import time import math -import numpy as np -import librosa import operator -import socket -import threading import os -import gzip +import time + +import librosa +import numpy as np from utils.helpers import get_settings, Detection -from utils.notifications import sendAppriseNotifications -from utils.parse_settings import config_to_settings os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' os.environ['CUDA_VISIBLE_DEVICES'] = '' @@ -27,47 +17,13 @@ try: except BaseException: from tensorflow import lite as tflite - -HEADER = 64 -PORT = 5050 -SERVER = "localhost" -ADDR = (SERVER, PORT) -FORMAT = 'utf-8' -DISCONNECT_MESSAGE = "!DISCONNECT" - userDir = os.path.expanduser('~') -DB_PATH = userDir + '/BirdNET-Pi/scripts/birds.db' - INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST = (None, None, None) PREDICTED_SPECIES_LIST = [] model, priv_thresh, sf_thresh = (None, None, None) mdata, mdata_params = (None, None) -server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - - -def bind_port(): - try: - server.bind(ADDR) - except BaseException: - print("Waiting on socket") - time.sleep(5) - - -# Open most recent Configuration and grab DB_PWD as a python variable -with open(userDir + '/BirdNET-Pi/scripts/thisrun.txt', 'r') as f: - this_run = f.readlines() - audiofmt = "." + str(str(str([i for i in this_run if i.startswith('AUDIOFMT')]).split('=')[1]).split('\\')[0]) - priv_thresh = float("." + str(str(str([i for i in this_run if i.startswith('PRIVACY_THRESHOLD')]).split('=')[1]).split('\\')[0])) / 10 - try: - model = str(str(str([i for i in this_run if i.startswith('MODEL')]).split('=')[1]).split('\\')[0]) - sf_thresh = str(str(str([i for i in this_run if i.startswith('SF_THRESH')]).split('=')[1]).split('\\')[0]) - except Exception: - model = "BirdNET_6K_GLOBAL_MODEL" - sf_thresh = 0.03 - def loadModel(): @@ -336,303 +292,6 @@ def get_metadata(lat, lon, week): return mdata -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) - and (entry[0] in PREDICTED_SPECIES_LIST or len(PREDICTED_SPECIES_LIST) == 0)): - rfile.write(d + ';' + entry[0].replace('_', ';').split("/")[0] + ';' + 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.") - - while True: - msg_length = conn.recv(HEADER).decode(FORMAT) - if not msg_length: - break - - msg_length = int(msg_length) - msg = conn.recv(msg_length).decode(FORMAT) - if not msg: - break - if msg == DISCONNECT_MESSAGE: - break - - # print(f"[{addr}] {msg}") - - args = type('', (), {})() - - args.i = '' - args.o = '' - 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 & handle errors - try: - audioData = readAudioData(args.i, args.overlap) - - except (NameError, TypeError) as e: - print(f"Error with the following info: {e}") - open('~/BirdNET-Pi/analyzing_now.txt', 'w').close() - - finally: - pass - - # 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 - - # Get the RSTP stream identifier from the filename if it exists - RTSP_ident_for_fn = "" - RTSP_ident = re.search("RTSP_[0-9]+-", file_name) - if RTSP_ident is not None: - RTSP_ident_for_fn = RTSP_ident.group() - - # Find and remove the identifier for the RSTP stream url it was from that is added when more than one - # RSTP stream is recorded simultaneously, in order to make the filenames unique as filenames are all - # generated at the same time - file_name = re.sub("RTSP_[0-9]+-", "", file_name) - - # Now we can read the date and time as normal - # First portion of the filename contaning the date in Y m d - file_date = file_name.split('-birdnet-')[0] - # Second portion of the filename containing the time in H:M:S - file_time = file_name.split('-birdnet-')[1] - # Join the date and time together to get a complete string representing when the audio was recorded - 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)) - - # Process audio data and get detections - detections = analyzeAudioData(audioData, args.lat, args.lon, week, args.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(userDir + '/BirdNET-Pi/BirdDB.txt', 'a') as rfile: - for d in detections: - species_apprised_this_run = [] - 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) - and (entry[0] in PREDICTED_SPECIES_LIST or len(PREDICTED_SPECIES_LIST) == 0)): - # Write to text file. - rfile.write(str(current_date) + ';' + str(current_time) + ';' + entry[0].replace('_', ';').split("/")[0] + ';' - + str(entry[1]) + ";" + str(args.lat) + ';' + str(args.lon) + ';' + str(min_conf) + ';' + str(week) + ';' - + str(args.sensitivity) + ';' + str(args.overlap) + '\n') - - # Write to database - Date = str(current_date) - Time = str(current_time) - species = entry[0].split("/")[0] - Sci_Name, Com_Name = species.split('_') - score = entry[1] - Confidence = str(round(score * 100)) - 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) - Com_Name = Com_Name.replace("'", "") - File_Name = Com_Name.replace(" ", "_") + '-' + Confidence + '-' + \ - Date.replace("/", "-") + '-birdnet-' + RTSP_ident_for_fn + Time + audiofmt - - # Connect to SQLite Database - for attempt_number in range(3): - try: - con = sqlite3.connect(DB_PATH) - 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() - break - except BaseException: - print("Database busy") - time.sleep(2) - - # Apprise of detection if not already alerted this run. - if not entry[0] in species_apprised_this_run: - settings_dict = config_to_settings(userDir + '/BirdNET-Pi/scripts/thisrun.txt') - sendAppriseNotifications(species, - str(score), - str(round(score * 100)), - File_Name, - Date, - Time, - Week, - Lat, - Lon, - Cutoff, - Sens, - Overlap, - settings_dict, - DB_PATH) - species_apprised_this_run.append(entry[0]) - - 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) + - ';' + - File_Name + - '\n') - - if birdweather_id != "99999": - try: - - 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() - gzip_wav_data = gzip.compress(wav_data) - response = requests.post(url=soundscape_url, data=gzip_wav_data, headers={'Content-Type': 'application/octet-stream', - 'Content-Encoding': 'gzip'}) - 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].split("/")[0] + "\"," - post_scientificName = "\"scientificName\": \"" + entry[0].split('_')[0] + "\"," - - if model == "BirdNET_GLOBAL_6K_V2.4_Model_FP16": - post_algorithm = "\"algorithm\": " + "\"2p4\"" + "," - else: - 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) - except BaseException: - print("Cannot POST right now") - conn.send(myReturn.encode(FORMAT)) - - # time.sleep(3) - - conn.close() - - def load_global_model(): global INTERPRETER global model, priv_thresh, sf_thresh @@ -671,22 +330,3 @@ def run_analysis(file): d = Detection(time_slot.split(';')[0], time_slot.split(';')[1], entry[0], entry[1]) confident_detections.append(d) return confident_detections - - -def start(): - bind_port() - # Load model - global INTERPRETER - 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...") -if __name__ == '__main__': - start() diff --git a/scripts/service_controls.php b/scripts/service_controls.php index 1888599..3d51a7b 100644 --- a/scripts/service_controls.php +++ b/scripts/service_controls.php @@ -4,10 +4,10 @@ function service_status($name) { $home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd"); $home = trim($home); - if($name == "birdnet_server.service") { - $filesinproc=trim(shell_exec("ls ".$home."/BirdSongs/Processed | wc -l")); + if($name == "birdnet_analysis.service") { + $filesinproc=trim(shell_exec("ls ".$home."/BirdSongs/StreamData | wc -l")); if($filesinproc > 200) { - echo "(stalled - backlog of ".$filesinproc." files in ~/BirdSongs/Processed/)"; + echo "(stalled - backlog of ".$filesinproc." files in ~/BirdSongs/StreamData/)"; return; } } @@ -45,21 +45,7 @@ function service_status($name) {
- -