diff --git a/README.md b/README.md index 6680ba2..e513baa 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Currently listening in these countries . . . that I know of . . . - Italy - Finland - Australia +- Canada ## Features * 24/7 recording and BirdNET-Lite analysis diff --git a/birdnet.conf-defaults b/birdnet.conf-defaults index 5863aab..f21fcad 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/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 a2e755f..de0b5c6 100755 --- a/scripts/birdnet_analysis.sh +++ b/scripts/birdnet_analysis.sh @@ -167,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 947802b..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 @@ -30,13 +30,14 @@ for h in "${SCAN_DIRS[@]}";do 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 diff --git a/scripts/install_config.sh b/scripts/install_config.sh index e6ed905..351c3a2 100755 --- a/scripts/install_config.sh +++ b/scripts/install_config.sh @@ -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/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/overview.php b/scripts/overview.php index 8328913..d45d588 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:
-
-
+


Confidence:
@@ -112,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 —

- + @@ -169,7 +169,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { var timer = ''; searchterm = ""; -document.getElementById("searchterm").oninput = (function(e) { +document.getElementById("searchterm").onkeydown = (function(e) { if (e.key === "Enter") { clearTimeout(timer); searchDetections(document.getElementById("searchterm").value); @@ -178,7 +178,11 @@ document.getElementById("searchterm").oninput = (function(e) { clearTimeout(timer); timer = setTimeout(function() { searchDetections(document.getElementById("searchterm").value); - document.getElementById("searchterm").blur(); + + setTimeout(function() { + // search auto submitted and now the user is probably scrolling, get the keyboard out of the way & prevent browser from jumping to the top when a video is played + document.getElementById("searchterm").blur(); + }, 2000); }, 1000); } }); @@ -194,7 +198,7 @@ function switchViews(element) { } } function searchDetections(searchvalue) { - document.getElementById("detections_table").innerHTML = ""; + document.getElementById("detections_table").innerHTML = "

Loading...

"; searchterm = searchvalue; loadDetections(40,undefined); } diff --git a/scripts/update_birdnet.sh b/scripts/update_birdnet.sh index 254efd1..0135963 100755 --- a/scripts/update_birdnet.sh +++ b/scripts/update_birdnet.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Update BirdNET-Pi +# Update BirdNET-Pi's Git Repo source /etc/birdnet/birdnet.conf trap 'exit 1' SIGINT SIGHUP USER=$(awk -F: '/1000/ {print $1}' /etc/passwd) @@ -11,4 +11,7 @@ sudo -u${USER} git -C $my_dir pull -f sudo systemctl daemon-reload sudo -u${USER} git -C $my_dir stash pop sudo ln -sf $my_dir/* /usr/local/bin/ -sudo $my_dir/update_birdnet_snippets.sh + +# The script below handles changes to the host system +# Any additions to the updater should be placed in that file. +sudo $my_dir/update_birdnet_snippets.sh \ No newline at end of file