diff --git a/.gitignore b/.gitignore index 4f334a3..ce80226 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ var/ /.venv/ -/analyzer/ \ No newline at end of file +/analyzer/ + +.env + +species_list.txt \ No newline at end of file diff --git a/daemon/miner.sh b/.ideas/birdnet_obs_miner.sh similarity index 100% rename from daemon/miner.sh rename to .ideas/birdnet_obs_miner.sh diff --git a/INSTALL.md b/INSTALL.md index 7a793ff..da017d6 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,5 +1,13 @@ # Installation Guide for BirdNET-stream +This guide allow you to install BirdNET-stream step by step on your debian based system. + +For a one-liner installation, you can use the following command: + +```bash +curl -sL https://raw.githubusercontent.com/birdnet-stream/birdnet-stream/master/install.sh | bash +``` + ## Requirements - git @@ -25,7 +33,8 @@ sudo apt-get install ffmpeg ### Clone BirdNET-stream repository ```bash -git clone https://forge.chapril.org/UncleSamulus/BirdNET-stream.git +git clone --recurse-submodules https://forge.chapril.org/UncleSamulus/BirdNET-stream.git +``` ### Setup python virtualenv and packages diff --git a/README.md b/README.md index ecca5fd..4534669 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,22 @@ BirdNET-stream logo image IA generated

