Merge branch 'mcguirepr89:main' into main

This commit is contained in:
lloydbayley
2023-03-15 16:03:47 +11:00
committed by GitHub
16 changed files with 21516 additions and 95 deletions
+6 -6
View File
@@ -14,7 +14,7 @@ Icon made by <a href="https://www.freepik.com" title="Freepik">Freepik</a> from
</p>
## Introduction
BirdNET-Pi is built on the (deprecated) [TFLite version of BirdNET](https://github.com/kahst/BirdNET-Lite) by [**@kahst**](https://github.com/kahst) <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><img src="https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg"></a> using [pre-built TFLite binaries](https://github.com/PINTO0309/TensorflowLite-bin) by [**@PINTO0309**](https://github.com/PINTO0309) . It is able to recognize bird sounds from a USB microphone or sound card in realtime and share its data with the rest of the world.
BirdNET-Pi is built on the [BirdNET framework](https://github.com/kahst/BirdNET-Analyzer) by [**@kahst**](https://github.com/kahst) <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><img src="https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg"></a> using [pre-built TFLite binaries](https://github.com/PINTO0309/TensorflowLite-bin) by [**@PINTO0309**](https://github.com/PINTO0309) . It is able to recognize bird sounds from a USB microphone or sound card in realtime and share its data with the rest of the world.
Check out birds from around the world
- [BirdWeather](https://app.birdweather.com)<br>
@@ -64,11 +64,11 @@ Currently listening in these countries . . . that I know of . . .
- Brazil
## Features
* 24/7 recording and automatic identification of bird songs using BirdNET machine learning
* Bird sounds are automatically extracted and catalogued
* Visualize your recorded bird data and analyze trends
* Live audio stream & spectrogram
* Automatic disk space management, where old audio files are periodically purged
* **24/7 recording and automatic identification** of bird songs, chirps, and peeps using BirdNET machine learning
* **Automatic extraction and cataloguing** of bird sounds
* **Tools to visualize your recorded bird data** and analyze trends
* **Live audio stream and spectrogram**
* **Automatic disk space management** that periodically purges old audio files to manage disk space
* [BirdWeather](https://app.birdweather.com) integration -- you can request a BirdWeather ID from BirdNET-Pi's "Tools" > "Settings" page
* Web interface access to all data and logs provided by [Caddy](https://caddyserver.com)
* [GoTTY](https://github.com/yudai/gotty) Web Terminal
+12
View File
@@ -195,12 +195,24 @@ DATABASE_LANG=en
SILENCE_UPDATE_INDICATOR=0
## RAW_SPECTROGRAM is for removing the axes and labels of the spectrograms
## that are generated by Sox for each detection for a cleaner appearance.
RAW_SPECTROGRAM=0
## HEARTBEAT_URL is a location to ping every time some analysis is done
## no information is sent to the the URL, it is a heart beat to show that the
## analysis is continuing
HEARTBEAT_URL=
## CUSTOM_IMAGE and CUSTOM_IMAGE_TITLE allow you to show a custom image on the
## Overview page of your BirdNET-Pi. This can be used to show a dynamically
## updating picture of your garden, for example.
CUSTOM_IMAGE=
CUSTOM_IMAGE_TITLE=""
## These are just for debugging
LAST_RUN=
THIS_RUN=
+1
View File
@@ -0,0 +1 @@
<svg class="img-fluid" id="outputsvg" xmlns="http://www.w3.org/2000/svg" style="transform: matrix(1.3, 0, 0, 1.3, 0, 0); transform-origin: 50% 50%; cursor: move; transition: transform 200ms ease-in-out 0s;" width="120" height="120" viewBox="0 0 1200 1200"><g id="led6HDXxLpKB3rJBInqSAJ" fill="rgb(253,174,179)" style="transform: none; transform-origin: 50% 50%;"><g style="transform: none;"><path id="ppWkGCoaW" d="M143 1036 l-28 -24 -6 -169 -7 -168 52 -49 c28 -26 100 -90 159 -142 100 -87 111 -94 151 -94 39 0 50 6 127 74 72 64 86 72 101 62 9 -7 91 -78 182 -159 129 -115 171 -147 193 -147 l28 0 -5 396 -5 396 -28 24 -28 24 -429 0 -429 0 -28 -24z"></path></g></g><g id="l1PReg5IXY9KUxbReAvzebZ" fill="rgb(239,68,82)" style="transform: none; transform-origin: 50% 50%;"><g style="transform: none;"><path id="pRauFKI19" d="M100 649 l0 -73 48 -45 c137 -132 293 -261 314 -261 13 0 37 12 54 28 117 102 143 122 161 122 19 0 37 -15 241 -193 109 -94 139 -106 167 -64 8 13 13 35 11 48 -3 13 -5 27 -5 32 -1 4 -6 7 -11 7 -6 0 -84 65 -173 144 -210 187 -213 189 -240 182 -12 -3 -60 -40 -106 -81 -46 -41 -90 -75 -97 -75 -12 0 -87 62 -255 210 -119 106 -109 104 -109 19z"></path></g></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
var pluginTrendlineLinear={id:"trendlineLinear",afterDraw:function(a){var t,s;for(var e in a.scales)if("x"==e[0]?s=a.scales[e]:t=a.scales[e],s&&t)break;var n=a.chart.ctx;a.data.datasets.forEach(function(t,e){if(t.trendlineLinear&&a.isDatasetVisible(e)&&0!=t.data.length){var i=a.getDatasetMeta(e);addFitter(i,n,t,s,a.scales[i.yAxisID])}}),n.setLineDash([])}};function addFitter(t,e,i,a,s){var n=i.trendlineLinear.style||i.borderColor,r=i.trendlineLinear.width||i.borderWidth,o=i.trendlineLinear.lineStyle||"solid";n=void 0!==n?n:"rgba(169,169,169, .6)",r=void 0!==r?r:3;var l=new LineFitter,d=i.data.length-1,h=t.data[0]._model.x,u=t.data[d]._model.x,m=!1;i.data&&"object"==typeof i.data[0]&&(m=!0),i.data.forEach(function(t,e){if(null!=t)if("time"===a.options.type){var i=null!=t.x?t.x:t.t;l.add(new Date(i).getTime(),t.y)}else m?l.add(t.x,t.y):l.add(e,t)});var x=a.getPixelForValue(l.minx),c=a.getPixelForValue(l.maxx),f=s.getPixelForValue(l.f(l.minx)),g=s.getPixelForValue(l.f(l.maxx));m||(x=h,c=u);var p=t.controller.chart.chartArea.bottom,v=t.controller.chart.width;if(p<f){var X=f-p,L=f-g;f=p,x+=v*(X/L)}else if(p<g){X=g-p,L=g-f;g=p,c=v-(c-(v-v*(X/L)))}e.lineWidth=r,"dotted"===o&&e.setLineDash([2,3]),e.beginPath(),e.moveTo(x,f),e.lineTo(c,g),e.strokeStyle=n,e.stroke()}function LineFitter(){this.count=0,this.sumX=0,this.sumX2=0,this.sumXY=0,this.sumY=0,this.minx=1e100,this.maxx=-1e100}LineFitter.prototype={add:function(t,e){t=parseFloat(t),e=parseFloat(e),this.count++,this.sumX+=t,this.sumX2+=t*t,this.sumXY+=t*e,this.sumY+=e,t<this.minx&&(this.minx=t),t>this.maxx&&(this.maxx=t)},f:function(t){t=parseFloat(t);var e=this.count*this.sumX2-this.sumX*this.sumX;return(this.sumX2*this.sumY-this.sumX*this.sumXY)/e+t*((this.count*this.sumXY-this.sumX*this.sumY)/e)}},"undefined"!=typeof window&&window.Chart&&window.Chart.plugins.register(pluginTrendlineLinear);try{module.exports=exports=pluginTrendlineLinear}catch(t){}
+49 -1
View File
@@ -11,6 +11,12 @@ if(!isset($_SESSION['behind'])) {
$num_commits_behind = $matches[1];
$_SESSION['behind'] = $num_commits_behind;
}
if (preg_match('/\b(\d+)\b and \b(\d+)\b different commits each/', $str, $matches)) {
$num1 = (int) $matches[1];
$num2 = (int) $matches[2];
$sum = $num1 + $num2;
$_SESSION['behind'] = $sum;
}
if(isset($_SESSION['behind'])&&intval($_SESSION['behind']) >= 99) {?>
<style>
.updatenumber {
@@ -307,10 +313,52 @@ if(isset($_GET['view'])){
$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);
$results = str_replace("Your branch is up to date", "<span style='color:limegreen'><b>Your branch is up to date</b></span>",$results);
$results = str_replace("(+)", "(<span style='color:lime;font-weight:bold'>+</span>)",$results);
$results = str_replace("(-)", "(<span style='color:red;font-weight:bold'>-</span>)",$results);
// split the input string into lines
$lines = explode("\n", $results);
// iterate over each line
foreach ($lines as &$line) {
// check if the line matches the pattern
if (preg_match('/^(.+?)\s*\|\s*(\d+)\s*([\+\- ]+)(\d+)?$/', $line, $matches)) {
// extract the filename, count, and indicator letters
$filename = $matches[1];
$count = $matches[2];
$diff = $matches[3];
$delta = $matches[4] ?? '';
// determine the indicator letters
$diff_array = str_split($diff);
$indicators = array_map(function ($d) use ($delta) {
if ($d === '+') {
return "<span style='color:lime;'><b>+</b></span>";
} elseif ($d === '-') {
return "<span style='color:red;'><b>-</b></span>";
} elseif ($d === ' ') {
if ($delta !== '') {
return 'A';
} else {
return ' ';
}
}
}, $diff_array);
// modify the line with the new indicator letters
$line = sprintf('%-35s|%3d %s%s', $filename, $count, implode('', $indicators), $delta);
}
}
// rejoin the modified lines into a string
$output = implode("\n", $lines);
$results = $output;
// remove script tags (xss)
$results = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $results);
if(strlen($results) == 0) {
$results = "This command has no output.";
}
echo "<table style='min-width:70%;'><tr class='relative'><th>Output of command:`".$initcommand."`<button class='copyimage' style='right:40px' onclick='copyOutput(this);'>Copy</button></th></tr><tr><td><pre style='text-align:left'>$results</pre></td></tr></table>";
echo "<table style='min-width:70%;'><tr class='relative'><th>Output of command:`".$initcommand."`<button class='copyimage' style='right:40px' onclick='copyOutput(this);'>Copy</button></th></tr><tr><td style='padding-left: 0px;padding-right: 0px;padding-bottom: 0px;padding-top: 0px;'><pre class='bash' style='text-align:left;margin:0px'>$results</pre></td></tr></table>";
} else {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
+40 -1
View File
@@ -200,6 +200,33 @@ if(isset($_GET['submit'])) {
$contents2 = preg_replace("/SILENCE_UPDATE_INDICATOR=.*/", "SILENCE_UPDATE_INDICATOR=0", $contents2);
}
if(isset($_GET["raw_spectrogram"])) {
$raw_spectrogram = 1;
if(strcmp($RAW_SPECTROGRAM,$config['RAW_SPECTROGRAM']) !== 0) {
$contents = preg_replace("/RAW_SPECTROGRAM=.*/", "RAW_SPECTROGRAM=$raw_spectrogram", $contents);
$contents2 = preg_replace("/RAW_SPECTROGRAM=.*/", "RAW_SPECTROGRAM=$raw_spectrogram", $contents2);
}
} else {
$contents = preg_replace("/RAW_SPECTROGRAM=.*/", "RAW_SPECTROGRAM=0", $contents);
$contents2 = preg_replace("/RAW_SPECTROGRAM=.*/", "RAW_SPECTROGRAM=0", $contents2);
}
if(isset($_GET["custom_image"])) {
$custom_image = $_GET["custom_image"];
if(strcmp($custom_image,$config['CUSTOM_IMAGE']) !== 0) {
$contents = preg_replace("/CUSTOM_IMAGE=.*/", "CUSTOM_IMAGE=$custom_image", $contents);
$contents2 = preg_replace("/CUSTOM_IMAGE=.*/", "CUSTOM_IMAGE=$custom_image", $contents2);
}
}
if(isset($_GET["custom_image_label"])) {
$custom_image_label = $_GET["custom_image_label"];
if(strcmp($custom_image_label,$config['CUSTOM_IMAGE_TITLE']) !== 0) {
$contents = preg_replace("/CUSTOM_IMAGE_TITLE=.*/", "CUSTOM_IMAGE_TITLE=$custom_image_label", $contents);
$contents2 = preg_replace("/CUSTOM_IMAGE_TITLE=.*/", "CUSTOM_IMAGE_TITLE=$custom_image_label", $contents2);
}
}
$fh = fopen('/etc/birdnet/birdnet.conf', "w");
$fh2 = fopen("./scripts/thisrun.txt", "w");
fwrite($fh, $contents);
@@ -287,6 +314,18 @@ foreach($formats as $format){
<label for="silence_update_indicator">Silence Update Indicator: </label>
<input type="checkbox" name="silence_update_indicator" <?php if($newconfig['SILENCE_UPDATE_INDICATOR'] == 1) { echo "checked"; };?> ><br>
<label for="raw_spectrogram">Minimalist Spectrograms: </label>
<input type="checkbox" name="raw_spectrogram" <?php if($newconfig['RAW_SPECTROGRAM'] == 1) { echo "checked"; };?> ><br>
<br>
<p>These allow you to show a custom image on the Overview page of your BirdNET-Pi. This can be used to show a dynamically updating picture of your garden, for example.</p>
<label for="custom_image">Custom Image Absolute Path: </label>
<input name="custom_image" type="text" value="<?php print($newconfig['CUSTOM_IMAGE']);?>"/><br>
<label for="custom_image_label">Custom Image Label: </label>
<input name="custom_image_label" type="text" value="<?php print($newconfig['CUSTOM_IMAGE_TITLE']);?>"/><br>
<h3>BirdNET-Lite Settings</h3>
<p>
@@ -308,7 +347,7 @@ foreach($formats as $format){
<h3>Accessibility Settings</h3>
<p>Birdsongs Frequency shifting configuration:<br>
this can be useful for hearing impaired people.<br>
this can be useful for hearing impaired people. Note: audio files will only be pitch shifted when the "FREQ SHIFT" button is manually clicked for a detection on the "Recordings" page.<br>
<p style="margin-left: 40px">
+3 -2
View File
@@ -474,9 +474,10 @@ function runProcess() {
<dl>
<dt>BirdNET_6K_GLOBAL_MODEL (2020)</dt><br>
<dd id="ddnewline">This model comes from BirdNET-Lite, with bird sound recognition for more than 6,000 species worldwide. This is the default option and will generally work very well for most use cases.</dd>
<dd id="ddnewline">This is the BirdNET-Lite model, with bird sound recognition for more than 6,000 species worldwide. This is the default option and will generally work very for people in most of the world.</dd>
<dt>BirdNET_GLOBAL_3K_V2.2_Model_FP16 (2022)</dt><br>
<dd id="ddnewline">This model comes from BirdNET-Analyzer, a newer work-in-progress project with aims to improve on the old model. Currently it only supports about 3,500 species worldwide, so for users in North America, this model is generally much more accurate than the above model, but elsewhere it will be less accurate and possibly useless.</dd>
<dd id="ddnewline">This is the BirdNET-Analyzer model, a newer work-in-progress project with aims to improve on the BirdNET-Lite model. Currently it only supports about 3,500 species worldwide, meaning for some regions (North America, Europe, Australia) it will usually outperform the BirdNET-Lite model, but for other regions it will be worse.</dd><br>
<dt>(in-depth technical write-up on the models <a target="_blank" href="https://github.com/mcguirepr89/BirdNET-Pi/wiki/BirdNET-Pi:-some-theory-on-classification-&-some-practical-hints">here</a>)</dt>
</dl>
</td></tr></table><br>
+16 -5
View File
@@ -118,11 +118,22 @@ for h in "${SCAN_DIRS[@]}";do
sox -V1 "${h}/${OLDFILE}" "${NEWSPECIES_BYDATE}/${NEWFILE}" \
trim ="${START}" ="${END}"
# Create spectrogram for extraction
sox -V1 "${NEWSPECIES_BYDATE}/${NEWFILE}" -n remix 1 rate 24k spectrogram \
-t "${COMMON_NAME}" \
-c "${NEWSPECIES_BYDATE//$HOME\/}/${NEWFILE}" \
-o "${NEWSPECIES_BYDATE}/${NEWFILE}.png"
RAW_SPECTROGRAM=${RAW_SPECTROGRAM}
# Check if RAW_SPECTROGRAM is 1
if [ "$RAW_SPECTROGRAM" == "1" ]; then
# If it is, add "-r" as an argument to the SOX command
sox -V1 "${NEWSPECIES_BYDATE}/${NEWFILE}" -n remix 1 rate 24k spectrogram \
-t "${COMMON_NAME}" \
-c "${NEWSPECIES_BYDATE//$HOME\/}/${NEWFILE}" \
-o "${NEWSPECIES_BYDATE}/${NEWFILE}.png" \
-r
else
# If it's not, run the SOX command without the "-r" argument
sox -V1 "${NEWSPECIES_BYDATE}/${NEWFILE}" -n remix 1 rate 24k spectrogram \
-t "${COMMON_NAME}" \
-c "${NEWSPECIES_BYDATE//$HOME\/}/${NEWFILE}" \
-o "${NEWSPECIES_BYDATE}/${NEWFILE}.png"
fi
done < "${TMPFILE}"
+12
View File
@@ -228,6 +228,18 @@ HEARTBEAT_URL=
SILENCE_UPDATE_INDICATOR=0
## RAW_SPECTROGRAM is for removing the axes and labels of the spectrograms
## that are generated by Sox for each detection for a cleaner appearance.
RAW_SPECTROGRAM=0
## CUSTOM_IMAGE and CUSTOM_IMAGE_TITLE allow you to show a custom image on the
## Overview page of your BirdNET-Pi. This can be used to show a dynamically
## updating picture of your garden, for example.
CUSTOM_IMAGE=
CUSTOM_IMAGE_TITLE=""
## These are just for debugging
LAST_RUN=
THIS_RUN=
+201 -6
View File
@@ -23,6 +23,20 @@ $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['custom_image'])){
if(isset($config["CUSTOM_IMAGE"])) {
?>
<br>
<h3><?php echo $config["CUSTOM_IMAGE_TITLE"]; ?></h3>
<?php
$image_data = file_get_contents($config["CUSTOM_IMAGE"]);
$image_base64 = base64_encode($image_data);
$img_tag = "<img src='data:image/png;base64," . $image_base64 . "'>";
echo $img_tag;
}
die();
}
if(isset($_GET['fetch_chart_string']) && $_GET['fetch_chart_string'] == "true") {
$myDate = date('Y-m-d');
$chart = "Combo-$myDate.png";
@@ -93,11 +107,21 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
}
}
$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", $engname).$comnameprefix."&sort=relevance".$args."&per_page=5&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 = '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));
// Make the API call
$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", $engname).$comnameprefix."&sort=relevance".$args."&per_page=5&media=photos&format=json&nojsoncallback=1"), true)["photos"]["photo"];
// Find the first photo that is not blacklisted
$photo = null;
foreach ($flickrjson as $flickrphoto) {
if ($flickrphoto["id"] !== "4892923285") {
$photo = $flickrphoto;
break;
}
}
$modaltext = "https://flickr.com/photos/".$photo["owner"]."/".$photo["id"];
$authorlink = "https://flickr.com/people/".$photo["owner"];
$imageurl = 'https://farm' .$photo["farm"]. '.static.flickr.com/' .$photo["server"]. '/' .$photo["id"]. '_' .$photo["secret"]. '.jpg';
array_push($_SESSION['images'], array($comname,$imageurl,$photo["title"], $modaltext, $authorlink));
$image = $_SESSION['images'][count($_SESSION['images'])-1];
}
}
@@ -131,7 +155,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
<?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>
<button type="submit" name="species" value="<?php echo $mostrecent['Com_Name'];?>"><?php echo $mostrecent['Com_Name'];?></button><img style="width: unset !important;display: inline;height: 1em;cursor:pointer" title="View species stats" onclick="generateMiniGraph(this, '<?php echo $comname; ?>')" width=25 src="images/chart.svg"></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>
@@ -236,6 +260,8 @@ body::-webkit-scrollbar {
<button onclick="hideDialog()">Close</button>
</dialog>
<script src="static/dialog-polyfill.js"></script>
<script src="static/Chart.bundle.js"></script>
<script src="static/chartjs-plugin-trendline.min.js"></script>
<script>
var dialog = document.querySelector('dialog');
dialogPolyfill.registerDialog(dialog);
@@ -282,8 +308,11 @@ if (file_exists('./Charts/'.$chart)) {
$refresh = $config['RECORDING_LENGTH'];
$time = time();
echo "<img id=\"spectrogramimage\" src=\"/spectrogram.png?nocache=$time\">";
?>
<div id="customimage"></div>
</div>
</div>
@@ -360,5 +389,171 @@ window.addEventListener("load", function(){
window.setInterval(function(){
document.getElementById("spectrogramimage").src = "/spectrogram.png?nocache="+Date.now();
}, <?php echo $refresh; ?>*1000);
<?php if(isset($config["CUSTOM_IMAGE"])){?>
// every 1 second, this loop will run and refresh the custom image
window.setInterval(function(){
// Find the customimage element
var customimage = document.getElementById("customimage");
function updateCustomImage() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "overview.php?custom_image=true", true);
xhr.onload = function() {
customimage.innerHTML = xhr.responseText;
}
xhr.send();
}
updateCustomImage();
}, 1000);
<?php } ?>
</script>
<style>
.tooltip {
background-color: white;
border: 1px solid #ccc;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
padding: 10px;
transition: opacity 0.2s ease-in-out;
}
</style>
<script>
function generateMiniGraph(elem, comname) {
// Make an AJAX call to fetch the number of detections for the bird species
var xhr = new XMLHttpRequest();
xhr.open('GET', '/todays_detections.php?comname=' + comname);
xhr.onload = function() {
if (xhr.status === 200) {
var detections = JSON.parse(xhr.responseText);
console.log(detections)
// Create a div element for the chart window
var chartWindow = document.createElement('div');
chartWindow.className = "chartdiv"
chartWindow.style.position = 'fixed';
chartWindow.style.top = '0%';
chartWindow.style.left = '50%';
chartWindow.style.width = window.innerWidth < 700 ? '40%' : '20%';
chartWindow.style.height = window.innerWidth < 700 ? '25%' : '16%';
chartWindow.style.backgroundColor = '#fff';
chartWindow.style.zIndex = '9999';
chartWindow.style.overflow = 'auto';
chartWindow.style.borderRadius = '5px';
chartWindow.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
document.body.appendChild(chartWindow);
// Create a canvas element for the chart
var canvas = document.createElement('canvas');
canvas.width = chartWindow.offsetWidth;
canvas.height = chartWindow.offsetHeight;
chartWindow.appendChild(canvas);
// Create a new Chart.js chart
var ctx = canvas.getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: detections.map(item => item.date),
datasets: [{
label: 'Detections',
data: detections.map(item => item.count),
backgroundColor: '#9fe29b',
borderColor: '#77c487',
borderWidth: 1,
lineTension: 0.3, // Add smoothing to the line
pointRadius: 1, // Make the data points smaller
pointHitRadius: 10, // Increase the area around data points for mouse events
trendlineLinear: {
style: "rgba(55, 99, 64, 0.5)",
lineStyle: "solid",
width: 1.5
}
}]
},
options: {
layout: {
padding: {
right: 10
}
},
title: {
display: true,
text: 'Detections Over 30d'
},
legend: {
display: false
},
scales: {
xAxes: [{
display: false,
gridLines: {
display: false // Hide the gridlines on the x-axis
},
ticks: {
autoSkip: true,
maxTicksLimit: 2
}
}],
yAxes: [{
gridLines: {
display: false // Hide the gridlines on the y-axis
},
ticks: {
beginAtZero: true,
precision: 0,
stepSize: 1
}
}]
}
}
});
var buttonRect = elem.getBoundingClientRect();
var chartRect = chartWindow.getBoundingClientRect();
if (window.innerWidth < 700) {
chartWindow.style.left = 'calc(75% - ' + (chartRect.width / 2) + 'px)';
} else {
chartWindow.style.left = (buttonRect.right + 10) + 'px';
}
// Calculate the top position of the chart to center it with the button
var buttonCenter = buttonRect.top + (buttonRect.height / 2);
var chartHeight = chartWindow.offsetHeight;
var chartTop = buttonCenter - (chartHeight / 2);
chartWindow.style.top = chartTop + 'px';
// Add a close button to the chart window
var closeButton = document.createElement('button');
closeButton.id = "chartcb";
closeButton.innerText = 'X';
closeButton.style.position = 'absolute';
closeButton.style.top = '5px';
closeButton.style.right = '5px';
closeButton.addEventListener('click', function() {
document.body.removeChild(chartWindow);
});
chartWindow.appendChild(closeButton);
}
};
xhr.send();
}
// Listen for the scroll event on the window object
window.addEventListener('scroll', function() {
// Get all chart elements
var charts = document.querySelectorAll('.chartdiv');
// Loop through all chart elements and remove them
charts.forEach(function(chart) {
chart.parentNode.removeChild(chart);
});
});
</script>
+109 -46
View File
@@ -109,27 +109,27 @@ if(isset($_GET['shiftfile'])) {
$pi = $home."/BirdSongs/Extracted/By_Date/";
if(isset($_GET['doshift'])) {
$freqshift_tool = $config['FREQSHIFT_TOOL'];
$freqshift_tool = $config['FREQSHIFT_TOOL'];
if ($freqshift_tool == "ffmpeg") {
$cmd = "/usr/bin/nohup /usr/bin/ffmpeg -y -i \"".$pi.$filename."\" -af \"rubberband=pitch=".$config['FREQSHIFT_LO']."/".$config['FREQSHIFT_HI']."\" \"".$shifted_path.$filename."\"";
shell_exec("mkdir -p ".$shifted_path.$dir." && echo \"".$cmd."\" > /tmp/shift.sh && chmod +x /tmp/shift.sh");
if ($freqshift_tool == "ffmpeg") {
$cmd = "/usr/bin/nohup /usr/bin/ffmpeg -y -i \"".$pi.$filename."\" -af \"rubberband=pitch=".$config['FREQSHIFT_LO']."/".$config['FREQSHIFT_HI']."\" \"".$shifted_path.$filename."\"";
shell_exec("mkdir -p ".$shifted_path.$dir." && echo \"".$cmd."\" > /tmp/shift.sh && chmod +x /tmp/shift.sh");
} else if ($freqshift_tool == "sox") {
//linux.die.net/man/1/sox
$soxopt = "-q";
$soxpitch = $config['FREQSHIFT_PITCH'];
$cmd = "/usr/bin/nohup /usr/bin/sox \"".$pi.$filename."\" \"".$shifted_path.$filename."\" pitch ".$soxopt." ".$soxpitch;
shell_exec("mkdir -p ".$shifted_path.$dir." && echo \"".$cmd."\" > /tmp/shift.sh && chmod +x /tmp/shift.sh");
}
} else if ($freqshift_tool == "sox") {
//linux.die.net/man/1/sox
$soxopt = "-q";
$soxpitch = $config['FREQSHIFT_PITCH'];
$cmd = "/usr/bin/nohup /usr/bin/sox \"".$pi.$filename."\" \"".$shifted_path.$filename."\" pitch ".$soxopt." ".$soxpitch;
shell_exec("mkdir -p ".$shifted_path.$dir." && echo \"".$cmd."\" > /tmp/shift.sh && chmod +x /tmp/shift.sh");
}
shell_exec("/tmp/shift.sh");
shell_exec("rm -f /tmp/shift.sh");
shell_exec("/tmp/shift.sh");
shell_exec("rm -f /tmp/shift.sh");
} else {
$cmd = "rm -f " . $shifted_path.$filename;
shell_exec("echo \"".$cmd."\" > /tmp/unshift.sh && chmod +x /tmp/unshift.sh");
shell_exec("/tmp/unshift.sh");
shell_exec("rm -f /tmp/unshift.sh");
$cmd = "rm -f " . $shifted_path.$filename;
shell_exec("echo \"".$cmd."\" > /tmp/unshift.sh && chmod +x /tmp/unshift.sh");
shell_exec("/tmp/unshift.sh");
shell_exec("rm -f /tmp/unshift.sh");
}
echo "OK";
@@ -259,21 +259,21 @@ function toggleShiftFreq(filename, shiftAction, elem) {
elem.setAttribute("title", "This file has been shifted down in frequency.");
elem.setAttribute("onclick", elem.getAttribute("onclick").replace("shift","unshift"));
console.log("shifted freqs of " + filename);
video=elem.parentNode.getElementsByTagName("video")[0];
video.setAttribute("title", video.getAttribute("title").replace("/By_Date/","/By_Date/shifted/"));
source = video.getElementsByTagName("source")[0];
source.setAttribute("src", source.getAttribute("src").replace("/By_Date/","/By_Date/shifted/"));
video.load();
video=elem.parentNode.getElementsByTagName("video")[0];
video.setAttribute("title", video.getAttribute("title").replace("/By_Date/","/By_Date/shifted/"));
source = video.getElementsByTagName("source")[0];
source.setAttribute("src", source.getAttribute("src").replace("/By_Date/","/By_Date/shifted/"));
video.load();
} else {
elem.setAttribute("src","images/shift.svg");
elem.setAttribute("title", "This file is not shifted in frequency.");
elem.setAttribute("onclick", elem.getAttribute("onclick").replace("unshift","shift"));
console.log("unshifted freqs of " + filename);
video=elem.parentNode.getElementsByTagName("video")[0];
video.setAttribute("title", video.getAttribute("title").replace("/By_Date/shifted/","/By_Date/"));
source = video.getElementsByTagName("source")[0];
source.setAttribute("src", source.getAttribute("src").replace("/By_Date/shifted/","/By_Date/"));
video.load();
video=elem.parentNode.getElementsByTagName("video")[0];
video.setAttribute("title", video.getAttribute("title").replace("/By_Date/shifted/","/By_Date/"));
source = video.getElementsByTagName("source")[0];
source.setAttribute("src", source.getAttribute("src").replace("/By_Date/shifted/","/By_Date/"));
video.load();
}
}
}
@@ -325,22 +325,76 @@ if(!isset($_GET['species']) && !isset($_GET['filename'])){
#By Species
} elseif($view == "byspecies") {
while($results=$result->fetchArray(SQLITE3_ASSOC)){
$birds = array();
while($results=$result->fetchArray(SQLITE3_ASSOC))
{
$name = $results['Com_Name'];
echo "<td>
<button action=\"submit\" name=\"species\" value=\"$name\">$name</button></td></tr>";}
#Specific Date
} elseif($view == "date") {
while($results=$result->fetchArray(SQLITE3_ASSOC)){
$name = $results['Com_Name'];
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>";
}
$birds[] = $name;
}
if(count($birds) > 45) {
$num_cols = 3;
} else {
$num_cols = 1;
}
$num_rows = ceil(count($birds) / $num_cols);
for ($row = 0; $row < $num_rows; $row++) {
echo "<tr>";
for ($col = 0; $col < $num_cols; $col++) {
$index = $row + $col * $num_rows;
if ($index < count($birds)) {
?>
<td class="spec">
<button type="submit" name="species" value="<?php echo $birds[$index];?>"><?php echo $birds[$index];?></button>
</td>
<?php
} else {
echo "<td></td>";
}
}
echo "</tr>";
}
} elseif($view == "date") {
$birds = array();
while($results=$result->fetchArray(SQLITE3_ASSOC))
{
$name = $results['Com_Name'];
if(realpath($home."/BirdSongs/Extracted/By_Date/".$date."/".str_replace(" ", "_",$name)) !== false){
$birds[] = $name;
}
}
if(count($birds) > 45) {
$num_cols = 3;
} else {
$num_cols = 1;
}
$num_rows = ceil(count($birds) / $num_cols);
for ($row = 0; $row < $num_rows; $row++) {
echo "<tr>";
for ($col = 0; $col < $num_cols; $col++) {
$index = $row + $col * $num_rows;
if ($index < count($birds)) {
?>
<td class="spec">
<button type="submit" name="species" value="<?php echo $birds[$index];?>"><?php echo $birds[$index];?></button>
</td>
<?php
} else {
echo "<td></td>";
}
}
echo "</tr>";
}
#Choose
} else {
echo "<td>
@@ -452,7 +506,7 @@ echo "<table>
$shiftImageIcon = "images/unshift.svg";
$shiftTitle = "This file has been shifted down in frequency.";
$shiftAction = "unshift";
$filename = $filename_shifted;
$filename = $filename_shifted;
} else {
$shiftImageIcon = "images/shift.svg";
$shiftTitle = "This file is not shifted in frequency.";
@@ -460,7 +514,7 @@ echo "<table>
}
echo "<tr>
<td class=\"relative\">
<td class=\"relative\">
<img style='cursor:pointer;right:90px' src='images/delete.svg' onclick='deleteDetection(\"".$filename_formatted."\")' class=\"copyimage\" width=25 title='Delete Detection'>
<img style='cursor:pointer;right:45px' onclick='toggleLock(\"".$filename_formatted."\",\"".$type."\", this)' class=\"copyimage\" width=25 title=\"".$title."\" src=\"".$imageicon."\">
@@ -471,7 +525,7 @@ echo "<table>
</tr>";
} else {
echo "<tr>
<td class=\"relative\">$date $time<br>$confidence
<td class=\"relative\">$date $time<br>$confidence
<img style='cursor:pointer' src='images/delete.svg' onclick='deleteDetection(\"".$filename_formatted."\")' class=\"copyimage\" width=25 title='Delete Detection'><br>
".$imageelem."
</td>
@@ -527,7 +581,7 @@ echo "<table>
$shiftImageIcon = "images/unshift.svg";
$shiftTitle = "This file has been shifted down in frequency.";
$shiftAction = "unshift";
$filename = $filename_shifted;
$filename = $filename_shifted;
} else {
$shiftImageIcon = "images/shift.svg";
$shiftTitle = "This file is not shifted in frequency.";
@@ -535,7 +589,7 @@ echo "<table>
}
echo "<tr>
<td class=\"relative\">
<td class=\"relative\">
<img style='cursor:pointer;right:90px' src='images/delete.svg' onclick='deleteDetection(\"".$filename_formatted."\", true)' class=\"copyimage\" width=25 title='Delete Detection'>
<img style='cursor:pointer;right:45px' onclick='toggleLock(\"".$filename_formatted."\",\"".$type."\", this)' class=\"copyimage\" width=25 title=\"".$title."\" src=\"".$imageicon."\">
@@ -545,7 +599,7 @@ echo "<table>
</tr>";
} else {
echo "<tr>
<td class=\"relative\">$date $time<br>$confidence
<td class=\"relative\">$date $time<br>$confidence
<img style='cursor:pointer' src='images/delete.svg' onclick='deleteDetection(\"".$filename_formatted."\", true)' class=\"copyimage\" width=25 title='Delete Detection'><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>";
@@ -553,4 +607,13 @@ echo "<table>
}echo "</table>";}?>
</div>
<style>
td.spec {
width: calc(100% / <?php echo $num_cols;?>);
}
tr:first-child td.spec {
padding-top: 10px;
}
</style>
</html>
+52 -19
View File
@@ -87,26 +87,59 @@ if(!file_exists($home."/BirdNET-Pi/scripts/disk_check_exclude.txt") || strpos(fi
</button>
</form>
</div>
<table>
<?php
while($results=$result2->fetchArray(SQLITE3_ASSOC))
{
$comname = preg_replace('/ /', '_', $results['Com_Name']);
$comname = preg_replace('/\'/', '', $comname);
$filename = "/By_Date/".$results['Date']."/".$comname."/".$results['File_Name'];
?>
<tr>
<form action="" method="GET">
<td><input type="hidden" name="sort" value="<?php if(isset($_GET['sort'])){echo $_GET['sort'];}?>">
<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>
</td>
<?php
}
?>
</form>
</tr>
<table style="padding-top:10px">
<?php
$birds = array();
while($results=$result2->fetchArray(SQLITE3_ASSOC))
{
$comname = preg_replace('/ /', '_', $results['Com_Name']);
$comname = preg_replace('/\'/', '', $comname);
$filename = "/By_Date/".$results['Date']."/".$comname."/".$results['File_Name'];
$birds[] = $results['Com_Name'];
}
if(count($birds) > 45) {
$num_cols = 3;
} else {
$num_cols = 1;
}
$num_rows = ceil(count($birds) / $num_cols);
for ($row = 0; $row < $num_rows; $row++) {
echo "<tr>";
for ($col = 0; $col < $num_cols; $col++) {
$index = $row + $col * $num_rows;
if ($index < count($birds)) {
?>
<td>
<form action="" method="GET">
<input type="hidden" name="sort" value="<?php if(isset($_GET['sort'])){echo $_GET['sort'];}?>">
<input type="hidden" name="view" value="Species Stats">
<button type="submit" name="species" value="<?php echo $birds[$index];?>"><?php echo $birds[$index];?></button>
</form>
</td>
<?php
} else {
echo "<td></td>";
}
}
echo "</tr>";
}
?>
</table>
<style>
td {
padding: 0px;
width: calc(100% / <?php echo $num_cols;?>);
}
tr:first-child td {
padding-top: 10px;
}
</style>
</div>
<dialog id="attribution-dialog">
<h1 id="modalHeading"></h1>
+6
View File
@@ -10,6 +10,12 @@ if (preg_match("/behind '.*?' by (\d+) commit(s?)\b/", $str, $matches)) {
$num_commits_behind = $matches[1];
$_SESSION['behind'] = $num_commits_behind;
}
if (preg_match('/\b(\d+)\b and \b(\d+)\b different commits each/', $str, $matches)) {
$num1 = (int) $matches[1];
$num2 = (int) $matches[2];
$sum = $num1 + $num2;
$_SESSION['behind'] = $sum;
}
?><html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<br>
+216 -7
View File
@@ -80,6 +80,52 @@ $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['comname'])) {
$birdName = $_GET['comname'];
$birdName = str_replace("_", " ", $birdName);
// Prepare a SQL statement to retrieve the detection data for the specified bird
$stmt = $db->prepare('SELECT Date, COUNT(*) AS Detections FROM detections WHERE Com_Name = :com_name AND Date BETWEEN DATE("now", "-30 days") AND DATE("now") GROUP BY Date');
// Bind the bird name parameter to the SQL statement
$stmt->bindValue(':com_name', $birdName);
// Execute the SQL statement and get the result set
$result = $stmt->execute();
// Fetch the result set as an associative array
$data = array();
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$data[$row['Date']] = $row['Detections'];
}
// Create an array of all dates in the last 14 days
$last14Days = array();
for ($i = 0; $i < 31; $i++) {
$last14Days[] = date('Y-m-d', strtotime("-$i days"));
}
// Merge the data array with the last14Days array
$data = array_merge(array_fill_keys($last14Days, 0), $data);
// Sort the data by date in ascending order
ksort($data);
// Convert the data to an array of objects
$data = array_map(function($date, $count) {
return array('date' => $date, 'count' => $count);
}, array_keys($data), $data);
// Close the database connection
$db->close();
// Return the data as JSON
echo json_encode($data);
die();
}
// from https://stackoverflow.com/questions/2690504/php-producing-relative-date-time-from-timestamps
function relativeTime($ts)
{
@@ -218,11 +264,21 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
break;
}
}
$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", $engname).$comnameprefix."&sort=relevance".$args."&per_page=5&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 = '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));
// Make the API call
$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", $engname).$comnameprefix."&sort=relevance".$args."&per_page=5&media=photos&format=json&nojsoncallback=1"), true)["photos"]["photo"];
// Find the first photo that is not blacklisted
$photo = null;
foreach ($flickrjson as $flickrphoto) {
if ($flickrphoto["id"] !== "4892923285") {
$photo = $flickrphoto;
break;
}
}
$modaltext = "https://flickr.com/photos/".$photo["owner"]."/".$photo["id"];
$authorlink = "https://flickr.com/people/".$photo["owner"];
$imageurl = 'https://farm' .$photo["farm"]. '.static.flickr.com/' .$photo["server"]. '/' .$photo["id"]. '_' .$photo["secret"]. '.jpg';
array_push($_SESSION['images'], array($comname,$imageurl,$photo["title"], $modaltext, $authorlink));
$image = $_SESSION['images'][count($_SESSION['images'])-1];
}
}
@@ -230,6 +286,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
<?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" title="Open in new tab" width=25 src="images/copy.png"></a>
<div class="centered_image_container">
<?php if(!empty($config["FLICKR_API_KEY"]) && strlen($image[2]) > 0) { ?>
@@ -237,7 +294,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
<?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>
<b><a class="a2" href="https://allaboutbirds.org/guide/<?php echo $comname;?>" target="top"><?php echo $todaytable['Com_Name'];?></a></b><img style="height: 1em;cursor:pointer" title="View species stats" onclick="generateMiniGraph(this, '<?php echo $comname; ?>')" width=25 src="images/chart.svg"><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 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>
@@ -252,7 +309,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
<?php } ?>
</div>
<div>
<b><a class="a2" <?php if($_GET['kiosk'] == false){?>href="https://allaboutbirds.org/guide/<?php echo $comname;?>"<?php } else {echo "style='color:blue;'";} ?> target="top"><?php echo $todaytable['Com_Name'];?></a></b><br>
<b><a class="a2" <?php if($_GET['kiosk'] == false){?>href="https://allaboutbirds.org/guide/<?php echo $comname;?>"<?php } else {echo "style='color:blue;'";} ?> target="top"><?php echo $todaytable['Com_Name'];?></a></b> <img style="height: 1em;cursor:pointer;float:unset;display:inline" title="View species stats" onclick="generateMiniGraph(this, '<?php echo $comname; ?>')" width=25 src="images/chart.svg"><br>
<a class="a2" <?php if($_GET['kiosk'] == false){?>href="https://wikipedia.org/wiki/<?php echo $sciname;?>"<?php } else {echo "style='color:blue;'";} ?> target="top"><i><?php echo $todaytable['Sci_Name'];?></i></a><br></td>
</div></div>
<td><b>Confidence:</b> <?php echo round((float)round($todaytable['Confidence'],2) * 100 ) . '%';?><br></td>
@@ -327,6 +384,9 @@ die();
<button style="background-color: #9fe29b;padding:20px" onclick="hideDialog()">Close</button>
</dialog>
<script src="static/dialog-polyfill.js"></script>
<script src="static/Chart.bundle.js"></script>
<script src="static/chartjs-plugin-trendline.min.js"></script>
<script>
var dialog = document.querySelector('dialog');
dialogPolyfill.registerDialog(dialog);
@@ -497,3 +557,152 @@ window.addEventListener("load", function(){
});
</script>
<style>
.tooltip {
background-color: white;
border: 1px solid #ccc;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
padding: 10px;
transition: opacity 0.2s ease-in-out;
}
</style>
<script>
function generateMiniGraph(elem, comname) {
// Make an AJAX call to fetch the number of detections for the bird species
var xhr = new XMLHttpRequest();
xhr.open('GET', '/todays_detections.php?comname=' + comname);
xhr.onload = function() {
if (xhr.status === 200) {
var detections = JSON.parse(xhr.responseText);
console.log(detections)
// Create a div element for the chart window
var chartWindow = document.createElement('div');
chartWindow.className = "chartdiv"
chartWindow.style.position = 'fixed';
chartWindow.style.top = '0%';
chartWindow.style.left = '50%';
chartWindow.style.width = window.innerWidth < 700 ? '40%' : '20%';
chartWindow.style.height = window.innerWidth < 700 ? '25%' : '16%';
chartWindow.style.backgroundColor = '#fff';
chartWindow.style.zIndex = '9999';
chartWindow.style.overflow = 'auto';
chartWindow.style.borderRadius = '5px';
chartWindow.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
document.body.appendChild(chartWindow);
// Create a canvas element for the chart
var canvas = document.createElement('canvas');
canvas.width = chartWindow.offsetWidth;
canvas.height = chartWindow.offsetHeight;
chartWindow.appendChild(canvas);
// Create a new Chart.js chart
var ctx = canvas.getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
labels: detections.map(item => item.date),
datasets: [{
label: 'Detections',
data: detections.map(item => item.count),
backgroundColor: '#9fe29b',
borderColor: '#77c487',
borderWidth: 1,
lineTension: 0.3, // Add smoothing to the line
pointRadius: 1, // Make the data points smaller
pointHitRadius: 10, // Increase the area around data points for mouse events
trendlineLinear: {
style: "rgba(55, 99, 64, 0.5)",
lineStyle: "solid",
width: 1.5
}
}]
},
options: {
layout: {
padding: {
right: 10
}
},
title: {
display: true,
text: 'Detections Over 30d'
},
legend: {
display: false
},
scales: {
xAxes: [{
display: false,
gridLines: {
display: false // Hide the gridlines on the x-axis
},
ticks: {
autoSkip: true,
maxTicksLimit: 2
}
}],
yAxes: [{
gridLines: {
display: false // Hide the gridlines on the y-axis
},
ticks: {
beginAtZero: true,
precision: 0,
stepSize: 1
}
}]
}
}
});
// Position the chart window to the right of the button
var buttonRect = elem.getBoundingClientRect();
var chartRect = chartWindow.getBoundingClientRect();
if (window.innerWidth < 700) {
chartWindow.style.left = 'calc(75% - ' + (chartRect.width / 2) + 'px)';
} else {
chartWindow.style.left = (buttonRect.right + 10) + 'px';
}
// Calculate the top position of the chart to center it with the button
var buttonCenter = buttonRect.top + (buttonRect.height / 2);
var chartHeight = chartWindow.offsetHeight;
var chartTop = buttonCenter - (chartHeight / 2);
chartWindow.style.top = chartTop + 'px';
// Add a close button to the chart window
var closeButton = document.createElement('button');
closeButton.id = "chartcb";
closeButton.innerText = 'X';
closeButton.style.position = 'absolute';
closeButton.style.top = '5px';
closeButton.style.right = '5px';
closeButton.addEventListener('click', function() {
document.body.removeChild(chartWindow);
});
chartWindow.appendChild(closeButton);
}
};
xhr.send();
}
// Listen for the scroll event on the window object
window.addEventListener('scroll', function() {
// Get all chart elements
var charts = document.querySelectorAll('.chartdiv');
// Loop through all chart elements and remove them
charts.forEach(function(chart) {
chart.parentNode.removeChild(chart);
});
});
</script>
+16 -2
View File
@@ -7,8 +7,10 @@ HOME=$(awk -F: '/1000/ {print $6}' /etc/passwd)
my_dir=$HOME/BirdNET-Pi/scripts
# Sets proper permissions and ownership
sudo -E chown -R $USER:$USER $HOME/*
sudo chmod -R g+wr $HOME/*
#sudo -E chown -R $USER:$USER $HOME/*
#sudo chmod -R g+wr $HOME/*
find $HOME/* -not -user $USER -execdir sudo -E chown $USER:$USER {} \+
find $HOME/* -not -user $USER -execdir sudo chmod g+wr {} \+
# Create blank sitename as it's optional. First time install will use $HOSTNAME.
if ! grep SITE_NAME /etc/birdnet/birdnet.conf &>/dev/null;then
@@ -168,5 +170,17 @@ if ! grep -q 'RuntimeMaxSec=' "$HOME/BirdNET-Pi/templates/birdnet_analysis.servi
sudo systemctl daemon-reload && restart_services.sh
fi
if ! grep RAW_SPECTROGRAM /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "RAW_SPECTROGRAM=0" >> /etc/birdnet/birdnet.conf
fi
if ! grep CUSTOM_IMAGE /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "CUSTOM_IMAGE=" >> /etc/birdnet/birdnet.conf
fi
if ! grep CUSTOM_IMAGE_TITLE /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "CUSTOM_IMAGE_TITLE=\"\"" >> /etc/birdnet/birdnet.conf
fi
sudo systemctl daemon-reload
restart_services.sh