From f8e360895a4ac69fda934692ee4a676da81bdb9b Mon Sep 17 00:00:00 2001 From: frederik Date: Sun, 23 Nov 2025 14:06:07 +0100 Subject: [PATCH 1/6] automatically download external models --- scripts/utils/models.py | 52 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/scripts/utils/models.py b/scripts/utils/models.py index 5569ef639..adeea8daa 100644 --- a/scripts/utils/models.py +++ b/scripts/utils/models.py @@ -2,8 +2,10 @@ import math import operator import os +import tarfile import numpy as np +import requests from .helpers import get_settings, get_model_labels, MODEL_PATH @@ -50,6 +52,29 @@ def get_meta_model(model=None, version=None): return MDataModel2(conf.getfloat('SF_THRESH')) +def download_file(url, file_path): + tmp_file = f"{file_path}_tmp" + session = requests.Session() + response = session.get(url, stream=True) + response.raise_for_status() + block_size = 32768 + + log.info('Downloading: %s' % os.path.basename(file_path)) + downloaded_size = 0 + try: + with open(tmp_file, "wb") as outfile: + for data in response.iter_content(block_size): + downloaded_size += len(data) + log.info(f'Progress: {downloaded_size}') + outfile.write(data) + except requests.exceptions.HTTPError as e: + if os.path.exists(tmp_file): + os.unlink(tmp_file) + raise e + + os.rename(tmp_file, file_path) + + class Basemodel: chunk_duration = None sample_rate = None @@ -58,8 +83,9 @@ class Basemodel: _output_layer = 0 def __init__(self): - model_path = os.path.join(MODEL_PATH, f'{self.model_name}.tflite') - self.interpreter = tflite.Interpreter(model_path) + self.model_path = os.path.join(MODEL_PATH, f'{self.model_name}.tflite') + self.ensure_model() + self.interpreter = tflite.Interpreter(self.model_path) self.interpreter.allocate_tensors() input_details = self.interpreter.get_input_details() output_details = self.interpreter.get_output_details() @@ -82,6 +108,9 @@ def set_meta_data(self, lat, lon, week): def get_species_list(self): return [] + def ensure_model(self): + pass + class BirdNet(Basemodel): chunk_duration = 3 @@ -182,10 +211,29 @@ def predict(self, chunk): exp_x = np.exp(logits - np.max(logits)) # Stabilizing to prevent overflow return self.label(exp_x / np.sum(exp_x)) + def ensure_model(self): + if os.path.exists(self.model_path): + return + base_url = 'https://github.com/Nachtzuster/BirdNET-Pi/releases/download/v0.11' + file = 'Perch_v2.tar.gz' + tmp_file = os.path.join(MODEL_PATH, file) + download_file(f'{base_url}/{file}', tmp_file) + log.info(f'Extracting {file}...') + with tarfile.open(tmp_file, "r:gz") as tar: + tar.extractall(MODEL_PATH) + os.unlink(tmp_file) + class BirdNETGo20250916(BirdNetV2_4): model_name = 'BirdNET-Go_classifier_20250916' + def ensure_model(self): + if os.path.exists(self.model_path): + return + base_url = 'https://raw.githubusercontent.com/tphakala/birdnet-go-classifiers/refs/heads/main/20250916' + for file in ['BirdNET-Go_classifier_20250916_Labels.txt', 'BirdNET-Go_classifier_20250916.tflite']: + download_file(f'{base_url}/{file}', os.path.join(MODEL_PATH, file)) + class MDataModel: model_name = None From 477253a43dcbfc747a565cb5f522d540ee6b412c Mon Sep 17 00:00:00 2001 From: frederik Date: Sun, 23 Nov 2025 15:19:32 +0100 Subject: [PATCH 2/6] adjust samplerate to model --- scripts/birdnet_recording.sh | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/scripts/birdnet_recording.sh b/scripts/birdnet_recording.sh index c714a16a6..839c38c50 100755 --- a/scripts/birdnet_recording.sh +++ b/scripts/birdnet_recording.sh @@ -4,24 +4,32 @@ source /etc/birdnet/birdnet.conf loop_ffmpeg(){ while true;do - if ! ffmpeg -hide_banner -xerror -loglevel $LOGGING_LEVEL -nostdin ${1} -i ${2} -vn -map a:0 -acodec pcm_s16le -ac 2 -ar 48000 -f segment -segment_format wav -segment_time ${RECORDING_LENGTH} -strftime 1 ${RECS_DIR}/StreamData/%F-birdnet-RTSP_${3}-%H:%M:%S.wav + if ! ffmpeg -hide_banner -xerror -loglevel $LOGGING_LEVEL -nostdin ${1} -i ${2} -vn -map a:0 -acodec pcm_s16le -ac 1 -ar ${SAMPLERATE} -f segment -segment_format wav -segment_time ${RECORDING_LENGTH} -strftime 1 ${RECS_DIR}/StreamData/%F-birdnet-RTSP_${3}-%H:%M:%S.wav then sleep 1 fi done } -# Read the logging level from the configuration option -LOGGING_LEVEL="${LogLevel_BirdnetRecordingService}" -# If empty for some reason default to log level of error -[ -z $LOGGING_LEVEL ] && LOGGING_LEVEL='error' +# Set logging level, default to 'error' if not set +LOGGING_LEVEL="${LogLevel_BirdnetRecordingService:-error}" + # Additionally if we're at debug or info level then allow printing of script commands and variables if [ "$LOGGING_LEVEL" == "info" ] || [ "$LOGGING_LEVEL" == "debug" ];then # Enable printing of commands/variables etc to terminal for debugging set -x fi -[ -z $RECORDING_LENGTH ] && RECORDING_LENGTH=15 +REC_CARD="${REC_CARD:-default}" +RECORDING_LENGTH="${RECORDING_LENGTH:-15}" + +# Set sample rate based on the model +if [[ "$MODEL" == "Perch_v2" ]]; then + SAMPLERATE=32000 +else + SAMPLERATE=48000 +fi + [ -d $RECS_DIR/StreamData ] || mkdir -p $RECS_DIR/StreamData if [ -n "${RTSP_STREAM}" ];then @@ -50,12 +58,7 @@ else if pgrep arecord &> /dev/null ;then echo "Recording" else - if [ -z ${REC_CARD} ];then - arecord -f S16_LE -c${CHANNELS} -r48000 -t wav --max-file-time ${RECORDING_LENGTH}\ - --use-strftime ${RECS_DIR}/StreamData/%F-birdnet-%H:%M:%S.wav - else - arecord -f S16_LE -c${CHANNELS} -r48000 -t wav --max-file-time ${RECORDING_LENGTH}\ - -D "${REC_CARD}" --use-strftime ${RECS_DIR}/StreamData/%F-birdnet-%H:%M:%S.wav - fi + arecord -f S16_LE -c${CHANNELS} -r ${SAMPLERATE} -t wav --max-file-time ${RECORDING_LENGTH}\ + -D "${REC_CARD}" --use-strftime ${RECS_DIR}/StreamData/%F-birdnet-%H:%M:%S.wav fi fi From 4692d1a9796e1c62b32b94c77f511314f4cd7c47 Mon Sep 17 00:00:00 2001 From: frederik Date: Sun, 23 Nov 2025 16:33:17 +0100 Subject: [PATCH 3/6] add new models to ui --- scripts/advanced.php | 4 ++-- scripts/config.php | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/scripts/advanced.php b/scripts/advanced.php index 2a65d1b61..9b4096cd5 100644 --- a/scripts/advanced.php +++ b/scripts/advanced.php @@ -329,7 +329,7 @@ If different than 0 (keep all), defines the number of files to keep for each species, with priority given to files with higher confidence. This value does not include files from the last 7 days, these new files are protected against auto-deletion. - Note only the spectrogram and audio files are deleted, the obsevation data remains in the database. + Note only the spectrogram and audio files are deleted, the observation data remains in the database. The files protected through the "lock" icon are also not affected.
@@ -345,7 +345,7 @@ Set Channels to the number of channels supported by your sound card. 32 max.


- Set Recording Length in seconds between 6 and 60. Multiples of 3 are recommended, as BirdNET analyzes in 3-second chunks.

+ Set Recording Length in seconds between 6 and 60. Multiples of 3 are recommended for BirdNET models, multiples of 5 for Perch.


Set Extraction Length to something less than your Recording Length. Min=3 Max=Recording Length

diff --git a/scripts/config.php b/scripts/config.php index ae12ebc00..8081961a8 100644 --- a/scripts/config.php +++ b/scripts/config.php @@ -235,7 +235,7 @@ function() {