Merge pull request #5 from mcguirepr89/ui-tweaks

UI tweaks
This commit is contained in:
Jake Herbst
2022-05-20 09:00:16 -04:00
committed by GitHub
23 changed files with 783 additions and 136 deletions
+1
View File
@@ -22,6 +22,7 @@ Check out birds from around the world
- [Norman, Oklahoma, United States](https://normanok.birdnetpi.com)<br>
- [Nijmegen, the Netherlands](https://nijmegen.birdnetpi.com)<br>
- [Murrysville, Pennsylvania, United States](https://murrysvillepa.birdnetpi.com/)
- [Berowra, New South Wales, Australia](https://berowra.birdnetpi.com/)
[Share your installation!!](https://github.com/mcguirepr89/BirdNET-Pi/wiki/Sharing-Your-BirdNET-Pi)
+4
View File
@@ -62,6 +62,10 @@ 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
#---------------------- Flickr Images API Configuration -----------------------#
FLICKR_API_KEY=
################################################################################
#-------------------------------- Defaults ----------------------------------#
################################################################################
+42
View File
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
<g id="XMLID_504_">
<path id="XMLID_505_" d="M65,330h200c8.284,0,15-6.716,15-15V145c0-8.284-6.716-15-15-15h-15V85c0-46.869-38.131-85-85-85
S80,38.131,80,85v45H65c-8.284,0-15,6.716-15,15v170C50,323.284,56.716,330,65,330z M207.481,219.356l-42.5,42.5
c-2.929,2.929-6.768,4.394-10.606,4.394s-7.678-1.465-10.606-4.394l-21.25-21.25c-5.858-5.858-5.858-15.354,0-21.213
c5.857-5.858,15.355-5.858,21.213,0l10.644,10.643l31.894-31.893c5.857-5.858,15.355-5.858,21.213,0
C213.34,204.002,213.34,213.498,207.481,219.356z M110,85c0-30.327,24.673-55,55-55s55,24.673,55,55v45H110V85z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 466.008 466.008" style="enable-background:new 0 0 466.008 466.008;" xml:space="preserve">
<g>
<g>
<path d="M233.004,0C104.224,0,0,104.212,0,233.004c0,128.781,104.212,233.004,233.004,233.004
c128.782,0,233.004-104.212,233.004-233.004C466.008,104.222,361.796,0,233.004,0z M244.484,242.659l-63.512,75.511
c-5.333,6.34-14.797,7.156-21.135,1.824c-6.34-5.333-7.157-14.795-1.824-21.135l59.991-71.325V58.028c0-8.284,6.716-15,15-15
s15,6.716,15,15v174.976h0C248.004,236.536,246.757,239.956,244.484,242.659z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 946 B

+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
<g id="XMLID_516_">
<path id="XMLID_517_" d="M15,160c8.284,0,15-6.716,15-15V85c0-30.327,24.673-55,55-55c30.327,0,55,24.673,55,55v45h-25
c-8.284,0-15,6.716-15,15v170c0,8.284,6.716,15,15,15h200c8.284,0,15-6.716,15-15V145c0-8.284-6.716-15-15-15H170V85
c0-46.869-38.131-85-85-85S0,38.131,0,85v60C0,153.284,6.716,160,15,160z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.
+69 -11
View File
@@ -1,6 +1,10 @@
@font-face {
font-family: 'Roboto Flex' ;
src: url('static/RobotoFlex-Regular.ttf') format('truetype');
}
* {
font-family: 'Arial', 'Gill Sans', 'Gill Sans MT',
' Calibri', 'Trebuchet MS', 'sans-serif';
font-family: 'Roboto Flex', sans-serif;
box-sizing: border-box;
font-size: medium;
}
@@ -37,6 +41,9 @@ table {
border-spacing: 0;
margin-left: auto;
margin-right: auto;
box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10);
border-radius:3px;
overflow: hidden;
}
td {
@@ -44,7 +51,6 @@ td {
vertical-align: top;
background-color: rgb(219, 255, 235);
font-weight: lighter;
border: 2px solid black;
text-align: center;
}
@@ -132,6 +138,8 @@ button:hover {
margin-left: auto;
margin-right: auto;
margin-bottom: 15px;
box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10); !important;
border-radius: 4px;
}
.topnav form {
@@ -178,7 +186,6 @@ button:hover {
.overview th {
background-color: rgb(219, 255, 235);
border: 2px solid black;
text-align: center;
padding: 12px;
}
@@ -238,7 +245,6 @@ button:hover {
}
.views .centered button {
border: 2px solid black;
background-color: rgb(219, 255, 235);
padding: 12px;
}
@@ -257,7 +263,6 @@ button:hover {
}
.settings button {
border: 2px solid black;
background-color: rgb(219, 255, 235);
padding: 12px;
}
@@ -284,7 +289,7 @@ button:hover {
.customlabels button,.customlabels2 button {
padding: 12px;
background-color: rgb(219, 255, 235);
border: 2px solid black;
width: 100%;
}
@@ -339,7 +344,7 @@ button:hover {
}
.systemcontrols button,.servicecontrols button {
border: 2px solid black;
background-color: rgb(219, 255, 235);
padding: 12px;
width: 50%;
@@ -541,7 +546,7 @@ button:hover {
font-size:x-large;
background:#dbffeb;
padding:5px;
border: 2px solid black;
}
button.legacyview {
color:gray;
@@ -552,12 +557,65 @@ button.legacyview {
font-size:small;
background:#dbffeb;
padding:5px;
border: 2px solid black;
}
button.loadmore {
margin-top:10px;
font-size:x-large;
background:#dbffeb;
padding:10px;
border: 2px solid black;
}
tr {
background-color:#9fe29b;
}
.history.centered form {
display:flex;
justify-content: center;
}
.history.centered input {
margin-right:5px;
border:0px;
}
.centered form#views button {
box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.10);
margin:2px;
}
input {
box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10);
}
dialog {
border:none;
}
dialog::backdrop {
background: repeating-linear-gradient(
30deg,
rgba(24, 194, 236, 0.2),
rgba(24, 194, 236, 0.2) 1px,
rgba(24, 194, 236, 0.3) 1px,
rgba(24, 194, 236, 0.3) 20px
);
backdrop-filter: blur(1px)
}
.centered_image_container {
font-size:19px !important;
display:inline-block;
position:relative;
margin-bottom:3px;
}
.centered_image_container img.img1 {
cursor:pointer;
height:95%;
position:absolute;
right:110%;
top:0px;
border-radius: 5px;
width:unset;
}
.centered_image_container * {
font-size:19px !important;
}
.centered_image_container form {
margin-bottom:0px;
}
+28 -6
View File
@@ -47,6 +47,17 @@ body::-webkit-scrollbar {
} 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">
@@ -80,7 +91,7 @@ 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\">
<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>
@@ -90,8 +101,8 @@ if(isset($_GET['view'])){
<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>
</div>";
} else {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
@@ -175,7 +186,7 @@ if(isset($_GET['view'])){
$submitteduser = $_SERVER['PHP_AUTH_USER'];
if($submittedpwd == $caddypwd && $submitteduser == 'birdnet'){
#ACCESS THE WEB TERMINAL
echo "<iframe src='/terminal'></iframe>";
echo "<iframe src='/terminal'></iframe>";
} else {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
@@ -201,9 +212,20 @@ if(isset($_GET['view'])){
$submitteduser = $_SERVER['PHP_AUTH_USER'];
if($submittedpwd == $caddypwd && $submitteduser == 'birdnet'){
$command = $_GET['submit'];
if(isset($command)){
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");
echo "<pre>$results</pre>";
$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>";
} else {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
+1 -1
View File
@@ -204,7 +204,7 @@ if (file_exists('./scripts/thisrun.txt')) {
<input name="full_disk" type="radio" id="purge" value="purge" <?php if (strcmp($newconfig['FULL_DISK'], "purge") == 0) { echo "checked"; }?>>Purge</label>
<label for="keep">
<input name="full_disk" type="radio" id="keep" value="keep" <?php if (strcmp($newconfig['FULL_DISK'], "keep") == 0) { echo "checked"; }?>>Keep</label>
<p>When the disk becomes full, you can choose to 'purge' old files to make room for new ones or 'keep' your data and stop all services instead.</p>
<p>When the disk becomes full, you can choose to 'purge' old files to make room for new ones or 'keep' your data and stop all services instead.<br>Note: you can exclude specific files from 'purge' on the Recordings page.</p>
<label for="rec_card">Audio Card: </label>
<input name="rec_card" type="text" value="<?php print($newconfig['REC_CARD']);?>" required/><br>
<p>Set Audio Card to 'default' to use PulseAudio (always recommended), or an ALSA recognized sound card device from the output of `aplay -L`.</p>
+1 -1
View File
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
journalctl --no-hostname -q -o short -fu birdnet_analysis -ubirdnet_server -uextraction | sed "s/$(date "+%b %d ")//g;s/${HOME//\//\\/}\///g"
journalctl --no-hostname -q -o short -fu birdnet_analysis -ubirdnet_server -uextraction | sed "s/$(date "+%b %d ")//g;s/${HOME//\//\\/}\///g;/Line/d;/systemd/d;s/ .*\[.*\]: /---/"
+9 -1
View File
@@ -10,6 +10,7 @@ $birdweather_id = $_GET["birdweather_id"];
$apprise_input = $_GET['apprise_input'];
$apprise_notification_title = $_GET['apprise_notification_title'];
$apprise_notification_body = $_GET['apprise_notification_body'];
$flickr_api_key = $_GET['flickr_api_key'];
if(isset($_GET['apprise_notify_each_detection'])) {
$apprise_notify_each_detection = 1;
} else {
@@ -45,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("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents);
$contents2 = file_get_contents("./scripts/thisrun.txt");
@@ -54,6 +56,8 @@ $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("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents2);
$fh = fopen("/etc/birdnet/birdnet.conf", "w");
$fh2 = fopen("./scripts/thisrun.txt", "w");
@@ -144,6 +148,10 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}
<label for="apprise_notify_each_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>
<label for="flickr_api_key">Flickr API Key: </label>
<input name="flickr_api_key" type="text" value="<?php print($config['FLICKR_API_KEY']);?>"/><br>
<p>Set your Flickr API key to enable the display of bird images next to detections. <a target="_blank" href="https://www.flickr.com/services/api/misc.api_keys.html">Get your free key here.</a></p>
<h3>Localization</h3>
<label for="language">Database Language: </label>
<select name="language">
@@ -206,7 +214,7 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}
?>
<label for="appt">Select a Date and Time:</label><br>
<span>If connected to the internet, retrieve time automatically?</span>
<input type="checkbox" onchange='handleChange(this)' name="apprise_notify_each_detection" <?php echo $checkedvalue; ?> ><br>
<input type="checkbox" onchange='handleChange(this)' <?php echo $checkedvalue; ?> ><br>
<input onclick="this.showPicker()" type="date" id="date" name="date" value="<?php echo date('Y-m-d') ?>" <?php echo $disabledvalue; ?>>
<input onclick="this.showPicker()" type="time" id="time" name="time" value="<?php echo date('H:i') ?>" <?php echo $disabledvalue; ?>>
<br><br><br>
+10
View File
@@ -7,6 +7,8 @@ import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
from datetime import datetime
import textwrap
import matplotlib.font_manager as font_manager
from matplotlib import rcParams
userDir = os.path.expanduser('~')
conn = sqlite3.connect(userDir + '/BirdNET-Pi/scripts/birds.db')
@@ -29,6 +31,14 @@ df['Hour of Day'] = [r.hour for r in df.Time]
#Create separate dataframes for separate locations
df_plt=df #Default to use the whole Dbase
# Add every font at the specified location
font_dir = ['$HOME/BirdNET-Pi/homepage/static']
for font in font_manager.findSystemFonts(font_dir):
font_manager.fontManager.addfont(font)
# Set font family globally
rcParams['font.family'] = 'Roboto Flex'
#Get todays readings
now = datetime.now()
df_plt_today = df_plt[df_plt['Date']==now.strftime("%Y-%m-%d")]
+1
View File
@@ -37,6 +37,7 @@ $totalcount = $result1->fetchArray(SQLITE3_ASSOC);
<td><?php echo $totalcount['COUNT(*)'];?></td>
</tr>
</table>
<br><br>
<?php
if (file_exists('./Charts/'.$chart)) {
echo "<img src=\"/Charts/$chart?nocache=time()\" >";
+4
View File
@@ -75,6 +75,10 @@ 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
#---------------------- Flickr Images API Configuration -----------------------#
FLICKR_API_KEY=
################################################################################
#-------------------------------- Defaults ----------------------------------#
################################################################################
+58 -12
View File
@@ -8,6 +8,12 @@ if($db == False) {
header("refresh: 0;");
}
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');
}
if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isset($_GET['previous_detection_identifier'])) {
$statement4 = $db->prepare('SELECT Com_Name, Sci_Name, Date, Time, Confidence, File_Name FROM detections ORDER BY Date DESC, Time DESC LIMIT 5');
@@ -16,6 +22,8 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
header("refresh: 0;");
}
$result4 = $statement4->execute();
$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)) {
$comname = preg_replace('/ /', '_', $mostrecent['Com_Name']);
@@ -37,6 +45,22 @@ 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 (!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));
if($key !== false) {
$image = $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];
}
}
?>
<style>
.fade-in {
@@ -60,13 +84,17 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
<h3>Most Recent Detection: <span style="font-weight: normal;"><?php echo $mostrecent['Date']." ".$mostrecent['Time'];?></span></h3>
<tr>
<td class="relative"><a target="_blank" href="index.php?filename=<?php echo $mostrecent['File_Name']; ?>"><img class="copyimage" width="25" height="25" src="images/copy.png"></a>
<form action="" method="GET">
<input type="hidden" name="view" value="Species Stats">
<button type="submit" name="species" value="<?php echo $mostrecent['Com_Name'];?>"><?php echo $mostrecent['Com_Name'];?></button></br>
<a href="https://wikipedia.org/wiki/<?php echo $sciname;?>" target="_blank"/><i><?php echo $mostrecent['Sci_Name'];?></i></a>
<br>Confidence: <?php echo $mostrecent['Confidence'];?><br>
<video style="margin-top:10px" 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;?>"></video></td>
</form>
<div class="centered_image_container" style="margin-bottom: 0px !important;">
<?php if(!empty($config["FLICKR_API_KEY"])) { ?>
<img onclick='setModalText(<?php echo $iterations; ?>,"<?php echo urlencode($image[2]); ?>", "<?php echo $image[3]; ?>", "<?php echo $image[4]; ?>", "<?php echo $image[1]; ?>")' src="<?php echo $image[1]; ?>" class="img1">
<?php } ?>
<form action="" method="GET">
<input type="hidden" name="view" value="Species Stats">
<button type="submit" name="species" value="<?php echo $mostrecent['Com_Name'];?>"><?php echo $mostrecent['Com_Name'];?></button></br>
<a href="https://wikipedia.org/wiki/<?php echo $sciname;?>" target="_blank"/><i><?php echo $mostrecent['Sci_Name'];?></i></a>
<br>Confidence: <?php echo $percent = round((float)round($mostrecent['Confidence'],2) * 100 ) . '%';?><br></div><br>
<video style="margin-top:10px" 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;?>"></video></td>
</form>
</tr>
</table> <?php break;
}
@@ -153,17 +181,35 @@ body::-webkit-scrollbar {
</style>
</head>
<div class="overview">
<dialog id="attribution-dialog">
<h1 id="modalHeading"></h1>
<p id="modalText"></p>
<button onclick="hideDialog()">Close</button>
</dialog>
<script>
var dialog = document.querySelector('dialog');
dialogPolyfill.registerDialog(dialog);
function showDialog() {
document.getElementById('attribution-dialog').showModal();
}
function hideDialog() {
document.getElementById('attribution-dialog').close();
}
function setModalText(iter, title, text, authorlink, photolink) {
document.getElementById('modalHeading').innerHTML = "Photo: \""+decodeURIComponent(title.replace("+"," "))+"\" Attribution";
document.getElementById('modalText').innerHTML = "<div><img style='border-radius:5px' src='"+photolink+"'></div><br><div>Image link: <a target='_blank' href="+text+">"+text+"</a><br>Author link: <a target='_blank' href="+authorlink+">"+authorlink+"</a></div>";
showDialog();
}
</script>
<div class="overview-stats">
<div class="left-column">
</div>
<div class="right-column">
<div class="chart">
<?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();
if (file_exists('./Charts/'.$chart)) {
+184 -30
View File
@@ -9,18 +9,62 @@ if($db == False){
header("refresh: 0;");
}
#By Date
if(isset($_GET['byfilename'])){
$statement = $db->prepare('SELECT DISTINCT(Date) FROM detections GROUP BY Date');
if($statement == False){
echo "Database is busy";
header("refresh: 0;");
if (file_exists('./scripts/thisrun.txt')) {
$config = parse_ini_file('./scripts/thisrun.txt');
} elseif (file_exists('firstrun.ini')) {
$config = parse_ini_file('firstrun.ini');
}
$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd");
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
$home = trim($home);
if(isset($_GET['excludefile'])) {
if(isset($_SERVER['PHP_AUTH_USER'])) {
$submittedpwd = $_SERVER['PHP_AUTH_PW'];
$submitteduser = $_SERVER['PHP_AUTH_USER'];
if($submittedpwd == $config['CADDY_PWD'] && $submitteduser == 'birdnet'){
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");
}
if(isset($_GET['exclude_add'])) {
$myfile = fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "a") or die("Unable to open file!");
$txt = $_GET['excludefile'];
fwrite($myfile, $txt."\n");
fwrite($myfile, $txt.".png\n");
fclose($myfile);
echo "OK";
die();
} else {
$lines = file($home."/BirdNET-Pi/scripts/disk_check_exclude.txt");
$search = $_GET['excludefile'];
$result = '';
foreach($lines as $line) {
if(stripos($line, $search) === false && stripos($line, $search.".png") === false) {
$result .= $line;
}
}
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", $result);
echo "OK";
die();
}
} else {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo 'You must be authenticated to change the protection of files.';
exit;
}
} else {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo 'You must be authenticated to change the protection of files.';
exit;
}
$result = $statement->execute();
$view = "bydate";
#By Date
}elseif(isset($_GET['bydate'])){
$statement = $db->prepare('SELECT DISTINCT(Date) FROM detections GROUP BY Date');
}
if(isset($_GET['bydate'])){
$statement = $db->prepare('SELECT DISTINCT(Date) FROM detections GROUP BY Date ORDER BY Date DESC');
if($statement == False){
echo "Database is busy";
header("refresh: 0;");
@@ -79,7 +123,6 @@ if(isset($_GET['byfilename'])){
session_unset();
$view = "choose";
}
?>
<html>
@@ -90,6 +133,31 @@ if(isset($_GET['byfilename'])){
</style>
</head>
<script>
function toggleLock(filename, type, elem) {
const xhttp = new XMLHttpRequest();
xhttp.onload = function() {
if(this.responseText == "OK"){
if(type == "add") {
elem.setAttribute("src","images/lock.svg");
elem.setAttribute("title", "This file is delete protected.");
elem.setAttribute("onclick", elem.getAttribute("onclick").replace("add","del"));
} else {
elem.setAttribute("src","images/unlock.svg");
elem.setAttribute("title", "This file is not delete protected.");
elem.setAttribute("onclick", elem.getAttribute("onclick").replace("del","add"));
}
}
}
if(type == "add") {
xhttp.open("GET", "play.php?excludefile="+filename+"&exclude_add=true", true);
} else {
xhttp.open("GET", "play.php?excludefile="+filename+"&exclude_del=true", true);
}
xhttp.send();
}
</script>
<?php
#If no specific species
if(!isset($_GET['species']) && !isset($_GET['filename'])){
@@ -111,7 +179,6 @@ if(!isset($_GET['species']) && !isset($_GET['filename'])){
</div>
<?php } ?>
<table>
<tr>
<form action="" method="GET">
@@ -121,13 +188,15 @@ if(!isset($_GET['species']) && !isset($_GET['filename'])){
if($view == "bydate") {
while($results=$result->fetchArray(SQLITE3_ASSOC)){
$date = $results['Date'];
if(realpath($home."/BirdSongs/Extracted/By_Date/".$date) !== false){
echo "<td>
<button action=\"submit\" name=\"date\" value=\"$date\">$date</button></td></tr>";}
<button action=\"submit\" name=\"date\" value=\"$date\">$date</button></td></tr>";}}
#By Species
} elseif($view == "byspecies") {
while($results=$result->fetchArray(SQLITE3_ASSOC)){
$name = $results['Com_Name'];
echo "<td>
<button action=\"submit\" name=\"species\" value=\"$name\">$name</button></td></tr>";}
@@ -135,8 +204,11 @@ if(!isset($_GET['species']) && !isset($_GET['filename'])){
} elseif($view == "date") {
while($results=$result->fetchArray(SQLITE3_ASSOC)){
$name = $results['Com_Name'];
echo "<td>
<button action=\"submit\" name=\"species\" value=\"$name\">$name</button></td></tr>";}
if(realpath($home."/BirdSongs/Extracted/By_Date/".$date."/".str_replace(" ", "_",$name)) !== false){
echo "<td>
<button action=\"submit\" name=\"species\" value=\"$name\">$name</button></td></tr>";
}
}
#Choose
} else {
@@ -151,13 +223,42 @@ if(!isset($_GET['species']) && !isset($_GET['filename'])){
}
#Specific Species
if(isset($_GET['species'])){
if(isset($_GET['species'])){ ?>
<div style="width: auto;
text-align: center">
<form action="" method="GET">
<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">
</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">
</button>
</form>
</div>
<?php
// add disk_check_exclude.txt lines into an array for grepping
$fp = @fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", 'r');
if ($fp) {
$disk_check_exclude_arr = explode("\n", fread($fp, filesize($home."/BirdNET-Pi/scripts/disk_check_exclude.txt")));
}
$name = $_GET['species'];
if(isset($_SESSION['date'])) {
$date = $_SESSION['date'];
$statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" AND Date == \"$date\" ORDER BY Time DESC");
if(isset($_GET['sort']) && $_GET['sort'] == "confidence") {
$statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" AND Date == \"$date\" ORDER BY Confidence DESC");
} else {
$statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" AND Date == \"$date\" ORDER BY Time DESC");
}
} else {
$statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" ORDER BY Date DESC, Time DESC");}
if(isset($_GET['sort']) && $_GET['sort'] == "confidence") {
$statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" ORDER BY Confidence DESC");
} else {
$statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" ORDER BY Date DESC, Time DESC");
}
}
if($statement2 == False){
echo "Database is busy";
header("refresh: 0;");
@@ -167,6 +268,7 @@ if(isset($_GET['species'])){
<tr>
<th>$name</th>
</tr>";
$iter=0;
while($results=$result2->fetchArray(SQLITE3_ASSOC))
{
$comname = preg_replace('/ /', '_', $results['Com_Name']);
@@ -176,13 +278,40 @@ if(isset($_GET['species'])){
$sciname = preg_replace('/ /', '_', $results['Sci_Name']);
$sci_name = $results['Sci_Name'];
$time = $results['Time'];
$confidence = $results['Confidence'];
echo "<tr>
<td class=\"relative\"><a target=\"_blank\" href=\"index.php?filename=".$results['File_Name']."\"><img class=\"copyimage\" width=25 src=\"images/copy.png\"></a>$date $time<br>$confidence<br>
<video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster=\"$filename.png\" preload=\"none\" title=\"$filename\"><source src=\"$filename\"></video></td>
</tr>";
$confidence = round((float)round($results['Confidence'],2) * 100 ) . '%';
$filename_formatted = $date."/".$comname."/".$results['File_Name'];
}echo "</table>";}
// file was deleted by disk check, no need to show the detection in recordings
if(!file_exists($home."/BirdSongs/Extracted/".$filename)) {
continue;
}
$iter++;
if($config["FULL_DISK"] == "purge") {
if(!in_array($filename_formatted, $disk_check_exclude_arr)) {
$imageicon = "images/unlock.svg";
$title = "This file is not delete protected.";
$type = "add";
} else {
$imageicon = "images/lock.svg";
$title = "This file is delete protected.";
$type = "del";
}
echo "<tr>
<td class='relative'>$date $time<br>$confidence<br><img style='cursor:pointer' onclick='toggleLock(\"".$filename_formatted."\",\"".$type."\", this)' class=\"copyimage\" width=25 title=\"".$title."\" src=\"".$imageicon."\">
<a href=\"$filename\"><img src=\"$filename.png\"></a>
</td>
</tr>";
} else {
echo "<tr>
<td class='relative'>$date $time<br>$confidence<br>
<a href=\"$filename\"><img src=\"$filename.png\"></a>
</td>
</tr>";
}
}if($iter == 0){ echo "<tr><td><b>No recordings were found.</b><br><br><span style='font-size:small'>They may have been deleted to make space for new recordings. You can modify this setting for the future in Tools -> Settings -> Advanced Settings -> Full Disk Behavior.</small></td></tr>";}echo "</table>";}
if(isset($_GET['filename'])){
$name = $_GET['filename'];
@@ -205,11 +334,36 @@ if(isset($_GET['filename'])){
$sciname = preg_replace('/ /', '_', $results['Sci_Name']);
$sci_name = $results['Sci_Name'];
$time = $results['Time'];
$confidence = $results['Confidence'];
echo "<tr>
<td>$date $time<br>$confidence<br>
<video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster=\"$filename.png\" preload=\"none\" title=\"$filename\"><source src=\"$filename\"></video></td>
</tr>";
$confidence = round((float)round($results['Confidence'],2) * 100 ) . '%';
$filename_formatted = $date."/".$comname."/".$results['File_Name'];
// add disk_check_exclude.txt lines into an array for grepping
$fp = @fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", 'r');
if ($fp) {
$disk_check_exclude_arr = explode("\n", fread($fp, filesize($home."/BirdNET-Pi/scripts/disk_check_exclude.txt")));
}
if($config["FULL_DISK"] == "purge") {
if(!in_array($filename_formatted, $disk_check_exclude_arr)) {
$imageicon = "images/unlock.svg";
$title = "This file is not delete protected.";
$type = "add";
} else {
$imageicon = "images/lock.svg";
$title = "This file is delete protected.";
$type = "del";
}
echo "<tr>
<td class=\"relative\"><img style='cursor:pointer' onclick='toggleLock(\"".$filename_formatted."\",\"".$type."\", this)' class=\"copyimage\" width=25 title=\"".$title."\" src=\"".$imageicon."\">$date $time<br>$confidence<br>
<video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster=\"$filename.png\" preload=\"none\" title=\"$filename\"><source src=\"$filename\"></video></td>
</tr>";
} else {
echo "<tr>
<td class=\"relative\">$date $time<br>$confidence<br>
<video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster=\"$filename.png\" preload=\"none\" title=\"$filename\"><source src=\"$filename\"></video></td>
</tr>";
}
}echo "</table>";}?>
</div>
+83 -30
View File
@@ -60,7 +60,8 @@ df2=df2.set_index('DateTime')
# Date as slider
Start_Date = pd.to_datetime(df2.index.min()).date()
End_Date = pd.to_datetime(df2.index.max()).date()
Date_Slider = st.slider('Date Range',
cols1,cols2= st.columns((1,1))
Date_Slider = cols1.slider('Date Range',
min_value = Start_Date-timedelta(days=1),
max_value = End_Date,
value=(Start_Date,
@@ -72,14 +73,29 @@ Date_Slider = st.slider('Date Range',
filt = (df2.index >= pd.Timestamp(Date_Slider[0])) & (df2.index <= pd.Timestamp(Date_Slider[1]+timedelta(days=1)))
df2 = df2[filt]
st.write('<style>div.row-widget.stRadio > div{flex-direction:row;justify-content: left;} </style>', unsafe_allow_html=True)
st.write('<style>div.st-bf{flex-direction:column;} div.st-ag{font-weight:bold;padding-left:2px;}</style>', unsafe_allow_html=True)
resample_sel=cols2.radio("Select Resample Resolution - To downsample and make run faster select longer period, Daily provides a view on detections at 15 min intervals through the day", ('1 minute', '5 minutes', '10 minutes', 'Hourly', 'Daily'))
resample_times = {'1 minute':'1min',
'5 minutes':'5min',
'10 minutes':'10min',
'Hourly':'1H',
'Daily':'1D'
}
resample_time = resample_times[resample_sel]
df5=df2.resample(resample_time)['Com_Name'].aggregate('unique').explode()
#Create species count for selected date range
Specie_Count=df2['Com_Name'].value_counts()
Specie_Count=df5.value_counts()
#Create species treemap
# Create Hourly Crosstab
hourly=pd.crosstab(df2['Com_Name'],df2.index.hour, dropna=False)
hourly=pd.crosstab(df5,df5.index.hour, dropna=False)
# Filter on species
species = list(hourly.index)
@@ -91,7 +107,7 @@ top_N = cols1.slider(
value=min(10,len(Specie_Count))
)
top_N_species = (df2['Com_Name'].value_counts()[:top_N])
top_N_species = (df5.value_counts()[:top_N])
specie = cols2.selectbox('Which bird would you like to explore for the dates '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'?', species,
@@ -104,13 +120,13 @@ font_size=15
#specie filter
filt=df2['Com_Name']==specie
df_counts=df2[filt].resample('D').count()
df_counts=sum(df5==specie)
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>',
'Total Detect:'+str('{:,}'.format(sum(df_counts.Time)))+
'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))
)
@@ -123,43 +139,80 @@ fig.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), r
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)
specie_filt= df5==specie
df3=df5[specie_filt]
detections2= pd.crosstab(df3, df3.index.hour)
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,
width = 1000,
height = 500,
showlegend=False,
polar = dict(
radialaxis = dict(
tickfont_size = font_size,
showticklabels = True,
hoverformat = "#%{theta}: <br>Popularity: %{percent} </br> %{r}"
if resample_time != '1D':
fig.add_trace(go.Barpolar(r = detections, theta=theta), row=1, col=2)
fig.update_layout(
autosize=False,
width = 1000,
height = 500,
showlegend=False,
polar = dict(
radialaxis = dict(
tickfont_size = font_size,
showticklabels = False,
hoverformat = "#%{theta}: <br>Popularity: %{percent} </br> %{r}"
),
angularaxis = dict(
tickfont_size= font_size,
rotation = -90,
direction = 'clockwise',
tickmode='array',
tickvals=[0,15,35,45,60,75,90,105,120,135,150,165,180,195,210,225,240,255,270,285,300,315,330,345],
ticktext=['12am','1am','2am','3am','4am','5am', '6am','7am','8am','9am','10am','11am','12pm','1pm','2pm','3pm','4pm','5pm', '6pm','7pm','8pm','9pm','10pm','11pm'],
hoverformat = "#%{theta}: <br>Popularity: %{percent} </br> %{r}"
),
angularaxis = dict(
tickfont_size= font_size,
rotation = -90,
direction = 'clockwise',
tickmode='array',
tickvals=[0,15,35,45,60,75,90,105,120,135,150,165,180,195,210,225,240,255,270,285,300,315,330,345],
ticktext=['12am','1am','2am','3am','4am','5am', '6am','7am','8am','9am','10am','11am','12pm','1pm','2pm','3pm','4pm','5pm', '6pm','7pm','8pm','9pm','10pm','11pm'],
hoverformat = "#%{theta}: <br>Popularity: %{percent} </br> %{r}"
),
),
)
),
)
daily=pd.crosstab(df2['Com_Name'],df2.index.date, dropna=False)
daily=pd.crosstab(df5,df5.index.date, dropna=False)
fig.add_trace(go.Bar(x=daily.columns, y=daily.loc[specie]), row=3, col=2)
fig.add_trace(go.Bar(x=daily.columns, y=daily.loc[specie]), row=3, col=2)
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>',
# '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))
# )
)
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)
fig_x = [d.strftime('%d-%m-%Y') for d in day_hour_freq.index.tolist()]
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)
# container=st.container()
# config={'displayModelBar': False}
st.plotly_chart(fig, use_container_width=True) #, config=config)
+1 -1
View File
@@ -22,6 +22,6 @@ sudo systemctl restart "${i}"
done
until grep 5050 <(netstat -tulpn 2>&1);do
sudo systemctl restart birdnet_server.service
sleep 20
sleep 30
done
sudo systemctl restart birdnet_analysis.service
+5 -5
View File
@@ -261,7 +261,7 @@ def writeResultsToFile(detections, min_conf, path):
def handle_client(conn, addr):
global INCLUDE_LIST
global EXCLUDE_LIST
print(f"[NEW CONNECTION] {addr} connected.")
#print(f"[NEW CONNECTION] {addr} connected.")
connected = True
while connected:
@@ -335,7 +335,7 @@ def handle_client(conn, addr):
# Get Date/Time from filename in case Pi gets behind
#now = datetime.now()
full_file_name = args.i
print('FULL FILENAME: -' + full_file_name + '-')
#print('FULL FILENAME: -' + full_file_name + '-')
file_name = Path(full_file_name).stem
file_date = file_name.split('-birdnet-')[0]
file_time = file_name.split('-birdnet-')[1]
@@ -463,13 +463,13 @@ def start():
global INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST
INTERPRETER = loadModel()
server.listen()
print(f"[LISTENING] Server is listening on {SERVER}")
#print(f"[LISTENING] Server is listening on {SERVER}")
while True:
conn, addr = server.accept()
thread = threading.Thread(target=handle_client, args=(conn, addr))
thread.start()
print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}")
#print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}")
print("[STARTING] server is starting...")
#print("[STARTING] server is starting...")
start()
+83 -14
View File
@@ -1,16 +1,85 @@
<?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();
echo "<img id=\"spectrogramimage\" style=\"width:100%;height:100%\" src=\"/spectrogram.png?nocache=$time\">";
?>
<script>
// 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);
// 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;
window.onload = function(){
if (started) return;
started = true;
initialize();
};
function initialize() {
document.body.querySelector('h1').remove();
const CVS = document.body.querySelector('canvas');
const CTX = CVS.getContext('2d');
const W = CVS.width = window.innerWidth;
const H = CVS.height = window.innerHeight;
const ACTX = new AudioContext();
const ANALYSER = ACTX.createAnalyser();
ANALYSER.fftSize = 2048;
process();
function process() {
const SOURCE = ACTX.createMediaElementSource(document.getElementById('player'));
SOURCE.connect(ANALYSER);
SOURCE.connect(ACTX.destination)
const DATA = new Uint8Array(ANALYSER.frequencyBinCount);
const LEN = DATA.length;
const h = H / LEN;
const x = W - 1;
CTX.fillStyle = 'hsl(280, 100%, 10%)';
CTX.fillRect(0, 0, W, H);
loop();
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 hue = Math.round((rat * 120) + 280 % 360);
let sat = '100%';
let lit = 10 + (70 * rat) + '%';
CTX.beginPath();
CTX.strokeStyle = `hsl(${hue}, ${sat}, ${lit})`;
CTX.moveTo(x, H - (i * h));
CTX.lineTo(x, H - (i * h + h));
CTX.stroke();
}
}
}
}
</script>
<style>
html, body {
height: 100%;
}
canvas {
display: block;
height: 85%;
width: 100%;
}
h1 {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin: 0;
}
</style>
<audio style="display:none" controls="" crossorigin="anonymous" id='player' autoplay=""><source src="/stream"></audio>
<h1>Loading...</h1>
<canvas></canvas>
+54 -19
View File
@@ -56,7 +56,18 @@ 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);
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "");
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 ) );
}
?>
<html lang="en">
@@ -106,17 +117,40 @@ $filename = "/By_Date/".$results['Date']."/".$comname."/".$results['File_Name'];
</tr>
</table>
</div>
<dialog id="attribution-dialog">
<h1 id="modalHeading"></h1>
<p id="modalText"></p>
<button onclick="hideDialog()">Close</button>
</dialog>
<script>
var dialog = document.querySelector('dialog');
dialogPolyfill.registerDialog(dialog);
function showDialog() {
document.getElementById('attribution-dialog').showModal();
}
function hideDialog() {
document.getElementById('attribution-dialog').close();
}
function setModalText(iter, title, text, authorlink) {
document.getElementById('modalHeading').innerHTML = "Photo "+iter+": \""+title+"\" Attribution";
document.getElementById('modalText').innerHTML = "Image link: <a target='_blank' href="+text+">"+text+"</a><br>Author link: <a target='_blank' href="+authorlink+">"+authorlink+"</a>";
showDialog();
}
</script>
<div class="column center">
<?php if(!isset($_GET['species'])){
?><p class="centered">Choose a species to load images from Wikimedia Commons.</p>
?><p class="centered">Choose a species to load images from Flickr.</p>
<?php
};?>
<?php if(isset($_GET['species'])){
$species = $_GET['species'];
$iter=0;
while($results=$result3->fetchArray(SQLITE3_ASSOC)){
$count = $results['COUNT(*)'];
$maxconf = $results['MAX(Confidence)'];
$maxconf = round((float)round($results['MAX(Confidence)'],2) * 100 ) . '%';
$date = $results['Date'];
$time = $results['Time'];
$name = $results['Com_Name'];
@@ -136,46 +170,47 @@ while($results=$result3->fetchArray(SQLITE3_ASSOC)){
<video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster=\"$filename.png\" title=\"$filename\"><source src=\"$filename\"></video></td>
</tr>
</table>
<p>Loading Images from <a href=\"https://commons.wikimedia.org/w/index.php?search=$linkname&title=Special:MediaSearch&go=Go&type=image\" target=\"_blank\">Wikimedia Commons</a></p>", '6096');
<p>Loading Images from Flickr</p>", '6096');
echo "<script>document.getElementsByTagName(\"h3\")[0].scrollIntoView();</script>";
ob_flush();
flush();
$imagelink = "https://commons.wikimedia.org/w/index.php?search=$linkname&title=Special:MediaSearch&go=Go&type=image";
$homepage = file_get_contents($imagelink);
preg_match_all("{<img\\s*(.*?)src=('.*?'|\".*?\"|[^\\s]+)(.*?)\\s*/?>}ims", $homepage, $matches, PREG_SET_ORDER);
foreach ($matches as $val) {
$pos = strpos($val[2],"/");
$link = substr($val[2],1,-1);
if($pos !== 1 && strpos($link, "upload") == true && strpos($link, "CentralAutoLogin") == false)
echo "<img src=\"$link\">";
$flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=2158f6c5d66e89778bd0d340953f1bcf&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) {
$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>";
}
}}
}
}
?>
<br><br><br>
<table>
<?php
$excludelines = [];
while($results=$result->fetchArray(SQLITE3_ASSOC))
{
$comname = preg_replace('/ /', '_', $results['Com_Name']);
$comname = preg_replace('/\'/', '', $comname);
$filename = "/By_Date/".$results['Date']."/".$comname."/".$results['File_Name'];
$excludefile = fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "a") or die("Unable to open file!");
$txt = $results['Date']."/".$comname."/".$results['File_Name']."\n".$results['Date']."/".$comname."/".$results['File_Name'].".png\n";
fwrite($excludefile, $txt);
array_push($excludelines, $results['Date']."/".$comname."/".$results['File_Name']);
array_push($excludelines, $results['Date']."/".$comname."/".$results['File_Name'].".png");
?>
<tr>
<form action="" method="GET">
<input type="hidden" name="sort" value="<?php if(isset($_GET['sort'])){echo $_GET['sort'];}?>">
<td class="relative"><a target="_blank" href="index.php?filename=<?php echo $results['File_Name']; ?>"><img class="copyimage" width=25 src="images/copy.png"></a><input type="hidden" name="view" value="Species Stats">
<button type="submit" name="species" value="<?php echo $results['Com_Name'];?>"><?php echo $results['Com_Name'];?></button><br><b>Occurrences:</b> <?php echo $results['COUNT(*)'];?><br>
<b>Max Confidence:</b> <?php echo $results['MAX(Confidence)'];?><br>
<b>Max Confidence:</b> <?php echo $percent = round((float)round($results['MAX(Confidence)'],2) * 100 ) . '%';?><br>
<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);
}
?>
</table>
@@ -183,4 +218,4 @@ fwrite($excludefile, $txt);
</div>
</div>
</body>
</html>
</html>
+58 -5
View File
@@ -67,7 +67,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 '.$searchquery);
}
if($statement0 == False){
echo "Database is busy";
@@ -77,7 +77,15 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
?> <table>
<?php
$images = [];
$iterations = 0;
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');
}
while($todaytable=$result0->fetchArray(SQLITE3_ASSOC))
{
$iterations++;
@@ -86,13 +94,35 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
$comname = preg_replace('/\'/', '_', $comname);
$filename = "/By_Date/".date('Y-m-d')."/".$comname."/".$todaytable['File_Name'];
$sciname = preg_replace('/ /', '_', $todaytable['Sci_Name']);
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));
if($key !== false) {
$image = $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];
}
}
?>
<?php if(isset($_GET['display_limit']) && is_numeric($_GET['display_limit'])){ ?>
<tr class="relative" id="<?php echo $iterations; ?>">
<td class="relative"><a target="_blank" href="index.php?filename=<?php echo $todaytable['File_Name']; ?>"><img class="copyimage" width=25 src="images/copy.png"></a><?php echo $todaytable['Time'];?><br>
<td class="relative"><a target="_blank" href="index.php?filename=<?php echo $todaytable['File_Name']; ?>"><img class="copyimage" width=25 src="images/copy.png"></a>
<div class="centered_image_container">
<?php if(!empty($config["FLICKR_API_KEY"])) { ?>
<img onclick='setModalText(<?php echo $iterations; ?>,"<?php echo urlencode($image[2]); ?>", "<?php echo $image[3]; ?>", "<?php echo $image[4]; ?>", "<?php echo $image[1]; ?>")' src="<?php echo $image[1]; ?>" class="img1">
<?php } ?>
<?php echo $todaytable['Time'];?><br>
<b><a class="a2" href="https://allaboutbirds.org/guide/<?php echo $comname;?>" target="top"><?php echo $todaytable['Com_Name'];?></a></b><br>
<a class="a2" href="https://wikipedia.org/wiki/<?php echo $sciname;?>" target="top"><i><?php echo $todaytable['Sci_Name'];?></i></a><br>
<b>Confidence:</b> <?php echo $todaytable['Confidence'];?><br>
<b>Confidence:</b> <?php echo round((float)round($todaytable['Confidence'],2) * 100 ) . '%';?><br></div><br>
<video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster="<?php echo $filename.".png";?>" preload="none" title="<?php echo $filename;?>"><source preload="none" src="<?php echo $filename;?>"></video>
</td>
<?php } else { //legacy mode ?>
@@ -100,7 +130,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
<td><?php echo $todaytable['Time'];?><br></td><td>
<b><a class="a2" href="https://allaboutbirds.org/guide/<?php echo $comname;?>" target="top"><?php echo $todaytable['Com_Name'];?></a></b><br>
<a class="a2" href="https://wikipedia.org/wiki/<?php echo $sciname;?>" target="top"><i><?php echo $todaytable['Sci_Name'];?></i></a><br></td>
<td><b>Confidence:</b> <?php echo $todaytable['Confidence'];?><br></td>
<td><b>Confidence:</b> <?php echo round((float)round($todaytable['Confidence'],2) * 100 ) . '%';?><br></td>
<td style="min-width:180px"><audio controls preload="none" title="<?php echo $filename;?>"><source preload="none" src="<?php echo $filename;?>"></video>
</td>
<?php } ?>
@@ -132,6 +162,29 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
</style>
</head>
<div class="viewdb">
<dialog id="attribution-dialog">
<h1 id="modalHeading"></h1>
<p id="modalText"></p>
<button onclick="hideDialog()">Close</button>
</dialog>
<script>
var dialog = document.querySelector('dialog');
dialogPolyfill.registerDialog(dialog);
function showDialog() {
document.getElementById('attribution-dialog').showModal();
}
function hideDialog() {
document.getElementById('attribution-dialog').close();
}
function setModalText(iter, title, text, authorlink, photolink) {
document.getElementById('modalHeading').innerHTML = "Photo: \""+decodeURIComponent(title.replace("+"," "))+"\" Attribution";
document.getElementById('modalText').innerHTML = "<div><img style='border-radius:5px' src='"+photolink+"'></div><br><div>Image link: <a target='_blank' href="+text+">"+text+"</a><br>Author link: <a target='_blank' href="+authorlink+">"+authorlink+"</a></div>";
showDialog();
}
</script>
<h3>Number of Detections</h3>
<table>
<tr>
@@ -158,7 +211,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
<h3>Today's Detections — <input autocomplete="off" size="11" type="text" placeholder="Search..." id="searchterm" name="searchterm"></h3>
<div style="padding-bottom:10px" id="detections_table"></div>
<div style="padding-bottom:10px" id="detections_table"><h3>Loading...</h3></div>
<button onclick="switchViews(this);" class="legacyview">Legacy view</button>
+4
View File
@@ -54,3 +54,7 @@ if grep bash $HOME/BirdNET-Pi/templates/web_terminal.service;then
sudo systemctl daemon-reload
sudo systemctl restart web_terminal.service
fi
[ -L ~/BirdSongs/Extracted/static ] || ln -sf ~/BirdNET-Pi/homepage/static ~/BirdSongs/Extracted
if ! grep FLICKR_API_KEY /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "FLICKR_API_KEY=" >> /etc/birdnet/birdnet.conf
fi