On the fly species converter

This commit is contained in:
Alexandre
2024-05-21 14:41:17 +02:00
committed by GitHub
parent b750e8e8b9
commit f56385f0e4
9 changed files with 4 additions and 2541 deletions

View File

@@ -81,19 +81,19 @@ if bashio::config.true "SPECIES_CONVERTER"; then
# Remove the extracted lines from the original file
sed -i '/if(\$_GET\['\''view'\''\] == "File"){/,$d' "$HOME"/BirdNET-Pi/homepage/views.php
# Add new text
cat "/helpers/convert_list/views.add" >> "$HOME"/BirdNET-Pi/homepage/views.php
cat "/helpers/views.add" >> "$HOME"/BirdNET-Pi/homepage/views.php
cat "$HOME"/BirdNET-Pi/homepage/views.php.temp >> "$HOME"/BirdNET-Pi/homepage/views.php
# Clean up: Remove the temporary file
rm "$HOME"/BirdNET-Pi/homepage/views.php.temp
fi
# Add the converter script
if [ ! -f "$HOME"/BirdNET-Pi/scripts/convert_list.php ]; then
mv -f /helpers/convert_list/convert_list.php "$HOME"/BirdNET-Pi/scripts/convert_list.php
mv -f /helpers/convert_list.php "$HOME"/BirdNET-Pi/scripts/convert_list.php
chown pi:pi "$HOME"/BirdNET-Pi/scripts/convert_list.php
chmod 664 "$HOME"/BirdNET-Pi/scripts/convert_list.php
fi
# Change server
if ! grep -q "converted_entry" "$HOME"/BirdNET-Pi/scripts/server.py; then
sed -i "/INTERPRETER, M_INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST/c INTERPRETER, M_INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST, CONVERT_LIST = (None, None, None, None, None)" "$HOME"/BirdNET-Pi/scripts/server.py

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 341.874 341.874" xml:space="preserve">
<path d="M341.338,56.544c-0.865-1.748-2.654-2.834-4.669-2.834c-0.243,0-0.471,0.016-0.662,0.035l-13.201,0.573l10.858-10.302
l0.248-0.132l2.895-3.426l-1.536-3.02c-0.887-1.743-2.67-2.826-4.654-2.826c-0.489,0-0.928,0.066-1.289,0.15l-35.572,6.485
c-0.648-0.844-1.308-1.672-1.984-2.479c-12.529-14.959-29.409-22.865-48.814-22.865c-4.852,0-9.917,0.497-15.056,1.476
c-36.111,6.882-53.736,34.2-72.396,63.122c-24.924,38.632-50.697,78.579-124.683,78.579c-6.337,0-13.028-0.301-19.886-0.896
c-0.695-0.061-1.358-0.091-1.97-0.091c-3.851,0-6.544,1.251-8.006,3.717c-2.208,3.727-0.062,7.647,0.969,9.531l0.142,0.261
c13.907,25.674,34.957,47.705,60.9,63.712c23.211,14.321,49.99,23.099,76.99,25.806v52.677c0,6.747,5.517,12.171,12.265,12.171
h28.832c0.799,0,1.593-0.092,2.373-0.243c0.783,0.158,1.593,0.243,2.422,0.243h28.832c3.66,0,7.256-1.609,9.62-4.359
c2.116-2.461,3.024-5.496,2.556-8.58c-1.294-8.509-12.61-11.532-22.768-11.532c-2.314,0-6.642,0.184-11.032,1.307l3.213-44.469
c3.401-0.65,6.804-1.365,10.205-2.186c46.987-11.342,72.971-42.049,86.494-65.814c16.654-29.266,23.972-64.827,20.076-97.568
c-0.326-2.739-0.727-5.427-1.202-8.063l27.382-21.343c1.025-0.608,1.824-1.513,2.262-2.59
C342.051,59.4,341.991,57.852,341.338,56.544z M173.964,301.607c-1-0.067-2.282-0.101-3.326-0.101c-2.314,0-6.727,0.18-11.117,1.303
l3.079-40.844c3.728-0.08,7.365-0.271,11.365-0.568V301.607z M137.724,201.387c-15.404,10.814-31.967,11.775-41.318-4.436
c-10.372,3.407-21.528,2.202-26.284-7.327c-1.491-2.988,0.775-5.469,2.189-5.541c52.375-2.654,99.886-43.521,118.922-86.605
c1.398-3.165,5.691-3.562,6.524-0.52C212.89,152.204,194.946,219.858,137.724,201.387z M242.354,87.651
c-9.213,0-16.682-7.469-16.682-16.682s7.469-16.682,16.682-16.682c9.213,0,16.682,7.469,16.682,16.682
S251.567,87.651,242.354,87.651z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,117 +0,0 @@
#!/bin/bash
# This scripts allows to change the identification of a Birdnet-pi detection
#################
# SET VARIABLES #
#################
HOME="/home/pi"
source /etc/birdnet/birdnet.conf &>/dev/null
# Get arguments
OLDNAME="$1" #OLDNAME="Mésange_charbonnière-78-2024-05-02-birdnet-RTSP_1-18:14:08.mp3"
NEWNAME="$2" #NEWNAME="Lapinus atricapilla_Lapinu à tête noire"
# Set log level
OUTPUT_TYPE="${3:-debug}" # Set 3rd argument to debug to have all outputs
# Ask for user input if no arguments
if [ -z "$OLDNAME" ]; then read -r -p 'OLDNAME (finishing by mp3): ' OLDNAME; fi
if [ -z "$NEWNAME" ]; then read -r -p 'NEWNAME (sciname_commoname): ' NEWNAME; fi
# Fixed values
LABELS_FILE="$HOME/BirdNET-Pi/model/labels.txt"
DB_FILE="$HOME/BirdNET-Pi/scripts/birds.db"
DETECTIONS_TABLE="detections"
###################
# VALIDITY CHECKS #
###################
# Check if files exist
if [ ! -f "$LABELS_FILE" ]; then echo "$LABELS_FILE doesn't exist, exiting" && exit 1; fi
if [ ! -f "$DB_FILE" ]; then echo "$DB_FILE doesn't exist, exiting" && exit 1; fi
# Check if inputs are valid
if [[ "$1" != *".mp3" ]]; then
echo "The first argument should be a filename starting with the common name of the bird and finishing by mp3!"
echo "Instead, it is : $1"
exit 1
elif [[ "$2" != *"_"* ]]; then
echo "The second argument should be in the format : \"scientific name_common name\""
echo "Instead, it is : $2"
exit 1
fi
# Check if $NEWNAME is found in the file $LABELS_FILE
if ! grep -q "$NEWNAME" "$LABELS_FILE"; then
echo "Error: $NEWNAME not found in $LABELS_FILE"
exit 1
fi
# Check if the common name as the same as the first
OLDNAME_space="${OLDNAME//_/ }"
if [[ "${OLDNAME_space%%-*}" == "${NEWNAME#*_}" ]]; then
echo "Error: $OLDNAME has the same common name as $NEWNAME"
exit 1
fi
##################
# EXECUTE SCRIPT #
##################
# Intro
[[ "$OUTPUT_TYPE" == "debug" ]] && echo "Starting to modify $OLDNAME to $NEWNAME"
# Get the line where the column "File_Name" matches exactly $OLDNAME
IFS='|' read -r OLDNAME_sciname OLDNAME_comname OLDNAME_date < <(sqlite3 "$DB_FILE" "SELECT Sci_Name, Com_Name, Date FROM $DETECTIONS_TABLE WHERE File_Name = '$OLDNAME' LIMIT 1;")
if [[ -z "$OLDNAME_sciname" ]]; then
echo "Error: No line matching $OLDNAME in $DB_FILE"
exit 1
fi
# Extract the part before the _ from $NEWNAME
NEWNAME_comname="${NEWNAME#*_}"
NEWNAME_sciname="${NEWNAME%%_*}"
# Replace spaces with underscores
NEWNAME_comname2="${NEWNAME_comname// /_}"
OLDNAME_comname2="${OLDNAME_comname// /_}"
# Replace OLDNAME_comname2 with NEWNAME_comname2 in OLDNAME
NEWNAME_filename="${OLDNAME//$OLDNAME_comname2/$NEWNAME_comname2}"
[[ "$OUTPUT_TYPE" == "debug" ]] && echo "This script will change the identification $OLDNAME from $OLDNAME_comname to ${NEWNAME#*_}"
########################
# EXECUTE : MOVE FILES #
########################
# Check if the file exists
FILE_PATH="$HOME/BirdSongs/Extracted/By_Date/$OLDNAME_date/$OLDNAME_comname2/$OLDNAME"
if [[ -f $FILE_PATH ]]; then
# Ensure the new directory exists
NEW_DIR="$HOME/BirdSongs/Extracted/By_Date/$OLDNAME_date/$NEWNAME_comname2"
mkdir -p "$NEW_DIR"
# Move and rename the file
mv "$FILE_PATH" "$NEW_DIR/$NEWNAME_filename"
mv "$FILE_PATH".png "$NEW_DIR/$NEWNAME_filename".png
[[ "$OUTPUT_TYPE" == "debug" ]] && echo "Files moved!"
else
echo "Error: File $FILE_PATH does not exist"
fi
###################################
# EXECUTE : UPDATE DATABASE FILES #
###################################
# Update the database
sqlite3 "$DB_FILE" "UPDATE $DETECTIONS_TABLE SET Sci_Name = '$NEWNAME_sciname', Com_Name = '$NEWNAME_comname', File_Name = '$NEWNAME_filename' WHERE File_Name = '$OLDNAME';"
[[ "$OUTPUT_TYPE" == "debug" ]] && echo "Database entry removed"
[[ "$OUTPUT_TYPE" == "debug" ]] && echo "All done!"

View File

@@ -1,705 +0,0 @@
<?php
/* Prevent XSS input */
$_GET = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING);
$_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);
error_reporting(E_ERROR);
ini_set('display_errors',1);
require_once 'scripts/common.php';
$home = get_home();
$config = get_config();
$db = new SQLite3('./scripts/birds.db', SQLITE3_OPEN_READONLY);
$db->busyTimeout(1000);
if(isset($_GET['deletefile'])) {
ensure_authenticated('You must be authenticated to delete files.');
if (preg_match('~^.*(\.\.\/).+$~', $_GET['deletefile'])) {
echo "Error";
die();
}
$db_writable = new SQLite3('./scripts/birds.db', SQLITE3_OPEN_READWRITE);
$db->busyTimeout(1000);
$statement1 = $db_writable->prepare('DELETE FROM detections WHERE File_Name = :file_name LIMIT 1');
ensure_db_ok($statement1);
$statement1->bindValue(':file_name', explode("/", $_GET['deletefile'])[2]);
$file_pointer = $home."/BirdSongs/Extracted/By_Date/".$_GET['deletefile'];
if (!exec("sudo rm $file_pointer 2>&1 && sudo rm $file_pointer.png 2>&1", $output)) {
echo "OK";
} else {
echo "Error - file deletion failed : " . implode(", ", $output) . "<br>";
}
$result1 = $statement1->execute();
if ($result1 === false || $db_writable->changes() === 0) {
echo "Error - database line deletion failed : " . $db_writable->lastErrorMsg();
}
$db_writable->close();
die();
}
if(isset($_GET['excludefile'])) {
ensure_authenticated('You must be authenticated to change the protection of files.');
if(!file_exists($home."/BirdNET-Pi/scripts/disk_check_exclude.txt")) {
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "##start\n##end\n");
}
if(isset($_GET['exclude_add'])) {
$myfile = fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "a") or die("Unable to open file!");
$txt = $_GET['excludefile'];
fwrite($myfile, $txt."\n");
fwrite($myfile, $txt.".png\n");
fclose($myfile);
echo "OK";
die();
} else {
$lines = file($home."/BirdNET-Pi/scripts/disk_check_exclude.txt");
$search = $_GET['excludefile'];
$result = '';
foreach($lines as $line) {
if(stripos($line, $search) === false && stripos($line, $search.".png") === false) {
$result .= $line;
}
}
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", $result);
echo "OK";
die();
}
}
if(isset($_GET['getlabels'])) {
$labels = file('/home/pi/BirdNET-Pi/model/labels.txt', FILE_IGNORE_NEW_LINES);
echo json_encode($labels);
die();
}
if(isset($_GET['changefile']) && isset($_GET['newname'])) {
ensure_authenticated('You must be authenticated to delete files.');
if (preg_match('~^.*(\.\.\/).+$~', $_GET['changefile'])) {
echo "Error";
die();
}
$oldname = basename(urldecode($_GET['changefile']));
$newname = urldecode($_GET['newname']);
if (!exec("$home/BirdNET-Pi/scripts/birdnet_changeidentification.sh \"$oldname\" \"$newname\" log_errors 2>&1", $output)) {
echo "OK";
} else {
echo "Error : " . implode(", ", $output) . "<br>";
}
die();
}
$shifted_path = $home."/BirdSongs/Extracted/By_Date/shifted/";
if(isset($_GET['shiftfile'])) {
ensure_authenticated('You cannot shift files for this installation');
$filename = $_GET['shiftfile'];
$pp = pathinfo($filename);
$dir = $pp['dirname'];
$fn = $pp['filename'];
$ext = $pp['extension'];
$pi = $home."/BirdSongs/Extracted/By_Date/";
if(isset($_GET['doshift'])) {
$freqshift_tool = $config['FREQSHIFT_TOOL'];
if ($freqshift_tool == "ffmpeg") {
$cmd = "sudo /usr/bin/nohup /usr/bin/ffmpeg -y -i ".escapeshellarg($pi.$filename)." -af \"rubberband=pitch=".$config['FREQSHIFT_LO']."/".$config['FREQSHIFT_HI']."\" ".escapeshellarg($shifted_path.$filename)."";
shell_exec("sudo mkdir -p ".$shifted_path.$dir." && ".$cmd);
} else if ($freqshift_tool == "sox") {
//linux.die.net/man/1/sox
$soxopt = "-q";
$soxpitch = $config['FREQSHIFT_PITCH'];
$cmd = "sudo /usr/bin/nohup /usr/bin/sox ".escapeshellarg($pi.$filename)." ".escapeshellarg($shifted_path.$filename)." pitch ".$soxopt." ".$soxpitch;
shell_exec("sudo mkdir -p ".$shifted_path.$dir." && ".$cmd);
}
} else {
$cmd = "sudo rm -f " . escapeshellarg($shifted_path.$filename);
shell_exec($cmd);
}
echo "OK";
die();
}
if(isset($_GET['bydate'])){
$statement = $db->prepare('SELECT DISTINCT(Date) FROM detections GROUP BY Date ORDER BY Date DESC');
ensure_db_ok($statement);
$result = $statement->execute();
$view = "bydate";
#Specific Date
} elseif(isset($_GET['date'])) {
$date = $_GET['date'];
session_start();
$_SESSION['date'] = $date;
if(isset($_GET['sort']) && $_GET['sort'] == "occurrences") {
$statement = $db->prepare("SELECT DISTINCT(Com_Name) FROM detections WHERE Date == \"$date\" GROUP BY Com_Name ORDER BY COUNT(*) DESC");
} else {
$statement = $db->prepare("SELECT DISTINCT(Com_Name) FROM detections WHERE Date == \"$date\" ORDER BY Com_Name");
}
ensure_db_ok($statement);
$result = $statement->execute();
$view = "date";
#By Species
} elseif(isset($_GET['byspecies'])) {
if(isset($_GET['sort']) && $_GET['sort'] == "occurrences") {
$statement = $db->prepare('SELECT DISTINCT(Com_Name) FROM detections GROUP BY Com_Name ORDER BY COUNT(*) DESC');
} else {
$statement = $db->prepare('SELECT DISTINCT(Com_Name) FROM detections ORDER BY Com_Name ASC');
}
session_start();
ensure_db_ok($statement);
$result = $statement->execute();
$view = "byspecies";
#Specific Species
} elseif(isset($_GET['species'])) {
$species = htmlspecialchars_decode($_GET['species'], ENT_QUOTES);
session_start();
$_SESSION['species'] = $species;
$statement = $db->prepare("SELECT * FROM detections WHERE Com_Name == \"$species\" ORDER BY Com_Name");
ensure_db_ok($statement);
$statement3 = $db->prepare("SELECT Date, Time, Sci_Name, MAX(Confidence), File_Name FROM detections WHERE Com_Name == \"$species\" ORDER BY Com_Name");
ensure_db_ok($statement3);
$result = $statement->execute();
$result3 = $statement3->execute();
$view = "species";
} else {
unset($_SESSION['species']);
unset($_SESSION['date']);
$view = "choose";
}
if (get_included_files()[0] === __FILE__) {
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>';
}
?>
<script>
function deleteDetection(filename,copylink=false) {
if (confirm("Are you sure you want to delete this detection from the database?") == true) {
const xhttp = new XMLHttpRequest();
xhttp.onload = function() {
if(this.responseText == "OK"){
if(copylink == true) {
window.top.close();
} else {
location.reload();
}
} else {
alert(this.responseText);
}
}
xhttp.open("GET", "play.php?deletefile="+filename, true);
xhttp.send();
}
}
function toggleLock(filename, type, elem) {
const xhttp = new XMLHttpRequest();
xhttp.onload = function() {
if(this.responseText == "OK"){
if(type == "add") {
elem.setAttribute("src","images/lock.svg");
elem.setAttribute("title", "This file is excluded from being purged.");
elem.setAttribute("onclick", elem.getAttribute("onclick").replace("add","del"));
} else {
elem.setAttribute("src","images/unlock.svg");
elem.setAttribute("title", "This file will be deleted when disk space needs to be freed.");
elem.setAttribute("onclick", elem.getAttribute("onclick").replace("del","add"));
}
}
}
if(type == "add") {
xhttp.open("GET", "play.php?excludefile="+filename+"&exclude_add=true", true);
} else {
xhttp.open("GET", "play.php?excludefile="+filename+"&exclude_del=true", true);
}
xhttp.send();
elem.setAttribute("src","images/spinner.gif");
}
function toggleShiftFreq(filename, shiftAction, elem) {
const xhttp = new XMLHttpRequest();
xhttp.onload = function() {
if(this.responseText == "OK"){
if(shiftAction == "shift") {
elem.setAttribute("src","images/unshift.svg");
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");
if (video.length > 0) {
video[0].setAttribute("title", video[0].getAttribute("title").replace("/By_Date/","/By_Date/shifted/"));
source = video[0].getElementsByTagName("source")[0];
source.setAttribute("src", source.getAttribute("src").replace("/By_Date/","/By_Date/shifted/"));
video[0].load();
} else {
atag=elem.parentNode.getElementsByTagName("a")[0];
atag.setAttribute("href", atag.getAttribute("href").replace("/By_Date/","/By_Date/shifted/"));
}
} 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");
if (video.length > 0) {
video[0].setAttribute("title", video[0].getAttribute("title").replace("/By_Date/shifted/","/By_Date/"));
source = video[0].getElementsByTagName("source")[0];
source.setAttribute("src", source.getAttribute("src").replace("/By_Date/shifted/","/By_Date/"));
video[0].load();
} else {
atag=elem.parentNode.getElementsByTagName("a")[0];
atag.setAttribute("href", atag.getAttribute("href").replace("/By_Date/shifted/","/By_Date/"));
}
}
}
}
if(shiftAction == "shift") {
console.log("shifting freqs of " + filename);
xhttp.open("GET", "play.php?shiftfile="+filename+"&doshift=true", true);
} else {
console.log("unshifting freqs of " + filename);
xhttp.open("GET", "play.php?shiftfile="+filename, true);
}
xhttp.send();
elem.setAttribute("src","images/spinner.gif");
}
function changeDetection(filename,copylink=false) {
const xhttp = new XMLHttpRequest();
xhttp.onload = function() {
const labels = JSON.parse(this.responseText);
let dropdown = '<input type="text" id="filterInput" placeholder="Type to filter..."><select id="labelDropdown" size="5" style="display: block; margin: 0 auto;"></select>';
// Check if the modal already exists
let modal = document.getElementById('myModal');
if (!modal) {
// Create a modal box
modal = document.createElement('div');
modal.setAttribute('id', 'myModal');
modal.setAttribute('class', 'modal');
// Create a content box
let content = document.createElement('div');
content.setAttribute('class', 'modal-content');
// Add a title to the modal box
let title = document.createElement('h2');
title.textContent = 'Please select the correct specie here:';
content.appendChild(title);
// Add the dropdown to the content
let selectElement = document.createElement('div');
selectElement.innerHTML = dropdown;
content.appendChild(selectElement);
// Append the content to the modal
modal.appendChild(content);
// Append the modal to the body
document.body.appendChild(modal);
}
// Display the modal
modal.style.display = "block";
// Populate the dropdown list
let dropdownList = document.getElementById('labelDropdown');
labels.forEach(label => {
let option = document.createElement('option');
option.value = label;
option.text = label;
dropdownList.appendChild(option);
});
// Add an event listener to the modal box to hide it when clicked outside
document.addEventListener('click', function(event) {
if (event.target == modal) {
modal.style.display = "none";
dropdownList.selectedIndex = -1; // Reset the dropdown selection
}
});
// Add an event listener to the input box to filter the dropdown list
document.getElementById('filterInput').addEventListener('keyup', function() {
let filter = this.value.toUpperCase();
let options = dropdownList.options;
// Clear the dropdown list
while (dropdownList.firstChild) {
dropdownList.removeChild(dropdownList.firstChild);
}
// Populate the dropdown list with the filtered labels
labels.forEach(label => {
if (label.toUpperCase().indexOf(filter) > -1) {
let option = document.createElement('option');
option.value = label;
option.text = label;
dropdownList.appendChild(option);
}
});
});
dropdownList.addEventListener('change', function() {
const newname = this.value;
// Check if the default option is selected
if (newname === '') {
return; // Exit the function early
}
if (confirm("Are you sure you want to change the specie identified in this detection to " + newname + "?") == true) {
const xhttp2 = new XMLHttpRequest();
xhttp2.onload = function() {
if(this.responseText == "OK"){
if(copylink == true) {
window.top.close();
} else {
location.reload();
}
} else {
alert(this.responseText);
}
}
xhttp2.open("GET", "play.php?changefile="+filename+"&newname="+newname, true);
xhttp2.send();
}
// Hide the modal box and reset the dropdown selection
modal.style.display = "none";
this.selectedIndex = -1;
});
}
xhttp.open("GET", "play.php?getlabels=true", true);
xhttp.send();
}
</script>
<?php
#If no specific species
if(!isset($_GET['species']) && !isset($_GET['filename'])){
?>
<div class="play">
<?php if($view == "byspecies" || $view == "date") { ?>
<div style="width: auto;
text-align: center">
<form action="views.php" method="GET">
<input type="hidden" name="view" value="Recordings">
<input type="hidden" name="<?php echo $view; ?>" value="<?php echo $_GET['date']; ?>">
<button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "alphabetical"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="alphabetical">
<img src="images/sort_abc.svg" title="Sort by alphabetical" alt="Sort by alphabetical">
</button>
<button <?php if(isset($_GET['sort']) && $_GET['sort'] == "occurrences"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="occurrences">
<img src="images/sort_occ.svg" title="Sort by occurrences" alt="Sort by occurrences">
</button>
</form>
</div>
<br>
<?php } ?>
<form action="views.php" method="GET">
<input type="hidden" name="view" value="Recordings">
<table>
<?php
#By Date
if($view == "bydate") {
while($results=$result->fetchArray(SQLITE3_ASSOC)){
$date = $results['Date'];
if(realpath($home."/BirdSongs/Extracted/By_Date/".$date) !== false){
echo "<td>
<button action=\"submit\" name=\"date\" value=\"$date\">".($date == date('Y-m-d') ? "Today" : $date)."</button></td></tr>";}}
#By Species
} elseif($view == "byspecies") {
$birds = array();
while($results=$result->fetchArray(SQLITE3_ASSOC))
{
$name = $results['Com_Name'];
$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'];
$dir_name = str_replace("'", '', $name);
if(realpath($home."/BirdSongs/Extracted/By_Date/".$date."/".str_replace(" ", "_", $dir_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>
<button action=\"submit\" name=\"byspecies\" value=\"byspecies\">By Species</button></td></tr>
<tr><td><button action=\"submit\" name=\"bydate\" value=\"bydate\">By Date</button></td>";
}
echo "</table></form>";
}
#Specific Species
if(isset($_GET['species'])){ ?>
<div style="width: auto;
text-align: center">
<form action="views.php" method="GET">
<input type="hidden" name="view" value="Recordings">
<input type="hidden" name="species" value="<?php echo $_GET['species']; ?>">
<input type="hidden" name="sort" value="<?php echo $_GET['sort']; ?>">
<button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "" || $_GET['sort'] == "date"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="date">
<img width=35px src="images/sort_date.svg" title="Sort by date" alt="Sort by date">
</button>
<button <?php if(isset($_GET['sort']) && $_GET['sort'] == "confidence"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="confidence">
<img src="images/sort_occ.svg" title="Sort by confidence" alt="Sort by confidence">
</button><br>
<input style="margin-top:10px" <?php if(isset($_GET['only_excluded'])){ echo "checked"; }?> type="checkbox" name="only_excluded" onChange="submit()">
<label for="onlyverified">Only Show Purge Excluded</label>
</form>
</div>
<?php
// add disk_check_exclude.txt lines into an array for grepping
$fp = @fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", 'r');
if ($fp) {
$disk_check_exclude_arr = explode("\n", fread($fp, filesize($home."/BirdNET-Pi/scripts/disk_check_exclude.txt")));
} else {
$disk_check_exclude_arr = [];
}
$name = htmlspecialchars_decode($_GET['species'], ENT_QUOTES);
if(isset($_SESSION['date'])) {
$date = $_SESSION['date'];
if(isset($_GET['sort']) && $_GET['sort'] == "confidence") {
$statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" AND Date == \"$date\" ORDER BY Confidence DESC");
} else {
$statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" AND Date == \"$date\" ORDER BY Time DESC");
}
} else {
if(isset($_GET['sort']) && $_GET['sort'] == "confidence") {
$statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" ORDER BY Confidence DESC");
} else {
$statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" ORDER BY Date DESC, Time DESC");
}
}
ensure_db_ok($statement2);
$result2 = $statement2->execute();
$num_rows = 0;
while ($result2->fetchArray(SQLITE3_ASSOC)) {
$num_rows++;
}
$result2->reset(); // reset the pointer to the beginning of the result set
echo "<table>
<tr>
<th>$name</th>
</tr>";
$iter=0;
while($results=$result2->fetchArray(SQLITE3_ASSOC))
{
$comname = preg_replace('/ /', '_', $results['Com_Name']);
$comname = preg_replace('/\'/', '', $comname);
$date = $results['Date'];
$filename = "/By_Date/".$date."/".$comname."/".$results['File_Name'];
$filename_shifted = "/By_Date/shifted/".$date."/".$comname."/".$results['File_Name'];
$filename_png = $filename . ".png";
$sciname = preg_replace('/ /', '_', $results['Sci_Name']);
$sci_name = $results['Sci_Name'];
$time = $results['Time'];
$confidence = round((float)round($results['Confidence'],2) * 100 ) . '%';
$filename_formatted = $date."/".$comname."/".$results['File_Name'];
// file was deleted by disk check, no need to show the detection in recordings
if(!file_exists($home."/BirdSongs/Extracted/".$filename)) {
continue;
}
if(!in_array($filename_formatted, $disk_check_exclude_arr) && isset($_GET['only_excluded'])) {
continue;
}
$iter++;
if($num_rows < 100){
$imageelem = "<video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster=\"$filename_png\" preload=\"none\" title=\"$filename\"><source src=\"$filename\"></video>";
} else {
$imageelem = "<a href=\"$filename\"><img src=\"$filename_png\"></a>";
}
if($config["FULL_DISK"] == "purge") {
if(!in_array($filename_formatted, $disk_check_exclude_arr)) {
$imageicon = "images/unlock.svg";
$title = "This file will be deleted when disk space needs to be freed (>95% usage).";
$type = "add";
} else {
$imageicon = "images/lock.svg";
$title = "This file is excluded from being purged.";
$type = "del";
}
if(file_exists($shifted_path.$filename_formatted)) {
$shiftImageIcon = "images/unshift.svg";
$shiftTitle = "This file has been shifted down in frequency.";
$shiftAction = "unshift";
$filename = $filename_shifted;
} else {
$shiftImageIcon = "images/shift.svg";
$shiftTitle = "This file is not shifted in frequency.";
$shiftAction = "shift";
}
echo "<tr>
<td class=\"relative\">
<img style='cursor:pointer;right:120px' src='images/delete.svg' onclick='deleteDetection(\"".$filename_formatted."\")' class=\"copyimage\" width=25 title='Delete Detection'>
<img style='cursor:pointer;right:85px' src='images/bird.svg' onclick='changeDetection(\"".$filename_formatted."\")' class=\"copyimage\" width=25 title='Change Detection'>
<img style='cursor:pointer;right:45px' onclick='toggleLock(\"".$filename_formatted."\",\"".$type."\", this)' class=\"copyimage\" width=25 title=\"".$title."\" src=\"".$imageicon."\">
<img style='cursor:pointer' onclick='toggleShiftFreq(\"".$filename_formatted."\",\"".$shiftAction."\", this)' class=\"copyimage\" width=25 title=\"".$shiftTitle."\" src=\"".$shiftImageIcon."\"> $date $time<br>$confidence<br>
".$imageelem."
</td>
</tr>";
} else {
echo "<tr>
<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>
</tr>";
}
}if($iter == 0){ echo "<tr><td><b>No recordings were found.</b><br><br><span style='font-size:medium'>They may have been deleted to make space for new recordings. You can prevent this from happening in the future by clicking the <img src='images/unlock.svg' style='width:20px'> icon in the top right of a recording.<br>You can also modify this behavior globally under \"Full Disk Behavior\" <a href='views.php?view=Advanced'>here.</a></span></td></tr>";}echo "</table>";}
if(isset($_GET['filename'])){
$name = $_GET['filename'];
$statement2 = $db->prepare("SELECT * FROM detections where File_name == \"$name\" ORDER BY Date DESC, Time DESC");
ensure_db_ok($statement2);
$result2 = $statement2->execute();
echo "<table>
<tr>
<th>$name</th>
</tr>";
while($results=$result2->fetchArray(SQLITE3_ASSOC))
{
$comname = preg_replace('/ /', '_', $results['Com_Name']);
$comname = preg_replace('/\'/', '', $comname);
$date = $results['Date'];
$filename = "/By_Date/".$date."/".$comname."/".$results['File_Name'];
$filename_shifted = "/By_Date/shifted/".$date."/".$comname."/".$results['File_Name'];
$filename_png = $filename . ".png";
$sciname = preg_replace('/ /', '_', $results['Sci_Name']);
$sci_name = $results['Sci_Name'];
$time = $results['Time'];
$confidence = round((float)round($results['Confidence'],2) * 100 ) . '%';
$filename_formatted = $date."/".$comname."/".$results['File_Name'];
// add disk_check_exclude.txt lines into an array for grepping
$fp = @fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", 'r');
if ($fp) {
$disk_check_exclude_arr = explode("\n", fread($fp, filesize($home."/BirdNET-Pi/scripts/disk_check_exclude.txt")));
} else {
$disk_check_exclude_arr = [];
}
if($config["FULL_DISK"] == "purge") {
if(!in_array($filename_formatted, $disk_check_exclude_arr)) {
$imageicon = "images/unlock.svg";
$title = "This file will be deleted when disk space needs to be freed (>95% usage).";
$type = "add";
} else {
$imageicon = "images/lock.svg";
$title = "This file is excluded from being purged.";
$type = "del";
}
if(file_exists($shifted_path.$filename_formatted)) {
$shiftImageIcon = "images/unshift.svg";
$shiftTitle = "This file has been shifted down in frequency.";
$shiftAction = "unshift";
$filename = $filename_shifted;
} else {
$shiftImageIcon = "images/shift.svg";
$shiftTitle = "This file is not shifted in frequency.";
$shiftAction = "shift";
}
echo "<tr>
<td class=\"relative\">
<img style='cursor:pointer;right:120px' src='images/delete.svg' onclick='deleteDetection(\"".$filename_formatted."\", true)' class=\"copyimage\" width=25 title='Delete Detection'>
<img style='cursor:pointer;right:85px' src='images/bird.svg' onclick='changeDetection(\"".$filename_formatted."\")' class=\"copyimage\" width=25 title='Change Detection'>
<img style='cursor:pointer;right:45px' onclick='toggleLock(\"".$filename_formatted."\",\"".$type."\", this)' class=\"copyimage\" width=25 title=\"".$title."\" src=\"".$imageicon."\">
<img style='cursor:pointer' onclick='toggleShiftFreq(\"".$filename_formatted."\",\"".$shiftAction."\", this)' class=\"copyimage\" width=25 title=\"".$shiftTitle."\" src=\"".$shiftImageIcon."\">$date $time<br>$confidence<br>
<video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster=\"$filename_png\" preload=\"none\" title=\"$filename\"><source src=\"$filename\"></video></td>
</tr>";
} else {
echo "<tr>
<td class=\"relative\">$date $time<br>$confidence
<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>";
}
}echo "</table>";}
echo "</div>";
if (get_included_files()[0] === __FILE__) {
echo '</html>';
}

View File

@@ -1,894 +0,0 @@
@font-face {
font-family: 'Roboto Flex' ;
src: url('static/RobotoFlex-Regular.ttf') format('truetype');
}
* {
font-family: 'Roboto Flex', sans-serif;
box-sizing: border-box;
font-size: medium;
}
a {
text-decoration: none;
}
a:hover {
font-weight: bold;
}
h3 {
text-align: center;
margin-bottom: 12px;
}
iframe {
border: none;
height: 85%;
width: 100%;
position: fixed;
}
body {
margin: 0;
background-color: rgb(119, 196, 135);
}
table {
background-color: transparent;
border-collapse: collapse;
border-spacing: 0;
margin-left: auto;
margin-right: auto;
box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10);
border-radius:3px;
overflow: hidden;
}
td {
padding: 10px;
vertical-align: top;
background-color: rgb(219, 255, 235);
font-weight: lighter;
text-align: center;
}
th {
padding: 12px;
font-weight: bold;
height: auto;
text-align: center;
}
audio, video{
max-height: 100%;
max-width: 100%;
}
label {
font-weight: bold;
}
hr {
border-color:black;
}
button {
background-color: transparent;
border: none;
color: black;
cursor: pointer;
transition:background-color 0.2s;
}
.disabled{
cursor: not-allowed;
pointer-events: none;
opacity:0.5;
}
button:hover {
color: blue;
}
.row {
display: flex;
}
.centered {
text-align: center;
display: block;
width: auto;
margin-left: auto;
margin-right: auto;
}
.banner {
height: 7%;
text-align: center;
}
.banner h1 {
padding: .5em;
letter-spacing: 5px;
}
.banner audio,.banner form {
float: right;
width: 120px;
margin-left: -120px;
margin-right: auto;
}
.banner button {
padding-top: 8px;
font-weight: bold;
letter-spacing: 1px;
}
.banner a {
text-decoration: none;
color: black;
font-size: x-large;
}
.logo img {
position: absolute;
top: 0;
left: 0;
padding: 10px;
}
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
text-align: center; /* Center the content */
}
@media (max-width: 768px) {
.modal-content {
width: 95%;
margin: 10% auto;
}
#labelDropdown {
max-width: 100%;
overflow-x: auto;
}
}
.topnav {
background-color: rgb(159, 226, 155);
display: flex;
flex: 65%;
width: 65%;
min-width: min-content;
justify-content: space-between;
margin-left: auto;
margin-right: auto;
margin-bottom: 15px;
box-shadow: 0px 0px 28px 1px rgba(0, 0, 0, 0.10) !important;
border-radius: 4px;
}
.topimage {
width:175px;
display:initial !important;
}
.topnav form {
margin: 0;
padding: 0;
}
.topnav button {
background-color: transparent;
text-align: center;
padding: 14px 16px;
width: auto;
vertical-align: middle;
}
.topnav button:hover {
background-color: rgb(219, 255, 235);
color: black;
}
.topnav button.active {
background-color: #04AA6D;
color: white;
}
.topnav .button-hover {
background-color: rgb(219, 255, 235) !important;
color: black;
}
.topnav .icon {
display: none;
}
.overview th {
background-color: rgb(219, 255, 235);
text-align: center;
padding: 12px;
}
.overview td {
vertical-align: middle;
}
.overview div img {
max-height: 100%;
display: flex;
justify-content: center;
margin-right: auto;
margin-left: auto;
}
.overview .chart {
margin-top: 10px;
}
.overview-stats {
display: flex;
justify-content: center;
}
.left-column {
flex: 10%;
padding-left: 10px;
}
.right-column {
flex: 90%;
margin-right: 10%;
}
.stats td {
vertical-align: middle;
}
.stats table {
height: auto;
}
.stats button:hover {
color: blue;
}
.overview button, .center button{
font-weight: bold;
color: blue;
}
.history table,.history img {
width: auto;
margin-left: auto;
margin-right: auto;
}
.views {
transition: opacity 0.3s;
-webkit-transition: opacity 0.3s;
}
.views .centered button {
background-color: rgb(219, 255, 235);
padding: 12px;
transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%);
}
.views .centered button:hover {
background-color: rgb(159, 226, 155);
color: black;
box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
}
.settings {
padding: 12px;
}
.settings h2 {
margin-top: 0px;
}
.settings p {
margin-bottom: 0px;
}
.settings h3 {
text-align: left;
}
.settings button {
background-color: rgb(219, 255, 235);
padding: 12px;
}
.settings button:hover {
background-color: rgb(159, 226, 155);
color: black;
}
.float button {
margin-top: 6px;
margin-bottom: 6px;
}
.customlabels,.customlabels2 {
float:left;
}
.customlabels table {
height: 100%;
}
.customlabels td,.customlabels2 td {
border:none;
background-color: transparent;
vertical-align: middle;
}
.customlabels button,.customlabels2 button {
padding: 12px;
background-color: rgb(219, 255, 235);
width: 100%;
}
.column1, .column3 {
width: 45%;
}
.smaller {
width: 100%;
display: none;
margin-left: auto;
margin-right: auto;
}
.column2 {
text-align: center;
width: 10%;
height: 80%;
}
.column4 {
text-align: justify;
width: 10%;
height: 50%;
}
.column1 form,.column3 form {
width: 80%;
margin-left: auto;
margin-right: auto;
}
.column1 select,.column3 select {
height: 80%;
width: 100%;
}
.spectrogram {
width:50%
}
.full {
width:100%;
}
.logbutton, .navbuttons {
float: left;
}
.systemcontrols form,.servicecontrols form {
/*text-align: center;*/
}
.servicecontrols button {
background-color: rgb(219, 255, 235);
padding: 12px;
width: 50%;
}
.systemcontrols button {
background-color: rgb(219, 255, 235);
display: block;
padding: 12px;
width: 50%;
margin: 16px auto;
}
.servicecontrols button {
width: 20%;
}
.btn-group-center {
text-align:center;
/*align-content: center;*/
margin: 16px auto;
position:relative;
/*display:inline-block;*/
}
.slider {
-webkit-appearance: none;
width: 33%;
height: 15px;
border-radius: 5px;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
border-radius: 50%;
background: #04AA6D;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 25px;
height: 25px;
border-radius: 50%;
background: #04AA6D;
cursor: pointer;
}
#body::-webkit-scrollbar {
/*display:none*/
}
@media screen and (max-width: 1290px) {
.column1,.column2,.column3,.column4 {
height: 90%
}
.left-column {
display: none;
}
.right-column {
flex: 100%;
margin: 0;
}
img {
max-width: 100%;
}
.overview {
overflow-x: hidden;
}
.overview .right-column .chart img {
margin-left: 5%;
margin-right: auto;
margin-top: 10px;
}
}
@media screen and (max-width: 1000px) {
.customlabels form,.customlabels2 form {
width: 95%;
}
.column1, .column3 {
width: 50%;
height: 100%;
}
.column1 select,.column3 select {
height: 70%;
}
.column2,.column4 {
display: none;
}
.smaller{
display: block;
}
.systemcontrols button,.servicecontrols button {
width: 60%;
padding: 12px;
background-color: rgb(219, 255, 235);
}
.topnav {
flex: 100%;
width: 100%;
}
.topnav button {display: none;}
.topnav button.icon {
padding: 0;
margin: 0;
display: block;
width: 100%;
margin-left: auto;
margin-right: auto;
}
.banner {
height: auto;
}
.banner img {
display: none;
}
.logo img {
display: block;
width: 60px;
height: 60px;
}
.topnav.responsive {position: relative;}
.topnav.responsive button {
display: block;
text-align: center;
}
}
@media screen and (max-width: 800px) {
.column1, .column3 {
width: 100%;
}
.systemcontrols button,.servicecontrols button {
width: 80%;
padding: 12px;
background-color: rgb(219, 255, 235);
}
.stats img {
width: 100%;
margin-left:auto;
margin-right:auto;
}
.overview img {
width: 100%
}
.banner {
height: auto;
margin-left: 60px;
}
.banner img {
display: none;
}
.stream {
float: right;
display: block;
width: 100px;
}
.logo img {
display: block;
width: 60px;
height: 60px;
}
.play table,.overview table,.stats table {
width: 100%;
}
.topnav {
flex: 100%;
width: 100%;
flex-direction: column;
}
.topnav button {
font-size: large;
width: 100%
}
.topnav button {display: none;}
.topnav button.icon {
margin: 0;
padding: 0;
display: block;
width: 100%;
margin-left: auto;
margin-right: auto;
}
.topnav.responsive {position: relative;}
.topnav.responsive button {
display: block;
}
.left-column {
display: none;
}
.left {
display:none;
}
}
.copyimage {
position:absolute;
top:7px;
right:7px;
width:25px !important;
height:25px !important;
}
.copyimage-mobile {
width: 16px !important;
height: 16px !important;
}
.relative {
position:relative;
}
.sortbutton {
margin-top:10px;
font-size:x-large;
background:#dbffeb;
padding:5px;
box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.10);
}
button.legacyview {
display: none;
color:gray;
margin:5px;
float:right;
z-index:100;
position:relative;
font-size:small;
background:#dbffeb;
padding:5px;
transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%);
}
button.legacyview:hover {
box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
}
button.loadmore {
margin-top:10px;
font-size:x-large;
background:#dbffeb;
padding:10px;
transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%);
}
button.loadmore:hover {
box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
}
#searchterm {
transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
}
#searchterm:hover {
box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
}
tr {
background-color:#9fe29b;
}
.history.centered form {
display:flex;
justify-content: center;
}
.history.centered input {
margin-right:5px;
border:0px;
}
.centered form#views button {
box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.10);
margin:2px;
transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%);
}
.centered form#views button:hover {
box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
}
dl {
margin: 1em 0 0 1em;
}
dt {
float: left;
clear: left;
width: auto;
text-align: left;
font-weight: bold;
color: black;
}
dd::before {
content: ": ";
}
input {
box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10);
}
dialog {
border:none;
}
dialog::backdrop {
background: repeating-linear-gradient(
30deg,
rgba(24, 194, 236, 0.2),
rgba(24, 194, 236, 0.2) 1px,
rgba(24, 194, 236, 0.3) 1px,
rgba(24, 194, 236, 0.3) 20px
);
backdrop-filter: blur(1px)
}
.centered_image_container {
font-size:19px !important;
display:inline-block;
position:relative;
margin-bottom:3px;
}
.centered_image_container img.img1 {
transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%);
cursor:pointer;
height:95%;
position:absolute;
right:110%;
top:0px;
border-radius: 5px;
width:unset;
}
.centered_image_container img.img1:hover{
opacity:0.8;
box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
}
#birdimage {
transition:box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%);
cursor:pointer;
border-radius: 5px;
}
#birdimage:hover {
opacity:0.8;
box-shadow:0px 2px 4px -1px rgb(0 0 0 / 20%), 0px 4px 5px 0px rgb(0 0 0 / 14%), 0px 1px 10px 0px rgb(0 0 0 / 12%);
}
.centered_image_container * {
font-size:19px !important;
}
.centered_image_container form {
margin-bottom:0px;
}
.brbanner {
padding:15px;
background-color:rgb(159, 226, 155);
text-align:center;
font-size:large;
}
#gain.centered {
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;
}
#detections_table_overview table {
width:944px;
}
#recent_detection_middle_td{
width:33%;
}
@media screen and (max-width:500px) {
#recent_detection_middle_td{
width:66%;
}
}
#recent_detection_middle_td img{
width:unset !important;
height:75px;
float:left;
}
.settingstable {
margin-left:unset;
margin-right:unset;
}
.settingstable td {
text-align:unset;
}
.settingstable textarea {
width:100%;
margin-top:10px;
}
.settingstable h2 {
font-size:x-large;
}
.plaintable {
box-shadow: unset;
}
.plaintable td {
padding: unset;
}
.brbanner h1 {
margin:0px;
font-size: xx-large;
}
.testbtn {
background:#77c487 !important;
}
pre.bash {
background-color: black;
color: white;
font-size: medium ;
font-family: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
width: 100%;
display: inline-block;
}
pre#timer.bash {
display:unset;
width:unset;
}
#toolsbtn {
min-width: max-content;
}
#showpassword {
cursor:pointer;
margin-left:2px;
height:5px;
line-height:5px;
padding:3px;
background-color:#9fe29b
}
#newrtspstream{
cursor: pointer;
margin-left: 2px;
height: 5px;
line-height: 5px;
padding: 3px;
background-color: #9fe29b;
}
.exclude_species_list_option_highlight {
color: black;
background-color: rgb(119, 196, 135);
font-weight: bolder;
}
#ddnewline::before {
content: none;
}

