diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 220bbd0..915ace1 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] +custom: https://thesatanictemple.com/pages/fundraiser#donate diff --git a/README.md b/README.md index 6680ba2..213a5bd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- BirdNET-Pi + BirdNET-Pi

A realtime acoustic bird classification system for the Raspberry Pi 4B @@ -37,6 +37,8 @@ Currently listening in these countries . . . that I know of . . . - Italy - Finland - Australia +- Canada +- Switzerland ## Features * 24/7 recording and BirdNET-Lite analysis @@ -141,3 +143,6 @@ Current database languages include the list below: | Swedish | 264 | 4.15% | | Thai | 5580 | 87.71% | | Ukrainian | 646 | 10.15% | + +## :thinking: +Are you a lucky ducky with an extra Raspberry Pi 4B lying around? [Here's an idea!](https://foldingathome.org/alternative-downloads) diff --git a/birdnet.conf-defaults b/birdnet.conf-defaults index 6a7aa7f..d1dd7b9 100644 --- a/birdnet.conf-defaults +++ b/birdnet.conf-defaults @@ -48,6 +48,13 @@ ICE_PWD=birdnetpi BIRDNETPI_URL= +#---------------------------- RTSP Stream URL -------------------------------# + +## If RTSP_STREAM is set, the system will use the RTSP stream as its audio +## source instead of recording its own audio. If this variable is kept empty, +## BirdNET-Pi will default to recording its own audio. + +RTSP_STREAM= #------------------- Mobile Notifications via Pushed.co ---------------------# #____________The two variables below enable mobile notifications_______________# diff --git a/homepage/style.css b/homepage/style.css index 618fb92..6469e1c 100644 --- a/homepage/style.css +++ b/homepage/style.css @@ -513,3 +513,21 @@ button:hover { padding:5px; border: 2px solid black; } +button.legacyview { + color:gray; + margin:5px; + float:right; + z-index:100; + position:relative; + font-size:small; + background:#dbffeb; + padding:5px; + border: 2px solid black; +} +button.loadmore { + margin-top:10px; + font-size:x-large; + background:#dbffeb; + padding:10px; + border: 2px solid black; +} diff --git a/homepage/views.php b/homepage/views.php index 55f2c75..942f6bf 100644 --- a/homepage/views.php +++ b/homepage/views.php @@ -185,10 +185,32 @@ if(isset($_GET['view'])){ } } } elseif(isset($_GET['submit'])) { - $command = $_GET['submit']; - if(isset($command)){ - $results = shell_exec("$command 2>&1"); - echo "

