mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-06-04 23:04:09 +02:00
battybirdnet-pi
This commit is contained in:
12
battybirdnet-pi/rootfs/custom-services.d/00-php_pfm.sh
Normal file
12
battybirdnet-pi/rootfs/custom-services.d/00-php_pfm.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Correct /config permissions after startup
|
||||
chown pi:pi /config
|
||||
|
||||
# Waiting for dbus
|
||||
until [[ -e /var/run/dbus/system_bus_socket ]]; do
|
||||
sleep 1s
|
||||
done
|
||||
echo "Starting service: php pfm"
|
||||
exec /usr/sbin/php-fpm* -F
|
||||
9
battybirdnet-pi/rootfs/custom-services.d/01-avahi.sh
Normal file
9
battybirdnet-pi/rootfs/custom-services.d/01-avahi.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# Waiting for dbus
|
||||
until [[ -e /var/run/dbus/system_bus_socket ]]; do
|
||||
sleep 1s
|
||||
done
|
||||
|
||||
echo "Starting service: avahi daemon"
|
||||
exec \
|
||||
avahi-daemon --no-chroot
|
||||
21
battybirdnet-pi/rootfs/custom-services.d/02-caddy.sh
Normal file
21
battybirdnet-pi/rootfs/custom-services.d/02-caddy.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Dependencies
|
||||
sockfile="empty"
|
||||
until [[ -e /var/run/dbus/system_bus_socket ]] && [[ -e "$sockfile" ]]; do
|
||||
sleep 1s
|
||||
sockfile="$(find /run/php -name "*.sock")"
|
||||
done
|
||||
|
||||
# Correct fpm.sock
|
||||
chown caddy:caddy /run/php/php*-fpm.sock
|
||||
sed -i "s|/run/php/php-fpm.sock|$sockfile|g" /helpers/caddy_ingress.sh
|
||||
sed -i "s|/run/php/php-fpm.sock|$sockfile|g" /etc/caddy/Caddyfile
|
||||
sed -i "s|/run/php/php-fpm.sock|$sockfile|g" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
|
||||
# Update caddyfile with password
|
||||
/."$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh &>/dev/null || true
|
||||
|
||||
echo "Starting service: caddy"
|
||||
/usr/bin/caddy run --config /etc/caddy/Caddyfile
|
||||
6
battybirdnet-pi/rootfs/custom-services.d/02-nginx.sh
Normal file
6
battybirdnet-pi/rootfs/custom-services.d/02-nginx.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
echo "Starting service: nginx"
|
||||
nginx
|
||||
92
battybirdnet-pi/rootfs/custom-services.d/30-monitoring.sh
Normal file
92
battybirdnet-pi/rootfs/custom-services.d/30-monitoring.sh
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
echo "Starting service: throttlerecording"
|
||||
touch "$HOME"/BirdSongs/StreamData/analyzing_now.txt
|
||||
|
||||
# variables for readability
|
||||
srv="birdnet_recording"
|
||||
analyzing_now="."
|
||||
counter=10
|
||||
set +u
|
||||
# shellcheck disable=SC1091
|
||||
source /config/birdnet.conf 2>/dev/null
|
||||
|
||||
# Ensure folder exists
|
||||
ingest_dir="$RECS_DIR/StreamData"
|
||||
|
||||
# Check permissions
|
||||
mkdir -p "$ingest_dir"
|
||||
chown -R pi:pi "$ingest_dir"
|
||||
chmod -R 755 "$ingest_dir"
|
||||
ingest_dir="$(readlink -f "$ingest_dir")" || true
|
||||
mkdir -p "$ingest_dir"
|
||||
chown -R pi:pi "$ingest_dir"
|
||||
chmod -R 755 "$ingest_dir"
|
||||
|
||||
function apprisealert() {
|
||||
# Set failed check so it only runs once
|
||||
touch "$HOME"/BirdNET-Pi/failed_servicescheck
|
||||
NOTIFICATION=""
|
||||
STOPPEDSERVICE="<br><b>Stopped services:</b> "
|
||||
services=(birdnet_analysis
|
||||
chart_viewer
|
||||
spectrogram_viewer
|
||||
icecast2
|
||||
birdnet_recording
|
||||
birdnet_log
|
||||
birdnet_stats)
|
||||
for i in "${services[@]}"; do
|
||||
if [[ "$(sudo systemctl is-active "${i}".service)" == "inactive" ]]; then
|
||||
STOPPEDSERVICE+="${i}; "
|
||||
fi
|
||||
done
|
||||
NOTIFICATION+="$STOPPEDSERVICE"
|
||||
NOTIFICATION+="<br><b>Additional informations</b>: "
|
||||
NOTIFICATION+="<br><b>Since:</b> ${LASTCHECK:-unknown}"
|
||||
NOTIFICATION+="<br><b>System:</b> ${SITE_NAME:-$(hostname)}"
|
||||
NOTIFICATION+="<br>Available disk space: $(df -h "$(readlink -f "$HOME/BirdSongs")" | awk 'NR==2 {print $4}')"
|
||||
if [ -n "$BIRDNETPI_URL" ]; then
|
||||
NOTIFICATION+="<br> <a href=\"$BIRDNETPI_URL\">Access your battybirdnet-pi</a>"
|
||||
fi
|
||||
TITLE="BirdNET-Analyzer stopped"
|
||||
"$HOME"/BirdNET-Pi/birdnet/bin/apprise -vv -t "$TITLE" -b "${NOTIFICATION}" --input-format=html --config="$HOME/BirdNET-Pi/apprise.txt"
|
||||
}
|
||||
|
||||
while true; do
|
||||
sleep 61
|
||||
|
||||
# Restart analysis if clogged
|
||||
############################
|
||||
|
||||
if ((counter <= 0)); then
|
||||
latest="$(cat "$ingest_dir"/analyzing_now.txt)"
|
||||
if [[ "$latest" == "$analyzing_now" ]]; then
|
||||
echo "$(date) WARNING no change in analyzing_now for 10 iterations, restarting services"
|
||||
/."$HOME"/BirdNET-Pi/scripts/restart_services.sh
|
||||
fi
|
||||
counter=10
|
||||
analyzing_now=$(cat "$ingest_dir"/analyzing_now.txt)
|
||||
fi
|
||||
|
||||
# Pause recorder to catch-up
|
||||
############################
|
||||
|
||||
wavs="$(find "$ingest_dir" -maxdepth 1 -name '*.wav' | wc -l)"
|
||||
state="$(systemctl is-active "$srv")"
|
||||
|
||||
bashio::log.green "$(date) INFO ${wavs} wav files waiting in $ingest_dir, $srv state is $state"
|
||||
|
||||
if ((wavs > 100)); then
|
||||
bashio::log.red "$(date) WARNING too many files in queue, pausing $srv"
|
||||
sudo systemctl stop "$srv"
|
||||
sudo systemctl restart birdnet_analysis
|
||||
if [ -s "$HOME/BirdNET-Pi/apprise.txt" ]; then apprisealert; fi
|
||||
elif [[ "$state" != "active" ]]; then
|
||||
bashio::log.yellow "$(date) INFO started $srv service"
|
||||
sudo systemctl start $srv
|
||||
sudo systemctl restart birdnet_analysis
|
||||
fi
|
||||
|
||||
((counter--))
|
||||
done
|
||||
18
battybirdnet-pi/rootfs/etc/cont-finish.d/savestreamdata.sh
Normal file
18
battybirdnet-pi/rootfs/etc/cont-finish.d/savestreamdata.sh
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
if [ -d "$HOME"/BirdSongs/StreamData ]; then
|
||||
bashio::log.fatal "Container stopping, saving temporary files"
|
||||
|
||||
# Stop the services in parallel
|
||||
systemctl stop birdnet_analysis &
|
||||
systemctl stop birdnet_recording
|
||||
|
||||
# Check if there are files in StreamData and move them to /data/StreamData
|
||||
mkdir -p /data/StreamData
|
||||
if [ "$(ls -A "$HOME"/BirdSongs/StreamData)" ]; then
|
||||
mv -v "$HOME"/BirdSongs/StreamData/* /data/StreamData/
|
||||
fi
|
||||
|
||||
bashio::log.fatal "... files safe, allowing container to stop"
|
||||
fi
|
||||
87
battybirdnet-pi/rootfs/etc/cont-init.d/01-structure.sh
Normal file
87
battybirdnet-pi/rootfs/etc/cont-init.d/01-structure.sh
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
###############
|
||||
# SET /CONFIG #
|
||||
###############
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Ensuring the file structure is correct :"
|
||||
|
||||
# Define structure
|
||||
echo "... creating default files"
|
||||
touch /config/include_species_list.txt # Should be null
|
||||
for files in apprise.txt exclude_species_list.txt IdentifiedSoFar.txt disk_check_exclude.txt confirmed_species_list.txt blacklisted_images.txt; do
|
||||
if [ ! -f /config/"$files" ]; then
|
||||
echo "" > /config/"$files"
|
||||
fi
|
||||
done
|
||||
|
||||
# Get BirdSongs folder locations
|
||||
BIRDSONGS_FOLDER="/config/BirdSongs"
|
||||
if bashio::config.has_value "BIRDSONGS_FOLDER"; then
|
||||
BIRDSONGS_FOLDER_OPTION="$(bashio::config "BIRDSONGS_FOLDER")"
|
||||
echo "... BIRDSONGS_FOLDER set to $BIRDSONGS_FOLDER_OPTION"
|
||||
mkdir -p "$BIRDSONGS_FOLDER_OPTION" || bashio::log.fatal "...... folder couldn't be created"
|
||||
chown -R pi:pi "$BIRDSONGS_FOLDER_OPTION" || bashio::log.fatal "...... folder couldn't be given permissions for 1000:1000"
|
||||
if [ -d "$BIRDSONGS_FOLDER_OPTION" ] && [ "$(stat -c '%u:%g' "$BIRDSONGS_FOLDER_OPTION")" == "1000:1000" ]; then
|
||||
BIRDSONGS_FOLDER="$BIRDSONGS_FOLDER_OPTION"
|
||||
else
|
||||
bashio::log.yellow "BIRDSONGS_FOLDER reverted to /config/BirdSongs"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create BirdSongs folder
|
||||
echo "... creating default folders ; it is highly recommended to store those on a ssd"
|
||||
mkdir -p "$BIRDSONGS_FOLDER"/By_Date
|
||||
mkdir -p "$BIRDSONGS_FOLDER"/Charts
|
||||
|
||||
# If tmpfs is installed, use it
|
||||
if df -T /tmp | grep -q "tmpfs"; then
|
||||
echo "... tmpfs detected, using it for StreamData and Processed to reduce disk wear"
|
||||
mkdir -p /tmp/StreamData
|
||||
mkdir -p /tmp/Processed
|
||||
rm -r "$HOME"/BirdSongs/StreamData
|
||||
rm -r "$HOME"/BirdSongs/Processed
|
||||
sudo -u pi ln -fs /tmp/StreamData "$HOME"/BirdSongs/StreamData
|
||||
sudo -u pi ln -fs /tmp/Processed "$HOME"/BirdSongs/Processed
|
||||
fi
|
||||
|
||||
# Permissions for created files and folders
|
||||
echo "... set permissions to user pi"
|
||||
chown -R pi:pi /config /etc/birdnet "$BIRDSONGS_FOLDER" /tmp
|
||||
chmod -R 755 /config /config /etc/birdnet "$BIRDSONGS_FOLDER" /tmp
|
||||
|
||||
# Save default birdnet.conf to perform sanity check
|
||||
cp "$HOME"/BirdNET-Pi/birdnet.conf "$HOME"/BirdNET-Pi/birdnet.bak
|
||||
|
||||
# Symlink files
|
||||
echo "... creating symlink"
|
||||
for files in "$HOME/BirdNET-Pi/birdnet.conf" "$HOME/BirdNET-Pi/blacklisted_images.txt" "$HOME/BirdNET-Pi/scripts/birds.db" "$HOME/BirdNET-Pi/BirdDB.txt" "$HOME/BirdNET-Pi/scripts/disk_check_exclude.txt" "$HOME/BirdNET-Pi/apprise.txt" "$HOME/BirdNET-Pi/exclude_species_list.txt" "$HOME/BirdNET-Pi/include_species_list.txt" "$HOME/BirdNET-Pi/IdentifiedSoFar.txt" "$HOME/BirdNET-Pi/confirmed_species_list.txt"; do
|
||||
filename="${files##*/}"
|
||||
if [ ! -f /config/"$filename" ]; then
|
||||
if [ -f "$files" ]; then
|
||||
echo "... copying $filename" && sudo -u pi mv "$files" /config/
|
||||
else
|
||||
touch /config/"$filename"
|
||||
fi
|
||||
fi
|
||||
if [ -e "$files" ]; then rm "$files"; fi
|
||||
sudo -u pi ln -fs /config/"$filename" "$files" || bashio::log.fatal "Symlink creation failed for $filename"
|
||||
sudo -u pi ln -fs /config/"$filename" /etc/birdnet/"$filename" || bashio::log.fatal "Symlink creation failed for $filename"
|
||||
done
|
||||
|
||||
# Symlink folders
|
||||
for folders in By_Date Charts; do
|
||||
echo "... creating symlink for $BIRDSONGS_FOLDER/$folders"
|
||||
rm -r "$HOME/BirdSongs/Extracted/${folders:?}"
|
||||
sudo -u pi ln -fs "$BIRDSONGS_FOLDER"/"$folders" "$HOME/BirdSongs/Extracted/$folders"
|
||||
done
|
||||
|
||||
# Permissions for created files and folders
|
||||
echo "... check permissions"
|
||||
chmod -R 755 /config/*
|
||||
chmod 777 /config
|
||||
|
||||
echo " "
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Check if there are files in "$HOME"/BirdSongs/StreamData and move them to /data/StreamData
|
||||
if [ -d /data/StreamData ] && [ "$(ls -A /data/StreamData/)" ]; then
|
||||
|
||||
bashio::log.warning "Container was stopped while files were still being analysed, restoring them"
|
||||
|
||||
# Copy files
|
||||
if [ "$(ls -A /data/StreamData)" ]; then
|
||||
mv -v /data/StreamData/* "$HOME"/BirdSongs/StreamData/
|
||||
fi
|
||||
echo "... done"
|
||||
echo ""
|
||||
|
||||
# Setting permissions
|
||||
chown -R pi:pi "$HOME"/BirdSongs
|
||||
chmod -R 755 "$HOME"/BirdSongs
|
||||
|
||||
# Cleaning folder
|
||||
rm -r /data/StreamData
|
||||
|
||||
fi
|
||||
|
||||
54
battybirdnet-pi/rootfs/etc/cont-init.d/31-checks.sh
Normal file
54
battybirdnet-pi/rootfs/etc/cont-init.d/31-checks.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
######################
|
||||
# CHECK BIRDNET.CONF #
|
||||
######################
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Checking your birndet.conf file integrity"
|
||||
|
||||
# Set variables
|
||||
configcurrent="$HOME"/BirdNET-Pi/birdnet.conf
|
||||
configtemplate="$HOME"/BirdNET-Pi/birdnet.bak
|
||||
|
||||
# Extract variable names from config template and read each one
|
||||
grep -o '^[^#=]*=' "$configtemplate" | sed 's/=//' | while read -r var; do
|
||||
# Check if the variable is in configcurrent, if not, append it
|
||||
if ! grep -q "^$var=" "$configcurrent"; then
|
||||
# At which line was the variable in the initial file
|
||||
bashio::log.yellow "...$var was missing from your birdnet.conf file, it was re-added"
|
||||
grep "^$var=" "$configtemplate" >> "$configcurrent"
|
||||
fi
|
||||
# Check for duplicates
|
||||
if [ "$(grep -c "^$var=" "$configcurrent")" -gt 1 ]; then
|
||||
bashio::log.error "Duplicate variable $var found in $configcurrent, all were commented out expect for the first one"
|
||||
awk -v var="$var" '{ if ($0 ~ "^[[:blank:]]*"var && c++ > 0) print "#" $0; else print $0; }' "$configcurrent" > temp && mv temp "$configcurrent"
|
||||
fi
|
||||
done
|
||||
|
||||
################
|
||||
# CHECK AMIXER #
|
||||
################
|
||||
|
||||
# If default capture is set at 0%, increase it to 50%
|
||||
# current_volume="$(amixer sget Capture | grep -oP '\[\d+%]' | tr -d '[]%' | head -1)" 2>/dev/null || true
|
||||
# current_volume="${current_volume:-100}"
|
||||
|
||||
# Set the default microphone volume to 50% if it's currently at 0%
|
||||
# if [[ "$current_volume" -eq 0 ]]; then
|
||||
# amixer sset Capture 70%
|
||||
# bashio::log.warning "Microphone was off, volume set to 70%."
|
||||
# fi
|
||||
|
||||
##############
|
||||
# CHECK PORT #
|
||||
##############
|
||||
|
||||
if [[ "$(bashio::addon.port "80")" == 3000 ]]; then
|
||||
bashio::log.fatal "This is crazy but your port is set to 3000 and streamlit doesn't accept this port! You need to change it from the addon options and restart. Thanks"
|
||||
sleep infinity
|
||||
fi
|
||||
|
||||
echo " "
|
||||
47
battybirdnet-pi/rootfs/etc/cont-init.d/33-mqtt.sh
Normal file
47
battybirdnet-pi/rootfs/etc/cont-init.d/33-mqtt.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
if bashio::services.available 'mqtt' && ! bashio::config.true 'MQTT_DISABLED' ; then
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "MQTT addon is active on your system! battybirdnet-pi is now automatically configured to send its ouptut to MQTT"
|
||||
bashio::log.blue "MQTT user : $(bashio::services "mqtt" "username")"
|
||||
bashio::log.blue "MQTT password : $(bashio::services "mqtt" "password")"
|
||||
bashio::log.blue "MQTT broker : tcp://$(bashio::services "mqtt" "host"):$(bashio::services "mqtt" "port")"
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "Data will be posted to the topic : 'birdnet'"
|
||||
bashio::log.blue "Json data : {'Date', 'Time', 'ScientificName', 'CommonName', 'Confidence', 'SpeciesCode', 'ClipName', 'url'}"
|
||||
bashio::log.blue "---"
|
||||
|
||||
# Apply MQTT settings
|
||||
sed -i "s|%%mqtt_server%%|$(bashio::services "mqtt" "host")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_port%%|$(bashio::services "mqtt" "port")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_user%%|$(bashio::services "mqtt" "username")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_pass%%|$(bashio::services "mqtt" "password")|g" /helpers/birdnet_to_mqtt.py
|
||||
|
||||
# Copy script
|
||||
cp /helpers/birdnet_to_mqtt.py /usr/bin/birdnet_to_mqtt.py
|
||||
cp /helpers/birdnet_to_mqtt.sh /custom-services.d
|
||||
chmod 777 /usr/bin/birdnet_to_mqtt.py
|
||||
chmod 777 /custom-services.d/birdnet_to_mqtt.sh
|
||||
elif bashio::config.has_value "MQTT_HOST_manual" && bashio::config.has_value "MQTT_PORT_manual"; then
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "MQTT is manually configured in the addon options"
|
||||
bashio::log.blue "battybirdnet-pi is now automatically configured to send its ouptut to MQTT"
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "Data will be posted to the topic : 'birdnet'"
|
||||
bashio::log.blue "Json data : {'Date', 'Time', 'ScientificName', 'CommonName', 'Confidence', 'SpeciesCode', 'ClipName', 'url'}"
|
||||
bashio::log.blue "---"
|
||||
|
||||
# Apply MQTT settings
|
||||
sed -i "s|%%mqtt_server%%|$(bashio::config "MQTT_HOST_manual")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_port%%|$(bashio::config "MQTT_PORT_manual")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_user%%|$(bashio::config "MQTT_USER_manual")|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_pass%%|$(bashio::config "MQTT_PASSWORD_manual")|g" /helpers/birdnet_to_mqtt.py
|
||||
|
||||
# Copy script
|
||||
cp /helpers/birdnet_to_mqtt.py /usr/bin/birdnet_to_mqtt.py
|
||||
cp /helpers/birdnet_to_mqtt.sh /custom-services.d
|
||||
chmod +x /usr/bin/birdnet_to_mqtt.py
|
||||
chmod +x /custom-services.d/birdnet_to_mqtt.sh
|
||||
fi
|
||||
127
battybirdnet-pi/rootfs/etc/cont-init.d/71-newfeatures.sh
Normal file
127
battybirdnet-pi/rootfs/etc/cont-init.d/71-newfeatures.sh
Normal file
@@ -0,0 +1,127 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
################
|
||||
# ADD FEATURES #
|
||||
################
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Adding optional features"
|
||||
|
||||
# Denoiser
|
||||
#if bashio::config.true "DENOISER_ANALYSIS_ENABLED"; then
|
||||
# sed -i "s|ar 48000|ar 48000 -af \"arnndn=m=sample.rnnn\"|g" "$HOME"/BirdNET-Pi/scripts/birdnet_recording.sh
|
||||
# sed -i "s|ar 48000|ar 48000 -af afftdn=nr=30:nt=w:om=o|g" "$HOME"/BirdNET-Pi/scripts/birdnet_recording.sh
|
||||
#fi
|
||||
|
||||
# Add species conversion system
|
||||
###############################
|
||||
if bashio::config.true "SPECIES_CONVERTER_ENABLED"; then
|
||||
echo "... adding feature of SPECIES_CONVERTER, a new tab is added to your Tools"
|
||||
touch /config/convert_species_list.txt
|
||||
chown pi:pi /config/convert_species_list.txt
|
||||
sudo -u pi ln -fs /config/convert_species_list.txt "$HOME"/BirdNET-Pi/
|
||||
sudo -u pi ln -fs /config/convert_species_list.txt "$HOME"/BirdNET-Pi/scripts/
|
||||
# Not useful
|
||||
sed -i "/exclude_species_list.txt/a sudo -u pi ln -fs /config/convert_species_list.txt $HOME/BirdNET-Pi/scripts/" "$HOME"/BirdNET-Pi/scripts/clear_all_data.sh
|
||||
sed -i "/exclude_species_list.txt/a sudo -u pi ln -fs /config/convert_species_list.txt $HOME/BirdNET-Pi/scripts/" "$HOME"/BirdNET-Pi/scripts/install_services.sh
|
||||
# Modify views.php if not already done
|
||||
if ! grep -q "Converted" "$HOME"/BirdNET-Pi/homepage/views.php; then
|
||||
# Add button
|
||||
# shellcheck disable=SC2016
|
||||
sed -i '/Excluded Species List/a\ <button type=\\"submit\\" name=\\"view\\" value=\\"Converted\\" form=\\"views\\">Converted Species List</button>' "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
# Flag to indicate whether we've found the target line
|
||||
found_target=false
|
||||
# Read the original file line by line
|
||||
while IFS= read -r line; do
|
||||
if [[ $line == *"if(\$_GET['view'] == \"File\"){"* ]]; then
|
||||
found_target=true
|
||||
fi
|
||||
if $found_target; then
|
||||
echo "$line" >> "$HOME"/BirdNET-Pi/homepage/views.php.temp
|
||||
fi
|
||||
done < "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
# Remove the extracted lines from the original file
|
||||
# shellcheck disable=SC2016
|
||||
sed -i '/if(\$_GET\['\''view'\''\] == "File"){/,$d' "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
# Add new text
|
||||
cat "/helpers/views.add" >> "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
cat "$HOME"/BirdNET-Pi/homepage/views.php.temp >> "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
# Clean up: Remove the temporary file
|
||||
rm "$HOME"/BirdNET-Pi/homepage/views.php.temp
|
||||
fi
|
||||
|
||||
# Add the converter script
|
||||
if [ ! -f "$HOME"/BirdNET-Pi/scripts/convert_list.php ]; then
|
||||
mv -f /helpers/convert_list.php "$HOME"/BirdNET-Pi/scripts/convert_list.php
|
||||
chown pi:pi "$HOME"/BirdNET-Pi/scripts/convert_list.php
|
||||
chmod 664 "$HOME"/BirdNET-Pi/scripts/convert_list.php
|
||||
fi
|
||||
|
||||
# Change server
|
||||
if ! grep -q "converted_entry" "$HOME"/BirdNET-Pi/scripts/server.py; then
|
||||
sed -i "/INTERPRETER, M_INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST/c INTERPRETER, M_INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST, CONVERT_LIST = (None, None, None, None, None)" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/global INCLUDE_LIST, EXCLUDE_LIST/c\ global INCLUDE_LIST, EXCLUDE_LIST, CONVERT_LIST, CONVERT_DICT" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/exclude_species_list.txt/a\ CONVERT_DICT = {row.split(';')[0]: row.split(';')[1] for row in CONVERT_LIST}" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/exclude_species_list.txt/a\ CONVERT_LIST = loadCustomSpeciesList(os.path.expanduser(\"~/BirdNET-Pi/convert_species_list.txt\"))" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "s|entry\[0\]|converted_entry|g" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "s|if converted_entry in|if entry\[0\] in|g" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ converted_entry = entry[0]" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ else :" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ log.info('WARNING : ' + entry[0] + ' converted to ' + converted_entry)" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ converted_entry = CONVERT_DICT.get(entry[0], entry[0])" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ if entry[0] in CONVERT_DICT:" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/for entry in entries/a\ if entry[1] >= conf.getfloat('CONFIDENCE'):" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "/converted_entry in INCLUDE_LIST or len(INCLUDE_LIST)/c\ if ((converted_entry in INCLUDE_LIST or len(INCLUDE_LIST) == 0)" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "s| d = Detection| d = Detection|g" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
sed -i "s| confident_detections| confident_detections|g" "$HOME"/BirdNET-Pi/scripts/server.py
|
||||
fi
|
||||
fi || true
|
||||
|
||||
# Enable the Processed folder
|
||||
#############################
|
||||
|
||||
if bashio::config.true "PROCESSED_FOLDER_ENABLED" && ! grep -q "processed_size" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py; then
|
||||
echo "... Enabling the Processed folder : the last 15 wav files will be stored there"
|
||||
# Adapt config.php
|
||||
sed -i "/GET\[\"info_site\"\]/a\ \$processed_size = \$_GET\[\"processed_size\"\];" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\$contents = file_get_contents/a\ \$contents = preg_replace\(\"/PROCESSED_SIZE=\.\*/\", \"PROCESSED_SIZE=\$processed_size\", \$contents\);" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i <table class=\"settingstable\"><tr><td>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i <h2>Processed folder management </h2>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i <label for=\"processed_size\">Amount of files to keep after analysis :</label>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i <input name=\"processed_size\" type=\"number\" style=\"width:6em;\" max=\"90\" min=\"0\" step=\"1\" value=\"<\?php print(\$config\['PROCESSED_SIZE'\]);?>\"/>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i </td></tr><tr><td>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i Processed is the directory where the formerly 'Analyzed' files are moved after extractions, mostly for troubleshooting purposes.<br>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i This value defines the maximum amount of files that are kept before replacement with new files.<br>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i </td></tr></table>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
sed -i "/\"success\"/i\ <br>" "$HOME"/BirdNET-Pi/scripts/config.php
|
||||
# Adapt birdnet_analysis.py - move_to_processed
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ os.remove(files.pop(0))" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ while len(files) > processed_size:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ files.sort(key=os.path.getmtime)" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ files = glob.glob(os.path.join(processed_dir, '*'))" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ os.rename(file_name, os.path.join(processed_dir, os.path.basename(file_name)))" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ processed_dir = os.path.join(get_settings()['RECS_DIR'], 'Processed')" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\def move_to_processed(file_name, processed_size):" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ " "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
# Adapt birdnet_analysis.py - get_processed_size
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ return 0" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ except (ValueError, TypeError):" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ return processed_size if isinstance(processed_size, int) else 0" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ processed_size = get_settings().getint('PROCESSED_SIZE')" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ try:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\def get_processed_size():" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/log.info('handle_reporting_queue done')/a\ " "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
# Modify calls
|
||||
sed -i "/from subprocess import CalledProcessError/a\import glob" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/from subprocess import CalledProcessError/a\import time" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
# Modify main code
|
||||
sed -i "/os.remove(file.file_name)/i\ processed_size = get_processed_size()" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/os.remove(file.file_name)/i\ if processed_size > 0:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/os.remove(file.file_name)/i\ move_to_processed(file.file_name, processed_size)" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/os.remove(file.file_name)/i\ else:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i "/os.remove(file.file_name)/c\ os.remove(file.file_name)" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
fi || true
|
||||
|
||||
echo " "
|
||||
66
battybirdnet-pi/rootfs/etc/cont-init.d/81-modifications.sh
Normal file
66
battybirdnet-pi/rootfs/etc/cont-init.d/81-modifications.sh
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
################
|
||||
# MODIFY WEBUI #
|
||||
################
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Adapting webui"
|
||||
|
||||
# Remove services tab
|
||||
echo "... removing System Controls from webui as should be used from HA"
|
||||
sed -i '/>System Controls/d' "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
|
||||
# Remove services tab
|
||||
echo "... removing Ram drive from webui as it is handled from HA"
|
||||
sed -i '/Ram drive/{n;s/center"/center" style="display: none;"/;}' "$HOME"/BirdNET-Pi/scripts/service_controls.php
|
||||
sed -i '/Ram drive/d' "$HOME"/BirdNET-Pi/scripts/service_controls.php
|
||||
|
||||
# Correct services to start as user pi
|
||||
echo "... correct services to start as pi"
|
||||
for file in $(find "$HOME"/BirdNET-Pi/templates/birdnet*.service -print0 | xargs -0 basename -a) livestream.service chart_viewer.service chart_viewer.service spectrogram_viewer.service; do
|
||||
if [[ "$file" != "birdnet_log.service" ]]; then
|
||||
sed -i "s|ExecStart=|ExecStart=/usr/bin/sudo -u pi |g" "$HOME/BirdNET-Pi/templates/$file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Send services log to container logs
|
||||
echo "... send services log to container logs"
|
||||
for file in $(find "$HOME"/BirdNET-Pi/templates/birdnet*.service -print0 | xargs -0 basename -a) livestream.service chart_viewer.service chart_viewer.service spectrogram_viewer.service; do
|
||||
sed -i "/Service/a StandardError=append:/proc/1/fd/1" "$HOME/BirdNET-Pi/templates/$file"
|
||||
sed -i "/Service/a StandardOutput=append:/proc/1/fd/1" "$HOME/BirdNET-Pi/templates/$file"
|
||||
done
|
||||
|
||||
# Avoid preselection in include and exclude lists
|
||||
echo "... avoid preselecting options in include and exclude lists"
|
||||
sed -i "s|option selected|option disabled|g" "$HOME"/BirdNET-Pi/scripts/include_list.php
|
||||
sed -i "s|option selected|option disabled|g" "$HOME"/BirdNET-Pi/scripts/exclude_list.php
|
||||
|
||||
# Correct log services to show /proc/1/fd/1
|
||||
echo "... show container logs in /logs"
|
||||
sed -i "/User=pi/d" "$HOME/BirdNET-Pi/templates/birdnet_log.service"
|
||||
sed -i "s|birdnet_log.sh|cat /proc/1/fd/1|g" "$HOME/BirdNET-Pi/templates/birdnet_log.service"
|
||||
|
||||
# Make sure config is correctly formatted.
|
||||
echo "... caddyfile modifications"
|
||||
#Correct instructions
|
||||
caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
#Change port to leave 80 free for certificate requests
|
||||
sed -i "s|http://|http://:8081|g" /etc/caddy/Caddyfile
|
||||
sed -i "s|http://|http://:8081|g" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
#Remove default file that blocks 80
|
||||
if [ -f /etc/caddy/Caddyfile.original ]; then rm /etc/caddy/Caddyfile.original; fi
|
||||
|
||||
# Improve webui paths to facilitate ingress
|
||||
echo "... correcting webui paths"
|
||||
sed -i "s|/stats|/stats/|g" "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
sed -i "s|/log|/log/|g" "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
|
||||
# If port 80 is enabled, make sure it is still 80
|
||||
if [ -n "$(bashio::addon.port 80)" ] && [ "$(bashio::addon.port 80)" != 80 ]; then
|
||||
bashio::log.fatal "The port 80 is enabled, but should still be 80 if you want the automatic ssl certificates generation to work"
|
||||
fi
|
||||
|
||||
echo " "
|
||||
37
battybirdnet-pi/rootfs/etc/cont-init.d/91-nginx_ingress.sh
Normal file
37
battybirdnet-pi/rootfs/etc/cont-init.d/91-nginx_ingress.sh
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
#################
|
||||
# NGINX SETTING #
|
||||
#################
|
||||
|
||||
declare ingress_interface
|
||||
declare ingress_port
|
||||
declare ingress_entry
|
||||
|
||||
# Variables
|
||||
ingress_port=$(bashio::addon.ingress_port)
|
||||
ingress_interface=$(bashio::addon.ip_address)
|
||||
ingress_entry=$(bashio::addon.ingress_entry)
|
||||
|
||||
# Quits if ingress not active
|
||||
if [ -z "$ingress_entry" ]; then exit 0; fi
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Adapting for ingress"
|
||||
echo "... setting up nginx"
|
||||
sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf
|
||||
sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf
|
||||
sed -i "s|%%ingress_entry%%|${ingress_entry}|g" /etc/nginx/servers/ingress.conf
|
||||
|
||||
echo "... ensuring restricted area access"
|
||||
echo "${ingress_entry}" > /ingress_url
|
||||
sed -i "/function is_authenticated/a if (strpos(\$_SERVER['HTTP_REFERER'], '/api/hassio_ingress') !== false && strpos(\$_SERVER['HTTP_REFERER'], trim(file_get_contents('/ingress_url'))) !== false) { \$ret = true; return \$ret; }" "$HOME"/BirdNET-Pi/scripts/common.php
|
||||
|
||||
echo "... adapt Caddyfile for ingress"
|
||||
chmod +x /helpers/caddy_ingress.sh
|
||||
/./helpers/caddy_ingress.sh
|
||||
sed -i "/sudo caddy fmt --overwrite/i /./helpers/caddy_ingress.sh" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
|
||||
echo " "
|
||||
20
battybirdnet-pi/rootfs/etc/cont-init.d/92-ssl.sh
Normal file
20
battybirdnet-pi/rootfs/etc/cont-init.d/92-ssl.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
###############
|
||||
# SSL SETTING #
|
||||
###############
|
||||
|
||||
if bashio::config.true 'ssl'; then
|
||||
bashio::log.info "Ssl is enabled using addon options, setting up nginx"
|
||||
bashio::config.require.ssl
|
||||
certfile=$(bashio::config 'certfile')
|
||||
keyfile=$(bashio::config 'keyfile')
|
||||
sed -i "2a\ tls /ssl/${certfile} /ssl/${keyfile}" /etc/caddy/Caddyfile
|
||||
sed -i "s|http://:8081|https://:8081|g" /etc/caddy/Caddyfile
|
||||
sed -i "s|http://:8081|https://:8081|g" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
sed -i "/https:/a tls /ssl/${certfile} /ssl/${keyfile}" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
fi
|
||||
|
||||
echo " "
|
||||
72
battybirdnet-pi/rootfs/etc/cont-init.d/99-run.sh
Normal file
72
battybirdnet-pi/rootfs/etc/cont-init.d/99-run.sh
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
##############
|
||||
# SET SYSTEM #
|
||||
##############
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Setting password for the user pi"
|
||||
echo "pi:$(bashio::config "pi_password")" | sudo chpasswd
|
||||
echo "... done"
|
||||
|
||||
echo " "
|
||||
bashio::log.info "Starting system services"
|
||||
|
||||
# Set TZ
|
||||
if bashio::config.has_value 'TZ'; then
|
||||
TIMEZONE=$(bashio::config 'TZ')
|
||||
echo "... setting timezone to $TIMEZONE"
|
||||
ln -snf /usr/share/zoneinfo/"$TIMEZONE" /etc/localtime
|
||||
echo "$TIMEZONE" >/etc/timezone
|
||||
fi || (bashio::log.fatal "Error : $TIMEZONE not found. Here is a list of valid timezones : https://manpages.ubuntu.com/manpages/focal/man3/DateTime::TimeZone::Catalog.3pm.html")
|
||||
|
||||
# Correcting systemctl
|
||||
echo "... correcting systemctl"
|
||||
mv /helpers/systemctl3.py /bin/systemctl
|
||||
chmod a+x /bin/systemctl
|
||||
|
||||
# Correcting systemctl
|
||||
echo "... correcting datetimectl"
|
||||
mv /helpers/timedatectl /usr/bin/timedatectl
|
||||
chmod a+x /usr/bin/timedatectl
|
||||
|
||||
# Correct language labels
|
||||
export "$(grep "^DATABASE_LANG" /config/birdnet.conf)"
|
||||
# Saving default of en
|
||||
cp "$HOME"/BirdNET-Pi/model/labels.txt "$HOME"/BirdNET-Pi/model/labels.bak
|
||||
# Adapt to new language
|
||||
echo "... adapting labels according to birdnet.conf file to $DATABASE_LANG"
|
||||
/."$HOME"/BirdNET-Pi/scripts/install_language_label_nm.sh -l "$DATABASE_LANG"
|
||||
|
||||
echo "... starting cron"
|
||||
systemctl start cron
|
||||
|
||||
# Starting dbus
|
||||
echo "... starting dbus"
|
||||
service dbus start
|
||||
|
||||
# Starting journald
|
||||
# echo "... starting journald"
|
||||
# systemctl start systemd-journald
|
||||
|
||||
# Starting services
|
||||
echo ""
|
||||
bashio::log.info "Starting battybirdnet-pi services"
|
||||
chmod +x "$HOME"/BirdNET-Pi/scripts/restart_services.sh
|
||||
"$HOME"/BirdNET-Pi/scripts/restart_services.sh
|
||||
|
||||
if bashio::config.true LIVESTREAM_BOOT_ENABLED; then
|
||||
echo "... starting livestream"
|
||||
sudo systemctl enable icecast2
|
||||
sudo systemctl start icecast2.service
|
||||
sudo systemctl enable --now livestream.service
|
||||
fi
|
||||
|
||||
# Correct the phpsysinfo for the correct gotty service
|
||||
gottyservice="$(pgrep -l "gotty" | awk '{print $NF}' | head -n 1)"
|
||||
echo "... using $gottyservice in phpsysinfo"
|
||||
sed -i "s/,gotty,/,${gottyservice:-gotty},/g" "$HOME"/BirdNET-Pi/templates/phpsysinfo.ini
|
||||
|
||||
echo " "
|
||||
96
battybirdnet-pi/rootfs/etc/nginx/includes/mime.types
Normal file
96
battybirdnet-pi/rootfs/etc/nginx/includes/mime.types
Normal file
@@ -0,0 +1,96 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
16
battybirdnet-pi/rootfs/etc/nginx/includes/proxy_params.conf
Normal file
16
battybirdnet-pi/rootfs/etc/nginx/includes/proxy_params.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
proxy_http_version 1.1;
|
||||
proxy_ignore_client_abort off;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_redirect off;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_max_temp_file_size 0;
|
||||
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
1
battybirdnet-pi/rootfs/etc/nginx/includes/resolver.conf
Normal file
1
battybirdnet-pi/rootfs/etc/nginx/includes/resolver.conf
Normal file
@@ -0,0 +1 @@
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
@@ -0,0 +1,6 @@
|
||||
root /dev/null;
|
||||
server_name $hostname;
|
||||
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
@@ -0,0 +1,9 @@
|
||||
ssl_protocols TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA;
|
||||
ssl_ecdh_curve secp384r1;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
3
battybirdnet-pi/rootfs/etc/nginx/includes/upstream.conf
Normal file
3
battybirdnet-pi/rootfs/etc/nginx/includes/upstream.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
upstream backend {
|
||||
server 127.0.0.1:80;
|
||||
}
|
||||
78
battybirdnet-pi/rootfs/etc/nginx/nginx.conf
Normal file
78
battybirdnet-pi/rootfs/etc/nginx/nginx.conf
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
# Run nginx in foreground.
|
||||
daemon off;
|
||||
|
||||
# This is run inside Docker.
|
||||
user root;
|
||||
|
||||
# Pid storage location.
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
# Set number of worker processes.
|
||||
worker_processes auto;
|
||||
|
||||
# Enables the use of JIT for regular expressions to speed-up their processing.
|
||||
pcre_jit on;
|
||||
|
||||
# Write error log to Hass.io add-on log.
|
||||
error_log /proc/1/fd/1 error;
|
||||
|
||||
# Load allowed environment vars
|
||||
env HASSIO_TOKEN;
|
||||
|
||||
# Load dynamic modules.
|
||||
include /etc/nginx/modules/*.conf;
|
||||
|
||||
# Max num of simultaneous connections by a worker process.
|
||||
events {
|
||||
worker_connections 8192;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/includes/mime.types;
|
||||
|
||||
# https://emby.media/community/index.php?/topic/93074-how-to-emby-with-nginx-with-windows-specific-tips-and-csp-options/
|
||||
server_names_hash_bucket_size 64;
|
||||
gzip_disable "msie6";
|
||||
gzip_comp_level 6;
|
||||
gzip_min_length 1100;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_proxied any;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/js
|
||||
text/xml
|
||||
text/javascript
|
||||
application/javascript
|
||||
application/x-javascript
|
||||
application/json
|
||||
application/xml
|
||||
application/rss+xml
|
||||
image/svg+xml;
|
||||
proxy_connect_timeout 1h;
|
||||
|
||||
log_format hassio '[$time_local] $status '
|
||||
'$http_x_forwarded_for($remote_addr) '
|
||||
'$request ($http_user_agent)';
|
||||
|
||||
access_log /proc/1/fd/1 hassio;
|
||||
client_max_body_size 4G;
|
||||
default_type application/octet-stream;
|
||||
gzip on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
tcp_nodelay on;
|
||||
tcp_nopush on;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
include /etc/nginx/includes/resolver.conf;
|
||||
include /etc/nginx/includes/upstream.conf;
|
||||
|
||||
include /etc/nginx/servers/*.conf;
|
||||
}
|
||||
47
battybirdnet-pi/rootfs/etc/nginx/servers/ingress.conf
Normal file
47
battybirdnet-pi/rootfs/etc/nginx/servers/ingress.conf
Normal file
@@ -0,0 +1,47 @@
|
||||
server {
|
||||
listen %%interface%%:%%port%% default_server;
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
|
||||
proxy_buffering off;
|
||||
auth_basic_user_file /home/pi/.htpasswd;
|
||||
|
||||
location /log {
|
||||
# Proxy pass
|
||||
proxy_pass http://localhost:8082;
|
||||
}
|
||||
|
||||
location /stats {
|
||||
# Proxy pass
|
||||
proxy_pass http://localhost:8082;
|
||||
}
|
||||
|
||||
location /terminal {
|
||||
# Proxy pass
|
||||
proxy_pass http://localhost:8082;
|
||||
}
|
||||
|
||||
location / {
|
||||
# Proxy pass
|
||||
proxy_pass http://localhost:8082;
|
||||
|
||||
# Next three lines allow websockets
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Correct base_url
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once off;
|
||||
sub_filter_types *;
|
||||
sub_filter /spectrogram %%ingress_entry%%/spectrogram;
|
||||
sub_filter /By_Date/ %%ingress_entry%%/By_Date/;
|
||||
sub_filter /Charts/ %%ingress_entry%%/Charts/;
|
||||
sub_filter /todays %%ingress_entry%%/todays;
|
||||
sub_filter href=\"/ href=\"%%ingress_entry%%/;
|
||||
sub_filter src=\"/ src=\"%%ingress_entry%%/;
|
||||
sub_filter hx-get=\"/ hx-get=\"%%ingress_entry%%/;
|
||||
sub_filter action=\"/ action=\"%%ingress_entry%%/;
|
||||
}
|
||||
|
||||
}
|
||||
124
battybirdnet-pi/rootfs/helpers/birdnet_to_mqtt.py
Normal file
124
battybirdnet-pi/rootfs/helpers/birdnet_to_mqtt.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#! /usr/bin/env python3
|
||||
# birdnet_to_mqtt.py
|
||||
#
|
||||
# Adapted from : https://gist.github.com/deepcoder/c309087c456fc733435b47d83f4113ff
|
||||
# Adapted from : https://gist.github.com/JuanMeeske/08b839246a62ff38778f701fc1da5554
|
||||
#
|
||||
# monitor the records in the syslog file for info from the birdnet system on birds that it detects
|
||||
# publish this data to mqtt
|
||||
#
|
||||
|
||||
import time
|
||||
import re
|
||||
import dateparser
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import paho.mqtt.client as mqtt
|
||||
import subprocess
|
||||
|
||||
# Setup basic configuration for logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# this generator function monitors the requested file handle for new lines added at its end
|
||||
# the newly added line is returned by the function
|
||||
def file_row_generator(s):
|
||||
while True :
|
||||
line = s.readline()
|
||||
if not line:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
yield line
|
||||
|
||||
# mqtt server
|
||||
mqtt_server = "%%mqtt_server%%" # server for mqtt
|
||||
mqtt_user = "%%mqtt_user%%" # Replace with your MQTT username
|
||||
mqtt_pass = "%%mqtt_pass%%" # Replace with your MQTT password
|
||||
mqtt_port = %%mqtt_port%% # port for mqtt
|
||||
|
||||
# mqtt topic for bird heard above threshold will be published
|
||||
mqtt_topic_confident_birds = 'birdnet'
|
||||
|
||||
# url base for website that will be used to look up info about bird
|
||||
bird_lookup_url_base = 'http://en.wikipedia.org/wiki/'
|
||||
|
||||
# regular expression patters used to decode the records from birdnet
|
||||
re_high_clean = re.compile(r'(?<=^\[birdnet_analysis\]\[INFO\] ).*?(?=\.mp3$)')
|
||||
|
||||
syslog = open('/proc/1/fd/1', 'r')
|
||||
|
||||
def on_connect(client, userdata, flags, rc, properties=None):
|
||||
""" Callback for when the client receives a CONNACK response from the server. """
|
||||
if rc == 0:
|
||||
logging.info("Connected to MQTT Broker!")
|
||||
else:
|
||||
logging.error(f"Failed to connect, return code {rc}\n")
|
||||
|
||||
def get_bird_code(scientific_name):
|
||||
with open('/home/pi/BirdNET-Pi/scripts/ebird.php', 'r') as file:
|
||||
data = file.read()
|
||||
|
||||
# Extract the array from the PHP file
|
||||
array_str = re.search(r'\$ebirds = \[(.*?)\];', data, re.DOTALL).group(1)
|
||||
|
||||
# Convert the PHP array to a Python dictionary
|
||||
bird_dict = {re.search(r'"(.*?)"', line).group(1): re.search(r'=> "(.*?)"', line).group(1)
|
||||
for line in array_str.split('\n') if '=>' in line}
|
||||
|
||||
# Return the corresponding value for the given bird's scientific name
|
||||
return bird_dict.get(scientific_name)
|
||||
|
||||
# this little hack is to make each received record for the all birds section unique
|
||||
# the date and time that the log returns is only down to the 1 second accuracy, do
|
||||
# you can get multiple records with same date and time, this will make Home Assistant not
|
||||
# think there is a new reading so we add a incrementing tenth of second to each record received
|
||||
ts_noise = 0.0
|
||||
|
||||
#try :
|
||||
# connect to MQTT server
|
||||
mqttc = mqtt.Client('birdnet_mqtt') # Create instance of client with client ID
|
||||
mqttc.username_pw_set(mqtt_user, mqtt_pass) # Use credentials
|
||||
mqttc.connect(mqtt_server, mqtt_port) # Connect to (broker, port, keepalive-time)
|
||||
mqttc.on_connect = on_connect
|
||||
mqttc.loop_start()
|
||||
|
||||
# call the generator function and process each line that is returned
|
||||
for row in file_row_generator(syslog):
|
||||
# bird found above confidence level found, process it
|
||||
if re_high_clean.search(row) :
|
||||
|
||||
# this slacker regular expression work, extracts the data about the bird found from the log line
|
||||
# I do the parse in two passes, because I did not know the re to do it in one!
|
||||
|
||||
raw_high_bird = re.search(re_high_clean, row)
|
||||
raw_high_bird = raw_high_bird.group(0)
|
||||
|
||||
# the fields we want are separated by semicolons, so split
|
||||
high_bird_fields = raw_high_bird.split(';')
|
||||
|
||||
# build a structure in python that will be converted to json
|
||||
bird = {}
|
||||
|
||||
# human time in this record is in two fields, date and time. They are human format
|
||||
# combine them together separated by a space and they turn the human data into a python
|
||||
# timestamp
|
||||
raw_ts = high_bird_fields[0] + ' ' + high_bird_fields[1]
|
||||
|
||||
#bird['ts'] = str(datetime.datetime.timestamp(dateparser.parse(raw_ts)))
|
||||
bird['Date'] = high_bird_fields[0]
|
||||
bird['Time'] = high_bird_fields[1]
|
||||
bird['ScientificName'] = high_bird_fields[2]
|
||||
bird['CommonName'] = high_bird_fields[3]
|
||||
bird['Confidence'] = high_bird_fields[4]
|
||||
bird['SpeciesCode'] = get_bird_code(high_bird_fields[2])
|
||||
bird['ClipName'] = high_bird_fields[11]
|
||||
|
||||
# build a url from scientific name of bird that can be used to lookup info about bird
|
||||
bird['url'] = bird_lookup_url_base + high_bird_fields[2].replace(' ', '_')
|
||||
|
||||
# convert to json string we can sent to mqtt
|
||||
json_bird = json.dumps(bird)
|
||||
|
||||
print('Posted to MQTT : ok')
|
||||
|
||||
mqttc.publish(mqtt_topic_confident_birds, json_bird, 1)
|
||||
5
battybirdnet-pi/rootfs/helpers/birdnet_to_mqtt.sh
Normal file
5
battybirdnet-pi/rootfs/helpers/birdnet_to_mqtt.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
echo "Starting service: mqtt automated publish"
|
||||
"$PYTHON_VIRTUAL_ENV" /usr/bin/birdnet_to_mqtt.py &>/proc/1/fd/1
|
||||
24
battybirdnet-pi/rootfs/helpers/caddy_ingress.sh
Normal file
24
battybirdnet-pi/rootfs/helpers/caddy_ingress.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Get values
|
||||
source /etc/birdnet/birdnet.conf
|
||||
|
||||
# Create ingress configuration for Caddyfile
|
||||
cat << EOF >> /etc/caddy/Caddyfile
|
||||
:8082 {
|
||||
root * ${EXTRACTED}
|
||||
file_server browse
|
||||
handle /By_Date/* {
|
||||
file_server browse
|
||||
}
|
||||
handle /Charts/* {
|
||||
file_server browse
|
||||
}
|
||||
reverse_proxy /stream localhost:8000
|
||||
php_fastcgi unix//run/php/php-fpm.sock
|
||||
reverse_proxy /log* localhost:8080
|
||||
reverse_proxy /stats* localhost:8501
|
||||
reverse_proxy /terminal* localhost:8888
|
||||
}
|
||||
EOF
|
||||
116
battybirdnet-pi/rootfs/helpers/convert_list.php
Normal file
116
battybirdnet-pi/rootfs/helpers/convert_list.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<p><strong>This tool will allow to convert on-the-fly species to compensate for model errors. It SHOULD NOT BE USED except if you know what you are doing, instead the model errors should be reported to the owner. However, it is still convenient for systematic biases that are confirmed through careful listening of samples, while waiting for the models to be updated.</strong></p>
|
||||
|
||||
<div class="customlabels column1">
|
||||
<form action="" method="GET" id="add">
|
||||
<input type="hidden" id="species" name="species">
|
||||
<h3>Specie to convert from :</h3>
|
||||
<!-- Input box to filter options in the first table -->
|
||||
<input type="text" id="species1Search" onkeyup="filterOptions('species1')" placeholder="Search for species...">
|
||||
<select name="species1" id="species1" size="25">
|
||||
<?php
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors',1);
|
||||
|
||||
$filename = './scripts/labels.txt';
|
||||
$eachline = file($filename, FILE_IGNORE_NEW_LINES);
|
||||
|
||||
foreach($eachline as $lines){echo
|
||||
"<option value=\"".$lines."\">$lines</option>";}
|
||||
?>
|
||||
</select>
|
||||
<br><br> <!-- Added a space between the two tables -->
|
||||
<h3>Specie to convert to :</h3>
|
||||
<!-- Input box to filter options in the second table -->
|
||||
<input type="text" id="species2Search" onkeyup="filterOptions('species2')" placeholder="Search for species...">
|
||||
<select name="species2" id="species2" size="25">
|
||||
<?php
|
||||
foreach($eachline as $lines){echo
|
||||
"<option value=\"".$lines."\">$lines</option>";}
|
||||
?>
|
||||
</select>
|
||||
<input type="hidden" name="add" value="add">
|
||||
</form>
|
||||
<div class="customlabels smaller">
|
||||
<button type="submit" name="view" value="Converted" form="add">>>ADD>></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="customlabels column2">
|
||||
<table><td>
|
||||
<button type="submit" name="view" value="Converted" form="add">>>ADD>></button>
|
||||
<br><br>
|
||||
<button type="submit" name="view" value="Converted" form="del">REMOVE</button>
|
||||
</td></table>
|
||||
</div>
|
||||
|
||||
<div class="customlabels column3" style="margin-top: 0;"> <!-- Removed the blank space above the table -->
|
||||
<form action="" method="GET" id="del">
|
||||
<h3>Converted Species List</h3>
|
||||
<select name="species[]" id="value2" multiple size="25">
|
||||
<?php
|
||||
$filename = './scripts/convert_species_list.txt'; // Changed the file path
|
||||
$eachline = file($filename, FILE_IGNORE_NEW_LINES);
|
||||
foreach($eachline as $lines){
|
||||
echo
|
||||
"<option value=\"".$lines."\">$lines</option>";
|
||||
}?>
|
||||
</select>
|
||||
<input type="hidden" name="del" value="del">
|
||||
</form>
|
||||
<div class="customlabels smaller">
|
||||
<button type="submit" name="view" value="Converted" form="del">REMOVE</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="hiddenSpecies" name="hiddenSpecies">
|
||||
|
||||
<script>
|
||||
document.getElementById("add").addEventListener("submit", function(event) {
|
||||
var speciesSelect1 = document.getElementById("species1");
|
||||
var speciesSelect2 = document.getElementById("species2");
|
||||
if (speciesSelect1.selectedIndex < 0 || speciesSelect2.selectedIndex < 0) {
|
||||
alert("Please select a species from both lists.");
|
||||
document.querySelector('.views').style.opacity = 1;
|
||||
event.preventDefault();
|
||||
} else {
|
||||
var selectedSpecies1 = speciesSelect1.options[speciesSelect1.selectedIndex].value;
|
||||
var selectedSpecies2 = speciesSelect2.options[speciesSelect2.selectedIndex].value;
|
||||
document.getElementById("species").value = selectedSpecies1 + ";" + selectedSpecies2;
|
||||
}
|
||||
});
|
||||
|
||||
// Store the original list of options in a variable
|
||||
var originalOptions = {};
|
||||
|
||||
// Function to filter options in a select element
|
||||
function filterOptions(id) {
|
||||
var input = document.getElementById(id + "Search");
|
||||
var filter = input.value.toUpperCase();
|
||||
var select = document.getElementById(id);
|
||||
var options = select.getElementsByTagName("option");
|
||||
|
||||
// If the original list of options for this select element hasn't been stored yet, store it
|
||||
if (!originalOptions[id]) {
|
||||
originalOptions[id] = Array.from(options).map(option => option.value);
|
||||
}
|
||||
|
||||
// Clear the select element
|
||||
while (select.firstChild) {
|
||||
select.removeChild(select.firstChild);
|
||||
}
|
||||
|
||||
// Populate the select element with the filtered labels
|
||||
originalOptions[id].forEach(label => {
|
||||
if (label.toUpperCase().indexOf(filter) > -1) {
|
||||
let option = document.createElement('option');
|
||||
option.value = label;
|
||||
option.text = label;
|
||||
select.appendChild(option);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
70
battybirdnet-pi/rootfs/helpers/spectral_analysis.py
Normal file
70
battybirdnet-pi/rootfs/helpers/spectral_analysis.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import numpy as np
|
||||
import scipy.io.wavfile as wavfile
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
import glob
|
||||
import sys # Import the sys module
|
||||
|
||||
from utils.helpers import get_settings
|
||||
|
||||
# Dependencies /usr/bin/pip install numpy scipy matplotlib
|
||||
|
||||
# Define the directory containing the WAV files
|
||||
conf = get_settings()
|
||||
input_directory = os.path.join(conf['RECS_DIR'], 'StreamData')
|
||||
output_directory = os.path.join(conf['RECS_DIR'], 'Extracted/Charts')
|
||||
|
||||
# Ensure the output directory exists
|
||||
if not os.path.exists(output_directory):
|
||||
os.makedirs(output_directory)
|
||||
|
||||
# Check if a command-line argument is provided
|
||||
if len(sys.argv) > 1:
|
||||
# If an argument is provided, use it as the file to analyze
|
||||
wav_files = [sys.argv[1]]
|
||||
else:
|
||||
# If no argument is provided, analyze all WAV files in the directory
|
||||
wav_files = glob.glob(os.path.join(input_directory, '*.wav'))
|
||||
|
||||
# Process each file
|
||||
for file_path in wav_files:
|
||||
# Load the WAV file
|
||||
sample_rate, audio_data = wavfile.read(file_path)
|
||||
|
||||
# If stereo, select only one channel
|
||||
if len(audio_data.shape) > 1:
|
||||
audio_data = audio_data[:, 0]
|
||||
|
||||
# Apply the Hamming window to the audio data
|
||||
hamming_window = np.hamming(len(audio_data))
|
||||
windowed_data = audio_data * hamming_window
|
||||
|
||||
# Compute the FFT of the windowed audio data
|
||||
audio_fft = np.fft.fft(windowed_data)
|
||||
audio_fft = np.abs(audio_fft)
|
||||
|
||||
# Compute the frequencies associated with the FFT values
|
||||
frequencies = np.fft.fftfreq(len(windowed_data), d=1/sample_rate)
|
||||
|
||||
# Select the range of interest
|
||||
idx = np.where((frequencies >= 150) & (frequencies <= 15000))
|
||||
|
||||
# Calculate the saturation threshold based on the bit depth
|
||||
bit_depth = audio_data.dtype.itemsize * 8
|
||||
max_amplitude = 2**(bit_depth - 1) - 1
|
||||
saturation_threshold = 0.8 * max_amplitude
|
||||
|
||||
# Plot the spectrum with a logarithmic Y-axis
|
||||
plt.figure(figsize=(10, 6))
|
||||
plt.semilogy(frequencies[idx], audio_fft[idx], label='Spectrum')
|
||||
plt.axhline(y=saturation_threshold, color='r', linestyle='--', label='Saturation Threshold')
|
||||
plt.xlabel("Frequency (Hz)")
|
||||
plt.ylabel("Amplitude (Logarithmic)")
|
||||
plt.title(f"Frequency Spectrum (150 - 15000 Hz) - {os.path.basename(file_path)}")
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
|
||||
# Save the plot as a PNG file
|
||||
output_filename = os.path.basename(file_path).replace('.wav', '_spectrum.png')
|
||||
plt.savefig(os.path.join(output_directory, output_filename))
|
||||
plt.close() # Close the figure to free memory
|
||||
62
battybirdnet-pi/rootfs/helpers/spectral_analysis.sh
Normal file
62
battybirdnet-pi/rootfs/helpers/spectral_analysis.sh
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
# Performs the recording from the specified RTSP stream or soundcard
|
||||
source /etc/birdnet/birdnet.conf
|
||||
|
||||
# Read the logging level from the configuration option
|
||||
LOGGING_LEVEL="${LogLevel_BirdnetRecordingService}"
|
||||
# If empty for some reason default to log level of error
|
||||
[ -z "$LOGGING_LEVEL" ] && LOGGING_LEVEL='error'
|
||||
# Additionally if we're at debug or info level then allow printing of script commands and variables
|
||||
if [ "$LOGGING_LEVEL" == "info" ] || [ "$LOGGING_LEVEL" == "debug" ];then
|
||||
# Enable printing of commands/variables etc to terminal for debugging
|
||||
set -x
|
||||
fi
|
||||
|
||||
[ -z "$RECORDING_LENGTH" ] && RECORDING_LENGTH=15
|
||||
[ -d "$RECS_DIR"/StreamData ] || mkdir -p "$RECS_DIR"/StreamData
|
||||
|
||||
filename="Spectrum_$(date "+%Y-%m-%d_%H:%M").wav"
|
||||
|
||||
if [ ! -z "$RTSP_STREAM" ];then
|
||||
# Explode the RSPT steam setting into an array so we can count the number we have
|
||||
RTSP_STREAMS_EXPLODED_ARRAY=("${RTSP_STREAM//,/ }")
|
||||
|
||||
while true;do
|
||||
|
||||
# Initially start the count off at 1 - our very first stream
|
||||
RTSP_STREAMS_STARTED_COUNT=1
|
||||
FFMPEG_PARAMS=""
|
||||
|
||||
# Loop over the streams
|
||||
for i in "${RTSP_STREAMS_EXPLODED_ARRAY[@]}"
|
||||
do
|
||||
# Map id used to map input to output (first stream being 0), this is 0 based in ffmpeg so decrement our counter (which is more human readable) by 1
|
||||
MAP_ID="$((RTSP_STREAMS_STARTED_COUNT-1))"
|
||||
# Build up the parameters to process the RSTP stream, including mapping for the output
|
||||
FFMPEG_PARAMS+="-vn -thread_queue_size 512 -i ${i} -map ${MAP_ID}:a:0 -t ${RECORDING_LENGTH} -acodec pcm_s16le -ac 2 -ar 48000 file:${RECS_DIR}/StreamData/$filename "
|
||||
# Increment counter
|
||||
((RTSP_STREAMS_STARTED_COUNT += 1))
|
||||
done
|
||||
|
||||
# Make sure were passing something valid to ffmpeg, ffmpeg will run interactive and control our loop by waiting ${RECORDING_LENGTH} between loops because it will stop once that much has been recorded
|
||||
if [ -n "$FFMPEG_PARAMS" ];then
|
||||
ffmpeg -hide_banner -loglevel "$LOGGING_LEVEL" -nostdin "$FFMPEG_PARAMS"
|
||||
fi
|
||||
|
||||
done
|
||||
else
|
||||
if pgrep arecord &> /dev/null ;then
|
||||
echo "Recording"
|
||||
else
|
||||
if [ -z "${REC_CARD}" ];then
|
||||
arecord -f S16_LE -c"${CHANNELS}" -r48000 -t wav --max-file-time "${RECORDING_LENGTH}"\
|
||||
--use-strftime "${RECS_DIR}"/StreamData/"$filename"
|
||||
else
|
||||
arecord -f S16_LE -c"${CHANNELS}" -r48000 -t wav --max-file-time "${RECORDING_LENGTH}"\
|
||||
-D "${REC_CARD}" --use-strftime "${RECS_DIR}"/StreamData/"$filename"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create the spectral analysis
|
||||
"$PYTHON_VIRTUAL_ENV" "$HOME"/BirdNET-Pi/scripts/spectral_analysis.py
|
||||
6851
battybirdnet-pi/rootfs/helpers/systemctl3.py
Normal file
6851
battybirdnet-pi/rootfs/helpers/systemctl3.py
Normal file
File diff suppressed because it is too large
Load Diff
72
battybirdnet-pi/rootfs/helpers/timedatectl
Normal file
72
battybirdnet-pi/rootfs/helpers/timedatectl
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to show the current timezone, with two alternative methods
|
||||
show_timezone() {
|
||||
# Check if the /etc/timezone file exists
|
||||
if [ -f /etc/timezone ]; then
|
||||
timezone=$(cat /etc/timezone)
|
||||
elif [ -f /etc/localtime ]; then
|
||||
timezone=$(readlink /etc/localtime)
|
||||
timezone=${timezone/\/usr\/share\/zoneinfo\//}
|
||||
else
|
||||
timezone="Cannot determine timezone."
|
||||
fi
|
||||
echo "$timezone"
|
||||
}
|
||||
|
||||
# Function to set the timezone
|
||||
set_timezone() {
|
||||
new_timezone="$1"
|
||||
echo "$new_timezone" | sudo tee /etc/timezone >/dev/null
|
||||
sudo ln -sf /usr/share/zoneinfo/"$new_timezone" /etc/localtime
|
||||
if [ -f /etc/environment ]; then sudo sed -i "/TZ/c\TZ=$new_timezone" /etc/environment; fi
|
||||
if [ -d /var/run/s6/container_environment ]; then echo "$new_timezone" | sudo tee /var/run/s6/container_environment/TZ > /dev/null; fi
|
||||
echo "$new_timezone"
|
||||
}
|
||||
|
||||
# Main script
|
||||
case "$1" in
|
||||
"set-ntp")
|
||||
case "$2" in
|
||||
"false")
|
||||
sudo systemctl stop systemd-timesyncd
|
||||
sudo systemctl disable systemd-timesyncd
|
||||
echo "NTP disabled"
|
||||
;;
|
||||
"true")
|
||||
sudo systemctl start systemd-timesyncd
|
||||
sudo systemctl enable systemd-timesyncd
|
||||
echo "NTP enabled"
|
||||
;;
|
||||
*)
|
||||
echo "Invalid argument for set-ntp. Use 'false' or 'true'."
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
"show")
|
||||
show_timezone
|
||||
;;
|
||||
"set-timezone")
|
||||
set_timezone "$2"
|
||||
;;
|
||||
*)
|
||||
# Get values
|
||||
local_time="$(date)"
|
||||
utc_time="$(date -u)"
|
||||
time_zone="$(show_timezone)"
|
||||
# Check if NTP is used
|
||||
if sudo systemctl status systemd-timesyncd | grep -q " active"; then
|
||||
ntp_status="yes"
|
||||
ntp_service="active"
|
||||
else
|
||||
ntp_status="no"
|
||||
ntp_service="inactive"
|
||||
fi
|
||||
# Print the information
|
||||
echo "Local time: $local_time"
|
||||
echo "Universal time: $utc_time"
|
||||
echo "Time zone: $time_zone"
|
||||
echo "Network time on: $ntp_status"
|
||||
echo "NTP service: $ntp_service"
|
||||
;;
|
||||
esac
|
||||
27
battybirdnet-pi/rootfs/helpers/views.add
Normal file
27
battybirdnet-pi/rootfs/helpers/views.add
Normal file
@@ -0,0 +1,27 @@
|
||||
if($_GET['view'] == "Converted"){
|
||||
ensure_authenticated();
|
||||
if(isset($_GET['species']) && isset($_GET['add'])){
|
||||
$file = './scripts/convert_species_list.txt';
|
||||
$str = file_get_contents("$file");
|
||||
$str = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $str);
|
||||
file_put_contents("$file", "$str");
|
||||
// Write $_GET['species'] to the file
|
||||
file_put_contents("./scripts/convert_species_list.txt", htmlspecialchars_decode($_GET['species'], ENT_QUOTES)."\n", FILE_APPEND);
|
||||
} elseif (isset($_GET['species']) && isset($_GET['del'])){
|
||||
$file = './scripts/convert_species_list.txt';
|
||||
$str = file_get_contents("$file");
|
||||
$str = preg_replace('/^\h*\v+/m', '', $str);
|
||||
file_put_contents("$file", "$str");
|
||||
foreach($_GET['species'] as $selectedOption) {
|
||||
$content = file_get_contents("./scripts/convert_species_list.txt");
|
||||
$newcontent = str_replace($selectedOption, "", "$content");
|
||||
$newcontent = str_replace(htmlspecialchars_decode($selectedOption, ENT_QUOTES), "", "$content");
|
||||
file_put_contents("./scripts/convert_species_list.txt", "$newcontent");
|
||||
}
|
||||
$file = './scripts/convert_species_list.txt';
|
||||
$str = file_get_contents("$file");
|
||||
$str = preg_replace('/^\h*\v+/m', '', $str);
|
||||
file_put_contents("$file", "$str");
|
||||
}
|
||||
include('./scripts/convert_list.php');
|
||||
}
|
||||
Reference in New Issue
Block a user