Update install.sh
This commit is contained in:
parent
0970405102
commit
cf032c5f8d
|
@ -0,0 +1,4 @@
|
||||||
|
var
|
||||||
|
.venv
|
||||||
|
.github
|
||||||
|
.ideas
|
|
@ -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
|
set -e
|
||||||
|
|
||||||
# Load config file
|
# Load config file
|
||||||
config_filepath="./config/analyzer.conf"
|
config_filepath="./config/birdnet.conf"
|
||||||
|
|
||||||
if [ -f "$config_filepath" ]; then
|
if [ -f "$config_filepath" ]; then
|
||||||
source "$config_filepath"
|
source "$config_filepath"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
# Load config file
|
# Load config file
|
||||||
config_filepath="./config/analyzer.conf"
|
config_filepath="./config/birdnet.conf"
|
||||||
|
|
||||||
if [ -f "$config_filepath" ]; then
|
if [ -f "$config_filepath" ]; then
|
||||||
source "$config_filepath"
|
source "$config_filepath"
|
||||||
|
|
|
@ -170,7 +170,7 @@ Launch and enable icecast:
|
||||||
sudo systemctl enable --now icecast2
|
sudo systemctl enable --now icecast2
|
||||||
```
|
```
|
||||||
|
|
||||||
Adapt `config/analyzer.conf` to this configuration:
|
Adapt `config/birdnet.conf` to this configuration:
|
||||||
|
|
||||||
```conf
|
```conf
|
||||||
ICECAST_USER=source
|
ICECAST_USER=source
|
||||||
|
|
2
TODO
2
TODO
|
@ -1,5 +1,5 @@
|
||||||
- Fix clean script
|
|
||||||
- Fix service manager
|
- Fix service manager
|
||||||
- Add docker support
|
- Add docker support
|
||||||
- Species i18n
|
- Species i18n
|
||||||
- File purge policy
|
- File purge policy
|
||||||
|
- Add and test RTSP support
|
|
@ -8,7 +8,7 @@ debug() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
config_filepath="./config/analyzer.conf"
|
config_filepath="./config/birdnet.conf"
|
||||||
|
|
||||||
if [ -f "$config_filepath" ]; then
|
if [ -f "$config_filepath" ]; then
|
||||||
source "$config_filepath"
|
source "$config_filepath"
|
||||||
|
|
|
@ -13,7 +13,7 @@ debug() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
config_filepath="./config/analyzer.conf"
|
config_filepath="./config/birdnet.conf"
|
||||||
|
|
||||||
if [ -f "$config_filepath" ]; then
|
if [ -f "$config_filepath" ]; then
|
||||||
source "$config_filepath"
|
source "$config_filepath"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
# set -x
|
# set -x
|
||||||
|
|
||||||
config_filepath="./config/analyzer.conf"
|
config_filepath="./config/birdnet.conf"
|
||||||
|
|
||||||
if [ -f "$config_filepath" ]; then
|
if [ -f "$config_filepath" ]; then
|
||||||
source "$config_filepath"
|
source "$config_filepath"
|
||||||
|
|
|
@ -16,7 +16,7 @@ debug() {
|
||||||
source ./daemon/database/scripts/database.sh
|
source ./daemon/database/scripts/database.sh
|
||||||
|
|
||||||
# Load config
|
# Load config
|
||||||
source ./config/analyzer.conf
|
source ./config/birdnet.conf
|
||||||
# Check config
|
# Check config
|
||||||
if [[ -z ${CHUNK_FOLDER} ]]; then
|
if [[ -z ${CHUNK_FOLDER} ]]; then
|
||||||
echo "CHUNK_FOLDER is not set"
|
echo "CHUNK_FOLDER is not set"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
DEBUG=${DEBUG:-0}
|
DEBUG=${DEBUG:-1}
|
||||||
|
|
||||||
export PULSE_RUNTIME_PATH="/run/user/$(id -u)/pulse/"
|
export PULSE_RUNTIME_PATH="/run/user/$(id -u)/pulse/"
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ record_loop() {
|
||||||
DURATION=$2
|
DURATION=$2
|
||||||
debug "New recording loop."
|
debug "New recording loop."
|
||||||
while true; do
|
while true; do
|
||||||
record $DEVICE $DURATION
|
record_device $DEVICE $DURATION
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ record_device() {
|
||||||
ffmpeg $FFMPEG_OPTIONS -f pulse -i ${DEVICE} -t ${DURATION} -af "volume=$RECORDING_AMPLIFY" file:${CHUNK_FOLDER}/in/birdnet_$(date "+%Y%m%d_%H%M%S").wav
|
ffmpeg $FFMPEG_OPTIONS -f pulse -i ${DEVICE} -t ${DURATION} -af "volume=$RECORDING_AMPLIFY" file:${CHUNK_FOLDER}/in/birdnet_$(date "+%Y%m%d_%H%M%S").wav
|
||||||
}
|
}
|
||||||
|
|
||||||
config_filepath="./config/analyzer.conf"
|
config_filepath="./config/birdnet.conf"
|
||||||
|
|
||||||
if [ -f "$config_filepath" ]; then
|
if [ -f "$config_filepath" ]; then
|
||||||
source "$config_filepath"
|
source "$config_filepath"
|
||||||
|
@ -56,18 +56,18 @@ check_folder
|
||||||
|
|
||||||
[ -z $RECORDING_DURATION ] && RECORDING_DURATION=15
|
[ -z $RECORDING_DURATION ] && RECORDING_DURATION=15
|
||||||
|
|
||||||
if [[ -z $AUDIO_DEVICE ]]; then
|
if [[ $AUDIO_RECORDING = "true" ]]; then
|
||||||
|
debug "Recording with on board device"
|
||||||
|
if [[ -z $AUDIO_DEVICE ]]; then
|
||||||
echo "AUDIO_DEVICE is not set"
|
echo "AUDIO_DEVICE is not set"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
record_loop $AUDIO_DEVICE $RECORDING_DURATION
|
||||||
if [[ $AUDIO_RECORDING = "true" ]]; then
|
|
||||||
record_loop $AUDIO_DEVICE $RECORDING_DURATION &
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $AUDIO_STATIONS = "true" ]]; then
|
if [[ $AUDIO_STATIONS = "true" ]]; then
|
||||||
for station in $(ls ./config/stations/*.conf); do
|
for station in $(ls ./config/stations/*.conf); do
|
||||||
source $station
|
source $station
|
||||||
record_stream $STATION_URL $RECORDING_DURATION &
|
record_stream $STATION_URL $RECORDING_DURATION
|
||||||
done
|
done
|
||||||
fi
|
fi
|
|
@ -18,7 +18,7 @@ stream() {
|
||||||
-f mp3 "icecast://source:${ICECAST_PASSWORD}@${ICECAST_HOST}:${ICECAST_PORT}/${ICECAST_MOUNT}" -listen 1
|
-f mp3 "icecast://source:${ICECAST_PASSWORD}@${ICECAST_HOST}:${ICECAST_PORT}/${ICECAST_MOUNT}" -listen 1
|
||||||
}
|
}
|
||||||
|
|
||||||
config_filepath="./config/analyzer.conf"
|
config_filepath="./config/birdnet.conf"
|
||||||
|
|
||||||
if [ -f "$config_filepath" ]; then
|
if [ -f "$config_filepath" ]; then
|
||||||
source "$config_filepath"
|
source "$config_filepath"
|
||||||
|
|
|
@ -8,7 +8,7 @@ debug() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Load config file
|
# Load config file
|
||||||
config_filepath="./config/analyzer.conf"
|
config_filepath="./config/birdnet.conf"
|
||||||
|
|
||||||
if [ -f "$config_filepath" ]; then
|
if [ -f "$config_filepath" ]; then
|
||||||
source "$config_filepath"
|
source "$config_filepath"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
# Load config file
|
# Load config file
|
||||||
config_filepath="./config/analyzer.conf"
|
config_filepath="./config/birdnet.conf"
|
||||||
|
|
||||||
if [ -f "$config_filepath" ]; then
|
if [ -f "$config_filepath" ]; then
|
||||||
source "$config_filepath"
|
source "$config_filepath"
|
||||||
|
|
|
@ -7,7 +7,7 @@ verbose = False
|
||||||
|
|
||||||
"""Load config"""
|
"""Load config"""
|
||||||
def load_conf():
|
def load_conf():
|
||||||
with open("./config/analyzer.conf", "r") as f:
|
with open("./config/birdnet.conf", "r") as f:
|
||||||
conf = f.readlines()
|
conf = f.readlines()
|
||||||
res = dict(map(str.strip, sub.split('=', 1)) for sub in conf if '=' in sub)
|
res = dict(map(str.strip, sub.split('=', 1)) for sub in conf if '=' in sub)
|
||||||
return res
|
return res
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
source ./config/analyzer.conf
|
source ./config/birdnet.conf
|
||||||
|
|
||||||
# Create database in case it was not created yet
|
# Create database in case it was not created yet
|
||||||
./daemon/database/scripts/create.sh
|
./daemon/database/scripts/create.sh
|
||||||
|
@ -23,7 +23,7 @@ get_taxon_id() {
|
||||||
}
|
}
|
||||||
|
|
||||||
insert_taxon() {
|
insert_taxon() {
|
||||||
query $DATABASE "INSERT INTO taxon (scientific_name, common_name) VALUES (\"$1\", \"$2\")"
|
query "INSERT INTO taxon (scientific_name, common_name) VALUES (\"$1\", \"$2\")"
|
||||||
}
|
}
|
||||||
|
|
||||||
insert_location() {
|
insert_location() {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
from curses import def_prog_mode
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from xml.sax.handler import feature_external_ges
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from matplotlib.colors import LogNorm
|
from matplotlib.colors import LogNorm
|
||||||
|
@ -12,75 +10,58 @@ from datetime import datetime
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"readings": 10,
|
"readings": 10,
|
||||||
"palette": "Greens",
|
"palette": "Greens",
|
||||||
|
"db": "./var/db.sqlite",
|
||||||
|
"date": "2022-08-14"
|
||||||
}
|
}
|
||||||
|
|
||||||
db = None
|
db = sqlite3.connect(CONFIG['db'])
|
||||||
def get_database():
|
|
||||||
global db
|
|
||||||
if db is None:
|
|
||||||
db = sqlite3.connect('/home/ortion/Desktop/db.sqlite')
|
|
||||||
return db
|
|
||||||
|
|
||||||
|
df = pd.read_sql_query("""SELECT common_name, date, location_id, confidence
|
||||||
def get_detection_hourly(date):
|
|
||||||
db = get_database()
|
|
||||||
df = pd.read_sql_query("""SELECT common_name, date, location_id, confidence
|
|
||||||
FROM observation
|
FROM observation
|
||||||
INNER JOIN taxon
|
INNER JOIN taxon
|
||||||
ON observation.taxon_id = taxon.taxon_id""", db)
|
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']]
|
||||||
|
|
||||||
df['date'] = pd.to_datetime(df['date'])
|
top_on_date = (df_on_date['common_name'].value_counts()[:CONFIG['readings']])
|
||||||
df['hour'] = df['date'].dt.hour
|
|
||||||
df['date'] = df['date'].dt.date
|
|
||||||
df['date'] = df['date'].astype(str)
|
|
||||||
|
|
||||||
df_on_date = df[df['date'] == date]
|
df_top_on_date = df_on_date[df_on_date['common_name'].isin(top_on_date.index)]
|
||||||
return df_on_date
|
|
||||||
|
|
||||||
|
# Create a figure with 2 subplots
|
||||||
def get_top_species(df, limit=10):
|
fig, axs = plt.subplots(1, 2, figsize=(15, 4), gridspec_kw=dict(
|
||||||
return df['common_name'].value_counts()[:CONFIG['readings']]
|
|
||||||
|
|
||||||
|
|
||||||
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)]
|
|
||||||
|
|
||||||
|
|
||||||
def get_frequence_order(df, limit=10):
|
|
||||||
pd.value_counts(df['common_name']).iloc[:limit]
|
|
||||||
|
|
||||||
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]))
|
width_ratios=[3, 6]))
|
||||||
plt.subplots_adjust(left=None, bottom=None, right=None,
|
plt.subplots_adjust(left=None, bottom=None, right=None,
|
||||||
top=None, wspace=0, hspace=0)
|
top=None, wspace=0, hspace=0)
|
||||||
|
|
||||||
frequencies_order = get_frequence_order(df_detections, limit=CONFIG["readings"])
|
# Get species frequencies
|
||||||
# Get min max confidences
|
frequencies_order = pd.value_counts(df['common_name']).iloc[:CONFIG['readings']].index
|
||||||
confidence_minmax = df_detections.groupby('common_name')['confidence'].max()
|
# Get min max confidences
|
||||||
# Norm values for color palette
|
confidence_minmax = df_top_on_date.groupby('common_name')['confidence'].max()
|
||||||
norm = plt.Normalize(confidence_minmax.values.min(),
|
confidence_minmax = confidence_minmax.reindex(frequencies_order)
|
||||||
|
# Norm values for color palette
|
||||||
|
norm = plt.Normalize(confidence_minmax.values.min(),
|
||||||
confidence_minmax.values.max())
|
confidence_minmax.values.max())
|
||||||
colors = plt.cm.Greens(norm(confidence_minmax))
|
|
||||||
plot = sns.countplot(y='common_name', data=df_top_detections, palette=colors, order=frequencies_order, ax=axs[0])
|
|
||||||
|
|
||||||
plot.set(ylabel=None)
|
colors = plt.cm.Greens(norm(confidence_minmax))
|
||||||
plot.set(xlabel="Detections")
|
plot = sns.countplot(y='common_name', data=df_top_on_date, palette=colors, order=frequencies_order, ax=axs[0])
|
||||||
|
|
||||||
heat = pd.crosstab(df_top_detections['common_name'], df_top_detections['hour'])
|
plot.set(ylabel=None)
|
||||||
# Order heatmap Birds by frequency of occurrance
|
plot.set(xlabel="Detections")
|
||||||
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 = pd.crosstab(df_top_on_date['common_name'], df_top_on_date['hour'])
|
||||||
heat_frame = pd.DataFrame(data=0, index=heat.index, columns=hours_in_day)
|
# Order heatmap Birds by frequency of occurrance
|
||||||
heat = (heat + heat_frame).fillna(0)
|
heat.index = pd.CategoricalIndex(heat.index, categories=frequencies_order)
|
||||||
|
heat.sort_index(level=0, inplace=True)
|
||||||
|
|
||||||
# Generate heatmap plot
|
hours_in_day = pd.Series(data=range(0, 24))
|
||||||
plot = sns.heatmap(
|
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,
|
heat,
|
||||||
norm=LogNorm(),
|
norm=LogNorm(),
|
||||||
annot=True,
|
annot=True,
|
||||||
|
@ -94,27 +75,18 @@ def presence_chart(date, filename):
|
||||||
linewidth=0.5,
|
linewidth=0.5,
|
||||||
linecolor="Grey",
|
linecolor="Grey",
|
||||||
ax=axs[1],
|
ax=axs[1],
|
||||||
yticklabels=False
|
yticklabels=False)
|
||||||
)
|
plot.set_xticklabels(plot.get_xticklabels(), rotation=0, size=7)
|
||||||
plot.set_xticklabels(plot.get_xticklabels(), rotation=0, size=7)
|
|
||||||
|
|
||||||
for _, spine in plot.spines.items():
|
for _, spine in plot.spines.items():
|
||||||
spine.set_visible(True)
|
spine.set_visible(True)
|
||||||
|
|
||||||
plot.set(ylabel=None)
|
plot.set(ylabel=None)
|
||||||
plot.set(xlabel="Hour of day")
|
plot.set(xlabel="Hour of day")
|
||||||
fig.subplots_adjust(top=0.9)
|
fig.subplots_adjust(top=0.9)
|
||||||
plt.suptitle(f"Top {CONFIG['readings']} species (Updated on {datetime.now().strftime('%Y/%m-%d %H:%M')})")
|
plt.suptitle(f"Top {CONFIG['readings']} species (Updated on {datetime.now().strftime('%Y/%m-%d %H:%M')})")
|
||||||
|
|
||||||
plt.savefig(filename)
|
plt.savefig(f"./var/charts/chart_{CONFIG['date']}.png", dpi=300)
|
||||||
plt.close()
|
plt.close()
|
||||||
|
|
||||||
def main():
|
db.close()
|
||||||
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()
|
|
|
@ -10,11 +10,13 @@ WORKDIR /home/birdnet
|
||||||
# Upgrade system
|
# Upgrade system
|
||||||
RUN apt-get update && apt-get upgrade -y
|
RUN apt-get update && apt-get upgrade -y
|
||||||
|
|
||||||
# Install curl
|
# Install sudo
|
||||||
RUN apt-get install -y \
|
RUN apt-get install -y \
|
||||||
curl \
|
sudo \
|
||||||
sudo
|
git
|
||||||
|
|
||||||
RUN curl -sL https://raw.githubusercontent.com/UncleSamulus/BirdNET-stream/master/install.sh | bash
|
COPY ./install.sh install.sh
|
||||||
|
|
||||||
|
RUN bash ./install.sh
|
||||||
|
|
||||||
USER birdnet
|
USER birdnet
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/UncleSamulus/BirdNET-stream.git
|
git clone https://github.com/UncleSamulus/BirdNET-stream.git
|
||||||
cd ./BirdNET-stream/docker/all
|
cd ./BirdNET-stream/docker/all
|
||||||
docker build -t "birdnet_all:latest" .
|
docker build -t "birdnet_all:latest" -f ./docker/all/Dockerfile .
|
||||||
```
|
```
|
||||||
|
|
||||||
If `docker` command does not work because of unsufficient permissions, you could add your user to `docker` group:
|
If `docker` command does not work because of unsufficient permissions, you could add your user to `docker` group:
|
||||||
|
|
101
install.sh
101
install.sh
|
@ -5,13 +5,13 @@ set -e
|
||||||
|
|
||||||
DEBUG=${DEBUG:-0}
|
DEBUG=${DEBUG:-0}
|
||||||
|
|
||||||
REQUIREMENTS="git ffmpeg python3-pip python3-dev"
|
REQUIREMENTS="git ffmpeg python3 python3-pip python3-dev"
|
||||||
REPOSITORY=${REPOSITORY:-https://github.com/UncleSamulus/BirdNET-stream.git}
|
REPOSITORY=${REPOSITORY:-https://github.com/UncleSamulus/BirdNET-stream.git}
|
||||||
|
WORKDIR="$(pwd)/BirdNET-stream"
|
||||||
|
PYTHON_VENV="./.venv/birdnet-stream"
|
||||||
|
|
||||||
debug() {
|
debug() {
|
||||||
if [ $DEBUG -eq 1 ]; then
|
[[ $DEBUG -eq 1 ]] && echo "$@"
|
||||||
echo "$1"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
install_requirements() {
|
install_requirements() {
|
||||||
|
@ -19,52 +19,53 @@ install_requirements() {
|
||||||
# Install requirements
|
# Install requirements
|
||||||
missing_requirements=""
|
missing_requirements=""
|
||||||
for requirement in $requirements; do
|
for requirement in $requirements; do
|
||||||
if ! dpkg -s $requirement >/dev/null 2>&1; then
|
if ! dpkg -s "$requirement" >/dev/null 2>&1; then
|
||||||
missing_requirements="$missing_requirements $requirement"
|
missing_requirements="$missing_requirements $requirement"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [ -n "$missing_requirements" ]; then
|
if [[ -n "$missing_requirements" ]]; then
|
||||||
debug "Installing missing requirements: $missing_requirements"
|
debug "Installing missing requirements: $missing_requirements"
|
||||||
sudo apt-get install -y $missing_requirements
|
sudo apt-get install -y "$missing_requirements"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install BirdNET-stream
|
# Install BirdNET-stream
|
||||||
install_birdnetstream() {
|
install_birdnetstream() {
|
||||||
# Check if repo is not already installed
|
# Check if repo is not already installed
|
||||||
workdir=$(pwd)
|
if [[ -d "$DIR" ]]; then
|
||||||
if [ -d "$workdir/BirdNET-stream" ]; then
|
debug "BirdNET-stream is already installed, use update script (not implemented yet)"
|
||||||
debug "BirdNET-stream is already installed"
|
exit 1
|
||||||
else
|
else
|
||||||
|
debug "Installing BirdNET-stream"
|
||||||
|
debug "Creating BirdNET-stream directory"
|
||||||
|
mkdir -p "$WORKDIR"
|
||||||
# Clone BirdNET-stream
|
# Clone BirdNET-stream
|
||||||
|
cd "$WORKDIR"
|
||||||
debug "Cloning BirdNET-stream from $REPOSITORY"
|
debug "Cloning BirdNET-stream from $REPOSITORY"
|
||||||
git clone --recurse-submodules $REPOSITORY
|
git clone --recurse-submodules "$REPOSITORY" .
|
||||||
# Install BirdNET-stream
|
debug "Creating python3 virtual environment $PYTHON_VENV"
|
||||||
fi
|
|
||||||
cd BirdNET-stream
|
|
||||||
debug "Creating python3 virtual environment '$PYTHON_VENV'"
|
|
||||||
python3 -m venv $PYTHON_VENV
|
python3 -m venv $PYTHON_VENV
|
||||||
debug "Activating $PYTHON_VENV"
|
debug "Activating $PYTHON_VENV"
|
||||||
source .venv/birdnet-stream/bin/activate
|
source "$PYTHON_VENV/bin/activate"
|
||||||
debug "Installing python packages"
|
debug "Installing python packages"
|
||||||
pip install -U pip
|
pip3 install -U pip
|
||||||
pip install -r requirements.txt
|
pip3 install -r requirements.txt
|
||||||
|
debug "Creating ./var directory"
|
||||||
|
mkdir -p ./var/{charts,chunks/{in,out}}
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install systemd services
|
# Install systemd services
|
||||||
install_birdnetstream_services() {
|
install_birdnetstream_services() {
|
||||||
cd BirdNET-stream
|
GROUP=birdnet
|
||||||
DIR=$(pwd)
|
|
||||||
GROUP=$USER
|
|
||||||
debug "Setting up BirdNET stream systemd services"
|
debug "Setting up BirdNET stream systemd services"
|
||||||
services="birdnet_recording.service birdnet_analyzis.service birdnet_miner.timer birdnet_miner.service birdnet_plotter.service birdnet_plotter.timer"
|
services="birdnet_recording.service birdnet_analyzis.service birdnet_miner.timer birdnet_miner.service birdnet_plotter.service birdnet_plotter.timer"
|
||||||
read -r -a services_array <<<"$services"
|
read -r -a services_array <<< "$services"
|
||||||
|
for service in "${services_array[@]}"; do
|
||||||
for service in ${services_array[@]}; do
|
sudo cp "daemon/systemd/templates/$service" "/etc/systemd/system/"
|
||||||
sudo cp daemon/systemd/templates/$service /etc/systemd/system/
|
|
||||||
variables="DIR USER GROUP"
|
variables="DIR USER GROUP"
|
||||||
for variable in $variables; do
|
for variable in $variables; do
|
||||||
sudo sed -i "s|<$variable>|${!variable}|g" /etc/systemd/system/$service
|
sudo sed -i "s|<$variable>|${!variable}|g" "/etc/systemd/system/$service"
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
|
@ -90,15 +91,17 @@ install_php8() {
|
||||||
}
|
}
|
||||||
|
|
||||||
install_composer() {
|
install_composer() {
|
||||||
|
cd /tmp
|
||||||
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"\nphp -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"\nphp composer-setup.php\nphp -r "unlink('composer-setup.php');"
|
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"\nphp -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"\nphp composer-setup.php\nphp -r "unlink('composer-setup.php');"
|
||||||
sudo mv /composer.phar /usr/local/bin/composer
|
sudo mv ./composer.phar /usr/local/bin/composer
|
||||||
|
cd -
|
||||||
}
|
}
|
||||||
|
|
||||||
install_nodejs() {
|
install_nodejs() {
|
||||||
# Install nodejs
|
# Install nodejs
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
|
||||||
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
|
export NVM_DIR="$([[ -z "${XDG_CONFIG_HOME-}" ]] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
[[ -s "$NVM_DIR/nvm.sh" ]] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
||||||
nvm install 16
|
nvm install 16
|
||||||
nvm use 16
|
nvm use 16
|
||||||
install_requirements "npm"
|
install_requirements "npm"
|
||||||
|
@ -116,30 +119,60 @@ install_web_interface() {
|
||||||
# Install nodejs 16
|
# Install nodejs 16
|
||||||
install_nodejs
|
install_nodejs
|
||||||
# Install Symfony web app
|
# Install Symfony web app
|
||||||
cd BirdNET-stream
|
cd "$WORKDIR"
|
||||||
cd www
|
cd www
|
||||||
debug "Creating nginx configuration"
|
debug "Creating nginx configuration"
|
||||||
cp nginx.conf /etc/nginx/sites-available/birdnet-stream.conf
|
sudo cp nginx.conf /etc/nginx/sites-available/birdnet-stream.conf
|
||||||
sudo mkdir /var/log/nginx/birdnet/
|
sudo mkdir /var/log/nginx/birdnet/
|
||||||
echo "Info: Please edit /etc/nginx/sites-available/birdnet-stream.conf to set the correct server name and paths"
|
sudo ln -s /etc/nginx/sites-available/birdnet-stream.conf /etc/nginx/sites-available/birdnet-stream.conf
|
||||||
sudo ln -s /etc/nginx/sites-available/birdnet-stream.conf /etc/nginx/sites-enabled/birdnet-stream.conf
|
debug "Info: Please edit /etc/nginx/sites-available/birdnet-stream.conf to set the correct server name and paths"
|
||||||
sudo systemctl enable --now nginx
|
sudo systemctl enable --now nginx
|
||||||
sudo systemctl restart nginx
|
sudo systemctl restart nginx
|
||||||
debug "Retrieving composer dependencies"
|
debug "Retrieving composer dependencies"
|
||||||
composer install
|
composer install
|
||||||
|
debug "PHP dependencies installed"
|
||||||
debug "Installing nodejs dependencies"
|
debug "Installing nodejs dependencies"
|
||||||
yarn install
|
yarn install
|
||||||
|
debug "npm dependencies installed"
|
||||||
debug "Building assets"
|
debug "Building assets"
|
||||||
yarn build
|
yarn build
|
||||||
|
debug "Webpack assets built"
|
||||||
debug "Web interface is available"
|
debug "Web interface is available"
|
||||||
debug "Please restart nginx after double check of /etc/nginx/sites-available/birdnet-stream.conf"
|
debug "Please restart nginx after double check of /etc/nginx/sites-available/birdnet-stream.conf"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
change_value() {
|
||||||
|
local variable_name
|
||||||
|
variable_name="$1"
|
||||||
|
local variable_new_value
|
||||||
|
variable_new_value="$2"
|
||||||
|
local variable_filepath="$3"
|
||||||
|
sed -i "s|$variable_name=.*|$variable_name=\"$variable_new_value\"|g" "$variable_filepath"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_config() {
|
||||||
|
debug "Updating config"
|
||||||
|
cd "$WORKDIR"
|
||||||
|
cp ./config/birdnet.conf.example ./config/birdnet.conf
|
||||||
|
config_filepath="$WORKDIR/config/birdnet.conf"
|
||||||
|
change_value "DIR" "$WORKDIR" "$config_filepath"
|
||||||
|
change_value "PYTHON_VENV" "$PYTHON_VENV" "$config_filepath"
|
||||||
|
change_value "AUDIO_RECORDING" "true" "$config_filepath"
|
||||||
|
source "$config_filepath"
|
||||||
|
cd www
|
||||||
|
debug "Setup webapp .env"
|
||||||
|
cp .env.local.example .env.local
|
||||||
|
change_value "RECORDS_DIR" "$CHUNKS_FOLDER" ".env.local"
|
||||||
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
install_requirements $REQUIREMENTS
|
install_requirements "$REQUIREMENTS"
|
||||||
install_birdnetstream
|
install_birdnetstream
|
||||||
install_birdnetstream_services
|
install_birdnetstream_services
|
||||||
install_web_interface
|
install_web_interface
|
||||||
|
install_config
|
||||||
|
update_permissions "$WORKDIR"
|
||||||
|
debug "Installation done"
|
||||||
}
|
}
|
||||||
|
|
||||||
main
|
main
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
|
# Fix permissions on BirdNET-stream files when messed up
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DEBUG=${DEBUG:-1}
|
DEBUG=${DEBUG:-0}
|
||||||
debug() {
|
debug() {
|
||||||
if [ $DEBUG -eq 1 ]; then
|
[ $DEBUG -eq 1 ] && echo "$@"
|
||||||
echo "$1"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config_filepath="./config/analyzer.conf"
|
config_filepath="./config/birdnet.conf"
|
||||||
|
|
||||||
if [ -f "$config_filepath" ]; then
|
if [ -f "$config_filepath" ]; then
|
||||||
source "$config_filepath"
|
source "$config_filepath"
|
||||||
|
@ -18,7 +16,6 @@ else
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
GROUP=birdnet
|
GROUP=birdnet
|
||||||
|
|
||||||
sudo chown -R $USER:$GROUP $CHUNK_FOLDER
|
sudo chown -R $USER:$GROUP $CHUNK_FOLDER
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"core-js": "^3.23.0",
|
"core-js": "^3.23.0",
|
||||||
"git-revision-webpack-plugin": "^5.0.0",
|
"git-revision-webpack-plugin": "^5.0.0",
|
||||||
"regenerator-runtime": "^0.13.9",
|
"regenerator-runtime": "^0.13.9",
|
||||||
|
"webpack": "^5.74.0",
|
||||||
"webpack-notifier": "^1.15.0"
|
"webpack-notifier": "^1.15.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -7630,7 +7631,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
|
||||||
"integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
|
"integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/eslint-scope": "^3.7.3",
|
"@types/eslint-scope": "^3.7.3",
|
||||||
"@types/estree": "^0.0.51",
|
"@types/estree": "^0.0.51",
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"core-js": "^3.23.0",
|
"core-js": "^3.23.0",
|
||||||
"git-revision-webpack-plugin": "^5.0.0",
|
"git-revision-webpack-plugin": "^5.0.0",
|
||||||
"regenerator-runtime": "^0.13.9",
|
"regenerator-runtime": "^0.13.9",
|
||||||
|
"webpack": "^5.74.0",
|
||||||
"webpack-notifier": "^1.15.0"
|
"webpack-notifier": "^1.15.0"
|
||||||
},
|
},
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
|
|
|
@ -10,8 +10,8 @@ class DisksController extends AbstractController
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/disks/", name="disks_index")
|
* @Route("/disks/", name="disks")
|
||||||
* @Route("{_locale}/disks/", name="disks_index_i18n")
|
* @Route("{_locale}/disks/", name="disks_i18n")
|
||||||
*/
|
*/
|
||||||
public function index() {
|
public function index() {
|
||||||
return $this->render('disks/index.html.twig', [
|
return $this->render('disks/index.html.twig', [
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{% extends "base.html.twig" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{{ "Disk usage"|trans }}</h2>
|
<h2>{{ "Disk usage"|trans }}</h2>
|
||||||
<div class="disk">
|
<div class="disk">
|
||||||
|
|
|
@ -38,6 +38,9 @@
|
||||||
} %}
|
} %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<span class="dropdown-toggle">{{ "Tools"|trans }}</span>
|
||||||
|
<ul class="dropdown-content">
|
||||||
{% include 'utils/nav-item.html.twig' with {
|
{% include 'utils/nav-item.html.twig' with {
|
||||||
route: 'logs',
|
route: 'logs',
|
||||||
text: 'View Logs'|trans
|
text: 'View Logs'|trans
|
||||||
|
@ -46,6 +49,12 @@
|
||||||
route: 'services_status',
|
route: 'services_status',
|
||||||
text: 'Status'|trans
|
text: 'Status'|trans
|
||||||
} %}
|
} %}
|
||||||
|
{% include "utils/nav-item.html.twig" with {
|
||||||
|
route: 'disks',
|
||||||
|
text: 'Disks'|trans
|
||||||
|
} %}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="VAw_dLX" resname="BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI.">
|
<trans-unit id="VAw_dLX" resname="BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI.">
|
||||||
<source>BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI.</source>
|
<source>BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI.</source>
|
||||||
<target>BirdNET-stream est un logiciel d'analyse en temps réel de l'environement sonore basé sur BirdNET.</target>
|
<target>BirdNET-stream est un logiciel d'analyse en temps réel de l'environnement sonore basé sur BirdNET.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="vvz1r3A" resname="It aims to be able to run on any computer with a microphone.">
|
<trans-unit id="vvz1r3A" resname="It aims to be able to run on any computer with a microphone.">
|
||||||
<source>It aims to be able to run on any computer with a microphone.</source>
|
<source>It aims to be able to run on any computer with a microphone.</source>
|
||||||
|
|
|
@ -4252,7 +4252,7 @@
|
||||||
"resolved" "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz"
|
"resolved" "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz"
|
||||||
"version" "3.2.3"
|
"version" "3.2.3"
|
||||||
|
|
||||||
"webpack@^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "webpack@^4.0.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", "webpack@^5.0.0", "webpack@^5.1.0", "webpack@^5.72", "webpack@>=2", "webpack@>=4.0.0 <6.0.0", "webpack@>=5.0.0", "webpack@4.x.x || 5.x.x":
|
"webpack@^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "webpack@^4.0.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", "webpack@^5.0.0", "webpack@^5.1.0", "webpack@^5.72", "webpack@^5.74.0", "webpack@>=2", "webpack@>=4.0.0 <6.0.0", "webpack@>=5.0.0", "webpack@4.x.x || 5.x.x":
|
||||||
"integrity" "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA=="
|
"integrity" "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA=="
|
||||||
"resolved" "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz"
|
"resolved" "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz"
|
||||||
"version" "5.74.0"
|
"version" "5.74.0"
|
||||||
|
|
Loading…
Reference in New Issue