Skip to content

Commit 53df7c8

Browse files
committed
multiple changes
1 parent 82a750c commit 53df7c8

14 files changed

Lines changed: 135 additions & 26 deletions

File tree

.github/workflows/ci.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,53 @@ on:
1111
workflow_dispatch:
1212

1313
jobs:
14+
build-and-dl-models:
15+
runs-on: ${{ matrix.os }}
16+
17+
strategy:
18+
matrix:
19+
os:
20+
[
21+
ubuntu-slim,
22+
ubuntu-24.04,
23+
ubuntu-24.04-arm,
24+
macos-15,
25+
macos-15-intel,
26+
windows-2025,
27+
]
28+
python-version: ["3.12"]
29+
30+
steps:
31+
- uses: actions/checkout@v4
32+
33+
- name: Set up Python ${{ matrix.python-version }}
34+
uses: actions/setup-python@v5
35+
with:
36+
python-version: ${{ matrix.python-version }}
37+
38+
- name: Upgrade pip
39+
run: python -m pip install --upgrade pip
40+
41+
- name: Install dependencies
42+
run: |
43+
python -m pip install uv build --upgrade
44+
python -m uv pip install twine build --upgrade
45+
python -m uv pip install --system .[tests]
46+
47+
# - name: Run linters
48+
# run: |
49+
# ruff check src/birdnet
50+
# ruff check src/birdnet_tests
51+
52+
# - name: Type checking
53+
# run: mypy
54+
55+
- name: Download models
56+
run: |
57+
pytest -m "load_model" -n auto
58+
1459
build-and-test-no-litert:
60+
needs: build-and-dl-models
1561
runs-on: ${{ matrix.os }}
1662

