updating references to old repo to new repo

This commit is contained in:
Patrick McGuire
2021-09-28 14:48:25 -04:00
parent 7d31737f03
commit bc2d3df898
42 changed files with 3078 additions and 217 deletions
+11 -129
View File
@@ -1,129 +1,11 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
*.pyc
*.flac
model/BirdNET_Soundscape_Model.pkl
.vscode
datasets/
birdnet.conf
miniforge
scripts/install_miniforge.sh
IdentifiedSoFar.txt
IdentifiedSoFar.txt.bak
Birders_Guide_Installer_Configuration.txt
+423
View File
@@ -0,0 +1,423 @@
#!/usr/bin/env bash
set -e
my_dir=${HOME}/BirdNET-Lite
trap '${my_dir}/scripts/dump_logs.sh && exit' EXIT SIGHUP SIGINT
if [ "$(uname -m)" != "aarch64" ];then
echo "BirdNET-Lite requires a 64-bit OS.
It looks like your operating system is using $(uname -m),
but would need to be aarch64.
Please take a look at https://birdnetwiki.pmcgui.xyz for more
information"
exit 1
fi
install_zram_swap() {
echo
echo "Configuring zram.service"
sudo touch /etc/modules-load.d/zram.conf
echo 'zram' | sudo tee /etc/modules-load.d/zram.conf
sudo touch /etc/modprobe.d/zram.conf
echo 'options zram num_devices=1' | sudo tee /etc/modprobe.d/zram.conf
sudo touch /etc/udev/rules.d/99-zram.rules
echo 'KERNEL=="zram0", ATTR{disksize}="4G",TAG+="systemd"' \
| sudo tee /etc/udev/rules.d/99-zram.rules
sudo touch /etc/systemd/system/zram.service
echo "Installing zram.service"
cat << EOF | sudo tee /etc/systemd/system/zram.service &> /dev/null
[Unit]
Description=Swap with zram
After=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=true
ExecStartPre=/sbin/mkswap /dev/zram0
ExecStart=/sbin/swapon /dev/zram0
ExecStop=/sbin/swapoff /dev/zram0
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable zram
echo
echo "Installing stage 2 installation script now."
cd ~
curl -s -O "https://raw.githubusercontent.com/mcguirepr89/BirdNET-Lite/rpi4/Birders_Guide_Installer.sh"
chmod +x Birders_Guide_Installer.sh
cat << EOF | sudo tee /etc/systemd/user/birdnet-system-installer.service &> /dev/null
[Unit]
Description=A BirdNET-Lite Installation Script Service
After=graphical.target network-online.target
[Service]
Type=simple
Restart=on-failure
RestartSec=3s
ExecStart=lxterminal -e /home/pi/Birders_Guide_Installer.sh
[Install]
WantedBy=default.target
EOF
systemctl --user enable birdnet-system-installer.service
echo
echo "Stage 1 complete"
touch ${HOME}/stage_1_complete
echo
echo "Rebooting the system in 5 seconds"
sleep 5
sudo reboot
}
stage_1() {
echo
echo "Beginning Stage 1"
echo
echo "Ensuring the system is up-to-date."
sudo apt -qq update
sudo apt -qqy full-upgrade
sudo apt -y autoremove --purge
echo "System Updated!"
if ! which git &> /dev/null; then
echo "Installing git"
sudo apt install -qqy git
fi
ZRAM="$(swapon --show=SIZE,NAME | awk -FG '!/SIZE/ && /zram/ {print $1}')"
[ ! -z ${ZRAM} ] || ZRAM=0
if [ ${ZRAM} -lt 4 ];then
install_zram_swap
else
echo "Stage 1 complete"
stage_2
exit
fi
}
stage_2() {
echo
echo "Beginning stage 2"
echo
echo "Checking for an internet connection to continue . . ."
until ping -c 1 google.com &> /dev/null; do
sleep 1
done
echo "Connected!"
echo
if [ ! -d ${my_dir} ];then
cd ~ || exit 1
echo "Cloning the BirdNET-Lite repository in your home directory"
git clone https://github.com/mcguirepr89/BirdNET-Lite.git !/BirdNET-Lite
fi
if [ -f ${my_dir}/Birders_Guide_Installer_Configuration.txt ];then
echo
echo
echo "Follow the instructions to fill out the LATITUDE and LONGITUDE variables
and set the passwords for the live audio stream. Save the file after editing
and then close the Mouse Pad editing window to continue."
mousepad ${my_dir}/Birders_Guide_Installer_Configuration.txt &> /dev/null
while pgrep mouse &> /dev/null;do
sleep 1
done
source ${my_dir}/Birders_Guide_Installer_Configuration.txt || exit 1
else
echo "Something went wrong. I can't find the configuration file."
exit 1
fi
if [ -z ${LATITUDE} ] || [ -z ${LONGITUDE} ] || [ -z ${CADDY_PWD} ] || [ -z ${ICE_PWD} ];then
echo
echo
echo "It looks like you haven't filled out the Birders_Guide_Installer_Configuration.txt file
completely.
Open that file to edit it. (Go to the folder icon in the top left and look for the \"BirdNET-Lite\"
folder and double-click the file called \"Birders_Guide_Installer_Configuration.txt\"
Enter the latitude and longitude of where the BirdNET-Lite will be.
You can find this information at https://maps.google.com
Find your location on the map and right click to find your coordinates.
After you have filled out the configuration file, you can re-run this script. Just do the exact
same things you did to start this (copying and pasting from the Wiki) to try again.
Press Enter to close this window.
Good luck!"
read
exit 1
fi
echo "Installing the BirdNET-Lite configuration file."
[ -f ${my_dir}/soundcard_params.txt ] || touch ${my_dir}/soundcard_params.txt
SOUND_PARAMS="${HOME}/BirdNET-Lite/soundcard_params.txt"
SOUND_CARD="$(sudo -u pi aplay -L \
| grep -e '^hw' \
| cut -d, -f1 \
| grep -ve 'vc4' -e 'Head' -e 'PCH' \
| uniq)"
script -c "arecord -D ${SOUND_CARD} --dump-hw-params" -a ${SOUND_PARAMS} &> /dev/null
install_birdnet_config || exit 1
echo "Installing the BirdNET-Lite"
if ${my_dir}/scripts/install_birdnet.sh << EOF ; then
n
EOF
echo "The next time you power on the raspberry pi, all of the services will start up automatically.
The installation has finished. Press Enter to close this window."
read
else
echo "Something went wrong during installation. Open a github issue or email mcguirepr89@gmail.com"
fi
}
install_birdnet_config() {
cat << EOF > ${my_dir}/birdnet.conf
################################################################################
# Configuration settings for BirdNET as a service #
################################################################################
#___________The four variables below are the only that are required.___________#
## BIRDNET_USER should be the non-root user systemd should use to execute each
## service.
BIRDNET_USER=pi
## RECS_DIR is the location birdnet_analysis.service will look for the data-set
## it needs to analyze. Be sure this directory is readable and writable for
## the BIRDNET_USER. If you are going to be accessing a remote data-set, you
## still need to set this, as this will be where the remote directory gets
## mounted locally. See REMOTE_RECS_DIR below for mounting remote data-sets.
RECS_DIR=${HOME}/BirdSongs
## LATITUDE and LONGITUDE are self-explanatroy. Find them easily at
## maps.google.com. Only go to the thousanths place for these variables
## Example: these coordinates would indicate the Eiffel Tower in Paris, France.
## LATITUDE=48.858
## LONGITUDE=2.294
LATITUDE="${LATITUDE}"
LONGITUDE="${LONGITUDE}"
################################################################################
#------------------------------ Extraction Service ---------------------------#
# Keep this EMPTY if you do not want this device to perform the extractions #
## DO_EXTRACTIONS is simply a setting for enabling the extraction.service.
## Set this to Y or y to enable extractions.
DO_EXTRACTIONS=y
################################################################################
#----------------------------- Recording Service ----------------------------#
# Keep this EMPTY if you do not want this device to perform the recording. #
## DO_RECORDING is simply a setting for enabling the 24/7 birdnet_recording.service.
## Set this to Y or y to enable recording.
DO_RECORDING=y
################################################################################
#----------------- Mounting a remote directory with systemd -----------------#
#_______________The four variables below can be set to enable a_______________#
#___________________systemd.mount for analysis, extraction,____________________#
#______________________________or file-serving_________________________________#
# Leave these settings EMPTY if your data-set is local. #
## REMOTE is simply a setting for enabling the systemd.mount to use a remote
## filesystem for the data storage and service.
## Set this to Y or y to enable the systemd.mount.
REMOTE=
## REMOTE_HOST is the IP address, hostname, or domain name SSH should use to
## connect for FUSE to mount its remote directories locally.
REMOTE_HOST=
## REMOTE_USER is the user SSH will use to connect to the REMOTE_HOST.
REMOTE_USER=
## REMOTE_RECS_DIR is the directory on the REMOTE_HOST which contains the
## data-set SSHFS should mount to this system for local access. This is NOT the
## directory where you will access the data on this machine. See RECS_DIR for
## that.
REMOTE_RECS_DIR=
################################################################################
#----------------------- Web-hosting/Caddy File-server -----------------------#
#__________The two variables below can be set to enable web access_____________#
#____________to your data,(e.g., extractions, raw data, live___________________#
#______________audio stream, BirdNET.selection.txt files)______________________#
# Leave these EMPTY if you do not want to enable web access #
## EXTRACTIONS_URL is the URL where the extractions, data-set, and live-stream
## will be web-hosted. If you do not own a domain, or would just prefer to keep
## BirdNET-Lite on your local network, you can set this to http://localhost.
## Setting this (even to http://localhost) will also allow you to enable the
## GoTTY web logging features below.
EXTRACTIONS_URL=http://raspberrypi.local
## CADDY_PWD is the plaintext password (that will be hashed) and used to access
## the "Processed" directory and live audio stream. This MUST be set if you
## choose to enable this feature.
CADDY_PWD=${CADDY_PWD}
################################################################################
#------------------------- Live Audio Stream --------------------------------#
#_____________The variable below configures/enables the live___________________#
#_____________________________audio stream.____________________________________#
# Keep this EMPTY if you do not wish to enable the live stream #
# or if this device is not doing the recording #
## ICE_PWD is the password that icecast2 will use to authenticate ffmpeg as a
## trusted source for the stream. You will never need to enter this manually
## anywhere other than here.
ICE_PWD=${ICE_PWD}
################################################################################
#------------------- Mobile Notifications via Pushed.co ---------------------#
#____________The two variables below enable mobile notifications_______________#
#_____________See https://pushed.co/quick-start-guide to get___________________#
#_________________________these values for your app.___________________________#
# Keep these EMPTY if haven't setup a Pushed.co App yet. #
## Pushed.co App Key and App Secret
PUSHED_APP_KEY=${PUSHED_APP_KEY}
PUSHED_APP_SECRET=${PUSHED_APP_SECRET}
################################################################################
#------------------------------- NoMachine ----------------------------------#
#_____________The variable below can be set include NoMachine__________________#
#_________________remote desktop software to be installed._____________________#
# Keep this EMPTY if you do not want to install NoMachine. #
## INSTALL_NOMACHINE is simply a setting that can be enabled to install
## NoMachine alongside the BirdNET-Lite for remote desktop access. This in-
## staller assumes personal use. Please reference the LICENSE file included
## in this repository for more information.
## Set this to Y or y to install NoMachine alongside the BirdNET-Lite
INSTALL_NOMACHINE=y
################################################################################
#-------------------------------- Defaults ----------------------------------#
#______The six variables below are default settings that you (probably)________#
#__________________don't need to change at all, but can._______________________#
## REC_CARD is the sound card you would want the birdnet_recording.service to
## use. This setting is irrelevant if you are not planning on doing data
## collection via recording on this machine. The command substitution below
## looks for a USB microphone's dsnoop alsa device. The dsnoop device lets
## birdnet_recording.service and livestream.service share the raw audio stream
## from the microphone. If you would like to use a different microphone than
## what this produces, or if your microphone does not support creating a
## dsnoop device, you can set this explicitly from a list of the available
## devices from the output of running 'aplay -L'
REC_CARD="\$(sudo -u pi aplay -L \
| grep dsnoop \
| cut -d, -f1 \
| grep -ve 'vc4' -e 'Head' -e 'PCH' \
| uniq)"
## PROCESSED is the directory where the formerly 'Analyzed' files are moved
## after extractions have been made from them. This includes both WAVE and
## BirdNET.selection.txt files.
PROCESSED=${RECS_DIR}/Processed
## EXTRACTED is the directory where the extracted audio selections are moved.
EXTRACTED=${RECS_DIR}/Extracted
## IDFILE is the file that keeps a complete list of every spececies that
## BirdNET has identified from your data-set. It is persistent across
## data-sets, so would need to be whiped clean through deleting or renaming
## it. A backup is automatically made from this variable each time it is
## updated (structure: ${IDFILE}.bak), and would also need to be removed
## or renamed to start a new file between data-sets. Alternately, you can
## change this variable between data-sets to preserve records of disparate
## data-sets according to name.
IDFILE=${HOME}/BirdNET-Lite/IdentifiedSoFar.txt
## OVERLAP is the value in seconds which BirdNET should use when analyzing
## the data. The values must be between 0.0-2.9.
OVERLAP="0.0"
## CONFIDENCE is the minimum confidence level from 0.0-1.0 BirdNET's analysis
## should reach before creating an entry in the BirdNET.selection.txt file.
## Don't set this to 1.0 or you won't have any results.
CONFIDENCE="0.7"
################################################################################
#------------------------------ Auto-Generated ------------------------------#
#_____________________The variables below are auto-generated___________________#
#______________________________during installation_____________________________#
## CHANNELS holds the variabel that corresponds to the number of channels the
## sound card supports.
CHANNELS=$(awk '/CHANN/ { print $2 }' ${SOUND_PARAMS} | sed 's/\r$//')
# Don't the three below
## ANALYZED is where the extraction.service looks for audio and
## BirdNET.selection.txt files after they have been processed by the
## birdnet_analysis.service. This is NOT where the analyzed files are moved --
## analyzed files are always created within the same directory
## birdnet_analysis.service finds them.
ANALYZED=${RECS_DIR}/*/*Analyzed
## SYSTEMD_MOUNT is created from the RECS_DIR variable to comply with systemd
## mount naming requirements.
SYSTEMD_MOUNT=$(echo ${RECS_DIR#/} | tr / -).mount
## VENV is the virtual environment where the the BirdNET python build is found,
## i.e, VENV is the virtual environment miniforge built for BirdNET.
VENV=${my_dir}/miniforge/envs/birdnet
EOF
[ -d /etc/birdnet ] || sudo mkdir /etc/birdnet
sudo ln -sf ${my_dir}/birdnet.conf /etc/birdnet/birdnet.conf
}
echo "
Welcome to the Birders Guide Installer script!
The installer runs in two stages:
Stage 1 configures and enables the zRAM kernel module and allocates 4G
to its swapping size if needed. This will trigger a reboot.
Stage 1 also ensures the system is up to date.
Stage 2 guides you through configuring the essentials and installs the full BirdNET-Lite."
if [ ! -f ${HOME}/stage_1_complete ] ;then
stage_1
else
stage_2
if [ -f ${HOME}/Birders_Guide_Installer.sh ];then
rm ${HOME}/Birders_Guide_Installer.sh
fi
rm ${HOME}/stage_1_complete
${HOME}/scripts/dump_logs.sh
systemctl --user disable --now birdnet-system-installer.service
sudo rm -f /etc/systemd/user/birdnet-system-installer.service
fi
+37
View File
@@ -0,0 +1,37 @@
# Birders Guide Installer Configuration File
#####NOTE: the '#' sign needs to precede the lines where they are currently
##### DO NOT DELETE ANY OF THEM!!!
###########################################################################
# Fill this out in order to install BirdNET-Lite
#
# 1. To find this information, you can go to https://maps.google.com
# 1a. Find the location on the map where you will be putting the BirdNET-Lite
# 1b. Right-click the location to view the latitude and longitude and click them to
# copy them to your clip-board.
# 2. Enter the latitude and longitude here. Be certain not to mix them up and
# only go to the thousandth's decimal place. The first number is the latitude,
# and the second is longitude. See the example below.
# 3. Set a password that you will use to access your live audio stream. This is the
# "CADDY_PWD" field. You can set this to anything, but keep it alpha-numeric.
# You will probably also want to write this one down if you ever plan to listen
# to the live stream.
# 4. Set a password that that IceCast2 will use to authenticate the stream source.
# You can set this to anything also, but keep it alpha-numeric. You will never use
# this again, so just set it to your favorite word.
# 5. FILE SAVE!!! Don't forget to save this file after editing.
# 6. Close this window to continue the installation.
# Example: these coordinates would indicate the Eiffel Tower in Paris, France.
#LATITUDE=48.858
#LONGITUDE=2.294
LATITUDE=
LONGITUDE=
CADDY_PWD=
ICE_PWD=
# This section can be left alone. If you setup a Pushed.co
# mobile application, you can enter the App secret and secret
# key in order to enable phone notifications for new species
# detections.
PUSHED_APP_SECRET=
PUSHED_APP_KEY=
+90 -88
View File
@@ -1,110 +1,112 @@
# BirdNET-Lite
TFLite version of BirdNET. Bird sound recognition for more than 6,000 species worldwide.
# BirdNET-Lite for arm64/aarch64 (Raspberry Pi 4)
### Built on https://github.com/kahst/BirdNET -- checkout the Wiki at [BirdNETWiki.pmcgui.xyz](https://birdnetwiki.pmcgui.xyz)
Center for Conservation Bioacoustics, Cornell Lab of Ornithology, Cornell University
This project offers an installation script for BirdNET as a systemd service on arm64 (aarch64) Debian-based operating systems, namely RaspiOS. The installation script offers to walk the user through setting up the '*birdnet.conf*' main configuration file interactively, or can read from an existing '*birdnet.conf*'. A variety of configurations can be attained through this installation script.
Go to https://birdnet.cornell.edu to learn more about the project.
BirdNET-Lite can be configured with the following optional services:
- A 24/7 recording script that can be easily configured to use any available sound card
- An extraction service that extracts the audio selections identified by BirdNET by date and species
- A Caddy instance that serves the extracted files and live audio stream (icecast2) (requires dsnoop capable mic)
- A species list updating and notification script supporting mobile notifications via Pushed.co (sorry, Android users, Pushed.co doesn't seem to work for you)
- NoMachine remote desktop software (for personal use only)
Want to use BirdNET to analyze a large dataset? Don't hesitate to contact us: ccb-birdnet@cornell.edu
An installation one-liner is available [HERE](https://birdnetwiki.pmcgui.xyz/wiki/Birder%27s_Guide_to_BirdNET-Lite#Install_BirdNET-Lite) for RaspiOS-ARM64 meeting the prequisites below. It installs all services listed above.
- Prerequisites:
- An updated RaspiOS for AArch64 that has locale, WiFi, time-zone, and pi user password set. A guide is available [here](https://birdnetwiki.pmcgui.xyz/wiki/Birder%27s_Guide_to_BirdNET-Lite#Install_the_base_operating_system_.28OS.29). 64GB SD card for best performance.
- A USB microphone (dsnoop capable to enable live audio stream).
- Running the installer from within the Raspberry Pi's desktop environment (i.e., not over SSH -- for SSH installations, see installation options 2 & 3)
# Setup (Ubuntu 18.04)
TFLite for x86 platforms comes with the standard Tensorflow package. If you are on a different platform, you need to install a dedicated version of TFLite (e.g., a pre-compiled version for Raspberry Pi). See Raspberry Pi 4B installation instructions below.
## What the installation does
1. Looks for a *'birdnet.conf'* file in the *BirdNET-Lite* main directory
1. If a *'birdnet.conf'* file exists and is filled out properly, the installation is nearly
non-interactive and builds the system based off of the services configured in the *'birdnet.conf'* file
1. If the installer cannot find a *'birdnet.conf'* file, the installation is interactive and will
walk the user through creating the '*birdnet.conf'* file interactively.
1. Installs the following system dependencies:
- ffmpeg
- libblas-dev
- liblapack-dev
- caddy (for web access to extractions)
- icecast2 (live audio stream)
- alsa-utils (for recording)
- sshfs (to mount remote sound file directories)
1. Installs BirdNET-Lite scripts in */usr/local/bin*
1. Installs all selected services based on '*birdnet.conf*'
1. Installs *miniforge* for the aarch64 architecture using the current release from https://github.com/conda-forge/miniforge
1. Builds BirdNET in miniforge's *'birdnet'* virtual environment
1. Enables (but does not start) the services
## x86
We need to setup TF2.3+ for BirdNET. First, we install Python 3 and pip:
## What you should know before any installation
1. The licensing information for the software that is used (see [LICENSE](https://raw.githubusercontent.com/mcguirepr89/BirdNET-Lite/BirdNET-Lite-for-raspi4/LICENSE)).
1. The **latitude** and **longitude** where the bird recordings take place. Google maps is an easy way to find these (right-clicking the location).
1. In order for the live audio stream to work at the same time as the birdnet_recording.service, the microphone needs to be dsnoop capable. If you are wondering whether your mic supports creating the dsnoop device, you can use `aplay -L | awk -F, '/dsn/ {print $1}' | grep -ve 'vc4' -e 'Head' -e 'PCH' | uniq` to check. (No output means your microphone does not support creating a dsnoop device and therefore cannot also provide an audio stream while recording. The birdnet_recording.service, however, should not be affected by this and the installation one-liner can still be used. The live stream link simply will not work.)
```
sudo apt-get update
sudo apt-get install python3-dev python3-pip
sudo pip3 install --upgrade pip
```
## What you should know for a manual installation
1. The **local directory** where the recordings should be found on your local computer. BirdNET-Lite supports setting up a systemd.mount for automounting remote directories. So for instance, if the actual recordings live on RemoteHost's `/home/user/recordings` directory, but you would like them to be found on your device at `/home/me/BirdNET-recordings`, then `/home/me/BirdNET-recordings` will be your answer to that question.
1. If mounting the recordings directory from a remote host, you need to know the **remote hostname, username, and password** to connect to it via SSH, as well as the **absolute path of the recordings on the remote host**.
1. If you are using a special microphone or have multiple sound cards and would like to specify which to use for recording, you can edit the `~/BirdNET-Lite/birdnet.conf` file before the installation and set the **REC_CARD** variable to the sound card of your choice. Copy your desired sound card line from the output of `aplay -L | awk -F, '/^dsn:/ { print $1 }'`(prefered), or `aplay -L | awk -F, '/^hw:/ { print $1 }'`(if prefered is not available).
1. If you would like to take advantage of Caddy's automatic handling of SSL certificates to be able to host a public website where your friends can hear your bird sounds, forward ports 80 and 443 to the host you want to serve the files. You may also want to purchase a domain name.
- *Note: If you're just keeping this on your local network, **be sure to set your extraction URL to something 'http://'** (on RaspiOS, I recommend 'http://raspberrypi.local') to disable Caddy's automatic HTTPS. Alternatively, you may edit the `/etc/caddy/Caddyfile` after installation and add the `tls internal` directive to the site block to have Caddy issue a self-signed certificate for an HTTPS connection.*
1. If you would like to take advantage of BirdNET-Lite's ability to send New Species mobile notifications, you can easily setup a Pushed.co notification app (see the #TODOs at the bottom for more info). After setting up your application, make note of your **App Key** and **App Secret** -- you will need these to enable mobile notifications for new species.
- *Note for Android users: it seems that the Pushed.co Mobile App does not work for Android devices, which is a huge bummer. If anyone knows of an Android alternative, or if anyone might be able to come up with a home-spun notification system, please let me know.*
Then, we can install Tensorflow with:
## How to install
#### Option 1 (Recommended) -- Install All Services
1. In the terminal run: `curl -s https://raw.githubusercontent.com/mcguirepr89/BirdNET-Lite/BirdNET-Lite-for-raspi4/Birders_Guide_Installer.sh | bash`
```
sudo pip3 install tensorflow
```
##### Options 2 & 3 require you setup 4GB of swapping. That step is included in the directions below.
#### Option 2 -- Pre-fill birdnet.conf
1. In the terminal run `git clone https://github.com/mcguirepr89/BirdNET-Lite.git ~/BirdNET-Lite`
1. You can copy the included *'birdnet.conf-defaults'* template to create and configure the BirdNET-Lite
to your needs before running the installer. Issue `cp ~/BirdNET-Lite/birdnet.conf-defaults ~/BirdNET-Lite/birdnet.conf`.
Edit the new *'birdnet.conf'* file to suit your needs and save it.
If you choose this method, the installation will be (nearly) non-interactive.
1. Setup zRAM swapping. Run `~/BirdNET-Lite/scripts/install_zram_service.sh && sudo reboot`
1. After the reboot, run `~/BirdNET-Lite/scripts/install_birdnet.sh`
#### Option 3 -- Interactive Installation
1. In the terminal run `git clone https://github.com/mcguirepr89/BirdNET-Lite.git ~/BirdNET-Lite`
1. Setup zRAM swapping. Run `~/BirdNET-Lite/scripts/install_zram_service.sh && sudo reboot`
1. After the reboot, run `~/BirdNET-Lite/scripts/install_birdnet.sh`
1. Follow the installation prompts to configure the BirdNET-Lite to your needs.
- Note: The installation should be run as a regular user. If run on an OS other than RaspiOS, be sure the regular user is in the sudoers file or the sudo group.
TFLite on x86 platform currently only supports CPUs.
## Access your BirdNET-Lite
If you configured BirdNET-Lite with the Caddy webserver, you can access the extractions locally at
Note: Make sure to set `CUDA_VISIBLE_DEVICES=""` in your environment variables. Or set `os.environ['CUDA_VISIBLE_DEVICES'] = ''` at the top of your Python script.
- http://birdnetsystem.local
In this example, we use Librosa to open audio files. Install Librosa with:
You can also view the log output for the <code>birdnet_analysis.service</code> and <code>extraction.service</code> at
```
sudo pip3 install librosa
sudo apt-get install ffmpeg
```
- http://birdlog.local
- http://extractionlog.local
You can use any other audio lib if you like, or pass raw audio signals to the model.
and the BirdNET-Lite Statistics Report at
- http://birdstats.local
If you don't use Librosa, make sure to install NumPy:
If you opt to also install NoMachine alongside the BirdNET-Lite, you can also access BirdNET-Lite
remotely following the address information that can be found on the NoMachine's server information page.
```
sudo pip3 install numpy
```
## Examples
These are examples of my personal instance of the BirdNET-Lite on a Raspberry Pi 4B.
- https://birdsounds.pmcgui.xyz -- My BirdNET-Lite Extractions page
- https://birdlog.pmcgui.xyz -- My 'birdlog' birdnet_analysis.service log
- https://extraction.pmcgui.xyz -- My 'extractionlog' extraction.service log
- https://birdstats.pmcgui.xyz -- My 'birdstats' BirdNET-Lite Report
Note: BirdNET expects 3-second chunks of raw audio data, sampled at 48 kHz.
## How to reconfigure the system
At any time, you can completely reconfigure the system to select or remove features. To reconfigure the system, simply run the included "reconfigure_birdnet.sh" script (as the regular user) and follow the prompts to create a new birdnet.conf file and install new services: `~/BirdNET-Lite/scripts/reconfigure_birdnet.sh`
## Raspberry Pi 4B running AArch64 OS
These steps install BirdNET-Lite on a Raspberry Pi 4B running an AArch64 OS using pre-built TFLite binaries.
1. Install dependencies<br>`sudo apt install swig libjpeg-dev zlib1g-dev python3-dev unzip wget python3-pip curl git cmake make`
1. Update pip, whell, and setuptools:<br>`sudo pip3 install --upgrade pip wheel setuptools`
1. Fetch pre-built binaries:
1. `curl -sc /tmp/cookie "https://drive.google.com/uc?export=download&id=1dlEbugFDJXs-YDBCUC6WjADVtIttWxZA" > /dev/null`
1. `CODE="$(awk '/_warning_/ {print $NF}' /tmp/cookie)"`
1. `curl -Lb /tmp/cookie "https://drive.google.com/uc?export=download&confirm=${CODE}&id=1dlEbugFDJXs-YDBCUC6WjADVtIttWxZA" -o tflite_runtime-2.6.0-cp37-none-linux_aarch64.whl`
1. `sudo pip3 install --upgrade tflite_runtime-2.6.0-cp37-none-linux_aarch64.whl`
1. `sudo pip3 install librosa`
1. `sudo apt-get install ffmpeg`
1. `git clone https://github.com/kahst/BirdNET-Lite.git`
1. `cd BirdNET-Lite/`<br>and test:
1. `python3 analyze.py --i 'example/XC558716 - Soundscape.mp3' --lat 35.4244 --lon -120.7463 --week 18`
## How to uninstall BirdNET-Lite
To remove BirdNET-Lite, run the included '*uninstall.sh*' script as the regular user.
1. Issue `/usr/local/bin/uninstall.sh && cd ~ && rm -drf BirdNET-Lite`
# Usage
## Troubleshooting
**General** -- At anytime, you can run the included `~/BirdNET-Lite/dump_logs.sh` script to create a compressed tar ball of system logs that may provide a helpful overview of the system services. In addition, you can upload it in a new issue along with a description of what you are experiencing. dump_logs.sh scrubs password information, but does retain LATITUDE and LONGITUDE information. If at all concerned with privacy, you're welcome to send them to me via email at mailto:mcguirepr89@gmail.com.
You can run BirdNET via the command line. You can add a few parameters that affect the output.
**Audio** -- If you have problems with the _bridnet_recording.service_ or _livestream.service_, try setting the REC_CARD setting in the _birdnet.conf_ file to `REC_CARD=default` and the CHANNELS variable to `CHANNELS=2`. This works for two very different microphones I have, so it may work for you. If it does, please let me know, as I may change the code as a result. Also, during installation, a file is created called `~/BirdNET-Lite/soundcard_params.txt` that may provide helpful information for customized settings.
The input parameters include:
```
--i, Path to input file.
--o, Path to output file. Defaults to result.csv.
--lat, Recording location latitude. Set -1 to ignore.
--lon, Recording location longitude. Set -1 to ignore.
--week, Week of the year when the recording was made. Values in [1, 48] (4 weeks per month). Set -1 to ignore.
--overlap, Overlap in seconds between extracted spectrograms. Values in [0.0, 2.9]. Defaults tp 0.0.
--sensitivity, Detection sensitivity; Higher values result in higher sensitivity. Values in [0.5, 1.5]. Defaults to 1.0.
--min_conf, Minimum confidence threshold. Values in [0.01, 0.99]. Defaults to 0.1.
--custom_list, Path to text file containing a list of species. Not used if not provided.
```
Note: A custom species list needs to contain one species label per line. Take a look at the `model/label.txt` for the correct species label. Only labels from this text file are valid. You can find an example of a valid custom list in the 'example' folder.
Here are two example commands to run this BirdNET version:
```
python3 analyze.py --i 'example/XC558716 - Soundscape.mp3' --lat 35.4244 --lon -120.7463 --week 18
python3 analyze.py --i 'example/XC563936 - Soundscape.mp3' --lat 47.6766 --lon -122.294 --week 11 --overlap 1.5 --min_conf 0.25 --sensitivity 1.25 --custom_list 'example/custom_species_list.txt'
```
Note: Please make sure to provide lat, lon, and week. BirdNET will work without these values, but the results might be less reliable.
The results of the anlysis will be stored in a result file in CSV format. All confidence values are raw prediction scores and should be post-processed to eliminate occasional false-positive results.
# Contact us
Please don't hesitate to contact us if you have any issues with the code or if you have any other remarks or questions.
Our e-mail address: ccb-birdnet@cornell.edu
We are always open for a collaboration with you.
# Funding
This project is supported by Jake Holshuh (Cornell class of 69). The Arthur Vining Davis Foundations also kindly support our efforts.
**Installation** -- The installer _should_ always create a compressed set of system logs whether it succeeds or fails. Its location is `~/BirdNET-Lite/logs.tar.gz`. Take a look through there or feel free to create a new issue and upload it along with a description of what you are experiencing.
### TODO & Notes:
1. I ought to add the steps to setup a Pushed.co application for the mobile notifications feature. Here is a link for now https://pushed.co/quick-start-guide
+224
View File
@@ -0,0 +1,224 @@
################################################################################
# Configuration settings for BirdNET as a service #
################################################################################
INSTALL_DATE="$(date "+%D")"
#___________The four variables below are the only that are required.___________#
## BIRDNET_USER should be the non-root user systemd should use to execute each
## service.
BIRDNET_USER=
## RECS_DIR is the location birdnet_analysis.service will look for the data-set
## it needs to analyze. Be sure this directory is readable and writable for
## the BIRDNET_USER. If you are going to be accessing a remote data-set, you
## still need to set this, as this will be where the remote directory gets
## mounted locally. See REMOTE_RECS_DIR below for mounting remote data-sets.
RECS_DIR=
## LATITUDE and LONGITUDE are self-explanatroy. Find them easily at
## maps.google.com. Only go to the thousanths place for these variables
## Example: these coordinates would indicate the Eiffel Tower in Paris, France.
## LATITUDE=48.858
## LONGITUDE=2.294
LATITUDE=
LONGITUDE=
################################################################################
#------------------------------ Extraction Service ---------------------------#
# Keep this EMPTY if you do not want this device to perform the extractions #
## DO_EXTRACTIONS is simply a setting for enabling the extraction.service.
## Set this to Y or y to enable extractions.
DO_EXTRACTIONS=
################################################################################
#----------------------------- Recording Service ----------------------------#
# Keep this EMPTY if you do not want this device to perform the recording. #
## DO_RECORDING is simply a setting for enabling the 24/7 birdnet_recording.service.
## Set this to Y or y to enable recording.
DO_RECORDING=
################################################################################
#----------------- Mounting a remote directory with systemd -----------------#
#_______________The four variables below can be set to enable a_______________#
#___________________systemd.mount for analysis, extraction,____________________#
#______________________________or file-serving_________________________________#
# Leave these settings EMPTY if your data-set is local. #
## REMOTE is simply a setting for enabling the systemd.mount to use a remote
## filesystem for the data storage and service.
## Set this to Y or y to enable the systemd.mount.
REMOTE=
## REMOTE_HOST is the IP address, hostname, or domain name SSH should use to
## connect for FUSE to mount its remote directories locally.
REMOTE_HOST=
## REMOTE_USER is the user SSH will use to connect to the REMOTE_HOST.
REMOTE_USER=
## REMOTE_RECS_DIR is the directory on the REMOTE_HOST which contains the
## data-set SSHFS should mount to this system for local access. This is NOT the
## directory where you will access the data on this machine. See RECS_DIR for
## that.
REMOTE_RECS_DIR=
################################################################################
#----------------------- Web-hosting/Caddy File-server -----------------------#
#__________The two variables below can be set to enable web access_____________#
#____________to your data,(e.g., extractions, raw data, live___________________#
#______________audio stream, BirdNET.selection.txt files)______________________#
# Leave these EMPTY if you do not want to enable web access #
## EXTRACTIONS_URL is the URL where the extractions, data-set, and live-stream
## will be web-hosted. If you do not own a domain, or would just prefer to keep
## BirdNET-Lite on your local network, you can set this to http://localhost.
## Setting this (even to http://localhost) will also allow you to enable the
## GoTTY web logging features below.
EXTRACTIONS_URL=
## CADDY_PWD is the plaintext password (that will be hashed) and used to access
## the "Processed" directory and live audio stream. This MUST be set if you
## choose to enable this feature.
CADDY_PWD=
################################################################################
#------------------------- Live Audio Stream --------------------------------#
#_____________The variable below configures/enables the live___________________#
#_____________________________audio stream.____________________________________#
# Keep this EMPTY if you do not wish to enable the live stream #
# or if this device is not doing the recording #
## ICE_PWD is the password that icecast2 will use to authenticate ffmpeg as a
## trusted source for the stream. You will never need to enter this manually
## anywhere other than here.
ICE_PWD=
################################################################################
#------------------- Mobile Notifications via Pushed.co ---------------------#
#____________The two variables below enable mobile notifications_______________#
#_____________See https://pushed.co/quick-start-guide to get___________________#
#_________________________these values for your app.___________________________#
# Keep these EMPTY if haven't setup a Pushed.co App yet. #
## Pushed.co App Key and App Secret
PUSHED_APP_KEY=
PUSHED_APP_SECRET=
################################################################################
#------------------------------- NoMachine ----------------------------------#
#_____________The variable below can be set include NoMachine__________________#
#_________________remote desktop software to be installed._____________________#
# Keep this EMPTY if you do not want to install NoMachine. #
## INSTALL_NOMACHINE is simply a setting that can be enabled to install
## NoMachine alongside the BirdNET-Lite for remote desktop access. This in-
## staller assumes personal use. Please reference the LICENSE file included
## in this repository for more information.
## Set this to Y or y to install NoMachine alongside the BirdNET-Lite
INSTALL_NOMACHINE=
################################################################################
#-------------------------------- Defaults ----------------------------------#
#______The seven variables below are default settings that you (probably)______#
#__________________don't need to change at all, but can._______________________#
## REC_CARD is the sound card you would want the birdnet_recording.service to
## use. This setting is irrelevant if you are not planning on doing data
## collection via recording on this machine. The command substitution below
## looks for a USB microphone's dsnoop alsa device. The dsnoop device lets
## birdnet_recording.service and livestream.service share the raw audio stream
## from the microphone. If you would like to use a different microphone than
## what this produces, or if your microphone does not support creating a
## dsnoop device, you can set this explicitly from a list of the available
## devices from the output of running 'aplay -L'
REC_CARD="\$(sudo -u pi aplay -L \
| grep dsnoop \
| cut -d, -f1 \
| grep -ve 'vc4' -e 'Head' -e 'PCH' \
| uniq)"
## PROCESSED is the directory where the formerly 'Analyzed' files are moved
## after extractions have been made from them. This includes both WAVE and
## BirdNET.selection.txt files.
PROCESSED=${RECS_DIR}/Processed
## EXTRACTED is the directory where the extracted audio selections are moved.
EXTRACTED=${RECS_DIR}/Extracted
## IDFILE is the file that keeps a complete list of every spececies that
## BirdNET has identified from your data-set. It is persistent across
## data-sets, so would need to be whiped clean through deleting or renaming
## it. A backup is automatically made from this variable each time it is
## updated (structure: ${IDFILE}.bak), and would also need to be removed
## or renamed to start a new file between data-sets. Alternately, you can
## change this variable between data-sets to preserve records of disparate
## data-sets according to name.
IDFILE=${HOME}/BirdNET-Lite/IdentifiedSoFar.txt
## OVERLAP is the value in seconds which BirdNET should use when analyzing
## the data. The values must be between 0.0-2.9.
OVERLAP="0.0"
## CONFIDENCE is the minimum confidence level from 0.0-1.0 BirdNET's analysis
## should reach before creating an entry in the BirdNET.selection.txt file.
## Don't set this to 1.0 or you won't have any results.
CONFIDENCE="0.7"
################################################################################
#------------------------------ Auto-Generated ------------------------------#
#_____________________The variables below are auto-generated___________________#
#______________________________during installation_____________________________#
## CHANNELS holds the variabel that corresponds to the number of channels the
## sound card supports.
CHANNELS=${CHANNELS}
# Don't the three below
## ANALYZED is where the extraction.service looks for audio and
## BirdNET.selection.txt files after they have been processed by the
## birdnet_analysis.service. This is NOT where the analyzed files are moved --
## analyzed files are always created within the same directory
## birdnet_analysis.service finds them.
ANALYZED=${RECS_DIR}/*/*Analyzed
## SYSTEMD_MOUNT is created from the RECS_DIR variable to comply with systemd
## mount naming requirements.
SYSTEMD_MOUNT=$(echo ${RECS_DIR#/} | tr / -).mount
## VENV is the virtual environment where the the BirdNET python build is found,
## i.e, VENV is the virtual environment miniforge built for BirdNET.
VENV=$(dirname ${my_dir})/miniforge/envs/birdnet
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# Runs BirdNET in virtual environment
#set -x
source /etc/birdnet/birdnet.conf
DAYS=(
"2 days ago"
"yesterday"
"today"
)
# Create an array of the day's audio files
# Uses 1st argument:
# - {DIRECTORY}
get_files() {
echo "Starting get_files() for ${1}"
files=($( find ${1} -maxdepth 1 -name '*wav' \
| sort \
| awk -F "/" '{print $NF}' ))
[ -n "${files[1]}" ] && echo "Files loaded"
}
# Move all files that have been analyzed already into newly created "Analyzed"
# directory
# Uses 1st argument:
# - {DIRECTORY}
move_analyzed() {
echo "Starting move_analyzed() for ${1}"
for i in "${files[@]}";do
j="$(echo "${i}" | cut -d'.' -f1).BirdNET.selections.txt"
if [ -f "${1}/${j}" ];then
if [ ! -d "${1}-Analyzed" ];then
mkdir -vvvvvvvp "${1}-Analyzed" && echo "'Analyzed' directory created"
fi
echo "Moving analyzed files to new directory"
mv -vv "${1}/${i}" "${1}-Analyzed/"
mv -vv "${1}/${j}" "${1}-Analyzed/"
fi
done
}
# Run BirdNET analysis on the remaining WAVE files for the day
# Uses 1st and 2nd arguments:
# - {DIRECTORY}
# - {"today", "yesterday", "2 days ago",...}
run_analysis() {
echo "Starting run_analysis() for ${1}"
WEEK=$(date --date="${2}" +"%U")
cd ${HOME}/BirdNET-Lite || exit 1
"${VENV}"/bin/python analyze.py \
--i "${1}" \
--lat "${LATITUDE}" \
--lon "${LONGITUDE}" \
--week "${WEEK}" \
--overlap "${OVERLAP}" \
--min_conf "${CONFIDENCE}"
}
# The three main functions
# Requires 2 arguments:
# - {DIRECTORY}
# - {"today", "yesterday", "2 days ago",...}
run_birdnet() {
echo "Starting run_birdnet() in \"${1}\" for \""${2}"\""
sleep 1
get_files "${1}"
sleep 1
move_analyzed "${1}"
sleep 1
run_analysis "${1}" "${2}"
}
if [ $(find ${RECS_DIR} -maxdepth 1 -name '*wav' | wc -l) -gt 0 ];then
run_birdnet "${RECS_DIR}" "today"
fi
for i in ${!DAYS[@]};do
DIRECTORY="$RECS_DIR/$(date --date="${DAYS[$i]}" "+%B-%Y/%d-%A")"
if [ $(find ${DIRECTORY} -name '*wav' | wc -l) -gt 0 ];then
run_birdnet "${DIRECTORY}" "${DAYS[$i]}"
fi
done
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -x
source /etc/birdnet/birdnet.conf
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 60 \
--use-strftime ${RECS_DIR}/%B-%Y/%d-%A/%F-birdnet-%I:%M%P.wav
else
arecord -f S16_LE -c${CHANNELS} -r48000 -t wav --max-file-time 60 \
-D "${REC_CARD}" --use-strftime \
${RECS_DIR}/%B-%Y/%d-%A/%F-birdnet-%I:%M%P.wav
fi
fi
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# BirdNET Stats Page
trap 'setterm --cursor on' EXIT
source /etc/birdnet/birdnet.conf
setterm --cursor off
while true;do
cat << "EOF"
.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.
( _ ____ __ _ )
) |_)o.__||\ ||_ |__(_ __|_ _ ._ _ |_) _ ._ _ .__|_ (
( |_)||(_|| \||_ | __)\/_> |_(/_| | | | \(/_|_)(_)| |_ )
) / | (
"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"+.+"
EOF
if [ "$(find ${EXTRACTED} -name '*.wav' | wc -l)" -ge 1 ];then
a=$( find "${EXTRACTED}" -name '*.wav' \
| awk -F "/" '{print $NF}' \
| cut -d'-' -f1 \
| sort -n \
| tail -n1 )
else
a=0
fi
echo
SOFAR=$(wc -l ${IDFILE}| cut -d' ' -f1)
echo " -$a detections so far"
echo
echo " -$SOFAR species identified so far"
while read -r line;do
echo " + $line"
done < ${IDFILE}
echo
echo -n "Listening since "${INSTALL_DATE}""
sleep 180
clear
done
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source /etc/birdnet/birdnet.conf
cd "${PROCESSED}" || exit 1
FIND_DATE=*$(date --date="2 days ago" "+%F")*
find . -name "${FIND_DATE}" -exec rm -rfv {} +
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# Disk Usage Indicator for TMUX
df -h / | awk '/\// {print "Disk Size="$2", Used="$3", Available="$4", "$5" of disk used"}'
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# A comprehensive log dumper
# set -x # Uncomment to debug
source /etc/birdnet/birdnet.conf &> /dev/null
LOG_DIR="${HOME}/BirdNET-Lite/logs"
SERVICES=(avahi-alias@.service
birdnet_analysis.service
birdnet_log.service
birdnet_recording.service
birdstats.service
birdterminal.service
caddy.service
extraction_log.service
extraction.service
extraction.timer
icecast2.service
livestream.service
${SYSTEMD_MOUNT})
# Create logs directory
[ -d ${LOG_DIR} ] || mkdir ${LOG_DIR}
# Create services logs
for i in "${SERVICES[@]}";do
if [ -L /etc/systemd/system/multi-user.target.wants/${i} ];then
journalctl -u ${i} -n 100 --no-pager > ${LOG_DIR}/${i}.log
cp -L /etc/systemd/system/multi-user.target.wants/${i} ${LOG_DIR}/${i}
fi
done
# Create password-removed birdnet.conf
sed -e '/PWD=/d' ${HOME}/BirdNET-Lite/birdnet.conf > ${LOG_DIR}/birdnet.conf
# Create password-removed Caddyfile
if [ -f /etc/caddy/Caddyfile ];then
sed -e '/basicauth/,+2d' /etc/caddy/Caddyfile > ${LOG_DIR}/Caddyfile
fi
# Get sound card specs
SOUND_CARD="$(aplay -L \
| awk -F, '/^hw:/ {print $1}' \
| grep -ve 'vc4' -e 'Head' -e 'PCH' \
| uniq)"
echo "SOUND_CARD=${SOUND_CARD}" > ${LOG_DIR}/soundcard
script -c "arecord -D ${SOUND_CARD} --dump-hw-params" -a ${LOG_DIR}/soundcard &> /dev/null
# Get system info
CALLS=("df -h" "free -h" "ifconfig" "find ${RECS_DIR}")
for i in "${CALLS[@]}";do
${i} >> ${LOG_DIR}/sysinfo
echo "
===============================================================================
===============================================================================
" >> ${LOG_DIR}/sysinfo
done
# TAR the logs into a ball
tar --remove-files -cvpzf ${HOME}/BirdNET-Lite/logs.tar.gz ${LOG_DIR} &> /dev/null
# Finished
echo "Your compressed logs are located at ${HOME}/BirdNET-Lite/logs.tar.gz"
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env bash
# Exit when any command fails
#set -x
set -e
# Keep track of the last executed command
trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG
# Echo an error message before exiting
trap 'echo "\"${last_command}\" command exited with code $?."' EXIT
# Remove temporary file
trap 'rm -f $TMPFILE' EXIT
source /etc/birdnet/birdnet.conf
# Set Variables
TMPFILE=$(mktemp)
# SCAN_DIRS are all directories marked "Analyzed"
SCAN_DIRS=($(find ${ANALYZED} -type d | sort ))
# This sets our while loop integer iterator 'a' -- it first checks whether
# there are any extractions, and if so, this instance of the extraction will
# start the 'a' value where the most recent instance left off.
# Ex: If the last extraction file is 189-%date%species.wav, 'a' will be 190.
# Else, 'a' starts at 1
if [ "$(find ${EXTRACTED} -name '*.wav' | wc -l)" -ge 1 ];then
a=$(( $( find "${EXTRACTED}" -name '*.wav' \
| awk -F "/" '{print $NF}' \
| cut -d'-' -f1 \
| sort -n \
| tail -n1 ) + 1 ))
else
a=1
fi
echo "Starting numbering at ${a}"
for h in "${SCAN_DIRS[@]}";do
echo "Creating the TMPFILE"
# The TMPFILE is created from each "Selection" txt file BirdNET creates
# within each "Analyzed" directory
# Field 1: Original WAVE file name
# Field 2: Extraction start time in seconds
# Field 3: Extraction end time in seconds
# Field 4: New WAVE file name to use
# Field 5: The species name
# Iterates over each "Analyzed" directory
for i in $(find ${h} -name '*txt' | sort );do
# Iterates over each '.txt' file found in each "Analyzed" directory
# to create the TMPFILE
sort -k 6n "$i" \
| awk '/Spect/ {print}' \
>> $TMPFILE
done
# The extraction reads each line of the TMPFILE and sets the variables ffmpeg
# will use.
while read -r line;do
a=$a
DATE="$(echo "${line}" \
| awk '{print $5}' \
| awk -F- '{print $1"-"$2"-"$3}')"
OLDFILE="$(echo "${line}" | awk '{print $5}')"
START="$(echo "${line}" | awk '{print $6}')"
END="$(echo "${line}" | awk '{print $7}')"
SPECIES=""$(echo ${line//\'} \
| awk '{for(i=11;i<=NF;++i)printf $i""FS ; print ""}' \
| cut -d'0' -f1 \
| xargs)""
NEWFILE="${SPECIES// /_}-${OLDFILE}"
NEWSPECIES_BYDATE="${EXTRACTED}/By_Date/${DATE}/${SPECIES// /_}"
NEWSPECIES_BYSPEC="${EXTRACTED}/By_Species/${SPECIES// /_}"
# If the extracted file already exists, increment the 'a' variable once
# but move onto the next line of the TMPFILE for extraction.
if [[ -f "${NEWSPECIES_BYDATE}/${a}-${NEWFILE}" ]];then
echo "Extraction exists. Moving on"
a=$((a+1))
continue
fi
echo "Checking for ${h}/${OLDFILE}"
# Before extracting the "Selection," the script checks to be sure the
# original WAVE file still exists.
[[ -f "${h}/${OLDFILE}" ]] || continue
echo "Checking for ${NEWSPECIES_BYDATE}"
# If a directory does not already exist for the species (by date),
# it is created
[[ -d "${NEWSPECIES_BYDATE}" ]] || mkdir -p "${NEWSPECIES_BYDATE}"
echo "Checking for ${NEWSPECIES_BYSPEC}"
# If a directory does not already exist for the species (by-species),
# it is created.
[[ -d "${NEWSPECIES_BYSPEC}" ]] || mkdir -p "${NEWSPECIES_BYSPEC}"
# If there are already 20 extracted entries for a given species
# for today, remove the oldest file and create the new one.
if [[ "$(find ${NEWSPECIES_BYDATE} | wc -l)" -ge 21 ]];then
echo "20 ${SPECIES}s, already! Removing the oldest by-date and making a new one"
cd ${NEWSPECIES_BYDATE} || exit 1
ls -1t . | tail -n +21 | xargs -r rm -vv
fi
echo "Extracting audio . . . "
# If the above tests have passed, then the extraction happens.
# After creating the extracted files by-date, and a directory tree
# structured by-species, symbolic links are made to populate the new
# directory.
ffmpeg -hide_banner -loglevel error -nostdin -i "${h}/${OLDFILE}" \
-acodec copy -ss "${START}" -to "${END}"\
"${NEWSPECIES_BYDATE}/${a}-${NEWFILE}"
if [[ "$(find ${NEWSPECIES_BYSPEC} | wc -l)" -ge 21 ]];then
echo "20 ${SPECIES}s, already! Removing the oldest by-species and making a new one"
cd ${NEWSPECIES_BYSPEC} || exit 1
ls -1t . | tail -n +21 | xargs -r rm -vv
ln -fs "${NEWSPECIES_BYDATE}/${a}-${NEWFILE}"\
"${NEWSPECIES_BYSPEC}/${a}-${NEWFILE}"
echo "Success! New extraction for ${SPECIES}"
else
ln -fs "${NEWSPECIES_BYDATE}/${a}-${NEWFILE}"\
"${NEWSPECIES_BYSPEC}/${a}-${NEWFILE}"
fi
# Finally, 'a' is incremented by one to allow multiple extractions per
# species per minute.
a=$((a + 1))
done < "${TMPFILE}"
echo -e "\n\n\nFINISHED!!! Processed extractions for ${h}"
# Once each line of the TMPFILE has been processed, the TMPFILE is emptied
# for the next iteration of the for loop.
>"${TMPFILE}"
# Rename files that have been processed so that they are not processed on the
# next extraction.
[[ -d "${PROCESSED}" ]] || mkdir "${PROCESSED}"
echo "Moving processed files to ${PROCESSED}"
mv -v ${h}/* ${PROCESSED} || continue
done
echo "Linking Processed files to "${EXTRACTED}/Processed" web directory"
# After all audio extractions have taken place, a directory is created to house
# the original WAVE and .txt files used for this extraction processs.
if [[ ! -L ${EXTRACTED}/Processed ]] || [[ ! -e ${EXTRACTED}/Processed ]];then
ln -sf ${PROCESSED} ${EXTRACTED}/Processed
fi
# That's all!
echo "Finished -- the extracted sections are in:
$(find -L ${EXTRACTED} -maxdepth 1)"
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env bash
# Install BirdNET script
#set -x # debugging
set -e # exit installation if anything fails
my_dir=$(realpath $(dirname $0))
trap '${my_dir}/dump_logs.sh && echo -e "\n\nExiting the installation. Goodbye!" && exit 1' SIGINT
cd $my_dir || exit 1
if [ "$(uname -m)" != "aarch64" ];then
echo "BirdNET-Lite requires a 64-bit OS.
It looks like your operating system is using $(uname -m),
but would need to be aarch64.
Please take a look at https://birdnetwiki.pmcgui.xyz for more
information"
exit 1
fi
#Install/Configure /etc/birdnet/birdnet.conf
./install_config.sh || exit 1
sudo ./install_services.sh || exit 1
source /etc/birdnet/birdnet.conf
CODE="$(awk '/_warning_/ {print $NF}' /tmp/cookie)"
TFLITE_URL="https://drive.google.com/uc?export=download&id=1dlEbugFDJXs-YDBCUC6WjADVtIttWxZA"
TF_COOKIE="https://drive.google.com/uc?export=download&confirm=${CODE}&id=1dlEbugFDJXs-YDBCUC6WjADVtIttWxZA"
APT_DEPS=(swig ffmpeg wget unzip curl cmake make)
LIBS_MODULES=(libjpeg-dev zlib1g-dev python3-dev python3-pip)
install_deps() {
echo " Checking dependencies"
sudo apt update &> /dev/null
for i in "${LIBS_MODULES[@]}";do
if [ $(apt list --installed 2>/dev/null | grep "$i" | wc -l) -le 0 ];then
echo " Installing $i"
sudo apt -y install ${i} &> /dev/null
else
echo " $i is installed!"
fi
done
for i in "${APT_DEPS[@]}";do
if ! which $i &>/dev/null ;then
echo " Installing $i"
sudo apt -y install ${i} &> /dev/null
else
echo " $i is installed!"
fi
done
}
install_birdnet() {
set -e
cd ~/BirdNET-Lite || exit 1
echo "Upgrading pip, whell, and setuptools"
sudo pip3 install --upgrade pip wheel setuptools
echo "Fetching the TFLite pre-built binaries"
curl -sc /tmp/cookie ${TFLITE_URL} > /dev/null
curl -Lb /tmp/cookie ${TF_COOKIE} -o tflite_runtime-2.6.0-cp37-none-linux_aarch64.whl
echo "Installing the new TFLite bin wheel"
sudo pip3 install --upgrade tflite_runtime-2.6.0-cp37-none-linux_aarch64.whl
echo "Installing colorama==0.4.4"
sudo pip3 install colorama==0.4.4
echo "Installing librosa"
sudo pip3 install librosa
}
echo "
This script will do the following:
#1: Install the following BirdNET system dependencies:
- ffmpeg
- swig
- libjpeg-dev
- zlib1g-dev
- python3-dev
- curl
- cmake
- make
- wget
#2: Copies the systemd .service and .mount files and enables those chosen
#3: Adds cron environments and jobs chosen"
echo
read -sp "\
Be sure you have read the software license before installing. This is
available in the BirdNET-Lite directory as "LICENSE"
If you DO NOT want to install BirdNET and the birdnet_analysis.service,
press Ctrl+C to cancel. If you DO wish to install BirdNET and the
birdnet_analysis.service, press ENTER to continue with the installation."
echo
echo
[ -d ${RECS_DIR} ] || mkdir -p ${RECS_DIR} &> /dev/null
install_deps
if [ ! -d ${VENV} ];then
install_birdnet
fi
echo " BirdNet is installed!!
To start the service manually, issue:
'sudo systemctl start birdnet_analysis'
To monitor the service logs, issue:
'journalctl -fu birdnet_analysis'
To stop the service manually, issue:
'sudo systemctl stop birdnet_analysis'
To stop and disable the service, issue:
'sudo systemctl disable --now birdnet_analysis.service'
Visit
http://birdnetsystem.local to see your extractions,
http://birdlog.local to see the log output of the birdnet_analysis.service,
http://extractionlog.local to see the log output of the extraction.service, and
http://birdstats.local to see the BirdNET-Lite Report"
echo
read -n1 -p " Would you like to run the birdnet_analysis.service now?" YN
echo
case $YN in
[Yy] ) sudo systemctl start birdnet_analysis.service \
&& journalctl -fu birdnet_analysis;;
* ) echo " Thanks for installing BirdNET-Lite!!
I hope it was helpful!";;
esac
+412
View File
@@ -0,0 +1,412 @@
#!/usr/bin/env bash
# Creates and installs the /etc/birdnet/birdnet.conf file
#set -x # Uncomment to enable debugging
set -e
trap 'exit 1' SIGINT SIGHUP
my_dir=$(realpath $(dirname $0))
BIRDNET_CONF="$(dirname ${my_dir})/birdnet.conf"
get_RECS_DIR() {
read -p "What is the full path to your recordings directory (locally)? " RECS_DIR
}
get_LATITUDE() {
read -p "What is the latitude where the recordings were made? " LATITUDE
}
get_LONGITUDE() {
read -p "What is the longitude where the recordings were made? " LONGITUDE
}
get_DO_EXTRACTIONS() {
while true; do
read -n1 -p "Do you want this device to perform the extractions? " DO_EXTRACTIONS
echo
case $DO_EXTRACTIONS in
[Yy] ) break;;
[Nn] ) break;;
* ) echo "You must answer with Yes or No (y or n)";;
esac
done
}
get_DO_RECORDING() {
while true; do
read -n1 -p "Is this device also doing the recording? " DO_RECORDING
echo
case $DO_RECORDING in
[Yy] ) break;;
[Nn] ) break;;
* ) echo "You must answer with Yes or No (y or n)";;
esac
done
}
get_REMOTE() {
while true; do
read -n1 -p "Are the recordings mounted on a remote file system?" REMOTE
echo
case $REMOTE in
[Yy] )
read -p "What is the remote hostname or IP address for the recorder? " REMOTE_HOST
read -p "Who is the remote user? " REMOTE_USER
read -p "What is the absolute path of the recordings directory on the remote host? " REMOTE_RECS_DIR
break;;
[Nn] ) break;;
* ) echo "Please answer Yes or No (y or n)";;
esac
done
}
get_EXTRACTIONS_URL() {
while true;do
read -n1 -p "Would you like to access the extractions via a web browser?
*Note: It is recommended, (but not required), that you run the web
server on the same host that does the extractions. If the extraction
service and web server are on different hosts, the \"By_Species\" and
\"Processed\" symbolic links won't work. The \"By-Date\" extractions,
however, will work as expected." CADDY_SERVICE
echo
case $CADDY_SERVICE in
[Yy] ) read -p "What URL would you like to publish the extractions to?
*Note: Set this to http://localhost if you do not want to make the
extractions publically available: " EXTRACTIONS_URL
get_CADDY_PWD
get_ICE_PWD
break;;
[Nn] ) EXTRACTIONS_URL= CADDY_PWD= ICE_PWD=;break;;
* ) echo "Please answer Yes or No";;
esac
done
}
get_CADDY_PWD() {
if [ -z ${CADDY_PWD} ]; then
while true; do
read -p "Please set a password to protect your data: " CADDY_PWD
case $CADDY_PWD in
"" ) echo "The password cannot be empty. Please try again.";;
* ) break;;
esac
done
fi
}
get_ICE_PWD() {
if [ ! -z ${CADDY_PWD} ] && [[ ${DO_RECORDING} =~ [Yy] ]];then
while true; do
read -n1 -p "Would you like to enable the live audio streaming service?" LIVE_STREAM
echo
case $LIVE_STREAM in
[Yy] )
read -p "Please set the icecast password. Use only alphanumeric characters." ICE_PWD
echo
case ${ICE_PWD} in
"" ) echo "The password cannot be empty. Please try again.";;
*) break;;
esac
break;;
[Nn] ) break;;
* ) echo "You must answer Yes or No (y or n).";;
esac
done
fi
}
get_PUSHED() {
while true; do
read -n1 -p "Do you have a free App key to receive mobile notifications via Pushed.co?" YN
echo
case $YN in
[Yy] ) read -p "Enter your Pushed.co App Key: " PUSHED_APP_KEY
read -p "Enter your Pushed.co App Key Secret: " PUSHED_APP_SECRET
break;;
[Nn] ) PUSHED_APP_KEY=
PUSHED_APP_SECRET=
break;;
* ) echo "A simple Yea or Nay will do";;
esac
done
}
get_INSTALL_NOMACHINE() {
while true; do
read -n1 -p "Would you like to also install NoMachine for remote desktop access?" INSTALL_NOMACHINE
echo
case $INSTALL_NOMACHINE in
[Yy] ) break;;
[Nn] ) break;;
* ) echo "You must answer with Yes or No (y or n)";;
esac
done
}
get_CHANNELS() {
[ -f $(dirname ${my_dir})/soundcard_params.txt ] || touch $(dirname ${my_dir})/soundcard_params.txt
SOUND_PARAMS=$(dirname ${my_dir})/soundcard_params.txt
SOUND_CARD="$(sudo -u ${USER} aplay -L \
| awk -F, '/^hw:/ {print $1}' \
| grep -ve 'vc4' -e 'Head' -e 'PCH' \
| uniq)"
script -c "arecord -D ${SOUND_CARD} --dump-hw-params" -a "${SOUND_PARAMS}" &> /dev/null
CHANNELS=$(awk '/CHANN/ { print $2 }' "${SOUND_PARAMS}" | sed 's/\r$//')
echo "Number of channels available: ${CHANNELS}"
}
configure() {
get_RECS_DIR
get_LATITUDE
get_LONGITUDE
get_DO_EXTRACTIONS
get_DO_RECORDING
get_REMOTE
get_EXTRACTIONS_URL
get_PUSHED
get_INSTALL_NOMACHINE
get_CHANNELS
}
install_birdnet_conf() {
cat << EOF > $(dirname ${my_dir})/birdnet.conf
################################################################################
# Configuration settings for BirdNET as a service #
################################################################################
INSTALL_DATE="$(date "+%D")"
#___________The four variables below are the only that are required.___________#
## BIRDNET_USER should be the non-root user systemd should use to execute each
## service.
BIRDNET_USER=${USER}
## RECS_DIR is the location birdnet_analysis.service will look for the data-set
## it needs to analyze. Be sure this directory is readable and writable for
## the BIRDNET_USER. If you are going to be accessing a remote data-set, you
## still need to set this, as this will be where the remote directory gets
## mounted locally. See REMOTE_RECS_DIR below for mounting remote data-sets.
RECS_DIR=${RECS_DIR}
## LATITUDE and LONGITUDE are self-explanatroy. Find them easily at
## maps.google.com. Only go to the thousanths place for these variables
## Example: these coordinates would indicate the Eiffel Tower in Paris, France.
## LATITUDE=48.858
## LONGITUDE=2.294
LATITUDE="${LATITUDE}"
LONGITUDE="${LONGITUDE}"
################################################################################
#------------------------------ Extraction Service ---------------------------#
# Keep this EMPTY if you do not want this device to perform the extractions #
## DO_EXTRACTIONS is simply a setting for enabling the extraction.service.
## Set this to Y or y to enable extractions.
DO_EXTRACTIONS=${DO_EXTRACTIONS}
################################################################################
#----------------------------- Recording Service ----------------------------#
# Keep this EMPTY if you do not want this device to perform the recording. #
## DO_RECORDING is simply a setting for enabling the 24/7 birdnet_recording.service.
## Set this to Y or y to enable recording.
DO_RECORDING=${DO_RECORDING}
################################################################################
#----------------- Mounting a remote directory with systemd -----------------#
#_______________The four variables below can be set to enable a_______________#
#___________________systemd.mount for analysis, extraction,____________________#
#______________________________or file-serving_________________________________#
# Leave these settings EMPTY if your data-set is local. #
## REMOTE is simply a setting for enabling the systemd.mount to use a remote
## filesystem for the data storage and service.
## Set this to Y or y to enable the systemd.mount.
REMOTE=${REMOTE}
## REMOTE_HOST is the IP address, hostname, or domain name SSH should use to
## connect for FUSE to mount its remote directories locally.
REMOTE_HOST=${REMOTE_HOST}
## REMOTE_USER is the user SSH will use to connect to the REMOTE_HOST.
REMOTE_USER=${REMOTE_USER}
## REMOTE_RECS_DIR is the directory on the REMOTE_HOST which contains the
## data-set SSHFS should mount to this system for local access. This is NOT the
## directory where you will access the data on this machine. See RECS_DIR for
## that.
REMOTE_RECS_DIR=${REMOTE_RECS_DIR}
################################################################################
#----------------------- Web-hosting/Caddy File-server -----------------------#
#__________The two variables below can be set to enable web access_____________#
#____________to your data,(e.g., extractions, raw data, live___________________#
#______________audio stream, BirdNET.selection.txt files)______________________#
# Leave these EMPTY if you do not want to enable web access #
## EXTRACTIONS_URL is the URL where the extractions, data-set, and live-stream
## will be web-hosted. If you do not own a domain, or would just prefer to keep
## BirdNET-Lite on your local network, you can set this to http://localhost.
## Setting this (even to http://localhost) will also allow you to enable the
## GoTTY web logging features below.
EXTRACTIONS_URL=${EXTRACTIONS_URL}
## CADDY_PWD is the plaintext password (that will be hashed) and used to access
## the "Processed" directory and live audio stream. This MUST be set if you
## choose to enable this feature.
CADDY_PWD=${CADDY_PWD}
################################################################################
#------------------------- Live Audio Stream --------------------------------#
#_____________The variable below configures/enables the live___________________#
#_____________________________audio stream.____________________________________#
# Keep this EMPTY if you do not wish to enable the live stream #
# or if this device is not doing the recording #
## ICE_PWD is the password that icecast2 will use to authenticate ffmpeg as a
## trusted source for the stream. You will never need to enter this manually
## anywhere other than here.
ICE_PWD=${ICE_PWD}
################################################################################
#------------------- Mobile Notifications via Pushed.co ---------------------#
#____________The two variables below enable mobile notifications_______________#
#_____________See https://pushed.co/quick-start-guide to get___________________#
#_________________________these values for your app.___________________________#
# Keep these EMPTY if haven't setup a Pushed.co App yet. #
## Pushed.co App Key and App Secret
PUSHED_APP_KEY=${PUSHED_APP_KEY}
PUSHED_APP_SECRET=${PUSHED_APP_SECRET}
################################################################################
#------------------------------- NoMachine ----------------------------------#
#_____________The variable below can be set include NoMachine__________________#
#_________________remote desktop software to be installed._____________________#
# Keep this EMPTY if you do not want to install NoMachine. #
## INSTALL_NOMACHINE is simply a setting that can be enabled to install
## NoMachine alongside the BirdNET-Lite for remote desktop access. This in-
## staller assumes personal use. Please reference the LICENSE file included
## in this repository for more information.
## Set this to Y or y to install NoMachine alongside the BirdNET-Lite
INSTALL_NOMACHINE=${INSTALL_NOMACHINE}
################################################################################
#-------------------------------- Defaults ----------------------------------#
#________The six variables below are default settings that you (probably)______#
#__________________don't need to change at all, but can._______________________#
## REC_CARD is the sound card you would want the birdnet_recording.service to
## use. This setting is irrelevant if you are not planning on doing data
## collection via recording on this machine. The command substitution below
## looks for a USB microphone's dsnoop alsa device. The dsnoop device lets
## birdnet_recording.service and livestream.service share the raw audio stream
## from the microphone. If you would like to use a different microphone than
## what this produces, or if your microphone does not support creating a
## dsnoop device, you can set this explicitly from a list of the available
## devices from the output of running 'aplay -L'
REC_CARD="\$(sudo -u pi aplay -L \
| grep dsnoop \
| cut -d, -f1 \
| grep -ve 'vc4' -e 'Head' -e 'PCH' \
| uniq)"
## PROCESSED is the directory where the formerly 'Analyzed' files are moved
## after extractions have been made from them. This includes both WAVE and
## BirdNET.selection.txt files.
PROCESSED=${RECS_DIR}/Processed
## EXTRACTED is the directory where the extracted audio selections are moved.
EXTRACTED=${RECS_DIR}/Extracted
## IDFILE is the file that keeps a complete list of every spececies that
## BirdNET has identified from your data-set. It is persistent across
## data-sets, so would need to be whiped clean through deleting or renaming
## it. A backup is automatically made from this variable each time it is
## updated (structure: ${IDFILE}.bak), and would also need to be removed
## or renamed to start a new file between data-sets. Alternately, you can
## change this variable between data-sets to preserve records of disparate
## data-sets according to name.
IDFILE=${HOME}/BirdNET-Lite/IdentifiedSoFar.txt
## OVERLAP is the value in seconds which BirdNET should use when analyzing
## the data. The values must be between 0.0-2.9.
OVERLAP="0.0"
## CONFIDENCE is the minimum confidence level from 0.0-1.0 BirdNET's analysis
## should reach before creating an entry in the BirdNET.selection.txt file.
## Don't set this to 1.0 or you won't have any results.
CONFIDENCE="0.7"
################################################################################
#------------------------------ Auto-Generated ------------------------------#
#_____________________The variables below are auto-generated___________________#
#______________________________during installation_____________________________#
## CHANNELS holds the variabel that corresponds to the number of channels the
## sound card supports.
CHANNELS=${CHANNELS}
# Don't touch the three below
## ANALYZED is where the extraction.service looks for audio and
## BirdNET.selection.txt files after they have been processed by the
## birdnet_analysis.service. This is NOT where the analyzed files are moved --
## analyzed files are always created within the same directory
## birdnet_analysis.service finds them.
ANALYZED=${RECS_DIR}/*/*Analyzed
## SYSTEMD_MOUNT is created from the RECS_DIR variable to comply with systemd
## mount naming requirements.
SYSTEMD_MOUNT=$(echo ${RECS_DIR#/} | tr / -).mount
## VENV is the virtual environment where the the BirdNET python build is found,
## i.e, VENV is the virtual environment miniforge built for BirdNET.
VENV=$(dirname ${my_dir})/miniforge/envs/birdnet
EOF
[ -d /etc/birdnet ] || sudo mkdir /etc/birdnet
sudo ln -sf $(dirname ${my_dir})/birdnet.conf /etc/birdnet/birdnet.conf
}
# Checks for a birdnet.conf file in the BirdNET-Lite directory for a
# non-interactive installation. Otherwise,the installation is interactive.
if [ -f ${BIRDNET_CONF} ];then
source ${BIRDNET_CONF}
install_birdnet_conf
else
configure
install_birdnet_conf
fi
+441
View File
@@ -0,0 +1,441 @@
#!/usr/bin/env bash
# This installs the services that have been selected
#set -x # Uncomment to enable debugging
trap 'rm -f ${TMPFILE}' EXIT
trap 'exit 1' SIGINT SIGHUP
my_dir=$(realpath $(dirname $0))
TMPFILE=$(mktemp)
nomachine_url="https://download.nomachine.com/download/7.6/Arm/nomachine_7.6.2_3_arm64.deb"
gotty_url="https://github.com/yudai/gotty/releases/download/v1.0.1/gotty_linux_arm.tar.gz"
CONFIG_FILE="$(dirname ${my_dir})/birdnet.conf"
install_scripts() {
echo "Installing BirdNET-Lite scripts to /usr/local/bin"
ln -sf ${my_dir}/* /usr/local/bin/
}
install_birdnet_analysis() {
echo "Installing the birdnet_analysis.service"
cat << EOF > /etc/systemd/system/birdnet_analysis.service
[Unit]
Description=BirdNET Analysis
[Service]
Restart=always
RuntimeMaxSec=10800
Type=simple
RestartSec=3
User=${USER}
ExecStart=/usr/local/bin/birdnet_analysis.sh
[Install]
WantedBy=multi-user.target
EOF
systemctl enable birdnet_analysis.service
}
install_extraction_service() {
echo "Installing the extraction.service and extraction.timer"
cat << EOF > /etc/systemd/system/extraction.service
[Unit]
Description=BirdNET BirdSound Extraction
[Service]
Restart=on-failure
RestartSec=3
Type=simple
User=${USER}
ExecStart=/usr/local/bin/extract_new_birdsounds.sh
[Install]
WantedBy=multi-user.target
EOF
cat << EOF > /etc/systemd/system/extraction.timer
[Unit]
Description=BirdNET BirdSound Extraction Timer
Requires=extraction.service
[Timer]
Unit=extraction.service
OnCalendar=*:0/10
[Install]
WantedBy=multi-user.target
EOF
systemctl enable extraction.timer
systemctl enable extraction.service
echo "Adding the species_updater.cron"
if ! crontab -u ${USER} -l &> /dev/null;then
crontab -u ${USER} $(dirname ${my_dir})/templates/species_updater.cron &> /dev/null
else
crontab -u ${USER} -l > ${TMPFILE}
cat $(dirname ${my_dir})/templates/species_updater.cron >> ${TMPFILE}
crontab -u ${USER} "${TMPFILE}" &> /dev/null
fi
}
create_necessary_dirs() {
echo "Creating necessary directories"
[ -d ${EXTRACTED} ] || sudo -u ${USER} mkdir -p ${EXTRACTED}
[ -d ${EXTRACTED}/By_Date ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Date
[ -d ${EXTRACTED}/By_Species ] || sudo -u ${USER} mkdir -p ${EXTRACTED}/By_Species
[ -d ${PROCESSED} ] || sudo -u ${USER} mkdir -p ${PROCESSED}
}
install_alsa() {
echo "Checking for alsa-utils"
if which arecord &> /dev/null ;then
echo "alsa-utils installed"
else
echo "Installing alsa-utils"
apt -qqq update
apt install -qqy alsa-utils
echo "alsa-utils installed"
fi
}
install_recording_service() {
echo "Installing birdnet_recording.service"
cat << EOF > /etc/systemd/system/birdnet_recording.service
[Unit]
Description=BirdNET Recording
[Service]
Environment=XDG_RUNTIME_DIR=/run/user/1000
Restart=always
Type=simple
RestartSec=3
User=${USER}
ExecStart=/usr/local/bin/birdnet_recording.sh
[Install]
WantedBy=multi-user.target
EOF
systemctl enable birdnet_recording.service
}
install_sshfs() {
echo "Checking for SSHFS to mount remote filesystem"
if ! which sshfs &> /dev/null ;then
echo "Installing SSHFS"
apt -qqq update
apt install -qqqy sshfs
fi
}
setup_sshkeys() {
echo "Setting up SSH keys for SSHFS"
echo "Adding remote host key to ${HOME}/.ssh/known_hosts"
ssh-keyscan -H ${REMOTE_HOST} >> ${HOME}/.ssh/known_hosts
chown ${USER}:${USER} ${HOME}/.ssh/known_hosts &> /dev/null
if [ ! -f ${HOME}/.ssh/id_ed25519.pub ];then
echo "Creating a new key"
ssh-keygen -t ed25519 -f ${HOME}/.ssh/id_ed25519 -P ""
fi
chown -R ${USER}:${USER} ${HOME}/.ssh/ &> /dev/null
echo "Copying public key to ${REMOTE_HOST}"
ssh-copy-id ${REMOTE_USER}@${REMOTE_HOST}
}
install_systemd_mount() {
echo "Installing systemd.mount"
cat << EOF > /etc/systemd/system/${SYSTEMD_MOUNT}
[Unit]
Description=Mount remote fs with sshfs
DefaultDependencies=no
Conflicts=umount.target
After=network-online.target
Before=umount.target
Wants=network-online.target
[Install]
WantedBy=multi-user.target
[Mount]
What=${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_RECS_DIR}
Where=${RECS_DIR}
Type=fuse.sshfs
Options=delay_connect,_netdev,allow_other,IdentityFile=${HOME}/.ssh/id_ed25519,reconnect,ServerAliveInterval=30,ServerAliveCountMax=5,x-systemd.automount,uid=1000,gid=1000
TimeoutSec=60
EOF
}
install_caddy() {
if ! which caddy &> /dev/null ;then
echo "Installing Caddy"
curl -1sLf \
'https://dl.cloudsmith.io/public/caddy/stable/setup.deb.sh' \
| sudo -E bash
apt -qq update
apt install -qqy caddy
systemctl enable --now caddy
else
echo "Caddy is installed"
systemctl enable --now caddy
fi
}
install_Caddyfile() {
echo "Installing the Caddyfile"
[ -d /etc/caddy ] || mkdir /etc/caddy
cp $(dirname ${my_dir})/templates/index.html ${EXTRACTED}/
if [ -f /etc/caddy/Caddyfile ];then
cp /etc/caddy/Caddyfile{,.original}
fi
HASHWORD=$(caddy hash-password -plaintext ${CADDY_PWD})
cat << EOF > /etc/caddy/Caddyfile
${EXTRACTIONS_URL} {
root * ${EXTRACTED}
file_server browse
basicauth /Processed* {
birdnet ${HASHWORD}
}
basicauth /stream {
birdnet ${HASHWORD}
}
reverse_proxy /stream localhost:8000
}
http://birdnetsystem.local {
root * ${EXTRACTED}
file_server browse
basicauth /Processed* {
birdnet ${HASHWORD}
}
basicauth /stream {
birdnet ${HASHWORD}
}
reverse_proxy /stream localhost:8000
}
http://birdlog.local {
reverse_proxy localhost:8080
}
http://extractionlog.local {
reverse_proxy localhost:8888
}
http://birdstats.local {
reverse_proxy localhost:9090
}
EOF
systemctl reload caddy
}
install_avahi_aliases() {
echo "Installing Avahi Services"
if ! which avahi-publish &> /dev/null; then
echo "Installing avahi-utils"
apt install -y avahi-utils &> /dev/null
fi
echo "Installing avahi-alias service"
cat << 'EOF' > /etc/systemd/system/avahi-alias@.service
[Unit]
Description=Publish %I as alias for %H.local via mdns
After=network.target network-online.target
Requires=network-online.target
[Service]
Restart=always
Type=simple
ExecStart=/bin/bash -c "/usr/bin/avahi-publish -a -R %I $(avahi-resolve -4 -n %H.local | cut -f 2)"
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now avahi-alias@birdnetsystem.local.service
systemctl enable --now avahi-alias@birdlog.local.service
systemctl enable --now avahi-alias@extractionlog.local.service
systemctl enable --now avahi-alias@birdstats.local.service
}
install_gotty_logs() {
echo "Installing GoTTY logging"
if ! which gotty &> /dev/null;then
echo "Installing GoTTY binary"
wget -c ${gotty_url} -O - | tar -xz -C /usr/local/bin/
fi
sudo -u ${USER} ln -sf $(dirname ${my_dir})/templates/gotty \
${HOME}/.gotty
echo "Installing the birdnet_log.service"
cat << EOF > /etc/systemd/system/birdnet_log.service
[Unit]
Description=BirdNET Analysis Log
[Service]
Restart=on-failure
RestartSec=3
Type=simple
User=${USER}
Environment=TERM=xterm-256color
ExecStart=/usr/local/bin/gotty -p 8080 --title-format "BirdNET-Lite Log" journalctl -fu birdnet_analysis.service
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now birdnet_log.service
echo "Installing the extraction_log.service"
cat << EOF > /etc/systemd/system/extraction_log.service
[Unit]
Description=BirdNET Extraction Log
[Service]
Restart=on-failure
RestartSec=3
Type=simple
User=${USER}
Environment=TERM=xterm-256color
ExecStart=/usr/local/bin/gotty -p 8888 --title-format "Extractions Log" journalctl -fu extraction.service
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now extraction_log.service
echo "Installing the birdstats.service"
cat << EOF > /etc/systemd/system/birdstats.service
[Unit]
Description=BirdNET Statistics Log
[Service]
Restart=on-failure
RestartSec=3
Type=simple
User=${USER}
Environment=TERM=xterm-256color
ExecStart=/usr/local/bin/gotty -p 9090 --title-format "BirdNET-Lite Statistics" /usr/local/bin/birdnet_stats.sh
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now birdstats.service
}
install_icecast() {
if ! which icecast2;then
echo "Installing IceCast2"
apt -qq update
echo "icecast2 icecast2/icecast-setup boolean false" | debconf-set-selections
apt install -qqy icecast2
config_icecast
systemctl enable --now icecast2
/etc/init.d/icecast2 start
else
echo "Icecast2 is installed"
config_icecast
systemctl enable --now icecast2
/etc/init.d/icecast2 start
fi
}
config_icecast() {
if [ -f /etc/icecast2/icecast.xml ];then
cp /etc/icecast2/icecast.xml{,.prebirdnetsystem}
fi
sed -i 's/>admin</>birdnet</g' /etc/icecast2/icecast.xml
passwords=("source-" "relay-" "admin-" "master-" "")
for i in "${passwords[@]}";do
sed -i "s/<${i}password>.*<\/${i}password>/<${i}password>${ICE_PWD}<\/${i}password>/g" /etc/icecast2/icecast.xml
done
}
install_livestream_service() {
echo "Installing Live Stream service"
cat << EOF > /etc/systemd/system/livestream.service
[Unit]
Description=BirdNET-Lite Live Stream
[Service]
Environment=XDG_RUNTIME_DIR=/run/user/1000
Restart=always
Type=simple
RestartSec=3
User=${USER}
ExecStart=/usr/local/bin/livestream.sh
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now livestream.service
}
install_nomachine() {
echo "Installing NoMachine"
cd ~
curl -s -O "${nomachine_url}"
apt install -y ${HOME}/nomachine_7.6.2_3_arm64.deb
rm -f ${HOME}/nomachine_7.6.2_3_arm64.deb
}
install_systemd_overrides() {
for i in caddy birdnet_analysis extraction birdnet_recording;do
if [ -f /etc/systemd/system/${i}.service ];then
[ -d /etc/systemd/system/${i}.d ] || mkdir /etc/systemd/system/${i}.d
echo "Installing the systemd overrides.conf for the ${i}.service"
cat << EOF > /etc/systemd/system/${i}.d/overrides.conf
[Unit]
After=network.target network-online.target ${SYSTEMD_MOUNT}
Requires=network-online.target ${SYSTEMD_MOUNT}
EOF
fi
done
}
install_cleanup_cron() {
echo "Installing the cleanup.cron"
if ! crontab -u ${USER} -l &> /dev/null;then
crontab -u ${USER} $(dirname ${my_dir})/templates/cleanup.cron &> /dev/null
else
crontab -u ${USER} -l > ${TMPFILE}
cat $(dirname ${my_dir})/templates/cleanup.cron >> ${TMPFILE}
crontab -u ${USER} "${TMPFILE}" &> /dev/null
fi
}
install_selected_services() {
install_scripts
install_birdnet_analysis
if [[ "${DO_EXTRACTIONS}" =~ [Yy] ]];then
install_extraction_service
create_necessary_dirs
fi
if [[ "${DO_RECORDING}" =~ [Yy] ]];then
install_alsa
install_recording_service
fi
if [[ "${REMOTE}" =~ [Yy] ]];then
install_sshfs
setup_sshkeys
install_systemd_mount
fi
if [ ! -z "${EXTRACTIONS_URL}" ];then
install_caddy
install_Caddyfile
install_avahi_aliases
install_gotty_logs
fi
if [ ! -z "${ICE_PWD}" ];then
install_icecast
install_livestream_service
fi
if [ ! -z "${INSTALL_NOMACHINE}" ];then
install_nomachine
fi
if [[ "${REMOTE}" =~ [Yy] ]];then
install_systemd_overrides
fi
install_cleanup_cron
}
if [ -f ${CONFIG_FILE} ];then
source ${CONFIG_FILE}
USER=${BIRDNET_USER}
HOME="$(getent passwd ${BIRDNET_USER} | cut -d: -f6)"
install_selected_services
else
echo "Unable to find a configuration file. Please make sure that $CONFIG_FILE exists."
fi
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# This script installs a web-based terminal @ http://birdterminal.local
# ONLY run this additional script if you trust everyone on your
# local network completely as the credentials are sent WITHOUT
# any SSL/TLS encryption. For a secure remote connection to your
# BirdNET-Lite command line, consider enabling SSH on the
# Raspberry Pi and using another Linux machine or an SSH
# client software, or you can alternately add the 'tls internal'
# directive to the Caddyfile to add a self-signed certificate for TLS/SSL
# encryption. For remote desktop access, NoMachine can be installed.
source /etc/birdnet/birdnet.conf
my_dir=$(realpath $(dirname $0))
# Install tmux from version control
install_tmux() {
DEPENDS=(
automake
autoconf
libevent-2*
libevent-dev
ncurses-bin
ncurses-base
ncurses-term
libncurses-dev
build-essential
bison
pkg-config
gcc
)
if which tmux &>/dev/null; then
echo "tmux is installed"
else
sudo apt update && sudo apt -y install "${DEPENDS[@]}"
cd ${HOME} && git clone https://github.com/tmux/tmux.git
cd tmux
sh autogen.sh
./configure && make && sudo make install
cd && rm -drf ./tmux
sudo ln -sf "$(dirname ${my_dir})/templates/tmux.conf" /etc/tmux.conf
fi
}
install_web_terminal() {
cat << EOF | sudo tee /etc/systemd/system/birdterminal.service
[Unit]
Description=A BirdNET-Lite Web Terminal
[Service]
Restart=on-failure
RestartSec=3
Type=simple
User=${BIRDNET_USER}
Environment=TERM=xterm-256color
ExecStart=/usr/local/bin/gotty -w --title-format "Login!" -p 9111 tmux new -A -s Login sudo bash -c login
[Install]
WantedBy=multi-user.target
EOF
HASHWORD="$(caddy hash-password -plaintext "${CADDY_PWD}")"
cat << EOF | sudo tee -a /etc/caddy/Caddyfile
http://birdterminal.local {
reverse_proxy localhost:9111
basicauth {
birdnet "${HASHWORD}"
}
}
EOF
sudo systemctl enable --now birdterminal.service
sudo systemctl enable --now avahi-alias@birdterminal.local.service
sudo systemctl restart caddy
}
install_tmux
install_web_terminal
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
echo "Configuring zram.service"
sudo touch /etc/modules-load.d/zram.conf
echo 'zram' | sudo tee /etc/modules-load.d/zram.conf
sudo touch /etc/modprobe.d/zram.conf
echo 'options zram num_devices=1' | sudo tee /etc/modprobe.d/zram.conf
sudo touch /etc/udev/rules.d/99-zram.rules
echo 'KERNEL=="zram0", ATTR{disksize}="4G",TAG+="systemd"' \
| sudo tee /etc/udev/rules.d/99-zram.rules
sudo touch /etc/systemd/system/zram.service
echo "Installing zram.service"
cat << EOF | sudo tee /etc/systemd/system/zram.service
[Unit]
Description=Swap with zram
After=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=true
ExecStartPre=/sbin/mkswap /dev/zram0
ExecStart=/sbin/swapon /dev/zram0
ExecStop=/sbin/swapoff /dev/zram0
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable zram
echo "You'll need to reboot for this to take effect."
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Live Audio Stream Service Script
source /etc/birdnet/birdnet.conf
if [ -z ${REC_CARD} ];then
echo "Stream not supported"
else
ffmpeg -loglevel 52 -ac ${CHANNELS} -f alsa -i ${REC_CARD} -acodec libmp3lame \
-b:a 320k -ac ${CHANNELS} -content_type 'audio/mpeg' \
-f mp3 icecast://source:${ICE_PWD}@localhost:8000/stream -re
fi
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Pretty date suffixes
TODAY="$(date +%e)"
if [[ $TODAY == ' 1' ]] || [[ $TODAY == 21 ]] || [[ $TODAY == 31 ]]; then
SUFFIX="st"
elif [[ $TODAY == ' 2' ]] || [[ $TODAY == 22 ]];then
SUFFIX="nd"
elif [[ $TODAY == ' 3' ]] || [[ $TODAY == 23 ]];then
SUFFIX="rd"
else
SUFFIX="th"
fi
echo "$SUFFIX"
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Reconfigure the BirdNET-Lite
source /etc/birdnet/birdnet.conf
uninstall.sh
${HOME}/BirdNET-Lite/scripts/install_config.sh
sudo ${HOME}/BirdNET-Lite/scripts/install_services.sh
echo "BirdNET-Lite has now been reconfigured."
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Sends a notification if a new species is detected
# set -x
trap 'rm -f $TMPFILE' SIGINT SIGHUP EXIT
source /etc/birdnet/birdnet.conf
TMPFILE=$(mktemp)
[ -f ${IDFILE} ] || touch ${IDFILE}
cat "${IDFILE}" > "${TMPFILE}"
/usr/local/bin/update_species.sh &> /dev/null
if ! diff "${IDFILE}" "${TMPFILE}"; then
SPECIES=("$(diff "${IDFILE}" "${TMPFILE}" \
| awk '/</ {print $2" "$3}')")
NOTIFICATION="New Species Detected: ${SPECIES[@]}"
sudo systemctl restart birdnet_analysis && sleep 30
sudo systemctl start extraction
if [ ! -z ${PUSHED_APP_KEY} ];then
curl -X POST -s \
--form-string "app_key=${PUSHED_APP_KEY}" \
--form-string "app_secret=${PUSHED_APP_SECRET}" \
--form-string "target_type=app" \
--form-string "content=${NOTIFICATION}" \
https://api.pushed.co/1/push
fi
fi
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
# Uninstall script to remove everything
# set -x # Uncomment to debug
trap 'rm -f ${TMPFILE}' EXIT
source /etc/birdnet/birdnet.conf &> /dev/null
SCRIPTS=(/usr/local/bin/birdnet_analysis.sh
/usr/local/bin/birdnet_recording.sh
/usr/local/bin/birdnet_stats.sh
/usr/local/bin/cleanup.sh
/usr/local/bin/disk_usage.sh
/usr/local/bin/dump_logs.sh
/usr/local/bin/extract_new_birdsounds.sh
/usr/local/bin/install_birdnet.sh
/usr/local/bin/install_config.sh
/usr/local/bin/install_services.sh
/usr/local/bin/install_tmux_services.sh
/usr/local/bin/install_zram_service.sh
/usr/local/bin/livestream.sh
/usr/local/bin/pretty_date.sh
/usr/local/bin/reconfigure_birdnet.sh
/usr/local/bin/species_notifier.sh
/usr/local/bin/uninstall.sh
/usr/local/bin/update_species.sh
${HOME}/.gotty)
SERVICES=(avahi-alias@birdlog.local.service
avahi-alias@birdnetsystem.local.service
avahi-alias@birdstats.local.service
avahi-alias@extractionlog.local.service
avahi-alias@birdterminal.local.service
birdnet_analysis.d
birdnet_analysis.service
birdnet_log.service
birdnet_recording.d
birdnet_recording.service
birdstats.service
birdterminal.service
caddy.d
caddy.service
extraction_log.service
extraction.d
extraction.service
extraction.timer
livestream.service
${SYSTEMD_MOUNT})
remove_services() {
for i in "${SERVICES[@]}"; do
if [ -L /etc/systemd/system/multi-user.target.wants/"${i}" ];then
sudo systemctl disable --now "${i}"
fi
if [ -f /etc/systemd/system/"${i}" ];then
sudo rm /etc/systemd/system/"${i}"
fi
if [ -d /etc/systemd/system/"${i}" ];then
sudo rm -drf /etc/systemd/system/"${i}"
fi
done
remove_icecast
remove_crons
}
remove_crons() {
TMPFILE=$(mktemp)
crontab -l | sed -e '/birdnet/,+1d' > "${TMPFILE}"
crontab "${TMPFILE}"
}
remove_icecast() {
if [ -f /etc/init.d/icecast2 ];then
sudo /etc/init.d/icecast2 stop
sudo systemctl disable --now icecast2
fi
}
remove_scripts() {
for i in "${SCRIPTS[@]}";do
if [ -L "${i}" ];then
sudo rm -v "${i}"
fi
done
}
remove_services
remove_scripts
if [ -d /etc/birdnet ];then sudo rm -drf /etc/birdnet;fi
if [ -f ${HOME}/BirdNET-Lite/birdnet.conf ];then sudo rm -f ${HOME}/BirdNET-Lite/birdnet.conf;fi
echo "Uninstall finished. Remove this directory with 'rm -drfv' to finish."
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Update the species list
# set -x
trap 'rm -f "$TMPFILE"' SIGINT SIGTERM EXIT
source /etc/birdnet/birdnet.conf
TMPFILE=$(mktemp) || exit 1
[ -f ${IDFILE} ] || touch ${IDFILE}
IDFILEBAKUP="${IDFILE}.bak"
if [ $(find ${ANALYZED} -name '*txt' | wc -l) -ge 1 ];then
sort $(find ${ANALYZED} -name '*txt') \
| awk '/Spect/ {for(i=11;i<=NF;++i)printf $i""FS ; print ""}' \
| cut -d'0' -f1 \
| sort -u > "$TMPFILE"
cat "$IDFILE" >> "$TMPFILE"
cp "$IDFILE" "$IDFILEBAKUP"
sort -u "$TMPFILE" > "$IDFILE"
cat "$IDFILE"
else
cat "$IDFILE"
fi
+31
View File
@@ -0,0 +1,31 @@
${EXTRACTIONS_URL} {
root * ${EXTRACTED}
file_server browse
basicauth ${PROCESSED}* {
birdnet ${HASHWORD}
}
basicauth /stream {
birdnet ${HASHWORD}
}
reverse_proxy /stream localhost:8000
}
http://birdnetsystem.local {
root * ${EXTRACTED}
file_server browse
basicauth ${PROCESSED}* {
birdnet ${HASHWORD}
}
basicauth /stream {
birdnet ${HASHWORD}
}
reverse_proxy /stream localhost:8000
}
http://birdlog.local {
reverse_proxy localhost:8080
}
http://extractionlog.local {
reverse_proxy localhost:8888
}
http://birdterminal.local {
reverse_proxy localhost:9111
}
+29
View File
@@ -0,0 +1,29 @@
${EXTRACTIONS_URL} {
root * ${EXTRACTED}
file_server browse
basicauth ${PROCESSED}* {
birdnet ${HASHWORD}
}
basicauth /stream {
birdnet ${HASHWORD}
}
}
http://birdnetsystem.local {
root * ${EXTRACTED}
file_server browse
basicauth ${PROCESSED}* {
birdnet ${HASHWORD}
}
basicauth /stream {
birdnet ${HASHWORD}
}
}
http://birdlog.local {
reverse_proxy localhost:8080
}
http://extractionlog.local {
reverse_proxy localhost:8888
}
http://birdterminal.local {
reverse_proxy localhost:9111
}
+3
View File
@@ -0,0 +1,3 @@
[Unit]
After=network.target network-online.target ${SYSTEMD_MOUNT}
Requires=network-online.target ${SYSTEMD_MOUNT}
+17
View File
@@ -0,0 +1,17 @@
[Unit]
Description=Mount remote fs with sshfs
DefaultDependencies=no
Conflicts=umount.target
After=network-online.target
Before=umount.target
Wants=network-online.target
[Install]
WantedBy=multi-user.target
[Mount]
What="${REMOTE_USER}"@"${REMOTE_HOST}":"${REMOTE_RECS_DIR}"
Where="${RECS_DIR}"
Type=fuse.sshfs
Options=delay_connect,_netdev,allow_other,IdentityFile=${HOME}/.ssh/id_ed25519,reconnect,ServerAliveInterval=30,ServerAliveCountMax=5,x-systemd.automount,uid=1000,gid=1000
TimeoutSec=60
+10
View File
@@ -0,0 +1,10 @@
[Unit]
Description=Publish %I as alias for %H.local via mdns
After=network.target network-online.target
Requires=network-online.target
[Service]
Restart=always
Type=simple
ExecStart=/bin/bash -c "/usr/bin/avahi-publish -a -R %I $(avahi-resolve -4 -n %H.local | cut -f 2)"
[Install]
WantedBy=multi-user.target
+14
View File
@@ -0,0 +1,14 @@
[Unit]
Description=BirdNET Analysis
Requires=${SYSTEMD_MOUNT}
After=network-online.target ${SYSTEMD_MOUNT}
[Service]
Restart=always
Type=simple
RestartSec=3
User=${BIRDNET_USER}
ExecStart=/usr/local/bin/birdnet_analysis.sh
[Install]
WantedBy=multi-user.target
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=BirdNET Analysis Log
[Service]
Restart=on-failure
RestartSec=3
Type=simple
User=${BIRDNET_USER}
Environment=TERM=xterm-256color
ExecStart=/usr/local/bin/gotty -p 8080 --title-format "BirdNET-Lite Log" journalctl -fu birdnet_analysis.service
[Install]
WantedBy=multi-user.target
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=BirdNET Recording
[Service]
Environment=XDG_RUNTIME_DIR=/run/user/1000
Restart=always
Type=simple
RestartSec=3
User=${BIRDNET_USER}
ExecStart=/usr/local/bin/birdnet_recording.sh
[Install]
WantedBy=multi-user.target
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=BirdNET Statistics Log
[Service]
Restart=on-failure
RestartSec=3
Type=simple
User=${BIRDNET_USER}
Environment=TERM=xterm-256color
ExecStart=/usr/local/bin/gotty -p 9090 --title-format "BirdNET-Lite Statistics" /usr/local/bin/birdnet_stats.sh
[Install]
WantedBy=multi-user.target
+13
View File
@@ -0,0 +1,13 @@
[Unit]
Description=A BirdNET-Lite Web Terminal
[Service]
Restart=on-failure
RestartSec=3
Type=simple
User=${BIRDNET_USER}
Environment=TERM=xterm-256color
ExecStart=/usr/local/bin/gotty -w --title-format "Login!" -p 9111 tmux new -A -s Login sudo bash -c login
[Install]
WantedBy=multi-user.target
+2
View File
@@ -0,0 +1,2 @@
#birdnet
0 1 * * * /usr/local/bin/cleanup.sh &> /dev/null
+10
View File
@@ -0,0 +1,10 @@
[Unit]
Description=BirdNET BirdSound Extraction
[Service]
Restart=on-failure
RestartSec=3
Type=simple
User=${BIRDNET_USER}
ExecStart=/usr/local/bin/extract_new_birdsounds.sh
[Install]
WantedBy=multi-user.target
+8
View File
@@ -0,0 +1,8 @@
[Unit]
Description=BirdNET BirdSound Extraction Timer
Requires=extraction.service
[Timer]
Unit=extraction.service
OnCalendar=*:0/10
[Install]
WantedBy=multi-user.target
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=BirdNET Extraction Log
[Service]
Restart=on-failure
RestartSec=3
Type=simple
User=${BIRDNET_USER}
Environment=TERM=xterm-256color
ExecStart=/usr/local/bin/gotty -p 8888 --title-format "Extractions Log" journalctl -fu extraction.service
[Install]
WantedBy=multi-user.target
+5
View File
@@ -0,0 +1,5 @@
preferences {
font_size = 16
foreground_color = "rgb(0, 0, 0)"
background_color = "rgb(119, 196, 135)"
}
+445
View File
@@ -0,0 +1,445 @@
<!DOCTYPE html>
<html>
<head>
<title>BirdNET-Lite</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { padding: 0; margin: 0; }
body {
font-family: sans-serif;
text-rendering: optimizespeed;
background-color: #ffffff;
}
a {
color: #006ed3;
text-decoration: none;
}
a:hover,
h1 a:hover {
color: #319cff;
}
header,
#summary {
padding-left: 5%;
padding-right: 5%;
}
th:first-child,
td:first-child {
width: 5%;
}
th:last-child,
td:last-child {
width: 5%;
}
header {
padding-top: 25px;
padding-bottom: 15px;
background-color: #f2f2f2;
}
h1 {
font-size: 20px;
font-weight: normal;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
color: #999;
}
h1 a {
color: #000;
margin: 0 4px;
}
h1 a:hover {
text-decoration: underline;
}
h1 a:first-child {
margin: 0;
}
main {
display: block;
}
.meta {
font-size: 12px;
font-family: Verdana, sans-serif;
border-bottom: 1px solid #9C9C9C;
padding-top: 10px;
padding-bottom: 10px;
}
.meta-item {
margin-right: 1em;
}
#filter {
padding: 4px;
border: 1px solid #CCC;
}
table {
width: 100%;
border-collapse: collapse;
}
tr {
border-bottom: 1px dashed #dadada;
}
tbody tr:hover {
background-color: #ffffec;
}
th,
td {
text-align: left;
padding: 10px 0;
}
th {
padding-top: 15px;
padding-bottom: 15px;
font-size: 16px;
white-space: nowrap;
}
th a {
color: black;
}
th svg {
vertical-align: middle;
}
td {
white-space: nowrap;
font-size: 14px;
}
td:nth-child(2) {
width: 80%;
}
td:nth-child(3),
th:nth-child(3) {
padding: 0 20px 0 20px;
}
th:nth-child(4),
td:nth-child(4) {
text-align: right;
}
td:nth-child(2) svg {
position: absolute;
}
td .name,
td .goup {
margin-left: 1.75em;
word-break: break-all;
overflow-wrap: break-word;
white-space: pre-wrap;
}
.icon {
margin-right: 5px;
}
.icon.sort {
display: inline-block;
width: 1em;
height: 1em;
position: relative;
top: .2em;
}
.icon.sort .top {
position: absolute;
left: 0;
top: -1px;
}
.icon.sort .bottom {
position: absolute;
bottom: -1px;
left: 0;
}
footer {
padding: 40px 20px;
font-size: 12px;
text-align: center;
}
@media (max-width: 600px) {
.hideable {
display: none;
}
td:nth-child(2) {
width: auto;
}
th:nth-child(3),
td:nth-child(3) {
padding-right: 5%;
text-align: right;
}
h1 {
color: #000;
}
h1 a {
margin: 0;
}
#filter {
max-width: 100px;
}
}
@media (prefers-color-scheme: dark) {
body {
background-color: #101010;
color: #dddddd;
}
header {
background-color: #151515;
}
tbody tr:hover {
background-color: #252525;
}
header a,
th a {
color: #dddddd;
}
a {
color: #5796d1;
text-decoration: none;
}
a:hover,
h1 a:hover {
color: #62b2fd;
}
tr {
border-bottom: 1px dashed rgba(255, 255, 255, 0.12);
}
#up-arrow,
#down-arrow {
fill: #dddddd;
}
#filter {
background-color: #151515;
color: #ffffff;
border: 1px solid #212121;
}
.meta {
border-bottom: 1px solid #212121
}
}
</style>
</head>
<body onload='initFilter()'>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="0" width="0" style="position: absolute;">
<defs>
<!-- Folder -->
<g id="folder" fill-rule="nonzero" fill="none">
<path d="M285.22 37.55h-142.6L110.9 0H31.7C14.25 0 0 16.9 0 37.55v75.1h316.92V75.1c0-20.65-14.26-37.55-31.7-37.55z" fill="#FFA000"/>
<path d="M285.22 36H31.7C14.25 36 0 50.28 0 67.74v158.7c0 17.47 14.26 31.75 31.7 31.75H285.2c17.44 0 31.7-14.3 31.7-31.75V67.75c0-17.47-14.26-31.75-31.7-31.75z" fill="#FFCA28"/>
</g>
<g id="folder-shortcut" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="folder-shortcut-group" fill-rule="nonzero">
<g id="folder-shortcut-shape">
<path d="M285.224876,37.5486902 L142.612438,37.5486902 L110.920785,0 L31.6916529,0 C14.2612438,0 0,16.8969106 0,37.5486902 L0,112.646071 L316.916529,112.646071 L316.916529,75.0973805 C316.916529,54.4456008 302.655285,37.5486902 285.224876,37.5486902 Z" id="Shape" fill="#FFA000"></path>
<path d="M285.224876,36 L31.6916529,36 C14.2612438,36 0,50.2838568 0,67.7419039 L0,226.451424 C0,243.909471 14.2612438,258.193328 31.6916529,258.193328 L285.224876,258.193328 C302.655285,258.193328 316.916529,243.909471 316.916529,226.451424 L316.916529,67.7419039 C316.916529,50.2838568 302.655285,36 285.224876,36 Z" id="Shape" fill="#FFCA28"></path>
</g>
<path d="M126.154134,250.559184 C126.850974,251.883673 127.300549,253.006122 127.772602,254.106122 C128.469442,255.206122 128.919016,256.104082 129.638335,257.002041 C130.559962,258.326531 131.728855,259 133.100057,259 C134.493737,259 135.415364,258.55102 136.112204,257.67551 C136.809044,257.002041 137.258619,255.902041 137.258619,254.577551 C137.258619,253.904082 137.258619,252.804082 137.033832,251.457143 C136.786566,249.908163 136.561779,249.032653 136.561779,248.583673 C136.089726,242.814286 135.864939,237.920408 135.864939,233.273469 C135.864939,225.057143 136.786566,217.514286 138.180246,210.846939 C139.798713,204.202041 141.889234,198.634694 144.429328,193.763265 C147.216689,188.869388 150.678411,184.873469 154.836973,181.326531 C158.995535,177.779592 163.626149,174.883673 168.481552,172.661224 C173.336954,170.438776 179.113983,168.665306 185.587852,167.340816 C192.061722,166.218367 198.760378,165.342857 205.481514,164.669388 C212.18017,164.220408 219.598146,163.995918 228.162535,163.995918 L246.055591,163.995918 L246.055591,195.514286 C246.055591,197.736735 246.752431,199.510204 248.370899,201.059184 C250.214153,202.608163 252.079886,203.506122 254.372715,203.506122 C256.463236,203.506122 258.531277,202.608163 260.172223,201.059184 L326.102289,137.797959 C327.720757,136.24898 328.642384,134.47551 328.642384,132.253061 C328.642384,130.030612 327.720757,128.257143 326.102289,126.708163 L260.172223,63.4469388 C258.553756,61.8979592 256.463236,61 254.395194,61 C252.079886,61 250.236632,61.8979592 248.393377,63.4469388 C246.77491,64.9959184 246.07807,66.7693878 246.07807,68.9918367 L246.07807,100.510204 L228.162535,100.510204 C166.863084,100.510204 129.166282,117.167347 115.274437,150.459184 C110.666301,161.54898 108.350993,175.310204 108.350993,191.742857 C108.350993,205.279592 113.903236,223.912245 124.760454,247.438776 C125.00772,248.112245 125.457294,249.010204 126.154134,250.559184 Z" id="Shape" fill="#FFFFFF" transform="translate(218.496689, 160.000000) scale(-1, 1) translate(-218.496689, -160.000000) "></path>
</g>
</g>
<!-- File -->
<g id="file" stroke="#000" stroke-width="25" fill="#FFF" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<path d="M13 24.12v274.76c0 6.16 5.87 11.12 13.17 11.12H239c7.3 0 13.17-4.96 13.17-11.12V136.15S132.6 13 128.37 13H26.17C18.87 13 13 17.96 13 24.12z"/>
<path d="M129.37 13L129 113.9c0 10.58 7.26 19.1 16.27 19.1H249L129.37 13z"/>
</g>
<g id="file-shortcut" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="file-shortcut-group" transform="translate(13.000000, 13.000000)">
<g id="file-shortcut-shape" stroke="#000000" stroke-width="25" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round">
<path d="M0,11.1214886 L0,285.878477 C0,292.039924 5.87498876,296.999983 13.1728373,296.999983 L225.997983,296.999983 C233.295974,296.999983 239.17082,292.039942 239.17082,285.878477 L239.17082,123.145388 C239.17082,123.145388 119.58541,2.84217094e-14 115.369423,2.84217094e-14 L13.1728576,2.84217094e-14 C5.87500907,-1.71479982e-05 0,4.96022995 0,11.1214886 Z" id="rect1171"></path>
<path d="M116.37005,0 L116,100.904964 C116,111.483663 123.258008,120 132.273377,120 L236,120 L116.37005,0 L116.37005,0 Z" id="rect1794"></path>
</g>
<path d="M47.803141,294.093878 C48.4999811,295.177551 48.9495553,296.095918 49.4216083,296.995918 C50.1184484,297.895918 50.5680227,298.630612 51.2873415,299.365306 C52.2089688,300.44898 53.3778619,301 54.7490634,301 C56.1427436,301 57.0643709,300.632653 57.761211,299.916327 C58.4580511,299.365306 58.9076254,298.465306 58.9076254,297.381633 C58.9076254,296.830612 58.9076254,295.930612 58.6828382,294.828571 C58.4355724,293.561224 58.2107852,292.844898 58.2107852,292.477551 C57.7387323,287.757143 57.5139451,283.753061 57.5139451,279.95102 C57.5139451,273.228571 58.4355724,267.057143 59.8292526,261.602041 C61.44772,256.165306 63.5382403,251.610204 66.0783349,247.62449 C68.8656954,243.620408 72.3274172,240.35102 76.4859792,237.44898 C80.6445412,234.546939 85.2751561,232.177551 90.1305582,230.359184 C94.9859603,228.540816 100.76299,227.089796 107.236859,226.006122 C113.710728,225.087755 120.409385,224.371429 127.13052,223.820408 C133.829177,223.453061 141.247152,223.269388 149.811542,223.269388 L167.704598,223.269388 L167.704598,249.057143 C167.704598,250.87551 168.401438,252.326531 170.019905,253.593878 C171.86316,254.861224 173.728893,255.595918 176.021722,255.595918 C178.112242,255.595918 180.180284,254.861224 181.82123,253.593878 L247.751296,201.834694 C249.369763,200.567347 250.291391,199.116327 250.291391,197.297959 C250.291391,195.479592 249.369763,194.028571 247.751296,192.761224 L181.82123,141.002041 C180.202763,139.734694 178.112242,139 176.044201,139 C173.728893,139 171.885639,139.734694 170.042384,141.002041 C168.423917,142.269388 167.727077,143.720408 167.727077,145.538776 L167.727077,171.326531 L149.811542,171.326531 C88.5120908,171.326531 50.8152886,184.955102 36.9234437,212.193878 C32.3153075,221.267347 30,232.526531 30,245.971429 C30,257.046939 35.5522422,272.291837 46.4094607,291.540816 C46.6567266,292.091837 47.1063009,292.826531 47.803141,294.093878 Z" id="Shape-Copy" fill="#000000" fill-rule="nonzero" transform="translate(140.145695, 220.000000) scale(-1, 1) translate(-140.145695, -220.000000) "></path>
</g>
</g>
<!-- Up arrow -->
<g id="up-arrow" transform="translate(-279.22 -208.12)">
<path transform="matrix(.22413 0 0 .12089 335.67 164.35)" stroke-width="0" d="m-194.17 412.01h-28.827-28.827l14.414-24.965 14.414-24.965 14.414 24.965z"/>
</g>
<!-- Down arrow -->
<g id="down-arrow" transform="translate(-279.22 -208.12)">
<path transform="matrix(.22413 0 0 -.12089 335.67 257.93)" stroke-width="0" d="m-194.17 412.01h-28.827-28.827l14.414-24.965 14.414-24.965 14.414 24.965z"/>
</g>
</defs>
</svg>
<header>
<h1>
<a href="">BirdNET-Lite Extractions</a>
</h1>
</header>
<main>
<div class="meta">
<div id="summary">
<span class="meta-item"><b>2</b> directories</span>
<span class="meta-item"><b>2</b> files</span>
<span class="meta-item"><input type="text" placeholder="filter" id="filter" onkeyup='filter()'></span>
</div>
</div>
<div class="listing">
<table aria-describedby="summary">
<thead>
<tr>
<th></th>
<th>
<a href="?sort=namedirfirst&order=desc" class="icon"><svg width="1em" height=".5em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
<a href="?sort=name&order=asc">Name</a>
</th>
<th>
<a href="?sort=size&order=asc">Size</a>
</th>
<th class="hideable">
<a href="?sort=time&order=asc">Modified</a>
</th>
<th class="hideable"></th>
</tr>
</thead>
<tbody>
<tr class="file">
<td></td>
<td>
<a href="./By_Date/">
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 317 259"><use xlink:href="#folder"></use></svg>
<span class="name">By_Date</span>
</a>
</td>
<td data-order="-1">&mdash;</td>
<td class="hideable"><time datetime="2021-09-01T21:40:53Z">09/01/2021 09:40:53 PM +00:00</time></td>
<td class="hideable"></td>
</tr>
<tr class="file">
<td></td>
<td>
<a href="./By_Species/">
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 317 259"><use xlink:href="#folder"></use></svg>
<span class="name">By_Species</span>
</a>
</td>
<td data-order="-1">&mdash;</td>
<td class="hideable"><time datetime="2021-09-01T21:40:48Z">09/01/2021 09:40:48 PM +00:00</time></td>
<td class="hideable"></td>
</tr>
<tr class="file">
<td></td>
<td>
<a href="./Processed">
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 265 323"><use xlink:href="#file-shortcut"></use></svg>
<span class="name">Processed</span>
</a>
</td>
<td data-order="28">28 B</td>
<td class="hideable"><time datetime="2021-09-01T21:40:14Z">09/01/2021 09:40:14 PM +00:00</time></td>
<td class="hideable"></td>
</tr>
<tr class="file">
<td></td>
<td>
<a href="./stream">
<svg width="1.5em" height="1em" version="1.1" viewBox="0 0 265 323"><use xlink:href="#file"></use></svg>
<span class="name">stream</span>
</a>
</td>
<td data-order="0">0 B</td>
<td class="hideable"><time datetime="2021-09-01T21:30:42Z">09/01/2021 09:30:42 PM +00:00</time></td>
<td class="hideable"></td>
</tr>
</tbody>
</table>
</div>
</main>
<footer>
Served with <a rel="noopener noreferrer" href="https://caddyserver.com">Caddy</a>
</footer>
<script>
var filterEl = document.getElementById('filter');
filterEl.focus();
function initFilter() {
if (!filterEl.value) {
var filterParam = new URL(window.location.href).searchParams.get('filter');
if (filterParam) {
filterEl.value = filterParam;
}
}
filter();
}
function filter() {
var q = filterEl.value.trim().toLowerCase();
var elems = document.querySelectorAll('tr.file');
elems.forEach(function(el) {
if (!q) {
el.style.display = '';
return;
}
var nameEl = el.querySelector('.name');
var nameVal = nameEl.textContent.trim().toLowerCase();
if (nameVal.indexOf(q) !== -1) {
el.style.display = '';
} else {
el.style.display = 'none';
}
});
}
function localizeDatetime(e, index, ar) {
if (e.textContent === undefined) {
return;
}
var d = new Date(e.getAttribute('datetime'));
if (isNaN(d)) {
d = new Date(e.textContent);
if (isNaN(d)) {
return;
}
}
e.textContent = d.toLocaleString([], {day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit"});
}
var timeList = Array.prototype.slice.call(document.getElementsByTagName("time"));
timeList.forEach(localizeDatetime);
</script>
</body>
</html>
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=BirdNET-Lite Live Stream
[Service]
Environment=XDG_RUNTIME_DIR=/run/user/1000
Restart=always
Type=simple
RestartSec=3
User=${BIRDNET_USER}
ExecStart=ffmpeg -loglevel 52 -ac 2 -f alsa -i ${REC_CARD} -acodec libmp3lame -b:a 320k -ac 2 -content_type 'audio/mpeg' -f mp3 icecast://source:${ICE_PWD}@localhost:8000/stream -re
[Install]
WantedBy=multi-user.target
+2
View File
@@ -0,0 +1,2 @@
#birdnet
*/5 * * * * /usr/local/bin/species_notifier.sh &> /dev/null
+31
View File
@@ -0,0 +1,31 @@
# This is the tmux.conf
set-option -g base-index 1
set-option -g pane-base-index 1
set-option -g allow-rename off
set-option -g clock-mode-style 12
set-option -g clock-mode-colour brightgreen
set-option -g window-status-current-style bg=brightgreen,fg=black
set-option -g window-status-separator '| |'
set-window-option -g mode-keys vi
set-option -g status-interval 5
set-option -g status 2
set-option -g status-format[1] '#[bg=cyan,fg=black]#[align=left][BirdNET-Lite] #[bg=cyan]#[align=centre]#(vcgencmd measure_temp) #(vcgencmd get_throttled)#[align=right]#(~/BirdNET-Lite/scripts/disk_usage.sh)%l:%M%p'
set-option -g status-style bg=magenta,fg=cyan
set-option -g status-justify centre
set-option -g status-left-length 50
set-option -g status-left '#[fg=brightyellow]arm clock #(vcgencmd measure_clock arm)'
set-option -g status-right '#[fg=brightyellow]%A, %B %e#(~/BirdNET-Lite/scripts/pretty_date.sh)'
bind r source-file /etc/tmux.conf \; display "Reloaded!"
bind -n M-1 select-window -t 1
bind -n M-2 select-window -t 2
bind -n M-3 select-window -t 3
bind -n M-4 select-window -t 4
bind -n M-5 select-window -t 5
bind -n M-6 select-window -t 6
bind -n M-7 select-window -t 7
bind -n M-8 select-window -t 8
bind -n M-TAB next