diff --git a/README.md b/README.md index df8e235..f23451c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Icon made by Freepik from

## Introduction -BirdNET-Pi is built on the [TFLite version of BirdNET](https://github.com/kahst/BirdNET-Lite) by [**@kahst**](https://github.com/kahst) using [pre-built TFLite binaries](https://github.com/PINTO0309/TensorflowLite-bin) by [**@PINTO0309**](https://github.com/PINTO0309) . It is able to recognize bird sounds from a USB sound card in realtime and share its data with the rest of the world. +BirdNET-Pi is built on the [TFLite version of BirdNET](https://github.com/kahst/BirdNET-Lite) by [**@kahst**](https://github.com/kahst) using [pre-built TFLite binaries](https://github.com/PINTO0309/TensorflowLite-bin) by [**@PINTO0309**](https://github.com/PINTO0309) . It is able to recognize bird sounds from a USB microphone or sound card in realtime and share its data with the rest of the world. Check out birds from around the world - [BirdWeather](https://app.birdweather.com)
diff --git a/birdnet.conf-defaults b/birdnet.conf-defaults index e75cda1..e53af20 100644 --- a/birdnet.conf-defaults +++ b/birdnet.conf-defaults @@ -60,7 +60,8 @@ RTSP_STREAM= APPRISE_NOTIFICATION_TITLE="New BirdNET-Pi Detection" APPRISE_NOTIFICATION_BODY="A \$sciname \$comname was just detected with a confidence of \$confidence" -APPRISE_NOTIFY_EACH_DETECTION=false +APPRISE_NOTIFY_EACH_DETECTION=0 +APPRISE_NOTIFY_NEW_SPECIES=0 #---------------------- Flickr Images API Configuration -----------------------# diff --git a/homepage/images/bnp.png b/homepage/images/bnp.png new file mode 100644 index 0000000..db5334b Binary files /dev/null and b/homepage/images/bnp.png differ diff --git a/homepage/index.php b/homepage/index.php index 03f8c34..536f673 100644 --- a/homepage/index.php +++ b/homepage/index.php @@ -5,7 +5,7 @@ body::-webkit-scrollbar { display:none } - + "; } else { header('WWW-Authenticate: Basic realm="My Realm"'); @@ -46,12 +46,12 @@ if(isset($_GET['stream'])){ } } } else { - echo " + echo "
-

BirdNET-Pi

+

"; } if(isset($_GET['filename'])) { diff --git a/homepage/style.css b/homepage/style.css index b43b2c3..3319185 100644 --- a/homepage/style.css +++ b/homepage/style.css @@ -70,11 +70,16 @@ label { font-weight: bold; } +hr { + border-color:black; +} + button { background-color: transparent; border: none; color: black; cursor: pointer; + transition:background-color 0.2s; } button:hover { @@ -138,10 +143,15 @@ button:hover { margin-left: auto; margin-right: auto; margin-bottom: 15px; - box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10); !important; + box-shadow: 0px 0px 28px 1px rgba(0, 0, 0, 0.10); !important; border-radius: 4px; } +.topimage { + width:175px; + display:initial !important; +} + .topnav form { margin: 0; padding: 0; @@ -181,7 +191,7 @@ button:hover { } .stats table { - width: 75%; + width: 60%; } .overview th { @@ -247,11 +257,14 @@ button:hover { .views .centered button { background-color: rgb(219, 255, 235); padding: 12px; + transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); + box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%); } .views .centered button:hover { background-color: rgb(159, 226, 155); color: black; + box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%); } .settings { @@ -546,7 +559,7 @@ button:hover { font-size:x-large; background:#dbffeb; padding:5px; - + box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.10); } button.legacyview { color:gray; @@ -557,15 +570,31 @@ button.legacyview { font-size:small; background:#dbffeb; padding:5px; - + transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); + box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%); } +button.legacyview:hover { + box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%); +} + button.loadmore { margin-top:10px; font-size:x-large; background:#dbffeb; padding:10px; + transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); + box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%); } +button.loadmore:hover { + box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%); +} +#searchterm { + transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); +} +#searchterm:hover { + box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%); +} tr { background-color:#9fe29b; @@ -581,6 +610,11 @@ tr { .centered form#views button { box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.10); margin:2px; + transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); + box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%); +} +.centered form#views button:hover { + box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%); } input { box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10); @@ -605,6 +639,8 @@ dialog::backdrop { margin-bottom:3px; } .centered_image_container img.img1 { + transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); + box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%); cursor:pointer; height:95%; position:absolute; @@ -613,9 +649,19 @@ dialog::backdrop { border-radius: 5px; width:unset; } +.centered_image_container img.img1:hover{ + opacity:0.8; + box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%); +} .centered_image_container * { font-size:19px !important; } .centered_image_container form { margin-bottom:0px; +} +.brbanner { + padding:15px; + background-color:rgb(159, 226, 155); + text-align:center; + font-size:large; } \ No newline at end of file diff --git a/homepage/views.php b/homepage/views.php index e190569..33f4621 100644 --- a/homepage/views.php +++ b/homepage/views.php @@ -37,27 +37,27 @@ body::-webkit-scrollbar {
@@ -70,7 +70,7 @@ if(isset($_GET['view'])){ if($_GET['view'] == "View Log"){echo "";} if($_GET['view'] == "Overview"){include('overview.php');} if($_GET['view'] == "Today's Detections"){include('todays_detections.php');} - if($_GET['view'] == "Species Stats"){echo "

";include('stats.php');} + if($_GET['view'] == "Species Stats"){include('stats.php');} if($_GET['view'] == "Streamlit"){echo "";} if($_GET['view'] == "Daily Charts"){include('history.php');} if($_GET['view'] == "Tools"){ @@ -91,18 +91,18 @@ if(isset($_GET['view'])){ if($submittedpwd == $caddypwd && $submitteduser == 'birdnet'){ $url = $_SERVER['SERVER_NAME']."/scripts/adminer.php"; echo "
-
- - - - - - - - - -
-
"; +
+ + + + + + + + + +
+
"; } else { header('WWW-Authenticate: Basic realm="My Realm"'); header('HTTP/1.0 401 Unauthorized'); @@ -196,36 +196,82 @@ if(isset($_GET['view'])){ } } } elseif(isset($_GET['submit'])) { - 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)){ + 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']; + $allowedCommands = array('sudo systemctl stop livestream.service && sudo /etc/init.d/icecast2 stop', + 'sudo systemctl restart livestream.service && sudo /etc/init.d/icecast2 restart', + 'sudo systemctl disable --now livestream.service && sudo systemctl disable icecast2 && sudo /etc/init.d/icecast2 stop', + 'sudo systemctl enable icecast2 && sudo /etc/init.d/icecast2 start && sudo systemctl enable --now livestream.service', + 'sudo systemctl stop web_terminal.service', + 'sudo systemctl restart web_terminal.service', + 'sudo systemctl disable --now web_terminal.service', + 'sudo systemctl enable --now web_terminal.service', + 'sudo systemctl stop birdnet_log.service', + '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', + 'sudo systemctl enable --now birdnet_analysis.service', + 'sudo systemctl stop birdnet_stats.service', + 'sudo systemctl restart birdnet_stats.service', + 'sudo systemctl disable --now birdnet_stats.service', + 'sudo systemctl enable --now birdnet_stats.service', + 'sudo systemctl stop birdnet_recording.service', + 'sudo systemctl restart birdnet_recording.service', + 'sudo systemctl disable --now birdnet_recording.service', + 'sudo systemctl enable --now birdnet_recording.service', + 'sudo systemctl stop chart_viewer.service', + 'sudo systemctl restart chart_viewer.service', + 'sudo systemctl disable --now chart_viewer.service', + 'sudo systemctl enable --now chart_viewer.service', + 'sudo systemctl stop spectrogram_viewer.service', + 'sudo systemctl restart spectrogram_viewer.service', + 'sudo systemctl disable --now spectrogram_viewer.service', + 'sudo systemctl enable --now spectrogram_viewer.service', + 'stop_core_services.sh', + 'restart_services.sh', + 'sudo reboot', + 'update_birdnet.sh', + 'sudo shutdown now', + 'sudo clear_all_data.sh'); + $command = $_GET['submit']; + if($submittedpwd == $caddypwd && $submitteduser == 'birdnet' && in_array($command,$allowedCommands)){ + if(isset($command)){ $initcommand = $command; - if (strpos($command, "systemctl") !== false) { - $tmp = explode(" ",trim($command)); - $command .= "& sleep 3;sudo systemctl status ".end($tmp); - } - $results = shell_exec("$command 2>&1"); - $results = str_replace("FAILURE", "FAILURE", $results); - $results = str_replace("failed", "failed",$results); - $results = str_replace("active (running)", "active (running)",$results); - if(strlen($results) == 0) { + if (strpos($command, "systemctl") !== false) { + $tmp = explode(" ",trim($command)); + $command .= "& sleep 3;sudo systemctl status ".end($tmp); + } + $results = shell_exec("$command 2>&1"); + $results = str_replace("FAILURE", "FAILURE", $results); + $results = str_replace("failed", "failed",$results); + $results = str_replace("active (running)", "active (running)",$results); + if(strlen($results) == 0) { $results = "This command has no output."; - } - echo "
Output of command:`".$initcommand."`
$results
"; + } + echo "
Output of command:`".$initcommand."`
$results
"; } else { header('WWW-Authenticate: Basic realm="My Realm"'); header('HTTP/1.0 401 Unauthorized'); diff --git a/scripts/birdnet_analysis.sh b/scripts/birdnet_analysis.sh index 94ea8c5..51d2d0f 100755 --- a/scripts/birdnet_analysis.sh +++ b/scripts/birdnet_analysis.sh @@ -20,11 +20,13 @@ make_thisrun() { make_thisrun &> /dev/null if ! diff ${LAST_RUN} ${THIS_RUN};then echo "The birdnet.conf file has changed" - echo "Reloading services" + 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} - sudo systemctl stop birdnet_recording.service - sudo rm -rf ${RECS_DIR}/$(date +%B-%Y/%d-%A)/* - sudo systemctl start birdnet_recording.service fi INCLUDE_LIST="$HOME/BirdNET-Pi/include_species_list.txt" diff --git a/scripts/config.php b/scripts/config.php index 4fecbba..8b13084 100644 --- a/scripts/config.php +++ b/scripts/config.php @@ -16,10 +16,10 @@ if(isset($_GET['apprise_notify_each_detection'])) { } else { $apprise_notify_each_detection = 0; } -if(isset($_GET['apprise_notify_each_species'])) { - exec('sudo systemctl start pushed_notifications.service'); +if(isset($_GET['apprise_notify_new_species'])) { + $apprise_notify_new_species = 1; } else { - exec('sudo systemctl stop pushed_notifications.service'); + $apprise_notify_new_species = 0; } // logic for setting the date and time based on user inputs from the form below @@ -46,6 +46,7 @@ $contents = preg_replace("/BIRDWEATHER_ID=.*/", "BIRDWEATHER_ID=$birdweather_id" $contents = preg_replace("/APPRISE_NOTIFICATION_TITLE=.*/", "APPRISE_NOTIFICATION_TITLE=\"$apprise_notification_title\"", $contents); $contents = preg_replace("/APPRISE_NOTIFICATION_BODY=.*/", "APPRISE_NOTIFICATION_BODY=\"$apprise_notification_body\"", $contents); $contents = preg_replace("/APPRISE_NOTIFY_EACH_DETECTION=.*/", "APPRISE_NOTIFY_EACH_DETECTION=$apprise_notify_each_detection", $contents); +$contents = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES=.*/", "APPRISE_NOTIFY_NEW_SPECIES=$apprise_notify_new_species", $contents); $contents = preg_replace("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents); @@ -56,6 +57,7 @@ $contents2 = preg_replace("/BIRDWEATHER_ID=.*/", "BIRDWEATHER_ID=$birdweather_id $contents2 = preg_replace("/APPRISE_NOTIFICATION_TITLE=.*/", "APPRISE_NOTIFICATION_TITLE=\"$apprise_notification_title\"", $contents2); $contents2 = preg_replace("/APPRISE_NOTIFICATION_BODY=.*/", "APPRISE_NOTIFICATION_BODY=\"$apprise_notification_body\"", $contents2); $contents2 = preg_replace("/APPRISE_NOTIFY_EACH_DETECTION=.*/", "APPRISE_NOTIFY_EACH_DETECTION=$apprise_notify_each_detection", $contents2); +$contents2 = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES=.*/", "APPRISE_NOTIFY_NEW_SPECIES=$apprise_notify_new_species", $contents2); $contents2 = preg_replace("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents2); @@ -80,7 +82,7 @@ if ($language != "none"){ $home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd"); $home = trim($home); $command = "sudo -u".$user." mv ".$home."/BirdNET-Pi/model/labels.txt ".$home."/BirdNET-Pi/model/labels.txt.old && sudo -u".$user." unzip ".$home."/BirdNET-Pi/model/labels_l18n.zip ".$language." -d ".$home."/BirdNET-Pi/model && sudo -u".$user." mv ".$home."/BirdNET-Pi/model/".$language." ".$home."/BirdNET-Pi/model/labels.txt"; - $command_output = `sudo $command`; + $command_output = `$command`; `sudo restart_services.sh`; } } @@ -144,8 +146,8 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}

- > -
+ > +
>

Bird Photos from Flickr

diff --git a/scripts/daily_plot.py b/scripts/daily_plot.py index 2d80dc3..a20e8d2 100755 --- a/scripts/daily_plot.py +++ b/scripts/daily_plot.py @@ -32,7 +32,7 @@ df['Hour of Day'] = [r.hour for r in df.Time] df_plt=df #Default to use the whole Dbase # Add every font at the specified location -font_dir = ['$HOME/BirdNET-Pi/homepage/static'] +font_dir = [userDir+'/BirdNET-Pi/homepage/static'] for font in font_manager.findSystemFonts(font_dir): font_manager.fontManager.addfont(font) diff --git a/scripts/install_config.sh b/scripts/install_config.sh index c526cda..daab688 100755 --- a/scripts/install_config.sh +++ b/scripts/install_config.sh @@ -73,7 +73,8 @@ RTSP_STREAM= APPRISE_NOTIFICATION_TITLE="New BirdNET-Pi Detection" APPRISE_NOTIFICATION_BODY="A \$sciname \$comname was just detected with a confidence of \$confidence" -APPRISE_NOTIFY_EACH_DETECTION=false +APPRISE_NOTIFY_EACH_DETECTION=0 +APPRISE_NOTIFY_NEW_SPECIES=0 #---------------------- Flickr Images API Configuration -----------------------# diff --git a/scripts/install_services.sh b/scripts/install_services.sh index a63febe..77e42e5 100755 --- a/scripts/install_services.sh +++ b/scripts/install_services.sh @@ -13,7 +13,7 @@ export PYTHON_VIRTUAL_ENV="$HOME/BirdNET-Pi/birdnet/bin/python3" install_depends() { apt install -y debian-keyring debian-archive-keyring apt-transport-https - curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo tee /etc/apt/trusted.gpg.d/caddy-stable.asc + curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list apt -qqq update && apt -qqy upgrade echo "icecast2 icecast2/icecast-setup boolean false" | debconf-set-selections @@ -93,22 +93,6 @@ EOF systemctl enable extraction.service } -install_pushed_notifications() { - cat << EOF > $HOME/BirdNET-Pi/templates/pushed_notifications.service -[Unit] -Description=BirdNET-Pi Pushed.co Notifications -[Service] -Restart=on-success -RestartSec=3 -Type=simple -User=$USER -ExecStart=/usr/local/bin/species_notifier.sh -[Install] -WantedBy=multi-user.target -EOF - ln -sf $HOME/BirdNET-Pi/templates/pushed_notifications.service /usr/lib/systemd/system -} - create_necessary_dirs() { echo "Creating necessary directories" [ -d ${EXTRACTED} ] || sudo -u ${USER} mkdir -p ${EXTRACTED} @@ -438,7 +422,6 @@ install_services() { install_recording_service install_custom_recording_service # But does not enable install_extraction_service - install_pushed_notifications install_spectrogram_service install_chart_viewer_service install_gotty_logs diff --git a/scripts/overview.php b/scripts/overview.php index a5a3bdc..39be33b 100644 --- a/scripts/overview.php +++ b/scripts/overview.php @@ -1,4 +1,7 @@ execute(); - $images = []; + if(!isset($_SESSION['images'])) { + $_SESSION['images'] = []; + } $iterations = 0; // 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)) { @@ -45,19 +50,20 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse // we've found our valid detection! ignore everything else from the database loop if(strpos($headers[0],'200')) { if($_GET['previous_detection_identifier'] == $filename) { die(); } + if($_GET['only_name'] == "true") { echo $comname.",".$filename;die(); } if (!empty($config["FLICKR_API_KEY"])) { // if we already searched flickr for this species before, use the previous image rather than doing an unneccesary api call - $key = array_search($comname, array_column($images, 0)); + $key = array_search($comname, array_column($_SESSION['images'], 0)); if($key !== false) { - $image = $images[$key]; + $image = $_SESSION['images'][$key]; } else { $flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=".str_replace("_", "%20", $comname)."&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=5&orientation=square,portrait&format=json&media=photos&nojsoncallback=1"), true)["photos"]["photo"][0]; $modaltext = "https://flickr.com/photos/".$flickrjson["owner"]."/".$flickrjson["id"]; $authorlink = "https://flickr.com/people/".$flickrjson["owner"]; - $imageurl = 'http://farm' .$flickrjson["farm"]. '.static.flickr.com/' .$flickrjson["server"]. '/' .$flickrjson["id"]. '_' .$flickrjson["secret"]. '.jpg'; - array_push($images, array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink)); - $image = $images[count($images)-1]; + $imageurl = 'https://farm' .$flickrjson["farm"]. '.static.flickr.com/' .$flickrjson["server"]. '/' .$flickrjson["id"]. '_' .$flickrjson["secret"]. '.jpg'; + array_push($_SESSION['images'], array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink)); + $image = $_SESSION['images'][count($_SESSION['images'])-1]; } } @@ -238,7 +244,7 @@ function loadDetectionIfNewExists(previous_detection_identifier=undefined) { const xhttp = new XMLHttpRequest(); xhttp.onload = function() { // if there's a new detection that needs to be updated to the page - if(this.responseText.length > 0 && !this.responseText.includes("Database is busy.")) { + 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/play.php b/scripts/play.php index 9789c3d..cf7e30f 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -1,7 +1,5 @@ @@ -230,10 +228,10 @@ if(isset($_GET['species'])){ ?> diff --git a/scripts/plotly_streamlit.py b/scripts/plotly_streamlit.py index da8d771..2d7eb6a 100755 --- a/scripts/plotly_streamlit.py +++ b/scripts/plotly_streamlit.py @@ -8,6 +8,7 @@ from datetime import timedelta, datetime from pathlib import Path import sqlite3 from sqlite3 import Connection +import plotly.express as px userDir = os.path.expanduser('~') URI_SQLITE_DB = userDir + '/BirdNET-Pi/scripts/birds.db' @@ -122,42 +123,43 @@ filt=df2['Com_Name']==specie df_counts=sum(df5==specie) -fig = make_subplots( + + + +if resample_time != '1D': + fig = make_subplots( rows=3, cols =2, specs= [[{"type":"xy","rowspan":3}, {"type":"polar","rowspan":2}], [{"rowspan":1}, {"rowspan":1} ], [None, {"type":"xy","rowspan":1}]], - subplot_titles=('Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'', + subplot_titles=('Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+' for '+str(resample_sel)+' sampling interval.'+'', 'Total Detect:'+str('{:,}'.format(df_counts))+ ' Confidence Max:'+str('{:.2f}%'.format(max(df2[df2['Com_Name']==specie]['Confidence'])*100))+ ' '+' Median:'+str('{:.2f}%'.format(np.median(df2[df2['Com_Name']==specie]['Confidence'])*100)) ) - ) -fig.layout.annotations[1].update(x=0.7,y=0.25, font_size=15) + ) + fig.layout.annotations[1].update(x=0.7,y=0.25, font_size=15) -#Plot seen species for selected date range and number of species -fig.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), row=1,col=1) + #Plot seen species for selected date range and number of species + fig.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), row=1,col=1) -fig.update_layout( - margin=dict(l=0, r=0, t=50, b=0), - yaxis={'categoryorder':'total ascending'}) + fig.update_layout( + margin=dict(l=0, r=0, t=50, b=0), + yaxis={'categoryorder':'total ascending'}) -# Set 360 degrees, 24 hours for polar plot -theta = np.linspace(0.0, 360, 24, endpoint=False) + # Set 360 degrees, 24 hours for polar plot + theta = np.linspace(0.0, 360, 24, endpoint=False) -specie_filt= df5==specie -df3=df5[specie_filt] + specie_filt= df5==specie + df3=df5[specie_filt] -detections2= pd.crosstab(df3, df3.index.hour) + detections2= pd.crosstab(df3, df3.index.hour) -d=pd.DataFrame(np.zeros((23,1))).squeeze() -detections = hourly.loc[specie] -detections=(d+detections).fillna(0) - - -if resample_time != '1D': + d=pd.DataFrame(np.zeros((23,1))).squeeze() + detections = hourly.loc[specie] + detections=(d+detections).fillna(0) fig.add_trace(go.Barpolar(r = detections, theta=theta), row=1, col=2) fig.update_layout( autosize=False, @@ -191,15 +193,18 @@ if resample_time != '1D': else: fig = make_subplots( rows=1, cols =2, -# specs= [[{"type":"xy","rowspan":1}], -# [{"rowspan":1}], -# ], -# subplot_titles=('Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'', + specs= [[{"type":"xy","rowspan":1},{"type":"xy","rowspan":1}]], + + + subplot_titles=('Daily Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'', + 'Daily ' + specie+ ' Detections on 15 minute intervals '), # 'Total Detect:'+str('{:,}'.format(df_counts))+ # ' Confidence Max:'+str('{:.2f}%'.format(max(df2[df2['Com_Name']==specie]['Confidence'])*100))+ # ' '+' Median:'+str('{:.2f}%'.format(np.median(df2[df2['Com_Name']==specie]['Confidence'])*100)) # ) ) + + fig.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), row=1,col=1) df4=df2['Com_Name'][df2['Com_Name']==specie].resample('15min').count() df4.index=[df4.index.date, df4.index.time] day_hour_freq=df4.unstack().fillna(0) @@ -208,11 +213,13 @@ else: fig_y = [h.strftime('%H:%M') for h in day_hour_freq.columns.tolist()] fig_z = day_hour_freq.values.transpose() fig_heatmap = go.Figure(data=go.Heatmap(x=fig_x,y=fig_y,z=fig_z)) - fig.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), row=1,col=1) + fig.update_layout( margin=dict(l=0, r=0, t=50, b=0), yaxis={'categoryorder':'total ascending'}) - fig.add_trace(go.Heatmap(x=fig_x,y=fig_y,z=fig_z, autocolorscale = False, colorscale = 'blackbody'), row=1, col=2) + color_pals= px.colors.named_colorscales() + selected_pal = cols2.selectbox('Select Color Pallet for Daily Detections', color_pals) + fig.add_trace(go.Heatmap(x=fig_x,y=fig_y,z=fig_z, autocolorscale = False, colorscale = selected_pal), row=1, col=2) # container=st.container() # config={'displayModelBar': False} st.plotly_chart(fig, use_container_width=True) #, config=config) diff --git a/scripts/restart_services.sh b/scripts/restart_services.sh index 98993af..ad4e9f3 100755 --- a/scripts/restart_services.sh +++ b/scripts/restart_services.sh @@ -9,7 +9,6 @@ sudo pkill server.py sudo systemctl stop birdnet_recording.service services=(web_terminal.service spectrogram_viewer.service -pushed_notifications.service livestream.service icecast2.service extraction.service @@ -22,6 +21,6 @@ sudo systemctl restart "${i}" done until grep 5050 <(netstat -tulpn 2>&1);do sudo systemctl restart birdnet_server.service -sleep 30 +sleep 45 done sudo systemctl restart birdnet_analysis.service diff --git a/scripts/server.py b/scripts/server.py index 35833e1..38a06fc 100755 --- a/scripts/server.py +++ b/scripts/server.py @@ -243,6 +243,33 @@ def sendAppriseNotifications(species,confidence): title=title, ) + if str(str(str([i for i in this_run if i.startswith('APPRISE_NOTIFY_NEW_SPECIES')]).split('=')[1]).split('\\')[0]) == "1": + try: + con = sqlite3.connect(userDir + '/BirdNET-Pi/scripts/birds.db') + con.row_factory = lambda cursor, row: row[0] + cur = con.cursor() + cur.execute("SELECT DISTINCT(Com_Name) FROM detections") + known_species = cur.fetchall() + sciName,comName = species.split("_") + + print("\ncomName: ",comName) + print("\nknown_species: ",known_species) + if comName not in known_species: + apobj = apprise.Apprise() + config = apprise.AppriseConfig() + config.add(userDir + '/BirdNET-Pi/apprise.txt') + apobj.add(config) + + apobj.notify( + body=body.replace("$sciname",species.split("_")[0]).replace("$comname",species.split("_")[1]).replace("$confidence",confidence), + title=title, + ) + + con.close() + except: + print("Database busy") + time.sleep(2) + def writeResultsToFile(detections, min_conf, path): print('WRITING RESULTS TO', path, '...', end=' ') diff --git a/scripts/service_controls.php b/scripts/service_controls.php index 8abde5e..e81952b 100644 --- a/scripts/service_controls.php +++ b/scripts/service_controls.php @@ -73,13 +73,6 @@ -
-

Pushed Notifications

- - - - -
diff --git a/scripts/spectrogram.php b/scripts/spectrogram.php index 48eb20f..f95ee72 100644 --- a/scripts/spectrogram.php +++ b/scripts/spectrogram.php @@ -1,19 +1,92 @@ -