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>
## 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
- [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_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 -----------------------#
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+4 -4
View File
@@ -5,7 +5,7 @@ body::-webkit-scrollbar {
display:none
}
</style>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="style.css?v=14">
<body>
<div class="banner">
<div class="logo">
@@ -36,7 +36,7 @@ if(isset($_GET['stream'])){
echo "
<audio controls autoplay><source src=\"/stream\"></audio>
</div>
<h1><a href=\"/\">BirdNET-Pi</a></h1>
<h1><a href=\"/\"><img class=\"topimage\" src=\"images/bnp.png\"></a></h1>
</div>";
} else {
header('WWW-Authenticate: Basic realm="My Realm"');
@@ -46,12 +46,12 @@ if(isset($_GET['stream'])){
}
}
} else {
echo "
echo "
<form action=\"\" method=\"GET\">
<button type=\"submit\" name=\"stream\" value=\"play\">Live Audio</button>
</form>
</div>
<h1><a href=\"/\">BirdNET-Pi</a></h1>
<h1><a href=\"/\"><img class=\"topimage\" src=\"images/bnp.png\"></a></h1>
</div>";
}
if(isset($_GET['filename'])) {
+50 -4
View File
@@ -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;
}
+107 -61
View File
@@ -37,27 +37,27 @@ body::-webkit-scrollbar {
</div>
<script>
var topbuttons = document.querySelectorAll("button[form='views']");
if(window.location.search.substr(1) != '') {
for (var i = 0; i < topbuttons.length; i++) {
if(topbuttons[i].value == decodeURIComponent(window.location.search.substr(1)).replace(/\+/g,' ').split('=').pop()) {
topbuttons[i].classList.add("button-hover");
}
}
} else {
topbuttons[0].classList.add("button-hover");
}
function copyOutput(elem) {
elem.innerHTML = 'Copied!';
const copyText = document.getElementsByTagName("pre")[0].textContent;
const textArea = document.createElement('textarea');
textArea.style.position = 'absolute';
textArea.style.left = '-100%';
textArea.textContent = copyText;
document.body.append(textArea);
textArea.select();
document.execCommand("copy");
var topbuttons = document.querySelectorAll("button[form='views']");
if(window.location.search.substr(1) != '') {
for (var i = 0; i < topbuttons.length; i++) {
if(topbuttons[i].value == decodeURIComponent(window.location.search.substr(1)).replace(/\+/g,' ').split('=').pop()) {
topbuttons[i].classList.add("button-hover");
}
}
} else {
topbuttons[0].classList.add("button-hover");
}
function copyOutput(elem) {
elem.innerHTML = 'Copied!';
const copyText = document.getElementsByTagName("pre")[0].textContent;
const textArea = document.createElement('textarea');
textArea.style.position = 'absolute';
textArea.style.left = '-100%';
textArea.textContent = copyText;
document.body.append(textArea);
textArea.select();
document.execCommand("copy");
}
</script>
<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'] == "Overview"){include('overview.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'] == "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 "<div class=\"centered\">
<form action=\"\" method=\"GET\" id=\"views\">
<button type=\"submit\" name=\"view\" value=\"Settings\" form=\"views\">Settings</button>
<button type=\"submit\" name=\"view\" value=\"System Info\" form=\"views\">System Info</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=\"File\" form=\"views\">File Manager</button>
<a href=\"scripts/adminer.php\" target=\"_top\"><button type=\"submit\" form=\"\">Database Maintenanace</button></a>
<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=\"Excluded\" form=\"views\">Excluded Species List</button>
</form>
</div>";
<form action=\"\" method=\"GET\" id=\"views\">
<button type=\"submit\" name=\"view\" value=\"Settings\" form=\"views\">Settings</button>
<button type=\"submit\" name=\"view\" value=\"System Info\" form=\"views\">System Info</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=\"File\" form=\"views\">File Manager</button>
<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=\"Included\" form=\"views\">Custom Species List</button>
<button type=\"submit\" name=\"view\" value=\"Excluded\" form=\"views\">Excluded Species List</button>
</form>
</div>";
} 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", "<span style='color:red'>FAILURE</span>", $results);
$results = str_replace("failed", "<span style='color:red'>failed</span>",$results);
$results = str_replace("active (running)", "<span style='color:green'><b>active (running)</b></span>",$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", "<span style='color:red'>FAILURE</span>", $results);
$results = str_replace("failed", "<span style='color:red'>failed</span>",$results);
$results = str_replace("active (running)", "<span style='color:green'><b>active (running)</b></span>",$results);
if(strlen($results) == 0) {
$results = "This command has no output.";
}
echo "<table style='min-width:70%;'><tr class='relative'><th>Output of command:`".$initcommand."`<button class='copyimage' style='right:40px' onclick='copyOutput(this);'>Copy</button></th></tr><tr><td><pre style='text-align:left'>$results</pre></td></tr></table>";
}
echo "<table style='min-width:70%;'><tr class='relative'><th>Output of command:`".$initcommand."`<button class='copyimage' style='right:40px' onclick='copyOutput(this);'>Copy</button></th></tr><tr><td><pre style='text-align:left'>$results</pre></td></tr></table>";
} else {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
+6 -4
View File
@@ -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"
+8 -6
View File
@@ -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}
<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>
<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"; } ?>>
<label for="apprise_notify_each_species">Notify each new species</label><br>
<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_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"; };?> >
<label for="apprise_notify_each_detection">Notify each new detection</label><br><br>
<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
# 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)
+2 -1
View File
@@ -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 -----------------------#
+1 -18
View File
@@ -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
+13 -7
View File
@@ -1,4 +1,7 @@
<?php
ini_set('session.gc_maxlifetime', 7200);
session_set_cookie_params(7200);
session_start();
$myDate = date('Y-m-d');
$chart = "Combo-$myDate.png";
@@ -22,7 +25,9 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
header("refresh: 0;");
}
$result4 = $statement4->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
+5 -7
View File
@@ -1,7 +1,5 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
error_reporting(E_ERROR);
$db = new SQLite3('./scripts/birds.db', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
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="<?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">
<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 <?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>
</form>
</div>
@@ -230,10 +228,10 @@ if(isset($_GET['species'])){ ?>
<input type="hidden" name="view" value="Recordings">
<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">
<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 <?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>
</form>
</div>
+33 -26
View File
@@ -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=('<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))+
' 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=('<b>Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'</b>',
specs= [[{"type":"xy","rowspan":1},{"type":"xy","rowspan":1}]],
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))+
# ' 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)
+1 -2
View File
@@ -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
+27
View File
@@ -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=' ')
-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 enable --now spectrogram_viewer.service">Enable</button>
</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">
<button type="submit" name="submit" value="stop_core_services.sh">Stop Core Services</button>
</form>
+84 -9
View File
@@ -1,19 +1,92 @@
<script>
<script>
// CREDITS: https://codepen.io/jakealbaugh/pen/jvQweW
// UPDATE: there is a problem in chrome with starting audio context
// before a user gesture. This fixes it.
var started = null;
var player = null;
const ctx = null;
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;
started = true;
initialize();
started = true;
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() {
document.body.querySelector('h1').remove();
const CVS = document.body.querySelector('canvas');
const CTX = CVS.getContext('2d');
CTX = CVS.getContext('2d');
const W = CVS.width = window.innerWidth;
const H = CVS.height = window.innerHeight;
@@ -25,12 +98,12 @@ function initialize() {
process();
function process() {
const SOURCE = ACTX.createMediaElementSource(document.getElementById('player'));
const SOURCE = ACTX.createMediaElementSource(player);
SOURCE.connect(ANALYSER);
SOURCE.connect(ACTX.destination)
const DATA = new Uint8Array(ANALYSER.frequencyBinCount);
const LEN = DATA.length;
const h = H / LEN;
const h = (H / LEN + 0.9);
const x = W - 1;
CTX.fillStyle = 'hsl(280, 100%, 10%)';
CTX.fillRect(0, 0, W, H);
@@ -40,11 +113,12 @@ function initialize() {
function loop() {
window.requestAnimationFrame(loop);
let imgData = CTX.getImageData(1, 0, W - 1, H);
CTX.fillRect(0, 0, W, H);
CTX.putImageData(imgData, 0, 0);
ANALYSER.getByteFrequencyData(DATA);
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 sat = '100%';
let lit = 10 + (70 * rat) + '%';
@@ -79,7 +153,8 @@ h1 {
}
</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>
<canvas></canvas>
+23 -21
View File
@@ -56,17 +56,9 @@ if(isset($_GET['species'])){
$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd");
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
$home = trim($home);
if(!file_exists($home."/BirdNET-Pi/scripts/disk_check_exclude.txt")) {
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "##start\n##end");
}
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 ) );
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", "");
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "##start\n##end\n");
}
?>
@@ -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="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">
<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 <?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>
</form>
</div>
@@ -177,23 +169,31 @@ while($results=$result3->fetchArray(SQLITE3_ASSOC)){
ob_flush();
flush();
if (file_exists('./scripts/thisrun.txt')) {
$config = parse_ini_file('./scripts/thisrun.txt');
$config = parse_ini_file('./scripts/thisrun.txt');
} elseif (file_exists('./scripts/firstrun.ini')) {
$config = parse_ini_file('./scripts/firstrun.ini');
$config = parse_ini_file('./scripts/firstrun.ini');
}
$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"];
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"];
foreach ($flickrjson as $val) {
foreach ($flickrjson as $val) {
$iter++;
$modaltext = "https://flickr.com/photos/".$val["owner"]."/".$val["id"];
$authorlink = "https://flickr.com/people/".$val["owner"];
$imageurl = 'http://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>";
$imageurl = 'https://farm' .$val["farm"]. '.static.flickr.com/' .$val["server"]. '/' .$val["id"]. '_' .$val["secret"]. '.jpg';
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>
<?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>
</tr>
<?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>
</form>
+23 -9
View File
@@ -1,4 +1,7 @@
<?php
ini_set('session.gc_maxlifetime', 7200);
session_set_cookie_params(7200);
session_start();
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
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');
} else {
// 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){
echo "Database is busy";
@@ -77,7 +80,10 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
?> <table>
<?php
$images = [];
if(!isset($_SESSION['images'])) {
$_SESSION['images'] = [];
}
$iterations = 0;
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 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&media=photos&format=json&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];
array_push($_SESSION['images'], array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink));
$image = $_SESSION['images'][count($_SESSION['images'])-1];
}
}
?>
@@ -241,7 +247,11 @@ document.getElementById("searchterm").onkeydown = (function(e) {
});
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") {
element.innerHTML = "Normal view";
loadDetections(undefined);
@@ -253,7 +263,11 @@ function switchViews(element) {
function searchDetections(searchvalue) {
document.getElementById("detections_table").innerHTML = "<h3>Loading...</h3>";
searchterm = searchvalue;
loadDetections(40,undefined);
if(document.getElementsByClassName('legacyview')[0].innerHTML == "Normal view") {
loadDetections(undefined,undefined);
} else {
loadDetections(40,undefined);
}
}
function loadDetections(detections_limit, element=undefined) {
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);
} else {
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
fi
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
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
@@ -58,3 +61,8 @@ fi
if ! grep FLICKR_API_KEY /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "FLICKR_API_KEY=" >> /etc/birdnet/birdnet.conf
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
*/5 * * * * $USER /usr/local/bin/disk_check.sh &> /dev/null
*/5 * * * * $USER /usr/local/bin/disk_check.sh >/dev/null 2>&1
#birdnet
*/3 * * * * $USER /usr/local/bin/cleanup.sh &> /dev/null
*/3 * * * * $USER /usr/local/bin/cleanup.sh >/dev/null 2>&1
#birdnet
@reboot $USER /usr/local/bin/cleanup.sh &> /dev/null
@reboot $USER /usr/local/bin/cleanup.sh >/dev/null 2>&1