+## Introduction + +BirdNET-stream record sound on Linux computer and analyze it with the help of the BirdNET algorithm. It extracts most interesting bird songs and calls accessible in a webapp. + +## Install + +On debian based system, you can install BirdNET-stream with the following command: + +```bash +curl -sL https://raw.githubusercontent.com/birdnet-stream/birdnet-stream/master/install.sh | bash +``` + +For finer control, or to adapt to your system, you can follow the instructions in the [INSTALL.md](./INSTALL.md) file. + +## Usage + ## Acknoledgements - [BirdNET](https://birdnet.cornell.edu) on which this project relies diff --git a/daemon/analyze-stream.sh b/daemon/birdnet_analyzis.sh similarity index 78% rename from daemon/analyze-stream.sh rename to daemon/birdnet_analyzis.sh index 8230e9d..e4ceee0 100755 --- a/daemon/analyze-stream.sh +++ b/daemon/birdnet_analyzis.sh @@ -39,35 +39,32 @@ check_prerequisites() { mkdir -p "${CHUNK_FOLDER}/out" fi fi - fi fi fi - if [[ -z ${SPECIES_LIST} ]]; - then + if [[ -z ${SPECIES_LIST} ]]; then echo "SPECIES_LIST is not set" exit 1 fi - if [[ -f $PYTHON_EXECUTABLE ]]; - then + if [[ -f ${PYTHON_EXECUTABLE} ]]; then if $verbose; then - echo "Python executable found: $PYTHON_EXECUTABLE" + echo "Python executable found: ${PYTHON_EXECUTABLE}" fi else - echo "Python executable not found: $PYTHON_EXECUTABLE" + echo "Python executable not found: ${PYTHON_EXECUTABLE}" exit 1 fi } # Get array of audio chunks to be processed get_chunk_list() { - find "$CHUNK_FOLDER/in" -type f -name '*.wav' -exec basename {} \; ! -size 0 | sort + find "${CHUNK_FOLDER}/in" -type f -name '*.wav' -exec basename {} \; ! -size 0 | sort } # Perform audio chunk analysis on one chunk analyze_chunk() { chunk_name=$1 - chunk_path="$CHUNK_FOLDER/in/$chunk_name" - output_dir="$CHUNK_FOLDER/out/$chunk_name.d" + chunk_path="${CHUNK_FOLDER}/in/$chunk_name" + output_dir="${CHUNK_FOLDER}/out/$chunk_name.d" mkdir -p "$output_dir" date=$(echo $chunk_name | cut -d'_' -f2) week=$(./daemon/weekof.sh $date) @@ -78,8 +75,8 @@ analyze_chunk() { analyze_chunks() { for chunk_name in $(get_chunk_list); do analyze_chunk $chunk_name - chunk_path="$CHUNK_FOLDER/in/$chunk_name" - mv $chunk_path "$CHUNK_FOLDER/out/$chunk_name" + chunk_path="${CHUNK_FOLDER}/in/$chunk_name" + mv $chunk_path "${CHUNK_FOLDER}/out/$chunk_name" done } @@ -87,4 +84,4 @@ analyze_chunks() { chunks=$(get_chunk_list) # Analyze all chunks in working directory -analyze_chunks $chunks \ No newline at end of file +analyze_chunks $chunks diff --git a/daemon/birdnet_clean.sh b/daemon/birdnet_clean.sh new file mode 100755 index 0000000..2b67a07 --- /dev/null +++ b/daemon/birdnet_clean.sh @@ -0,0 +1,76 @@ +#! /usr/bin/env bash +## +## Clean up var folder from useless files +## + +config_filepath="./config/analyzer.conf" + +if [ -f "$config_filepath" ]; then + source "$config_filepath" +else + echo "Config file not found: $config_filepath" + exit 1 +fi + +wav2dir_name() { + wav_path=$1 + dir_name=$(basename "$wav_path" .wav) + echo "$dir_name" +} + +# Clean out folder from empty audio +clean() { + rm -rf $(junk) +} + +# Check if string contains string +mem() { + string=$2 + substring=$1 + if [[ "$string" == *"$substring"* ]]; then + echo "true" + else + echo "false" + fi +} + +# Get list of junk files +junk() { + # Get all empty files from treatement folder + find "${CHUNK_FOLDER}/out" -type f -name '*.wav' -size 0 + for file in $junk; do + folder=$(wav2dir_name "$file") + if [[ -d $folder ]]; then + junk="$junk $folder" + fi + done + # Get all empty files from record folder + junk=$(find "${CHUNK_FOLDER}/in" -type f -name '*.wav' -exec basename {} \; ! -size 0) + # Get all empty treatment directories + junk="$junk $(find ${CHUNK_FOLDER}/out -type d -empty)" + # Get all empty record directories + treatement_folder=$(find "${CHUNK_FOLDER}/out" -type d ! -empty) + for folder in $treatement_folder; do + if ! $(mem $folder $junk) && $(no_bird_in_model_output $folder); then + junk="$junk $folder" + fi + done + echo "$junk" +} + +no_bird_in_model_output() { + folder=$1 + output="${folder}/model.out.csv" + lines=$(wc -l < "$output") + if [[ $lines -eq 1 ]]; then + echo "true" + else + echo "false" + fi +} + +main() { + clean +} + +main \ No newline at end of file diff --git a/daemon/record-chunks.sh b/daemon/birdnet_recording.sh similarity index 70% rename from daemon/record-chunks.sh rename to daemon/birdnet_recording.sh index bfd6e63..25b3511 100755 --- a/daemon/record-chunks.sh +++ b/daemon/birdnet_recording.sh @@ -1,7 +1,6 @@ #! /usr/bin/env bash -record_chunk() -{ +record_chunk() { DEVICE=$1 DURATION=$2 ffmpeg -f pulse -i ${DEVICE} -t ${DURATION} -vn -acodec pcm_s16le -ac 1 -ar 48000 file:${CHUNK_FOLDER}/in/birdnet_$(date "+%Y%m%d_%H%M%S").wav @@ -23,6 +22,16 @@ if [[ -z $AUDIO_DEVICE ]]; then exit 1 fi +check_folder() { + if [[ ! -d "${CHUNK_FOLDER}" ]]; then + echo "CHUNK_FOLDER does not exist: ${CHUNK_FOLDER}" + echo "Creating recording dir" + mkdir -p "${CHUNK_FOLDER}/in" + fi +} + +check_folder + while true; do record_chunk $AUDIO_DEVICE $RECORDING_DURATION -done \ No newline at end of file +done diff --git a/daemon/dist/birdnet-stream.service b/daemon/dist/birdnet-stream.service deleted file mode 100644 index e69de29..0000000 diff --git a/daemon/systemd/birdnet_analysis.service b/daemon/systemd/birdnet_analysis.service new file mode 100644 index 0000000..e9416f6 --- /dev/null +++ b/daemon/systemd/birdnet_analysis.service @@ -0,0 +1,15 @@ +# Launch BirdNET-Analyzer on the previously recorded audio chunks + +[Unit] +Description=BirdNET-stream Analyzis + +[Service] +User=$USER +Group=$USER +ExecStart=/home/$USER/BirdNET-Analyzer/deamon/birdnet_analysis.sh +Restart=always +RestartSec=3 +Type=simple + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/daemon/systemd/birdnet_recording.service b/daemon/systemd/birdnet_recording.service new file mode 100644 index 0000000..1244017 --- /dev/null +++ b/daemon/systemd/birdnet_recording.service @@ -0,0 +1,15 @@ +# BirdNET recording script launcher service + +[Unit] +Description=BirdNET-stream recording + +[Service] +User=1000 +Group=1000 +ExecStart="bash /home/$USER/BirdNET-stream/daemon/birdnet_recording.sh" +Restart=always +RestartSec=3 +Type=simple + +[Install] +WantedBy=multi-user.target diff --git a/daemon/weekof.sh b/daemon/weekof.sh index e3fb93f..2ca9c63 100755 --- a/daemon/weekof.sh +++ b/daemon/weekof.sh @@ -1,5 +1,6 @@ #! /usr/bin/env bash +# Take a week with format "YYYY-MM-DD" and return the week number. function weekof() { local date=$1 diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..3d61c1a --- /dev/null +++ b/install.sh @@ -0,0 +1,72 @@ +#! /usr/bin/env bash +set -X +set -e + +DEBUG=${DEBUG:-0} + +# Standard Installation Script for BirdNET-stream for Debian Based Linux distros + +REQUIREMENTS="git ffmpeg python3-pip python3-dev" +REPOSITORY="https://github.com/UncleSamulus/BirdNET-stream.git" +PYTHON_VENV=".venv/birdnet-stream" + +# Update system +update() { + sudo apt-get update + sudo apt-get upgrade +} + +debug() { + if [ $DEBUG -eq 1 ]; then + echo "$1" + fi +} + +install_requirements() { + if + requirements=$1 + # Install requirements + missing_requirements="" + for requirement in $requirements; do + if ! dpkg -s $requirement >/dev/null 2>&1; then + missing_requirements="$missing_requirements $requirement" + fi + done + if [ -n "$missing_requirements" ]; then + debug "Installing missing requirements: $missing_requirements" + sudo apt-get install $missing_requirements + fi +} + +# Install BirdNET-stream +install_birdnetstream() { + # Clone BirdNET-stream + debug "Cloning BirdNET-stream from $REPOSITORY" + git clone --recurse-submodules $REPOSITORY + # Install BirdNET-stream + cd BirdNET-stream + debug "Creating python3 virtual environment '$PYTHON_VENV'" + python3 -m venv $PYTHON_VENV + debug "Activating $PYTHON_VENV" + source .venv/birdnet-stream/bin/activate + debug "Installing python packages" + pip install -U pip + pip install -r requirements.txt +} + +# Install systemd services +install_birdnetstream_services() { + debug "Setting up BirdNET stream systemd services" + sudo ln -s $PWD/BirdNET-stream/daemon/systemd/birdnet_recording.service /etc/systemd/system/birdnet_recording.service + sudo ln -s $PWD/BirdNET-stream/daemon/systemd/birdnet_analyzis.service /etc/systemd/system/birdnet_analyzis.service + sudo systemctl daemon-reload + sudo systemctl enable --now birdnet_recording.service birdnet_analyzis.service +} + +main() { + install_requirements $REQUIREMENTS + install_birdnetstream + install_birdnetstream_services +} + +main \ No newline at end of file diff --git a/utils/purge.sh b/utils/purge.sh deleted file mode 100755 index dcab0ee..0000000 --- a/utils/purge.sh +++ /dev/null @@ -1,24 +0,0 @@ -#! /usr/bin/env bash - -# Load config file -config_filepath="./config/analyzer.conf" - -if [ -f "$config_filepath" ]; then - source "$config_filepath" -else - echo "Config file not found: $config_filepath" - exit 1 -fi - -# Remove all files from the temporary record directory -function clean_record_dir() -{ - rm -rf "$CHUNK_FOLDER/in/*.wav" -} - -function remove_empty_records() -{ - find $CHUNK_FOLDER/in -maxdepth 1 -name '*wav' -type f -size 0 -delete -} - -remove_empty_records \ No newline at end of file