This commit is contained in:
Alexandre
2024-05-18 17:36:56 +02:00
parent ffc0d4f2f0
commit b8722babdd
10 changed files with 2003 additions and 31 deletions

View File

@@ -0,0 +1,101 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
</style>
<p><strong>This tool will allow to convert on-the-fly species to compensate for model errors. It SHOULD NOT BE USED except if you know what you are doing, instead the model errors should be reported to the owner. However, it is still convenient for systematic biases that are confirmed through careful listening of samples, while waiting for the models to be updated.</strong></p>
<div class="customlabels column1">
<form action="" method="GET" id="add">
<input type="hidden" id="species" name="species">
<h3>Specie to convert from :</h3>
<!-- Input box to filter options in the first table -->
<input type="text" id="species1Search" onkeyup="filterOptions('species1')" placeholder="Search for species...">
<select name="species1" id="species1" size="25">
<?php
error_reporting(E_ALL);
ini_set('display_errors',1);
$filename = './scripts/labels.txt';
$eachline = file($filename, FILE_IGNORE_NEW_LINES);
foreach($eachline as $lines){echo
"<option value=\"".$lines."\">$lines</option>";}
?>
</select>
<br><br> <!-- Added a space between the two tables -->
<h3>Specie to convert to :</h3>
<!-- Input box to filter options in the second table -->
<input type="text" id="species2Search" onkeyup="filterOptions('species2')" placeholder="Search for species...">
<select name="species2" id="species2" size="25">
<?php
foreach($eachline as $lines){echo
"<option value=\"".$lines."\">$lines</option>";}
?>
</select>
<input type="hidden" name="add" value="add">
</form>
<div class="customlabels smaller">
<button type="submit" name="view" value="Converted" form="add">>>ADD>></button>
</div>
</div>
<div class="customlabels column2">
<table><td>
<button type="submit" name="view" value="Converted" form="add">>>ADD>></button>
<br><br>
<button type="submit" name="view" value="Converted" form="del">REMOVE</button>
</td></table>
</div>
<div class="customlabels column3" style="margin-top: 0;"> <!-- Removed the blank space above the table -->
<form action="" method="GET" id="del">
<h3>Converted Species List</h3>
<select name="species[]" id="value2" multiple size="25">
<?php
$filename = './scripts/convert_species_list.txt'; // Changed the file path
$eachline = file($filename, FILE_IGNORE_NEW_LINES);
foreach($eachline as $lines){
echo
"<option value=\"".$lines."\">$lines</option>";
}?>
</select>
<input type="hidden" name="del" value="del">
</form>
<div class="customlabels smaller">
<button type="submit" name="view" value="Converted" form="del">REMOVE</button>
</div>
</div>
<input type="hidden" id="hiddenSpecies" name="hiddenSpecies">
<script>
document.getElementById("add").addEventListener("submit", function(event) {
var speciesSelect1 = document.getElementById("species1");
var speciesSelect2 = document.getElementById("species2");
if (speciesSelect1.selectedIndex < 0 || speciesSelect2.selectedIndex < 0) {
alert("Please select a species from both lists.");
document.querySelector('.views').style.opacity = 1;
event.preventDefault();
} else {
var selectedSpecies1 = speciesSelect1.options[speciesSelect1.selectedIndex].value;
var selectedSpecies2 = speciesSelect2.options[speciesSelect2.selectedIndex].value;
document.getElementById("species").value = selectedSpecies1 + ";" + selectedSpecies2;
}
});
// Function to filter options in a select element
function filterOptions(id) {
var input = document.getElementById(id + "Search");
var filter = input.value.toUpperCase();
var select = document.getElementById(id);
var options = select.getElementsByTagName("option");
for (var i = 0; i < options.length; i++) {
var txtValue = options[i].textContent || options[i].innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
options[i].style.display = "";
} else {
options[i].style.display = "none";
}
}
}
</script>

View File

@@ -0,0 +1,342 @@
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 = (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

@@ -0,0 +1,457 @@
<?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>