View File

@@ -1,342 +0,0 @@
import datetime
import logging
import math
import operator
import os
import time
import librosa
import numpy as np
from utils.helpers import get_settings, Detection
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
os.environ['CUDA_VISIBLE_DEVICES'] = ''
try:
import tflite_runtime.interpreter as tflite
except BaseException:
from tensorflow import lite as tflite
log = logging.getLogger(__name__)
userDir = os.path.expanduser('~')
INTERPRETER, M_INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST, CONVERT_LIST, CONVERT_DICT = (None, None, None, None, None, None)
PREDICTED_SPECIES_LIST = []
model, priv_thresh, sf_thresh = (None, None, None)
mdata, mdata_params = (None, None)
def loadModel():
global INPUT_LAYER_INDEX
global OUTPUT_LAYER_INDEX
global MDATA_INPUT_INDEX
global CLASSES
log.info('LOADING TF LITE MODEL...')
# Load TFLite model and allocate tensors.
# model will either be BirdNET_GLOBAL_6K_V2.4_Model_FP16 (new) or BirdNET_6K_GLOBAL_MODEL (old)
modelpath = userDir + '/BirdNET-Pi/model/'+model+'.tflite'
myinterpreter = tflite.Interpreter(model_path=modelpath, num_threads=2)
myinterpreter.allocate_tensors()
# Get input and output tensors.
input_details = myinterpreter.get_input_details()
output_details = myinterpreter.get_output_details()
# Get input tensor index
INPUT_LAYER_INDEX = input_details[0]['index']
if model == "BirdNET_6K_GLOBAL_MODEL":
MDATA_INPUT_INDEX = input_details[1]['index']
OUTPUT_LAYER_INDEX = output_details[0]['index']
# Load labels
CLASSES = []
labelspath = userDir + '/BirdNET-Pi/model/labels.txt'
with open(labelspath, 'r') as lfile:
for line in lfile.readlines():
CLASSES.append(line.replace('\n', ''))
log.info('LOADING DONE!')
return myinterpreter
def loadMetaModel():
global M_INTERPRETER
global M_INPUT_LAYER_INDEX
global M_OUTPUT_LAYER_INDEX
if get_settings().getint('DATA_MODEL_VERSION') == 2:
data_model = 'BirdNET_GLOBAL_6K_V2.4_MData_Model_V2_FP16.tflite'
else:
data_model = 'BirdNET_GLOBAL_6K_V2.4_MData_Model_FP16.tflite'
# Load TFLite model and allocate tensors.
M_INTERPRETER = tflite.Interpreter(model_path=os.path.join(userDir, 'BirdNET-Pi/model', data_model))
M_INTERPRETER.allocate_tensors()
# Get input and output tensors.
input_details = M_INTERPRETER.get_input_details()
output_details = M_INTERPRETER.get_output_details()
# Get input tensor index
M_INPUT_LAYER_INDEX = input_details[0]['index']
M_OUTPUT_LAYER_INDEX = output_details[0]['index']
log.info("loaded META model")
def predictFilter(lat, lon, week):
global M_INTERPRETER
# Does interpreter exist?
if M_INTERPRETER is None:
loadMetaModel()
# Prepare mdata as sample
sample = np.expand_dims(np.array([lat, lon, week], dtype='float32'), 0)
# Run inference
M_INTERPRETER.set_tensor(M_INPUT_LAYER_INDEX, sample)
M_INTERPRETER.invoke()
return M_INTERPRETER.get_tensor(M_OUTPUT_LAYER_INDEX)[0]
def explore(lat, lon, week):
# Make filter prediction
l_filter = predictFilter(lat, lon, week)
# Apply threshold
l_filter = np.where(l_filter >= float(sf_thresh), l_filter, 0)
# Zip with labels
l_filter = list(zip(l_filter, CLASSES))
# Sort by filter value
l_filter = sorted(l_filter, key=lambda x: x[0], reverse=True)
return l_filter
def predictSpeciesList(lat, lon, week):
l_filter = explore(lat, lon, week)
for s in l_filter:
if s[0] >= float(sf_thresh):
# if there's a custom user-made include list, we only want to use the species in that
if (len(INCLUDE_LIST) == 0):
PREDICTED_SPECIES_LIST.append(s[1])
def loadCustomSpeciesList(path):
slist = []
if os.path.isfile(path):
with open(path, 'r') as csfile:
for line in csfile.readlines():
slist.append(line.replace('\r', '').replace('\n', ''))
return slist
def splitSignal(sig, rate, overlap, seconds=3.0, minlen=1.5):
# Split signal with overlap
sig_splits = []
for i in range(0, len(sig), int((seconds - overlap) * rate)):
split = sig[i:i + int(seconds * rate)]
# End of signal?
if len(split) < int(minlen * rate):
break
# Signal chunk too short? Fill with zeros.
if len(split) < int(rate * seconds):
temp = np.zeros((int(rate * seconds)))
temp[:len(split)] = split
split = temp
sig_splits.append(split)
return sig_splits
def readAudioData(path, overlap, sample_rate=48000):
log.info('READING AUDIO DATA...')
# Open file with librosa (uses ffmpeg or libav)
sig, rate = librosa.load(path, sr=sample_rate, mono=True, res_type='kaiser_fast')
# Split audio into 3-second chunks
chunks = splitSignal(sig, rate, overlap)
log.info('READING DONE! READ %d CHUNKS.', len(chunks))
return chunks
def convertMetadata(m):
# Convert week to cosine
if m[2] >= 1 and m[2] <= 48:
m[2] = math.cos(math.radians(m[2] * 7.5)) + 1
else:
m[2] = -1
# Add binary mask
mask = np.ones((3,))
if m[0] == -1 or m[1] == -1:
mask = np.zeros((3,))
if m[2] == -1:
mask[2] = 0.0
return np.concatenate([m, mask])
def custom_sigmoid(x, sensitivity=1.0):
return 1 / (1.0 + np.exp(-sensitivity * x))
def predict(sample, sensitivity):
global INTERPRETER
# Make a prediction
INTERPRETER.set_tensor(INPUT_LAYER_INDEX, np.array(sample[0], dtype='float32'))
if model == "BirdNET_6K_GLOBAL_MODEL":
INTERPRETER.set_tensor(MDATA_INPUT_INDEX, np.array(sample[1], dtype='float32'))
INTERPRETER.invoke()
prediction = INTERPRETER.get_tensor(OUTPUT_LAYER_INDEX)[0]
# Apply custom sigmoid
p_sigmoid = custom_sigmoid(prediction, sensitivity)
# Get label and scores for pooled predictions
p_labels = dict(zip(CLASSES, p_sigmoid))
# Sort by score
p_sorted = sorted(p_labels.items(), key=operator.itemgetter(1), reverse=True)
human_cutoff = max(10, int(len(p_sorted) * priv_thresh / 100.0))
log.debug("DATABASE SIZE: %d", len(p_sorted))
log.debug("HUMAN-CUTOFF AT: %d", human_cutoff)
for i in range(min(10, len(p_sorted))):
if p_sorted[i][0] == 'Human_Human':
with open(userDir + '/BirdNET-Pi/HUMAN.txt', 'a') as rfile:
rfile.write(str(datetime.datetime.now()) + str(p_sorted[i]) + ' ' + str(human_cutoff) + '\n')
return p_sorted[:human_cutoff]
def analyzeAudioData(chunks, lat, lon, week, sens, overlap,):
global INTERPRETER
sensitivity = max(0.5, min(1.0 - (sens - 1.0), 1.5))
detections = {}
start = time.time()
log.info('ANALYZING AUDIO...')
if model == "BirdNET_GLOBAL_6K_V2.4_Model_FP16":
if len(PREDICTED_SPECIES_LIST) == 0 or len(INCLUDE_LIST) != 0:
predictSpeciesList(lat, lon, week)
mdata = get_metadata(lat, lon, week)
# Parse every chunk
pred_start = 0.0
for c in chunks:
# Prepare as input signal
sig = np.expand_dims(c, 0)
# Make prediction
p = predict([sig, mdata], sensitivity)
# print("PPPPP",p)
HUMAN_DETECTED = False
# Catch if Human is recognized
for x in range(len(p)):
if "Human" in p[x][0]:
HUMAN_DETECTED = True
# Save result and timestamp
pred_end = pred_start + 3.0
# If human detected set all detections to human to make sure voices are not saved
if HUMAN_DETECTED is True:
p = [('Human_Human', 0.0)] * 10
detections[str(pred_start) + ';' + str(pred_end)] = p
pred_start = pred_end - overlap
log.info('DONE! Time %.2f SECONDS', time.time() - start)
return detections
def get_metadata(lat, lon, week):
global mdata, mdata_params
if mdata_params != [lat, lon, week]:
mdata_params = [lat, lon, week]
# Convert and prepare metadata
mdata = convertMetadata(np.array([lat, lon, week]))
mdata = np.expand_dims(mdata, 0)
return mdata
def load_global_model():
global INTERPRETER
global model, priv_thresh, sf_thresh
conf = get_settings()
model = conf['MODEL']
priv_thresh = conf.getfloat('PRIVACY_THRESHOLD')
sf_thresh = conf.getfloat('SF_THRESH')
INTERPRETER = loadModel()
def run_analysis(file):
global INCLUDE_LIST, EXCLUDE_LIST, CONVERT_LIST, CONVERT_DICT
INCLUDE_LIST = loadCustomSpeciesList(os.path.expanduser("~/BirdNET-Pi/include_species_list.txt"))
EXCLUDE_LIST = loadCustomSpeciesList(os.path.expanduser("~/BirdNET-Pi/exclude_species_list.txt"))
CONVERT_LIST = loadCustomSpeciesList(os.path.expanduser("~/BirdNET-Pi/convert_species_list.txt"))
CONVERT_DICT = {row.split(';')[0]: row.split(';')[1] for row in CONVERT_LIST}
conf = get_settings()
# Read audio data & handle errors
try:
audio_data = readAudioData(file.file_name, conf.getfloat('OVERLAP'))
except (NameError, TypeError) as e:
log.error("Error with the following info: %s", e)
return []
# Process audio data and get detections
raw_detections = analyzeAudioData(audio_data, conf.getfloat('LATITUDE'), conf.getfloat('LONGITUDE'), file.week,
conf.getfloat('SENSITIVITY'), conf.getfloat('OVERLAP'))
confident_detections = []
for time_slot, entries in raw_detections.items():
log.info('%s-%s', time_slot, entries[0])
for entry in entries:
if entry[1] >= conf.getfloat('CONFIDENCE'):
if entry[0] in CONVERT_DICT:
converted_entry = CONVERT_DICT.get(entry[0], entry[0])
else :
converted_entry = entry[0]
if (converted_entry in INCLUDE_LIST or len(INCLUDE_LIST) == 0) and \
(converted_entry not in EXCLUDE_LIST or len(EXCLUDE_LIST) == 0) and \
(converted_entry in PREDICTED_SPECIES_LIST or len(PREDICTED_SPECIES_LIST) == 0):
d = Detection(time_slot.split(';')[0], time_slot.split(';')[1], converted_entry, entry[1])
confident_detections.append(d)
return confident_detections

