Merge branch 'main' into remove-python-shebangs

This commit is contained in:
Patrick McGuire
2022-05-24 12:09:53 -04:00
committed by GitHub
22 changed files with 403 additions and 193 deletions
+6 -4
View File
@@ -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
View File
@@ -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>
+1 -1
View File
@@ -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)
+2 -1
View File
@@ -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 -----------------------#
+1 -18
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+1 -2
View File
@@ -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
+27
View File
@@ -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=' ')
-7
View File
@@ -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
View File
@@ -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
View File
@@ -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>
+23 -9
View File
@@ -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);
+9 -1
View File
@@ -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