From 4e43b5ee6969be05e549ba2622d8eaf8664a89d4 Mon Sep 17 00:00:00 2001 From: Twarner491 Date: Fri, 5 Jun 2026 16:28:18 -0700 Subject: [PATCH] [BUG] api: resolve recording/spectrogram dirs by normalized species name --- avian/api/recording.php | 22 +++++++++++++++++++--- avian/api/spectrogram.php | 18 +++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/avian/api/recording.php b/avian/api/recording.php index 33d3364..3335d88 100644 --- a/avian/api/recording.php +++ b/avian/api/recording.php @@ -141,16 +141,32 @@ if ($common === null) { // subdirectory named exactly $common, return the newest .mp3 inside. function newest_recording(string $rootDir, string $common): ?string { if (!is_dir($rootDir)) return null; + // Match species dirs by a normalised name so apostrophes / case / + // punctuation don't matter: "Anna's_Hummingbird" == "Annas_Hummingbird" + // == "annas hummingbird". BirdNET-Pi isn't consistent about the + // apostrophe in species dir names, which is why a bird like Anna's + // Hummingbird could 404 while every other species played fine. + $norm = function (string $s): string { + return preg_replace('/[^a-z0-9]/', '', strtolower($s)); + }; + $want = $norm($common); $dates = scandir($rootDir, SCANDIR_SORT_DESCENDING); if (!$dates) return null; foreach ($dates as $date) { if ($date[0] === '.') continue; - $speciesDir = "$rootDir/$date/$common"; - if (!is_dir($speciesDir)) continue; + $dayDir = "$rootDir/$date"; + if (!is_dir($dayDir)) continue; + $speciesDir = null; + foreach (scandir($dayDir) as $sub) { + if ($sub[0] === '.' || !is_dir("$dayDir/$sub")) continue; + if ($norm($sub) === $want) { $speciesDir = "$dayDir/$sub"; break; } + } + if ($speciesDir === null) continue; $files = scandir($speciesDir, SCANDIR_SORT_DESCENDING); if (!$files) continue; foreach ($files as $f) { - if (substr($f, -4) === '.mp3') { + // Skip zero-byte / truncated files a purge may have left behind. + if (substr($f, -4) === '.mp3' && @filesize("$speciesDir/$f") >= 64) { return "$speciesDir/$f"; } } diff --git a/avian/api/spectrogram.php b/avian/api/spectrogram.php index 169fdcb..7072b7d 100644 --- a/avian/api/spectrogram.php +++ b/avian/api/spectrogram.php @@ -128,16 +128,28 @@ $common = resolve_common($sci) ?? str_replace(' ', '_', $sci); function newest_spectrogram(string $rootDir, string $common): ?string { if (!is_dir($rootDir)) return null; + // Normalised match so apostrophes / case don't matter - same fix as + // recording.php (e.g. Anna's_Hummingbird vs Annas_Hummingbird). + $norm = function (string $s): string { + return preg_replace('/[^a-z0-9]/', '', strtolower($s)); + }; + $want = $norm($common); $dates = scandir($rootDir, SCANDIR_SORT_DESCENDING); if (!$dates) return null; foreach ($dates as $date) { if ($date[0] === '.') continue; - $speciesDir = "$rootDir/$date/$common"; - if (!is_dir($speciesDir)) continue; + $dayDir = "$rootDir/$date"; + if (!is_dir($dayDir)) continue; + $speciesDir = null; + foreach (scandir($dayDir) as $sub) { + if ($sub[0] === '.' || !is_dir("$dayDir/$sub")) continue; + if ($norm($sub) === $want) { $speciesDir = "$dayDir/$sub"; break; } + } + if ($speciesDir === null) continue; $files = scandir($speciesDir, SCANDIR_SORT_DESCENDING); if (!$files) continue; foreach ($files as $f) { - if (substr($f, -4) === '.png') { + if (substr($f, -4) === '.png' && @filesize("$speciesDir/$f") >= 64) { return "$speciesDir/$f"; } }