$results
"; + if (file_exists('./scripts/thisrun.txt')) { + $config = parse_ini_file('./scripts/thisrun.txt'); + } elseif (file_exists('./scripts/firstrun.ini')) { + $config = parse_ini_file('./scripts/firstrun.ini'); + } + $caddypwd = $config['CADDY_PWD']; + if (!isset($_SERVER['PHP_AUTH_USER'])) { + header('WWW-Authenticate: Basic realm="My Realm"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'You cannot access the web terminal'; + exit; + } else { + $submittedpwd = $_SERVER['PHP_AUTH_PW']; + $submitteduser = $_SERVER['PHP_AUTH_USER']; + if($submittedpwd == $caddypwd && $submitteduser == 'birdnet'){ + $command = $_GET['submit']; + if(isset($command)){ + $results = shell_exec("$command 2>&1"); + echo "
$results
"; + } else { + header('WWW-Authenticate: Basic realm="My Realm"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'You cannot access the web terminal'; + exit; + } + } } ob_end_flush(); } else {include('overview.php');} diff --git a/newinstaller.sh b/newinstaller.sh old mode 100644 new mode 100755 index 64fa222..a3f2f05 --- a/newinstaller.sh +++ b/newinstaller.sh @@ -6,11 +6,18 @@ USER=$USER export HOME=$HOME export USER=$USER -branch=main -if ! which git &> /dev/null;then +PACKAGES_MISSING= +for cmd in git jq ; do + if ! which $cmd &> /dev/null;then + PACKAGES_MISSING="${PACKAGES_MISSING} $cmd" + fi +done +if [[ ! -z $PACKAGES_MISSING ]] ; then sudo apt update - sudo apt -y install git + sudo apt -y install $PACKAGES_MISSING fi + +branch=main git clone -b $branch https://github.com/mcguirepr89/BirdNET-Pi.git ${HOME}/BirdNET-Pi && $HOME/BirdNET-Pi/scripts/install_birdnet.sh diff --git a/scripts/advanced.php b/scripts/advanced.php index a4da847..bfcd285 100644 --- a/scripts/advanced.php +++ b/scripts/advanced.php @@ -64,6 +64,19 @@ if(isset($_GET['submit'])) { } } + if(isset($_GET["rtsp_stream"])) { + $rtsp_stream = str_replace("\r\n", ",", $_GET["rtsp_stream"]); + if(strcmp($rtsp_stream,$config['RTSP_STREAM']) !== 0) { + $contents = preg_replace("/RTSP_STREAM=.*/", "RTSP_STREAM=$rtsp_stream", $contents); + $contents2 = preg_replace("/RTSP_STREAM=.*/", "RTSP_STREAM=$rtsp_stream", $contents2); + $fh = fopen('/etc/birdnet/birdnet.conf', "w"); + $fh2 = fopen("./scripts/thisrun.txt", "w"); + fwrite($fh, $contents); + fwrite($fh2, $contents2); + exec('sudo systemctl restart birdnet_recording.service'); + } + } + if(isset($_GET["overlap"])) { $overlap = $_GET["overlap"]; if(strcmp($overlap,$config['OVERLAP']) !== 0) { @@ -108,14 +121,14 @@ if(isset($_GET['submit'])) { $contents2 = preg_replace("/PRIVACY_MODE=.*/", "PRIVACY_MODE=$privacy_mode", $contents2); if(strcmp($privacy_mode,"on") == 0) { exec('sudo sed -i \'s/\/usr\/local\/bin\/server.py/\/usr\/local\/bin\/privacy_server.py/g\' ../../BirdNET-Pi/templates/birdnet_server.service'); - exec('sudo systemctl daemon-reload'); - exec('restart_services.sh'); - header('Location: /log'); + exec('sudo systemctl daemon-reload'); + exec('restart_services.sh'); + header('Location: /log'); } elseif(strcmp($privacy_mode,"off") == 0) { exec('sudo sed -i \'s/\/usr\/local\/bin\/privacy_server.py/\/usr\/local\/bin\/server.py/g\' ../../BirdNET-Pi/templates/birdnet_server.service'); - exec('sudo systemctl daemon-reload'); - exec('restart_services.sh'); - header('Location: /log'); + exec('sudo systemctl daemon-reload'); + exec('restart_services.sh'); + header('Location: /log'); } } } @@ -201,6 +214,9 @@ if (file_exists('./scripts/thisrun.txt')) {

Set Channels to the number of channels supported by your sound card. 32 max.

+ +
+

If you place an RTSP stream URL here, BirdNET-Pi will use that as its audio source.


Set Recording Length in seconds between 6 and 60. Multiples of 3 are recommended, as BirdNET analyzes in 3-second chunks.

