Update install.sh

This commit is contained in:
Samuel Ortion 2022-08-21 06:13:15 +02:00
parent 0970405102
commit cf032c5f8d
28 changed files with 333 additions and 181 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
var
.venv
.github
.ideas

133
.ideas/birdnet_archive.sh Executable file
View File

@ -0,0 +1,133 @@
#! /usr/bin/env bash
# Compress wav to flac and archive them as zip
# Requires: tar, gzip, ffmpeg
set -e
DEBUG=${DEBUG:-0}
debug() {
[[ $DEBUG -eq 1 ]] && echo "$@"
}
error() {
echo 1>&2 "$@"
}
audio_compress() {
local filepath
filepath="$1"
if [[ "$DRY" -eq 1 ]]; then
debug "Would compress $filepath to flac"
return 0
else
debug "Compressing $filepath"
ffmpeg -i "$filepath" -acodec flac -compression_level 10 "${filepath%.wav}.flac"
fi
}
all_audio_compress() {
local dir
dir="$1"
debug "Compressing all .wav audio in $dir"
for filepath in "$dir"/*.wav; do
if [[ "$DRY" -eq 1 ]]; then
debug "Would convert $filepath to flac and remove it"
else
audio_compress "$filepath"
debug "Removing $filepath"
rm "$filepath"
fi
done
}
record_datetime() {
source_wav=$1
source_base=$(basename "$source_wav" ".wav")
record_date=$(echo "$source_base" | cut -d"_" -f2)
record_time=$(echo "$source_base" | cut -d"_" -f3)
YYYY=$(echo "$record_date" | cut -c 1-4)
MM=$(echo "$record_date" | cut -c 5-6)
DD=$(echo "$record_date" | cut -c 7-8)
HH=$(echo "$record_time" | cut -c 1-2)
MI=$(echo "$record_time" | cut -c 3-4)
SS=$(echo "$record_time" | cut -c 5-6)
SSS="000"
date="$YYYY-$MM-$DD $HH:$MI:$SS.$SSS"
echo "$date"
}
source_wav() {
model_output_dir="$1"
wav=$(basename "$model_output_dir" | rev | cut --complement -d"." -f1 | rev)
echo "$wav"
}
birdnet_archive_older_than() {
local days
days="$1"
local date
date=$(date +"%Y-%m-%d")
local date_pivot
date_pivot=$(date -d "$date + $days days" +"%Y-%m-%d")
move_records_to_archive "$date_pivot"
zip_archives
}
move_records_to_archive() {
local date
date="$1"
local archives_dir
archives_dir="$2"
archive_path="${ARCHIVE_DIR}/$date"
debug "Moving records from $CHUNK_FOLDER/out to $archives_path"
for filepath in $(find "$CHUNK_FOLDER/out/" -name '*.wav.d'); do
wav=$(source_wav "$filepath")
dir=$(dirname "$filepath")
record_datetime=$(record_datetime "$wav")
if [[ "$record_datetime" == "$date" ]]; then
debug "Moving $filepath to $archive_path"
if [[ ! -d "$archive_path" ]]; then
mkdir -p "$archive_path"
fi
mv "$filepath" "$archive_path"
debug "Moving model output directory to archive"
mv "$dir" "$archive_path/"
debug "Moving wav to archive"
mv "$CHUNK_FOLDER/out/$wav" "$archive_path/"
fi
done
}
zip_archives() {
debug "Zipping archives in ${ARCHIVE_DIR}"
for archive_path in $(find "${ARCHIVE_DIR}" -type d); do
archive_name="birdnet_$(basename "$archive_path" | tr '-' '').tar.gz"
if [[ "$DRY" -eq 1 ]]; then
debug "Would zip $archive_path to $archive_name"
else
debug "Zipping $archive_path to $archive_name"
tar -czf "$archive_name" -C "$archive_path" .
debug "Removing temporary archive folder in ${ARCHIVE_DIR}"
rm -rf "$archive_path"
fi
done
}
main() {
config_filepath="./config/birdnet.conf"
[ -f "$config_filepath" ] || {
error "Config file not found: $config_filepath"
exit 1
}
source "$config_filepath"
if [[ -z "CHUNK_FOLDER" ]]; then
error "CHUNK_FOLDER not set in config file"
exit 1
fi
if [[ -z "ARCHIVE_FOLDER" ]]; then
error "ARCHIVE_FOLDER not set in config file"
exit 1
fi
debug "Launch birdnet archive script from $CHUNK_FOLDER to $ARCHIVE_FOLDER"
birdnet_archive_older_than $DAYS_TO_KEEP
}

View File

@ -3,7 +3,7 @@
set -e set -e
# Load config file # Load config file
config_filepath="./config/analyzer.conf" config_filepath="./config/birdnet.conf"
if [ -f "$config_filepath" ]; then if [ -f "$config_filepath" ]; then
source "$config_filepath" source "$config_filepath"

View File

@ -1,7 +1,7 @@
#! /usr/bin/env bash #! /usr/bin/env bash
# Load config file # Load config file
config_filepath="./config/analyzer.conf" config_filepath="./config/birdnet.conf"
if [ -f "$config_filepath" ]; then if [ -f "$config_filepath" ]; then
source "$config_filepath" source "$config_filepath"

View File

@ -170,7 +170,7 @@ Launch and enable icecast:
sudo systemctl enable --now icecast2 sudo systemctl enable --now icecast2
``` ```
Adapt `config/analyzer.conf` to this configuration: Adapt `config/birdnet.conf` to this configuration:
```conf ```conf
ICECAST_USER=source ICECAST_USER=source

4
TODO
View File

@ -1,5 +1,5 @@
- Fix clean script
- Fix service manager - Fix service manager
- Add docker support - Add docker support
- Species i18n - Species i18n
- File purge policy - File purge policy
- Add and test RTSP support

View File

@ -8,7 +8,7 @@ debug() {
fi fi
} }
config_filepath="./config/analyzer.conf" config_filepath="./config/birdnet.conf"
if [ -f "$config_filepath" ]; then if [ -f "$config_filepath" ]; then
source "$config_filepath" source "$config_filepath"

View File

@ -13,7 +13,7 @@ debug() {
fi fi
} }
config_filepath="./config/analyzer.conf" config_filepath="./config/birdnet.conf"
if [ -f "$config_filepath" ]; then if [ -f "$config_filepath" ]; then
source "$config_filepath" source "$config_filepath"

View File

@ -3,7 +3,7 @@
set -e set -e
# set -x # set -x
config_filepath="./config/analyzer.conf" config_filepath="./config/birdnet.conf"
if [ -f "$config_filepath" ]; then if [ -f "$config_filepath" ]; then
source "$config_filepath" source "$config_filepath"

View File

@ -16,7 +16,7 @@ debug() {
source ./daemon/database/scripts/database.sh source ./daemon/database/scripts/database.sh
# Load config # Load config
source ./config/analyzer.conf source ./config/birdnet.conf
# Check config # Check config
if [[ -z ${CHUNK_FOLDER} ]]; then if [[ -z ${CHUNK_FOLDER} ]]; then
echo "CHUNK_FOLDER is not set" echo "CHUNK_FOLDER is not set"

View File

@ -1,6 +1,6 @@
#! /usr/bin/env bash #! /usr/bin/env bash
DEBUG=${DEBUG:-0} DEBUG=${DEBUG:-1}
export PULSE_RUNTIME_PATH="/run/user/$(id -u)/pulse/" export PULSE_RUNTIME_PATH="/run/user/$(id -u)/pulse/"
@ -23,7 +23,7 @@ record_loop() {
DURATION=$2 DURATION=$2
debug "New recording loop." debug "New recording loop."
while true; do while true; do
record $DEVICE $DURATION record_device $DEVICE $DURATION
done done
} }
@ -33,7 +33,7 @@ record_stream() {
local STREAM=$1 local STREAM=$1
local DURATION=$2 local DURATION=$2
local debug "Recording from $STREAM for $DURATION seconds" local debug "Recording from $STREAM for $DURATION seconds"
ffmpeg $FFMPEG_OPTIONS -f -i ${DEVICE} -t ${DURATION} file:${CHUNK_FOLDER}/in/birdnet_$(date "+%Y%m%d_%H%M%S").wav ffmpeg $FFMPEG_OPTIONS -f -i ${DEVICE} -t ${DURATION} file:${CHUNK_FOLDER}/in/birdnet_$(date "+%Y%m%d_%H%M%S").wav
} }
record_device() { record_device() {
@ -43,7 +43,7 @@ record_device() {
ffmpeg $FFMPEG_OPTIONS -f pulse -i ${DEVICE} -t ${DURATION} -af "volume=$RECORDING_AMPLIFY" file:${CHUNK_FOLDER}/in/birdnet_$(date "+%Y%m%d_%H%M%S").wav ffmpeg $FFMPEG_OPTIONS -f pulse -i ${DEVICE} -t ${DURATION} -af "volume=$RECORDING_AMPLIFY" file:${CHUNK_FOLDER}/in/birdnet_$(date "+%Y%m%d_%H%M%S").wav
} }
config_filepath="./config/analyzer.conf" config_filepath="./config/birdnet.conf"
if [ -f "$config_filepath" ]; then if [ -f "$config_filepath" ]; then
source "$config_filepath" source "$config_filepath"
@ -56,18 +56,18 @@ check_folder
[ -z $RECORDING_DURATION ] && RECORDING_DURATION=15 [ -z $RECORDING_DURATION ] && RECORDING_DURATION=15
if [[ -z $AUDIO_DEVICE ]]; then
echo "AUDIO_DEVICE is not set"
exit 1
fi
if [[ $AUDIO_RECORDING = "true" ]]; then if [[ $AUDIO_RECORDING = "true" ]]; then
record_loop $AUDIO_DEVICE $RECORDING_DURATION & debug "Recording with on board device"
if [[ -z $AUDIO_DEVICE ]]; then
echo "AUDIO_DEVICE is not set"
exit 1
fi
record_loop $AUDIO_DEVICE $RECORDING_DURATION
fi fi
if [[ $AUDIO_STATIONS = "true" ]]; then if [[ $AUDIO_STATIONS = "true" ]]; then
for station in $(ls ./config/stations/*.conf); do for station in $(ls ./config/stations/*.conf); do
source $station source $station
record_stream $STATION_URL $RECORDING_DURATION & record_stream $STATION_URL $RECORDING_DURATION
done done
fi fi

View File

@ -18,7 +18,7 @@ stream() {
-f mp3 "icecast://source:${ICECAST_PASSWORD}@${ICECAST_HOST}:${ICECAST_PORT}/${ICECAST_MOUNT}" -listen 1 -f mp3 "icecast://source:${ICECAST_PASSWORD}@${ICECAST_HOST}:${ICECAST_PORT}/${ICECAST_MOUNT}" -listen 1
} }
config_filepath="./config/analyzer.conf" config_filepath="./config/birdnet.conf"
if [ -f "$config_filepath" ]; then if [ -f "$config_filepath" ]; then
source "$config_filepath" source "$config_filepath"

View File

@ -8,7 +8,7 @@ debug() {
} }
# Load config file # Load config file
config_filepath="./config/analyzer.conf" config_filepath="./config/birdnet.conf"
if [ -f "$config_filepath" ]; then if [ -f "$config_filepath" ]; then
source "$config_filepath" source "$config_filepath"

View File

@ -1,7 +1,7 @@
#! /usr/bin/env bash #! /usr/bin/env bash
# Load config file # Load config file
config_filepath="./config/analyzer.conf" config_filepath="./config/birdnet.conf"
if [ -f "$config_filepath" ]; then if [ -f "$config_filepath" ]; then
source "$config_filepath" source "$config_filepath"

View File

@ -7,7 +7,7 @@ verbose = False
"""Load config""" """Load config"""
def load_conf(): def load_conf():
with open("./config/analyzer.conf", "r") as f: with open("./config/birdnet.conf", "r") as f:
conf = f.readlines() conf = f.readlines()
res = dict(map(str.strip, sub.split('=', 1)) for sub in conf if '=' in sub) res = dict(map(str.strip, sub.split('=', 1)) for sub in conf if '=' in sub)
return res return res

View File

@ -3,7 +3,7 @@
set -e set -e
source ./config/analyzer.conf source ./config/birdnet.conf
# Create database in case it was not created yet # Create database in case it was not created yet
./daemon/database/scripts/create.sh ./daemon/database/scripts/create.sh
@ -23,7 +23,7 @@ get_taxon_id() {
} }
insert_taxon() { insert_taxon() {
query $DATABASE "INSERT INTO taxon (scientific_name, common_name) VALUES (\"$1\", \"$2\")" query "INSERT INTO taxon (scientific_name, common_name) VALUES (\"$1\", \"$2\")"
} }
insert_location() { insert_location() {

View File

@ -1,8 +1,6 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
from curses import def_prog_mode
import sqlite3 import sqlite3
from xml.sax.handler import feature_external_ges
import pandas as pd import pandas as pd
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm from matplotlib.colors import LogNorm
@ -12,109 +10,83 @@ from datetime import datetime
CONFIG = { CONFIG = {
"readings": 10, "readings": 10,
"palette": "Greens", "palette": "Greens",
"db": "./var/db.sqlite",
"date": "2022-08-14"
} }
db = None db = sqlite3.connect(CONFIG['db'])
def get_database():
global db
if db is None:
db = sqlite3.connect('/home/ortion/Desktop/db.sqlite')
return db
df = pd.read_sql_query("""SELECT common_name, date, location_id, confidence
FROM observation
INNER JOIN taxon
ON observation.taxon_id = taxon.taxon_id""", db)
df['date'] = pd.to_datetime(df['date'])
df['hour'] = df['date'].dt.hour
df['date'] = df['date'].dt.date
df['date'] = df['date'].astype(str)
df_on_date = df[df['date'] == CONFIG['date']]
top_on_date = (df_on_date['common_name'].value_counts()[:CONFIG['readings']])
def get_detection_hourly(date): df_top_on_date = df_on_date[df_on_date['common_name'].isin(top_on_date.index)]
db = get_database()
df = pd.read_sql_query("""SELECT common_name, date, location_id, confidence
FROM observation
INNER JOIN taxon
ON observation.taxon_id = taxon.taxon_id""", db)
df['date'] = pd.to_datetime(df['date']) # Create a figure with 2 subplots
df['hour'] = df['date'].dt.hour fig, axs = plt.subplots(1, 2, figsize=(15, 4), gridspec_kw=dict(
df['date'] = df['date'].dt.date width_ratios=[3, 6]))
df['date'] = df['date'].astype(str) plt.subplots_adjust(left=None, bottom=None, right=None,
top=None, wspace=0, hspace=0)
df_on_date = df[df['date'] == date] # Get species frequencies
return df_on_date frequencies_order = pd.value_counts(df['common_name']).iloc[:CONFIG['readings']].index
# Get min max confidences
confidence_minmax = df_top_on_date.groupby('common_name')['confidence'].max()
confidence_minmax = confidence_minmax.reindex(frequencies_order)
# Norm values for color palette
norm = plt.Normalize(confidence_minmax.values.min(),
confidence_minmax.values.max())
colors = plt.cm.Greens(norm(confidence_minmax))
plot = sns.countplot(y='common_name', data=df_top_on_date, palette=colors, order=frequencies_order, ax=axs[0])
def get_top_species(df, limit=10): plot.set(ylabel=None)
return df['common_name'].value_counts()[:CONFIG['readings']] plot.set(xlabel="Detections")
heat = pd.crosstab(df_top_on_date['common_name'], df_top_on_date['hour'])
# Order heatmap Birds by frequency of occurrance
heat.index = pd.CategoricalIndex(heat.index, categories=frequencies_order)
heat.sort_index(level=0, inplace=True)
def get_top_detections(df, limit=10): hours_in_day = pd.Series(data=range(0, 24))
df_top_species = get_top_species(df, limit=limit) heat_frame = pd.DataFrame(data=0, index=heat.index, columns=hours_in_day)
return df[df['common_name'].isin(df_top_species.index)] heat = (heat + heat_frame).fillna(0)
# Generate heatmap plot
plot = sns.heatmap(
heat,
norm=LogNorm(),
annot=True,
annot_kws={
"fontsize": 7
},
fmt="g",
cmap=CONFIG['palette'],
square=False,
cbar=False,
linewidth=0.5,
linecolor="Grey",
ax=axs[1],
yticklabels=False)
plot.set_xticklabels(plot.get_xticklabels(), rotation=0, size=7)
def get_frequence_order(df, limit=10): for _, spine in plot.spines.items():
pd.value_counts(df['common_name']).iloc[:limit] spine.set_visible(True)
def presence_chart(date, filename): plot.set(ylabel=None)
df_detections = get_detection_hourly(date) plot.set(xlabel="Hour of day")
df_top_detections = get_top_detections(df_detections, limit=CONFIG['readings']) fig.subplots_adjust(top=0.9)
fig, axs = plt.subplots(1, 2, figsize=(15, 4), gridspec_kw=dict( plt.suptitle(f"Top {CONFIG['readings']} species (Updated on {datetime.now().strftime('%Y/%m-%d %H:%M')})")
width_ratios=[3, 6]))
plt.subplots_adjust(left=None, bottom=None, right=None,
top=None, wspace=0, hspace=0)
frequencies_order = get_frequence_order(df_detections, limit=CONFIG["readings"]) plt.savefig(f"./var/charts/chart_{CONFIG['date']}.png", dpi=300)
# Get min max confidences plt.close()
confidence_minmax = df_detections.groupby('common_name')['confidence'].max()
# Norm values for color palette
norm = plt.Normalize(confidence_minmax.values.min(),
confidence_minmax.values.max())
colors = plt.cm.Greens(norm(confidence_minmax))
plot = sns.countplot(y='common_name', data=df_top_detections, palette=colors, order=frequencies_order, ax=axs[0])
plot.set(ylabel=None) db.close()
plot.set(xlabel="Detections")
heat = pd.crosstab(df_top_detections['common_name'], df_top_detections['hour'])
# Order heatmap Birds by frequency of occurrance
heat.index = pd.CategoricalIndex(heat.index, categories=frequencies_order)
heat.sort_index(level=0, inplace=True)
hours_in_day = pd.Series(data=range(0, 24))
heat_frame = pd.DataFrame(data=0, index=heat.index, columns=hours_in_day)
heat = (heat + heat_frame).fillna(0)
# Generate heatmap plot
plot = sns.heatmap(
heat,
norm=LogNorm(),
annot=True,
annot_kws={
"fontsize": 7
},
fmt="g",
cmap=CONFIG['palette'],
square=False,
cbar=False,
linewidth=0.5,
linecolor="Grey",
ax=axs[1],
yticklabels=False
)
plot.set_xticklabels(plot.get_xticklabels(), rotation=0, size=7)
for _, spine in plot.spines.items():
spine.set_visible(True)
plot.set(ylabel=None)
plot.set(xlabel="Hour of day")
fig.subplots_adjust(top=0.9)
plt.suptitle(f"Top {CONFIG['readings']} species (Updated on {datetime.now().strftime('%Y/%m-%d %H:%M')})")
plt.savefig(filename)
plt.close()
def main():
date = datetime.now().strftime('%Y%m%d')
presence_chart(date, f'./var/charts/chart_{date}.png')
# print(get_top_detections(get_detection_hourly(date), limit=10))
if not db is None:
db.close()
if __name__ == "__main__":
main()

View File

@ -10,11 +10,13 @@ WORKDIR /home/birdnet
# Upgrade system # Upgrade system
RUN apt-get update && apt-get upgrade -y RUN apt-get update && apt-get upgrade -y
# Install curl # Install sudo
RUN apt-get install -y \ RUN apt-get install -y \
curl \ sudo \
sudo git
RUN curl -sL https://raw.githubusercontent.com/UncleSamulus/BirdNET-stream/master/install.sh | bash COPY ./install.sh install.sh
RUN bash ./install.sh
USER birdnet USER birdnet

View File

@ -9,7 +9,7 @@
```bash ```bash
git clone https://github.com/UncleSamulus/BirdNET-stream.git git clone https://github.com/UncleSamulus/BirdNET-stream.git
cd ./BirdNET-stream/docker/all cd ./BirdNET-stream/docker/all
docker build -t "birdnet_all:latest" . docker build -t "birdnet_all:latest" -f ./docker/all/Dockerfile .
``` ```
If `docker` command does not work because of unsufficient permissions, you could add your user to `docker` group: If `docker` command does not work because of unsufficient permissions, you could add your user to `docker` group:

View File

@ -5,13 +5,13 @@ set -e
DEBUG=${DEBUG:-0} DEBUG=${DEBUG:-0}
REQUIREMENTS="git ffmpeg python3-pip python3-dev" REQUIREMENTS="git ffmpeg python3 python3-pip python3-dev"
REPOSITORY=${REPOSITORY:-https://github.com/UncleSamulus/BirdNET-stream.git} REPOSITORY=${REPOSITORY:-https://github.com/UncleSamulus/BirdNET-stream.git}
WORKDIR="$(pwd)/BirdNET-stream"
PYTHON_VENV="./.venv/birdnet-stream"
debug() { debug() {
if [ $DEBUG -eq 1 ]; then [[ $DEBUG -eq 1 ]] && echo "$@"
echo "$1"
fi
} }
install_requirements() { install_requirements() {
@ -19,52 +19,53 @@ install_requirements() {
# Install requirements # Install requirements
missing_requirements="" missing_requirements=""
for requirement in $requirements; do for requirement in $requirements; do
if ! dpkg -s $requirement >/dev/null 2>&1; then if ! dpkg -s "$requirement" >/dev/null 2>&1; then
missing_requirements="$missing_requirements $requirement" missing_requirements="$missing_requirements $requirement"
fi fi
done done
if [ -n "$missing_requirements" ]; then if [[ -n "$missing_requirements" ]]; then
debug "Installing missing requirements: $missing_requirements" debug "Installing missing requirements: $missing_requirements"
sudo apt-get install -y $missing_requirements sudo apt-get install -y "$missing_requirements"
fi fi
} }
# Install BirdNET-stream # Install BirdNET-stream
install_birdnetstream() { install_birdnetstream() {
# Check if repo is not already installed # Check if repo is not already installed
workdir=$(pwd) if [[ -d "$DIR" ]]; then
if [ -d "$workdir/BirdNET-stream" ]; then debug "BirdNET-stream is already installed, use update script (not implemented yet)"
debug "BirdNET-stream is already installed" exit 1
else else
debug "Installing BirdNET-stream"
debug "Creating BirdNET-stream directory"
mkdir -p "$WORKDIR"
# Clone BirdNET-stream # Clone BirdNET-stream
cd "$WORKDIR"
debug "Cloning BirdNET-stream from $REPOSITORY" debug "Cloning BirdNET-stream from $REPOSITORY"
git clone --recurse-submodules $REPOSITORY git clone --recurse-submodules "$REPOSITORY" .
# Install BirdNET-stream debug "Creating python3 virtual environment $PYTHON_VENV"
python3 -m venv $PYTHON_VENV
debug "Activating $PYTHON_VENV"
source "$PYTHON_VENV/bin/activate"
debug "Installing python packages"
pip3 install -U pip
pip3 install -r requirements.txt
debug "Creating ./var directory"
mkdir -p ./var/{charts,chunks/{in,out}}
fi fi
cd BirdNET-stream
debug "Creating python3 virtual environment '$PYTHON_VENV'"
python3 -m venv $PYTHON_VENV
debug "Activating $PYTHON_VENV"
source .venv/birdnet-stream/bin/activate
debug "Installing python packages"
pip install -U pip
pip install -r requirements.txt
} }
# Install systemd services # Install systemd services
install_birdnetstream_services() { install_birdnetstream_services() {
cd BirdNET-stream GROUP=birdnet
DIR=$(pwd)
GROUP=$USER
debug "Setting up BirdNET stream systemd services" debug "Setting up BirdNET stream systemd services"
services="birdnet_recording.service birdnet_analyzis.service birdnet_miner.timer birdnet_miner.service birdnet_plotter.service birdnet_plotter.timer" services="birdnet_recording.service birdnet_analyzis.service birdnet_miner.timer birdnet_miner.service birdnet_plotter.service birdnet_plotter.timer"
read -r -a services_array <<<"$services" read -r -a services_array <<< "$services"
for service in "${services_array[@]}"; do
for service in ${services_array[@]}; do sudo cp "daemon/systemd/templates/$service" "/etc/systemd/system/"
sudo cp daemon/systemd/templates/$service /etc/systemd/system/
variables="DIR USER GROUP" variables="DIR USER GROUP"
for variable in $variables; do for variable in $variables; do
sudo sed -i "s|<$variable>|${!variable}|g" /etc/systemd/system/$service sudo sed -i "s|<$variable>|${!variable}|g" "/etc/systemd/system/$service"
done done
done done
sudo systemctl daemon-reload sudo systemctl daemon-reload
@ -90,15 +91,17 @@ install_php8() {
} }
install_composer() { install_composer() {
cd /tmp
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"\nphp -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"\nphp composer-setup.php\nphp -r "unlink('composer-setup.php');" php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"\nphp -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"\nphp composer-setup.php\nphp -r "unlink('composer-setup.php');"
sudo mv /composer.phar /usr/local/bin/composer sudo mv ./composer.phar /usr/local/bin/composer
cd -
} }
install_nodejs() { install_nodejs() {
# Install nodejs # Install nodejs
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" export NVM_DIR="$([[ -z "${XDG_CONFIG_HOME-}" ]] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [[ -s "$NVM_DIR/nvm.sh" ]] && \. "$NVM_DIR/nvm.sh" # This loads nvm
nvm install 16 nvm install 16
nvm use 16 nvm use 16
install_requirements "npm" install_requirements "npm"
@ -116,30 +119,60 @@ install_web_interface() {
# Install nodejs 16 # Install nodejs 16
install_nodejs install_nodejs
# Install Symfony web app # Install Symfony web app
cd BirdNET-stream cd "$WORKDIR"
cd www cd www
debug "Creating nginx configuration" debug "Creating nginx configuration"
cp nginx.conf /etc/nginx/sites-available/birdnet-stream.conf sudo cp nginx.conf /etc/nginx/sites-available/birdnet-stream.conf
sudo mkdir /var/log/nginx/birdnet/ sudo mkdir /var/log/nginx/birdnet/
echo "Info: Please edit /etc/nginx/sites-available/birdnet-stream.conf to set the correct server name and paths" sudo ln -s /etc/nginx/sites-available/birdnet-stream.conf /etc/nginx/sites-available/birdnet-stream.conf
sudo ln -s /etc/nginx/sites-available/birdnet-stream.conf /etc/nginx/sites-enabled/birdnet-stream.conf debug "Info: Please edit /etc/nginx/sites-available/birdnet-stream.conf to set the correct server name and paths"
sudo systemctl enable --now nginx sudo systemctl enable --now nginx
sudo systemctl restart nginx sudo systemctl restart nginx
debug "Retrieving composer dependencies" debug "Retrieving composer dependencies"
composer install composer install
debug "PHP dependencies installed"
debug "Installing nodejs dependencies" debug "Installing nodejs dependencies"
yarn install yarn install
debug "npm dependencies installed"
debug "Building assets" debug "Building assets"
yarn build yarn build
debug "Webpack assets built"
debug "Web interface is available" debug "Web interface is available"
debug "Please restart nginx after double check of /etc/nginx/sites-available/birdnet-stream.conf" debug "Please restart nginx after double check of /etc/nginx/sites-available/birdnet-stream.conf"
} }
change_value() {
local variable_name
variable_name="$1"
local variable_new_value
variable_new_value="$2"
local variable_filepath="$3"
sed -i "s|$variable_name=.*|$variable_name=\"$variable_new_value\"|g" "$variable_filepath"
}
install_config() {
debug "Updating config"
cd "$WORKDIR"
cp ./config/birdnet.conf.example ./config/birdnet.conf
config_filepath="$WORKDIR/config/birdnet.conf"
change_value "DIR" "$WORKDIR" "$config_filepath"
change_value "PYTHON_VENV" "$PYTHON_VENV" "$config_filepath"
change_value "AUDIO_RECORDING" "true" "$config_filepath"
source "$config_filepath"
cd www
debug "Setup webapp .env"
cp .env.local.example .env.local
change_value "RECORDS_DIR" "$CHUNKS_FOLDER" ".env.local"
}
main() { main() {
install_requirements $REQUIREMENTS install_requirements "$REQUIREMENTS"
install_birdnetstream install_birdnetstream
install_birdnetstream_services install_birdnetstream_services
install_web_interface install_web_interface
install_config
update_permissions "$WORKDIR"
debug "Installation done"
} }
main main

View File

@ -1,15 +1,13 @@
#! /usr/bin/env bash #! /usr/bin/env bash
# Fix permissions on BirdNET-stream files when messed up
set -e set -e
DEBUG=${DEBUG:-1} DEBUG=${DEBUG:-0}
debug() { debug() {
if [ $DEBUG -eq 1 ]; then [ $DEBUG -eq 1 ] && echo "$@"
echo "$1"
fi
} }
config_filepath="./config/analyzer.conf" config_filepath="./config/birdnet.conf"
if [ -f "$config_filepath" ]; then if [ -f "$config_filepath" ]; then
source "$config_filepath" source "$config_filepath"
@ -18,7 +16,6 @@ else
exit 1 exit 1
fi fi
GROUP=birdnet GROUP=birdnet
sudo chown -R $USER:$GROUP $CHUNK_FOLDER sudo chown -R $USER:$GROUP $CHUNK_FOLDER

2
www/package-lock.json generated
View File

@ -16,6 +16,7 @@
"core-js": "^3.23.0", "core-js": "^3.23.0",
"git-revision-webpack-plugin": "^5.0.0", "git-revision-webpack-plugin": "^5.0.0",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"webpack": "^5.74.0",
"webpack-notifier": "^1.15.0" "webpack-notifier": "^1.15.0"
} }
}, },
@ -7630,7 +7631,6 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
"integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.3", "@types/eslint-scope": "^3.7.3",
"@types/estree": "^0.0.51", "@types/estree": "^0.0.51",

View File

@ -6,6 +6,7 @@
"core-js": "^3.23.0", "core-js": "^3.23.0",
"git-revision-webpack-plugin": "^5.0.0", "git-revision-webpack-plugin": "^5.0.0",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"webpack": "^5.74.0",
"webpack-notifier": "^1.15.0" "webpack-notifier": "^1.15.0"
}, },
"license": "UNLICENSED", "license": "UNLICENSED",

View File

@ -10,8 +10,8 @@ class DisksController extends AbstractController
{ {
/** /**
* @Route("/disks/", name="disks_index") * @Route("/disks/", name="disks")
* @Route("{_locale}/disks/", name="disks_index_i18n") * @Route("{_locale}/disks/", name="disks_i18n")
*/ */
public function index() { public function index() {
return $this->render('disks/index.html.twig', [ return $this->render('disks/index.html.twig', [

View File

@ -1,3 +1,4 @@
{% extends "base.html.twig" %}
{% block content %} {% block content %}
<h2>{{ "Disk usage"|trans }}</h2> <h2>{{ "Disk usage"|trans }}</h2>
<div class="disk"> <div class="disk">

View File

@ -38,14 +38,23 @@
} %} } %}
</ul> </ul>
</li> </li>
{% include 'utils/nav-item.html.twig' with { <li class="dropdown">
route: 'logs', <span class="dropdown-toggle">{{ "Tools"|trans }}</span>
text: 'View Logs'|trans <ul class="dropdown-content">
} %} {% include 'utils/nav-item.html.twig' with {
{% include 'utils/nav-item.html.twig' with { route: 'logs',
route: 'services_status', text: 'View Logs'|trans
text: 'Status'|trans } %}
} %} {% include 'utils/nav-item.html.twig' with {
route: 'services_status',
text: 'Status'|trans
} %}
{% include "utils/nav-item.html.twig" with {
route: 'disks',
text: 'Disks'|trans
} %}
</ul>
</li>
</ul> </ul>
</div> </div>
</nav> </nav>

View File

@ -11,7 +11,7 @@
</trans-unit> </trans-unit>
<trans-unit id="VAw_dLX" resname="BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI."> <trans-unit id="VAw_dLX" resname="BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI.">
<source>BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI.</source> <source>BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI.</source>
<target>BirdNET-stream est un logiciel d'analyse en temps réel de l'environement sonore basé sur BirdNET.</target> <target>BirdNET-stream est un logiciel d'analyse en temps réel de l'environnement sonore basé sur BirdNET.</target>
</trans-unit> </trans-unit>
<trans-unit id="vvz1r3A" resname="It aims to be able to run on any computer with a microphone."> <trans-unit id="vvz1r3A" resname="It aims to be able to run on any computer with a microphone.">
<source>It aims to be able to run on any computer with a microphone.</source> <source>It aims to be able to run on any computer with a microphone.</source>

View File

@ -4252,7 +4252,7 @@
"resolved" "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" "resolved" "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz"
"version" "3.2.3" "version" "3.2.3"
"webpack@^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "webpack@^4.0.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", "webpack@^5.0.0", "webpack@^5.1.0", "webpack@^5.72", "webpack@>=2", "webpack@>=4.0.0 <6.0.0", "webpack@>=5.0.0", "webpack@4.x.x || 5.x.x": "webpack@^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "webpack@^4.0.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", "webpack@^5.0.0", "webpack@^5.1.0", "webpack@^5.72", "webpack@^5.74.0", "webpack@>=2", "webpack@>=4.0.0 <6.0.0", "webpack@>=5.0.0", "webpack@4.x.x || 5.x.x":
"integrity" "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==" "integrity" "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA=="
"resolved" "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz" "resolved" "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz"
"version" "5.74.0" "version" "5.74.0"