Merge branch 'main' into remove-python-shebangs
This commit is contained in:
@@ -20,11 +20,13 @@ make_thisrun() {
|
||||
make_thisrun &> /dev/null
|
||||
if ! diff ${LAST_RUN} ${THIS_RUN};then
|
||||
echo "The birdnet.conf file has changed"
|
||||
echo "Reloading services"
|
||||
if grep REC <(diff $LAST_RUN $THIS_RUN);then
|
||||
echo "Recording element changed -- restarting 'birdnet_recording.service'"
|
||||
sudo systemctl stop birdnet_recording.service
|
||||
sudo rm -rf ${RECS_DIR}/$(date +%B-%Y/%d-%A)/*
|
||||
sudo systemctl start birdnet_recording.service
|
||||
fi
|
||||
cat ${THIS_RUN} > ${LAST_RUN}
|
||||
sudo systemctl stop birdnet_recording.service
|
||||
sudo rm -rf ${RECS_DIR}/$(date +%B-%Y/%d-%A)/*
|
||||
sudo systemctl start birdnet_recording.service
|
||||
fi
|
||||
|
||||
INCLUDE_LIST="$HOME/BirdNET-Pi/include_species_list.txt"
|
||||
|
||||
+8
-6
@@ -16,10 +16,10 @@ if(isset($_GET['apprise_notify_each_detection'])) {
|
||||
} else {
|
||||
$apprise_notify_each_detection = 0;
|
||||
}
|
||||
if(isset($_GET['apprise_notify_each_species'])) {
|
||||
exec('sudo systemctl start pushed_notifications.service');
|
||||
if(isset($_GET['apprise_notify_new_species'])) {
|
||||
$apprise_notify_new_species = 1;
|
||||
} else {
|
||||
exec('sudo systemctl stop pushed_notifications.service');
|
||||
$apprise_notify_new_species = 0;
|
||||
}
|
||||
|
||||
// logic for setting the date and time based on user inputs from the form below
|
||||
@@ -46,6 +46,7 @@ $contents = preg_replace("/BIRDWEATHER_ID=.*/", "BIRDWEATHER_ID=$birdweather_id"
|
||||
$contents = preg_replace("/APPRISE_NOTIFICATION_TITLE=.*/", "APPRISE_NOTIFICATION_TITLE=\"$apprise_notification_title\"", $contents);
|
||||
$contents = preg_replace("/APPRISE_NOTIFICATION_BODY=.*/", "APPRISE_NOTIFICATION_BODY=\"$apprise_notification_body\"", $contents);
|
||||
$contents = preg_replace("/APPRISE_NOTIFY_EACH_DETECTION=.*/", "APPRISE_NOTIFY_EACH_DETECTION=$apprise_notify_each_detection", $contents);
|
||||
$contents = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES=.*/", "APPRISE_NOTIFY_NEW_SPECIES=$apprise_notify_new_species", $contents);
|
||||
$contents = preg_replace("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents);
|
||||
|
||||
|
||||
@@ -56,6 +57,7 @@ $contents2 = preg_replace("/BIRDWEATHER_ID=.*/", "BIRDWEATHER_ID=$birdweather_id
|
||||
$contents2 = preg_replace("/APPRISE_NOTIFICATION_TITLE=.*/", "APPRISE_NOTIFICATION_TITLE=\"$apprise_notification_title\"", $contents2);
|
||||
$contents2 = preg_replace("/APPRISE_NOTIFICATION_BODY=.*/", "APPRISE_NOTIFICATION_BODY=\"$apprise_notification_body\"", $contents2);
|
||||
$contents2 = preg_replace("/APPRISE_NOTIFY_EACH_DETECTION=.*/", "APPRISE_NOTIFY_EACH_DETECTION=$apprise_notify_each_detection", $contents2);
|
||||
$contents2 = preg_replace("/APPRISE_NOTIFY_NEW_SPECIES=.*/", "APPRISE_NOTIFY_NEW_SPECIES=$apprise_notify_new_species", $contents2);
|
||||
$contents2 = preg_replace("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents2);
|
||||
|
||||
|
||||
@@ -80,7 +82,7 @@ if ($language != "none"){
|
||||
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
|
||||
$home = trim($home);
|
||||
$command = "sudo -u".$user." mv ".$home."/BirdNET-Pi/model/labels.txt ".$home."/BirdNET-Pi/model/labels.txt.old && sudo -u".$user." unzip ".$home."/BirdNET-Pi/model/labels_l18n.zip ".$language." -d ".$home."/BirdNET-Pi/model && sudo -u".$user." mv ".$home."/BirdNET-Pi/model/".$language." ".$home."/BirdNET-Pi/model/labels.txt";
|
||||
$command_output = `sudo $command`;
|
||||
$command_output = `$command`;
|
||||
`sudo restart_services.sh`;
|
||||
}
|
||||
}
|
||||
@@ -144,8 +146,8 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}
|
||||
<input name="apprise_notification_title" type="text" value="<?php print($config['APPRISE_NOTIFICATION_TITLE']);?>" /><br>
|
||||
<label for="apprise_notification_body">Notification Body (use variables $sciname, $comname, or $confidence): </label>
|
||||
<input name="apprise_notification_body" type="text" value="<?php print($config['APPRISE_NOTIFICATION_BODY']);?>" /><br>
|
||||
<input type="checkbox" name="apprise_notify_each_species" <?php $output = shell_exec("service pushed_notifications status"); if (!strpos($output, 'dead') !== false && filesize($home."/BirdNET-Pi/apprise.txt") != 0) { echo "checked"; } ?>>
|
||||
<label for="apprise_notify_each_species">Notify each new species</label><br>
|
||||
<input type="checkbox" name="apprise_notify_new_species" <?php if($config['APPRISE_NOTIFY_NEW_SPECIES'] == 1 && filesize($home."/BirdNET-Pi/apprise.txt") != 0) { echo "checked"; };?> >
|
||||
<label for="apprise_notify_new_species">Notify each new species</label><br>
|
||||
<input type="checkbox" name="apprise_notify_each_detection" <?php if($config['APPRISE_NOTIFY_EACH_DETECTION'] == 1 && filesize($home."/BirdNET-Pi/apprise.txt") != 0) { echo "checked"; };?> >
|
||||
<label for="apprise_notify_each_detection">Notify each new detection</label><br><br>
|
||||
<h3>Bird Photos from Flickr</h3>
|
||||
|
||||
@@ -32,7 +32,7 @@ df['Hour of Day'] = [r.hour for r in df.Time]
|
||||
df_plt=df #Default to use the whole Dbase
|
||||
|
||||
# Add every font at the specified location
|
||||
font_dir = ['$HOME/BirdNET-Pi/homepage/static']
|
||||
font_dir = [userDir+'/BirdNET-Pi/homepage/static']
|
||||
for font in font_manager.findSystemFonts(font_dir):
|
||||
font_manager.fontManager.addfont(font)
|
||||
|
||||
|
||||
@@ -73,7 +73,8 @@ RTSP_STREAM=
|
||||
|
||||
APPRISE_NOTIFICATION_TITLE="New BirdNET-Pi Detection"
|
||||
APPRISE_NOTIFICATION_BODY="A \$sciname \$comname was just detected with a confidence of \$confidence"
|
||||
APPRISE_NOTIFY_EACH_DETECTION=false
|
||||
APPRISE_NOTIFY_EACH_DETECTION=0
|
||||
APPRISE_NOTIFY_NEW_SPECIES=0
|
||||
|
||||
#---------------------- Flickr Images API Configuration -----------------------#
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export PYTHON_VIRTUAL_ENV="$HOME/BirdNET-Pi/birdnet/bin/python3"
|
||||
|
||||
install_depends() {
|
||||
apt install -y debian-keyring debian-archive-keyring apt-transport-https
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo tee /etc/apt/trusted.gpg.d/caddy-stable.asc
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||||
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
|
||||
apt -qqq update && apt -qqy upgrade
|
||||
echo "icecast2 icecast2/icecast-setup boolean false" | debconf-set-selections
|
||||
@@ -93,22 +93,6 @@ EOF
|
||||
systemctl enable extraction.service
|
||||
}
|
||||
|
||||
install_pushed_notifications() {
|
||||
cat << EOF > $HOME/BirdNET-Pi/templates/pushed_notifications.service
|
||||
[Unit]
|
||||
Description=BirdNET-Pi Pushed.co Notifications
|
||||
[Service]
|
||||
Restart=on-success
|
||||
RestartSec=3
|
||||
Type=simple
|
||||
User=$USER
|
||||
ExecStart=/usr/local/bin/species_notifier.sh
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
ln -sf $HOME/BirdNET-Pi/templates/pushed_notifications.service /usr/lib/systemd/system
|
||||
}
|
||||
|
||||
create_necessary_dirs() {
|
||||
echo "Creating necessary directories"
|
||||
[ -d ${EXTRACTED} ] || sudo -u ${USER} mkdir -p ${EXTRACTED}
|
||||
@@ -438,7 +422,6 @@ install_services() {
|
||||
install_recording_service
|
||||
install_custom_recording_service # But does not enable
|
||||
install_extraction_service
|
||||
install_pushed_notifications
|
||||
install_spectrogram_service
|
||||
install_chart_viewer_service
|
||||
install_gotty_logs
|
||||
|
||||
+13
-7
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
ini_set('session.gc_maxlifetime', 7200);
|
||||
session_set_cookie_params(7200);
|
||||
session_start();
|
||||
$myDate = date('Y-m-d');
|
||||
$chart = "Combo-$myDate.png";
|
||||
|
||||
@@ -22,7 +25,9 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
|
||||
header("refresh: 0;");
|
||||
}
|
||||
$result4 = $statement4->execute();
|
||||
$images = [];
|
||||
if(!isset($_SESSION['images'])) {
|
||||
$_SESSION['images'] = [];
|
||||
}
|
||||
$iterations = 0;
|
||||
// hopefully one of the 5 most recent detections has an image that is valid, we'll use that one as the most recent detection until the newer ones get their images created
|
||||
while($mostrecent = $result4->fetchArray(SQLITE3_ASSOC)) {
|
||||
@@ -45,19 +50,20 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
|
||||
// we've found our valid detection! ignore everything else from the database loop
|
||||
if(strpos($headers[0],'200')) {
|
||||
if($_GET['previous_detection_identifier'] == $filename) { die(); }
|
||||
if($_GET['only_name'] == "true") { echo $comname.",".$filename;die(); }
|
||||
|
||||
if (!empty($config["FLICKR_API_KEY"])) {
|
||||
// if we already searched flickr for this species before, use the previous image rather than doing an unneccesary api call
|
||||
$key = array_search($comname, array_column($images, 0));
|
||||
$key = array_search($comname, array_column($_SESSION['images'], 0));
|
||||
if($key !== false) {
|
||||
$image = $images[$key];
|
||||
$image = $_SESSION['images'][$key];
|
||||
} else {
|
||||
$flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=".str_replace("_", "%20", $comname)."&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=5&orientation=square,portrait&format=json&media=photos&nojsoncallback=1"), true)["photos"]["photo"][0];
|
||||
$modaltext = "https://flickr.com/photos/".$flickrjson["owner"]."/".$flickrjson["id"];
|
||||
$authorlink = "https://flickr.com/people/".$flickrjson["owner"];
|
||||
$imageurl = 'http://farm' .$flickrjson["farm"]. '.static.flickr.com/' .$flickrjson["server"]. '/' .$flickrjson["id"]. '_' .$flickrjson["secret"]. '.jpg';
|
||||
array_push($images, array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink));
|
||||
$image = $images[count($images)-1];
|
||||
$imageurl = 'https://farm' .$flickrjson["farm"]. '.static.flickr.com/' .$flickrjson["server"]. '/' .$flickrjson["id"]. '_' .$flickrjson["secret"]. '.jpg';
|
||||
array_push($_SESSION['images'], array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink));
|
||||
$image = $_SESSION['images'][count($_SESSION['images'])-1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +244,7 @@ function loadDetectionIfNewExists(previous_detection_identifier=undefined) {
|
||||
const xhttp = new XMLHttpRequest();
|
||||
xhttp.onload = function() {
|
||||
// if there's a new detection that needs to be updated to the page
|
||||
if(this.responseText.length > 0 && !this.responseText.includes("Database is busy.")) {
|
||||
if(this.responseText.length > 0 && !this.responseText.includes("Database is busy")) {
|
||||
document.getElementById("most_recent_detection").innerHTML = this.responseText;
|
||||
|
||||
// only going to load left chart if there's a new detection
|
||||
|
||||
+5
-7
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
error_reporting(E_ERROR);
|
||||
|
||||
$db = new SQLite3('./scripts/birds.db', SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE);
|
||||
if($db == False){
|
||||
@@ -170,10 +168,10 @@ if(!isset($_GET['species']) && !isset($_GET['filename'])){
|
||||
<input type="hidden" name="view" value="Recordings">
|
||||
<input type="hidden" name="<?php echo $view; ?>" value="<?php echo $_GET['date']; ?>">
|
||||
<button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "alphabetical"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="alphabetical">
|
||||
<img src="images/sort_abc.svg" alt="Sort by alphabetical">
|
||||
<img src="images/sort_abc.svg" title="Sort by alphabetical" alt="Sort by alphabetical">
|
||||
</button>
|
||||
<button <?php if(isset($_GET['sort']) && $_GET['sort'] == "occurrences"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="occurrences">
|
||||
<img src="images/sort_occ.svg" alt="Sort by occurrences">
|
||||
<img src="images/sort_occ.svg" title="Sort by occurrences" alt="Sort by occurrences">
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -230,10 +228,10 @@ if(isset($_GET['species'])){ ?>
|
||||
<input type="hidden" name="view" value="Recordings">
|
||||
<input type="hidden" name="species" value="<?php echo $_GET['species']; ?>">
|
||||
<button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "date"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="date">
|
||||
<img width=35px src="images/sort_date.svg" alt="Sort by date">
|
||||
<img width=35px src="images/sort_date.svg" title="Sort by date" alt="Sort by date">
|
||||
</button>
|
||||
<button <?php if(isset($_GET['sort']) && $_GET['sort'] == "confidence"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="confidence">
|
||||
<img src="images/sort_occ.svg" alt="Sort by confidence">
|
||||
<img src="images/sort_occ.svg" title="Sort by confidence" alt="Sort by confidence">
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
+33
-26
@@ -8,6 +8,7 @@ from datetime import timedelta, datetime
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
from sqlite3 import Connection
|
||||
import plotly.express as px
|
||||
|
||||
userDir = os.path.expanduser('~')
|
||||
URI_SQLITE_DB = userDir + '/BirdNET-Pi/scripts/birds.db'
|
||||
@@ -122,42 +123,43 @@ filt=df2['Com_Name']==specie
|
||||
|
||||
df_counts=sum(df5==specie)
|
||||
|
||||
fig = make_subplots(
|
||||
|
||||
|
||||
|
||||
if resample_time != '1D':
|
||||
fig = make_subplots(
|
||||
rows=3, cols =2,
|
||||
specs= [[{"type":"xy","rowspan":3}, {"type":"polar","rowspan":2}], [{"rowspan":1}, {"rowspan":1} ], [None, {"type":"xy","rowspan":1}]],
|
||||
subplot_titles=('<b>Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'</b>',
|
||||
subplot_titles=('<b>Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+' for '+str(resample_sel)+' sampling interval.'+'</b>',
|
||||
'Total Detect:'+str('{:,}'.format(df_counts))+
|
||||
' Confidence Max:'+str('{:.2f}%'.format(max(df2[df2['Com_Name']==specie]['Confidence'])*100))+
|
||||
' '+' Median:'+str('{:.2f}%'.format(np.median(df2[df2['Com_Name']==specie]['Confidence'])*100))
|
||||
)
|
||||
)
|
||||
fig.layout.annotations[1].update(x=0.7,y=0.25, font_size=15)
|
||||
)
|
||||
fig.layout.annotations[1].update(x=0.7,y=0.25, font_size=15)
|
||||
|
||||
#Plot seen species for selected date range and number of species
|
||||
fig.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), row=1,col=1)
|
||||
#Plot seen species for selected date range and number of species
|
||||
fig.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), row=1,col=1)
|
||||
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=0, t=50, b=0),
|
||||
yaxis={'categoryorder':'total ascending'})
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=0, t=50, b=0),
|
||||
yaxis={'categoryorder':'total ascending'})
|
||||
|
||||
|
||||
# Set 360 degrees, 24 hours for polar plot
|
||||
theta = np.linspace(0.0, 360, 24, endpoint=False)
|
||||
# Set 360 degrees, 24 hours for polar plot
|
||||
theta = np.linspace(0.0, 360, 24, endpoint=False)
|
||||
|
||||
specie_filt= df5==specie
|
||||
df3=df5[specie_filt]
|
||||
specie_filt= df5==specie
|
||||
df3=df5[specie_filt]
|
||||
|
||||
detections2= pd.crosstab(df3, df3.index.hour)
|
||||
detections2= pd.crosstab(df3, df3.index.hour)
|
||||
|
||||
|
||||
|
||||
|
||||
d=pd.DataFrame(np.zeros((23,1))).squeeze()
|
||||
detections = hourly.loc[specie]
|
||||
detections=(d+detections).fillna(0)
|
||||
|
||||
|
||||
if resample_time != '1D':
|
||||
d=pd.DataFrame(np.zeros((23,1))).squeeze()
|
||||
detections = hourly.loc[specie]
|
||||
detections=(d+detections).fillna(0)
|
||||
fig.add_trace(go.Barpolar(r = detections, theta=theta), row=1, col=2)
|
||||
fig.update_layout(
|
||||
autosize=False,
|
||||
@@ -191,15 +193,18 @@ if resample_time != '1D':
|
||||
else:
|
||||
fig = make_subplots(
|
||||
rows=1, cols =2,
|
||||
# specs= [[{"type":"xy","rowspan":1}],
|
||||
# [{"rowspan":1}],
|
||||
# ],
|
||||
# subplot_titles=('<b>Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'</b>',
|
||||
specs= [[{"type":"xy","rowspan":1},{"type":"xy","rowspan":1}]],
|
||||
|
||||
|
||||
subplot_titles=('<b>Daily Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'</b>',
|
||||
'<b>Daily ' + specie+ ' Detections on 15 minute intervals </b>'),
|
||||
# 'Total Detect:'+str('{:,}'.format(df_counts))+
|
||||
# ' Confidence Max:'+str('{:.2f}%'.format(max(df2[df2['Com_Name']==specie]['Confidence'])*100))+
|
||||
# ' '+' Median:'+str('{:.2f}%'.format(np.median(df2[df2['Com_Name']==specie]['Confidence'])*100))
|
||||
# )
|
||||
)
|
||||
|
||||
fig.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), row=1,col=1)
|
||||
df4=df2['Com_Name'][df2['Com_Name']==specie].resample('15min').count()
|
||||
df4.index=[df4.index.date, df4.index.time]
|
||||
day_hour_freq=df4.unstack().fillna(0)
|
||||
@@ -208,11 +213,13 @@ else:
|
||||
fig_y = [h.strftime('%H:%M') for h in day_hour_freq.columns.tolist()]
|
||||
fig_z = day_hour_freq.values.transpose()
|
||||
fig_heatmap = go.Figure(data=go.Heatmap(x=fig_x,y=fig_y,z=fig_z))
|
||||
fig.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), row=1,col=1)
|
||||
|
||||
fig.update_layout(
|
||||
margin=dict(l=0, r=0, t=50, b=0),
|
||||
yaxis={'categoryorder':'total ascending'})
|
||||
fig.add_trace(go.Heatmap(x=fig_x,y=fig_y,z=fig_z, autocolorscale = False, colorscale = 'blackbody'), row=1, col=2)
|
||||
color_pals= px.colors.named_colorscales()
|
||||
selected_pal = cols2.selectbox('Select Color Pallet for Daily Detections', color_pals)
|
||||
fig.add_trace(go.Heatmap(x=fig_x,y=fig_y,z=fig_z, autocolorscale = False, colorscale = selected_pal), row=1, col=2)
|
||||
# container=st.container()
|
||||
# config={'displayModelBar': False}
|
||||
st.plotly_chart(fig, use_container_width=True) #, config=config)
|
||||
|
||||
@@ -9,7 +9,6 @@ sudo pkill server.py
|
||||
sudo systemctl stop birdnet_recording.service
|
||||
services=(web_terminal.service
|
||||
spectrogram_viewer.service
|
||||
pushed_notifications.service
|
||||
livestream.service
|
||||
icecast2.service
|
||||
extraction.service
|
||||
@@ -22,6 +21,6 @@ sudo systemctl restart "${i}"
|
||||
done
|
||||
until grep 5050 <(netstat -tulpn 2>&1);do
|
||||
sudo systemctl restart birdnet_server.service
|
||||
sleep 30
|
||||
sleep 45
|
||||
done
|
||||
sudo systemctl restart birdnet_analysis.service
|
||||
|
||||
@@ -243,6 +243,33 @@ def sendAppriseNotifications(species,confidence):
|
||||
title=title,
|
||||
)
|
||||
|
||||
if str(str(str([i for i in this_run if i.startswith('APPRISE_NOTIFY_NEW_SPECIES')]).split('=')[1]).split('\\')[0]) == "1":
|
||||
try:
|
||||
con = sqlite3.connect(userDir + '/BirdNET-Pi/scripts/birds.db')
|
||||
con.row_factory = lambda cursor, row: row[0]
|
||||
cur = con.cursor()
|
||||
cur.execute("SELECT DISTINCT(Com_Name) FROM detections")
|
||||
known_species = cur.fetchall()
|
||||
sciName,comName = species.split("_")
|
||||
|
||||
print("\ncomName: ",comName)
|
||||
print("\nknown_species: ",known_species)
|
||||
if comName not in known_species:
|
||||
apobj = apprise.Apprise()
|
||||
config = apprise.AppriseConfig()
|
||||
config.add(userDir + '/BirdNET-Pi/apprise.txt')
|
||||
apobj.add(config)
|
||||
|
||||
apobj.notify(
|
||||
body=body.replace("$sciname",species.split("_")[0]).replace("$comname",species.split("_")[1]).replace("$confidence",confidence),
|
||||
title=title,
|
||||
)
|
||||
|
||||
con.close()
|
||||
except:
|
||||
print("Database busy")
|
||||
time.sleep(2)
|
||||
|
||||
def writeResultsToFile(detections, min_conf, path):
|
||||
|
||||
print('WRITING RESULTS TO', path, '...', end=' ')
|
||||
|
||||
@@ -73,13 +73,6 @@
|
||||
<button type="submit" name="submit" value="sudo systemctl disable --now spectrogram_viewer.service">Disable</button>
|
||||
<button type="submit" name="submit" value="sudo systemctl enable --now spectrogram_viewer.service">Enable</button>
|
||||
</form>
|
||||
<form action="" method="GET">
|
||||
<h3>Pushed Notifications</h3>
|
||||
<button type="submit" name="submit" value="sudo systemctl stop pushed_notifications.service">Stop</button>
|
||||
<button type="submit" name="submit" value="sudo systemctl restart pushed_notifications.service">Restart</button>
|
||||
<button type="submit" name="submit" value="sudo systemctl disable --now pushed_notifications.service">Disable</button>
|
||||
<button type="submit" name="submit" value="sudo systemctl enable --now pushed_notifications.service">Enable</button>
|
||||
</form>
|
||||
<form action="" method="GET">
|
||||
<button type="submit" name="submit" value="stop_core_services.sh">Stop Core Services</button>
|
||||
</form>
|
||||
|
||||
+84
-9
@@ -1,19 +1,92 @@
|
||||
<script>
|
||||
<script>
|
||||
// CREDITS: https://codepen.io/jakealbaugh/pen/jvQweW
|
||||
|
||||
// UPDATE: there is a problem in chrome with starting audio context
|
||||
// before a user gesture. This fixes it.
|
||||
var started = null;
|
||||
var player = null;
|
||||
const ctx = null;
|
||||
window.onload = function(){
|
||||
// if user agent includes iPhone or Mac use legacy mode
|
||||
if(window.navigator.userAgent.includes("iPhone") || window.navigator.userAgent.includes("Mac")) {
|
||||
document.getElementById("spectrogramimage").style.display="";
|
||||
document.body.querySelector('canvas').remove();
|
||||
document.getElementById('player').remove();
|
||||
document.body.querySelector('h1').remove();
|
||||
|
||||
<?php
|
||||
if (file_exists('./scripts/thisrun.txt')) {
|
||||
$config = parse_ini_file('./scripts/thisrun.txt');
|
||||
} elseif (file_exists('./scripts/firstrun.ini')) {
|
||||
$config = parse_ini_file('./scripts/firstrun.ini');
|
||||
}
|
||||
$refresh = $config['RECORDING_LENGTH'];
|
||||
$time = time();
|
||||
?>
|
||||
// every $refresh seconds, this loop will run and refresh the spectrogram image
|
||||
window.setInterval(function(){
|
||||
document.getElementById("spectrogramimage").src = "/spectrogram.png?nocache="+Date.now();
|
||||
}, <?php echo $refresh; ?>*1000);
|
||||
} else {
|
||||
document.getElementById("spectrogramimage").remove();
|
||||
|
||||
var audioelement = window.parent.document.getElementsByTagName("audio")[0];
|
||||
if (typeof(audioelement) != 'undefined') {
|
||||
|
||||
document.getElementById('player').remove();
|
||||
|
||||
player = audioelement;
|
||||
} else {
|
||||
player = document.getElementById('player');
|
||||
}
|
||||
player.play();
|
||||
if (started) return;
|
||||
started = true;
|
||||
initialize();
|
||||
started = true;
|
||||
initialize();
|
||||
}
|
||||
};
|
||||
|
||||
function fitTextOnCanvas(text,fontface,yPosition){
|
||||
var fontsize=300;
|
||||
do{
|
||||
fontsize--;
|
||||
CTX.font=fontsize+"px "+fontface;
|
||||
}while(CTX.measureText(text).width>document.body.querySelector('canvas').width)
|
||||
CTX.font = CTX.font=(fontsize*0.35)+"px "+fontface;
|
||||
CTX.fillText(text,document.body.querySelector('canvas').width - (document.body.querySelector('canvas').width * 0.50),yPosition);
|
||||
}
|
||||
|
||||
function applyText(text) {
|
||||
CTX.fillStyle = 'white';
|
||||
CTX.font = '25px Roboto Flex';
|
||||
fitTextOnCanvas(text,"Roboto Flex",document.body.querySelector('canvas').scrollHeight * 0.35)
|
||||
CTX.fillStyle = 'hsl(280, 100%, 10%)';
|
||||
}
|
||||
|
||||
var previous_detection_identifier = null;
|
||||
function loadDetectionIfNewExists() {
|
||||
const xhttp = new XMLHttpRequest();
|
||||
xhttp.onload = function() {
|
||||
// if there's a new detection that needs to be updated to the page
|
||||
if(this.responseText.length > 0 && !this.responseText.includes("Database")) {
|
||||
if(previous_detection_identifier != null){
|
||||
applyText(this.responseText.split(",")[0].replace("_"," "));
|
||||
}
|
||||
previous_detection_identifier = this.responseText.split(",")[1];
|
||||
}
|
||||
}
|
||||
xhttp.open("GET", "overview.php?ajax_detections=true&previous_detection_identifier="+previous_detection_identifier+"&only_name=true", true);
|
||||
xhttp.send();
|
||||
}
|
||||
|
||||
window.setInterval(function(){
|
||||
loadDetectionIfNewExists();
|
||||
}, 2500);
|
||||
|
||||
function initialize() {
|
||||
document.body.querySelector('h1').remove();
|
||||
const CVS = document.body.querySelector('canvas');
|
||||
const CTX = CVS.getContext('2d');
|
||||
CTX = CVS.getContext('2d');
|
||||
const W = CVS.width = window.innerWidth;
|
||||
const H = CVS.height = window.innerHeight;
|
||||
|
||||
@@ -25,12 +98,12 @@ function initialize() {
|
||||
process();
|
||||
|
||||
function process() {
|
||||
const SOURCE = ACTX.createMediaElementSource(document.getElementById('player'));
|
||||
const SOURCE = ACTX.createMediaElementSource(player);
|
||||
SOURCE.connect(ANALYSER);
|
||||
SOURCE.connect(ACTX.destination)
|
||||
const DATA = new Uint8Array(ANALYSER.frequencyBinCount);
|
||||
const LEN = DATA.length;
|
||||
const h = H / LEN;
|
||||
const h = (H / LEN + 0.9);
|
||||
const x = W - 1;
|
||||
CTX.fillStyle = 'hsl(280, 100%, 10%)';
|
||||
CTX.fillRect(0, 0, W, H);
|
||||
@@ -40,11 +113,12 @@ function initialize() {
|
||||
function loop() {
|
||||
window.requestAnimationFrame(loop);
|
||||
let imgData = CTX.getImageData(1, 0, W - 1, H);
|
||||
|
||||
CTX.fillRect(0, 0, W, H);
|
||||
CTX.putImageData(imgData, 0, 0);
|
||||
ANALYSER.getByteFrequencyData(DATA);
|
||||
for (let i = 0; i < LEN; i++) {
|
||||
let rat = DATA[i] / 128;
|
||||
let rat = DATA[i] / 128 ;
|
||||
let hue = Math.round((rat * 120) + 280 % 360);
|
||||
let sat = '100%';
|
||||
let lit = 10 + (70 * rat) + '%';
|
||||
@@ -79,7 +153,8 @@ h1 {
|
||||
}
|
||||
</style>
|
||||
|
||||
<audio style="display:none" controls="" crossorigin="anonymous" id='player' autoplay=""><source src="/stream"></audio>
|
||||
<img id="spectrogramimage" style="width:100%;height:100%;display:none" src="/spectrogram.png?nocache=<?php echo $time;?>">
|
||||
|
||||
<audio style="display:none" controls="" crossorigin="anonymous" id='player' preload="none"><source src="/stream"></audio>
|
||||
<h1>Loading...</h1>
|
||||
<canvas></canvas>
|
||||
|
||||
|
||||
+23
-21
@@ -56,17 +56,9 @@ if(isset($_GET['species'])){
|
||||
$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd");
|
||||
$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd");
|
||||
$home = trim($home);
|
||||
if(!file_exists($home."/BirdNET-Pi/scripts/disk_check_exclude.txt")) {
|
||||
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "##start\n##end");
|
||||
}
|
||||
function editfile( $sourcefile, $start='##start', $end='##end', $data=array() ){
|
||||
$lines=array_filter( file( $sourcefile , FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES ) );
|
||||
$output=array_merge(
|
||||
array_splice( $lines, 0, array_search( strtolower( $start ), array_map('strtolower', $lines ) ) + 1 ),
|
||||
$data,
|
||||
array_splice( $lines, array_search( strtolower( $end ), array_map('strtolower', $lines ) ) )
|
||||
);
|
||||
file_put_contents( $sourcefile, implode( PHP_EOL, $output ) );
|
||||
if(!file_exists($home."/BirdNET-Pi/scripts/disk_check_exclude.txt") || strpos(file_get_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt"),"##start") === false) {
|
||||
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "");
|
||||
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "##start\n##end\n");
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -89,10 +81,10 @@ function editfile( $sourcefile, $start='##start', $end='##end', $data=array() ){
|
||||
<input type="hidden" name="sort" value="<?php if(isset($_GET['sort'])){echo $_GET['sort'];}?>">
|
||||
<input type="hidden" name="view" value="Species Stats">
|
||||
<button <?php if(!isset($_GET['sort']) || $_GET['sort'] == "alphabetical"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="alphabetical">
|
||||
<img src="images/sort_abc.svg" alt="Sort by alphabetical">
|
||||
<img src="images/sort_abc.svg" title="Sort by alphabetical" alt="Sort by alphabetical">
|
||||
</button>
|
||||
<button <?php if(isset($_GET['sort']) && $_GET['sort'] == "occurrences"){ echo "style='background:#9fe29b !important;'"; }?> class="sortbutton" type="submit" name="sort" value="occurrences">
|
||||
<img src="images/sort_occ.svg" alt="Sort by occurrences">
|
||||
<img src="images/sort_occ.svg" title="Sort by occurrences" alt="Sort by occurrences">
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -177,23 +169,31 @@ while($results=$result3->fetchArray(SQLITE3_ASSOC)){
|
||||
ob_flush();
|
||||
flush();
|
||||
if (file_exists('./scripts/thisrun.txt')) {
|
||||
$config = parse_ini_file('./scripts/thisrun.txt');
|
||||
$config = parse_ini_file('./scripts/thisrun.txt');
|
||||
} elseif (file_exists('./scripts/firstrun.ini')) {
|
||||
$config = parse_ini_file('./scripts/firstrun.ini');
|
||||
$config = parse_ini_file('./scripts/firstrun.ini');
|
||||
}
|
||||
$flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=\"".str_replace('_', '+', $comname)."\"&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=15&format=json&nojsoncallback=1"), true)["photos"]["photo"];
|
||||
if (! empty($config["FLICKR_API_KEY"])) {
|
||||
$flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=\"".str_replace('_', '+', $comname)."\"&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=15&format=json&nojsoncallback=1"), true)["photos"]["photo"];
|
||||
|
||||
foreach ($flickrjson as $val) {
|
||||
|
||||
foreach ($flickrjson as $val) {
|
||||
$iter++;
|
||||
$modaltext = "https://flickr.com/photos/".$val["owner"]."/".$val["id"];
|
||||
$authorlink = "https://flickr.com/people/".$val["owner"];
|
||||
$imageurl = 'http://farm' .$val["farm"]. '.static.flickr.com/' .$val["server"]. '/' .$val["id"]. '_' .$val["secret"]. '.jpg';
|
||||
echo "<img style='vertical-align:top' src=\"$imageurl\"><span style='cursor:pointer;color:blue;text-decoration:underline;' onclick='setModalText(".$iter.",\"".$val["title"]."\",\"".$modaltext."\", \"".$authorlink."\")'>".$iter."</span>";
|
||||
$imageurl = 'https://farm' .$val["farm"]. '.static.flickr.com/' .$val["server"]. '/' .$val["id"]. '_' .$val["secret"]. '.jpg';
|
||||
echo "<span style='cursor:pointer;' onclick='setModalText(".$iter.",\"".$val["title"]."\",\"".$modaltext."\", \"".$authorlink."\")'><img style='vertical-align:top' src=\"$imageurl\"></span>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<br><br><br>
|
||||
<?php if(isset($_GET['species'])){?>
|
||||
<br><br>
|
||||
<div class="brbanner">Best Recordings for Other Species:</div><br>
|
||||
<?php } else {?>
|
||||
<hr><br>
|
||||
<?php } ?>
|
||||
|
||||
<table>
|
||||
<?php
|
||||
@@ -216,8 +216,10 @@ array_push($excludelines, $results['Date']."/".$comname."/".$results['File_Name'
|
||||
<b>Best Recording:</b> <?php echo $results['Date']." ".$results['Time'];?><br><video onplay='setLiveStreamVolume(0)' onended='setLiveStreamVolume(1)' onpause='setLiveStreamVolume(1)' controls poster="<?php echo $filename.".png";?>" preload="none" title="<?php echo $filename;?>"><source src="<?php echo $filename;?>" type="audio/mp3"></video></td>
|
||||
</tr>
|
||||
<?php
|
||||
editfile($home."/BirdNET-Pi/scripts/disk_check_exclude.txt","##start","##end",$excludelines);
|
||||
}
|
||||
|
||||
$file = file_get_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt");
|
||||
file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "##start"."\n".implode("\n",$excludelines)."\n".substr($file, strpos($file, "##end")));
|
||||
?>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
ini_set('session.gc_maxlifetime', 7200);
|
||||
session_set_cookie_params(7200);
|
||||
session_start();
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
@@ -67,7 +70,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
|
||||
$statement0 = $db->prepare('SELECT Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') '.$searchquery.' ORDER BY Time DESC LIMIT '.(intval($_GET['display_limit'])-40).',40');
|
||||
} else {
|
||||
// legacy mode
|
||||
$statement0 = $db->prepare('SELECT Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') '.$searchquery.' ORDER BY Time DESC '.$searchquery);
|
||||
$statement0 = $db->prepare('SELECT Time, Com_Name, Sci_Name, Confidence, File_Name FROM detections WHERE Date == Date(\'now\', \'localtime\') '.$searchquery.' ORDER BY Time DESC');
|
||||
}
|
||||
if($statement0 == False){
|
||||
echo "Database is busy";
|
||||
@@ -77,7 +80,10 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
|
||||
|
||||
?> <table>
|
||||
<?php
|
||||
$images = [];
|
||||
|
||||
if(!isset($_SESSION['images'])) {
|
||||
$_SESSION['images'] = [];
|
||||
}
|
||||
$iterations = 0;
|
||||
|
||||
if (file_exists('./scripts/thisrun.txt')) {
|
||||
@@ -97,16 +103,16 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
|
||||
|
||||
if (!empty($config["FLICKR_API_KEY"])) {
|
||||
// if we already searched flickr for this species before, use the previous image rather than doing an unneccesary api call
|
||||
$key = array_search($comname, array_column($images, 0));
|
||||
$key = array_search($comname, array_column($_SESSION['images'], 0));
|
||||
if($key !== false) {
|
||||
$image = $images[$key];
|
||||
$image = $_SESSION['images'][$key];
|
||||
} else {
|
||||
$flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=".str_replace("_", "%20", $comname)."&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=5&orientation=square,portrait&media=photos&format=json&nojsoncallback=1"), true)["photos"]["photo"][0];
|
||||
$modaltext = "https://flickr.com/photos/".$flickrjson["owner"]."/".$flickrjson["id"];
|
||||
$authorlink = "https://flickr.com/people/".$flickrjson["owner"];
|
||||
$imageurl = 'http://farm' .$flickrjson["farm"]. '.static.flickr.com/' .$flickrjson["server"]. '/' .$flickrjson["id"]. '_' .$flickrjson["secret"]. '.jpg';
|
||||
array_push($images, array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink));
|
||||
$image = $images[count($images)-1];
|
||||
array_push($_SESSION['images'], array($comname,$imageurl,$flickrjson["title"], $modaltext, $authorlink));
|
||||
$image = $_SESSION['images'][count($_SESSION['images'])-1];
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -241,7 +247,11 @@ document.getElementById("searchterm").onkeydown = (function(e) {
|
||||
});
|
||||
|
||||
function switchViews(element) {
|
||||
document.getElementById("detections_table").innerHTML = "";
|
||||
if(searchterm == ""){
|
||||
document.getElementById("detections_table").innerHTML = "<h3>Loading <?php echo $todaycount['COUNT(*)']; ?> detections...</h3>";
|
||||
} else {
|
||||
document.getElementById("detections_table").innerHTML = "<h3>Loading...</h3>";
|
||||
}
|
||||
if(element.innerHTML == "Legacy view") {
|
||||
element.innerHTML = "Normal view";
|
||||
loadDetections(undefined);
|
||||
@@ -253,7 +263,11 @@ function switchViews(element) {
|
||||
function searchDetections(searchvalue) {
|
||||
document.getElementById("detections_table").innerHTML = "<h3>Loading...</h3>";
|
||||
searchterm = searchvalue;
|
||||
loadDetections(40,undefined);
|
||||
if(document.getElementsByClassName('legacyview')[0].innerHTML == "Normal view") {
|
||||
loadDetections(undefined,undefined);
|
||||
} else {
|
||||
loadDetections(40,undefined);
|
||||
}
|
||||
}
|
||||
function loadDetections(detections_limit, element=undefined) {
|
||||
const xhttp = new XMLHttpRequest();
|
||||
@@ -267,7 +281,7 @@ function loadDetections(detections_limit, element=undefined) {
|
||||
}
|
||||
|
||||
}
|
||||
if(searchterm !== ""){
|
||||
if(searchterm != ""){
|
||||
xhttp.open("GET", "todays_detections.php?ajax_detections=true&display_limit="+detections_limit+"&searchterm="+searchterm, true);
|
||||
} else {
|
||||
xhttp.open("GET", "todays_detections.php?ajax_detections=true&display_limit="+detections_limit, true);
|
||||
|
||||
@@ -35,7 +35,10 @@ if ! grep APPRISE_NOTIFICATION_BODY /etc/birdnet/birdnet.conf &>/dev/null;then
|
||||
sudo -u$USER echo "APPRISE_NOTIFICATION_BODY=\"A \$sciname \$comname was just detected with a confidence of \$confidence\"" >> /etc/birdnet/birdnet.conf
|
||||
fi
|
||||
if ! grep APPRISE_NOTIFY_EACH_DETECTION /etc/birdnet/birdnet.conf &>/dev/null;then
|
||||
sudo -u$USER echo "APPRISE_NOTIFY_EACH_DETECTION=false" >> /etc/birdnet/birdnet.conf
|
||||
sudo -u$USER echo "APPRISE_NOTIFY_EACH_DETECTION=0 " >> /etc/birdnet/birdnet.conf
|
||||
fi
|
||||
if ! grep APPRISE_NOTIFY_NEW_SPECIES /etc/birdnet/birdnet.conf &>/dev/null;then
|
||||
sudo -u$USER echo "APPRISE_NOTIFY_EACH_DETECTION=0 " >> /etc/birdnet/birdnet.conf
|
||||
fi
|
||||
apprise_installation_status=$(~/BirdNET-Pi/birdnet/bin/python3 -c 'import pkgutil; print("installed" if pkgutil.find_loader("apprise") else "not installed")')
|
||||
if [[ "$apprise_installation_status" = "not installed" ]];then
|
||||
@@ -58,3 +61,8 @@ fi
|
||||
if ! grep FLICKR_API_KEY /etc/birdnet/birdnet.conf &>/dev/null;then
|
||||
sudo -u$USER echo "FLICKR_API_KEY=" >> /etc/birdnet/birdnet.conf
|
||||
fi
|
||||
if systemctl list-unit-files pushed_notifications.service;then
|
||||
sudo systemctl disable --now pushed_notifications.service
|
||||
sudo rm -f /usr/lib/systemd/system/pushed_notifications.service
|
||||
sudo rm $HOME/BirdNET-Pi/templates/pushed_notifications.service
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user