&1';
$output = @shell_exec($cmd);
$counts = [];
if ($output !== null) {
foreach (preg_split('/\\r?\\n/', $output) as $line) {
$line = trim($line);
if ($line === '') continue;
if (preg_match('/^([0-9]+(?:\\.[0-9]+)?)(k?)\\s*:\\s*(.+)$/i', $line, $m)) {
$num = (float)$m[1];
if (strtolower($m[2]) === 'k') $num *= 1000;
$counts[$m[3]] = (int)round($num);
}
}
}
echo json_encode($counts, JSON_UNESCAPED_UNICODE);
exit;
}
/* ---------- DB open (RO unless deleting) ---------- */
$flags = isset($_GET['delete']) ? SQLITE3_OPEN_READWRITE : SQLITE3_OPEN_READONLY;
$db = new SQLite3(__DIR__ . '/birds.db', $flags);
$db->busyTimeout(1000);
/* Paths / lists */
$base_symlink = $home . '/BirdSongs/Extracted/By_Date';
$base = realpath($base_symlink);
$confirm_file = __DIR__ . '/confirmed_species_list.txt';
$exclude_file = __DIR__ . '/exclude_species_list.txt';
$whitelist_file = __DIR__ . '/whitelist_species_list.txt';
foreach ([$confirm_file, $exclude_file, $whitelist_file] as $file) {
if (!file_exists($file)) touch($file);
}
$confirmed_species = file_exists($confirm_file) ? file($confirm_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];
$excluded_species = file_exists($exclude_file) ? array_map(fn($l) => explode('_', trim($l), 2)[0], file($exclude_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)) : [];
$whitelisted_species = file_exists($whitelist_file) ? array_map(fn($l) => explode('_', trim($l), 2)[0], file($whitelist_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)) : [];
$config = get_config();
$sf_thresh = isset($config['SF_THRESH']) ? (float)$config['SF_THRESH'] : 0.0;
/* ---------- helpers ---------- */
function join_path(...$parts): string { return preg_replace('#/+#', '/', implode('/', $parts)); }
function can_unlink(string $p): bool { return is_link($p) || is_file($p); }
function under_base(string $path, string $base): bool {
if ($base === false) return false;
$baseReal = rtrim(realpath($base) ?: $base, DIRECTORY_SEPARATOR);
$resolved = realpath($path);
if ($resolved === false) {
$parent = realpath(dirname($path));
if ($parent === false) return false;
$resolved = $parent . DIRECTORY_SEPARATOR . basename($path);
}
return $resolved === $baseReal || strpos($resolved, $baseReal . DIRECTORY_SEPARATOR) === 0;
}
/* Collect files/dirs for a species */
function collect_species_targets(SQLite3 $db, string $species, string $home, $base): array {
$stmt = $db->prepare('SELECT Date, Com_Name, Sci_Name, File_Name FROM detections WHERE Sci_Name = :name');
ensure_db_ok($stmt);
$stmt->bindValue(':name', $species, SQLITE3_TEXT);
$res = $stmt->execute();
$count = 0; $files = []; $dirs = []; $sci = null;
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
$count++; if ($sci === null) $sci = $row['Sci_Name'];
$dir = str_replace([' ', "'"], ['_', ''], $row['Com_Name']);
$candidates = [
join_path($home, 'BirdSongs/Extracted/By_Date', $row['Date'], $dir, $row['File_Name']),
join_path($home, 'BirdSongs/Extracted/By_Date/shifted', $row['Date'], $dir, $row['File_Name']),
];
foreach ($candidates as $c) {
if (can_unlink($c)) { $files[$c] = true; $dirs[] = dirname($c); continue; }
$d = realpath(dirname($c));
if ($d !== false) {
$alt = $d . DIRECTORY_SEPARATOR . basename($c);
if (can_unlink($alt) && under_base($alt, $base)) { $files[$alt] = true; $dirs[] = dirname($alt); }
}
}
}
return ['count'=>$count, 'files'=>array_keys($files), 'dirs'=>array_values(array_unique($dirs)), 'sci'=>$sci];
}
/* ---------- toggle exclude/whitelist/confirmed ---------- */
if (isset($_GET['toggle'], $_GET['species'], $_GET['action'])) {
$list = $_GET['toggle'];
$species = htmlspecialchars_decode($_GET['species'], ENT_QUOTES);
if ($list === 'exclude') { $file = $exclude_file; }
elseif ($list === 'whitelist') { $file = $whitelist_file; }
elseif ($list === 'confirmed') { $file = $confirm_file; }
else { header('Content-Type: text/plain'); echo 'Invalid list type'; exit; }
$lines = file_exists($file) ? file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];
if ($_GET['action'] === 'add') {
if (!in_array($species, $lines, true)) $lines[] = $species;
} else {
$lines = array_values(array_filter($lines, fn($l) => $l !== $species));
}
file_put_contents($file, implode("\n", $lines) . (empty($lines) ? "" : "\n"));
header('Content-Type: text/plain'); echo 'OK'; exit;
}
/* ---------- count ---------- */
if (isset($_GET['getcounts'])) {
header('Content-Type: application/json');
if ($base === false) { http_response_code(500); exit(json_encode(['error' => 'Base directory not found'])); }
$species = htmlspecialchars_decode($_GET['getcounts'], ENT_QUOTES);
$info = collect_species_targets($db, $species, $home, $base);
echo json_encode(['count' => $info['count'], 'files' => count($info['files'])]); exit;
}
/* ---------- delete ---------- */
if (isset($_GET['delete'])) {
header('Content-Type: application/json');
if ($base === false) { http_response_code(500); exit(json_encode(['error' => 'Base directory not found'])); }
$species = htmlspecialchars_decode($_GET['delete'], ENT_QUOTES);
$info = collect_species_targets($db, $species, $home, $base);
$deleted = count($info['files']);
foreach ($info['dirs'] as $dir) {
if (!under_base($dir, $base)) continue;
if (exec("sudo rm -r $dir 2>&1", $output)) {
echo "Error - files deletion failed : " . implode(", ", $output) . "
";
exit;
}
}
$del = $db->prepare('DELETE FROM detections WHERE Sci_Name = :name');
ensure_db_ok($del);
$del->bindValue(':name', $species, SQLITE3_TEXT);
$del->execute();
$lines_deleted = $db->changes();
if ($info['sci'] !== null && file_exists($confirm_file)) {
$identifier = $info['sci'];
$lines = array_values(array_filter($confirmed_species, fn($l) => $l !== $identifier));
file_put_contents($confirm_file, implode("\n", $lines) . (empty($lines) ? "" : "\n"));
}
echo json_encode(['lines' => $lines_deleted, 'files' => $deleted]); exit;
}
/* ---------- query species aggregates ---------- */
$sql = <<query($sql);
?>
| Common Name |
Scientific Name |
Stats |
Count |
Max Confidence |
Last Seen |
Probability |
Confirmed |
Excluded |
Whitelisted |
Delete |
fetchArray(SQLITE3_ASSOC)) {
$common = $row['Com_Name'];
$scient = $row['Sci_Name'];
$count = (int)$row['Count'];
$max_confidence = round((float)$row['MaxConfidence'] * 100, 1);
$identifier = $row['Sci_Name'].'_'.$row['Com_Name'];
$identifier_sci = $row['Sci_Name'];
$lastSeen = $row['LastSeen'] ?? '';
$lastSeenSort = $lastSeen ? (strtotime($lastSeen) ?: 0) : 0;
$common_link = "{$common}";
$is_confirmed = in_array($identifier_sci, $confirmed_species, true);
$is_excluded = in_array($identifier_sci, $excluded_species, true);
$is_whitelisted = in_array($identifier_sci, $whitelisted_species, true);
$comnamegraph = str_replace("'", "\'", $row['Com_Name']);
$chart_cell = sprintf("
", $comnamegraph);
$identifier_js = addslashes($identifier);
$identifier_sci_js = addslashes($identifier_sci);
$confirm_cell = $is_confirmed
? "
"
: "";
$excl_cell = $is_excluded
? "
"
: "";
$white_cell = $is_whitelisted
? "
"
: "";
$sciname_raw = $row['Sci_Name'];
$info_url = get_info_url($sciname_raw);
if (!empty($info_url)) {
$url = $info_url['URL'] ?? $info_url;
$scient_link = "{$scient}";
} else {
$scient_link = "{$scient}";
}
echo ""
. "| {$common_link} | "
. "{$scient_link} | "
. "{$chart_cell} | "
. "{$count} | "
. "{$max_confidence}% | "
. "{$lastSeen} | "
. "0.0000 | "
. "".$confirm_cell." | "
. "".$excl_cell." | "
. "".$white_cell." | "
. " | "
. "
";
} ?>