Merge branch 'main' into species_stats

This commit is contained in:
Patrick McGuire
2022-05-28 10:00:27 -04:00
committed by GitHub
19 changed files with 426 additions and 163 deletions
+27
View File
@@ -0,0 +1,27 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+6 -5
View File
@@ -2,7 +2,7 @@
BirdNET-Pi BirdNET-Pi
</h1> </h1>
<p align="center"> <p align="center">
A realtime acoustic bird classification system for the Raspberry Pi 4B A realtime acoustic bird classification system for the Raspberry Pi 4B, 3B+, and 0W2
</p> </p>
<p align="center"> <p align="center">
<img src="https://user-images.githubusercontent.com/60325264/140656397-bf76bad4-f110-467c-897d-992ff0f96476.png" /> <img src="https://user-images.githubusercontent.com/60325264/140656397-bf76bad4-f110-467c-897d-992ff0f96476.png" />
@@ -50,22 +50,23 @@ Currently listening in these countries . . . that I know of . . .
* Live audio stream & spectrogram * Live audio stream & spectrogram
* [BirdWeather](https://app.birdweather.com) integration -- you can request a BirdWeather ID from BirdNET-Pi's "Tools" > "Settings" page * [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) * Web interface access to all data and logs provided by [Caddy](https://caddyserver.com)
* Web Terminal * [GoTTY](https://github.com/yudai/gotty) Web Terminal
* [Tiny File Manager](https://tinyfilemanager.github.io/) * [Tiny File Manager](https://tinyfilemanager.github.io/)
* FTP server included * FTP server included
* SQLite3 Database * SQLite3 Database
* Adminer database maintenance * [Adminer](https://www.adminer.org/) database maintenance
* [phpSysInfo](https://github.com/phpsysinfo/phpsysinfo) * [phpSysInfo](https://github.com/phpsysinfo/phpsysinfo)
* [Apprise Notifications](https://github.com/caronc/apprise) supporting 70+ notification platforms * [Apprise Notifications](https://github.com/caronc/apprise) supporting 70+ notification platforms
* Localization supported * Localization supported
## Requirements ## Requirements
* A Raspberry Pi 4B or Raspberry Pi 3B+ (The 3B+ must run on RaspiOS-ARM64-**Lite**) * A Raspberry Pi 4B, Raspberry Pi 3B+, or Raspberry Pi 0W2 (The 3B+ and 0W2 must run on RaspiOS-ARM64-**Lite**)
* An SD Card with the **_64-bit version of RaspiOS_** installed (please use Bullseye) -- Lite is recommended, but the installation works on RaspiOS-ARM64-Full as well. [(Download the latest here)](https://downloads.raspberrypi.org/raspios_lite_arm64/images/) * An SD Card with the **_64-bit version of RaspiOS_** installed (please use Bullseye) -- Lite is recommended, but the installation works on RaspiOS-ARM64-Full as well. Downloads available within the [Raspberry Pi Imager](https://www.raspberrypi.com/software/).
* A USB Microphone or Sound Card * A USB Microphone or Sound Card
## Installation ## Installation
[A comprehensive installation guide is available here](https://github.com/mcguirepr89/BirdNET-Pi/wiki/Installation-Guide). [A comprehensive installation guide is available here](https://github.com/mcguirepr89/BirdNET-Pi/wiki/Installation-Guide).
[Raspberry Pi 0W2 installation guide available here](https://github.com/mcguirepr89/BirdNET-Pi/wiki/RPi0W2-Installation-Guide)
The system can be installed with: The system can be installed with:
``` ```
+4
View File
@@ -150,6 +150,10 @@ EXTRACTION_LENGTH=
AUDIOFMT=mp3 AUDIOFMT=mp3
## DATABASE_LANG is used to set the language for the database
DATABASE_LANG=en
## These are just for debugging ## These are just for debugging
LAST_RUN= LAST_RUN=
THIS_RUN= THIS_RUN=
+24
View File
@@ -82,6 +82,12 @@ button {
transition:background-color 0.2s; transition:background-color 0.2s;
} }
.disabled{
cursor: not-allowed;
pointer-events: none;
opacity:0.5;
}
button:hover { button:hover {
color: blue; color: blue;
} }
@@ -688,3 +694,21 @@ dialog::backdrop {
#gain.centered { #gain.centered {
margin-bottom:10px; margin-bottom:10px;
} }
.updatenumber {
margin-left:5px;
position:absolute;
display:inline-block;
background-color:#c8191a;
color:white;
width:20px;
line-height:20px;
border-radius:12px;
text-align:center;
font-size:small;
}
form#views button .updatenumber {
position:initial;
margin-left:0px;
}
+22 -2
View File
@@ -1,3 +1,19 @@
<?php
session_start();
$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($_SESSION['behind'])) {
$_SESSION['behind'] = trim(shell_exec("sudo git -C ".$home."/BirdNET-Pi fetch > /dev/null 2>&1 && git -C ".$home."/BirdNET-Pi status | sed -n '2 p' | cut -d ' ' -f 7"));
}
if(intval($_SESSION['behind']) >= 99) {?>
<style>
.updatenumber {
width:30px !important;
}
</style>
<?php }
?>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<style> <style>
body::-webkit-scrollbar { body::-webkit-scrollbar {
@@ -31,7 +47,7 @@ body::-webkit-scrollbar {
<button type="submit" name="view" value="View Log" form="views">View Log</button> <button type="submit" name="view" value="View Log" form="views">View Log</button>
</form> </form>
<form action="" method="GET" id="views"> <form action="" method="GET" id="views">
<button type="submit" name="view" value="Tools" form="views">Tools</button> <button type="submit" name="view" value="Tools" form="views">Tools<?php if(intval($_SESSION['behind']) >= 50){ $updatediv = ' <div class="updatenumber">'.$_SESSION["behind"].'</div>'; } else { $updatediv = ""; } echo $updatediv; ?></button>
</form> </form>
<button href="javascript:void(0);" class="icon" onclick="myFunction()"><img src="images/menu.png"></button> <button href="javascript:void(0);" class="icon" onclick="myFunction()"><img src="images/menu.png"></button>
</div> </div>
@@ -94,7 +110,7 @@ if(isset($_GET['view'])){
<form action=\"\" method=\"GET\" id=\"views\"> <form action=\"\" method=\"GET\" id=\"views\">
<button type=\"submit\" name=\"view\" value=\"Settings\" form=\"views\">Settings</button> <button type=\"submit\" name=\"view\" value=\"Settings\" form=\"views\">Settings</button>
<button type=\"submit\" name=\"view\" value=\"System Info\" form=\"views\">System Info</button> <button type=\"submit\" name=\"view\" value=\"System Info\" form=\"views\">System Info</button>
<button type=\"submit\" name=\"view\" value=\"System Controls\" form=\"views\">System Controls</button> <button type=\"submit\" name=\"view\" value=\"System Controls\" form=\"views\">System Controls".$updatediv."</button>
<button type=\"submit\" name=\"view\" value=\"Services\" form=\"views\">Services</button> <button type=\"submit\" name=\"view\" value=\"Services\" form=\"views\">Services</button>
<button type=\"submit\" name=\"view\" value=\"File\" form=\"views\">File Manager</button> <button type=\"submit\" name=\"view\" value=\"File\" form=\"views\">File Manager</button>
<a href=\"scripts/adminer.php\" target=\"_top\"><button type=\"submit\" form=\"\">Database Maintenance</button></a> <a href=\"scripts/adminer.php\" target=\"_top\"><button type=\"submit\" form=\"\">Database Maintenance</button></a>
@@ -264,10 +280,14 @@ if(isset($_GET['view'])){
$tmp = explode(" ",trim($command)); $tmp = explode(" ",trim($command));
$command .= "& sleep 3;sudo systemctl status ".end($tmp); $command .= "& sleep 3;sudo systemctl status ".end($tmp);
} }
if($initcommand == "update_birdnet.sh") {
unset($_SESSION['behind']);
}
$results = shell_exec("$command 2>&1"); $results = shell_exec("$command 2>&1");
$results = str_replace("FAILURE", "<span style='color:red'>FAILURE</span>", $results); $results = str_replace("FAILURE", "<span style='color:red'>FAILURE</span>", $results);
$results = str_replace("failed", "<span style='color:red'>failed</span>",$results); $results = str_replace("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("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);
if(strlen($results) == 0) { if(strlen($results) == 0) {
$results = "This command has no output."; $results = "This command has no output.";
} }
Binary file not shown.
+141 -103
View File
@@ -2,89 +2,112 @@
error_reporting(E_ERROR); error_reporting(E_ERROR);
ini_set('display_errors',1); ini_set('display_errors',1);
# Basic Settings function syslog_shell_exec($cmd, $sudo_user = null) {
if(isset($_GET["latitude"])){ if ($sudo_user) {
$latitude = $_GET["latitude"]; $cmd = "sudo -u $sudo_user $cmd";
$longitude = $_GET["longitude"]; }
$birdweather_id = $_GET["birdweather_id"]; $output = shell_exec($cmd);
$apprise_input = $_GET['apprise_input'];
$apprise_notification_title = $_GET['apprise_notification_title'];
$apprise_notification_body = $_GET['apprise_notification_body'];
$flickr_api_key = $_GET['flickr_api_key'];
if(isset($_GET['apprise_notify_each_detection'])) {
$apprise_notify_each_detection = 1;
} else {
$apprise_notify_each_detection = 0;
}
if(isset($_GET['apprise_notify_new_species'])) {
$apprise_notify_new_species = 1;
} else {
$apprise_notify_new_species = 0;
}
// logic for setting the date and time based on user inputs from the form below if (strlen($output) > 0) {
if(isset($_GET['date']) && isset($_GET['time'])) { syslog(LOG_INFO, $output);
// can't set the date manually if it's getting it from the internet, disable ntp
exec("sudo timedatectl set-ntp false");
exec("sudo date -s '".$_GET['date']." ".$_GET['time']."'");
} else {
// user checked 'use time from internet if available,' so make sure that's on
if(strlen(trim(exec("sudo timedatectl | grep \"NTP service: active\""))) == 0){
exec("sudo timedatectl set-ntp true");
sleep(3);
} }
} }
# Basic Settings
if(isset($_GET["latitude"])){
$latitude = $_GET["latitude"];
$longitude = $_GET["longitude"];
$birdweather_id = $_GET["birdweather_id"];
$apprise_input = $_GET['apprise_input'];
$apprise_notification_title = $_GET['apprise_notification_title'];
$apprise_notification_body = $_GET['apprise_notification_body'];
$flickr_api_key = $_GET['flickr_api_key'];
$language = $_GET["language"];
if(isset($_GET['apprise_notify_each_detection'])) {
$apprise_notify_each_detection = 1;
} else {
$apprise_notify_each_detection = 0;
}
if(isset($_GET['apprise_notify_new_species'])) {
$apprise_notify_new_species = 1;
} else {
$apprise_notify_new_species = 0;
}
// logic for setting the date and time based on user inputs from the form below
if(isset($_GET['date']) && isset($_GET['time'])) {
// can't set the date manually if it's getting it from the internet, disable ntp
exec("sudo timedatectl set-ntp false");
exec("sudo date -s '".$_GET['date']." ".$_GET['time']."'");
} else {
// user checked 'use time from internet if available,' so make sure that's on
if(strlen(trim(exec("sudo timedatectl | grep \"NTP service: active\""))) == 0){
exec("sudo timedatectl set-ntp true");
sleep(3);
}
}
// Update Language settings only if a change is requested
if (file_exists('./scripts/thisrun.txt')) {
$lang_config = parse_ini_file('./scripts/thisrun.txt');
} elseif (file_exists('./scripts/firstrun.ini')) {
$lang_config = parse_ini_file('./scripts/firstrun.ini');
}
if ($language != $lang_config['DATABASE_LANG']){
$user = trim(shell_exec("awk -F: '/1000/{print $1}' /etc/passwd"));
$home = trim(shell_exec("awk -F: '/1000/{print $6}' /etc/passwd"));
// Archive old language file
syslog_shell_exec("cp -f $home/BirdNET-Pi/model/labels.txt $home/BirdNET-Pi/model/labels.txt.old", $user);
// Install new language label file
syslog_shell_exec("$home/BirdNET-Pi/scripts/install_language_label.sh -l $language", $user);
syslog(LOG_INFO, "Successfully changed language to '$language'");
}
$contents = file_get_contents("/etc/birdnet/birdnet.conf");
$contents = preg_replace("/LATITUDE=.*/", "LATITUDE=$latitude", $contents);
$contents = preg_replace("/LONGITUDE=.*/", "LONGITUDE=$longitude", $contents);
$contents = preg_replace("/BIRDWEATHER_ID=.*/", "BIRDWEATHER_ID=$birdweather_id", $contents);
$contents = preg_replace("/APPRISE_NOTIFICATION_TITLE=.*/", "APPRISE_NOTIFICATION_TITLE=\"$apprise_notification_title\"", $contents);
$contents = preg_replace("/APPRISE_NOTIFICATION_BODY=.*/", "APPRISE_NOTIFICATION_BODY=\"$apprise_notification_body\"", $contents);
$contents = preg_replace("/APPRISE_NOTIFY_EACH_DETECTION=.*/", "APPRISE_NOTIFY_EACH_DETECTION=$apprise_notify_each_detection", $contents);
$contents = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES=.*/", "APPRISE_NOTIFY_NEW_SPECIES=$apprise_notify_new_species", $contents);
$contents = preg_replace("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents);
$contents = preg_replace("/DATABASE_LANG=.*/", "DATABASE_LANG=$language", $contents);
$contents = file_get_contents("/etc/birdnet/birdnet.conf"); $contents2 = file_get_contents("./scripts/thisrun.txt");
$contents = preg_replace("/LATITUDE=.*/", "LATITUDE=$latitude", $contents); $contents2 = preg_replace("/LATITUDE=.*/", "LATITUDE=$latitude", $contents2);
$contents = preg_replace("/LONGITUDE=.*/", "LONGITUDE=$longitude", $contents); $contents2 = preg_replace("/LONGITUDE=.*/", "LONGITUDE=$longitude", $contents2);
$contents = preg_replace("/BIRDWEATHER_ID=.*/", "BIRDWEATHER_ID=$birdweather_id", $contents); $contents2 = preg_replace("/BIRDWEATHER_ID=.*/", "BIRDWEATHER_ID=$birdweather_id", $contents2);
$contents = preg_replace("/APPRISE_NOTIFICATION_TITLE=.*/", "APPRISE_NOTIFICATION_TITLE=\"$apprise_notification_title\"", $contents); $contents2 = preg_replace("/APPRISE_NOTIFICATION_TITLE=.*/", "APPRISE_NOTIFICATION_TITLE=\"$apprise_notification_title\"", $contents2);
$contents = preg_replace("/APPRISE_NOTIFICATION_BODY=.*/", "APPRISE_NOTIFICATION_BODY=\"$apprise_notification_body\"", $contents); $contents2 = preg_replace("/APPRISE_NOTIFICATION_BODY=.*/", "APPRISE_NOTIFICATION_BODY=\"$apprise_notification_body\"", $contents2);
$contents = preg_replace("/APPRISE_NOTIFY_EACH_DETECTION=.*/", "APPRISE_NOTIFY_EACH_DETECTION=$apprise_notify_each_detection", $contents); $contents2 = preg_replace("/APPRISE_NOTIFY_EACH_DETECTION=.*/", "APPRISE_NOTIFY_EACH_DETECTION=$apprise_notify_each_detection", $contents2);
$contents = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES=.*/", "APPRISE_NOTIFY_NEW_SPECIES=$apprise_notify_new_species", $contents); $contents2 = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES=.*/", "APPRISE_NOTIFY_NEW_SPECIES=$apprise_notify_new_species", $contents2);
$contents = preg_replace("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents); $contents2 = preg_replace("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents2);
$contents2 = preg_replace("/DATABASE_LANG=.*/", "DATABASE_LANG=$language", $contents2);
$contents2 = file_get_contents("./scripts/thisrun.txt"); $fh = fopen("/etc/birdnet/birdnet.conf", "w");
$contents2 = preg_replace("/LATITUDE=.*/", "LATITUDE=$latitude", $contents2); $fh2 = fopen("./scripts/thisrun.txt", "w");
$contents2 = preg_replace("/LONGITUDE=.*/", "LONGITUDE=$longitude", $contents2); fwrite($fh, $contents);
$contents2 = preg_replace("/BIRDWEATHER_ID=.*/", "BIRDWEATHER_ID=$birdweather_id", $contents2); fwrite($fh2, $contents2);
$contents2 = preg_replace("/APPRISE_NOTIFICATION_TITLE=.*/", "APPRISE_NOTIFICATION_TITLE=\"$apprise_notification_title\"", $contents2);
$contents2 = preg_replace("/APPRISE_NOTIFICATION_BODY=.*/", "APPRISE_NOTIFICATION_BODY=\"$apprise_notification_body\"", $contents2);
$contents2 = preg_replace("/APPRISE_NOTIFY_EACH_DETECTION=.*/", "APPRISE_NOTIFY_EACH_DETECTION=$apprise_notify_each_detection", $contents2);
$contents2 = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES=.*/", "APPRISE_NOTIFY_NEW_SPECIES=$apprise_notify_new_species", $contents2);
$contents2 = preg_replace("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents2);
if(isset($apprise_input)){
$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd");
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
$home = trim($home);
$fh = fopen("/etc/birdnet/birdnet.conf", "w"); $appriseconfig = fopen($home."/BirdNET-Pi/apprise.txt", "w");
$fh2 = fopen("./scripts/thisrun.txt", "w"); fwrite($appriseconfig, $apprise_input);
fwrite($fh, $contents); }
fwrite($fh2, $contents2);
if(isset($apprise_input)){ syslog(LOG_INFO, "Restarting Services");
$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd"); shell_exec("sudo restart_services.sh");
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
$home = trim($home);
$appriseconfig = fopen($home."/BirdNET-Pi/apprise.txt", "w");
fwrite($appriseconfig, $apprise_input);
}
$language = $_GET["language"];
if ($language != "none"){
$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd");
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
$home = trim($home);
$command = "sudo -u".$user." mv ".$home."/BirdNET-Pi/model/labels.txt ".$home."/BirdNET-Pi/model/labels.txt.old && sudo -u".$user." unzip ".$home."/BirdNET-Pi/model/labels_l18n.zip ".$language." -d ".$home."/BirdNET-Pi/model && sudo -u".$user." mv ".$home."/BirdNET-Pi/model/".$language." ".$home."/BirdNET-Pi/model/labels.txt";
$command_output = `$command`;
`sudo restart_services.sh`;
}
} }
?> ?>
@@ -94,7 +117,7 @@ if ($language != "none"){
</head> </head>
<div class="settings"> <div class="settings">
<h2>Basic Settings</h2> <h2>Basic Settings</h2>
<form action="" method="GET"> <form id="basicform" action="" method="GET">
<?php <?php
if (file_exists('./scripts/thisrun.txt')) { if (file_exists('./scripts/thisrun.txt')) {
$config = parse_ini_file('./scripts/thisrun.txt'); $config = parse_ini_file('./scripts/thisrun.txt');
@@ -157,36 +180,51 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}
<h3>Localization</h3> <h3>Localization</h3>
<label for="language">Database Language: </label> <label for="language">Database Language: </label>
<select name="language"> <select name="language">
<option value="none">Select your language</option> <?php
<option value="labels_af.txt">Afrikaans</option> $langs = array(
<option value="labels_ca.txt">Catalan</option> 'not-selected' => 'Not Selected',
<option value="labels_cs.txt">Czech</option> "af" => "Afrikaans",
<option value="labels_zh.txt">Chinese</option> "ca" => "Catalan",
<option value="labels_hr.txt">Croatian</option> "cs" => "Czech",
<option value="labels_da.txt">Danish</option> "zh" => "Chinese",
<option value="labels_nl.txt">Dutch</option> "hr" => "Croatian",
<option value="labels_en.txt">English</option> "da" => "Danish",
<option value="labels_et.txt">Estonian</option> "nl" => "Dutch",
<option value="labels_fi.txt">Finnish</option> "en" => "English",
<option value="labels_fr.txt">French</option> "et" => "Estonian",
<option value="labels_de.txt">German</option> "fi" => "Finnish",
<option value="labels_hu.txt">Hungarian</option> "fr" => "French",
<option value="labels_is.txt">Icelandic</option> "de" => "German",
<option value="labels_id.txt">Indonesia</option> "hu" => "Hungarian",
<option value="labels_it.txt">Italian</option> "is" => "Icelandic",
<option value="labels_ja.txt">Japanese</option> "id" => "Indonesia",
<option value="labels_lv.txt">Latvian</option> "it" => "Italian",
<option value="labels_lt.txt">Lithuania</option> "ja" => "Japanese",
<option value="labels_no.txt">Norwegian</option> "lv" => "Latvian",
<option value="labels_pl.txt">Polish</option> "lt" => "Lithuania",
<option value="labels_pt.txt">Portugues</option> "no" => "Norwegian",
<option value="labels_ru.txt">Russian</option> "pl" => "Polish",
<option value="labels_sk.txt">Slovak</option> "pt" => "Portugues",
<option value="labels_sl.txt">Slovenian</option> "ru" => "Russian",
<option value="labels_es.txt">Spanish</option> "sk" => "Slovak",
<option value="labels_sv.txt">Swedish</option> "sl" => "Slovenian",
<option value="labels_th.txt">Thai</option> "es" => "Spanish",
<option value="labels_uk.txt">Ukrainian</option> "sv" => "Swedish",
"th" => "Thai",
"uk" => "Ukrainian"
);
// Create options for each language
foreach($langs as $langTag => $langName){
$isSelected = "";
if($config['DATABASE_LANG'] == $langTag){
$isSelected = 'selected="selected"';
}
echo "<option value='{$langTag}' $isSelected>$langName</option>";
}
?>
</select> </select>
<br><br> <br><br>
<script> <script>
@@ -223,7 +261,7 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}
<input type="hidden" name="status" value="success"> <input type="hidden" name="status" value="success">
<input type="hidden" name="submit" value="settings"> <input type="hidden" name="submit" value="settings">
<button type="submit" name="view" value="Settings"> <button type="submit" id="basicformsubmit" onclick="if(document.getElementById('basicform').checkValidity()){this.innerHTML = 'Updating... please wait.';this.classList.add('disabled')}" name="view" value="Settings">
<?php <?php
if(isset($_GET['status'])){ if(isset($_GET['status'])){
echo "Success!"; echo "Success!";
+5 -7
View File
@@ -31,14 +31,12 @@ install_birdnet() {
pip3 install -U -r $HOME/BirdNET-Pi/requirements.txt pip3 install -U -r $HOME/BirdNET-Pi/requirements.txt
} }
unpack_labels() {
unzip $HOME/BirdNET-Pi/model/labels_l18n.zip labels_en.txt \
-d $HOME/BirdNET-Pi/model
mv $HOME/BirdNET-Pi/model/labels_en.txt $HOME/BirdNET-Pi/model/labels.txt
}
[ -d ${RECS_DIR} ] || mkdir -p ${RECS_DIR} &> /dev/null [ -d ${RECS_DIR} ] || mkdir -p ${RECS_DIR} &> /dev/null
install_birdnet install_birdnet
unpack_labels
cd $my_dir/scripts || exit 1
./install_language_label.sh -l $DATABASE_LANG || exit 1
exit 0 exit 0
+2
View File
@@ -163,6 +163,8 @@ EXTRACTION_LENGTH=
AUDIOFMT=mp3 AUDIOFMT=mp3
## DATABASE_LANG is the language used for the bird species database
DATABASE_LANG=en
## These are just for debugging ## These are just for debugging
LAST_RUN= LAST_RUN=
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
usage() { echo "Usage: $0 -l <language i18n id>" 1>&2; exit 1; }
while getopts "l:" o; do
case "${o}" in
l)
lang=${OPTARG}
;;
*)
usage
;;
esac
done
shift $((OPTIND-1))
HOME=$(awk -F: '/1000/ {print $6}' /etc/passwd)
label_file_name="labels_${lang}.txt"
unzip -o $HOME/BirdNET-Pi/model/labels_l18n.zip $label_file_name \
-d $HOME/BirdNET-Pi/model \
&& mv -f $HOME/BirdNET-Pi/model/$label_file_name $HOME/BirdNET-Pi/model/labels.txt \
&& logger "[$0] Changed language label file to '$label_file_name'";
exit 0
+2 -2
View File
@@ -5,11 +5,11 @@ source /etc/birdnet/birdnet.conf
if [ -z ${REC_CARD} ];then if [ -z ${REC_CARD} ];then
echo "Stream not supported" echo "Stream not supported"
elif [[ ! -z ${RTSP_STREAM} ]];then elif [[ ! -z ${RTSP_STREAM} ]];then
ffmpeg -loglevel 52 -ac ${CHANNELS} -i ${RTSP_STREAM} -acodec libmp3lame \ ffmpeg -loglevel 32 -ac ${CHANNELS} -i ${RTSP_STREAM} -acodec libmp3lame \
-b:a 320k -ac ${CHANNELS} -content_type 'audio/mpeg' \ -b:a 320k -ac ${CHANNELS} -content_type 'audio/mpeg' \
-f mp3 icecast://source:${ICE_PWD}@localhost:8000/stream -re -f mp3 icecast://source:${ICE_PWD}@localhost:8000/stream -re
else else
ffmpeg -loglevel 52 -ac ${CHANNELS} -f alsa -i ${REC_CARD} -acodec libmp3lame \ ffmpeg -loglevel 32 -ac ${CHANNELS} -f alsa -i ${REC_CARD} -acodec libmp3lame \
-b:a 320k -ac ${CHANNELS} -content_type 'audio/mpeg' \ -b:a 320k -ac ${CHANNELS} -content_type 'audio/mpeg' \
-f mp3 icecast://source:${ICE_PWD}@localhost:8000/stream -re -f mp3 icecast://source:${ICE_PWD}@localhost:8000/stream -re
fi fi
+2
View File
@@ -1,4 +1,6 @@
<?php <?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.gc_maxlifetime', 7200); ini_set('session.gc_maxlifetime', 7200);
session_set_cookie_params(7200); session_set_cookie_params(7200);
session_start(); session_start();
+109 -13
View File
@@ -1,3 +1,36 @@
<?php
if(isset($_GET['ajax_csv'])) {
$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd");
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
$home = trim($home);
$files = scandir($home."/BirdSongs/".date('F-Y')."/".date('j-l')."/", SCANDIR_SORT_ASCENDING);
$newest_file = $files[2];
if($newest_file == $_GET['newest_file']) {
die();
}
echo "file,".$newest_file."\n";
$row = 1;
if (($handle = fopen($home."/BirdSongs/".date('F-Y')."/".date('j-l')."/".$newest_file.".csv", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
if($row != 1){
$num = count($data);
for ($c=0; $c < $num; $c++) {
$exp = explode(';',$data[$c]);
echo $exp[0].",".$exp[3].",".$exp[4]."\n";
}
}
$row++;
}
fclose($handle);
}
die();
}
?>
<script> <script>
// CREDITS: https://codepen.io/jakealbaugh/pen/jvQweW // CREDITS: https://codepen.io/jakealbaugh/pen/jvQweW
@@ -7,6 +40,9 @@ var started = null;
var player = null; var player = null;
var gain = 128; var gain = 128;
const ctx = null; const ctx = null;
let fps =[];
let avgfps;
let requestTime;
window.onload = function(){ window.onload = function(){
// if user agent includes iPhone or Mac use legacy mode // if user agent includes iPhone or Mac use legacy mode
if(window.navigator.userAgent.includes("iPhone") || window.navigator.userAgent.includes("Mac")) { if(window.navigator.userAgent.includes("iPhone") || window.navigator.userAgent.includes("Mac")) {
@@ -63,32 +99,83 @@ function fitTextOnCanvas(text,fontface,yPosition){
CTX.fillText(text,document.body.querySelector('canvas').width - (document.body.querySelector('canvas').width * 0.50),yPosition); CTX.fillText(text,document.body.querySelector('canvas').width - (document.body.querySelector('canvas').width * 0.50),yPosition);
} }
function applyText(text) { function applyText(text,x,y,opacity) {
CTX.fillStyle = 'white'; console.log("conf: "+opacity)
CTX.font = '25px Roboto Flex'; console.log(text+" "+parseInt(x)+" "+y)
fitTextOnCanvas(text,"Roboto Flex",document.body.querySelector('canvas').scrollHeight * 0.35) if(opacity < 0.2) {
opacity = 0.2;
}
CTX.textAlign = "center";
CTX.fillStyle = "rgba(255, 255, 255, "+opacity+")";
CTX.font = '15px Roboto Flex';
//fitTextOnCanvas(text,"Roboto Flex",document.body.querySelector('canvas').scrollHeight * 0.35)
CTX.fillText(text,parseInt(x),y)
CTX.fillStyle = 'hsl(280, 100%, 10%)'; CTX.fillStyle = 'hsl(280, 100%, 10%)';
} }
var previous_detection_identifier = null; var add =0;
var newest_file;
var lastbird;
function loadDetectionIfNewExists() { function loadDetectionIfNewExists() {
const xhttp = new XMLHttpRequest(); const xhttp = new XMLHttpRequest();
xhttp.onload = function() { xhttp.onload = function() {
// if there's a new detection that needs to be updated to the page // if there's a new detection that needs to be updated to the page
if(this.responseText.length > 0 && !this.responseText.includes("Database")) { if(this.responseText.length > 0 && !this.responseText.includes("Database")) {
if(previous_detection_identifier != null){
applyText(this.responseText.split(",")[0].replace("_"," ")); var split = this.responseText.split("\n")
for(var i = 1;i < split.length; i++) {
if(parseInt(split[i].split(",")[0]) >= 0){
newest_file = split[0].split(",")[1]
//applyText(split[i].split(",")[1],document.body.querySelector('canvas').width - ((parseInt(split[i].split(",")[0]))*avgfps), (document.body.querySelector('canvas').height * 0.50))
d1 = new Date(newest_file.split("-")[0]+"/"+newest_file.split("-")[1]+"/"+newest_file.split("-")[2]+ " "+newest_file.split("-")[4].replace(".wav",""))
console.log(d1)
d2 = new Date();
timeDiff = (d2-d1)/1000;
// stagger Y placement if a new bird
if(split[i].split(",")[1] != lastbird || split[i].split(",")[1].length > 15) {
if(add >= 80) {
add -= 40;
} else {
add += 20;
}
if(parseFloat(add + document.body.querySelector('canvas').height * 0.50) > document.body.querySelector('canvas').height || parseFloat(add + document.body.querySelector('canvas').height * 0.50) <= 0) {
add = 0;
}
}
// Date csv file was created + relative detection time of bird + mic delay
secago = Math.abs(timeDiff) - split[i].split(",")[0] - 6.8;
x = document.body.querySelector('canvas').width - ((parseInt(secago))*avgfps);
// if the text is too close to the right side of the canvas and will be cut off, wait 3 seconds before adding text
if(x > document.body.querySelector('canvas').width - (3*avgfps)) {
setTimeout(function (split,i,x) {
console.log(split[i])
console.log("originally at "+x+", now waiting 2 sec and at "+(x-(3*avgfps)))
applyText(split[i].split(",")[1],(x - (3*avgfps)), ((document.body.querySelector('canvas').height * 0.50) + add ), split[i].split(",")[2]);
}, 2000, split, i, x)
} else {
applyText(split[i].split(",")[1],x, ((document.body.querySelector('canvas').height * 0.50) + add ), split[i].split(",")[2])
}
lastbird = split[i].split(",")[1]
}
} }
previous_detection_identifier = this.responseText.split(",")[1];
} }
} }
xhttp.open("GET", "overview.php?ajax_detections=true&previous_detection_identifier="+previous_detection_identifier+"&only_name=true", true); xhttp.open("GET", "spectrogram.php?ajax_csv=true&newest_file="+newest_file, true);
xhttp.send(); xhttp.send();
} }
window.setInterval(function(){ window.setInterval(function(){
loadDetectionIfNewExists(); loadDetectionIfNewExists();
}, 2500); }, 500);
function initialize() { function initialize() {
document.body.querySelector('h1').remove(); document.body.querySelector('h1').remove();
@@ -102,7 +189,6 @@ function initialize() {
ANALYSER.fftSize = 2048; ANALYSER.fftSize = 2048;
try{ try{
process(); process();
} catch(e) { } catch(e) {
@@ -122,8 +208,18 @@ function initialize() {
loop(); loop();
function loop() { function loop(time) {
window.requestAnimationFrame(loop); if (requestTime) {
fpsval = Math.round(1000/((performance.now() - requestTime)))
if(fpsval > 0){
fps.push( fpsval);
}
}
if(fps.length > 0){
avgfps = fps.reduce((a, b) => a + b) / fps.length;
}
requestTime = time;
window.requestAnimationFrame((timeRes) => loop(timeRes));
let imgData = CTX.getImageData(1, 0, W - 1, H); let imgData = CTX.getImageData(1, 0, W - 1, H);
CTX.fillRect(0, 0, W, H); CTX.fillRect(0, 0, W, H);
+2 -3
View File
@@ -1,7 +1,6 @@
<?php <?php
ini_set('display_errors', 1); error_reporting(0);
ini_set('display_startup_errors', 1); ini_set('display_errors', 0);
error_reporting(E_ALL);
$db = new SQLite3('./scripts/birds.db', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE); $db = new SQLite3('./scripts/birds.db', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
if($db == False) { if($db == False) {
+1 -1
View File
@@ -7,7 +7,7 @@
<button type="submit" name="submit" value="sudo reboot" onclick="return confirm('Are you sure you want to reboot?')">Reboot</button> <button type="submit" name="submit" value="sudo reboot" onclick="return confirm('Are you sure you want to reboot?')">Reboot</button>
</form> </form>
<form action="" method="GET"> <form action="" method="GET">
<button type="submit" name="submit" value="update_birdnet.sh" onclick="return confirm('Are you sure you want to update?')">Update</button> <button type="submit" name="submit" value="update_birdnet.sh" onclick="return confirm('Are you sure you want to update?')">Update <?php if($_SESSION['behind'] != "0" && $_SESSION['behind'] != "with"){?><div class="updatenumber"><?php echo $_SESSION['behind']; ?></div><?php } ?></button>
</form> </form>
<form action="" method="GET"> <form action="" method="GET">
<button type="submit" name="submit" value="sudo shutdown now" onclick="return confirm('Are you sure you want to shutdown?')">Shutdown</button> <button type="submit" name="submit" value="sudo shutdown now" onclick="return confirm('Are you sure you want to shutdown?')">Shutdown</button>
+3 -4
View File
@@ -2,9 +2,8 @@
ini_set('session.gc_maxlifetime', 7200); ini_set('session.gc_maxlifetime', 7200);
session_set_cookie_params(7200); session_set_cookie_params(7200);
session_start(); session_start();
ini_set('display_errors', 1); error_reporting(0);
ini_set('display_startup_errors', 1); ini_set('display_errors', 0);
error_reporting(E_ALL);
$db = new SQLite3('./scripts/birds.db', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE); $db = new SQLite3('./scripts/birds.db', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
if($db == False){ if($db == False){
@@ -128,7 +127,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
$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)."&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=5&orientation=square,portrait&media=photos&format=json&nojsoncallback=1"), true)["photos"]["photo"][0]; $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)."&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=5&orientation=square,portrait&media=photos&format=json&nojsoncallback=1"), true)["photos"]["photo"][0];
$modaltext = "https://flickr.com/photos/".$flickrjson["owner"]."/".$flickrjson["id"]; $modaltext = "https://flickr.com/photos/".$flickrjson["owner"]."/".$flickrjson["id"];
$authorlink = "https://flickr.com/people/".$flickrjson["owner"]; $authorlink = "https://flickr.com/people/".$flickrjson["owner"];
$imageurl = 'http://farm' .$flickrjson["farm"]. '.static.flickr.com/' .$flickrjson["server"]. '/' .$flickrjson["id"]. '_' .$flickrjson["secret"]. '.jpg'; $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)); array_push($_SESSION['images'], array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink));
$image = $_SESSION['images'][count($_SESSION['images'])-1]; $image = $_SESSION['images'][count($_SESSION['images'])-1];
} }
+15 -8
View File
@@ -16,7 +16,7 @@ if [ -f $my_dir/privacy_server ] || [ -L /usr/local/bin/privacy_server.py ];then
fi fi
# Adds python virtual-env to the python systemd services # Adds python virtual-env to the python systemd services
if ! grep 'BirdNET-Pi/birdnet/' $HOME/BirdNET-Pi/templates/birdnet_server.service || ! grep 'BirdNET-Pi/birdnet' $HOME/BirdNET-Pi/templates/chart_viewer.service;then if ! grep 'BirdNET-Pi/birdnet/' $HOME/BirdNET-Pi/templates/birdnet_server.service &>/dev/null || ! grep 'BirdNET-Pi/birdnet' $HOME/BirdNET-Pi/templates/chart_viewer.service &>/dev/null;then
sudo -E sed -i "s|ExecStart=.*|ExecStart=$HOME/BirdNET-Pi/birdnet/bin/python3 /usr/local/bin/server.py|" ~/BirdNET-Pi/templates/birdnet_server.service sudo -E sed -i "s|ExecStart=.*|ExecStart=$HOME/BirdNET-Pi/birdnet/bin/python3 /usr/local/bin/server.py|" ~/BirdNET-Pi/templates/birdnet_server.service
sudo -E sed -i "s|ExecStart=.*|ExecStart=$HOME/BirdNET-Pi/birdnet/bin/python3 /usr/local/bin/daily_plot.py|" ~/BirdNET-Pi/templates/chart_viewer.service sudo -E sed -i "s|ExecStart=.*|ExecStart=$HOME/BirdNET-Pi/birdnet/bin/python3 /usr/local/bin/daily_plot.py|" ~/BirdNET-Pi/templates/chart_viewer.service
sudo systemctl daemon-reload && restart_services.sh sudo systemctl daemon-reload && restart_services.sh
@@ -40,6 +40,14 @@ fi
if ! grep APPRISE_NOTIFY_NEW_SPECIES /etc/birdnet/birdnet.conf &>/dev/null;then if ! grep APPRISE_NOTIFY_NEW_SPECIES /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "APPRISE_NOTIFY_NEW_SPECIES=0 " >> /etc/birdnet/birdnet.conf sudo -u$USER echo "APPRISE_NOTIFY_NEW_SPECIES=0 " >> /etc/birdnet/birdnet.conf
fi fi
# If the config does not contain the DATABASE_LANG setting, we'll want to add it.
# Defaults to not-selected, which config.php will know to render as a language option.
# The user can then select a language in the web interface and update with that.
if ! grep DATABASE_LANG /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "DATABASE_LANG=not-selected" >> /etc/birdnet/birdnet.conf
fi
apprise_installation_status=$(~/BirdNET-Pi/birdnet/bin/python3 -c 'import pkgutil; print("installed" if pkgutil.find_loader("apprise") else "not installed")') apprise_installation_status=$(~/BirdNET-Pi/birdnet/bin/python3 -c 'import pkgutil; print("installed" if pkgutil.find_loader("apprise") else "not installed")')
if [[ "$apprise_installation_status" = "not installed" ]];then if [[ "$apprise_installation_status" = "not installed" ]];then
~/BirdNET-Pi/birdnet/bin/pip3 install -U pip ~/BirdNET-Pi/birdnet/bin/pip3 install -U pip
@@ -52,7 +60,7 @@ fi
if ! grep RTSP_STREAM /etc/birdnet/birdnet.conf &>/dev/null;then if ! grep RTSP_STREAM /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "RTSP_STREAM=" >> /etc/birdnet/birdnet.conf sudo -u$USER echo "RTSP_STREAM=" >> /etc/birdnet/birdnet.conf
fi fi
if grep bash $HOME/BirdNET-Pi/templates/web_terminal.service;then if grep bash $HOME/BirdNET-Pi/templates/web_terminal.service &>/dev/null;then
sudo sed -i '/User/d;s/bash/login/g' $HOME/BirdNET-Pi/templates/web_terminal.service sudo sed -i '/User/d;s/bash/login/g' $HOME/BirdNET-Pi/templates/web_terminal.service
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl restart web_terminal.service sudo systemctl restart web_terminal.service
@@ -61,16 +69,15 @@ fi
if ! grep FLICKR_API_KEY /etc/birdnet/birdnet.conf &>/dev/null;then if ! grep FLICKR_API_KEY /etc/birdnet/birdnet.conf &>/dev/null;then
sudo -u$USER echo "FLICKR_API_KEY=" >> /etc/birdnet/birdnet.conf sudo -u$USER echo "FLICKR_API_KEY=" >> /etc/birdnet/birdnet.conf
fi fi
if systemctl list-unit-files pushed_notifications.service;then if systemctl list-unit-files pushed_notifications.service &>/dev/null;then
sudo systemctl disable --now pushed_notifications.service sudo systemctl disable --now pushed_notifications.service
sudo rm -f /usr/lib/systemd/system/pushed_notifications.service sudo rm -f /usr/lib/systemd/system/pushed_notifications.service
sudo rm $HOME/BirdNET-Pi/templates/pushed_notifications.service sudo rm $HOME/BirdNET-Pi/templates/pushed_notifications.service
fi fi
if [ ! -f $HOME/BirdNET-Pi/model/labels.txt ]
then if [ ! -f $HOME/BirdNET-Pi/model/labels.txt ];then
unzip $HOME/BirdNET-Pi/model/labels_l18n.zip labels_en.txt \ $my_dir/install_language_label.sh -l $DATABASE_LANG \
-d $HOME/BirdNET-Pi/model && logger "[$0] Installed new language label file for '$DATABASE_LANG'";
mv $HOME/BirdNET-Pi/model/labels_en.txt $HOME/BirdNET-Pi/model/labels.txt
fi fi
sudo systemctl daemon-reload sudo systemctl daemon-reload
+1 -1
View File
@@ -114,7 +114,7 @@ fi
cd ~/BirdNET-Pi cd ~/BirdNET-Pi
function remove() { function remove() {
source /etc/birdnet/birdnet.conf source /etc/birdnet/birdnet.conf
find $RECS_DIR -iname *${1}* -exec rm -drfv {} + find $EXTRACTED -iname *${1}* -exec rm -drfv {} +
} }
alias rerc="source ~/.bashrc" alias rerc="source ~/.bashrc"
alias vimrc="vim ~/.bashrc" alias vimrc="vim ~/.bashrc"