Download progress - #984
Conversation
Models are fetched on first use inside birdnet.load; the library's tqdm bar goes to stderr, which the frozen GUI diverts to the log file, so a first-run download of several hundred MB looked like a hang. gui.utils.download_progress registers birdnet's scoped download callback for the duration of an analysis: with a gr.Progress it drives the bar (fraction plus MB counts, unknown sizes keep the bar visible), without one it announces the download as a toast, and a retry is surfaced as a warning. UI failures inside the callback are logged, never raised - an exception escaping it aborts the download in the library. The CLI keeps the library's tqdm bar unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
They import gui.utils, which imports pywebview at module level; the gui-tests extra in CI has no pywebview, so the module stubs it before the import, like test_startup_imports does. Also keeps test_settings.py free of unrelated additions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The library's scoped callback registration is a plain set/restore, so two overlapping GUI events (single-file and multi-file analysis run as separate gradio events) could misroute updates and leave a stale callback registered after both finished. The adapter now keeps one sink per handler thread and registers a single dispatcher with the library while any sink is active; the library invokes the callback synchronously on the thread that called birdnet.load, so the thread id identifies the event. Covered by a test that interleaves two events. The species tab, whose first run downloads the geo model, is wrapped as well; the train, embeddings and search tabs still show nothing during a first-run download. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A preset or params file can set the slider without changing the model, so the model-change handler never resets it. The slider's own change handler now returns it to 1.0 whenever the selected model does not take a sensitivity, so the disabled slider always shows the value the analysis uses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8b40fae to
872a102
Compare
There was a problem hiding this comment.
Pull request overview
This PR improves first-run UX and model-compatibility handling by surfacing BirdNET model download progress in the GUI and by making “sensitivity” explicitly a BirdNET 2.4/custom-classifier-only control across CLI, core analysis, and GUI.
Changes:
- Add a GUI-scoped download progress dispatcher that routes
birdnetlibrary download events to Gradio progress bars/toasts. - Introduce
supports_sensitivity/effective_sensitivityto drop sensitivity for BirdNET 3.x/Perch (with warnings) and disable the GUI sensitivity slider accordingly. - Update docs, localization strings, and add tests covering both download progress routing and sensitivity slider behavior.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
birdnet_analyzer/gui/utils.py |
Adds download progress routing + sensitivity-slider enable/disable & restore logic. |
birdnet_analyzer/gui/analysis.py |
Wraps analysis run in download_progress(...) so model downloads show progress during analysis. |
birdnet_analyzer/gui/species.py |
Wraps species list generation in download_progress() for download visibility. |
birdnet_analyzer/model_utils.py |
Adds sensitivity support helpers and enforces an effective sensitivity in inference. |
birdnet_analyzer/analyze/core.py |
Normalizes sensitivity early so params/resume metadata reflect the effective value. |
birdnet_analyzer/cli.py |
Updates --sensitivity help text to reflect BirdNET 3.0/Perch behavior. |
birdnet_analyzer/lang/en.json |
Adds localized strings for “downloading model” and “download retrying”. |
birdnet_analyzer/lang/de.json |
Same localization additions (German). |
birdnet_analyzer/lang/fi.json |
Same localization additions (Finnish). |
birdnet_analyzer/lang/fr.json |
Same localization additions (French). |
birdnet_analyzer/lang/id.json |
Same localization additions (Indonesian). |
birdnet_analyzer/lang/pt-br.json |
Same localization additions (Portuguese - Brazil). |
birdnet_analyzer/lang/ru.json |
Same localization additions (Russian). |
birdnet_analyzer/lang/se.json |
Same localization additions (Northern Sami). |
birdnet_analyzer/lang/tlh.json |
Same localization additions (Klingon). |
birdnet_analyzer/lang/zh_CN.json |
Same localization additions (Simplified Chinese). |
birdnet_analyzer/lang/zh_TW.json |
Same localization additions (Traditional Chinese). |
docs/breaking-changes.rst |
Documents sensitivity being ignored for BirdNET 3.0 and GUI slider behavior. |
pyproject.toml |
Switches birdnet dependency to a direct GitHub tarball reference. |
tests/test_model_utils.py |
Adds unit tests for sensitivity support and inference sensitivity dropping. |
tests/gui/test_sensitivity_slider.py |
Adds GUI test verifying slider disable/restore behavior across model choices. |
tests/gui/test_download_progress.py |
Adds GUI test verifying birdnet download progress callback routing and concurrency behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| import warnings | ||
| from collections.abc import Callable | ||
| from contextlib import suppress | ||
| from contextlib import contextmanager, suppress |
There was a problem hiding this comment.
Agreed on the diagnosis, but this is pre-existing and out of scope here: gui.utils has imported webview at module level since long before this PR, and every test that reaches it stubs sys.modules['webview'] first (documented in AGENTS.md; both new test modules in this PR do so). Making gui.utils importable without pywebview is a real improvement (open_window, _WINDOW typing, the folder/file dialogs) but a separate refactor — filing it as a follow-up rather than widening this PR.
| if update.status == "started" and progress is None: | ||
| gr.Info(label) | ||
| elif update.status == "progress" and progress is not None: | ||
| if update.bytes_total: | ||
| done = _format_bytes(update.bytes_done) | ||
| total = _format_bytes(update.bytes_total) | ||
| progress( | ||
| min(update.bytes_done / update.bytes_total, 1.0), | ||
| desc=f"{label} ({done} / {total})", | ||
| ) | ||
| else: | ||
| progress(0.0, desc=f"{label} ({_format_bytes(update.bytes_done)})") | ||
| elif update.status == "retrying": | ||
| gr.Warning( | ||
| f"{loc.localize('progress-download-retrying')}: {name} - {update.error}" | ||
| ) | ||
| # "failed": the library raises right after; the operation reports it. |
There was a problem hiding this comment.
Fixed in 3629d8e: finished now drives the bar to 1.0 (progress events are throttled, so the last one can sit below 100%). Test asserts the final call is 1.0.
Progress events are throttled, so the last one can sit below 100%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No description provided.