Merge branch 'main' into remove-python-shebangs

This commit is contained in:
Patrick McGuire
2022-05-24 12:09:53 -04:00
committed by GitHub
22 changed files with 403 additions and 193 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ Icon made by <a href="https://www.freepik.com" title="Freepik">Freepik</a> from
</p> </p>
## Introduction ## Introduction
BirdNET-Pi is built on the [TFLite version of BirdNET](https://github.com/kahst/BirdNET-Lite) by [**@kahst**](https://github.com/kahst) <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><img src="https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg"></a> 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) <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><img src="https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg"></a> 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 Check out birds from around the world
- [BirdWeather](https://app.birdweather.com)<br> - [BirdWeather](https://app.birdweather.com)<br>
+2 -1
View File
@@ -60,7 +60,8 @@ RTSP_STREAM=
APPRISE_NOTIFICATION_TITLE="New BirdNET-Pi Detection" APPRISE_NOTIFICATION_TITLE="New BirdNET-Pi Detection"
APPRISE_NOTIFICATION_BODY="A \$sciname \$comname was just detected with a confidence of \$confidence" 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 -----------------------# #---------------------- Flickr Images API Configuration -----------------------#
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+3 -3
View File
@@ -5,7 +5,7 @@ body::-webkit-scrollbar {
display:none display:none
} }
</style> </style>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css?v=14">
<body> <body>
<div class="banner"> <div class="banner">
<div class="logo"> <div class="logo">
@@ -36,7 +36,7 @@ if(isset($_GET['stream'])){
echo " echo "
<audio controls autoplay><source src=\"/stream\"></audio> <audio controls autoplay><source src=\"/stream\"></audio>
</div> </div>
<h1><a href=\"/\">BirdNET-Pi</a></h1> <h1><a href=\"/\"><img class=\"topimage\" src=\"images/bnp.png\"></a></h1>
</div>"; </div>";
} else { } else {
header('WWW-Authenticate: Basic realm="My Realm"'); header('WWW-Authenticate: Basic realm="My Realm"');
@@ -51,7 +51,7 @@ if(isset($_GET['stream'])){
<button type=\"submit\" name=\"stream\" value=\"play\">Live Audio</button> <button type=\"submit\" name=\"stream\" value=\"play\">Live Audio</button>
</form> </form>
</div> </div>
<h1><a href=\"/\">BirdNET-Pi</a></h1> <h1><a href=\"/\"><img class=\"topimage\" src=\"images/bnp.png\"></a></h1>
</div>"; </div>";
} }
if(isset($_GET['filename'])) { if(isset($_GET['filename'])) {
+50 -4
View File
@@ -70,11 +70,16 @@ label {
font-weight: bold; font-weight: bold;
} }
hr {
border-color:black;
}
button { button {
background-color: transparent; background-color: transparent;
border: none; border: none;
color: black; color: black;
cursor: pointer; cursor: pointer;
transition:background-color 0.2s;
} }
button:hover { button:hover {
@@ -138,10 +143,15 @@ button:hover {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-bottom: 15px; 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; border-radius: 4px;
} }
.topimage {
width:175px;
display:initial !important;
}
.topnav form { .topnav form {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -181,7 +191,7 @@ button:hover {
} }
.stats table { .stats table {
width: 75%; width: 60%;
} }
.overview th { .overview th {
@@ -247,11 +257,14 @@ button:hover {
.views .centered button { .views .centered button {
background-color: rgb(219, 255, 235); background-color: rgb(219, 255, 235);
padding: 12px; 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 { .views .centered button:hover {
background-color: rgb(159, 226, 155); background-color: rgb(159, 226, 155);
color: black; 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 { .settings {
@@ -546,7 +559,7 @@ button:hover {
font-size:x-large; font-size:x-large;
background:#dbffeb; background:#dbffeb;
padding:5px; padding:5px;
box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.10);
} }
button.legacyview { button.legacyview {
color:gray; color:gray;
@@ -557,15 +570,31 @@ button.legacyview {
font-size:small; font-size:small;
background:#dbffeb; background:#dbffeb;
padding:5px; 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 { button.loadmore {
margin-top:10px; margin-top:10px;
font-size:x-large; font-size:x-large;
background:#dbffeb; background:#dbffeb;
padding:10px; 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 { tr {
background-color:#9fe29b; background-color:#9fe29b;
@@ -581,6 +610,11 @@ tr {
.centered form#views button { .centered form#views button {
box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.10); box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.10);
margin:2px; 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 { input {
box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10); box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10);
@@ -605,6 +639,8 @@ dialog::backdrop {
margin-bottom:3px; margin-bottom:3px;
} }
.centered_image_container img.img1 { .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; cursor:pointer;
height:95%; height:95%;
position:absolute; position:absolute;
@@ -613,9 +649,19 @@ dialog::backdrop {
border-radius: 5px; border-radius: 5px;
width:unset; 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 * { .centered_image_container * {
font-size:19px !important; font-size:19px !important;
} }
.centered_image_container form { .centered_image_container form {
margin-bottom:0px; margin-bottom:0px;
} }
.brbanner {
padding:15px;
background-color:rgb(159, 226, 155);
text-align:center;
font-size:large;
}
+55 -9
View File
@@ -37,17 +37,17 @@ body::-webkit-scrollbar {
</div> </div>
<script> <script>
var topbuttons = document.querySelectorAll("button[form='views']"); var topbuttons = document.querySelectorAll("button[form='views']");
if(window.location.search.substr(1) != '') { if(window.location.search.substr(1) != '') {
for (var i = 0; i < topbuttons.length; i++) { for (var i = 0; i < topbuttons.length; i++) {
if(topbuttons[i].value == decodeURIComponent(window.location.search.substr(1)).replace(/\+/g,' ').split('=').pop()) { if(topbuttons[i].value == decodeURIComponent(window.location.search.substr(1)).replace(/\+/g,' ').split('=').pop()) {
topbuttons[i].classList.add("button-hover"); topbuttons[i].classList.add("button-hover");
} }
} }
} else { } else {
topbuttons[0].classList.add("button-hover"); topbuttons[0].classList.add("button-hover");
} }
function copyOutput(elem) { function copyOutput(elem) {
elem.innerHTML = 'Copied!'; elem.innerHTML = 'Copied!';
const copyText = document.getElementsByTagName("pre")[0].textContent; const copyText = document.getElementsByTagName("pre")[0].textContent;
const textArea = document.createElement('textarea'); const textArea = document.createElement('textarea');
@@ -57,7 +57,7 @@ body::-webkit-scrollbar {
document.body.append(textArea); document.body.append(textArea);
textArea.select(); textArea.select();
document.execCommand("copy"); document.execCommand("copy");
} }
</script> </script>
<div class="views"> <div class="views">
@@ -70,7 +70,7 @@ if(isset($_GET['view'])){
if($_GET['view'] == "View Log"){echo "<body style=\"scroll:no;overflow-x:hidden;\"><iframe style=\"width:calc( 100% + 1em);\" src=\"/log\"></iframe></body>";} if($_GET['view'] == "View Log"){echo "<body style=\"scroll:no;overflow-x:hidden;\"><iframe style=\"width:calc( 100% + 1em);\" src=\"/log\"></iframe></body>";}
if($_GET['view'] == "Overview"){include('overview.php');} if($_GET['view'] == "Overview"){include('overview.php');}
if($_GET['view'] == "Today's Detections"){include('todays_detections.php');} if($_GET['view'] == "Today's Detections"){include('todays_detections.php');}
if($_GET['view'] == "Species Stats"){echo "<br><br>";include('stats.php');} if($_GET['view'] == "Species Stats"){include('stats.php');}
if($_GET['view'] == "Streamlit"){echo "<iframe src=\"/stats\"></iframe>";} if($_GET['view'] == "Streamlit"){echo "<iframe src=\"/stats\"></iframe>";}
if($_GET['view'] == "Daily Charts"){include('history.php');} if($_GET['view'] == "Daily Charts"){include('history.php');}
if($_GET['view'] == "Tools"){ if($_GET['view'] == "Tools"){
@@ -97,7 +97,7 @@ if(isset($_GET['view'])){
<button type=\"submit\" name=\"view\" value=\"System Controls\" form=\"views\">System Controls</button> <button type=\"submit\" name=\"view\" value=\"System Controls\" form=\"views\">System Controls</button>
<button type=\"submit\" name=\"view\" value=\"Services\" form=\"views\">Services</button> <button type=\"submit\" name=\"view\" value=\"Services\" form=\"views\">Services</button>
<button type=\"submit\" name=\"view\" value=\"File\" form=\"views\">File Manager</button> <button type=\"submit\" name=\"view\" value=\"File\" form=\"views\">File Manager</button>
<a href=\"scripts/adminer.php\" target=\"_top\"><button type=\"submit\" form=\"\">Database Maintenanace</button></a> <a href=\"scripts/adminer.php\" target=\"_top\"><button type=\"submit\" form=\"\">Database Maintenance</button></a>
<button type=\"submit\" name=\"view\" value=\"Webterm\" form=\"views\">Web Terminal</button> <button type=\"submit\" name=\"view\" value=\"Webterm\" form=\"views\">Web Terminal</button>
<button type=\"submit\" name=\"view\" value=\"Included\" form=\"views\">Custom Species List</button> <button type=\"submit\" name=\"view\" value=\"Included\" form=\"views\">Custom Species List</button>
<button type=\"submit\" name=\"view\" value=\"Excluded\" form=\"views\">Excluded Species List</button> <button type=\"submit\" name=\"view\" value=\"Excluded\" form=\"views\">Excluded Species List</button>
@@ -210,8 +210,54 @@ if(isset($_GET['view'])){
} else { } else {
$submittedpwd = $_SERVER['PHP_AUTH_PW']; $submittedpwd = $_SERVER['PHP_AUTH_PW'];
$submitteduser = $_SERVER['PHP_AUTH_USER']; $submitteduser = $_SERVER['PHP_AUTH_USER'];
if($submittedpwd == $caddypwd && $submitteduser == 'birdnet'){ $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']; $command = $_GET['submit'];
if($submittedpwd == $caddypwd && $submitteduser == 'birdnet' && in_array($command,$allowedCommands)){
if(isset($command)){ if(isset($command)){
$initcommand = $command; $initcommand = $command;
if (strpos($command, "systemctl") !== false) { if (strpos($command, "systemctl") !== false) {
+4 -2
View File
@@ -20,11 +20,13 @@ make_thisrun() {
make_thisrun &> /dev/null make_thisrun &> /dev/null
if ! diff ${LAST_RUN} ${THIS_RUN};then if ! diff ${LAST_RUN} ${THIS_RUN};then
echo "The birdnet.conf file has changed" echo "The birdnet.conf file has changed"
echo "Reloading services" if grep REC <(diff $LAST_RUN $THIS_RUN);then
cat ${THIS_RUN} > ${LAST_RUN} echo "Recording element changed -- restarting 'birdnet_recording.service'"
sudo systemctl stop birdnet_recording.service sudo systemctl stop birdnet_recording.service
sudo rm -rf ${RECS_DIR}/$(date +%B-%Y/%d-%A)/* sudo rm -rf ${RECS_DIR}/$(date +%B-%Y/%d-%A)/*
sudo systemctl start birdnet_recording.service sudo systemctl start birdnet_recording.service
fi
cat ${THIS_RUN} > ${LAST_RUN}
fi fi
INCLUDE_LIST="$HOME/BirdNET-Pi/include_species_list.txt" INCLUDE_LIST="$HOME/BirdNET-Pi/include_species_list.txt"
+8 -6
View File
@@ -16,10 +16,10 @@ if(isset($_GET['apprise_notify_each_detection'])) {
} else { } else {
$apprise_notify_each_detection = 0; $apprise_notify_each_detection = 0;
} }
if(isset($_GET['apprise_notify_each_species'])) { if(isset($_GET['apprise_notify_new_species'])) {
exec('sudo systemctl start pushed_notifications.service'); $apprise_notify_new_species = 1;
} else { } 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 // 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_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_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_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); $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_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_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_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); $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 = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
$home = trim($home); $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 = "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`; `sudo restart_services.sh`;
} }
} }
@@ -144,8 +146,8 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}
<input name="apprise_notification_title" type="text" value="<?php print($config['APPRISE_NOTIFICATION_TITLE']);?>" /><br> <input name="apprise_notification_title" type="text" value="<?php print($config['APPRISE_NOTIFICATION_TITLE']);?>" /><br>
<label for="apprise_notification_body">Notification Body (use variables $sciname, $comname, or $confidence): </label> <label for="apprise_notification_body">Notification Body (use variables $sciname, $comname, or $confidence): </label>
<input name="apprise_notification_body" type="text" value="<?php print($config['APPRISE_NOTIFICATION_BODY']);?>" /><br> <input name="apprise_notification_body" type="text" value="<?php print($config['APPRISE_NOTIFICATION_BODY']);?>" /><br>
<input type="checkbox" name="apprise_notify_each_species" <?php $output = shell_exec("service pushed_notifications status"); if (!strpos($output, 'dead') !== false && filesize($home."/BirdNET-Pi/apprise.txt") != 0) { echo "checked"; } ?>> <input type="checkbox" name="apprise_notify_new_species" <?php if($config['APPRISE_NOTIFY_NEW_SPECIES'] == 1 && filesize($home."/BirdNET-Pi/apprise.txt") != 0) { echo "checked"; };?> >
<label for="apprise_notify_each_species">Notify each new species</label><br> <label for="apprise_notify_new_species">Notify each new species</label><br>
<input type="checkbox" name="apprise_notify_each_detection" <?php if($config['APPRISE_NOTIFY_EACH_DETECTION'] == 1 && filesize($home."/BirdNET-Pi/apprise.txt") != 0) { echo "checked"; };?> > <input type="checkbox" name="apprise_notify_each_detection" <?php if($config['APPRISE_NOTIFY_EACH_DETECTION'] == 1 && filesize($home."/BirdNET-Pi/apprise.txt") != 0) { echo "checked"; };?> >
<label for="apprise_notify_each_detection">Notify each new detection</label><br><br> <label for="apprise_notify_each_detection">Notify each new detection</label><br><br>
<h3>Bird Photos from Flickr</h3> <h3>Bird Photos from Flickr</h3>
+1 -1
View File
@@ -32,7 +32,7 @@ df['Hour of Day'] = [r.hour for r in df.Time]
df_plt=df #Default to use the whole Dbase df_plt=df #Default to use the whole Dbase
# Add every font at the specified location # 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): for font in font_manager.findSystemFonts(font_dir):
font_manager.fontManager.addfont(font) font_manager.fontManager.addfont(font)
+2 -1
View File
@@ -73,7 +73,8 @@ RTSP_STREAM=
APPRISE_NOTIFICATION_TITLE="New BirdNET-Pi Detection" APPRISE_NOTIFICATION_TITLE="New BirdNET-Pi Detection"
APPRISE_NOTIFICATION_BODY="A \$sciname \$comname was just detected with a confidence of \$confidence" 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 -----------------------# #---------------------- Flickr Images API Configuration -----------------------#
+1 -18
View File
@@ -13,7 +13,7 @@ export PYTHON_VIRTUAL_ENV="$HOME/BirdNET-Pi/birdnet/bin/python3"
install_depends() { install_depends() {
apt install -y debian-keyring debian-archive-keyring apt-transport-https 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 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 apt -qqq update && apt -qqy upgrade
echo "icecast2 icecast2/icecast-setup boolean false" | debconf-set-selections echo "icecast2 icecast2/icecast-setup boolean false" | debconf-set-selections
@@ -93,22 +93,6 @@ EOF
systemctl enable extraction.service 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() { create_necessary_dirs() {
echo "Creating necessary directories" echo "Creating necessary directories"
[ -d ${EXTRACTED} ] || sudo -u ${USER} mkdir -p ${EXTRACTED} [ -d ${EXTRACTED} ] || sudo -u ${USER} mkdir -p ${EXTRACTED}
@@ -438,7 +422,6 @@ install_services() {
install_recording_service install_recording_service
install_custom_recording_service # But does not enable install_custom_recording_service # But does not enable
install_extraction_service install_extraction_service
install_pushed_notifications
install_spectrogram_service install_spectrogram_service
install_chart_viewer_service install_chart_viewer_service
install_gotty_logs install_gotty_logs
+13 -7
View File
@@ -1,4 +1,7 @@
<?php <?php
ini_set('session.gc_maxlifetime', 7200);
session_set_cookie_params(7200);
session_start();
$myDate = date('Y-m-d'); $myDate = date('Y-m-d');
$chart = "Combo-$myDate.png"; $chart = "Combo-$myDate.png";
@@ -22,7 +25,9 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
header("refresh: 0;"); header("refresh: 0;");
} }
$result4 = $statement4->execute(); $result4 = $statement4->execute();
$images = []; if(!isset($_SESSION['images'])) {
$_SESSION['images'] = [];
}
$iterations = 0; $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 // 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)) { 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 // we've found our valid detection! ignore everything else from the database loop
if(strpos($headers[0],'200')) { if(strpos($headers[0],'200')) {
if($_GET['previous_detection_identifier'] == $filename) { die(); } if($_GET['previous_detection_identifier'] == $filename) { die(); }
if($_GET['only_name'] == "true") { echo $comname.",".$filename;die(); }
if (!empty($config["FLICKR_API_KEY"])) { 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 // 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) { if($key !== false) {
$image = $images[$key]; $image = $_SESSION['images'][$key];
} else { } 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]; $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"]; $modaltext = "https://flickr.com/photos/".$flickrjson["owner"]."/".$flickrjson["id"];
$authorlink = "https://flickr.com/people/".$flickrjson["owner"]; $authorlink = "https://flickr.com/people/".$flickrjson["owner"];
$imageurl = 'http://farm' .$flickrjson["farm"]. '.static.flickr.com/' .$flickrjson["server"]. '/' .$flickrjson["id"]. '_' .$flickrjson["secret"]. '.jpg'; $imageurl = 'https://farm' .$flickrjson["farm"]. '.static.flickr.com/' .$flickrjson["server"]. '/' .$flickrjson["id"]. '_' .$flickrjson["secret"]. '.jpg';
array_push($images, array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink)); array_push($_SESSION['images'], array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink));
$image = $images[count($images)-1]; $image = $_SESSION['images'][count($_SESSION['images'])-1];
} }
} }
@@ -238,7 +244,7 @@ function loadDetectionIfNewExists(previous_detection_identifier=undefined) {
const xhttp = new XMLHttpRequest(); const xhttp = new XMLHttpRequest();
xhttp.onload = function() { xhttp.onload = function() {
// if there's a new detection that needs to be updated to the page // 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; document.getElementById("most_recent_detection").innerHTML = this.responseText;
// only going to load left chart if there's a new detection // only going to load left chart if there's a new detection
+5 -7
View File
@@ -1,7 +1,5 @@
<?php <?php
ini_set('display_errors', 1); error_reporting(E_ERROR);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$db = new SQLite3('./scripts/birds.db', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE); $db = new SQLite3('./scripts/birds.db', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
if($db == False){ if($db == False){
@@ -170,10 +168,10 @@ if(!isset($_GET['species']) && !isset($_GET['filename'])){
<input type="hidden" name="view" value="Recordings"> <input type="hidden" name="view" value="Recordings">
<input type="hidden" name="<?php echo $view; ?>" value="<?php echo $_GET['date']; ?>"> <input type="hidden" name="<?php echo $view; ?>" value="<?php echo $_GET['date']; ?>">
<button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "alphabetical"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="alphabetical"> <button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "alphabetical"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="alphabetical">
<img src="images/sort_abc.svg" alt="Sort by alphabetical"> <img src="images/sort_abc.svg" title="Sort by alphabetical" alt="Sort by alphabetical">
</button> </button>
<button <?php if(isset($_GET['sort']) && $_GET['sort'] == "occurrences"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="occurrences"> <button <?php if(isset($_GET['sort']) && $_GET['sort'] == "occurrences"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="occurrences">
<img src="images/sort_occ.svg" alt="Sort by occurrences"> <img src="images/sort_occ.svg" title="Sort by occurrences" alt="Sort by occurrences">
</button> </button>
</form> </form>
</div> </div>
@@ -230,10 +228,10 @@ if(isset($_GET['species'])){ ?>
<input type="hidden" name="view" value="Recordings"> <input type="hidden" name="view" value="Recordings">
<input type="hidden" name="species" value="<?php echo $_GET['species']; ?>"> <input type="hidden" name="species" value="<?php echo $_GET['species']; ?>">
<button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "date"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="date"> <button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "date"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="date">
<img width=35px src="images/sort_date.svg" alt="Sort by date"> <img width=35px src="images/sort_date.svg" title="Sort by date" alt="Sort by date">
</button> </button>
<button <?php if(isset($_GET['sort']) && $_GET['sort'] == "confidence"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="confidence"> <button <?php if(isset($_GET['sort']) && $_GET['sort'] == "confidence"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="confidence">
<img src="images/sort_occ.svg" alt="Sort by confidence"> <img src="images/sort_occ.svg" title="Sort by confidence" alt="Sort by confidence">
</button> </button>
</form> </form>
</div> </div>
+30 -23
View File
@@ -8,6 +8,7 @@ from datetime import timedelta, datetime
from pathlib import Path from pathlib import Path
import sqlite3 import sqlite3
from sqlite3 import Connection from sqlite3 import Connection
import plotly.express as px
userDir = os.path.expanduser('~') userDir = os.path.expanduser('~')
URI_SQLITE_DB = userDir + '/BirdNET-Pi/scripts/birds.db' URI_SQLITE_DB = userDir + '/BirdNET-Pi/scripts/birds.db'
@@ -122,42 +123,43 @@ filt=df2['Com_Name']==specie
df_counts=sum(df5==specie) df_counts=sum(df5==specie)
fig = make_subplots(
if resample_time != '1D':
fig = make_subplots(
rows=3, cols =2, rows=3, cols =2,
specs= [[{"type":"xy","rowspan":3}, {"type":"polar","rowspan":2}], [{"rowspan":1}, {"rowspan":1} ], [None, {"type":"xy","rowspan":1}]], specs= [[{"type":"xy","rowspan":3}, {"type":"polar","rowspan":2}], [{"rowspan":1}, {"rowspan":1} ], [None, {"type":"xy","rowspan":1}]],
subplot_titles=('<b>Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'</b>', subplot_titles=('<b>Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+' for '+str(resample_sel)+' sampling interval.'+'</b>',
'Total Detect:'+str('{:,}'.format(df_counts))+ 'Total Detect:'+str('{:,}'.format(df_counts))+
' Confidence Max:'+str('{:.2f}%'.format(max(df2[df2['Com_Name']==specie]['Confidence'])*100))+ ' 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)) ' '+' 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 #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.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), row=1,col=1)
fig.update_layout( fig.update_layout(
margin=dict(l=0, r=0, t=50, b=0), margin=dict(l=0, r=0, t=50, b=0),
yaxis={'categoryorder':'total ascending'}) yaxis={'categoryorder':'total ascending'})
# Set 360 degrees, 24 hours for polar plot # Set 360 degrees, 24 hours for polar plot
theta = np.linspace(0.0, 360, 24, endpoint=False) theta = np.linspace(0.0, 360, 24, endpoint=False)
specie_filt= df5==specie specie_filt= df5==specie
df3=df5[specie_filt] 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() d=pd.DataFrame(np.zeros((23,1))).squeeze()
detections = hourly.loc[specie] detections = hourly.loc[specie]
detections=(d+detections).fillna(0) detections=(d+detections).fillna(0)
if resample_time != '1D':
fig.add_trace(go.Barpolar(r = detections, theta=theta), row=1, col=2) fig.add_trace(go.Barpolar(r = detections, theta=theta), row=1, col=2)
fig.update_layout( fig.update_layout(
autosize=False, autosize=False,
@@ -191,15 +193,18 @@ if resample_time != '1D':
else: else:
fig = make_subplots( fig = make_subplots(
rows=1, cols =2, rows=1, cols =2,
# specs= [[{"type":"xy","rowspan":1}], specs= [[{"type":"xy","rowspan":1},{"type":"xy","rowspan":1}]],
# [{"rowspan":1}],
# ],
# subplot_titles=('<b>Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'</b>', subplot_titles=('<b>Daily Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'</b>',
'<b>Daily ' + specie+ ' Detections on 15 minute intervals </b>'),
# 'Total Detect:'+str('{:,}'.format(df_counts))+ # 'Total Detect:'+str('{:,}'.format(df_counts))+
# ' Confidence Max:'+str('{:.2f}%'.format(max(df2[df2['Com_Name']==specie]['Confidence'])*100))+ # ' 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)) # ' '+' 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=df2['Com_Name'][df2['Com_Name']==specie].resample('15min').count()
df4.index=[df4.index.date, df4.index.time] df4.index=[df4.index.date, df4.index.time]
day_hour_freq=df4.unstack().fillna(0) 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_y = [h.strftime('%H:%M') for h in day_hour_freq.columns.tolist()]
fig_z = day_hour_freq.values.transpose() fig_z = day_hour_freq.values.transpose()
fig_heatmap = go.Figure(data=go.Heatmap(x=fig_x,y=fig_y,z=fig_z)) 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( fig.update_layout(
margin=dict(l=0, r=0, t=50, b=0), margin=dict(l=0, r=0, t=50, b=0),
yaxis={'categoryorder':'total ascending'}) 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() # container=st.container()
# config={'displayModelBar': False} # config={'displayModelBar': False}
st.plotly_chart(fig, use_container_width=True) #, config=config) st.plotly_chart(fig, use_container_width=True) #, config=config)
+1 -2
View File
@@ -9,7 +9,6 @@ sudo pkill server.py
sudo systemctl stop birdnet_recording.service sudo systemctl stop birdnet_recording.service
services=(web_terminal.service services=(web_terminal.service
spectrogram_viewer.service spectrogram_viewer.service
pushed_notifications.service
livestream.service livestream.service
icecast2.service icecast2.service
extraction.service extraction.service
@@ -22,6 +21,6 @@ sudo systemctl restart "${i}"
done done
until grep 5050 <(netstat -tulpn 2>&1);do until grep 5050 <(netstat -tulpn 2>&1);do
sudo systemctl restart birdnet_server.service sudo systemctl restart birdnet_server.service
sleep 30 sleep 45
done done
sudo systemctl restart birdnet_analysis.service sudo systemctl restart birdnet_analysis.service
+27
View File
@@ -243,6 +243,33 @@ def sendAppriseNotifications(species,confidence):
title=title, 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): def writeResultsToFile(detections, min_conf, path):
print('WRITING RESULTS TO', path, '...', end=' ') print('WRITING RESULTS TO', path, '...', end=' ')
-7
View File
@@ -73,13 +73,6 @@
<button type="submit" name="submit" value="sudo systemctl disable --now spectrogram_viewer.service">Disable</button> <button type="submit" name="submit" value="sudo systemctl disable --now spectrogram_viewer.service">Disable</button>
<button type="submit" name="submit" value="sudo systemctl enable --now spectrogram_viewer.service">Enable</button> <button type="submit" name="submit" value="sudo systemctl enable --now spectrogram_viewer.service">Enable</button>
</form> </form>
<form action="" method="GET">
<h3>Pushed Notifications</h3>
<button type="submit" name="submit" value="sudo systemctl stop pushed_notifications.service">Stop</button>
<button type="submit" name="submit" value="sudo systemctl restart pushed_notifications.service">Restart</button>
<button type="submit" name="submit" value="sudo systemctl disable --now pushed_notifications.service">Disable</button>
<button type="submit" name="submit" value="sudo systemctl enable --now pushed_notifications.service">Enable</button>
</form>
<form action="" method="GET"> <form action="" method="GET">
<button type="submit" name="submit" value="stop_core_services.sh">Stop Core Services</button> <button type="submit" name="submit" value="stop_core_services.sh">Stop Core Services</button>
</form> </form>
+81 -6
View File
@@ -4,16 +4,89 @@
// UPDATE: there is a problem in chrome with starting audio context // UPDATE: there is a problem in chrome with starting audio context
// before a user gesture. This fixes it. // before a user gesture. This fixes it.
var started = null; var started = null;
var player = null;
const ctx = null;
window.onload = function(){ window.onload = function(){
// if user agent includes iPhone or Mac use legacy mode
if(window.navigator.userAgent.includes("iPhone") || window.navigator.userAgent.includes("Mac")) {
document.getElementById("spectrogramimage").style.display="";
document.body.querySelector('canvas').remove();
document.getElementById('player').remove();
document.body.querySelector('h1').remove();
<?php
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');
}
$refresh = $config['RECORDING_LENGTH'];
$time = time();
?>
// every $refresh seconds, this loop will run and refresh the spectrogram image
window.setInterval(function(){
document.getElementById("spectrogramimage").src = "/spectrogram.png?nocache="+Date.now();
}, <?php echo $refresh; ?>*1000);
} else {
document.getElementById("spectrogramimage").remove();
var audioelement = window.parent.document.getElementsByTagName("audio")[0];
if (typeof(audioelement) != 'undefined') {
document.getElementById('player').remove();
player = audioelement;
} else {
player = document.getElementById('player');
}
player.play();
if (started) return; if (started) return;
started = true; started = true;
initialize(); initialize();
}
}; };
function fitTextOnCanvas(text,fontface,yPosition){
var fontsize=300;
do{
fontsize--;
CTX.font=fontsize+"px "+fontface;
}while(CTX.measureText(text).width>document.body.querySelector('canvas').width)
CTX.font = CTX.font=(fontsize*0.35)+"px "+fontface;
CTX.fillText(text,document.body.querySelector('canvas').width - (document.body.querySelector('canvas').width * 0.50),yPosition);
}
function applyText(text) {
CTX.fillStyle = 'white';
CTX.font = '25px Roboto Flex';
fitTextOnCanvas(text,"Roboto Flex",document.body.querySelector('canvas').scrollHeight * 0.35)
CTX.fillStyle = 'hsl(280, 100%, 10%)';
}
var previous_detection_identifier = null;
function loadDetectionIfNewExists() {
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")) {
if(previous_detection_identifier != null){
applyText(this.responseText.split(",")[0].replace("_"," "));
}
previous_detection_identifier = this.responseText.split(",")[1];
}
}
xhttp.open("GET", "overview.php?ajax_detections=true&previous_detection_identifier="+previous_detection_identifier+"&only_name=true", true);
xhttp.send();
}
window.setInterval(function(){
loadDetectionIfNewExists();
}, 2500);
function initialize() { function initialize() {
document.body.querySelector('h1').remove(); document.body.querySelector('h1').remove();
const CVS = document.body.querySelector('canvas'); const CVS = document.body.querySelector('canvas');
const CTX = CVS.getContext('2d'); CTX = CVS.getContext('2d');
const W = CVS.width = window.innerWidth; const W = CVS.width = window.innerWidth;
const H = CVS.height = window.innerHeight; const H = CVS.height = window.innerHeight;
@@ -25,12 +98,12 @@ function initialize() {
process(); process();
function process() { function process() {
const SOURCE = ACTX.createMediaElementSource(document.getElementById('player')); const SOURCE = ACTX.createMediaElementSource(player);
SOURCE.connect(ANALYSER); SOURCE.connect(ANALYSER);
SOURCE.connect(ACTX.destination) SOURCE.connect(ACTX.destination)
const DATA = new Uint8Array(ANALYSER.frequencyBinCount); const DATA = new Uint8Array(ANALYSER.frequencyBinCount);
const LEN = DATA.length; const LEN = DATA.length;
const h = H / LEN; const h = (H / LEN + 0.9);
const x = W - 1; const x = W - 1;
CTX.fillStyle = 'hsl(280, 100%, 10%)'; CTX.fillStyle = 'hsl(280, 100%, 10%)';
CTX.fillRect(0, 0, W, H); CTX.fillRect(0, 0, W, H);
@@ -40,11 +113,12 @@ function initialize() {
function loop() { function loop() {
window.requestAnimationFrame(loop); window.requestAnimationFrame(loop);
let imgData = CTX.getImageData(1, 0, W - 1, H); let imgData = CTX.getImageData(1, 0, W - 1, H);
CTX.fillRect(0, 0, W, H); CTX.fillRect(0, 0, W, H);
CTX.putImageData(imgData, 0, 0); CTX.putImageData(imgData, 0, 0);
ANALYSER.getByteFrequencyData(DATA); ANALYSER.getByteFrequencyData(DATA);
for (let i = 0; i < LEN; i++) { for (let i = 0; i < LEN; i++) {
let rat = DATA[i] / 128; let rat = DATA[i] / 128 ;
let hue = Math.round((rat * 120) + 280 % 360); let hue = Math.round((rat * 120) + 280 % 360);
let sat = '100%'; let sat = '100%';
let lit = 10 + (70 * rat) + '%'; let lit = 10 + (70 * rat) + '%';
@@ -79,7 +153,8 @@ h1 {
} }
</style> </style>
<audio style="display:none" controls="" crossorigin="anonymous" id='player' autoplay=""><source src="/stream"></audio> <img id="spectrogramimage" style="width:100%;height:100%;display:none" src="/spectrogram.png?nocache=<?php echo $time;?>">
<audio style="display:none" controls="" crossorigin="anonymous" id='player' preload="none"><source src="/stream"></audio>
<h1>Loading...</h1> <h1>Loading...</h1>
<canvas></canvas> <canvas></canvas>
+19 -17
View File
@@ -56,17 +56,9 @@ if(isset($_GET['species'])){
$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd"); $user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd");
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd"); $home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
$home = trim($home); $home = trim($home);
if(!file_exists($home."/BirdNET-Pi/scripts/disk_check_exclude.txt")) { if(!file_exists($home."/BirdNET-Pi/scripts/disk_check_exclude.txt") || strpos(file_get_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt"),"##start") === false) {
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "##start\n##end"); file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "");
} file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "##start\n##end\n");
function editfile( $sourcefile, $start='##start', $end='##end', $data=array() ){
$lines=array_filter( file( $sourcefile , FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES ) );
$output=array_merge(
array_splice( $lines, 0, array_search( strtolower( $start ), array_map('strtolower', $lines ) ) + 1 ),
$data,
array_splice( $lines, array_search( strtolower( $end ), array_map('strtolower', $lines ) ) )
);
file_put_contents( $sourcefile, implode( PHP_EOL, $output ) );
} }
?> ?>
@@ -89,10 +81,10 @@ function editfile( $sourcefile, $start='##start', $end='##end', $data=array() ){
<input type="hidden" name="sort" value="<?php if(isset($_GET['sort'])){echo $_GET['sort'];}?>"> <input type="hidden" name="sort" value="<?php if(isset($_GET['sort'])){echo $_GET['sort'];}?>">
<input type="hidden" name="view" value="Species Stats"> <input type="hidden" name="view" value="Species Stats">
<button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "alphabetical"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="alphabetical"> <button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "alphabetical"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="alphabetical">
<img src="images/sort_abc.svg" alt="Sort by alphabetical"> <img src="images/sort_abc.svg" title="Sort by alphabetical" alt="Sort by alphabetical">
</button> </button>
<button <?php if(isset($_GET['sort']) && $_GET['sort'] == "occurrences"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="occurrences"> <button <?php if(isset($_GET['sort']) && $_GET['sort'] == "occurrences"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="occurrences">
<img src="images/sort_occ.svg" alt="Sort by occurrences"> <img src="images/sort_occ.svg" title="Sort by occurrences" alt="Sort by occurrences">
</button> </button>
</form> </form>
</div> </div>
@@ -181,19 +173,27 @@ while($results=$result3->fetchArray(SQLITE3_ASSOC)){
} elseif (file_exists('./scripts/firstrun.ini')) { } elseif (file_exists('./scripts/firstrun.ini')) {
$config = parse_ini_file('./scripts/firstrun.ini'); $config = parse_ini_file('./scripts/firstrun.ini');
} }
if (! empty($config["FLICKR_API_KEY"])) {
$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('_', '+', $comname)."\"&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=15&format=json&nojsoncallback=1"), true)["photos"]["photo"]; $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('_', '+', $comname)."\"&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=15&format=json&nojsoncallback=1"), true)["photos"]["photo"];
foreach ($flickrjson as $val) { foreach ($flickrjson as $val) {
$iter++; $iter++;
$modaltext = "https://flickr.com/photos/".$val["owner"]."/".$val["id"]; $modaltext = "https://flickr.com/photos/".$val["owner"]."/".$val["id"];
$authorlink = "https://flickr.com/people/".$val["owner"]; $authorlink = "https://flickr.com/people/".$val["owner"];
$imageurl = 'http://farm' .$val["farm"]. '.static.flickr.com/' .$val["server"]. '/' .$val["id"]. '_' .$val["secret"]. '.jpg'; $imageurl = 'https://farm' .$val["farm"]. '.static.flickr.com/' .$val["server"]. '/' .$val["id"]. '_' .$val["secret"]. '.jpg';
echo "<img style='vertical-align:top' src=\"$imageurl\"><span style='cursor:pointer;color:blue;text-decoration:underline;' onclick='setModalText(".$iter.",\"".$val["title"]."\",\"".$modaltext."\", \"".$authorlink."\")'>".$iter."</span>"; echo "<span style='cursor:pointer;' onclick='setModalText(".$iter.",\"".$val["title"]."\",\"".$modaltext."\", \"".$authorlink."\")'><img style='vertical-align:top' src=\"$imageurl\"></span>";
}
} }
} }
} }
?> ?>
<br><br><br> <?php if(isset($_GET['species'])){?>
<br><br>
<div class="brbanner">Best Recordings for Other Species:</div><br>
<?php } else {?>
<hr><br>
<?php } ?>
<table> <table>
<?php <?php
@@ -216,8 +216,10 @@ array_push($excludelines, $results['Date']."/".$comname."/".$results['File_Name'
<b>Best Recording:</b> <?php echo $results['Date']." ".$results['Time'];?><br><video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster="<?php echo $filename.".png";?>" preload="none" title="<?php echo $filename;?>"><source src="<?php echo $filename;?>" type="audio/mp3"></video></td> <b>Best Recording:</b> <?php echo $results['Date']." ".$results['Time'];?><br><video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster="<?php echo $filename.".png";?>" preload="none" title="<?php echo $filename;?>"><source src="<?php echo $filename;?>" type="audio/mp3"></video></td>
</tr> </tr>
<?php <?php
editfile($home."/BirdNET-Pi/scripts/disk_check_exclude.txt","##start","##end",$excludelines);
} }
$file = file_get_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt");
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "##start"."\n".implode("\n",$excludelines)."\n".substr($file, strpos($file, "##end")));
?> ?>
</table> </table>
</form> </form>
+22 -8
View File
@@ -1,4 +1,7 @@
<?php <?php
ini_set('session.gc_maxlifetime', 7200);
session_set_cookie_params(7200);
session_start();
ini_set('display_errors', 1); ini_set('display_errors', 1);
ini_set('display_startup_errors', 1); ini_set('display_startup_errors', 1);
error_reporting(E_ALL); error_reporting(E_ALL);
@@ -67,7 +70,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
$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'); $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 { } else {
// legacy mode // legacy mode
$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); $statement0 = $db->prepare('SELECT Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') '.$searchquery.' ORDER BY Time DESC');
} }
if($statement0 == False){ if($statement0 == False){
echo "Database is busy"; echo "Database is busy";
@@ -77,7 +80,10 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
?> <table> ?> <table>
<?php <?php
$images = [];
if(!isset($_SESSION['images'])) {
$_SESSION['images'] = [];
}
$iterations = 0; $iterations = 0;
if (file_exists('./scripts/thisrun.txt')) { if (file_exists('./scripts/thisrun.txt')) {
@@ -97,16 +103,16 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
if (!empty($config["FLICKR_API_KEY"])) { 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 // 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) { if($key !== false) {
$image = $images[$key]; $image = $_SESSION['images'][$key];
} else { } 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&media=photos&format=json&nojsoncallback=1"), true)["photos"]["photo"][0]; $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&media=photos&format=json&nojsoncallback=1"), true)["photos"]["photo"][0];
$modaltext = "https://flickr.com/photos/".$flickrjson["owner"]."/".$flickrjson["id"]; $modaltext = "https://flickr.com/photos/".$flickrjson["owner"]."/".$flickrjson["id"];
$authorlink = "https://flickr.com/people/".$flickrjson["owner"]; $authorlink = "https://flickr.com/people/".$flickrjson["owner"];
$imageurl = 'http://farm' .$flickrjson["farm"]. '.static.flickr.com/' .$flickrjson["server"]. '/' .$flickrjson["id"]. '_' .$flickrjson["secret"]. '.jpg'; $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)); array_push($_SESSION['images'], array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink));
$image = $images[count($images)-1]; $image = $_SESSION['images'][count($_SESSION['images'])-1];
} }
} }
?> ?>
@@ -241,7 +247,11 @@ document.getElementById("searchterm").onkeydown = (function(e) {
}); });
function switchViews(element) { function switchViews(element) {
document.getElementById("detections_table").innerHTML = ""; if(searchterm == ""){
document.getElementById("detections_table").innerHTML = "<h3>Loading <?php echo $todaycount['COUNT(*)']; ?> detections...</h3>";
} else {
document.getElementById("detections_table").innerHTML = "<h3>Loading...</h3>";
}
if(element.innerHTML == "Legacy view") { if(element.innerHTML == "Legacy view") {
element.innerHTML = "Normal view"; element.innerHTML = "Normal view";
loadDetections(undefined); loadDetections(undefined);
@@ -253,7 +263,11 @@ function switchViews(element) {
function searchDetections(searchvalue) { function searchDetections(searchvalue) {
document.getElementById("detections_table").innerHTML = "<h3>Loading...</h3>"; document.getElementById("detections_table").innerHTML = "<h3>Loading...</h3>";
searchterm = searchvalue; searchterm = searchvalue;
if(document.getElementsByClassName('legacyview')[0].innerHTML == "Normal view") {
loadDetections(undefined,undefined);
} else {
loadDetections(40,undefined); loadDetections(40,undefined);
}
} }
function loadDetections(detections_limit, element=undefined) { function loadDetections(detections_limit, element=undefined) {
const xhttp = new XMLHttpRequest(); const xhttp = new XMLHttpRequest();
@@ -267,7 +281,7 @@ function loadDetections(detections_limit, element=undefined) {
} }
} }
if(searchterm !== ""){ if(searchterm != ""){
xhttp.open("GET", "todays_detections.php?ajax_detections=true&display_limit="+detections_limit+"&searchterm="+searchterm, true); xhttp.open("GET", "todays_detections.php?ajax_detections=true&display_limit="+detections_limit+"&searchterm="+searchterm, true);
} else { } else {
xhttp.open("GET", "todays_detections.php?ajax_detections=true&display_limit="+detections_limit, true); xhttp.open("GET", "todays_detections.php?ajax_detections=true&display_limit="+detections_limit, true);
+9 -1
View File
@@ -35,7 +35,10 @@ if ! grep APPRISE_NOTIFICATION_BODY /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "APPRISE_NOTIFICATION_BODY=\"A \$sciname \$comname was just detected with a confidence of \$confidence\"" >> /etc/birdnet/birdnet.conf sudo -u$USER echo "APPRISE_NOTIFICATION_BODY=\"A \$sciname \$comname was just detected with a confidence of \$confidence\"" >> /etc/birdnet/birdnet.conf
fi fi
if ! grep APPRISE_NOTIFY_EACH_DETECTION /etc/birdnet/birdnet.conf &>/dev/null;then if ! grep APPRISE_NOTIFY_EACH_DETECTION /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "APPRISE_NOTIFY_EACH_DETECTION=false" >> /etc/birdnet/birdnet.conf sudo -u$USER echo "APPRISE_NOTIFY_EACH_DETECTION=0 " >> /etc/birdnet/birdnet.conf
fi
if ! grep APPRISE_NOTIFY_NEW_SPECIES /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "APPRISE_NOTIFY_EACH_DETECTION=0 " >> /etc/birdnet/birdnet.conf
fi fi
apprise_installation_status=$(~/BirdNET-Pi/birdnet/bin/python3 -c 'import pkgutil; print("installed" if pkgutil.find_loader("apprise") else "not installed")') apprise_installation_status=$(~/BirdNET-Pi/birdnet/bin/python3 -c 'import pkgutil; print("installed" if pkgutil.find_loader("apprise") else "not installed")')
if [[ "$apprise_installation_status" = "not installed" ]];then if [[ "$apprise_installation_status" = "not installed" ]];then
@@ -58,3 +61,8 @@ fi
if ! grep FLICKR_API_KEY /etc/birdnet/birdnet.conf &>/dev/null;then if ! grep FLICKR_API_KEY /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "FLICKR_API_KEY=" >> /etc/birdnet/birdnet.conf sudo -u$USER echo "FLICKR_API_KEY=" >> /etc/birdnet/birdnet.conf
fi fi
if systemctl list-unit-files pushed_notifications.service;then
sudo systemctl disable --now pushed_notifications.service
sudo rm -f /usr/lib/systemd/system/pushed_notifications.service
sudo rm $HOME/BirdNET-Pi/templates/pushed_notifications.service
fi
+3 -3
View File
@@ -1,6 +1,6 @@
#birdnet #birdnet
*/5 * * * * $USER /usr/local/bin/disk_check.sh &> /dev/null */5 * * * * $USER /usr/local/bin/disk_check.sh >/dev/null 2>&1
#birdnet #birdnet
*/3 * * * * $USER /usr/local/bin/cleanup.sh &> /dev/null */3 * * * * $USER /usr/local/bin/cleanup.sh >/dev/null 2>&1
#birdnet #birdnet
@reboot $USER /usr/local/bin/cleanup.sh &> /dev/null @reboot $USER /usr/local/bin/cleanup.sh >/dev/null 2>&1