Merge pull request #307 from mcguirepr89/ui-tweaks

UI tweaks
This commit is contained in:
Patrick McGuire
2022-05-24 11:57:39 -04:00
committed by GitHub
26 changed files with 996 additions and 195 deletions
+6 -1
View File
@@ -60,7 +60,12 @@ 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 -----------------------#
FLICKR_API_KEY=
################################################################################
#-------------------------------- Defaults ----------------------------------#
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+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

+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'])) {
Binary file not shown.
+116 -12
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;
}
@@ -64,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 {
@@ -132,6 +143,13 @@ button:hover {
margin-left: auto;
margin-right: auto;
margin-bottom: 15px;
box-shadow: 0px 0px 28px 1px rgba(0, 0, 0, 0.10); !important;
border-radius: 4px;
}
.topimage {
width:175px;
display:initial !important;
}
.topnav form {
@@ -173,12 +191,11 @@ button:hover {
}
.stats table {
width: 75%;
width: 60%;
}
.overview th {
background-color: rgb(219, 255, 235);
border: 2px solid black;
text-align: center;
padding: 12px;
}
@@ -238,14 +255,16 @@ button:hover {
}
.views .centered button {
border: 2px solid black;
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 {
@@ -257,7 +276,6 @@ button:hover {
}
.settings button {
border: 2px solid black;
background-color: rgb(219, 255, 235);
padding: 12px;
}
@@ -284,7 +302,7 @@ button:hover {
.customlabels button,.customlabels2 button {
padding: 12px;
background-color: rgb(219, 255, 235);
border: 2px solid black;
width: 100%;
}
@@ -339,7 +357,7 @@ button:hover {
}
.systemcontrols button,.servicecontrols button {
border: 2px solid black;
background-color: rgb(219, 255, 235);
padding: 12px;
width: 50%;
@@ -541,7 +559,7 @@ button:hover {
font-size:x-large;
background:#dbffeb;
padding:5px;
border: 2px solid black;
box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.10);
}
button.legacyview {
color:gray;
@@ -552,12 +570,98 @@ button.legacyview {
font-size:small;
background:#dbffeb;
padding:5px;
border: 2px solid black;
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;
border: 2px solid black;
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;
}
.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;
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);
}
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 {
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;
right:110%;
top:0px;
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;
}
+110 -42
View File
@@ -37,16 +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");
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">
@@ -59,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"){
@@ -80,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');
@@ -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');
@@ -185,25 +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)){
$results = shell_exec("$command 2>&1");
echo "<pre>$results</pre>";
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) {
$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>
+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"
+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/ .*\[.*\]: /---/"
+16 -6
View File
@@ -10,15 +10,16 @@ $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 {
$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
@@ -45,6 +46,8 @@ $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);
$contents2 = file_get_contents("./scripts/thisrun.txt");
@@ -54,6 +57,9 @@ $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);
$fh = fopen("/etc/birdnet/birdnet.conf", "w");
$fh2 = fopen("./scripts/thisrun.txt", "w");
@@ -76,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`;
}
}
@@ -140,10 +146,14 @@ 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>
<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">
+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 = [userDir+'/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()\" >";
+6 -1
View File
@@ -73,7 +73,12 @@ 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 -----------------------#
FLICKR_API_KEY=
################################################################################
#-------------------------------- Defaults ----------------------------------#
-17
View File
@@ -91,22 +91,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}
@@ -436,7 +420,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
+65 -13
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";
@@ -8,6 +11,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 +25,10 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
header("refresh: 0;");
}
$result4 = $statement4->execute();
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)) {
$comname = preg_replace('/ /', '_', $mostrecent['Com_Name']);
@@ -37,6 +50,23 @@ 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($_SESSION['images'], 0));
if($key !== false) {
$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 = '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];
}
}
?>
<style>
.fade-in {
@@ -60,13 +90,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 +187,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)) {
@@ -192,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
+187 -35
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){
@@ -9,18 +7,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 +121,6 @@ if(isset($_GET['byfilename'])){
session_unset();
$view = "choose";
}
?>
<html>
@@ -90,6 +131,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'])){
@@ -102,16 +168,15 @@ 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>
<?php } ?>
<table>
<tr>
<form action="" method="GET">
@@ -121,13 +186,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 +202,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 +221,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" 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" title="Sort by confidence" 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 +266,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 +276,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 +332,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>
+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 20
sleep 45
done
sudo systemctl restart birdnet_analysis.service
+32 -5
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=' ')
@@ -261,7 +288,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 +362,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 +490,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()
-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>
+157 -13
View File
@@ -1,16 +1,160 @@
<?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
// 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();
}
};
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(){
document.getElementById("spectrogramimage").src = "/spectrogram.png?nocache="+Date.now();
}, <?php echo $refresh; ?>*1000);
loadDetectionIfNewExists();
}, 2500);
function initialize() {
document.body.querySelector('h1').remove();
const CVS = document.body.querySelector('canvas');
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(player);
SOURCE.connect(ANALYSER);
SOURCE.connect(ACTX.destination)
const DATA = new Uint8Array(ANALYSER.frequencyBinCount);
const LEN = DATA.length;
const h = (H / LEN + 0.9);
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>
<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>
+63 -21
View File
@@ -56,7 +56,10 @@ 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") || 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");
}
?>
<html lang="en">
@@ -78,10 +81,10 @@ file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "");
<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>
@@ -106,17 +109,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,47 +162,63 @@ 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\">";
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 (! 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) {
$iter++;
$modaltext = "https://flickr.com/photos/".$val["owner"]."/".$val["id"];
$authorlink = "https://flickr.com/people/".$val["owner"];
$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
$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
}
$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>
+75 -8
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,18 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
?> <table>
<?php
if(!isset($_SESSION['images'])) {
$_SESSION['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 +100,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($_SESSION['images'], 0));
if($key !== false) {
$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($_SESSION['images'], array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink));
$image = $_SESSION['images'][count($_SESSION['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 +136,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 +168,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 +217,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>
@@ -188,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);
@@ -200,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();
@@ -214,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);
+13 -1
View File
@@ -30,7 +30,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
@@ -49,3 +52,12 @@ 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
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