From 7a895f5717fada3ebcf7ac5ca2a81ec5bd51276f Mon Sep 17 00:00:00 2001 From: Josef Haupt Date: Mon, 6 Jul 2026 12:03:18 +0200 Subject: [PATCH] Faster look up for input selection in multi file tab --- birdnet_analyzer/gui/multi_file.py | 17 +++++++++++++---- birdnet_analyzer/gui/utils.py | 12 ++++++++++++ birdnet_analyzer/utils.py | 25 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/birdnet_analyzer/gui/multi_file.py b/birdnet_analyzer/gui/multi_file.py index 6029d2bb9..d5ce5d270 100644 --- a/birdnet_analyzer/gui/multi_file.py +++ b/birdnet_analyzer/gui/multi_file.py @@ -143,19 +143,28 @@ def build_multi_analysis_tab() -> gu.TAB_BUILDER_RESULT: ], ) + preview_limit = 100 + def select_directory_on_empty(): folder = gu.select_folder(state_key="batch-analysis-data-dir") if folder: - files_and_durations = gu.get_audio_files_and_durations(folder) - if len(files_and_durations) > 100: + # Only load durations for the first files shown in the preview. + # Fetch one extra to detect whether more files exist without + # walking (and probing durations for) the whole directory. + files_and_durations = gu.get_audio_files_and_durations( + folder, max_files=preview_limit + 1 + ) + if len(files_and_durations) > preview_limit: + # Count the remaining files with a fast walk that skips durations. + total = gu.count_audio_files(folder) return [ folder, folder, [ - *files_and_durations[:100], + *files_and_durations[:preview_limit], ( - f"{len(files_and_durations) - 100} " + f"{total - preview_limit} " f"{loc.localize('multi-tab-more-files-label')}", "...", ), diff --git a/birdnet_analyzer/gui/utils.py b/birdnet_analyzer/gui/utils.py index e7422c26f..b678177d1 100644 --- a/birdnet_analyzer/gui/utils.py +++ b/birdnet_analyzer/gui/utils.py @@ -150,6 +150,18 @@ def get_audio_files_and_durations(folder, max_files=None): return files_and_durations +def count_audio_files(folder): + """Counts the audio files in a folder without collecting their paths or durations. + + Args: + folder (str): The path to the folder containing audio files. + + Returns: + int: The number of audio files in the folder (recursively). + """ + return utils.count_audio_files(folder) + + def set_window(window): """ Sets the global _WINDOW variable to the provided window object. diff --git a/birdnet_analyzer/utils.py b/birdnet_analyzer/utils.py index 880cf47a4..042f9829f 100644 --- a/birdnet_analyzer/utils.py +++ b/birdnet_analyzer/utils.py @@ -145,6 +145,31 @@ def collect_audio_files(path: str, max_files: int | None = None): return sorted(files) +def count_audio_files(path: str) -> int: + """Counts all audio files in the given directory. + + Faster than ``collect_audio_files`` when only the number of files is needed, + as it neither stores nor sorts the paths. + + Args: + path: The directory to be searched. + + Returns: + The number of audio files in the directory (recursively). + """ + count = 0 + + for _, _, flist in os.walk(path): + for f in flist: + if ( + not f.startswith(".") + and f.rsplit(".", 1)[-1].lower() in ALLOWED_FILETYPES + ): + count += 1 + + return count + + def read_lines( path: str | Path | None, trim: bool = False, fail_on_blank_lines: bool = False ) -> list[str]: