Merge branch 'mcguirepr89:main' into main
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user