mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-01-11 18:31:02 +01:00
On the fly species converter
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 |
@@ -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!"
|
||||
@@ -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>';
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user