a80d39d7b6
Makes things a little tidier and easier to change in the future, a custom message can be passed to authenticateUser() and this is output if the authentication fails. Added protection around Include and Exclude species pages as these still accessible & data can still be written to include and exclude species.txt even with a password.
1771 lines
62 KiB
PHP
1771 lines
62 KiB
PHP
<?php
|
|
if (realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME'])) {
|
|
header('HTTP/1.1 403 Forbidden', TRUE, 403);
|
|
}
|
|
|
|
ini_set('session.gc_maxlifetime', 7200);
|
|
ini_set('user_agent', 'PHP_Flickr/1.0');
|
|
ini_set('display_errors', 1);
|
|
error_reporting(E_ERROR);
|
|
|
|
//Setup the session if we're not using the API, since the API endpoint sets up it's own session
|
|
if (!isset($api_incl)) {
|
|
$api_incl = false;
|
|
session_set_cookie_params(7200);
|
|
session_start();
|
|
}
|
|
|
|
//Get the user and home directory for the first user in the system (userid 1000)
|
|
$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd");
|
|
$user = trim($user);
|
|
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
|
|
$home = trim($home);
|
|
|
|
////////// SET TIMEZONE //////////
|
|
// If we can get the timezome from the systems timezone file ust that
|
|
$sys_timezone = "";
|
|
if (file_exists('/etc/timezone')) {
|
|
$tz_data = file_get_contents('/etc/timezone');
|
|
if ($tz_data !== false) {
|
|
$sys_timezone = trim($tz_data);
|
|
}
|
|
} else {
|
|
// Else get timezone from the timedatectl command
|
|
$tz_data = shell_exec('timedatectl show');
|
|
$tz_data_array = parse_ini_string($tz_data);
|
|
if (is_array($tz_data_array) && array_key_exists('Timezone', $tz_data_array)) {
|
|
$sys_timezone = $tz_data_array['Timezone'];
|
|
}
|
|
}
|
|
//Finally if we have a valid timezone, set it as the one PHP uses
|
|
if ($sys_timezone !== "") {
|
|
date_default_timezone_set($sys_timezone);
|
|
}
|
|
|
|
|
|
////////// PARSES THE CONFIG FILE //////////
|
|
$config = [];
|
|
parseConfig();
|
|
|
|
//Set a default site name if nothing iss configured
|
|
if ($config["SITE_NAME"] == "") {
|
|
$site_name = "BirdNET-Pi";
|
|
} else {
|
|
$site_name = $config['SITE_NAME'];
|
|
}
|
|
|
|
$models = array("BirdNET_6K_GLOBAL_MODEL", "BirdNET_GLOBAL_3K_V2.3_Model_FP16");
|
|
$audio_formats = array("8svx", "aif", "aifc", "aiff", "aiffc", "al", "amb", "amr-nb", "amr-wb", "anb", "au", "avr", "awb", "caf", "cdda", "cdr", "cvs", "cvsd", "cvu", "dat", "dvms", "f32", "f4", "f64", "f8", "fap", "flac", "fssd", "gsm", "gsrt", "hcom", "htk", "ima", "ircam", "la", "lpc", "lpc10", "lu", "mat", "mat4", "mat5", "maud", "mp2", "mp3", "nist", "ogg", "paf", "prc", "pvf", "raw", "s1", "s16", "s2", "s24", "s3", "s32", "s4", "s8", "sb", "sd2", "sds", "sf", "sl", "sln", "smp", "snd", "sndfile", "sndr", "sndt", "sou", "sox", "sph", "sw", "txw", "u1", "u16", "u2", "u24", "u3", "u32", "u4", "u8", "ub", "ul", "uw", "vms", "voc", "vorbis", "vox", "w64", "wav", "wavpcm", "wv", "wve", "xa", "xi");
|
|
$freqshift_tools = array("sox", "ffmpeg");
|
|
|
|
//Database Languages
|
|
$langs = array(
|
|
'not-selected' => 'Not Selected',
|
|
"af" => "Afrikaans",
|
|
"ca" => "Catalan",
|
|
"cs" => "Czech",
|
|
"zh" => "Chinese",
|
|
"hr" => "Croatian",
|
|
"da" => "Danish",
|
|
"nl" => "Dutch",
|
|
"en" => "English",
|
|
"et" => "Estonian",
|
|
"fi" => "Finnish",
|
|
"fr" => "French",
|
|
"de" => "German",
|
|
"hu" => "Hungarian",
|
|
"is" => "Icelandic",
|
|
"id" => "Indonesia",
|
|
"it" => "Italian",
|
|
"ja" => "Japanese",
|
|
"lv" => "Latvian",
|
|
"lt" => "Lithuania",
|
|
"no" => "Norwegian",
|
|
"pl" => "Polish",
|
|
"pt" => "Portugues",
|
|
"ru" => "Russian",
|
|
"sk" => "Slovak",
|
|
"sl" => "Slovenian",
|
|
"es" => "Spanish",
|
|
"sv" => "Swedish",
|
|
"th" => "Thai",
|
|
"uk" => "Ukrainian"
|
|
);
|
|
|
|
//Reference to database connection
|
|
$DB_CONN = null;
|
|
$USER_AUTHENTICATED = false;
|
|
|
|
/**
|
|
* Connects to the bird.db SQLite3 database
|
|
* @return SQLite3
|
|
*/
|
|
function connect_to_birdsdb()
|
|
{
|
|
global $DB_CONN, $api_incl;
|
|
|
|
//Initially check to see if the DB is already connected, it will not be null if it has
|
|
if ($DB_CONN == null) {
|
|
try {
|
|
$DB_CONN = new SQLite3(getFilePath('birds.db'), SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
|
|
if ($DB_CONN == False) {
|
|
birdnet_error_log("connect_to_birdsdb:: birds.db database is busy");
|
|
}
|
|
} catch (Exception $sql_exec) {
|
|
birdnet_error_log("connect_to_birdsdb:: Exception occurred while trying to open birds.db - " . $sql_exec->getMessage());
|
|
}
|
|
}
|
|
|
|
return $DB_CONN;
|
|
}
|
|
|
|
/**
|
|
* Disconnects the database
|
|
*
|
|
* @return void
|
|
*/
|
|
function disconnect_from_birdsdb()
|
|
{
|
|
global $DB_CONN;
|
|
|
|
if ($DB_CONN != null) {
|
|
return $DB_CONN->close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Executes the supplied query and returns all results
|
|
*
|
|
* @param $query string The query to execute
|
|
* @param $bind_params string Any values that should be bound into the query
|
|
* @param $fetchAllRecords string Any values that should be bound into the query
|
|
* @param $fetchMode string Controls how result data is returned, default is @->default('SQLITE3_ASSOC') Associative Array;
|
|
* @return array
|
|
*/
|
|
function db_execute_query($query, $bind_params = [], $fetchAllRecords = false, $fetchMode = SQLITE3_ASSOC)
|
|
{
|
|
global $DB_CONN;
|
|
$success = false;
|
|
$message = '';
|
|
$data_to_return = null;
|
|
|
|
//Connect to the DB
|
|
connect_to_birdsdb();
|
|
try {
|
|
$stmt = $DB_CONN->prepare($query);
|
|
//
|
|
if (!empty($bind_params)) {
|
|
//Loop over the bind values and add them
|
|
foreach ($bind_params as $bind_key => $bind_value) {
|
|
$stmt->bindValue($bind_key, $bind_value);
|
|
}
|
|
}
|
|
|
|
if ($stmt == False) {
|
|
//get caller's function name
|
|
$caller_func_name = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'];
|
|
$error_msg = "$caller_func_name => db_execute_custom_query:: birds.db database is busy or a query error occurred";
|
|
//
|
|
$success = false;
|
|
$message = $error_msg;
|
|
$data_to_return = null;
|
|
|
|
//Log the error message
|
|
birdnet_error_log($error_msg);
|
|
} else {
|
|
$result = $stmt->execute();
|
|
//Initial result collection
|
|
$resultArray = $result->fetchArray($fetchMode);
|
|
|
|
if ($fetchAllRecords) {
|
|
$multiArray = array(); //array to store all rows
|
|
//Loop over the results to collect them all
|
|
while ($resultArray !== false) {
|
|
array_push($multiArray, $resultArray); //insert all rows to $multiArray
|
|
$resultArray = $result->fetchArray($fetchMode);
|
|
}
|
|
unset($resultArray); //unset temporary variable
|
|
|
|
$data_to_return = $multiArray;
|
|
} else {
|
|
$data_to_return = $resultArray;
|
|
}
|
|
|
|
//
|
|
$success = true;
|
|
$message = 'Ok';
|
|
}
|
|
} catch (Exception $sql_exec) {
|
|
$caller_func_name = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'];
|
|
$error_msg = "$caller_func_name => db_execute_custom_query:: Exception occurred while executing query - " . $sql_exec->getMessage();
|
|
//
|
|
$success = false;
|
|
$message = $error_msg;
|
|
$data_to_return = null;
|
|
|
|
birdnet_error_log($error_msg);
|
|
}
|
|
|
|
return array('success' => $success, 'message' => $message, 'data' => $data_to_return);
|
|
}
|
|
|
|
/**
|
|
* Returns a count of detections in the database
|
|
* @return array
|
|
*/
|
|
function getDetectionCountAll()
|
|
{
|
|
return db_execute_query('SELECT COUNT(*) FROM detections');
|
|
}
|
|
|
|
/**
|
|
* Returns today's count of detections in the database
|
|
* @return array
|
|
*/
|
|
function getDetectionCountToday()
|
|
{
|
|
return db_execute_query('SELECT COUNT(*) FROM detections WHERE Date == DATE(\'now\', \'localtime\')');
|
|
}
|
|
|
|
/**
|
|
* Returns detection info for the most recent detection today
|
|
* @return array
|
|
*/
|
|
function getMostRecentDetectionToday()
|
|
{
|
|
return db_execute_query('SELECT * FROM detections WHERE Date == DATE(\'now\', \'localtime\') ORDER BY TIME DESC LIMIT 1', [], true);
|
|
}
|
|
|
|
/**
|
|
* Returns a count of detections in the past hour
|
|
* @return array
|
|
*/
|
|
function getDetectionCountLastHour()
|
|
{
|
|
return db_execute_query('SELECT COUNT(*) FROM detections WHERE Date == Date(\'now\', \'localtime\') AND TIME >= TIME(\'now\', \'localtime\', \'-1 hour\')');
|
|
}
|
|
|
|
/**
|
|
* Returns the most recent detection
|
|
* @return array|string
|
|
*/
|
|
function getMostRecentDetection($limit = 1)
|
|
{
|
|
return db_execute_query('SELECT Com_Name, Sci_Name, Date, Time, Confidence, File_Name FROM detections ORDER BY Date DESC, Time DESC LIMIT :limit', [':limit' => $limit], true);
|
|
}
|
|
|
|
/**
|
|
* Returns a talley for todays detected species
|
|
* @return array
|
|
*/
|
|
function getSpeciesTalley($range = "today", $start_date = null, $end_date = null)
|
|
{
|
|
$range = strtolower($range);
|
|
|
|
if ($range == "today") {
|
|
$result = db_execute_query('SELECT COUNT(DISTINCT(Com_Name)) FROM detections WHERE Date == Date(\'now\', \'localtime\')');
|
|
} else if ($range == "custom") {
|
|
$result = db_execute_query('SELECT COUNT(DISTINCT(Com_Name)) FROM detections WHERE Date == :start_date', [':start_date' => $start_date], false);
|
|
} else if ($range == "range") {
|
|
$result = db_execute_query('SELECT COUNT(DISTINCT(Com_Name)) FROM detections WHERE Date BETWEEN :start_date AND :end_date', [':start_date' => $start_date, ':end_date' => $end_date]);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Returns a species talley from ALL detections
|
|
* @return array
|
|
*/
|
|
function getAllSpeciesTalley()
|
|
{
|
|
return db_execute_query('SELECT COUNT(DISTINCT(Com_Name)) FROM detections');
|
|
}
|
|
|
|
/**
|
|
* Returns the number of detections on the specified date
|
|
*
|
|
* @param $date string Date to count detections for
|
|
* @return array
|
|
*/
|
|
function getDetectionCountByDate($date, $date_range = false)
|
|
{
|
|
return db_execute_query("SELECT COUNT(*) FROM detections WHERE Date == :date", [':date' => $date], false);
|
|
}
|
|
|
|
/**
|
|
* Returns detections and counts for the specified date and start & finish times
|
|
*
|
|
* @param $date string Date to count detections for
|
|
* @param $starttime string Search for detections after this time
|
|
* @param $endtime string And detections up to this time
|
|
* @return array
|
|
*/
|
|
function getDetectionBreakdownByTime($date, $starttime, $endtime)
|
|
{
|
|
return db_execute_query('SELECT DISTINCT(Com_Name), COUNT(*) FROM detections WHERE Date == :date AND Time > :start_time AND Time < :end_time AND Confidence > 0.75 GROUP By Com_Name ORDER BY COUNT(*) DESC',
|
|
[
|
|
':date' => $date,
|
|
':start_time' => $starttime,
|
|
':end_time' => $endtime
|
|
],
|
|
true);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the detection count for a specified bird
|
|
* @param $birdName string Species Name to get stats for
|
|
* @return array
|
|
*/
|
|
function getBirdDetectionStats($birdName)
|
|
{
|
|
//Cleanup the bird/species name
|
|
$birdName = str_replace("_", " ", $birdName);
|
|
|
|
$birdDetections = db_execute_query('SELECT Date, COUNT(*) AS Detections FROM detections WHERE Com_Name = :com_name AND Date BETWEEN DATE("now", "-30 days") AND DATE("now") GROUP BY Date', [':com_name' => $birdName], true);
|
|
|
|
// Fetch the result set as an associative array
|
|
$data = array();
|
|
foreach ($birdDetections as $birdDetection) {
|
|
$data[$birdDetection['Date']] = $birdDetection['Detections'];
|
|
}
|
|
|
|
// Create an array of all dates in the last 14 days
|
|
$last14Days = array();
|
|
for ($i = 0; $i < 31; $i++) {
|
|
$last14Days[] = date('Y-m-d', strtotime("-$i days"));
|
|
}
|
|
|
|
// Merge the data array with the last14Days array
|
|
$data = array_merge(array_fill_keys($last14Days, 0), $data);
|
|
|
|
// Sort the data by date in ascending order
|
|
ksort($data);
|
|
|
|
// Convert the data to an array of objects
|
|
$data = array_map(function ($date, $count) {
|
|
return array('date' => $date, 'count' => $count);
|
|
}, array_keys($data), $data);
|
|
|
|
// Return the data as JSON - overwrite the result data
|
|
$birdDetections['data'] = json_encode($data);
|
|
|
|
return $birdDetections;
|
|
}
|
|
|
|
/**
|
|
* Returns detection info for the specified
|
|
* @param $birdName string Species name to get detection info on
|
|
* @param $date string OPTIONAL - The date on which to list species on
|
|
* @param $sort string OPTIONAL - Whether to sort the species by confidence value (so species ranked by detection accuracy/confidence)
|
|
* @return array|string
|
|
*/
|
|
function getSpeciesDetectionInfo($birdName, $date = null, $sort = null)
|
|
{
|
|
//If no date is supplied, then list a unique list of detection dates in the DB, sorted descending
|
|
if ($date == null) {
|
|
//No date set so automatically search by date and time if no sort value is set
|
|
if (isset($sort) && $sort == "confidence") {
|
|
$detectionInfo = db_execute_query("SELECT * FROM detections where Com_Name == :birdname ORDER BY Confidence DESC", [':birdname' => $birdName], true);
|
|
} else {
|
|
$detectionInfo = db_execute_query("SELECT * FROM detections where Com_Name == :birdname ORDER BY Date DESC, Time DESC", [':birdname' => $birdName], true);
|
|
}
|
|
} else {
|
|
//Date set so use that to filter the results depending on the sort value
|
|
if (isset($sort) && $sort == "confidence") {
|
|
$detectionInfo = db_execute_query("SELECT * FROM detections where Com_Name == :birdname AND Date == :date ORDER BY Confidence DESC", [':birdname' => $birdName, ':date' => $date], true);
|
|
} else {
|
|
$detectionInfo = db_execute_query("SELECT * FROM detections where Com_Name == :birdname AND Date == :date ORDER BY Time DESC", [':birdname' => $birdName, ':date' => $date], true);
|
|
}
|
|
}
|
|
|
|
return $detectionInfo;
|
|
}
|
|
|
|
/**
|
|
* Returns the detections for a specified filename
|
|
*
|
|
* @param $filename string The filename on which to get detections for
|
|
* @return array|string
|
|
*/
|
|
function getDetectionsByFilename($filename)
|
|
{
|
|
return db_execute_query("SELECT * FROM detections where File_name == :filename ORDER BY Date DESC, Time DESC", [':filename' => $filename], true);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of specie names on a specified date if supplied, else lists valid dates which can be passed again for specific list of species on that date
|
|
*
|
|
* @param $date string OPTIONAL - The date on which to list species on
|
|
* @param $sort string OPTIONAL - Whether to sort the species by occurrence (so species ranked by number of detections)
|
|
* @return array|string
|
|
*/
|
|
function getDetectionsByDate($date = null, $sort = null)
|
|
{
|
|
|
|
//If no date is supplied, then list a unique list of species in the DB
|
|
if ($date == null) {
|
|
$detectionsByDateResult = db_execute_query('SELECT DISTINCT(Date) FROM detections GROUP BY Date ORDER BY Date DESC', null, true);
|
|
} else {
|
|
//Else a date was supplied, first check the sort order if any
|
|
if (isset($sort) && $sort == "occurrences") {
|
|
$detectionsByDateResult = db_execute_query("SELECT DISTINCT(Com_Name) FROM detections WHERE Date == :date GROUP BY Com_Name ORDER BY COUNT(*) DESC", [':date' => $date], true);
|
|
} else {
|
|
$detectionsByDateResult = db_execute_query("SELECT DISTINCT(Com_Name) FROM detections WHERE Date == :date ORDER BY Com_Name", [':date' => $date], true);
|
|
}
|
|
}
|
|
|
|
return $detectionsByDateResult;
|
|
}
|
|
|
|
/**
|
|
* Returns a list detections for a species if supplied, else lists all detected species by name
|
|
*
|
|
* @param $species_name string OPTIONAL - List detections for this specific species
|
|
* @param $sort string OPTIONAL - Whether to sort the species by occurrence (so species ranked by number of detections)
|
|
* @return array
|
|
*/
|
|
function getDetectionsBySpecies($species_name = null, $sort = null)
|
|
{
|
|
//If no species is is supplied, then list all species
|
|
if (!isset($species_name)) {
|
|
if (isset($sort) && $sort == "occurrences") {
|
|
//Sort by occurrences
|
|
$speciesDetections = db_execute_query('SELECT DISTINCT(Com_Name) FROM detections GROUP BY Com_Name ORDER BY COUNT(*) DESC', null, true);
|
|
} else {
|
|
//Don't sort by occurrences
|
|
$speciesDetections = db_execute_query('SELECT DISTINCT(Com_Name) FROM detections ORDER BY Com_Name ASC', null, true);
|
|
}
|
|
} else {
|
|
//Else a species name was supplied, first check the sort order if any
|
|
$speciesDetections = db_execute_query("SELECT * FROM detections WHERE Com_Name == :species_name ORDER BY Com_Name", [':species_name' => $species_name], true);
|
|
//Also get the highest confidence record for this species
|
|
$speciesDetections_MaxConf = db_execute_query("SELECT Date, Time, Sci_Name, MAX(Confidence), File_Name FROM detections WHERE Com_Name == :species_name ORDER BY Com_Name", [':species_name' => $species_name], true);
|
|
}
|
|
|
|
//Rearrange data
|
|
$newReturnData = [];
|
|
$newReturnData['species'] = $speciesDetections['data'];
|
|
//Check to see if we have to get max confidence results for the species also
|
|
if (isset($speciesDetections_MaxConf)) {
|
|
$newReturnData['species_MaxConf'] = $speciesDetections_MaxConf['data'];
|
|
}
|
|
|
|
$speciesDetections['data'] = $newReturnData;
|
|
|
|
return $speciesDetections;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of species either ordered alphabetically (default) or ordered by the number of detections the species has
|
|
*
|
|
* @param $sort string OPTIONAL - Sort result alphabetically (supply null) or by number of occurrences (supply "occurrences"
|
|
* @return array
|
|
*/
|
|
function getSpeciesBestRecordingList($sort = null)
|
|
{
|
|
if (isset($sort) && $sort == "occurrences") {
|
|
//Sort by occurrences
|
|
$speciesBestRecording = db_execute_query('SELECT Date, Time, File_Name, Com_Name, COUNT(*), MAX(Confidence) FROM detections GROUP BY Com_Name ORDER BY COUNT(*) DESC', null, true);
|
|
|
|
} else {
|
|
//Don't sort by occurrences, sort by alphabetical
|
|
$speciesBestRecording = db_execute_query('SELECT Date, Time, File_Name, Com_Name, COUNT(*), MAX(Confidence) FROM detections GROUP BY Com_Name ORDER BY Com_Name ASC', null, true);
|
|
}
|
|
|
|
return $speciesBestRecording;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of best recordings for a supplied species
|
|
*
|
|
* @param $species_name string Name of the species
|
|
* @return array
|
|
*/
|
|
function getBestRecordingsForSpecies($species_name)
|
|
{
|
|
return db_execute_query("SELECT Com_Name, Sci_Name, COUNT(*), MAX(Confidence), File_Name, Date, Time from detections WHERE Com_Name = :species_name", [':species_name' => $species_name], true);
|
|
}
|
|
|
|
/**
|
|
* Get a list of todays detections
|
|
*
|
|
* @param $display_limit string Number of results to return
|
|
* @param $search_term string OPTIONAL: Return results that match the supplied term
|
|
* @param $hard_limit string OPTIONAL: Return a fix number of results
|
|
* @return array
|
|
*/
|
|
function getTodaysDetections($display_limit, $search_term = null, $hard_limit = null)
|
|
{
|
|
$bind_params = [];
|
|
|
|
if (isset($search_term)) {
|
|
if (strtolower(explode(" ", $search_term)[0]) == "not") {
|
|
$not = "NOT ";
|
|
$operator = "AND";
|
|
$search_term = str_replace("not ", "", $search_term);
|
|
$search_term = str_replace("NOT ", "", $search_term);
|
|
} else {
|
|
$not = "";
|
|
$operator = "OR";
|
|
}
|
|
$searchquery = "AND (Com_name " . $not . "LIKE :search_term " .
|
|
$operator . " Sci_name " . $not . "LIKE :search_term " .
|
|
$operator . " Confidence " . $not . "LIKE :search_term " .
|
|
$operator . " File_Name " . $not . "LIKE :search_term " .
|
|
$operator . " Time " . $not . "LIKE :search_term)";
|
|
|
|
$bind_params = [':search_term' => '%' . $search_term . '%'];
|
|
} else {
|
|
$searchquery = "";
|
|
}
|
|
|
|
if (isset($display_limit) && is_numeric($display_limit)) {
|
|
$bind_params[':display_limit'] = (intval($display_limit) - 40);
|
|
$result = db_execute_query('SELECT Date, Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') ' . $searchquery . ' ORDER BY Time DESC LIMIT :display_limit,40', $bind_params, true);
|
|
} else {
|
|
// legacy mode
|
|
if (isset($hard_limit) && is_numeric($hard_limit)) {
|
|
$bind_params[':hard_limit'] = $hard_limit;
|
|
$result = db_execute_query('SELECT Date, Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') ' . $searchquery . ' ORDER BY Time DESC LIMIT :hard_limit', $bind_params, true);
|
|
} else {
|
|
$result = db_execute_query('SELECT Date, Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') ' . $searchquery . ' ORDER BY Time DESC', $bind_params, true);
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Returns the species talley for last week and the week prior
|
|
*
|
|
* @return array[]
|
|
*/
|
|
function getWeeklyReportSpeciesTalley()
|
|
{
|
|
$last_week_dates = getLastWeekDates();
|
|
$startdate = $last_week_dates['start_date'];
|
|
$enddate = $last_week_dates['end_date'];
|
|
|
|
|
|
$totalspeciestally = getSpeciesTalley('range', date("Y-m-d", $startdate), date("Y-m-d", $enddate));
|
|
$priortotalspeciestally = getSpeciesTalley('range', date("Y-m-d", $startdate - (7 * 86400)), date("Y-m-d", $enddate - (7 * 86400)));
|
|
|
|
return ['totalspeciestally' => $totalspeciestally, 'priortotalspeciestally' => $priortotalspeciestally];
|
|
}
|
|
|
|
/**
|
|
* Returns the species talley for last week and the week prior
|
|
*
|
|
* @return array[]
|
|
*/
|
|
function getWeeklyReportSpeciesDetectionCounts($detections_asc = false)
|
|
{
|
|
$last_week_dates = getLastWeekDates();
|
|
$startdate = $last_week_dates['start_date'];
|
|
$enddate = $last_week_dates['end_date'];
|
|
|
|
|
|
$sort_order = $detections_asc ? 'ASC' : 'DESC';
|
|
|
|
$detections = db_execute_query('SELECT DISTINCT(Com_Name), COUNT(*) FROM detections WHERE Date BETWEEN :start_date AND :end_date GROUP By Com_Name ORDER BY COUNT(*) ' . $sort_order, [':start_date' => date("Y-m-d", $startdate), ':end_date' => date("Y-m-d", $enddate)], true);
|
|
$totalcount = db_execute_query('SELECT DISTINCT(Com_Name), COUNT(*) FROM detections WHERE Date BETWEEN :start_date AND :end_date', [':start_date' => date("Y-m-d", $startdate), ':end_date' => date("Y-m-d", $enddate)], true);
|
|
$priortotalcount = db_execute_query('SELECT DISTINCT(Com_Name), COUNT(*) FROM detections WHERE Date BETWEEN :start_date AND :end_date', [':start_date' => date("Y-m-d", date("Y-m-d", $startdate - (7 * 86400))), ':end_date' => date("Y-m-d", date("Y-m-d", $enddate - (7 * 86400)))], true);
|
|
|
|
return ['detections' => $detections, 'totalcount' => $totalcount, 'priortotalcount' => $priortotalcount];
|
|
}
|
|
|
|
/**
|
|
* Returns last weeks detection count for the specified species
|
|
*
|
|
* @param string $species_name Species to get last weeks detection count for
|
|
* @param bool $this_week Whether to find detections in or outside last week
|
|
* @return array
|
|
*/
|
|
function getWeeklyReportSpeciesDetection($species_name, $this_week = true)
|
|
{
|
|
$last_week_dates = getLastWeekDates();
|
|
$startdate = $last_week_dates['start_date'];
|
|
$enddate = $last_week_dates['end_date'];
|
|
|
|
if ($this_week) {
|
|
$result = db_execute_query('SELECT COUNT(*) FROM detections WHERE Com_Name == :species_name AND Date BETWEEN :start_date AND :end_date', [':species_name' => $species_name, ':start_date' => date("Y-m-d", $startdate - (7 * 86400)), ':end_date' => date("Y-m-d", $enddate - (7 * 86400))], false);
|
|
} else {
|
|
$result = db_execute_query('SELECT COUNT(*) FROM detections WHERE Com_Name == :species_name AND Date NOT BETWEEN :start_date AND :end_date', [':species_name' => $species_name, ':start_date' => date("Y-m-d", $startdate), ':end_date' => date("Y-m-d", $enddate)], false);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Deletes a specified detection by filename
|
|
*
|
|
* @param $filename string The filename of the detection e.g 2023-04-25/Pacific_Koel/Pacific_Koel-76-2023-04-25-birdnet-RTSP_2-16:24:05.mp3
|
|
* @return array
|
|
*/
|
|
function deleteDetection($filename)
|
|
{
|
|
global $DB_CONN, $api_incl;
|
|
$success = false;
|
|
$message = '';
|
|
$data_to_return = null;
|
|
|
|
//Check authentication before going any further
|
|
//the front end would have done this check already and such should pass
|
|
authenticateUser();
|
|
|
|
$filename_exploded = explode("/", $filename);
|
|
$actual_filename = $filename_exploded[2];
|
|
|
|
|
|
//If the detection was successfully deleted, remove the mp3 and png spectrogram
|
|
$file_pointer = getDirectory('home') . "/BirdSongs/Extracted/By_Date/" . $filename;
|
|
if (!exec("sudo rm $file_pointer && sudo rm $file_pointer.png")) {
|
|
$message = "OK";
|
|
$statement = db_execute_query('DELETE FROM detections WHERE File_Name = :filename LIMIT 1', [':filename' => $actual_filename]);
|
|
} else {
|
|
$message = "Error";
|
|
}
|
|
|
|
//
|
|
$success = true;
|
|
//Message set above
|
|
$data_to_return = $statement;
|
|
|
|
return array('success' => $success, 'message' => $message, 'data' => $data_to_return);
|
|
}
|
|
|
|
/**
|
|
* Protects the specified file from deletion
|
|
*
|
|
* @param $mode string Set the mode to add (protect) to remove (unprotect) the specified filepath from protection
|
|
* @param $filename_to_protect string The filename of the detection to protect e.g 2023-04-25/Pacific_Koel/Pacific_Koel-76-2023-04-25-birdnet-RTSP_2-16:24:05.mp3
|
|
* @return array
|
|
*/
|
|
function protectDetectionFromDeletion($mode, $filename_to_protect)
|
|
{
|
|
global $api_incl;
|
|
$success = false;
|
|
$message = '';
|
|
$data_to_return = null;
|
|
|
|
$mode = strtolower($mode);
|
|
|
|
$scripts_dir = getDirectory('scripts');
|
|
$disk_check_exclude_file = $scripts_dir . "/disk_check_exclude.txt";
|
|
|
|
//Initially create the file if it doesn't exist
|
|
if (!file_exists($disk_check_exclude_file)) {
|
|
file_put_contents($disk_check_exclude_file, "##start\n##end\n");
|
|
}
|
|
|
|
if ($mode == "protect") {
|
|
// load the data and delete the line from the array which is ##end,
|
|
// so that all excluded files sit between the ##start and ##end tags
|
|
$lines = file($disk_check_exclude_file);
|
|
$last = sizeof($lines) - 1;
|
|
unset($lines[$last]);
|
|
|
|
//Open the file and truncate contents
|
|
if (($myfile = fopen($disk_check_exclude_file, "w")) !== false) {
|
|
$txt = $filename_to_protect;
|
|
//Write all existing lines
|
|
fwrite($myfile, implode("", $lines));
|
|
//Write out the lines for the file were trying to exclude
|
|
fwrite($myfile, $txt . "\n");
|
|
fwrite($myfile, $txt . ".png\n");
|
|
//Insert the end tag again to make it the last line
|
|
fwrite($myfile, "##end\n");
|
|
|
|
fclose($myfile);
|
|
//
|
|
$success = true;
|
|
$message = "OK";
|
|
} else {
|
|
$success = false;
|
|
$message = "Unable to open file! " . $disk_check_exclude_file;
|
|
}
|
|
} else if ($mode == "unprotect") {
|
|
$lines = file($disk_check_exclude_file);
|
|
$search = $filename_to_protect;
|
|
|
|
$result = '';
|
|
foreach ($lines as $line) {
|
|
if (stripos($line, $search) === false && stripos($line, $search . ".png") === false) {
|
|
$result .= $line;
|
|
}
|
|
}
|
|
if (file_put_contents($disk_check_exclude_file, $result) !== false) {
|
|
$success = true;
|
|
$message = "OK";
|
|
} else {
|
|
$success = true;
|
|
$message = "Failed writing contents after removing file from protection, back to file $disk_check_exclude_file";
|
|
}
|
|
}
|
|
|
|
return array('success' => $success, 'message' => $message, 'data' => $data_to_return);
|
|
}
|
|
|
|
/**
|
|
* Frequency shifts the specified audio file to aid listening for hearing impaired people
|
|
*
|
|
* @param $filename
|
|
* @param $performShift
|
|
* @return array
|
|
*/
|
|
function frequencyShiftDetectionAudio($filename, $performShift = null)
|
|
{
|
|
global $config, $api_incl;
|
|
$success = false;
|
|
$message = '';
|
|
$data_to_return = null;
|
|
|
|
//Check authentication before going any further
|
|
//the front end would have done this check already and such should pass
|
|
authenticateUser();
|
|
|
|
$shifted_path = getDirectory('shifted_audio') . '/';
|
|
|
|
$pp = pathinfo($filename);
|
|
$dir = $pp['dirname'];
|
|
$fn = $pp['filename'];
|
|
$ext = $pp['extension'];
|
|
$pi = getDirectory('extracted_bydate') . '/';
|
|
|
|
if (isset($performShift) && $performShift == true) {
|
|
$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);
|
|
}
|
|
|
|
//
|
|
$success = true;
|
|
$message = "OK";
|
|
|
|
return array('success' => $success, 'message' => $message, 'data' => $data_to_return);
|
|
}
|
|
|
|
/**
|
|
* Adds the supplied Flickr Image Id to the list of blacklisted images
|
|
*
|
|
* @param $imageID
|
|
* @return array
|
|
*/
|
|
function blacklistFlickrImage($imageID)
|
|
{
|
|
$success = false;
|
|
$message = '';
|
|
$data_to_return = null;
|
|
|
|
$scripts_dir = getDirectory('scripts');
|
|
|
|
//Append the Flickr Image ID the blacklist file
|
|
if (($file_handle = fopen($scripts_dir . "/blacklisted_images.txt", 'a+')) !== false) {
|
|
fwrite($file_handle, $imageID . "\n");
|
|
fclose($file_handle);
|
|
//
|
|
$success = true;
|
|
$message = "Ok";
|
|
} else {
|
|
$success = false;
|
|
$message = "Failed";
|
|
}
|
|
|
|
return array('success' => $success, 'message' => $message, 'data' => $data_to_return);
|
|
}
|
|
|
|
/**
|
|
* Finds and returns a flicker image for the supplied detection, detection data must have the following columns Com_Name, Sci_Name, Date, Time, Confidence, File_Name
|
|
* getMostRecentDetections
|
|
*
|
|
* @param $detection_data
|
|
* @param bool $getAll
|
|
* @return string[][]
|
|
*/
|
|
function getFlickrImage($detection_data, $getAll = false)
|
|
{
|
|
global $config;
|
|
|
|
$success = false;
|
|
$message = '';
|
|
$data_to_return = null;
|
|
|
|
$flickr_data_to_return = array(
|
|
'Com_Name' => 'N/A',
|
|
'Sci_Name' => 'N/A',
|
|
'Date' => 'N/A',
|
|
'Time' => 'N/A',
|
|
'Confidence' => '0',
|
|
'File_Name' => 'N/A',
|
|
'Com_Name_clean' => 'N/A',
|
|
'Sci_Name_clean' => 'N/A',
|
|
'photos' => null,
|
|
'filename_path' => 'N/A',
|
|
'filename_formatted' => 'N/A'
|
|
);
|
|
$foundImage = false;
|
|
|
|
if (isset($detection_data)) {
|
|
$extracted_dir = getDirectory('extracted');
|
|
$home_dir = getDirectory('home');
|
|
|
|
//Setup out flickr image cache in the session
|
|
if (!isset($_SESSION['images'])) {
|
|
$_SESSION['images'] = [];
|
|
}
|
|
|
|
//Cleanup the Birds common and scientic name
|
|
$comname_clean = preg_replace('/ /', '_', $detection_data['Com_Name']);
|
|
$sciname_clean = preg_replace('/ /', '_', $detection_data['Sci_Name']);
|
|
$comname_clean = preg_replace('/\'/', '', $comname_clean);
|
|
$filename_path = "/By_Date/" . $detection_data['Date'] . "/" . $comname_clean . "/" . $detection_data['File_Name'];
|
|
$filename_formatted = $detection_data['Date'] . "/" . $comname_clean . "/" . $detection_data['File_Name'];
|
|
$args = "&license=2%2C3%2C4%2C5%2C6%2C9&orientation=square,portrait";
|
|
$comnameprefix = "%20bird";
|
|
|
|
if (!empty($config["FLICKR_API_KEY"])) {
|
|
|
|
if (!empty($config["FLICKR_FILTER_EMAIL"])) {
|
|
if (!isset($_SESSION["FLICKR_FILTER_EMAIL"])) {
|
|
unset($_SESSION['images']);
|
|
$_SESSION['FLICKR_FILTER_EMAIL'] = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.people.findByEmail&api_key=" . $config["FLICKR_API_KEY"] . "&find_email=" . $config["FLICKR_FILTER_EMAIL"] . "&format=json&nojsoncallback=1"), true)["user"]["nsid"];
|
|
}
|
|
$args = "&user_id=" . $_SESSION['FLICKR_FILTER_EMAIL'];
|
|
$comnameprefix = "";
|
|
} else {
|
|
if (isset($_SESSION["FLICKR_FILTER_EMAIL"])) {
|
|
unset($_SESSION["FLICKR_FILTER_EMAIL"]);
|
|
unset($_SESSION['images']);
|
|
}
|
|
}
|
|
|
|
|
|
// if we already searched flickr for this species before, use the previous image rather than doing an unneccesary api call
|
|
$key = array_search($comname_clean, array_column($_SESSION['images'], 0));
|
|
if ($key !== false && !$getAll) {
|
|
$flickr_image_data = $_SESSION['images'][$key];
|
|
|
|
$foundImage = true;
|
|
//Extract data out of the image data, either in our $_SESSION var already is recently pushed (new data)
|
|
$flickr_data_to_return['photos'][0]['image_url'] = $flickr_image_data[1];
|
|
$flickr_data_to_return['photos'][0]['photo_title'] = $flickr_image_data[2];
|
|
$flickr_data_to_return['photos'][0]['modal_text'] = $flickr_image_data[3];
|
|
$flickr_data_to_return['photos'][0]['author_link'] = $flickr_image_data[4];
|
|
$flickr_data_to_return['photos'][0]['license_url'] = $flickr_image_data[5];
|
|
} else {
|
|
// Get license information if we haven't already
|
|
if (empty($licenses_urls)) {
|
|
$licenses_url = "https://api.flickr.com/services/rest/?method=flickr.photos.licenses.getInfo&api_key=" . $config["FLICKR_API_KEY"] . "&format=json&nojsoncallback=1";
|
|
$licenses_response = file_get_contents($licenses_url);
|
|
$licenses_data = json_decode($licenses_response, true)["licenses"]["license"];
|
|
foreach ($licenses_data as $license) {
|
|
$license_id = $license["id"];
|
|
$license_name = $license["name"];
|
|
$license_url = $license["url"];
|
|
$licenses_urls[$license_id] = $license_url;
|
|
}
|
|
}
|
|
|
|
// only open the file once per script execution
|
|
if (!isset($lines)) {
|
|
$lines = file($home_dir . "/BirdNET-Pi/model/labels_flickr.txt");
|
|
}
|
|
// convert sci name to English name
|
|
foreach ($lines as $line) {
|
|
if (strpos($line, $detection_data['Sci_Name']) !== false) {
|
|
$sci_name_as_english_name = trim(explode("_", $line)[1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Read the blacklisted image ids from the file into an array
|
|
$blacklisted_ids = array_map('trim', file($home_dir . "/BirdNET-Pi/scripts/blacklisted_images.txt"));
|
|
|
|
// Make the API call
|
|
$flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=" . $config["FLICKR_API_KEY"] . "&text=" . str_replace(" ", "%20", $sci_name_as_english_name) . $comnameprefix . "&sort=relevance" . $args . "&per_page=5&media=photos&format=json&nojsoncallback=1"), true)["photos"]["photo"];
|
|
|
|
// Find the first photo that is not blacklisted or is not the specific blacklisted id
|
|
$photo = null;
|
|
foreach ($flickrjson as $flickrphoto) {
|
|
if ($flickrphoto["id"] !== "4892923285" && !in_array($flickrphoto["id"], $blacklisted_ids)) {
|
|
$photo = $flickrphoto;
|
|
|
|
$license_url = "https://api.flickr.com/services/rest/?method=flickr.photos.getInfo&api_key=" . $config["FLICKR_API_KEY"] . "&photo_id=" . $photo["id"] . "&format=json&nojsoncallback=1";
|
|
$license_response = file_get_contents($license_url);
|
|
$license_info = json_decode($license_response, true)["photo"]["license"];
|
|
$license_url = $licenses_urls[$license_info];
|
|
|
|
$modaltext = "https://flickr.com/photos/" . $photo["owner"] . "/" . $photo["id"];
|
|
$authorlink = "https://flickr.com/people/" . $photo["owner"];
|
|
$imageurl = 'https://farm' . $photo["farm"] . '.static.flickr.com/' . $photo["server"] . '/' . $photo["id"] . '_' . $photo["secret"] . '.jpg';
|
|
|
|
if (!$getAll) {
|
|
//Get single image then break the loop
|
|
array_push($_SESSION['images'], array($comname_clean, $imageurl, $photo["title"], $modaltext, $authorlink, $license_url));
|
|
$flickr_image_data = $_SESSION['images'][count($_SESSION['images']) - 1];
|
|
|
|
//Extract data out of the image data, either in our $_SESSION var already is recently pushed (new data)
|
|
$flickr_data_to_return['photos'][0]['image_url'] = $flickr_image_data[1];
|
|
$flickr_data_to_return['photos'][0]['photo_title'] = $flickr_image_data[2];
|
|
$flickr_data_to_return['photos'][0]['modal_text'] = $flickr_image_data[3];
|
|
$flickr_data_to_return['photos'][0]['author_link'] = $flickr_image_data[4];
|
|
$flickr_data_to_return['photos'][0]['license_url'] = $flickr_image_data[5];
|
|
|
|
//Break out of the loop
|
|
break;
|
|
} else {
|
|
//Get all images
|
|
//Extract data out of the image data, either in our $_SESSION var already is recently pushed (new data)
|
|
$flickr_data_to_return['photos'][(int)$flickrphoto["id"]]['image_url'] = $imageurl;
|
|
$flickr_data_to_return['photos'][(int)$flickrphoto["id"]]['photo_title'] = $photo["title"];
|
|
$flickr_data_to_return['photos'][(int)$flickrphoto["id"]]['modal_text'] = $modaltext;
|
|
$flickr_data_to_return['photos'][(int)$flickrphoto["id"]]['author_link'] = $authorlink;
|
|
$flickr_data_to_return['photos'][(int)$flickrphoto["id"]]['license_url'] = $license_url;
|
|
}
|
|
}
|
|
}
|
|
|
|
$foundImage = true;
|
|
$success = true;
|
|
$message = 'Flickr image data successfully downloaded';
|
|
}
|
|
|
|
//Fill out all other common data
|
|
$flickr_data_to_return['Com_Name'] = $detection_data['Com_Name'];
|
|
$flickr_data_to_return['Sci_Name'] = $detection_data['Sci_Name'];
|
|
$flickr_data_to_return['Date'] = $detection_data['Date'];
|
|
$flickr_data_to_return['Time'] = $detection_data['Time'];
|
|
$flickr_data_to_return['Confidence'] = array_key_exists('Confidence', $detection_data) ? $detection_data['Confidence'] : 0;
|
|
$flickr_data_to_return['File_Name'] = $detection_data['File_Name'];
|
|
|
|
$flickr_data_to_return['Com_Name_clean'] = $comname_clean;
|
|
$flickr_data_to_return['Sci_Name_clean'] = $sciname_clean;
|
|
|
|
$flickr_data_to_return['filename_path'] = $filename_path;
|
|
$flickr_data_to_return['filename_formatted'] = $filename_formatted;
|
|
}
|
|
}
|
|
|
|
return array('success' => $success, 'message' => $message, 'image_found' => $foundImage, 'data' => $flickr_data_to_return);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the filename of todays species chart for the main page
|
|
*
|
|
* @return string
|
|
*/
|
|
function getChartString()
|
|
{
|
|
$myDate = date('Y-m-d');
|
|
$chart = "Combo-$myDate.png";
|
|
return $chart;
|
|
}
|
|
|
|
/**
|
|
* Returns the start and end dates for last week
|
|
*
|
|
* @return string[]
|
|
*/
|
|
function getLastWeekDates()
|
|
{
|
|
$startdate = strtotime('last sunday') - (7 * 86400);
|
|
$enddate = strtotime('last sunday') - (1 * 86400);
|
|
|
|
return ['start_date' => $startdate, 'end_date' => $enddate];
|
|
}
|
|
|
|
/**
|
|
* Change Database Language
|
|
*
|
|
* @param $model
|
|
* @param $language
|
|
* @return string|null
|
|
*/
|
|
function changeLanguage($model, $language)
|
|
{
|
|
global $config, $user;
|
|
//Re-parse the config just in case
|
|
parseConfig();
|
|
$result = "";
|
|
|
|
// Update Language settings only if a change is requested
|
|
if ($model != $config['MODEL'] || $language != $config['DATABASE_LANG']) {
|
|
if (strlen($language) == 2) {
|
|
$scripts_dir = getDirectory('scripts');
|
|
|
|
// Archive old language file
|
|
$result = syslog_shell_exec("cp -f " . getFilePath('labels.txt') . " " . getFilePath('labels.txt.old'), $user);
|
|
|
|
if ($model == "BirdNET_GLOBAL_3K_V2.3_Model_FP16") {
|
|
// Install new language label file
|
|
$result = syslog_shell_exec("chmod +x $scripts_dir/install_language_label_nm.sh && $scripts_dir/install_language_label_nm.sh -l $language", $user);
|
|
} else {
|
|
$result = syslog_shell_exec("$scripts_dir/install_language_label.sh -l $language", $user);
|
|
}
|
|
|
|
syslog(LOG_INFO, "Successfully changed language to '$language' and model to '$model'");
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Directory Path Helper, returns a full directory path for a supplied directory name e.g home, processed, extracted
|
|
*
|
|
* @param string $dir The directory to obtain a path for
|
|
* @return string
|
|
*/
|
|
function getDirectory($dir)
|
|
{
|
|
global $config, $home;
|
|
$dir = strtolower($dir);
|
|
|
|
if ($dir == "home") {
|
|
return $home;
|
|
} else if ($dir == "birdnet-pi" || $dir == "birdnet_pi") {
|
|
return getDirectory('home') . '/BirdNET-Pi';
|
|
} else if ($dir == "recs_dir" || $dir == "recordings_dir") {
|
|
$recs_dir_setting = $config['RECS_DIR'];
|
|
return str_replace('$HOME', getDirectory('home'), $recs_dir_setting);
|
|
} else if ($dir == "processed") {
|
|
$processed_dir_setting = $config['PROCESSED'];
|
|
return getDirectory('recs_dir') . str_replace('${RECS_DIR}', '', $processed_dir_setting);
|
|
} else if ($dir == "extracted") {
|
|
$extracted_dir_setting = $config['EXTRACTED'];
|
|
return getDirectory('recs_dir') . str_replace('${RECS_DIR}', '', $extracted_dir_setting);
|
|
} elseif ($dir == "extracted_bydate" || $dir == "extracted_by_date") {
|
|
return getDirectory('extracted') . '/By_Date';
|
|
} elseif ($dir == "shifted_audio" || $dir == "shifted_dir") {
|
|
return getDirectory('home') . '/BirdSongs/Extracted/By_Date/shifted';
|
|
} elseif ($dir == "database") {
|
|
// NOT USED
|
|
return getDirectory('birdnet_pi') . '/database';
|
|
} elseif ($dir == "config") {
|
|
// NOT USED
|
|
return getDirectory('birdnet_pi') . '/config';
|
|
} elseif ($dir == "models" || $dir == "model") {
|
|
return getDirectory('birdnet_pi') . '/model';
|
|
} elseif ($dir == "python3_ve") {
|
|
return getDirectory('birdnet_pi') . '/birdnet/bin';
|
|
} elseif ($dir == "scripts") {
|
|
return getDirectory('birdnet_pi') . '/scripts';
|
|
} elseif ($dir == "templates") {
|
|
return getDirectory('birdnet_pi') . '/templates';
|
|
} elseif ($dir == "web" || $dir == "www") {
|
|
return getDirectory('birdnet_pi') . '/homepage';
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Returns the full filepath for the specified filename
|
|
*
|
|
* @param $filename
|
|
* @return string
|
|
*/
|
|
function getFilePath($filename)
|
|
{
|
|
if ($filename == "analyzing_now.txt") {
|
|
return getDirectory('birdnet_pi') . "/analyzing_now.txt";
|
|
//
|
|
} else if ($filename == "apprise.txt") {
|
|
return getDirectory('birdnet_pi') . "/apprise.txt";
|
|
//
|
|
} else if ($filename == "birdnet.conf") {
|
|
return getDirectory('birdnet_pi') . "/birdnet.conf";
|
|
//
|
|
} else if ($filename == "BirdDB.txt") {
|
|
return getDirectory('birdnet_pi') . "/BirdDB.txt";
|
|
//
|
|
} else if ($filename == "birds.db") {
|
|
return getDirectory('scripts') . "/birds.db";
|
|
//
|
|
} else if ($filename == "blacklisted_images.txt") {
|
|
return getDirectory('scripts') . "/blacklisted_images.txt";
|
|
//
|
|
} else if ($filename == "disk_check_exclude.txt") {
|
|
return getDirectory('scripts') . "/disk_check_exclude.txt";
|
|
//
|
|
} else if ($filename == "email_template") {
|
|
return getDirectory('scripts') . "/email_template";
|
|
//
|
|
} else if ($filename == "email_template2") {
|
|
return getDirectory('scripts') . "/email_template2";
|
|
//
|
|
} else if ($filename == "exclude_species_list.txt") {
|
|
return getDirectory('scripts') . "/exclude_species_list.txt";
|
|
//
|
|
} else if ($filename == "firstrun.ini") {
|
|
return getDirectory('home') . "/firstrun.ini";
|
|
//
|
|
} else if ($filename == "HUMAN.txt") {
|
|
return getDirectory('birdnet_pi') . "/HUMAN.txt";
|
|
//
|
|
} else if ($filename == "include_species_list.txt") {
|
|
return getDirectory('scripts') . "/include_species_list.txt";
|
|
//
|
|
} else if ($filename == "labels.txt" || $filename == "labels.txt.old") {
|
|
return getDirectory('model') . "/$filename";
|
|
//
|
|
} else if ($filename == "labels_flickr.txt") {
|
|
return getDirectory('model') . "/labels_flickr.txt";
|
|
//
|
|
} else if ($filename == "labels_l18n.zip") {
|
|
return getDirectory('model') . "/labels_l18n.zip";
|
|
//
|
|
} else if ($filename == "labels_lang.txt") {
|
|
return getDirectory('model') . "/labels_lang.txt";
|
|
//
|
|
} else if ($filename == "labels_nm.zip") {
|
|
return getDirectory('model') . "/labels_nm.zip";
|
|
//
|
|
} else if ($filename == "lastrun.txt") {
|
|
return getDirectory('scripts') . "/lastrun.txt";
|
|
//
|
|
} else if ($filename == "python3") {
|
|
return getDirectory('python3_ve') . "/python3 ";
|
|
//
|
|
} else if ($filename == "python3_appraise") {
|
|
return getDirectory('python3_ve') . "/apprise ";
|
|
//
|
|
} else if ($filename == "species.py") {
|
|
return getDirectory('scripts') . "/species.py";
|
|
//
|
|
} else if ($filename == "thisrun.txt") {
|
|
return getDirectory('scripts') . "/thisrun.txt";
|
|
//
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Updates the setting value for the supplied setting name, if the setting doesn't exist it will be created
|
|
*
|
|
* @param $setting_name string Setting Name e.g SITE_NAME, BIRDWEATHER_ID, MODEL etc
|
|
* @param $setting_value string|int Setting value - This value has to be exactly how it needs to be stored (same as what was used in preg_replace) and properly escaped eg "\"asetting"\"
|
|
* @param $post_save_command string CommaAnd to execute after saving
|
|
* @return void
|
|
*/
|
|
function saveSetting($setting_name, $setting_value, $post_save_command = null)
|
|
{
|
|
global $config;
|
|
|
|
//Check authentication before going any further
|
|
//the front end would have done this check already and such should pass
|
|
authenticateUser();
|
|
|
|
//Setting exists already, see if the value changed
|
|
if (array_key_exists($setting_name, $config)) {
|
|
//Strip any outer quotes from the setting value (e.g "$rec_card") and then test if it change how it was originally tested
|
|
//https://stackoverflow.com/questions/9734758/remove-quotes-from-start-and-end-of-string-in-php
|
|
$setting_value_clean = preg_replace('~^"?(.*?)"?$~', '$1', $setting_value);
|
|
|
|
if (strcmp($setting_value_clean, $config[$setting_name]) !== 0) {
|
|
$contents = file_get_contents(getFilePath('birdnet.conf'));
|
|
$contents2 = file_get_contents(getFilePath('thisrun.txt'));
|
|
//
|
|
if ($contents !== false && $contents2 !== false) {
|
|
//Update the value
|
|
$contents = preg_replace("/$setting_name=.*/", "$setting_name=$setting_value", $contents);
|
|
$contents2 = preg_replace("/$setting_name=.*/", "$setting_name=$setting_value", $contents2);
|
|
|
|
//Write all the data out again to the the respective files
|
|
// $fh = fopen("/etc/birdnet/birdnet.conf", "w");
|
|
$fh = fopen(getFilePath('birdnet.conf'), "w");
|
|
$fh2 = fopen(getFilePath('thisrun.txt'), "w");
|
|
|
|
if ($fh !== false && $fh2 !== false) {
|
|
fwrite($fh, $contents);
|
|
fwrite($fh2, $contents2);
|
|
//Check if we need to execute a command after saving
|
|
if (isset($post_save_command)) {
|
|
sleep(1);
|
|
if (!is_array($post_save_command)) {
|
|
executeSysCommand($post_save_command);
|
|
} else {
|
|
//array of commands
|
|
foreach ($post_save_command as $single_command) {
|
|
executeSysCommand($single_command);
|
|
}
|
|
}
|
|
}
|
|
//Reload the settings to update our global $config variable
|
|
parseConfig();
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
//Create the setting in the setting file
|
|
shell_exec('sudo echo "' . $setting_name . '=' . $setting_value . '" >> ' . getFilePath('birdnet.conf'));
|
|
//also update this run txt file
|
|
shell_exec('sudo echo "' . $setting_name . '=' . $setting_value . '" >> ' . getFilePath('thisrun.txt'));
|
|
|
|
//Check if we need to execute a command after saving
|
|
if (isset($post_save_command)) {
|
|
sleep(1);
|
|
if (!is_array($post_save_command)) {
|
|
executeSysCommand($post_save_command);
|
|
} else {
|
|
//array of commands
|
|
foreach ($post_save_command as $single_command) {
|
|
executeSysCommand($single_command);
|
|
}
|
|
}
|
|
}
|
|
//Reload the settings to update our global $config variable
|
|
parseConfig();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the value for the supplied setting name
|
|
*
|
|
* @param $setting_name
|
|
* @return mixed|null
|
|
*/
|
|
function getSetting($setting_name)
|
|
{
|
|
global $config;
|
|
$setting_value = null;
|
|
if (array_key_exists($setting_name, $config)) {
|
|
$setting_value = $config[$setting_name];
|
|
}
|
|
|
|
return $setting_value;
|
|
}
|
|
|
|
/**
|
|
* Returns the username of the user with User ID 1000
|
|
*
|
|
* @return string
|
|
*/
|
|
function getUser()
|
|
{
|
|
global $user;
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* Executes commands against the system through a shorthand command type
|
|
*
|
|
* @param $command_type string
|
|
* @param $extra_data_to_pass string|[] Data or params that will be passed through to the command
|
|
* @return false|string|null
|
|
*/
|
|
function executeSysCommand($command_type, $extra_data_to_pass = null)
|
|
{
|
|
global $user, $home;
|
|
|
|
//Check authentication before going any further
|
|
//the front end would have done this check already and such should pass
|
|
authenticateUser();
|
|
|
|
$command_type = strtolower($command_type);
|
|
$result = null;
|
|
|
|
if ($command_type == "appraise_notification") {
|
|
$result = shell_exec(getFilePath('python3_appraise') . " -vv --plugin-path " . $home . "/.apprise/plugins " . " -t '" . escapeshellcmd($extra_data_to_pass['title']) . "' -b '" . escapeshellcmd($extra_data_to_pass['body']) . "' " . $extra_data_to_pass['attach'] . " " . $extra_data_to_pass['cf'] . " ");
|
|
//
|
|
} else if ($command_type == "current_timezone") {
|
|
$result = shell_exec("cat /etc/timezone");
|
|
//
|
|
} else if ($command_type == "disable_ntp") {
|
|
$result = shell_exec("sudo timedatectl set-ntp false");
|
|
//
|
|
} else if ($command_type == "enable_ntp") {
|
|
$result = shell_exec("sudo timedatectl set-ntp true");
|
|
//
|
|
} else if ($command_type == "is_ntp_active") {
|
|
$result = shell_exec("sudo timedatectl | grep \"NTP service: active\"");
|
|
//
|
|
} else if ($command_type == "restart_php") {
|
|
$result = serviceMaintenance('restart php.service');
|
|
//
|
|
} else if ($command_type == "restart_services") {
|
|
syslog(LOG_INFO, "Restarting Services");
|
|
$result = serviceMaintenance('restart core.services');
|
|
//
|
|
} else if ($command_type == "restart birdnet_recording") {
|
|
$result = serviceMaintenance('restart birdnet_recording.service');
|
|
//
|
|
} else if ($command_type == "restart icecast2") {
|
|
$result = serviceMaintenance('restart icecast2.service');
|
|
//
|
|
} else if ($command_type == "restart livestream") {
|
|
$result = serviceMaintenance('restart livestream.service');
|
|
//
|
|
} else if ($command_type == "restart spectrogram_viewer") {
|
|
$result = serviceMaintenance('restart spectrogram_viewer.service');
|
|
//
|
|
} else if ($command_type == "set_date") {
|
|
$command = "sudo date -s '" . escapeshellcmd($extra_data_to_pass['date']) . " " . escapeshellcmd($extra_data_to_pass['time']) . "'";
|
|
$result = shell_exec($command);
|
|
//
|
|
} else if ($command_type == "set_timezone") {
|
|
$command = "sudo timedatectl set-timezone " . escapeshellcmd($extra_data_to_pass);
|
|
$result = shell_exec($command);
|
|
//
|
|
} else if ($command_type == "test_threshold") {
|
|
$command = "sudo -u $user " . getFilePath('python3') . ' ' . getFilePath('species.py') . " --threshold " . escapeshellcmd($extra_data_to_pass) . " 2>&1";
|
|
$result = shell_exec($command);
|
|
//
|
|
} else if ($command_type == "update_birdnet") {
|
|
$result = shell_exec("update_birdnet.sh");
|
|
//
|
|
} else if ($command_type == "reboot_birdnet") {
|
|
$result = shell_exec("sudo reboot");
|
|
//
|
|
} else if ($command_type == "shutdown_birdnet") {
|
|
$result = shell_exec("sudo shutdown now");
|
|
//
|
|
} else if ($command_type == "clear_all_data") {
|
|
$result = shell_exec("sudo clear_all_data.sh");
|
|
//
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Updates the caddy configuration, only ever called when BirdNET-Pi Password or Site URL is set
|
|
*
|
|
* @return void
|
|
*/
|
|
function update_caddyfile()
|
|
{
|
|
exec("sudo /usr/local/bin/update_caddyfile.sh > /dev/null 2>&1 &");
|
|
}
|
|
|
|
/**
|
|
* Performs tasks such as stop, restart, disable and enable on the supplied service
|
|
* input command must contain the service name and must contain a keyword that describes the action
|
|
* positioning does not matter
|
|
* eg. restart livestream.service, stop birdnet_recording.service
|
|
*
|
|
* @param $command String Command containing a keyword [stop,restart,disable,enable] and the service name
|
|
* @return false|string|null
|
|
*/
|
|
function serviceMaintenance($command)
|
|
{
|
|
$command = trim($command);
|
|
$result = "";
|
|
|
|
//Check authentication before going any further
|
|
//the front end would have done this check already and such should pass
|
|
authenticateUser();
|
|
|
|
///e.g $command = 'service stop livestream.service', match the service name
|
|
////// BIRDNET LOG SERVICE //////
|
|
if (strpos($command, 'birdnet_log.service') !== false) {
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('sudo systemctl stop birdnet_log.service 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('sudo systemctl restart birdnet_log.service 2>&1');
|
|
} else if (strpos($command, 'disable') !== false) {
|
|
$result = shell_exec('sudo systemctl disable --now birdnet_log.service 2>&1');
|
|
} else if (strpos($command, 'enable') !== false) {
|
|
$result = shell_exec('sudo systemctl enable --now birdnet_log.service 2>&1');
|
|
}
|
|
} ////// BIRDNET SERVER SERVICE //////
|
|
else if (strpos($command, 'birdnet_server.service') !== false) {
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('sudo systemctl stop birdnet_server.service 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('sudo systemctl restart birdnet_server.service 2>&1');
|
|
} else if (strpos($command, 'disable') !== false) {
|
|
$result = shell_exec('sudo systemctl disable --now birdnet_server.service 2>&1');
|
|
} else if (strpos($command, 'enable') !== false) {
|
|
$result = shell_exec('sudo systemctl enable --now birdnet_server.service 2>&1');
|
|
}
|
|
} ////// BIRDNET ANALYSIS SERVICE //////
|
|
else if (strpos($command, 'birdnet_analysis.service') !== false) {
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('sudo systemctl stop birdnet_analysis.service 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('sudo systemctl restart birdnet_analysis.service 2>&1');
|
|
} else if (strpos($command, 'disable') !== false) {
|
|
$result = shell_exec('sudo systemctl disable --now birdnet_analysis.service 2>&1');
|
|
} else if (strpos($command, 'enable') !== false) {
|
|
$result = shell_exec('sudo systemctl enable --now birdnet_analysis.service 2>&1');
|
|
}
|
|
} ////// BIRDNET STATS SERVICE //////
|
|
else if (strpos($command, 'birdnet_stats.service') !== false) {
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('sudo systemctl stop birdnet_stats.service 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('sudo systemctl restart birdnet_stats.service 2>&1');
|
|
} else if (strpos($command, 'disable') !== false) {
|
|
$result = shell_exec('sudo systemctl disable --now birdnet_stats.service 2>&1');
|
|
} else if (strpos($command, 'enable') !== false) {
|
|
$result = shell_exec('sudo systemctl enable --now birdnet_stats.service 2>&1');
|
|
}
|
|
} ////// BIRDNET RECORDING SERVICE //////
|
|
else if (strpos($command, 'birdnet_recording.service') !== false) {
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('sudo systemctl stop birdnet_recording.service 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('sudo systemctl restart birdnet_recording.service 2>&1');
|
|
} else if (strpos($command, 'disable') !== false) {
|
|
$result = shell_exec('sudo systemctl disable --now birdnet_recording.service 2>&1');
|
|
} else if (strpos($command, 'enable') !== false) {
|
|
$result = shell_exec('sudo systemctl enable --now birdnet_recording.service 2>&1');
|
|
}
|
|
} ////// LIVESTREAM SERVICE //////
|
|
else if (strpos($command, 'livestream.service') !== false) {
|
|
//Check which keyword exists
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('sudo systemctl stop livestream.service && sudo systemctl stop icecast2.service 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('sudo systemctl restart livestream.service && sudo systemctl restart icecast2.service 2>&1');
|
|
} else if (strpos($command, 'disable') !== false) {
|
|
$result = shell_exec('sudo systemctl disable --now livestream.service && sudo systemctl disable icecast2 && sudo systemctl stop icecast2.service 2>&1');
|
|
} else if (strpos($command, 'enable') !== false) {
|
|
$result = shell_exec('sudo systemctl enable icecast2 && sudo systemctl start icecast2.service && sudo systemctl enable --now livestream.service 2>&1');
|
|
}
|
|
} ////// WEB TERMINAL SERVICE //////
|
|
else if (strpos($command, 'web_terminal.service') !== false) {
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('sudo systemctl stop web_terminal.service 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('sudo systemctl restart web_terminal.service 2>&1');
|
|
} else if (strpos($command, 'disable') !== false) {
|
|
$result = shell_exec('sudo systemctl disable --now web_terminal.service 2>&1');
|
|
} else if (strpos($command, 'enable') !== false) {
|
|
$result = shell_exec('sudo systemctl enable --now web_terminal.service 2>&1');
|
|
}
|
|
} ////// EXTRACTION SERVICE //////
|
|
else if (strpos($command, 'extraction.service') !== false) {
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('sudo systemctl stop extraction.service 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('sudo systemctl restart extraction.service 2>&1');
|
|
} else if (strpos($command, 'disable') !== false) {
|
|
$result = shell_exec('sudo systemctl disable --now extraction.service 2>&1');
|
|
} else if (strpos($command, 'enable') !== false) {
|
|
$result = shell_exec('sudo systemctl enable --now extraction.service 2>&1');
|
|
}
|
|
}////// CHART VIEWER SERVICE //////
|
|
else if (strpos($command, 'chart_viewer.service') !== false) {
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('sudo systemctl stop chart_viewer.service 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('sudo systemctl restart chart_viewer.service 2>&1');
|
|
} else if (strpos($command, 'disable') !== false) {
|
|
$result = shell_exec('sudo systemctl disable --now chart_viewer.service 2>&1');
|
|
} else if (strpos($command, 'enable') !== false) {
|
|
$result = shell_exec('sudo systemctl enable --now chart_viewer.service 2>&1');
|
|
}
|
|
} ////// SPECTROGRAM VIEWER SERVICE //////
|
|
else if (strpos($command, 'spectrogram_viewer.service') !== false) {
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('sudo systemctl stop spectrogram_viewer.service 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('sudo systemctl restart spectrogram_viewer.service 2>&1');
|
|
} else if (strpos($command, 'disable') !== false) {
|
|
$result = shell_exec('sudo systemctl disable --now spectrogram_viewer.service 2>&1');
|
|
} else if (strpos($command, 'enable') !== false) {
|
|
$result = shell_exec('sudo systemctl enable --now spectrogram_viewer.service 2>&1');
|
|
}
|
|
} ////// CORE SERVICES //////
|
|
else if ((strpos($command, 'core.services') !== false) || (strpos($command, 'core services') !== false)) {
|
|
if (strpos($command, 'stop') !== false) {
|
|
$result = shell_exec('stop_core_services.sh 2>&1');
|
|
} else if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec('restart_services.sh 2>&1');
|
|
}
|
|
} ////// PHP FPM //////
|
|
else if (strpos($command, 'php.service') !== false) {
|
|
if (strpos($command, 'restart') !== false) {
|
|
$result = shell_exec("sudo service php7.4-fpm restart");
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Returns a the supplied timestamp as sometime human-readable
|
|
* from https://stackoverflow.com/questions/2690504/php-producing-relative-date-time-from-timestamps
|
|
*
|
|
* @param $ts
|
|
* @return false|string
|
|
*/
|
|
function relativeTime($ts)
|
|
{
|
|
if (!ctype_digit($ts))
|
|
$ts = strtotime($ts);
|
|
|
|
$diff = time() - $ts;
|
|
if ($diff == 0)
|
|
return 'now';
|
|
elseif ($diff > 0) {
|
|
$day_diff = floor($diff / 86400);
|
|
if ($day_diff == 0) {
|
|
if ($diff < 60) return 'just now';
|
|
if ($diff < 120) return '1 minute ago';
|
|
if ($diff < 3600) return floor($diff / 60) . ' minutes ago';
|
|
if ($diff < 7200) return '1 hour ago';
|
|
if ($diff < 86400) return floor($diff / 3600) . ' hours ago';
|
|
}
|
|
if ($day_diff == 1) return 'Yesterday';
|
|
if ($day_diff < 7) return $day_diff . ' days ago';
|
|
if ($day_diff < 31) return ceil($day_diff / 7) . ' weeks ago';
|
|
if ($day_diff < 60) return 'last month';
|
|
return date('F Y', $ts);
|
|
} else {
|
|
$diff = abs($diff);
|
|
$day_diff = floor($diff / 86400);
|
|
if ($day_diff == 0) {
|
|
if ($diff < 120) return 'in a minute';
|
|
if ($diff < 3600) return 'in ' . floor($diff / 60) . ' minutes';
|
|
if ($diff < 7200) return 'in an hour';
|
|
if ($diff < 86400) return 'in ' . floor($diff / 3600) . ' hours';
|
|
}
|
|
if ($day_diff == 1) return 'Tomorrow';
|
|
if ($day_diff < 4) return date('l', $ts);
|
|
if ($day_diff < 7 + (7 - date('w'))) return 'next week';
|
|
if (ceil($day_diff / 7) < 4) return 'in ' . ceil($day_diff / 7) . ' weeks';
|
|
if (date('n', $ts) == date('n') + 1) return 'next month';
|
|
return date('F Y', $ts);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute system command as SUDO and logs it's output in the syslog
|
|
*
|
|
* @param $cmd string Command to Execute
|
|
* @param $sudo_user string User to execute as
|
|
* @return string
|
|
*/
|
|
function syslog_shell_exec($cmd, $sudo_user = null)
|
|
{
|
|
$output = "";
|
|
//Check authentication before going any further
|
|
//the front end would have done this check already and such should pass
|
|
authenticateUser();
|
|
|
|
if ($sudo_user) {
|
|
$cmd = "sudo -u $sudo_user $cmd";
|
|
}
|
|
$output = shell_exec($cmd);
|
|
|
|
if (strlen($output) > 0) {
|
|
syslog(LOG_INFO, $output);
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Loads in configuration settings from either thisrun or firstrun.txt
|
|
*
|
|
* @return void
|
|
*/
|
|
function parseConfig()
|
|
{
|
|
global $config;
|
|
|
|
if (file_exists(getFilePath('thisrun.txt'))) {
|
|
$config = parse_ini_file(getFilePath('thisrun.txt'));
|
|
} elseif (file_exists(getFilePath('firstrun.txt'))) {
|
|
$config = parse_ini_file(getFilePath('firstrun.txt'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the apprise config file
|
|
*
|
|
* @param $apprise_config
|
|
* @return void
|
|
*/
|
|
function updateAppriseConfig($apprise_config)
|
|
{
|
|
if (isset($apprise_config)) {
|
|
$appriseconfig_config_file = fopen(getFilePath("apprise.txt"), "w");
|
|
fwrite($appriseconfig_config_file, $apprise_config);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the apprise config in apprise.txt
|
|
* @return false|string
|
|
*/
|
|
function getAppriseConfig()
|
|
{
|
|
if (file_exists(getFilePath('apprise.txt'))) {
|
|
$apprise_config = file_get_contents(getFilePath('apprise.txt'));
|
|
} else {
|
|
$apprise_config = "";
|
|
}
|
|
|
|
return $apprise_config;
|
|
}
|
|
|
|
/**
|
|
* Return the linecount of labels.txt
|
|
* @return int
|
|
*/
|
|
function getLabelsCount()
|
|
{
|
|
return count(file(getFilePath('labels.txt')));
|
|
}
|
|
|
|
/**
|
|
* Returns the current git commit hash of the BirdNET-Pi respository
|
|
* @return false|string|null
|
|
*/
|
|
function getGitCurrentHash()
|
|
{
|
|
global $user;
|
|
return shell_exec("cd " . getDirectory('home') . "/BirdNET-Pi && sudo -u " . $user . " git rev-list --max-count=1 HEAD");
|
|
}
|
|
|
|
/**
|
|
* Runs git fetch against the BirdNET-Pi repository
|
|
* @return false|string|null
|
|
*/
|
|
function doGitFetch()
|
|
{
|
|
global $user;
|
|
return shell_exec("sudo -u" . $user . " git -C " . getDirectory('home') . "/BirdNET-Pi fetch 2>&1");
|
|
}
|
|
|
|
/**
|
|
* Get the git status of the BirdNET-Pi repository
|
|
* @return false|string|null
|
|
*/
|
|
function getGitStatus()
|
|
{
|
|
global $user;
|
|
$behind = 0;
|
|
//Fetch latest updates
|
|
doGitFetch();
|
|
$git_status = trim(shell_exec("sudo -u" . $user . " git -C " . getDirectory('home') . "/BirdNET-Pi status"));
|
|
|
|
//Extract the text numberof commits from 'Your branch is behind '....' by XX commits,
|
|
if (preg_match("/behind '.*?' by (\d+) commit(s?)\b/", $git_status, $matches)) {
|
|
$num_commits_behind = $matches[1];
|
|
$behind = $num_commits_behind;
|
|
}
|
|
|
|
if (preg_match('/\b(\d+)\b and \b(\d+)\b different commits each/', $git_status, $matches)) {
|
|
$num1 = (int)$matches[1];
|
|
$num2 = (int)$matches[2];
|
|
$sum = $num1 + $num2;
|
|
$behind = $sum;
|
|
}
|
|
|
|
return $behind;
|
|
}
|
|
|
|
/**
|
|
* Returns the status of the specified service
|
|
*
|
|
* @param $name string Name of the service
|
|
* @return string[]
|
|
*/
|
|
function getServiceStatus($name)
|
|
{
|
|
$message = '';
|
|
$status = '';
|
|
|
|
if ($name == "birdnet_server.service") {
|
|
$filesinproc = trim(shell_exec("ls " . getDirectory('home') . "/BirdSongs/Processed | wc -l"));
|
|
if ($filesinproc > 200) {
|
|
$message = "stalled - backlog of " . $filesinproc . " files in ~/BirdSongs/Processed/";
|
|
|
|
}
|
|
}
|
|
$status = shell_exec("sudo systemctl status " . $name . " | grep Active | grep ' active\| activating\|running\|waiting\|start'");
|
|
if (strlen($status) > 0) {
|
|
$message = "(active)";
|
|
} else {
|
|
$message = "(inactive)";
|
|
}
|
|
|
|
return ["status" => $status, "message" => $message];
|
|
}
|
|
|
|
/**
|
|
* Returns the boolean flag
|
|
* @return false
|
|
*/
|
|
function userIsAuthenticated()
|
|
{
|
|
global $USER_AUTHENTICATED;
|
|
return $USER_AUTHENTICATED;
|
|
}
|
|
|
|
/**
|
|
* When called, authenticates the user using the built-in PHP_AUTH. This is the orignial authentication code just wrapped in a function
|
|
* This will automatically terminate the script if authentication fails
|
|
*
|
|
* @param $rejection_message string Message to return to the user if authentication fails
|
|
* @return bool
|
|
*/
|
|
function authenticateUser($rejection_message = "You cannot edit the settings for this installation")
|
|
{
|
|
global $config, $USER_AUTHENTICATED;
|
|
parseConfig();
|
|
$USER_AUTHENTICATED = false;
|
|
|
|
$caddypwd = $config['CADDY_PWD'];
|
|
if (!isset($_SERVER['PHP_AUTH_USER'])) {
|
|
header('WWW-Authenticate: Basic realm="BirdNET-Pi"');
|
|
header('HTTP/1.0 401 Unauthorized');
|
|
echo '<table><tr><td>' . $rejection_message . '</td></tr></table>';
|
|
exit;
|
|
} else {
|
|
//Read the supplied details
|
|
$submittedpwd = $_SERVER['PHP_AUTH_PW'];
|
|
$submitteduser = $_SERVER['PHP_AUTH_USER'];
|
|
//
|
|
if ($submittedpwd !== $caddypwd || $submitteduser !== 'birdnet') {
|
|
header('WWW-Authenticate: Basic realm="BirdNET-Pi"');
|
|
header('HTTP/1.0 401 Unauthorized');
|
|
echo '<table><tr><td>' . $rejection_message . '</td></tr></table>';
|
|
exit;
|
|
} else {
|
|
$USER_AUTHENTICATED = true;
|
|
}
|
|
}
|
|
|
|
return $USER_AUTHENTICATED;
|
|
}
|
|
|
|
/**
|
|
* Logging helper to accept a message and process it accordingly
|
|
* @param $message
|
|
* @param $level
|
|
* @return void
|
|
*/
|
|
function birdnet_error_log($message, $level = 'error')
|
|
{
|
|
$logfile_path = getDirectory('scripts') . "/web-ui-error.log";
|
|
|
|
if (!file_exists($logfile_path)) {
|
|
touch($logfile_path);
|
|
}
|
|
|
|
error_log($message . "\r\n", 3, $logfile_path);
|
|
}
|
|
|
|
|
|
register_shutdown_function('disconnect_from_birdsdb'); |