View File

@@ -1,457 +0,0 @@
<?php
/* Prevent XSS input */
$_GET = filter_input_array(INPUT_GET, FILTER_SANITIZE_STRING);
$_POST = filter_input_array(INPUT_POST, FILTER_SANITIZE_STRING);
session_start();
require_once 'scripts/common.php';
$user = get_user();
$home = get_home();
$config = get_config();
set_timezone();
if(is_authenticated() && (!isset($_SESSION['behind']) || !isset($_SESSION['behind_time']) || time() > $_SESSION['behind_time'] + 86400)) {
shell_exec("sudo -u".$user." git -C ".$home."/BirdNET-Pi fetch > /dev/null 2>/dev/null &");
$str = trim(shell_exec("sudo -u".$user." git -C ".$home."/BirdNET-Pi status"));
if (preg_match("/behind '.*?' by (\d+) commit(s?)\b/", $str, $matches)) {
$num_commits_behind = $matches[1];
}
if (preg_match('/\b(\d+)\b and \b(\d+)\b different commits each/', $str, $matches)) {
$num1 = (int) $matches[1];
$num2 = (int) $matches[2];
$num_commits_behind = $num1 + $num2;
}
if (stripos($str, "Your branch is up to date") !== false) {
$num_commits_behind = '0';
}
$_SESSION['behind'] = $num_commits_behind;
$_SESSION['behind_time'] = time();
}
if(isset($_SESSION['behind'])&&intval($_SESSION['behind']) >= 99) {?>
<style>
.updatenumber {
width:30px !important;
}
</style>
<?php }
if ($config["LATITUDE"] == "0.000" && $config["LONGITUDE"] == "0.000") {
echo "<center style='color:red'><b>WARNING: Your latitude and longitude are not set properly. Please do so now in Tools -> Settings.</center></b>";
}
elseif ($config["LATITUDE"] == "0.000") {
echo "<center style='color:red'><b>WARNING: Your latitude is not set properly. Please do so now in Tools -> Settings.</center></b>";
}
elseif ($config["LONGITUDE"] == "0.000") {
echo "<center style='color:red'><b>WARNING: Your longitude is not set properly. Please do so now in Tools -> Settings.</center></b>";
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BirdNET-Pi DB</title>
<link rel="stylesheet" href="style.css?v=<?php echo date ('n.d.y', filemtime('style.css')); ?>">
</head>
<body>
<form action="views.php" method="GET" id="views">
<div class="topnav" id="myTopnav">
<button type="submit" name="view" value="Overview" form="views">Overview</button>
<button type="submit" name="view" value="Todays Detections" form="views">Today's Detections</button>
<button type="submit" name="view" value="Spectrogram" form="views">Spectrogram</button>
<button type="submit" name="view" value="Species Stats" form="views">Best Recordings</button>
<button type="submit" name="view" value="Streamlit" form="views">Species Stats</button>
<button type="submit" name="view" value="Daily Charts" form="views">Daily Charts</button>
<button type="submit" name="view" value="Recordings" form="views">Recordings</button>
<button type="submit" name="view" value="View Log" form="views">View Log</button>
<button type="submit" name="view" value="Tools" form="views">Tools<?php if(isset($_SESSION['behind']) && intval($_SESSION['behind']) >= 50 && ($config['SILENCE_UPDATE_INDICATOR'] != 1)){ $updatediv = ' <div class="updatenumber">'.$_SESSION["behind"].'</div>'; } else { $updatediv = ""; } echo $updatediv; ?></button>
<button type="button" href="javascript:void(0);" class="icon" onclick="myFunction()"><img src="images/menu.png"></button>
</div>
</form>
<script>
window.onload = function() {
var elements = document.querySelectorAll("button[name=view]");
var setViewsOpacity = function() {
document.getElementsByClassName("views")[0].style.opacity = "0.5";
};
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', setViewsOpacity, false);
}
};
var topbuttons = document.querySelectorAll("button[form='views']");
if(window.location.search.substr(1) != '') {
for (var i = 0; i < topbuttons.length; i++) {
if(topbuttons[i].value == decodeURIComponent(window.location.search.substr(1)).replace(/\+/g,' ').split('=').pop()) {
topbuttons[i].classList.add("button-hover");
}
}
} else {
topbuttons[0].classList.add("button-hover");
}
function copyOutput(elem) {
elem.innerHTML = 'Copied!';
const copyText = document.getElementsByTagName("pre")[0].textContent;
const textArea = document.createElement('textarea');
textArea.style.position = 'absolute';
textArea.style.left = '-100%';
textArea.textContent = copyText;
document.body.append(textArea);
textArea.select();
document.execCommand("copy");
}
</script>
<div class="views">
<?php
if(isset($_GET['view'])){
if($_GET['view'] == "System Info"){echo "<iframe src='phpsysinfo/index.php'></iframe>";}
if($_GET['view'] == "System Controls"){
ensure_authenticated();
include('scripts/system_controls.php');
}
if($_GET['view'] == "Services"){
ensure_authenticated();
include('scripts/service_controls.php');
}
if($_GET['view'] == "Spectrogram"){include('spectrogram.php');}
if($_GET['view'] == "View Log"){echo "<body style=\"scroll:no;overflow-x:hidden;\"><iframe style=\"width:calc( 100% + 1em);\" src=\"/log\"></iframe></body>";}
if($_GET['view'] == "Overview"){include('overview.php');}
if($_GET['view'] == "Todays Detections"){include('todays_detections.php');}
if($_GET['view'] == "Kiosk"){$kiosk = true;include('todays_detections.php');}
if($_GET['view'] == "Species Stats"){include('stats.php');}
if($_GET['view'] == "Weekly Report"){include('weekly_report.php');}
if($_GET['view'] == "Streamlit"){echo "<iframe src=\"/stats\"></iframe>";}
if($_GET['view'] == "Daily Charts"){include('history.php');}
if($_GET['view'] == "Tools"){
ensure_authenticated();
$url = $_SERVER['SERVER_NAME']."/scripts/adminer.php";
echo "<div class=\"centered\">
<form action=\"views.php\" method=\"GET\" id=\"views\">
<button type=\"submit\" name=\"view\" value=\"Settings\" form=\"views\">Settings</button>
<button type=\"submit\" name=\"view\" value=\"System Info\" form=\"views\">System Info</button>
<button type=\"submit\" name=\"view\" value=\"System Controls\" form=\"views\">System Controls".$updatediv."</button>
<button type=\"submit\" name=\"view\" value=\"Services\" form=\"views\">Services</button>
<button type=\"submit\" name=\"view\" value=\"File\" form=\"views\">File Manager</button>
<a href=\"scripts/adminer.php\" target=\"_blank\"><button type=\"submit\" form=\"\">Database Maintenance</button></a>
<button type=\"submit\" name=\"view\" value=\"Webterm\" form=\"views\">Web Terminal</button>
<button type=\"submit\" name=\"view\" value=\"Included\" form=\"views\">Custom Species List</button>
<button type=\"submit\" name=\"view\" value=\"Excluded\" form=\"views\">Excluded Species List</button>
<button type=\"submit\" name=\"view\" value=\"Converted\" form=\"views\">Converted Species List</button>
</form>
</div>";
}
if($_GET['view'] == "Recordings"){include('play.php');}
if($_GET['view'] == "Settings"){include('scripts/config.php');}
if($_GET['view'] == "Advanced"){include('scripts/advanced.php');}
if($_GET['view'] == "Included"){
ensure_authenticated();
if(isset($_GET['species']) && isset($_GET['add'])){
$file = './scripts/include_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $str);
file_put_contents("$file", "$str");
if(isset($_GET['species'])){
foreach ($_GET['species'] as $selectedOption)
file_put_contents("./scripts/include_species_list.txt", htmlspecialchars_decode($selectedOption, ENT_QUOTES)."\n", FILE_APPEND);
}
} elseif(isset($_GET['species']) && isset($_GET['del'])){
$file = './scripts/include_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace('/^\h*\v+/m', '', $str);
file_put_contents("$file", "$str");
foreach($_GET['species'] as $selectedOption) {
$content = file_get_contents("../BirdNET-Pi/include_species_list.txt");
$newcontent = str_replace($selectedOption, "", "$content");
$newcontent = str_replace(htmlspecialchars_decode($selectedOption, ENT_QUOTES), "", "$newcontent");
file_put_contents("./scripts/include_species_list.txt", "$newcontent");
}
$file = './scripts/include_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace('/^\h*\v+/m', '', $str);
file_put_contents("$file", "$str");
}
include('./scripts/include_list.php');
}
if($_GET['view'] == "Excluded"){
ensure_authenticated();
if(isset($_GET['species']) && isset($_GET['add'])){
$file = './scripts/exclude_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $str);
file_put_contents("$file", "$str");
foreach ($_GET['species'] as $selectedOption)
file_put_contents("./scripts/exclude_species_list.txt", htmlspecialchars_decode($selectedOption, ENT_QUOTES)."\n", FILE_APPEND);
} elseif (isset($_GET['species']) && isset($_GET['del'])){
$file = './scripts/exclude_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace('/^\h*\v+/m', '', $str);
file_put_contents("$file", "$str");
foreach($_GET['species'] as $selectedOption) {
$content = file_get_contents("./scripts/exclude_species_list.txt");
$newcontent = str_replace($selectedOption, "", "$content");
$newcontent = str_replace(htmlspecialchars_decode($selectedOption, ENT_QUOTES), "", "$content");
file_put_contents("./scripts/exclude_species_list.txt", "$newcontent");
}
$file = './scripts/exclude_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace('/^\h*\v+/m', '', $str);
file_put_contents("$file", "$str");
}
include('./scripts/exclude_list.php');
}
if($_GET['view'] == "Converted"){
ensure_authenticated();
if(isset($_GET['species']) && isset($_GET['add'])){
$file = './scripts/convert_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $str);
file_put_contents("$file", "$str");
// Write $_GET['species'] to the file
file_put_contents("./scripts/convert_species_list.txt", htmlspecialchars_decode($_GET['species'], ENT_QUOTES)."\n", FILE_APPEND);
} elseif (isset($_GET['species']) && isset($_GET['del'])){
$file = './scripts/convert_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace('/^\h*\v+/m', '', $str);
file_put_contents("$file", "$str");
foreach($_GET['species'] as $selectedOption) {
$content = file_get_contents("./scripts/convert_species_list.txt");
$newcontent = str_replace($selectedOption, "", "$content");
$newcontent = str_replace(htmlspecialchars_decode($selectedOption, ENT_QUOTES), "", "$content");
file_put_contents("./scripts/convert_species_list.txt", "$newcontent");
}
$file = './scripts/convert_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace('/^\h*\v+/m', '', $str);
file_put_contents("$file", "$str");
}
include('./scripts/convert_list.php');
}
if($_GET['view'] == "File"){
echo "<iframe src='scripts/filemanager/filemanager.php'></iframe>";
}
if($_GET['view'] == "Webterm"){
ensure_authenticated('You cannot access the web terminal');
echo "<iframe src='/terminal'></iframe>";
}
} elseif(isset($_GET['submit'])) {
ensure_authenticated();
$allowedCommands = array('sudo systemctl stop livestream.service && sudo systemctl stop icecast2.service',
'sudo systemctl restart livestream.service && sudo systemctl restart icecast2.service',
'sudo systemctl disable --now livestream.service && sudo systemctl disable icecast2 && sudo systemctl stop icecast2.service',
'sudo systemctl enable icecast2 && sudo systemctl start icecast2.service && sudo systemctl enable --now livestream.service',
'sudo systemctl stop web_terminal.service',
'sudo systemctl restart web_terminal.service',
'sudo systemctl disable --now web_terminal.service',
'sudo systemctl enable --now web_terminal.service',
'sudo systemctl stop birdnet_log.service',
'sudo systemctl restart birdnet_log.service',
'sudo systemctl disable --now birdnet_log.service',
'sudo systemctl enable --now birdnet_log.service',
'sudo systemctl stop birdnet_analysis.service',
'sudo systemctl restart birdnet_analysis.service',
'sudo systemctl disable --now birdnet_analysis.service',
'sudo systemctl enable --now birdnet_analysis.service',
'sudo systemctl stop birdnet_stats.service',
'sudo systemctl restart birdnet_stats.service',
'sudo systemctl disable --now birdnet_stats.service',
'sudo systemctl enable --now birdnet_stats.service',
'sudo systemctl stop birdnet_recording.service',
'sudo systemctl restart birdnet_recording.service',
'sudo systemctl disable --now birdnet_recording.service',
'sudo systemctl enable --now birdnet_recording.service',
'sudo systemctl stop chart_viewer.service',
'sudo systemctl restart chart_viewer.service',
'sudo systemctl disable --now chart_viewer.service',
'sudo systemctl enable --now chart_viewer.service',
'sudo systemctl stop spectrogram_viewer.service',
'sudo systemctl restart spectrogram_viewer.service',
'sudo systemctl disable --now spectrogram_viewer.service',
'sudo systemctl enable --now spectrogram_viewer.service',
'sudo systemctl enable '.get_service_mount_name().' && sudo reboot',
'sudo systemctl disable '.get_service_mount_name().' && sudo reboot',
'stop_core_services.sh',
'restart_services.sh',
'sudo reboot',
'update_birdnet.sh',
'sudo shutdown now',
'sudo clear_all_data.sh');
$command = $_GET['submit'];
if(in_array($command,$allowedCommands)){
if(isset($command)){
$initcommand = $command;
if (strpos($command, "systemctl") !== false) {
//If there more than one command to execute, processes then separately
//currently only livestream service uses multiple commands to interact with the required services
if (strpos($command, " && ") !== false) {
$separate_commands = explode("&&", trim($command));
$new_multiservice_status_command = "";
foreach ($separate_commands as $indiv_service_command) {
//explode the string by " " space so we can get each individual component of the command
//and eventually the service name at the end
$separate_command_tmp = explode(" ", trim($indiv_service_command));
//get the service names
$new_multiservice_status_command .= " " . trim(end($separate_command_tmp));
}
$service_names = $new_multiservice_status_command;
} else {
//only one service needs restarting so we only need to query the status of one service
$tmp = explode(" ", trim($command));
$service_names = end($tmp);
}
$command .= " & sleep 3;sudo systemctl status " . $service_names;
}
if($initcommand == "update_birdnet.sh") {
session_unset();
}
$results = shell_exec("$command 2>&1");
$results = str_replace("FAILURE", "<span style='color:red'>FAILURE</span>", $results);
$results = str_replace("failed", "<span style='color:red'>failed</span>",$results);
$results = str_replace("active (running)", "<span style='color:green'><b>active (running)</b></span>",$results);
$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 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>";
}
}
ob_end_flush();
} else {include('overview.php');}
?>
<script>
function myFunction() {
var x = document.getElementById("myTopnav");
if (x.className === "topnav") {
x.className += " responsive";
} else {
x.className = "topnav";
}
}
function setLiveStreamVolume(vol) {
var audioelement = window.parent.document.getElementsByTagName("audio")[0];
if (typeof(audioelement) != 'undefined' && audioelement != null)
{
audioelement.volume = vol
}
}
window.onbeforeunload = function(event) {
// if the user is playing a video and then navigates away mid-play, the live stream audio should be unmuted again
var audioelement = window.parent.document.getElementsByTagName("audio")[0];
if (typeof(audioelement) != 'undefined' && audioelement != null)
{
audioelement.volume = 1
}
}
function getTheDate(increment) {
var theDate = "<?php if (isset($theDate)) echo $theDate;?>";
d = new Date(theDate);
d.setDate(d.getDate(theDate) + increment);
yyyy = d.getFullYear();
mm = d.getMonth() + 1; if (mm < 10) mm = "0" + mm;
dd = d.getDate(); if (dd < 10) dd = "0" + dd;
document.getElementById("SwipeSpinner").hidden = false;
window.location = "/views.php?date="+yyyy+"-"+mm+"-"+dd+"&view=Daily+Charts";
}
function installKeyAndSwipeEventHandler() {
for (var i = 0; i < topbuttons.length; i++) {
if (topbuttons[i].textContent == "Daily Charts" &&
topbuttons[i].className == "button-hover") {
document.onkeydown = function(event) {
switch (event.keyCode) {
case 37: //Left key
getTheDate(-1);
break;
case 39: //Right key
getTheDate(+1);
break;
}
};
// https://stackoverflow.com/questions/2264072/detect-a-finger-swipe-through-javascript-on-the-iphone-and-android
let touchstartX = 0;
let diffX = 0;
let touchstartY = 0;
let diffY = 0;
let startTime = 0;
let diffTime = 0;
function checkDirection() {
if (Math.abs(diffX) > Math.abs(diffY) && diffTime < 500) {
if (diffX > 20) getTheDate(+1);
if (diffX < -20) getTheDate(-1);
}
}
document.addEventListener('touchstart', e => {
touchstartX = e.changedTouches[0].screenX;
touchstartY = e.changedTouches[0].screenY;
startTime = Date.now();
});
document.addEventListener('touchend', e => {
diffX = touchstartX - e.changedTouches[0].screenX;
diffY = touchstartY - e.changedTouches[0].screenY;
diffTime = Date.now() - startTime;
checkDirection();
});
}
}
}
installKeyAndSwipeEventHandler();
</script>
</div>
</body>