diff --git a/scripts/birdnet_analysis.sh b/scripts/birdnet_analysis.sh index 3edfe2d..de0b5c6 100755 --- a/scripts/birdnet_analysis.sh +++ b/scripts/birdnet_analysis.sh @@ -91,32 +91,16 @@ run_analysis() { 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 - [ ${RECORDING_LENGTH} == "60" ] && RECORDING_LENGTH=01:00 - FILE_LENGTH="$(ffmpeg -i ${1}/${i} 2>&1 | awk -F. '/Duration/ {print $1}' | cut -d':' -f3-4)" - [ -z $FILE_LENGTH ] && sleep 1 && continue echo "RECORDING_LENGTH set to ${RECORDING_LENGTH}" a=0 - if [ "${RECORDING_LENGTH}" == "01:00" ];then - until [ "$(ffmpeg -i ${1}/${i} 2>&1 | awk -F. '/Duration/ {print $1}' | cut -d':' -f3-4)" == "${RECORDING_LENGTH}" ];do - sleep 1 - [ $a -ge 60 ] && rm -f ${1}/${i} && break - a=$((a+1)) - done - elif [ "${RECORDING_LENGTH}" -lt 10 ];then - until [ "$(ffmpeg -i ${1}/${i} 2>&1 | awk -F. '/Duration/ {print $1}' | cut -d':' -f3-4)" == "00:0${RECORDING_LENGTH}" ];do - sleep 1 - [ $a -ge ${RECORDING_LENGTH} ] && rm -f ${1}/${i} && break - a=$((a+1)) - done - else - until [ "$(ffmpeg -i ${1}/${i} 2>&1 | awk -F. '/Duration/ {print $1}' | cut -d':' -f3-4)" == "00:${RECORDING_LENGTH}" ];do - sleep 1 - [ $a -ge ${RECORDING_LENGTH} ] && rm -f ${1}/${i} && break - a=$((a+1)) - done - fi + until [ -z "$(lsof -t ${1}/${i})" ];do + sleep 2 + [ $a -ge ${RECORDING_LENGTH} ] && rm -f ${1}/${i} && break + a=$((a+2)) + done if ! grep 5050 <(netstat -tulpn 2>&1) &> /dev/null 2>&1;then echo "Waiting for socket" @@ -124,183 +108,48 @@ run_analysis() { sleep 1 done fi - if [ -f ${1}/${i} ] && [ ! -f ${INCLUDE_LIST} ] && [ ! -f ${EXCLUDE_LIST} ] && [ -z $BIRDWEATHER_ID ];then - echo "analyze.py \ ---i "${1}/${i}" \ ---o "${1}/${i}.csv" \ ---lat "${LATITUDE}" \ ---lon "${LONGITUDE}" \ ---week "${WEEK}" \ ---overlap "${OVERLAP}" \ ---sensitivity "${SENSITIVITY}" \ ---min_conf "${CONFIDENCE}"" - analyze.py \ - --i "${1}/${i}" \ - --o "${1}/${i}.csv" \ - --lat "${LATITUDE}" \ - --lon "${LONGITUDE}" \ - --week "${WEEK}" \ - --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ - --min_conf "${CONFIDENCE}" - elif [ -f ${1}/${i} ] && [ -f ${INCLUDELIST} ] && [ ! -f ${EXCLUDE_LIST} ] && [ -z $BIRDWEATHER_ID ];then - echo "analyze.py \ ---i "${1}/${i}" \ ---o "${1}/${i}.csv" \ ---lat "${LATITUDE}" \ ---lon "${LONGITUDE}" \ ---week "${WEEK}" \ ---overlap "${OVERLAP}" \ ---sensitivity "${SENSITIVITY}" \ ---min_conf "${CONFIDENCE}" \ ---include_list "${INCLUDE_LIST}"" - analyze.py \ - --i "${1}/${i}" \ - --o "${1}/${i}.csv" \ - --lat "${LATITUDE}" \ - --lon "${LONGITUDE}" \ - --week "${WEEK}" \ - --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ - --min_conf "${CONFIDENCE}" \ - --include_list "${INCLUDE_LIST}" - elif [ -f ${1}/${i} ] && [ ! -f ${INCLUDE_LIST} ] && [ -f ${EXCLUDE_LIST} ] && [ -z $BIRDWEATHER_ID ];then - echo "analyze.py \ ---i "${1}/${i}" \ ---o "${1}/${i}.csv" \ ---lat "${LATITUDE}" \ ---lon "${LONGITUDE}" \ ---week "${WEEK}" \ ---overlap "${OVERLAP}" \ ---sensitivity "${SENSITIVITY}" \ ---min_conf "${CONFIDENCE}" \ ---exclude_list "${EXCLUDE_LIST}"" - analyze.py \ - --i "${1}/${i}" \ - --o "${1}/${i}.csv" \ - --lat "${LATITUDE}" \ - --lon "${LONGITUDE}" \ - --week "${WEEK}" \ - --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ - --min_conf "${CONFIDENCE}" \ - --exclude_list "${EXCLUDE_LIST}" - elif [ -f ${1}/${i} ] && [ -f ${INCLUDE_LIST} ] && [ -f ${EXCLUDE_LIST} ] && [ -z $BIRDWEATHER_ID ];then - echo "analyze.py \ ---i "${1}/${i}" \ ---o "${1}/${i}.csv" \ ---lat "${LATITUDE}" \ ---lon "${LONGITUDE}" \ ---week "${WEEK}" \ ---overlap "${OVERLAP}" \ ---sensitivity "${SENSITIVITY}" \ ---min_conf "${CONFIDENCE}" \ ---include_list "${INCLUDE_LIST}" \ ---exclude_list "${EXCLUDE_LIST}"" - analyze.py \ - --i "${1}/${i}" \ - --o "${1}/${i}.csv" \ - --lat "${LATITUDE}" \ - --lon "${LONGITUDE}" \ - --week "${WEEK}" \ - --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ - --min_conf "${CONFIDENCE}" \ - --include_list "${INCLUDE_LIST}" \ - --exclude_list "${EXCLUDE_LIST}" - elif [ -f ${1}/${i} ] && [ ! -f ${INCLUDE_LIST} ] && [ ! -f ${EXCLUDE_LIST} ] && [ ! -z $BIRDWEATHER_ID ];then - echo "analyze.py \ ---i "${1}/${i}" \ ---o "${1}/${i}.csv" \ ---lat "${LATITUDE}" \ ---lon "${LONGITUDE}" \ ---week "${WEEK}" \ ---overlap "${OVERLAP}" \ ---sensitivity "${SENSITIVITY}" \ ---min_conf "${CONFIDENCE}" \ ---birdweather_id "IN_USE"" - analyze.py \ - --i "${1}/${i}" \ - --o "${1}/${i}.csv" \ - --lat "${LATITUDE}" \ - --lon "${LONGITUDE}" \ - --week "${WEEK}" \ - --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ - --min_conf "${CONFIDENCE}" \ - --birdweather_id "${BIRDWEATHER_ID}" - elif [ -f ${1}/${i} ] && [ -f ${INCLUDE_LIST} ] && [ ! -f ${EXCLUDE_LIST} ] && [ ! -z $BIRDWEATHER_ID ];then - echo "analyze.py \ ---i "${1}/${i}" \ ---o "${1}/${i}.csv" \ ---lat "${LATITUDE}" \ ---lon "${LONGITUDE}" \ ---week "${WEEK}" \ ---overlap "${OVERLAP}" \ ---sensitivity "${SENSITIVITY}" \ ---min_conf "${CONFIDENCE}" \ ---include_list "${INCLUDE_LIST}" \ ---birdweather_id "IN_USE"" - analyze.py \ - --i "${1}/${i}" \ - --o "${1}/${i}.csv" \ - --lat "${LATITUDE}" \ - --lon "${LONGITUDE}" \ - --week "${WEEK}" \ - --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ - --min_conf "${CONFIDENCE}" \ - --include_list "${INCLUDE_LIST}" \ - --birdweather_id "${BIRDWEATHER_ID}" - elif [ -f ${1}/${i} ] && [ ! -f ${INCLUDE_LIST} ] && [ -f ${EXCLUDE_LIST} ] && [ ! -z $BIRDWEATHER_ID ];then - echo "analyze.py \ ---i "${1}/${i}" \ ---o "${1}/${i}.csv" \ ---lat "${LATITUDE}" \ ---lon "${LONGITUDE}" \ ---week "${WEEK}" \ ---overlap "${OVERLAP}" \ ---sensitivity "${SENSITIVITY}" \ ---min_conf "${CONFIDENCE}" \ ---exclude_list "${EXCLUDE_LIST}" \ ---birdweather_id "IN_USE"" - analyze.py \ - --i "${1}/${i}" \ - --o "${1}/${i}.csv" \ - --lat "${LATITUDE}" \ - --lon "${LONGITUDE}" \ - --week "${WEEK}" \ - --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ - --min_conf "${CONFIDENCE}" \ - --exclude_list "${EXCLUDE_LIST}" \ - --birdweather_id "${BIRDWEATHER_ID}" - elif [ -f ${1}/${i} ] && [ -f ${INCLUDE_LIST} ] && [ -f ${EXCLUDE_LIST} ] && [ ! -z $BIRDWEATHER_ID ];then - echo "analyze.py \ ---i "${1}/${i}" \ ---o "${1}/${i}.csv" \ ---lat "${LATITUDE}" \ ---lon "${LONGITUDE}" \ ---week "${WEEK}" \ ---overlap "${OVERLAP}" \ ---sensitivity "${SENSITIVITY}" \ ---min_conf "${CONFIDENCE}" \ ---include_list "${INCLUDE_LIST}" \ ---exclude_list "${EXCLUDE_LIST}" \ ---birdweather_id "IN_USE"" - analyze.py \ - --i "${1}/${i}" \ - --o "${1}/${i}.csv" \ - --lat "${LATITUDE}" \ - --lon "${LONGITUDE}" \ - --week "${WEEK}" \ - --overlap "${OVERLAP}" \ - --sensitivity "${SENSITIVITY}" \ - --min_conf "${CONFIDENCE}" \ - --include_list "${INCLUDE_LIST}" \ - --exclude_list "${EXCLUDE_LIST}" \ - --birdweather_id "${BIRDWEATHER_ID}" + # prepare optional parameters for analyse.py + if [ -f ${INCLUDE_LIST} ]; then + INCLUDEPARAM="--include_list \"${INCLUDE_LIST}\"" + else + INCLUDEPARAM="" fi + if [ -f ${EXCLUDE_LIST} ]; then + EXCLUDEPARAM="--include_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 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_LOG} + 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} done } @@ -318,13 +167,16 @@ until grep 5050 <(netstat -tulpn 2>&1) &> /dev/null 2>&1;do done if [ $(find ${RECS_DIR} -maxdepth 1 -name '*wav' | wc -l) -gt 0 ];then + find $RECS_DIR -maxdepth 1 -name '*wav' -type f -size 0 -delete run_birdnet "${RECS_DIR}" 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' | wc -l) -gt 0 ];then + find $TODAY -name '*wav' -type f -size 0 -delete run_birdnet "${TODAY}" fi diff --git a/scripts/birdnet_recording.sh b/scripts/birdnet_recording.sh index 0798511..8a3063b 100755 --- a/scripts/birdnet_recording.sh +++ b/scripts/birdnet_recording.sh @@ -2,29 +2,29 @@ set -x source /etc/birdnet/birdnet.conf -if [ "${TIMESTAMP_FORMAT}" == "12" ];then - STAMP="%I:%M:%S%P" -else - STAMP="%H:%M:%S" -fi - [ -z $RECORDING_LENGTH ] && RECORDING_LENGTH=15 -if ! pulseaudio --check;then pulseaudio --start;fi - -if pgrep arecord &> /dev/null ;then - echo "Recording" -else - until grep 5050 <(netstat -tulpn 2>&1);do - sleep 1 +if [ ! -z $RTSP_STREAM ];then + while true;do + for i in ${RTSP_STREAM//,/ };do + ffmpeg -i ${i} -t ${RECORDING_LENGTH} -vn -acodec pcm_s16le -ac 2 -ar 48000 file:${RECS_DIR}/$(date "+%F")-birdnet-$(date "+%H:%M:%S").wav + done done - if [ -z ${REC_CARD} ];then - - arecord -f S16_LE -c${CHANNELS} -r48000 -t wav --max-file-time ${RECORDING_LENGTH}\ - --use-strftime ${RECS_DIR}/%B-%Y/%d-%A/%F-birdnet-${STAMP}.wav +else + if ! pulseaudio --check;then pulseaudio --start;fi + if pgrep arecord &> /dev/null ;then + echo "Recording" else - arecord -f S16_LE -c${CHANNELS} -r48000 -t wav --max-file-time ${RECORDING_LENGTH}\ - -D "${REC_CARD}" --use-strftime \ - ${RECS_DIR}/%B-%Y/%d-%A/%F-birdnet-${STAMP}.wav + until grep 5050 <(netstat -tulpn 2>&1);do + sleep 1 + done + if [ -z ${REC_CARD} ];then + arecord -f S16_LE -c${CHANNELS} -r48000 -t wav --max-file-time ${RECORDING_LENGTH}\ + --use-strftime ${RECS_DIR}/%B-%Y/%d-%A/%F-birdnet-%H:%M:%S.wav + else + arecord -f S16_LE -c${CHANNELS} -r48000 -t wav --max-file-time ${RECORDING_LENGTH}\ + -D "${REC_CARD}" --use-strftime \ + ${RECS_DIR}/%B-%Y/%d-%A/%F-birdnet-%H:%M:%S.wav + fi fi fi diff --git a/scripts/extract_new_birdsounds.sh b/scripts/extract_new_birdsounds.sh index cee339a..1d94c70 100755 --- a/scripts/extract_new_birdsounds.sh +++ b/scripts/extract_new_birdsounds.sh @@ -13,9 +13,9 @@ source /etc/birdnet/birdnet.conf # Set Variables TMPFILE=$(mktemp) -ANALYZED=${RECS_DIR}/*/*Analyzed -# SCAN_DIRS are all directories marked "Analyzed" -SCAN_DIRS=($(find ${ANALYZED} -type d 2>/dev/null | sort )) +#ANALYZED=${RECS_DIR}/*/*Analyzed +#SCAN_DIRS are all directories marked "Analyzed" +SCAN_DIRS=($(find $HOME -type d -name '*Analyzed' 2>/dev/null | sort )) for h in "${SCAN_DIRS[@]}";do # The TMPFILE is created from each .csv file BirdNET creates @@ -26,25 +26,18 @@ for h in "${SCAN_DIRS[@]}";do # Field 4: Common name # Field 5: Confidence - # Removes old directories - #if echo "${h}" | grep $(date --date="yesterday" "+%A") &> /dev/null;then - # echo "Removing old directories" - # rm -drf "${h}" - # rm -drf "$(echo ${h} | cut -d'-' -f1-3)" - # continue - #fi - # 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 "${i}" | cut -d'/' -f7 >> ${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 @@ -142,7 +135,14 @@ for h in "${SCAN_DIRS[@]}";do # next extraction. [[ -d "${PROCESSED}" ]] || mkdir "${PROCESSED}" #echo "Moving processed files to ${PROCESSED}" - mv ${h}/* ${PROCESSED} &> /dev/null || continue + 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" diff --git a/scripts/install_config.sh b/scripts/install_config.sh index 094a3b4..0ad72eb 100755 --- a/scripts/install_config.sh +++ b/scripts/install_config.sh @@ -19,8 +19,8 @@ install_config() { ## 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=$(curl -s4 ifconfig.co/json | jq .latitude) +LONGITUDE=$(curl -s4 ifconfig.co/json | jq .longitude) #--------------------- BirdWeather Station Information -----------------------# #_____________The variable below can be set to have your BirdNET-Pi____________# @@ -61,6 +61,13 @@ ICE_PWD=birdnetpi BIRDNETPI_URL= +#---------------------------- RTSP Stream URL -------------------------------# + +## If RTSP_STREAM is set, the system will use the RTSP stream as its audio +## source instead of recording its own audio. If this variable is kept empty, +## BirdNET-Pi will default to recording its own audio. + +RTSP_STREAM= #------------------- Mobile Notifications via Pushed.co ---------------------# #____________The two variables below enable mobile notifications_______________# diff --git a/scripts/install_services.sh b/scripts/install_services.sh index 7b486ae..b19e399 100755 --- a/scripts/install_services.sh +++ b/scripts/install_services.sh @@ -18,7 +18,7 @@ install_depends() { apt install -qqy caddy ftpd sqlite3 php-sqlite3 alsa-utils \ pulseaudio avahi-utils sox libsox-fmt-mp3 php php-fpm php-curl php-xml \ php-zip icecast2 swig ffmpeg wget unzip curl cmake make bc libjpeg-dev \ - zlib1g-dev python3-dev python3-pip python3-venv + zlib1g-dev python3-dev python3-pip python3-venv lsof } diff --git a/scripts/livestream.sh b/scripts/livestream.sh index bdebba7..d081d53 100755 --- a/scripts/livestream.sh +++ b/scripts/livestream.sh @@ -4,8 +4,12 @@ source /etc/birdnet/birdnet.conf if [ -z ${REC_CARD} ];then echo "Stream not supported" -else - ffmpeg -loglevel 52 -ac ${CHANNELS} -f alsa -i ${REC_CARD} -acodec libmp3lame \ +elif [[ ! -z ${RTSP_STREAM} ]];then + ffmpeg -loglevel 52 -ac ${CHANNELS} -i ${RTSP_STREAM} -acodec libmp3lame \ -b:a 320k -ac ${CHANNELS} -content_type 'audio/mpeg' \ -f mp3 icecast://source:${ICE_PWD}@localhost:8000/stream -re -fi +else + ffmpeg -loglevel 52 -ac ${CHANNELS} -f alsa -i ${REC_CARD} -acodec libmp3lame \ + -b:a 320k -ac ${CHANNELS} -content_type 'audio/mpeg' \ + -f mp3 icecast://source:${ICE_PWD}@localhost:8000/stream -re +fi \ No newline at end of file diff --git a/scripts/most_recent.sh b/scripts/most_recent.sh new file mode 100755 index 0000000..cf10249 --- /dev/null +++ b/scripts/most_recent.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +OLDIFS=$IFS +IFS=\| +most_recent_results=($(sqlite3 ~/BirdNET-Pi/scripts/birds.db \ + 'SELECT Com_Name, Time, Date FROM detections + ORDER BY Date DESC, Time DESC + LIMIT 1')) +today=$(date +%F) +yesterday=$(date --date="yesterday" +%F) +two_days_ago=$(date --date="2 days ago" +%F) + +echo -n The most recent detection was + +if [[ "${most_recent_results[0]}" =~ ^[AEIOU].* ]];then + echo -n " an ${most_recent_results[0]} at " +else + echo -n " a ${most_recent_results[0]} at " +fi +most_recent_results[1]=$(date --date="${most_recent_results[1]}" +%l:%M%p) +echo -n ${most_recent_results[1]} +if [[ ${most_recent_results[2]} == $today ]];then + echo " today." +elif [[ ${most_recent_results[2]} == $yesterday ]];then + echo " yesterday." +elif [[ ${most_recent_results[2]} == $two_days_ago ]];then + echo " two days ago." +else + echo " on ${most_recent_results[2]}." +fi + +IFS=$OLDIFS diff --git a/scripts/overview.php b/scripts/overview.php index 8328913..cba8a9f 100644 --- a/scripts/overview.php +++ b/scripts/overview.php @@ -10,65 +10,66 @@ if($db == False) { if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isset($_GET['previous_detection_identifier'])) { - $statement4 = $db->prepare('SELECT Com_Name, Sci_Name, Date, Time, Confidence, File_Name FROM detections ORDER BY Date DESC, Time DESC LIMIT 1'); + $statement4 = $db->prepare('SELECT Com_Name, Sci_Name, Date, Time, Confidence, File_Name FROM detections ORDER BY Date DESC, Time DESC LIMIT 5'); if($statement4 == False) { echo "Database is busy"; header("refresh: 0;"); } $result4 = $statement4->execute(); - $mostrecent = $result4->fetchArray(SQLITE3_ASSOC); - $comname = preg_replace('/ /', '_', $mostrecent['Com_Name']); - $sciname = preg_replace('/ /', '_', $mostrecent['Sci_Name']); - $comname = preg_replace('/\'/', '', $comname); - $filename = "/By_Date/".$mostrecent['Date']."/".$comname."/".$mostrecent['File_Name']; + // hopefully one of the 5 most recent detections has an image that is valid, we'll use that one as the most recent detection until the newer ones get their images created + while($mostrecent = $result4->fetchArray(SQLITE3_ASSOC)) { + $comname = preg_replace('/ /', '_', $mostrecent['Com_Name']); + $sciname = preg_replace('/ /', '_', $mostrecent['Sci_Name']); + $comname = preg_replace('/\'/', '', $comname); + $filename = "/By_Date/".$mostrecent['Date']."/".$comname."/".$mostrecent['File_Name']; - - if($_GET['previous_detection_identifier'] != $filename || $_GET['previous_detection_identifier'] == "undefined") { - // check to make sure the image actually exists, sometimes it takes a minute to be created - if (isset($_SERVER['HTTPS']) && - ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1) || - isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && - $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') { - $protocol = 'https://'; - } - else { - $protocol = 'http://'; - } - $headers = @get_headers($protocol.$_SERVER['HTTP_HOST'].$filename.".png"); - if(strpos($headers[0],'200')) { - ?> - + +

Most Recent Detection:

+ + + + +
+
+ +
+ +
Confidence:
+
- -

Most Recent Detection:

- - - - -
-
- -
- -
Confidence:
-
0) { + if(this.responseText.length > 0 && !this.responseText.includes("Database is busy.")) { document.getElementById("most_recent_detection").innerHTML = this.responseText; // only going to load left chart if there's a new detection diff --git a/scripts/todays_detections.php b/scripts/todays_detections.php index 2f25575..1b4db75 100644 --- a/scripts/todays_detections.php +++ b/scripts/todays_detections.php @@ -58,11 +58,16 @@ $result6 = $statement6->execute(); $totalspeciestally = $result6->fetchArray(SQLITE3_ASSOC); if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { + if(isset($_GET['searchterm'])) { + $searchquery = "AND (Com_name LIKE '%".$_GET['searchterm']."%' OR Sci_name LIKE '%".$_GET['searchterm']."%' OR Confidence LIKE '%".$_GET['searchterm']."%' OR File_Name LIKE '%".$_GET['searchterm']."%' OR Time LIKE '%".$_GET['searchterm']."%')"; + } else { + $searchquery = ""; + } if(isset($_GET['display_limit']) && is_numeric($_GET['display_limit'])){ - $statement0 = $db->prepare('SELECT Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') ORDER BY Time DESC LIMIT '.(intval($_GET['display_limit'])-40).',40'); + $statement0 = $db->prepare('SELECT Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') '.$searchquery.' ORDER BY Time DESC LIMIT '.(intval($_GET['display_limit'])-40).',40'); } else { // legacy mode - $statement0 = $db->prepare('SELECT Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') ORDER BY Time DESC '); + $statement0 = $db->prepare('SELECT Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') '.$searchquery.' ORDER BY Time DESC'.$searchquery); } if($statement0 == False){ echo "Database is busy"; @@ -84,7 +89,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { ?> -
+


Confidence:
@@ -107,7 +112,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { // don't show the button if there's no more detections to be displayed, we're at the end of the list if($iterations >= 40 && isset($_GET['display_limit']) && is_numeric($_GET['display_limit'])) { ?>
- +
-

Today's Detections

+

Today's Detections —

- +