1763
strategy:
@@ -62,6 +108,7 @@ jobs:
62108
python -m twine check dist/*
63109
64110
build-and-test-with-litert:
111+
needs: build-and-dl-models
65112
runs-on: ${{ matrix.os }}
66113

67114
strategy:

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Fixed #17: Issue on macOS with too long ring buffer names
1414
- Fixed #19: `queue.qsize()` is not used anymore
1515
- Fixed issue with hanging session because of logging
16+
- Fixed issue with downloading same model simultaneously
1617

1718
### Changed
1819

1920
- Improved prediction speed, esp. for half-precision models (+10 seg/s)
2021
- Lowered dependencies
22+
- Update `ai-edge-litert` to version 2.0.3 on `repro`
23+
- Better download progress indication of model files
24+
- Model loading in tests is done before running other tests
2125

2226
### Added
2327

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pip install birdnet --user
2323
pip install birdnet[and-cuda] --user
2424

2525
# For edge devices (e.g., Raspberry Pi)
26-
# Note: Does NOT work on Windows and not on Python 3.13 yet
26+
# Note: Does NOT work on Windows, Intel-based Macs and not on Python 3.13 yet
2727
pip install birdnet[litert] --user
2828
```
2929

pyproject.toml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ repro = [
6363
"psutil == 7.1.3",
6464
"pyarrow == 22.0.0",
6565
"tensorflow == 2.20.0",
66-
"ai-edge-litert == 2.0.2; sys_platform == 'linux' or sys_platform == 'darwin'",
66+
"ai-edge-litert == 2.0.3; sys_platform == 'linux' or sys_platform == 'darwin'",
6767
]
6868
tests = [
6969
"uv >= 0.9.7",
@@ -101,6 +101,7 @@ log_level = "DEBUG"
101101
testpaths = ["src/birdnet_tests"]
102102
norecursedirs = ["src/birdnet_v1_tests"]
103103
markers = [
104+
"load_model: tests that download all models, should be run before other tests",
104105
"litert: tests requiring ai_edge_litert backend which can not be loaded after tf is imported (raises ImportError) which happens on parallel test runs",
105106
"gpu: tests requiring a GPU to run and to be runned sequentially",
106107
"repro: tests requiring exact package versions to reproduce results",
@@ -159,24 +160,27 @@ deps =
159160
commands =
160161
# ruff check src/birdnet
161162
# ruff check src/birdnet_tests
162-
pytest -m "not repro and (not litert and not gpu)" -n auto
163-
pytest -m "not repro and litert" -n auto
164-
pytest -m "not repro and gpu" -n 1
163+
pytest -m "load_model" -n auto
164+
pytest -m "not load_model and not repro and (not litert and not gpu)" -n auto
165+
pytest -m "not load_model and not repro and litert" -n auto
166+
pytest -m "not load_model and not repro and gpu" -n 1
165167
166168
[testenv:py312-repro]
167169
deps =
168170
.[tests,repro]
169171
commands =
170-
pytest -m "repro and (not litert and not gpu)" -n auto
171-
pytest -m "repro and litert" -n auto
172-
pytest -m "repro and gpu" -n 1
172+
pytest -m "load_model" -n auto
173+
pytest -m "not load_model repro and (not litert and not gpu)" -n auto
174+
pytest -m "not load_model repro and litert" -n auto
175+
pytest -m "not load_model repro and gpu" -n 1
173176
174177
[testenv:py313]
175178
deps =
176179
.[tests,and-cuda]
177180
commands =
178-
pytest -m "not repro and (not litert and not gpu)" -n auto
179-
pytest -m "not repro and gpu" -n 1
181+
pytest -m "load_model" -n auto
182+
pytest -m "not load_model not repro and (not litert and not gpu)" -n auto
183+
pytest -m "not load_model not repro and gpu" -n 1
180184
"""
181185

182186
[build-system]

src/birdnet/acoustic_models/v2_4/pb.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def _check_acoustic_model_available(cls) -> bool:
4646
return model_is_downloaded
4747

4848
@classmethod
49-
def _download_acoustic_model(cls) -> None:
49+
def _download_model(cls) -> None:
5050
dl_url = "https://zenodo.org/records/15050749/files/BirdNET_v2.4_protobuf.zip"
5151
dl_size = 124522908
5252

@@ -56,10 +56,10 @@ def _download_acoustic_model(cls) -> None:
5656
dl_url,
5757
zip_download_path,
5858
download_size=dl_size,
59-
description="Downloading model",
59+
description="Downloading acoustic model v2.4 (pb)",
6060
)
6161

62-
print("Extracting models...")
62+
print("Extracting...")
6363
extract_dir = Path(temp_dir) / "extracted"
6464

6565
with zipfile.ZipFile(zip_download_path, "r") as zip_ref:
@@ -70,19 +70,21 @@ def _download_acoustic_model(cls) -> None:
7070

7171
acoustic_model_dir, acoustic_lang_dir = cls._get_paths()
7272
acoustic_model_dir.parent.mkdir(parents=True, exist_ok=True)
73+
shutil.rmtree(acoustic_model_dir, ignore_errors=True)
7374
shutil.move(acoustic_model_dl_dir, acoustic_model_dir)
7475

7576
acoustic_lang_dir.parent.mkdir(parents=True, exist_ok=True)
77+
shutil.rmtree(acoustic_lang_dir, ignore_errors=True)
7678
shutil.move(species_dl_dir, acoustic_lang_dir)
77-
print("Models extracted.")
79+
print("Extracted.")
7880

7981
@classmethod
8082
def get_model_path_and_labels(
8183
cls,
8284
lang: str,
8385
) -> tuple[Path, OrderedSet[str]]:
8486
if not cls._check_acoustic_model_available():
85-
cls._download_acoustic_model()
87+
cls._download_model()
8688
assert cls._check_acoustic_model_available()
8789

8890
model_dir, langs_path = cls._get_paths()

src/birdnet/acoustic_models/v2_4/tf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,14 @@ def _check_acoustic_model_available(cls, precision: MODEL_PRECISIONS) -> bool:
8282
return all((lang_dir / f"{lang}.txt").is_file() for lang in cls.AVAILABLE_LANGUAGES)
8383

8484
@classmethod
85-
def _download_acoustic_model(cls, precision: MODEL_PRECISIONS) -> None:
85+
def _download_model(cls, precision: MODEL_PRECISIONS) -> None:
8686
with tempfile.TemporaryDirectory(prefix="birdnet_download") as temp_dir:
8787
zip_download_path = Path(temp_dir) / "download.zip"
8888
download_file_tqdm(
8989
models[precision].dl_url,
9090
zip_download_path,
9191
download_size=models[precision].dl_size,
92-
description="Downloading model",
92+
description=f"Downloading acoustic model v2.4 (tf, {precision})",
9393
)
9494

9595
extract_dir = Path(temp_dir) / "extracted"
@@ -114,7 +114,7 @@ def get_model_path_and_labels(
114114
) -> tuple[Path, OrderedSet[str]]:
115115
assert lang in cls.AVAILABLE_LANGUAGES
116116
if not cls._check_acoustic_model_available(precision):
117-
cls._download_acoustic_model(precision)
117+
cls._download_model(precision)
118118
assert cls._check_acoustic_model_available(precision)
119119

120120
model_path, langs_path = cls._get_paths(precision)

src/birdnet/geo_models/v2_4/pb.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def _check_geo_model_available(cls) -> bool:
4343
return model_is_downloaded
4444

4545
@classmethod
46-
def _download_geo_model(cls) -> None:
46+
def _download_model(cls) -> None:
4747
dl_url = "https://zenodo.org/records/15050749/files/BirdNET_v2.4_protobuf.zip"
4848
dl_size = 124522908
4949

@@ -53,10 +53,10 @@ def _download_geo_model(cls) -> None:
5353
dl_url,
5454
zip_download_path,
5555
download_size=dl_size,
56-
description="Downloading model",
56+
description="Downloading geo model v2.4 (pb)",
5757
)
5858

59-
print("Extracting models...")
59+
print("Extracting...")
6060
extract_dir = Path(temp_dir) / "extracted"
6161

6262
with zipfile.ZipFile(zip_download_path, "r") as zip_ref:
@@ -67,19 +67,21 @@ def _download_geo_model(cls) -> None:
6767

6868
geo_model_dir, geo_lang_dir = cls._get_paths()
6969
geo_model_dir.parent.mkdir(parents=True, exist_ok=True)
70+
shutil.rmtree(geo_model_dir, ignore_errors=True)
7071
shutil.move(geo_model_dl_dir, geo_model_dir)
7172

7273
geo_lang_dir.parent.mkdir(parents=True, exist_ok=True)
74+
shutil.rmtree(geo_lang_dir, ignore_errors=True)
7375
shutil.move(species_dl_dir, geo_lang_dir)
74-
print("Models extracted.")
76+
print("Extracted.")
7577

7678
@classmethod
7779
def get_model_path_and_labels(
7880
cls,
7981
lang: str,
8082
) -> tuple[Path, OrderedSet[str]]:
8183
if not cls._check_geo_model_available():
82-
cls._download_geo_model()
84+
cls._download_model()
8385
assert cls._check_geo_model_available()
8486

8587
model_dir, langs_path = cls._get_paths()

src/birdnet/geo_models/v2_4/tf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ def _check_geo_model_available(cls) -> bool:
5757
return all((lang_dir / f"{lang}.txt").is_file() for lang in cls.AVAILABLE_LANGUAGES)
5858

5959
@classmethod
60-
def _download_geo_model(cls) -> None:
60+
def _download_model(cls) -> None:
6161
with tempfile.TemporaryDirectory(prefix="birdnet_download") as temp_dir:
6262
zip_download_path = Path(temp_dir) / "download.zip"
6363
download_file_tqdm(
6464
cls._model_info.dl_url,
6565
zip_download_path,
6666
download_size=cls._model_info.dl_size,
67-
description="Downloading model",
67+
description="Downloading geo model v2.4 (tf)",
6868
)
6969

7070
extract_dir = Path(temp_dir) / "extracted"
@@ -87,7 +87,7 @@ def _download_geo_model(cls) -> None:
8787
def get_model_path_and_labels(cls, lang: str) -> tuple[Path, OrderedSet[str]]:
8888
assert lang in cls.AVAILABLE_LANGUAGES
8989
if not cls._check_geo_model_available():
90-
cls._download_geo_model()
90+
cls._download_model()
9191
assert cls._check_geo_model_available()
9292

9393
model_path, langs_path = cls._get_paths()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from birdnet.acoustic_models.v2_4.pb import AcousticPBDownloaderV2_4
2+
3+
4+
def xtest_double_download() -> None:
5+
# takes too long to run normally
6+
AcousticPBDownloaderV2_4._download_model()
7+
AcousticPBDownloaderV2_4._download_model()
8+
9+
10+
if __name__ == "__main__":
11+
xtest_double_download()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from birdnet.acoustic_models.v2_4.tf import AcousticTFDownloaderV2_4
2+
3+
4+
def xtest_double_download() -> None:
5+
# takes too long to run normally
6+
AcousticTFDownloaderV2_4._download_model("fp32")
7+
AcousticTFDownloaderV2_4._download_model("fp32")
8+
9+
10+
if __name__ == "__main__":
11+
xtest_double_download()

0 commit comments

Comments
 (0)