dev #1
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@ -0,0 +1,4 @@
|
||||
var
|
||||
.venv
|
||||
.github
|
||||
.ideas
|
133
.ideas/birdnet_archive.sh
Executable file
133
.ideas/birdnet_archive.sh
Executable 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
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
set -e
|
||||
|
||||
# Load config file
|
||||
config_filepath="./config/analyzer.conf"
|
||||
config_filepath="./config/birdnet.conf"
|
||||
|
||||
if [ -f "$config_filepath" ]; then
|
||||
source "$config_filepath"
|
||||
|
@ -1,7 +1,7 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# Load config file
|
||||
config_filepath="./config/analyzer.conf"
|
||||
config_filepath="./config/birdnet.conf"
|
||||
|
||||
if [ -f "$config_filepath" ]; then
|
||||
source "$config_filepath"
|
||||
|
@ -170,7 +170,7 @@ Launch and enable icecast:
|
||||
sudo systemctl enable --now icecast2
|
||||
```
|
||||
|
||||
Adapt `config/analyzer.conf` to this configuration:
|
||||
Adapt `config/birdnet.conf` to this configuration:
|
||||
|
||||
```conf
|
||||
ICECAST_USER=source
|
||||
|
4
TODO
4
TODO
@ -1,5 +1,5 @@
|
||||
- Fix clean script
|
||||
- Fix service manager
|
||||
- Add docker support
|
||||
- Species i18n
|
||||
- File purge policy
|
||||
- File purge policy
|
||||
- Add and test RTSP support
|
@ -8,7 +8,7 @@ debug() {
|
||||
fi
|
||||
}
|
||||
|
||||
config_filepath="./config/analyzer.conf"
|
||||
config_filepath="./config/birdnet.conf"
|
||||
|
||||
if [ -f "$config_filepath" ]; then
|
||||
source "$config_filepath"
|
||||
|
@ -13,7 +13,7 @@ debug() {
|
||||
fi
|
||||
}
|
||||
|
||||
config_filepath="./config/analyzer.conf"
|
||||
config_filepath="./config/birdnet.conf"
|
||||
|
||||
if [ -f "$config_filepath" ]; then
|
||||
source "$config_filepath"
|
||||
|
@ -3,7 +3,7 @@
|
||||
set -e
|
||||
# set -x
|
||||
|
||||
config_filepath="./config/analyzer.conf"
|
||||
config_filepath="./config/birdnet.conf"
|
||||
|
||||
if [ -f "$config_filepath" ]; then
|
||||
source "$config_filepath"
|
||||
|
@ -16,7 +16,7 @@ debug() {
|
||||
source ./daemon/database/scripts/database.sh
|
||||
|
||||
# Load config
|
||||
source ./config/analyzer.conf
|
||||
source ./config/birdnet.conf
|
||||
# Check config
|
||||
if [[ -z ${CHUNK_FOLDER} ]]; then
|
||||
echo "CHUNK_FOLDER is not set"
|
||||
|
@ -1,6 +1,6 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
DEBUG=${DEBUG:-0}
|
||||
DEBUG=${DEBUG:-1}
|
||||
|
||||
export PULSE_RUNTIME_PATH="/run/user/$(id -u)/pulse/"
|
||||
|
||||
@ -23,7 +23,7 @@ record_loop() {
|
||||
DURATION=$2
|
||||
debug "New recording loop."
|
||||
while true; do
|
||||
record $DEVICE $DURATION
|
||||
record_device $DEVICE $DURATION
|
||||
done
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ record_stream() {
|
||||
local STREAM=$1
|
||||
local DURATION=$2
|
||||
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() {
|
||||
@ -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
|
||||
}
|
||||
|
||||
config_filepath="./config/analyzer.conf"
|
||||
config_filepath="./config/birdnet.conf"
|
||||
|
||||
if [ -f "$config_filepath" ]; then
|
||||
source "$config_filepath"
|
||||
@ -56,18 +56,18 @@ check_folder
|
||||
|
||||
[ -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
|
||||
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
|
||||
|
||||
if [[ $AUDIO_STATIONS = "true" ]]; then
|
||||
for station in $(ls ./config/stations/*.conf); do
|
||||
source $station
|
||||
record_stream $STATION_URL $RECORDING_DURATION &
|
||||
record_stream $STATION_URL $RECORDING_DURATION
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
@ -18,7 +18,7 @@ stream() {
|
||||
-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
|
||||
source "$config_filepath"
|
||||
|
@ -8,7 +8,7 @@ debug() {
|
||||
}
|
||||
|
||||
# Load config file
|
||||
config_filepath="./config/analyzer.conf"
|
||||
config_filepath="./config/birdnet.conf"
|
||||
|
||||
if [ -f "$config_filepath" ]; then
|
||||
source "$config_filepath"
|
||||
|
@ -1,7 +1,7 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# Load config file
|
||||
config_filepath="./config/analyzer.conf"
|
||||
config_filepath="./config/birdnet.conf"
|
||||
|
||||
if [ -f "$config_filepath" ]; then
|
||||
source "$config_filepath"
|
||||
|
@ -7,7 +7,7 @@ verbose = False
|
||||
|
||||
"""Load config"""
|
||||
def load_conf():
|
||||
with open("./config/analyzer.conf", "r") as f:
|
||||
with open("./config/birdnet.conf", "r") as f:
|
||||
conf = f.readlines()
|
||||
res = dict(map(str.strip, sub.split('=', 1)) for sub in conf if '=' in sub)
|
||||
return res
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
source ./config/analyzer.conf
|
||||
source ./config/birdnet.conf
|
||||
|
||||
# Create database in case it was not created yet
|
||||
./daemon/database/scripts/create.sh
|
||||
@ -23,7 +23,7 @@ get_taxon_id() {
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -1,8 +1,6 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
from curses import def_prog_mode
|
||||
import sqlite3
|
||||
from xml.sax.handler import feature_external_ges
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.colors import LogNorm
|
||||
@ -12,109 +10,83 @@ from datetime import datetime
|
||||
CONFIG = {
|
||||
"readings": 10,
|
||||
"palette": "Greens",
|
||||
"db": "./var/db.sqlite",
|
||||
"date": "2022-08-14"
|
||||
}
|
||||
|
||||
db = None
|
||||
def get_database():
|
||||
global db
|
||||
if db is None:
|
||||
db = sqlite3.connect('/home/ortion/Desktop/db.sqlite')
|
||||
return db
|
||||
db = sqlite3.connect(CONFIG['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):
|
||||
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_top_on_date = df_on_date[df_on_date['common_name'].isin(top_on_date.index)]
|
||||
|
||||
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)
|
||||
# Create a figure with 2 subplots
|
||||
fig, axs = plt.subplots(1, 2, figsize=(15, 4), gridspec_kw=dict(
|
||||
width_ratios=[3, 6]))
|
||||
plt.subplots_adjust(left=None, bottom=None, right=None,
|
||||
top=None, wspace=0, hspace=0)
|
||||
|
||||
df_on_date = df[df['date'] == date]
|
||||
return df_on_date
|
||||
# Get species frequencies
|
||||
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):
|
||||
return df['common_name'].value_counts()[:CONFIG['readings']]
|
||||
plot.set(ylabel=None)
|
||||
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):
|
||||
df_top_species = get_top_species(df, limit=limit)
|
||||
return df[df['common_name'].isin(df_top_species.index)]
|
||||
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)
|
||||
|
||||
def get_frequence_order(df, limit=10):
|
||||
pd.value_counts(df['common_name']).iloc[:limit]
|
||||
for _, spine in plot.spines.items():
|
||||
spine.set_visible(True)
|
||||
|
||||
def presence_chart(date, filename):
|
||||
df_detections = get_detection_hourly(date)
|
||||
df_top_detections = get_top_detections(df_detections, limit=CONFIG['readings'])
|
||||
fig, axs = plt.subplots(1, 2, figsize=(15, 4), gridspec_kw=dict(
|
||||
width_ratios=[3, 6]))
|
||||
plt.subplots_adjust(left=None, bottom=None, right=None,
|
||||
top=None, wspace=0, hspace=0)
|
||||
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')})")
|
||||
|
||||
frequencies_order = get_frequence_order(df_detections, limit=CONFIG["readings"])
|
||||
# Get min max confidences
|
||||
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])
|
||||
plt.savefig(f"./var/charts/chart_{CONFIG['date']}.png", dpi=300)
|
||||
plt.close()
|
||||
|
||||
plot.set(ylabel=None)
|
||||
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()
|
||||
db.close()
|
@ -10,11 +10,13 @@ WORKDIR /home/birdnet
|
||||
# Upgrade system
|
||||
RUN apt-get update && apt-get upgrade -y
|
||||
|
||||
# Install curl
|
||||
# Install sudo
|
||||
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
|
||||
|
@ -9,7 +9,7 @@
|
||||
```bash
|
||||
git clone https://github.com/UncleSamulus/BirdNET-stream.git
|
||||
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:
|
||||
|
105
install.sh
105
install.sh
@ -5,13 +5,13 @@ set -e
|
||||
|
||||
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}
|
||||
WORKDIR="$(pwd)/BirdNET-stream"
|
||||
PYTHON_VENV="./.venv/birdnet-stream"
|
||||
|
||||
debug() {
|
||||
if [ $DEBUG -eq 1 ]; then
|
||||
echo "$1"
|
||||
fi
|
||||
[[ $DEBUG -eq 1 ]] && echo "$@"
|
||||
}
|
||||
|
||||
install_requirements() {
|
||||
@ -19,52 +19,53 @@ install_requirements() {
|
||||
# Install requirements
|
||||
missing_requirements=""
|
||||
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"
|
||||
fi
|
||||
done
|
||||
if [ -n "$missing_requirements" ]; then
|
||||
if [[ -n "$missing_requirements" ]]; then
|
||||
debug "Installing missing requirements: $missing_requirements"
|
||||
sudo apt-get install -y $missing_requirements
|
||||
sudo apt-get install -y "$missing_requirements"
|
||||
fi
|
||||
}
|
||||
|
||||
# Install BirdNET-stream
|
||||
install_birdnetstream() {
|
||||
# Check if repo is not already installed
|
||||
workdir=$(pwd)
|
||||
if [ -d "$workdir/BirdNET-stream" ]; then
|
||||
debug "BirdNET-stream is already installed"
|
||||
if [[ -d "$DIR" ]]; then
|
||||
debug "BirdNET-stream is already installed, use update script (not implemented yet)"
|
||||
exit 1
|
||||
else
|
||||
debug "Installing BirdNET-stream"
|
||||
debug "Creating BirdNET-stream directory"
|
||||
mkdir -p "$WORKDIR"
|
||||
# Clone BirdNET-stream
|
||||
cd "$WORKDIR"
|
||||
debug "Cloning BirdNET-stream from $REPOSITORY"
|
||||
git clone --recurse-submodules $REPOSITORY
|
||||
# Install BirdNET-stream
|
||||
git clone --recurse-submodules "$REPOSITORY" .
|
||||
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
|
||||
cd BirdNET-stream
|
||||
debug "Creating python3 virtual environment '$PYTHON_VENV'"
|
||||
python3 -m venv $PYTHON_VENV
|
||||
debug "Activating $PYTHON_VENV"
|
||||
source .venv/birdnet-stream/bin/activate
|
||||
debug "Installing python packages"
|
||||
pip install -U pip
|
||||
pip install -r requirements.txt
|
||||
}
|
||||
|
||||
# Install systemd services
|
||||
install_birdnetstream_services() {
|
||||
cd BirdNET-stream
|
||||
DIR=$(pwd)
|
||||
GROUP=$USER
|
||||
GROUP=birdnet
|
||||
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"
|
||||
read -r -a services_array <<<"$services"
|
||||
|
||||
for service in ${services_array[@]}; do
|
||||
sudo cp daemon/systemd/templates/$service /etc/systemd/system/
|
||||
read -r -a services_array <<< "$services"
|
||||
for service in "${services_array[@]}"; do
|
||||
sudo cp "daemon/systemd/templates/$service" "/etc/systemd/system/"
|
||||
variables="DIR USER GROUP"
|
||||
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
|
||||
sudo systemctl daemon-reload
|
||||
@ -90,15 +91,17 @@ install_php8() {
|
||||
}
|
||||
|
||||
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');"
|
||||
sudo mv /composer.phar /usr/local/bin/composer
|
||||
sudo mv ./composer.phar /usr/local/bin/composer
|
||||
cd -
|
||||
}
|
||||
|
||||
install_nodejs() {
|
||||
# Install nodejs
|
||||
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")"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads 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
|
||||
nvm install 16
|
||||
nvm use 16
|
||||
install_requirements "npm"
|
||||
@ -116,30 +119,60 @@ install_web_interface() {
|
||||
# Install nodejs 16
|
||||
install_nodejs
|
||||
# Install Symfony web app
|
||||
cd BirdNET-stream
|
||||
cd "$WORKDIR"
|
||||
cd www
|
||||
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/
|
||||
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-enabled/birdnet-stream.conf
|
||||
sudo ln -s /etc/nginx/sites-available/birdnet-stream.conf /etc/nginx/sites-available/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 restart nginx
|
||||
debug "Retrieving composer dependencies"
|
||||
composer install
|
||||
debug "PHP dependencies installed"
|
||||
debug "Installing nodejs dependencies"
|
||||
yarn install
|
||||
debug "npm dependencies installed"
|
||||
debug "Building assets"
|
||||
yarn build
|
||||
debug "Webpack assets built"
|
||||
debug "Web interface is available"
|
||||
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() {
|
||||
install_requirements $REQUIREMENTS
|
||||
install_requirements "$REQUIREMENTS"
|
||||
install_birdnetstream
|
||||
install_birdnetstream_services
|
||||
install_web_interface
|
||||
install_config
|
||||
update_permissions "$WORKDIR"
|
||||
debug "Installation done"
|
||||
}
|
||||
|
||||
main
|
||||
|
@ -1,15 +1,13 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
# Fix permissions on BirdNET-stream files when messed up
|
||||
set -e
|
||||
|
||||
DEBUG=${DEBUG:-1}
|
||||
DEBUG=${DEBUG:-0}
|
||||
debug() {
|
||||
if [ $DEBUG -eq 1 ]; then
|
||||
echo "$1"
|
||||
fi
|
||||
[ $DEBUG -eq 1 ] && echo "$@"
|
||||
}
|
||||
|
||||
config_filepath="./config/analyzer.conf"
|
||||
config_filepath="./config/birdnet.conf"
|
||||
|
||||
if [ -f "$config_filepath" ]; then
|
||||
source "$config_filepath"
|
||||
@ -18,7 +16,6 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
GROUP=birdnet
|
||||
|
||||
sudo chown -R $USER:$GROUP $CHUNK_FOLDER
|
||||
|
2
www/package-lock.json
generated
2
www/package-lock.json
generated
@ -16,6 +16,7 @@
|
||||
"core-js": "^3.23.0",
|
||||
"git-revision-webpack-plugin": "^5.0.0",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-notifier": "^1.15.0"
|
||||
}
|
||||
},
|
||||
@ -7630,7 +7631,6 @@
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
|
||||
"integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
"@types/estree": "^0.0.51",
|
||||
|
@ -6,6 +6,7 @@
|
||||
"core-js": "^3.23.0",
|
||||
"git-revision-webpack-plugin": "^5.0.0",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-notifier": "^1.15.0"
|
||||
},
|
||||
"license": "UNLICENSED",
|
||||
|
@ -10,8 +10,8 @@ class DisksController extends AbstractController
|
||||
{
|
||||
|
||||
/**
|
||||
* @Route("/disks/", name="disks_index")
|
||||
* @Route("{_locale}/disks/", name="disks_index_i18n")
|
||||
* @Route("/disks/", name="disks")
|
||||
* @Route("{_locale}/disks/", name="disks_i18n")
|
||||
*/
|
||||
public function index() {
|
||||
return $this->render('disks/index.html.twig', [
|
||||
|
@ -1,3 +1,4 @@
|
||||
{% extends "base.html.twig" %}
|
||||
{% block content %}
|
||||
<h2>{{ "Disk usage"|trans }}</h2>
|
||||
<div class="disk">
|
||||
|
@ -38,14 +38,23 @@
|
||||
} %}
|
||||
</ul>
|
||||
</li>
|
||||
{% include 'utils/nav-item.html.twig' with {
|
||||
route: 'logs',
|
||||
text: 'View Logs'|trans
|
||||
} %}
|
||||
{% include 'utils/nav-item.html.twig' with {
|
||||
route: 'services_status',
|
||||
text: 'Status'|trans
|
||||
} %}
|
||||
<li class="dropdown">
|
||||
<span class="dropdown-toggle">{{ "Tools"|trans }}</span>
|
||||
<ul class="dropdown-content">
|
||||
{% include 'utils/nav-item.html.twig' with {
|
||||
route: 'logs',
|
||||
text: 'View Logs'|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>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -11,7 +11,7 @@
|
||||
</trans-unit>
|
||||
<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>
|
||||
<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 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>
|
||||
|
@ -4252,7 +4252,7 @@
|
||||
"resolved" "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz"
|
||||
"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=="
|
||||
"resolved" "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz"
|
||||
"version" "5.74.0"
|
||||
|
Loading…
Reference in New Issue
Block a user