From b550421be0ddde37a4b34f1affa267b3c5000c76 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Fri, 13 May 2022 14:09:34 -0400 Subject: [PATCH 01/37] UI tweaks --- homepage/static/RobotoFlex-Regular.ttf | Bin 0 -> 109812 bytes homepage/style.css | 47 +++++++++++++++++++------ scripts/daily_plot.py | 10 ++++++ scripts/history.php | 1 + 4 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 homepage/static/RobotoFlex-Regular.ttf diff --git a/homepage/static/RobotoFlex-Regular.ttf b/homepage/static/RobotoFlex-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3f13b17d867531f7b17cea65136ea80791bdf803 GIT binary patch literal 109812 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

=f%cE1mxgT^uPl7@6w*_~1eaMND|G?j0I{-c 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 literal 0 HcmV?d00001 diff --git a/homepage/style.css b/homepage/style.css index ba5e360..91547b0 100644 --- a/homepage/style.css +++ b/homepage/style.css @@ -1,6 +1,10 @@ +@font-face { + font-family: 'Roboto Flex' ; + src: url('static/RobotoFlex-Regular.ttf') format('truetype'); +} + * { - font-family: 'Arial', 'Gill Sans', 'Gill Sans MT', - ' Calibri', 'Trebuchet MS', 'sans-serif'; + font-family: 'Roboto Flex', sans-serif; box-sizing: border-box; font-size: medium; } @@ -37,6 +41,9 @@ table { border-spacing: 0; margin-left: auto; margin-right: auto; + box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10); + border-radius:3px; + overflow: hidden; } td { @@ -44,7 +51,6 @@ td { vertical-align: top; background-color: rgb(219, 255, 235); font-weight: lighter; - border: 2px solid black; text-align: center; } @@ -132,6 +138,8 @@ button:hover { margin-left: auto; margin-right: auto; margin-bottom: 15px; + box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10); !important; + border-radius: 4px; } .topnav form { @@ -178,7 +186,6 @@ button:hover { .overview th { background-color: rgb(219, 255, 235); - border: 2px solid black; text-align: center; padding: 12px; } @@ -238,7 +245,6 @@ button:hover { } .views .centered button { - border: 2px solid black; background-color: rgb(219, 255, 235); padding: 12px; } @@ -257,7 +263,6 @@ button:hover { } .settings button { - border: 2px solid black; background-color: rgb(219, 255, 235); padding: 12px; } @@ -284,7 +289,7 @@ button:hover { .customlabels button,.customlabels2 button { padding: 12px; background-color: rgb(219, 255, 235); - border: 2px solid black; + width: 100%; } @@ -339,7 +344,7 @@ button:hover { } .systemcontrols button,.servicecontrols button { - border: 2px solid black; + background-color: rgb(219, 255, 235); padding: 12px; width: 50%; @@ -541,7 +546,7 @@ button:hover { font-size:x-large; background:#dbffeb; padding:5px; - border: 2px solid black; + } button.legacyview { color:gray; @@ -552,12 +557,32 @@ button.legacyview { font-size:small; background:#dbffeb; padding:5px; - border: 2px solid black; + } button.loadmore { margin-top:10px; font-size:x-large; background:#dbffeb; padding:10px; - border: 2px solid black; + } + +tr { + background-color:#9fe29b; +} +.history.centered form { + display:flex; + justify-content: center; +} +.history.centered input { + margin-right:5px; + border:0px; +} +.centered form#views button { + box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.10); + margin:2px; +} +input { + box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10); +} + diff --git a/scripts/daily_plot.py b/scripts/daily_plot.py index e2147b0..2d80dc3 100755 --- a/scripts/daily_plot.py +++ b/scripts/daily_plot.py @@ -7,6 +7,8 @@ import matplotlib.pyplot as plt from matplotlib.colors import LogNorm from datetime import datetime import textwrap +import matplotlib.font_manager as font_manager +from matplotlib import rcParams userDir = os.path.expanduser('~') conn = sqlite3.connect(userDir + '/BirdNET-Pi/scripts/birds.db') @@ -29,6 +31,14 @@ df['Hour of Day'] = [r.hour for r in df.Time] #Create separate dataframes for separate locations df_plt=df #Default to use the whole Dbase +# Add every font at the specified location +font_dir = ['$HOME/BirdNET-Pi/homepage/static'] +for font in font_manager.findSystemFonts(font_dir): + font_manager.fontManager.addfont(font) + +# Set font family globally +rcParams['font.family'] = 'Roboto Flex' + #Get todays readings now = datetime.now() df_plt_today = df_plt[df_plt['Date']==now.strftime("%Y-%m-%d")] diff --git a/scripts/history.php b/scripts/history.php index 9a5e1df..88ca352 100644 --- a/scripts/history.php +++ b/scripts/history.php @@ -37,6 +37,7 @@ $totalcount = $result1->fetchArray(SQLITE3_ASSOC); +

"; From ac3fb313e619d741ec6c4d6851675de3c6450551 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Fri, 13 May 2022 15:04:30 -0400 Subject: [PATCH 02/37] symlink for fonts --- scripts/update_birdnet_snippets.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/update_birdnet_snippets.sh b/scripts/update_birdnet_snippets.sh index e9c89d3..eea92dc 100755 --- a/scripts/update_birdnet_snippets.sh +++ b/scripts/update_birdnet_snippets.sh @@ -49,3 +49,4 @@ if grep bash $HOME/BirdNET-Pi/templates/web_terminal.service;then sudo systemctl daemon-reload sudo systemctl restart web_terminal.service fi +[ -L ~/BirdSongs/Extracted/static ] || ln -sf ~/BirdNET-Pi/homepage/static ~/BirdSongs/Extracted From d0516f4464ae491ef6896e9104771525ac26e79b Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Sat, 14 May 2022 13:20:59 -0400 Subject: [PATCH 03/37] Much prettier "Update" screen --- homepage/views.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/homepage/views.php b/homepage/views.php index 942f6bf..d0f2ab2 100644 --- a/homepage/views.php +++ b/homepage/views.php @@ -47,6 +47,15 @@ body::-webkit-scrollbar { } else { topbuttons[0].classList.add("button-hover"); } + function copyOutput(elem) { + elem.innerHTML = 'Copied!'; + const copyText = document.getElementsByTagName("pre")[0].textContent; + const textArea = document.createElement('textarea'); + textArea.textContent = copyText; + document.body.append(textArea); + textArea.select(); + document.execCommand("copy"); + }

@@ -203,7 +212,7 @@ if(isset($_GET['view'])){ $command = $_GET['submit']; if(isset($command)){ $results = shell_exec("$command 2>&1"); - echo "
$results
"; + echo "
Output of command:`".$command."`
$results
"; } else { header('WWW-Authenticate: Basic realm="My Realm"'); header('HTTP/1.0 401 Unauthorized'); From a7e09bb5e582d207e74f849bf7c0b3fbd9ea78a3 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Sat, 14 May 2022 13:28:20 -0400 Subject: [PATCH 04/37] Much prettier every other command --- homepage/views.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/homepage/views.php b/homepage/views.php index d0f2ab2..67209ea 100644 --- a/homepage/views.php +++ b/homepage/views.php @@ -212,7 +212,10 @@ if(isset($_GET['view'])){ $command = $_GET['submit']; if(isset($command)){ $results = shell_exec("$command 2>&1"); - echo "
Output of command:`".$command."`
$results
"; + if(strlen($results) == 0) { + $results = "This command has no output."; + } + echo "
Output of command:`".$command."`
$results
"; } else { header('WWW-Authenticate: Basic realm="My Realm"'); header('HTTP/1.0 401 Unauthorized'); From 2c29cce4a221687b2e5c9af5eb35b54d4e8a57e3 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Sat, 14 May 2022 14:58:40 -0400 Subject: [PATCH 05/37] Giving service control commands an output --- homepage/views.php | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/homepage/views.php b/homepage/views.php index 67209ea..e190569 100644 --- a/homepage/views.php +++ b/homepage/views.php @@ -51,6 +51,8 @@ body::-webkit-scrollbar { elem.innerHTML = 'Copied!'; const copyText = document.getElementsByTagName("pre")[0].textContent; const textArea = document.createElement('textarea'); + textArea.style.position = 'absolute'; + textArea.style.left = '-100%'; textArea.textContent = copyText; document.body.append(textArea); textArea.select(); @@ -89,7 +91,7 @@ if(isset($_GET['view'])){ if($submittedpwd == $caddypwd && $submitteduser == 'birdnet'){ $url = $_SERVER['SERVER_NAME']."/scripts/adminer.php"; echo "
-
+ @@ -99,8 +101,8 @@ if(isset($_GET['view'])){ -
-
"; + +
"; } else { header('WWW-Authenticate: Basic realm="My Realm"'); header('HTTP/1.0 401 Unauthorized'); @@ -184,7 +186,7 @@ if(isset($_GET['view'])){ $submitteduser = $_SERVER['PHP_AUTH_USER']; if($submittedpwd == $caddypwd && $submitteduser == 'birdnet'){ #ACCESS THE WEB TERMINAL - echo ""; + echo ""; } else { header('WWW-Authenticate: Basic realm="My Realm"'); header('HTTP/1.0 401 Unauthorized'); @@ -210,12 +212,20 @@ if(isset($_GET['view'])){ $submitteduser = $_SERVER['PHP_AUTH_USER']; if($submittedpwd == $caddypwd && $submitteduser == 'birdnet'){ $command = $_GET['submit']; - if(isset($command)){ + if(isset($command)){ + $initcommand = $command; + if (strpos($command, "systemctl") !== false) { + $tmp = explode(" ",trim($command)); + $command .= "& sleep 3;sudo systemctl status ".end($tmp); + } $results = shell_exec("$command 2>&1"); + $results = str_replace("FAILURE", "FAILURE", $results); + $results = str_replace("failed", "failed",$results); + $results = str_replace("active (running)", "active (running)",$results); if(strlen($results) == 0) { $results = "This command has no output."; } - echo "
Output of command:`".$command."`
$results
"; + echo "
Output of command:`".$initcommand."`
$results
"; } else { header('WWW-Authenticate: Basic realm="My Realm"'); header('HTTP/1.0 401 Unauthorized'); From 470e5d142dd9cab16c778ddc5e49fef015896b0c Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Sat, 14 May 2022 15:48:03 -0400 Subject: [PATCH 06/37] Round confidence to 2 decimals --- scripts/overview.php | 2 +- scripts/play.php | 2 +- scripts/stats.php | 4 ++-- scripts/todays_detections.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/overview.php b/scripts/overview.php index cba8a9f..9087ead 100644 --- a/scripts/overview.php +++ b/scripts/overview.php @@ -64,7 +64,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
-
Confidence:
+
Confidence:
diff --git a/scripts/play.php b/scripts/play.php index ad869be..f6f84f5 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -176,7 +176,7 @@ if(isset($_GET['species'])){ $sciname = preg_replace('/ /', '_', $results['Sci_Name']); $sci_name = $results['Sci_Name']; $time = $results['Time']; - $confidence = $results['Confidence']; + $confidence = round($results['Confidence'],2); echo " $date $time
$confidence
diff --git a/scripts/stats.php b/scripts/stats.php index 230a54a..a136946 100644 --- a/scripts/stats.php +++ b/scripts/stats.php @@ -116,7 +116,7 @@ $filename = "/By_Date/".$results['Date']."/".$comname."/".$results['File_Name']; while($results=$result3->fetchArray(SQLITE3_ASSOC)){ $count = $results['COUNT(*)']; - $maxconf = $results['MAX(Confidence)']; + $maxconf = round($results['MAX(Confidence)'],2); $date = $results['Date']; $time = $results['Time']; $name = $results['Com_Name']; @@ -172,7 +172,7 @@ fwrite($excludefile, $txt);
Occurrences:
- Max Confidence:
+ Max Confidence:
Best Recording:



- Confidence:
+ Confidence:
From a6644c59aa554da094ebb974fe736a5be78c2b36 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Sat, 14 May 2022 16:10:18 -0400 Subject: [PATCH 07/37] Allow sorting of specific species in recordings TODO: add actual svg files for sort icons --- scripts/play.php | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/scripts/play.php b/scripts/play.php index f6f84f5..7825dbc 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -151,13 +151,36 @@ if(!isset($_GET['species']) && !isset($_GET['filename'])){ } #Specific Species -if(isset($_GET['species'])){ +if(isset($_GET['species'])){ ?> +
+
+ + + + +
+
+prepare("SELECT * FROM detections where Com_Name == \"$name\" AND Date == \"$date\" ORDER BY Time DESC"); + if(isset($_GET['sort']) && $_GET['sort'] == "confidence") { + $statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" AND Date == \"$date\" ORDER BY Confidence DESC"); + } else { + $statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" AND Date == \"$date\" ORDER BY Time DESC"); + } } else { - $statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" ORDER BY Date DESC, Time DESC");} + if(isset($_GET['sort']) && $_GET['sort'] == "confidence") { + $statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" ORDER BY Confidence DESC"); + } else { + $statement2 = $db->prepare("SELECT * FROM detections where Com_Name == \"$name\" ORDER BY Date DESC, Time DESC"); + } + } if($statement2 == False){ echo "Database is busy"; header("refresh: 0;"); @@ -167,6 +190,9 @@ if(isset($_GET['species'])){ $name "; + $user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd"); + $home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd"); + $home = trim($home); while($results=$result2->fetchArray(SQLITE3_ASSOC)) { $comname = preg_replace('/ /', '_', $results['Com_Name']); @@ -176,7 +202,13 @@ if(isset($_GET['species'])){ $sciname = preg_replace('/ /', '_', $results['Sci_Name']); $sci_name = $results['Sci_Name']; $time = $results['Time']; - $confidence = round($results['Confidence'],2); + $confidence = $results['Confidence']; + + // file was deleted by disk check, no need to show the detection in recordings + if(!file_exists($home."/BirdSongs/Extracted/".$filename)) { + continue; + } + echo " $date $time
$confidence
From 1e23ea2e51079aabc04739b1e6df831b975946fb Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Sat, 14 May 2022 16:17:20 -0400 Subject: [PATCH 08/37] User feedback for deleted recordings --- scripts/play.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/play.php b/scripts/play.php index 7825dbc..7418a95 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -193,6 +193,7 @@ 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); + $iter=0; while($results=$result2->fetchArray(SQLITE3_ASSOC)) { $comname = preg_replace('/ /', '_', $results['Com_Name']); @@ -208,13 +209,14 @@ if(isset($_GET['species'])){ ?> if(!file_exists($home."/BirdSongs/Extracted/".$filename)) { continue; } + $iter++; echo " $date $time
$confidence
"; - }echo "";} + }if($iter == 0){ echo "No recordings were found on this date.

They may have been deleted to make space for new recordings. You can modify this setting for the future in Tools -> Settings -> Advanced Settings -> Full Disk Behavior.";}echo "";} if(isset($_GET['filename'])){ $name = $_GET['filename']; From e16c62c1aabb7b242c127e027cab1777eeead20c Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Sat, 14 May 2022 16:21:25 -0400 Subject: [PATCH 09/37] Making recordings actually usable for huge amounts of detections --- scripts/play.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/play.php b/scripts/play.php index 7418a95..0c38486 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -212,8 +212,9 @@ if(isset($_GET['species'])){ ?> $iter++; echo " - $date $time
$confidence
- + $date $time
$confidence
+ + "; }if($iter == 0){ echo "No recordings were found on this date.

They may have been deleted to make space for new recordings. You can modify this setting for the future in Tools -> Settings -> Advanced Settings -> Full Disk Behavior.";}echo "";} @@ -242,7 +243,8 @@ if(isset($_GET['filename'])){ $confidence = $results['Confidence']; echo " $date $time
$confidence
- + + "; }echo "";}?> From 3cf41eeb889a4788802a1c64070504c8defab5bf Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Sat, 14 May 2022 17:39:53 -0400 Subject: [PATCH 10/37] adding icons --- homepage/images/sort_date.svg | 43 +++++++++++++++++++++++++++++++++++ scripts/play.php | 4 ++-- 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 homepage/images/sort_date.svg diff --git a/homepage/images/sort_date.svg b/homepage/images/sort_date.svg new file mode 100644 index 0000000..ab7fbd0 --- /dev/null +++ b/homepage/images/sort_date.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/play.php b/scripts/play.php index 0c38486..2b4058d 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -158,10 +158,10 @@ if(isset($_GET['species'])){ ?> From d6bdb5c3457f4d712c06c0884117561ff44c764c Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Mon, 16 May 2022 08:16:33 -0400 Subject: [PATCH 11/37] Don't show dates that don't have any recordings --- scripts/play.php | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/scripts/play.php b/scripts/play.php index 2b4058d..87912a8 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -9,18 +9,9 @@ if($db == False){ header("refresh: 0;"); } -#By Date -if(isset($_GET['byfilename'])){ - $statement = $db->prepare('SELECT DISTINCT(Date) FROM detections GROUP BY Date'); - if($statement == False){ - echo "Database is busy"; - header("refresh: 0;"); - } - $result = $statement->execute(); - $view = "bydate"; - #By Date -}elseif(isset($_GET['bydate'])){ - $statement = $db->prepare('SELECT DISTINCT(Date) FROM detections GROUP BY Date'); + +if(isset($_GET['bydate'])){ + $statement = $db->prepare('SELECT DISTINCT(Date) FROM detections GROUP BY Date ORDER BY Date DESC'); if($statement == False){ echo "Database is busy"; header("refresh: 0;"); @@ -79,7 +70,9 @@ if(isset($_GET['byfilename'])){ session_unset(); $view = "choose"; } - +$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd"); +$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd"); +$home = trim($home); ?> @@ -121,13 +114,15 @@ if(!isset($_GET['species']) && !isset($_GET['filename'])){ if($view == "bydate") { while($results=$result->fetchArray(SQLITE3_ASSOC)){ $date = $results['Date']; + if(realpath($home."/BirdSongs/Extracted/By_Date/".$date) !== false){ echo " - ";} + ";}} #By Species } elseif($view == "byspecies") { while($results=$result->fetchArray(SQLITE3_ASSOC)){ $name = $results['Com_Name']; + echo " ";} @@ -135,8 +130,11 @@ if(!isset($_GET['species']) && !isset($_GET['filename'])){ } elseif($view == "date") { while($results=$result->fetchArray(SQLITE3_ASSOC)){ $name = $results['Com_Name']; - echo " - ";} + if(realpath($home."/BirdSongs/Extracted/By_Date/".$date."/".str_replace(" ", "_",$name)) !== false){ + echo " + "; + } + } #Choose } else { @@ -190,9 +188,6 @@ if(isset($_GET['species'])){ ?> $name "; - $user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd"); - $home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd"); - $home = trim($home); $iter=0; while($results=$result2->fetchArray(SQLITE3_ASSOC)) { From f59d04a7bcf8ba1c13bccd302f9e43471155600a Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Mon, 16 May 2022 10:58:04 -0400 Subject: [PATCH 12/37] Allow specific recordings to be "delete protected" from disk-check.sh @mcguirepr89 I'm very happy with how this turned out! :smile: --- homepage/images/lock.svg | 42 +++++++++++++ homepage/images/unlock.svg | 40 ++++++++++++ scripts/advanced.php | 2 +- scripts/play.php | 123 ++++++++++++++++++++++++++++++++----- scripts/stats.php | 23 +++++-- 5 files changed, 209 insertions(+), 21 deletions(-) create mode 100644 homepage/images/lock.svg create mode 100644 homepage/images/unlock.svg diff --git a/homepage/images/lock.svg b/homepage/images/lock.svg new file mode 100644 index 0000000..a3bd577 --- /dev/null +++ b/homepage/images/lock.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/homepage/images/unlock.svg b/homepage/images/unlock.svg new file mode 100644 index 0000000..e8ddb69 --- /dev/null +++ b/homepage/images/unlock.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/advanced.php b/scripts/advanced.php index 9f67d46..657cb99 100644 --- a/scripts/advanced.php +++ b/scripts/advanced.php @@ -204,7 +204,7 @@ if (file_exists('./scripts/thisrun.txt')) { >Purge -

When the disk becomes full, you can choose to 'purge' old files to make room for new ones or 'keep' your data and stop all services instead.

+

When the disk becomes full, you can choose to 'purge' old files to make room for new ones or 'keep' your data and stop all services instead.
Note: you can exclude specific files from 'purge' on the Recordings page.


Set Audio Card to 'default' to use PulseAudio (always recommended), or an ALSA recognized sound card device from the output of `aplay -L`.

diff --git a/scripts/play.php b/scripts/play.php index 87912a8..2115253 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -9,6 +9,43 @@ if($db == False){ header("refresh: 0;"); } +if (file_exists('./scripts/thisrun.txt')) { + $config = parse_ini_file('./scripts/thisrun.txt'); +} elseif (file_exists('firstrun.ini')) { + $config = parse_ini_file('firstrun.ini'); +} + +$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd"); +$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd"); +$home = trim($home); + +if(isset($_GET['excludefile'])) { + 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"); + } + if(isset($_GET['exclude_add'])) { + $myfile = fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "a") or die("Unable to open file!"); + $txt = $_GET['excludefile']; + fwrite($myfile, $txt."\n"); + fwrite($myfile, $txt.".png\n"); + fclose($myfile); + echo "OK"; + die(); + } else { + $lines = file($home."/BirdNET-Pi/scripts/disk_check_exclude.txt"); + $search = $_GET['excludefile']; + + $result = ''; + foreach($lines as $line) { + if(stripos($line, $search) === false && stripos($line, $search.".png") === false) { + $result .= $line; + } + } + file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", $result); + echo "OK"; + die(); + } +} if(isset($_GET['bydate'])){ $statement = $db->prepare('SELECT DISTINCT(Date) FROM detections GROUP BY Date ORDER BY Date DESC'); @@ -70,9 +107,6 @@ if(isset($_GET['bydate'])){ session_unset(); $view = "choose"; } -$user = shell_exec("awk -F: '/1000/{print $1}' /etc/passwd"); -$home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd"); -$home = trim($home); ?> @@ -83,6 +117,29 @@ $home = trim($home); + + - @@ -199,6 +255,7 @@ if(isset($_GET['species'])){ ?> $sci_name = $results['Sci_Name']; $time = $results['Time']; $confidence = $results['Confidence']; + $filename_formatted = $date."/".$comname."/".$results['File_Name']; // file was deleted by disk check, no need to show the detection in recordings if(!file_exists($home."/BirdSongs/Extracted/".$filename)) { @@ -206,13 +263,31 @@ if(isset($_GET['species'])){ ?> } $iter++; - echo " - - "; + if($config["FULL_DISK"] == "purge") { + if(trim(shell_exec("cat ".$home."/BirdNET-Pi/scripts/disk_check_exclude.txt | grep \"".$filename_formatted."\" | wc -l")) == "0") { + $imageicon = "images/unlock.svg"; + $title = "This file is not delete protected."; + $type = "add"; + } else { + $imageicon = "images/lock.svg"; + $title = "This file is delete protected."; + $type = "del"; + } - }if($iter == 0){ echo "";}echo "
$date $time
$confidence
- -
No recordings were found on this date.

They may have been deleted to make space for new recordings. You can modify this setting for the future in Tools -> Settings -> Advanced Settings -> Full Disk Behavior.
";} + echo " + $date $time
$confidence
+ + + "; + } else { + echo " + $date $time
$confidence
+ + + "; + } + + }if($iter == 0){ echo "No recordings were found.

They may have been deleted to make space for new recordings. You can modify this setting for the future in Tools -> Settings -> Advanced Settings -> Full Disk Behavior.";}echo "";} if(isset($_GET['filename'])){ $name = $_GET['filename']; @@ -236,11 +311,29 @@ if(isset($_GET['filename'])){ $sci_name = $results['Sci_Name']; $time = $results['Time']; $confidence = $results['Confidence']; - echo " - $date $time
$confidence
- - - "; + $filename_formatted = $date."/".$comname."/".$results['File_Name']; + + if($config["FULL_DISK"] == "purge") { + if(trim(shell_exec("cat ".$home."/BirdNET-Pi/scripts/disk_check_exclude.txt | grep \"".$filename_formatted."\" | wc -l")) == "0") { + $imageicon = "images/unlock.svg"; + $title = "This file is not delete protected."; + $type = "add"; + } else { + $imageicon = "images/lock.svg"; + $title = "This file is delete protected."; + $type = "del"; + } + + echo " + $date $time
$confidence
+ + "; + } else { + echo " + $date $time
$confidence
+ + "; + } }echo "";}?> diff --git a/scripts/stats.php b/scripts/stats.php index a136946..3f41a22 100644 --- a/scripts/stats.php +++ b/scripts/stats.php @@ -56,7 +56,18 @@ 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); -file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", ""); +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 ) ); +} ?> @@ -151,21 +162,22 @@ while($results=$result3->fetchArray(SQLITE3_ASSOC)){ if($pos !== 1 && strpos($link, "upload") == true && strpos($link, "CentralAutoLogin") == false) echo ""; } -}} +} +} ?>


fetchArray(SQLITE3_ASSOC)) { $comname = preg_replace('/ /', '_', $results['Com_Name']); $comname = preg_replace('/\'/', '', $comname); $filename = "/By_Date/".$results['Date']."/".$comname."/".$results['File_Name']; -$excludefile = fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "a") or die("Unable to open file!"); -$txt = $results['Date']."/".$comname."/".$results['File_Name']."\n".$results['Date']."/".$comname."/".$results['File_Name'].".png\n"; -fwrite($excludefile, $txt); +array_push($excludelines, $results['Date']."/".$comname."/".$results['File_Name']); +array_push($excludelines, $results['Date']."/".$comname."/".$results['File_Name'].".png"); ?> @@ -176,6 +188,7 @@ fwrite($excludefile, $txt); Best Recording:
From 29f6de47aa696e29f3b3820353e58b5d00bf2612 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Mon, 16 May 2022 15:15:39 -0400 Subject: [PATCH 13/37] lazy loading --- scripts/play.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/play.php b/scripts/play.php index 2115253..d86fe34 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -276,13 +276,13 @@ if(isset($_GET['species'])){ ?> echo " $date $time
$confidence
- + "; } else { echo " $date $time
$confidence
- + "; } From a92119175f692e6f3991dea2eba29f694ca83fed Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Mon, 16 May 2022 19:50:36 -0400 Subject: [PATCH 14/37] Update todays_detections.php --- scripts/todays_detections.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/todays_detections.php b/scripts/todays_detections.php index 1651332..d2f5cfe 100644 --- a/scripts/todays_detections.php +++ b/scripts/todays_detections.php @@ -67,7 +67,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 '.$searchquery); } if($statement0 == False){ echo "Database is busy"; From ee675c4de2b7c35b37877cc616d66290a85a62c9 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Tue, 17 May 2022 11:16:55 -0400 Subject: [PATCH 15/37] Efficiency! --- scripts/play.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/play.php b/scripts/play.php index d86fe34..1e73cbf 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -220,6 +220,12 @@ if(isset($_GET['species'])){ ?> $iter++; if($config["FULL_DISK"] == "purge") { - if(trim(shell_exec("cat ".$home."/BirdNET-Pi/scripts/disk_check_exclude.txt | grep \"".$filename_formatted."\" | wc -l")) == "0") { + if(!in_array($filename_formatted, $disk_check_exclude_arr)) { $imageicon = "images/unlock.svg"; $title = "This file is not delete protected."; $type = "add"; @@ -314,7 +320,7 @@ if(isset($_GET['filename'])){ $filename_formatted = $date."/".$comname."/".$results['File_Name']; if($config["FULL_DISK"] == "purge") { - if(trim(shell_exec("cat ".$home."/BirdNET-Pi/scripts/disk_check_exclude.txt | grep \"".$filename_formatted."\" | wc -l")) == "0") { + if(!in_array($filename_formatted, $disk_check_exclude_arr)) { $imageicon = "images/unlock.svg"; $title = "This file is not delete protected."; $type = "add"; From 001ceea87354ac2b7ce46e493a4ba748d1f30dbc Mon Sep 17 00:00:00 2001 From: Patrick McGuire <60325264+mcguirepr89@users.noreply.github.com> Date: Tue, 17 May 2022 14:08:21 -0400 Subject: [PATCH 16/37] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 699facc..df8e235 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Check out birds from around the world - [Norman, Oklahoma, United States](https://normanok.birdnetpi.com)
- [Nijmegen, the Netherlands](https://nijmegen.birdnetpi.com)
- [Murrysville, Pennsylvania, United States](https://murrysvillepa.birdnetpi.com/) +- [Berowra, New South Wales, Australia](https://berowra.birdnetpi.com/) [Share your installation!!](https://github.com/mcguirepr89/BirdNET-Pi/wiki/Sharing-Your-BirdNET-Pi) From f59762229f4d7823121924a1059cd0e27294272e Mon Sep 17 00:00:00 2001 From: Patrick McGuire <60325264+mcguirepr89@users.noreply.github.com> Date: Tue, 17 May 2022 14:18:11 -0400 Subject: [PATCH 17/37] Update config.php --- scripts/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/config.php b/scripts/config.php index c662dfb..b925780 100644 --- a/scripts/config.php +++ b/scripts/config.php @@ -206,7 +206,7 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken} ?>
If connected to the internet, retrieve time automatically? - >
+ >
> >


From f7ce7545abeb9828a410afee984edc3a8ec36dbd Mon Sep 17 00:00:00 2001 From: Patrick McGuire <60325264+mcguirepr89@users.noreply.github.com> Date: Tue, 17 May 2022 15:11:52 -0400 Subject: [PATCH 18/37] iOS doesn't seem to support lazy loading on my iPhone, Safari and Chrome wouldn't load with the `loading="lazy"` method specified. Removing it fixes that -- I want to change it back for now, but then maybe figure out how to make it work on iOS ( I assume it is more resourceful to use lazy loading?) --- scripts/play.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/play.php b/scripts/play.php index 1e73cbf..91e6b1c 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -282,13 +282,13 @@ if(isset($_GET['species'])){ ?> echo " $date $time
$confidence
- + "; } else { echo " $date $time
$confidence
- + "; } From 8b8688d3d271cc4aaf346d03a5d61cf64349d017 Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Tue, 17 May 2022 15:36:18 -0400 Subject: [PATCH 19/37] these are the changes from small_tweaks --- scripts/birdnet_log.sh | 2 +- scripts/restart_services.sh | 2 +- scripts/server.py | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/birdnet_log.sh b/scripts/birdnet_log.sh index e480664..1d49023 100755 --- a/scripts/birdnet_log.sh +++ b/scripts/birdnet_log.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -journalctl --no-hostname -q -o short -fu birdnet_analysis -ubirdnet_server -uextraction | sed "s/$(date "+%b %d ")//g;s/${HOME//\//\\/}\///g" +journalctl --no-hostname -q -o short -fu birdnet_analysis -ubirdnet_server -uextraction | sed "s/$(date "+%b %d ")//g;s/${HOME//\//\\/}\///g;/Line/d;/systemd/d;s/ .*\[.*\]: /---/" diff --git a/scripts/restart_services.sh b/scripts/restart_services.sh index f0cead9..98993af 100755 --- a/scripts/restart_services.sh +++ b/scripts/restart_services.sh @@ -22,6 +22,6 @@ sudo systemctl restart "${i}" done until grep 5050 <(netstat -tulpn 2>&1);do sudo systemctl restart birdnet_server.service -sleep 20 +sleep 30 done sudo systemctl restart birdnet_analysis.service diff --git a/scripts/server.py b/scripts/server.py index 6c96e12..35833e1 100755 --- a/scripts/server.py +++ b/scripts/server.py @@ -261,7 +261,7 @@ def writeResultsToFile(detections, min_conf, path): def handle_client(conn, addr): global INCLUDE_LIST global EXCLUDE_LIST - print(f"[NEW CONNECTION] {addr} connected.") + #print(f"[NEW CONNECTION] {addr} connected.") connected = True while connected: @@ -335,7 +335,7 @@ def handle_client(conn, addr): # Get Date/Time from filename in case Pi gets behind #now = datetime.now() full_file_name = args.i - print('FULL FILENAME: -' + full_file_name + '-') + #print('FULL FILENAME: -' + full_file_name + '-') file_name = Path(full_file_name).stem file_date = file_name.split('-birdnet-')[0] file_time = file_name.split('-birdnet-')[1] @@ -463,13 +463,13 @@ def start(): global INTERPRETER, INCLUDE_LIST, EXCLUDE_LIST INTERPRETER = loadModel() server.listen() - print(f"[LISTENING] Server is listening on {SERVER}") + #print(f"[LISTENING] Server is listening on {SERVER}") while True: conn, addr = server.accept() thread = threading.Thread(target=handle_client, args=(conn, addr)) thread.start() - print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}") + #print(f"[ACTIVE CONNECTIONS] {threading.activeCount() - 1}") -print("[STARTING] server is starting...") +#print("[STARTING] server is starting...") start() From 5178a9876695cd73f8eea99884bde1e7a462e463 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Tue, 17 May 2022 18:54:48 -0400 Subject: [PATCH 20/37] Efficiency, the sequel! --- scripts/play.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/play.php b/scripts/play.php index 1e73cbf..c32c7d7 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -319,6 +319,12 @@ if(isset($_GET['filename'])){ $confidence = $results['Confidence']; $filename_formatted = $date."/".$comname."/".$results['File_Name']; + // add disk_check_exclude.txt lines into an array for grepping + $fp = @fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", 'r'); + if ($fp) { + $disk_check_exclude_arr = explode("\n", fread($fp, filesize($home."/BirdNET-Pi/scripts/disk_check_exclude.txt"))); + } + if($config["FULL_DISK"] == "purge") { if(!in_array($filename_formatted, $disk_check_exclude_arr)) { $imageicon = "images/unlock.svg"; From c859c2250b40f39df5e0d5879a65fb3810634351 Mon Sep 17 00:00:00 2001 From: CaiusX Date: Wed, 18 May 2022 14:35:34 +0200 Subject: [PATCH 21/37] New and improved Species Stats --- scripts/plotly_streamlit.py | 113 ++++++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/scripts/plotly_streamlit.py b/scripts/plotly_streamlit.py index b42906d..da8d771 100755 --- a/scripts/plotly_streamlit.py +++ b/scripts/plotly_streamlit.py @@ -60,7 +60,8 @@ df2=df2.set_index('DateTime') # Date as slider Start_Date = pd.to_datetime(df2.index.min()).date() End_Date = pd.to_datetime(df2.index.max()).date() -Date_Slider = st.slider('Date Range', +cols1,cols2= st.columns((1,1)) +Date_Slider = cols1.slider('Date Range', min_value = Start_Date-timedelta(days=1), max_value = End_Date, value=(Start_Date, @@ -72,14 +73,29 @@ Date_Slider = st.slider('Date Range', filt = (df2.index >= pd.Timestamp(Date_Slider[0])) & (df2.index <= pd.Timestamp(Date_Slider[1]+timedelta(days=1))) df2 = df2[filt] +st.write('', unsafe_allow_html=True) +st.write('', unsafe_allow_html=True) + +resample_sel=cols2.radio("Select Resample Resolution - To downsample and make run faster select longer period, Daily provides a view on detections at 15 min intervals through the day", ('1 minute', '5 minutes', '10 minutes', 'Hourly', 'Daily')) + +resample_times = {'1 minute':'1min', + '5 minutes':'5min', + '10 minutes':'10min', + 'Hourly':'1H', + 'Daily':'1D' + } +resample_time = resample_times[resample_sel] + +df5=df2.resample(resample_time)['Com_Name'].aggregate('unique').explode() + #Create species count for selected date range -Specie_Count=df2['Com_Name'].value_counts() +Specie_Count=df5.value_counts() #Create species treemap # Create Hourly Crosstab -hourly=pd.crosstab(df2['Com_Name'],df2.index.hour, dropna=False) +hourly=pd.crosstab(df5,df5.index.hour, dropna=False) # Filter on species species = list(hourly.index) @@ -91,7 +107,7 @@ top_N = cols1.slider( value=min(10,len(Specie_Count)) ) -top_N_species = (df2['Com_Name'].value_counts()[:top_N]) +top_N_species = (df5.value_counts()[:top_N]) specie = cols2.selectbox('Which bird would you like to explore for the dates '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'?', species, @@ -104,13 +120,13 @@ font_size=15 #specie filter filt=df2['Com_Name']==specie -df_counts=df2[filt].resample('D').count() +df_counts=sum(df5==specie) 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=('Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'', - 'Total Detect:'+str('{:,}'.format(sum(df_counts.Time)))+ + '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)) ) @@ -123,43 +139,80 @@ fig.add_trace(go.Bar(y=top_N_species.index, x=top_N_species, orientation='h'), r 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) +specie_filt= df5==specie +df3=df5[specie_filt] + +detections2= pd.crosstab(df3, df3.index.hour) + + + + 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, - width = 1000, - height = 500, - showlegend=False, - polar = dict( - radialaxis = dict( - tickfont_size = font_size, - showticklabels = True, - hoverformat = "#%{theta}:
Popularity: %{percent}
%{r}" + +if resample_time != '1D': + fig.add_trace(go.Barpolar(r = detections, theta=theta), row=1, col=2) + fig.update_layout( + autosize=False, + width = 1000, + height = 500, + showlegend=False, + polar = dict( + radialaxis = dict( + tickfont_size = font_size, + showticklabels = False, + hoverformat = "#%{theta}:
Popularity: %{percent}
%{r}" + ), + angularaxis = dict( + tickfont_size= font_size, + rotation = -90, + direction = 'clockwise', + tickmode='array', + tickvals=[0,15,35,45,60,75,90,105,120,135,150,165,180,195,210,225,240,255,270,285,300,315,330,345], + ticktext=['12am','1am','2am','3am','4am','5am', '6am','7am','8am','9am','10am','11am','12pm','1pm','2pm','3pm','4pm','5pm', '6pm','7pm','8pm','9pm','10pm','11pm'], + hoverformat = "#%{theta}:
Popularity: %{percent}
%{r}" ), - angularaxis = dict( - tickfont_size= font_size, - rotation = -90, - direction = 'clockwise', - tickmode='array', - tickvals=[0,15,35,45,60,75,90,105,120,135,150,165,180,195,210,225,240,255,270,285,300,315,330,345], - ticktext=['12am','1am','2am','3am','4am','5am', '6am','7am','8am','9am','10am','11am','12pm','1pm','2pm','3pm','4pm','5pm', '6pm','7pm','8pm','9pm','10pm','11pm'], - hoverformat = "#%{theta}:
Popularity: %{percent}
%{r}" - ), - ), - ) + ), + ) -daily=pd.crosstab(df2['Com_Name'],df2.index.date, dropna=False) + daily=pd.crosstab(df5,df5.index.date, dropna=False) -fig.add_trace(go.Bar(x=daily.columns, y=daily.loc[specie]), row=3, col=2) + fig.add_trace(go.Bar(x=daily.columns, y=daily.loc[specie]), row=3, col=2) +else: + fig = make_subplots( + rows=1, cols =2, +# specs= [[{"type":"xy","rowspan":1}], +# [{"rowspan":1}], +# ], +# subplot_titles=('Top '+ str(top_N) + ' Species in Date Range '+str(Date_Slider[0])+' to '+str(Date_Slider[1])+'', +# '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)) +# ) + ) + 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) + + fig_x = [d.strftime('%d-%m-%Y') for d in day_hour_freq.index.tolist()] + 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) # container=st.container() # config={'displayModelBar': False} st.plotly_chart(fig, use_container_width=True) #, config=config) From fb703030c777523af7e88042a2dd3fa662032435 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Wed, 18 May 2022 14:03:49 -0400 Subject: [PATCH 22/37] Flickr Implementation --- birdnet.conf-defaults | 4 ++ homepage/style.css | 34 ++++++++++++++- scripts/config.php | 8 ++++ scripts/install_config.sh | 4 ++ scripts/overview.php | 70 +++++++++++++++++++++++++----- scripts/stats.php | 44 ++++++++++++++----- scripts/todays_detections.php | 57 +++++++++++++++++++++++- scripts/update_birdnet_snippets.sh | 3 ++ 8 files changed, 198 insertions(+), 26 deletions(-) diff --git a/birdnet.conf-defaults b/birdnet.conf-defaults index 2b6f9f3..e75cda1 100644 --- a/birdnet.conf-defaults +++ b/birdnet.conf-defaults @@ -62,6 +62,10 @@ 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 +#---------------------- Flickr Images API Configuration -----------------------# + +FLICKR_API_KEY= + ################################################################################ #-------------------------------- Defaults ----------------------------------# ################################################################################ diff --git a/homepage/style.css b/homepage/style.css index 91547b0..0eed7bc 100644 --- a/homepage/style.css +++ b/homepage/style.css @@ -585,4 +585,36 @@ tr { input { box-shadow: 0px 0px 17px 1px rgba(0, 0, 0, 0.10); } - +dialog { + border:none; +} +dialog::backdrop { + background: repeating-linear-gradient( + 30deg, + rgba(24, 194, 236, 0.2), + rgba(24, 194, 236, 0.2) 1px, + rgba(24, 194, 236, 0.3) 1px, + rgba(24, 194, 236, 0.3) 20px + ); + backdrop-filter: blur(1px) +} +.centered_image_container { + font-size:19px !important; + display:inline-block; + position:relative; + margin-bottom:3px; +} +.centered_image_container img.img1 { + cursor:pointer; + height:95%; + position:absolute; + right:110%; + top:0px; + border-radius: 5px; +} +.centered_image_container * { + font-size:19px !important; +} +.centered_image_container form { + margin-bottom:0px; +} \ No newline at end of file diff --git a/scripts/config.php b/scripts/config.php index b925780..cabaa7e 100644 --- a/scripts/config.php +++ b/scripts/config.php @@ -10,6 +10,7 @@ $birdweather_id = $_GET["birdweather_id"]; $apprise_input = $_GET['apprise_input']; $apprise_notification_title = $_GET['apprise_notification_title']; $apprise_notification_body = $_GET['apprise_notification_body']; +$flickr_api_key = $_GET['flickr_api_key']; if(isset($_GET['apprise_notify_each_detection'])) { $apprise_notify_each_detection = 1; } else { @@ -45,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("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents); $contents2 = file_get_contents("./scripts/thisrun.txt"); @@ -54,6 +56,8 @@ $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("/FLICKR_API_KEY=.*/", "FLICKR_API_KEY=$flickr_api_key", $contents2); + $fh = fopen("/etc/birdnet/birdnet.conf", "w"); $fh2 = fopen("./scripts/thisrun.txt", "w"); @@ -144,6 +148,10 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}
>

+

Bird Photos from Flickr

+ +
+

Set your Flickr API key to enable the display of bird images next to detections. Get your free key here.

Localization

-
- -
Confidence:
- - +
+ + + +
+ +
+ +
Confidence:

+ +
+ +

+

+ +
+
+ +

+

+ +
+

Choose a species to load images from Wikimedia Commons.

+?>

Choose a species to load images from Flickr.

fetchArray(SQLITE3_ASSOC)){ $count = $results['COUNT(*)']; $maxconf = round($results['MAX(Confidence)'],2); @@ -147,20 +170,19 @@ while($results=$result3->fetchArray(SQLITE3_ASSOC)){ -

Loading Images from Wikimedia Commons

", '6096'); +

Loading Images from Flickr

", '6096'); echo ""; ob_flush(); flush(); - $imagelink = "https://commons.wikimedia.org/w/index.php?search=$linkname&title=Special:MediaSearch&go=Go&type=image"; - $homepage = file_get_contents($imagelink); - preg_match_all("{}ims", $homepage, $matches, PREG_SET_ORDER); - foreach ($matches as $val) { - $pos = strpos($val[2],"/"); - $link = substr($val[2],1,-1); - if($pos !== 1 && strpos($link, "upload") == true && strpos($link, "CentralAutoLogin") == false) - echo ""; + $flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=2158f6c5d66e89778bd0d340953f1bcf&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) { + $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 "".$iter.""; } } } diff --git a/scripts/todays_detections.php b/scripts/todays_detections.php index d2f5cfe..6daea3a 100644 --- a/scripts/todays_detections.php +++ b/scripts/todays_detections.php @@ -77,7 +77,15 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { ?> fetchArray(SQLITE3_ASSOC)) { $iterations++; @@ -86,13 +94,35 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { $comname = preg_replace('/\'/', '_', $comname); $filename = "/By_Date/".date('Y-m-d')."/".$comname."/".$todaytable['File_Name']; $sciname = preg_replace('/ /', '_', $todaytable['Sci_Name']); + + 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)); + if($key !== false) { + $image = $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=".$comname."&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=landscape,square&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]; + } + } ?> - @@ -132,6 +162,29 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
+ +

+

+ +
+

Number of Detections


+
+ +
+ + + + +


- Confidence:
+ Confidence:

diff --git a/scripts/update_birdnet_snippets.sh b/scripts/update_birdnet_snippets.sh index eea92dc..0082ca2 100755 --- a/scripts/update_birdnet_snippets.sh +++ b/scripts/update_birdnet_snippets.sh @@ -50,3 +50,6 @@ if grep bash $HOME/BirdNET-Pi/templates/web_terminal.service;then sudo systemctl restart web_terminal.service fi [ -L ~/BirdSongs/Extracted/static ] || ln -sf ~/BirdNET-Pi/homepage/static ~/BirdSongs/Extracted +if ! grep FLICKR_API_KEY /etc/birdnet/birdnet.conf &>/dev/null;then + sudo -u$USER echo "FLICKR_API_KEY=" >> /etc/birdnet/birdnet.conf +fi From 94c16d09703554f57533936644144dd207b7a288 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Wed, 18 May 2022 14:04:28 -0400 Subject: [PATCH 23/37] Update config.php --- scripts/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/config.php b/scripts/config.php index cabaa7e..7fbd0ed 100644 --- a/scripts/config.php +++ b/scripts/config.php @@ -150,7 +150,7 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}

Bird Photos from Flickr

-
+

Set your Flickr API key to enable the display of bird images next to detections. Get your free key here.

Localization

From 5bd1835b6f6dc0c870d1b794c4d7183c668be16b Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Wed, 18 May 2022 14:14:59 -0400 Subject: [PATCH 24/37] better mobile formatting --- scripts/overview.php | 2 +- scripts/todays_detections.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/overview.php b/scripts/overview.php index 80c95e2..7d596fd 100644 --- a/scripts/overview.php +++ b/scripts/overview.php @@ -52,7 +52,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse if($key !== false) { $image = $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=".$comname."&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=landscape,square&format=json&nojsoncallback=1"), true)["photos"]["photo"][0]; + $flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=\"".$comname."\"&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=square,portrait&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'; diff --git a/scripts/todays_detections.php b/scripts/todays_detections.php index 6daea3a..0eaea0d 100644 --- a/scripts/todays_detections.php +++ b/scripts/todays_detections.php @@ -101,7 +101,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { if($key !== false) { $image = $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=".$comname."&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=landscape,square&format=json&nojsoncallback=1"), true)["photos"]["photo"][0]; + $flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=\"".$comname."\"&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=square,portrait&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'; From 53bc242fffcb369b20d0dada190b2f267c5596d6 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Wed, 18 May 2022 14:16:20 -0400 Subject: [PATCH 25/37] searching fix --- scripts/overview.php | 2 +- scripts/todays_detections.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/overview.php b/scripts/overview.php index 7d596fd..580217b 100644 --- a/scripts/overview.php +++ b/scripts/overview.php @@ -52,7 +52,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse if($key !== false) { $image = $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=\"".$comname."\"&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=square,portrait&format=json&nojsoncallback=1"), true)["photos"]["photo"][0]; + $flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=".$comname."&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=square,portrait&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'; diff --git a/scripts/todays_detections.php b/scripts/todays_detections.php index 0eaea0d..c2fcd8a 100644 --- a/scripts/todays_detections.php +++ b/scripts/todays_detections.php @@ -101,7 +101,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { if($key !== false) { $image = $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=\"".$comname."\"&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=square,portrait&format=json&nojsoncallback=1"), true)["photos"]["photo"][0]; + $flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=".$comname."&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=square,portrait&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'; From cdbc0960ce85c7f0f4a4d1e74a40d6d8b03cb51e Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Wed, 18 May 2022 14:20:05 -0400 Subject: [PATCH 26/37] Update style.css --- homepage/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/homepage/style.css b/homepage/style.css index 0eed7bc..b43b2c3 100644 --- a/homepage/style.css +++ b/homepage/style.css @@ -611,6 +611,7 @@ dialog::backdrop { right:110%; top:0px; border-radius: 5px; + width:unset; } .centered_image_container * { font-size:19px !important; From b12c2846dab7b038e448cd458f96680c75f5287a Mon Sep 17 00:00:00 2001 From: mcguirepr89 Date: Wed, 18 May 2022 17:41:55 -0400 Subject: [PATCH 27/37] open flicker in new tab --- scripts/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/config.php b/scripts/config.php index 7fbd0ed..4fecbba 100644 --- a/scripts/config.php +++ b/scripts/config.php @@ -151,7 +151,7 @@ https://discordapp.com/api/webhooks/{WebhookID}/{WebhookToken}

Bird Photos from Flickr


-

Set your Flickr API key to enable the display of bird images next to detections. Get your free key here.

+

Set your Flickr API key to enable the display of bird images next to detections. Get your free key here.

Localization


-
Confidence:

+
Confidence:

From c3fe4828f1042bfd864c4011b7ca4ffa3f4425de Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Thu, 19 May 2022 08:16:20 -0400 Subject: [PATCH 29/37] Let's actually use CC images! --- scripts/overview.php | 2 +- scripts/todays_detections.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/overview.php b/scripts/overview.php index e025239..f8479da 100644 --- a/scripts/overview.php +++ b/scripts/overview.php @@ -52,7 +52,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse if($key !== false) { $image = $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=".$comname."&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=square,portrait&format=json&nojsoncallback=1"), true)["photos"]["photo"][0]; + $flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=".$comname."&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=5&orientation=square,portrait&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'; diff --git a/scripts/todays_detections.php b/scripts/todays_detections.php index c2fcd8a..18ee509 100644 --- a/scripts/todays_detections.php +++ b/scripts/todays_detections.php @@ -101,7 +101,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { if($key !== false) { $image = $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=".$comname."&license=7%2C9%2C10&sort=relevance&per_page=5&orientation=square,portrait&format=json&nojsoncallback=1"), true)["photos"]["photo"][0]; + $flickrjson = json_decode(file_get_contents("https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=".$config["FLICKR_API_KEY"]."&text=".$comname."&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=5&orientation=square,portrait&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'; From 787c5d65a980092a583aa9ca334bb254ac80e9be Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Thu, 19 May 2022 08:30:48 -0400 Subject: [PATCH 30/37] better search results also adding percentage conf to other files --- scripts/overview.php | 2 +- scripts/play.php | 4 ++-- scripts/stats.php | 4 ++-- scripts/todays_detections.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/overview.php b/scripts/overview.php index f8479da..49c0ccb 100644 --- a/scripts/overview.php +++ b/scripts/overview.php @@ -52,7 +52,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse if($key !== false) { $image = $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=".$comname."&license=2%2C3%2C4%2C5%2C6%2C9&sort=relevance&per_page=5&orientation=square,portrait&format=json&nojsoncallback=1"), true)["photos"]["photo"][0]; + $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'; diff --git a/scripts/play.php b/scripts/play.php index 370f53f..aca9574 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -260,7 +260,7 @@ if(isset($_GET['species'])){ ?> $sciname = preg_replace('/ /', '_', $results['Sci_Name']); $sci_name = $results['Sci_Name']; $time = $results['Time']; - $confidence = $results['Confidence']; + $confidence = round((float)round($results['Confidence'],2) * 100 ) . '%'; $filename_formatted = $date."/".$comname."/".$results['File_Name']; // file was deleted by disk check, no need to show the detection in recordings @@ -316,7 +316,7 @@ if(isset($_GET['filename'])){ $sciname = preg_replace('/ /', '_', $results['Sci_Name']); $sci_name = $results['Sci_Name']; $time = $results['Time']; - $confidence = $results['Confidence']; + $confidence = round((float)round($results['Confidence'],2) * 100 ) . '%'; $filename_formatted = $date."/".$comname."/".$results['File_Name']; // add disk_check_exclude.txt lines into an array for grepping diff --git a/scripts/stats.php b/scripts/stats.php index 65d532b..cfa2c47 100644 --- a/scripts/stats.php +++ b/scripts/stats.php @@ -150,7 +150,7 @@ function setModalText(iter, title, text, authorlink) { $iter=0; while($results=$result3->fetchArray(SQLITE3_ASSOC)){ $count = $results['COUNT(*)']; - $maxconf = round($results['MAX(Confidence)'],2); + $maxconf = round((float)round($results['MAX(Confidence)'],2) * 100 ) . '%'; $date = $results['Date']; $time = $results['Time']; $name = $results['Com_Name']; @@ -206,7 +206,7 @@ array_push($excludelines, $results['Date']."/".$comname."/".$results['File_Name' Date: Thu, 19 May 2022 09:38:25 -0400 Subject: [PATCH 31/37] typo --- scripts/stats.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/stats.php b/scripts/stats.php index cfa2c47..fce469b 100644 --- a/scripts/stats.php +++ b/scripts/stats.php @@ -206,7 +206,7 @@ array_push($excludelines, $results['Date']."/".$comname."/".$results['File_Name' - \ No newline at end of file + From da4a8cc4ced0ae39e996ee0ecb2bd331e6c327bc Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Thu, 19 May 2022 14:03:14 -0400 Subject: [PATCH 32/37] Live spectrogram @mcguirepr89 I found this cool demo on codepen: https://codepen.io/jakealbaugh/pen/jvQweW And I adapted it to use the BirdNET-Pi audio livestream. Check it out!! --- scripts/spectrogram.php | 97 +++++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 14 deletions(-) diff --git a/scripts/spectrogram.php b/scripts/spectrogram.php index 8140cfc..48d384c 100644 --- a/scripts/spectrogram.php +++ b/scripts/spectrogram.php @@ -1,16 +1,85 @@ -"; -?> + + + +

Loading...

+ + From 389abfdd017ec33044cbaace6d07118dc7f13986 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Thu, 19 May 2022 15:32:15 -0400 Subject: [PATCH 33/37] Update spectrogram.php --- scripts/spectrogram.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/spectrogram.php b/scripts/spectrogram.php index 48d384c..48eb20f 100644 --- a/scripts/spectrogram.php +++ b/scripts/spectrogram.php @@ -20,7 +20,7 @@ function initialize() { const ACTX = new AudioContext(); const ANALYSER = ACTX.createAnalyser(); - ANALYSER.fftSize = 4096; + ANALYSER.fftSize = 2048; process(); @@ -44,7 +44,7 @@ function initialize() { CTX.putImageData(imgData, 0, 0); ANALYSER.getByteFrequencyData(DATA); for (let i = 0; i < LEN; i++) { - let rat = DATA[i] / 255; + let rat = DATA[i] / 128; let hue = Math.round((rat * 120) + 280 % 360); let sat = '100%'; let lit = 10 + (70 * rat) + '%'; From 9a906dcbc87308cdb6889a6be4403510e374346b Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Thu, 19 May 2022 17:16:03 -0400 Subject: [PATCH 34/37] Auth on delete protection change --- scripts/play.php | 76 ++++++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/scripts/play.php b/scripts/play.php index aca9574..9789c3d 100644 --- a/scripts/play.php +++ b/scripts/play.php @@ -20,30 +20,46 @@ $home = shell_exec("awk -F: '/1000/{print $6}' /etc/passwd"); $home = trim($home); if(isset($_GET['excludefile'])) { - 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"); - } - if(isset($_GET['exclude_add'])) { - $myfile = fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "a") or die("Unable to open file!"); - $txt = $_GET['excludefile']; - fwrite($myfile, $txt."\n"); - fwrite($myfile, $txt.".png\n"); - fclose($myfile); - echo "OK"; - die(); - } else { - $lines = file($home."/BirdNET-Pi/scripts/disk_check_exclude.txt"); - $search = $_GET['excludefile']; + if(isset($_SERVER['PHP_AUTH_USER'])) { + $submittedpwd = $_SERVER['PHP_AUTH_PW']; + $submitteduser = $_SERVER['PHP_AUTH_USER']; + if($submittedpwd == $config['CADDY_PWD'] && $submitteduser == 'birdnet'){ + 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"); + } + if(isset($_GET['exclude_add'])) { + $myfile = fopen($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", "a") or die("Unable to open file!"); + $txt = $_GET['excludefile']; + fwrite($myfile, $txt."\n"); + fwrite($myfile, $txt.".png\n"); + fclose($myfile); + echo "OK"; + die(); + } else { + $lines = file($home."/BirdNET-Pi/scripts/disk_check_exclude.txt"); + $search = $_GET['excludefile']; - $result = ''; - foreach($lines as $line) { - if(stripos($line, $search) === false && stripos($line, $search.".png") === false) { - $result .= $line; + $result = ''; + foreach($lines as $line) { + if(stripos($line, $search) === false && stripos($line, $search.".png") === false) { + $result .= $line; + } } + file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", $result); + echo "OK"; + die(); + } + } else { + header('WWW-Authenticate: Basic realm="My Realm"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'You must be authenticated to change the protection of files.'; + exit; } - file_put_contents($home."/BirdNET-Pi/scripts/disk_check_exclude.txt", $result); - echo "OK"; - die(); + } else { + header('WWW-Authenticate: Basic realm="My Realm"'); + header('HTTP/1.0 401 Unauthorized'); + echo 'You must be authenticated to change the protection of files.'; + exit; } } @@ -121,14 +137,16 @@ if(isset($_GET['bydate'])){ function toggleLock(filename, type, elem) { const xhttp = new XMLHttpRequest(); xhttp.onload = function() { - if(type == "add") { - elem.setAttribute("src","images/lock.svg"); - elem.setAttribute("title", "This file is delete protected."); - elem.setAttribute("onclick", elem.getAttribute("onclick").replace("add","del")); - } else { - elem.setAttribute("src","images/unlock.svg"); - elem.setAttribute("title", "This file is not delete protected."); - elem.setAttribute("onclick", elem.getAttribute("onclick").replace("del","add")); + if(this.responseText == "OK"){ + if(type == "add") { + elem.setAttribute("src","images/lock.svg"); + elem.setAttribute("title", "This file is delete protected."); + elem.setAttribute("onclick", elem.getAttribute("onclick").replace("add","del")); + } else { + elem.setAttribute("src","images/unlock.svg"); + elem.setAttribute("title", "This file is not delete protected."); + elem.setAttribute("onclick", elem.getAttribute("onclick").replace("del","add")); + } } } if(type == "add") { From 5cbe301fd853c6ae5493ec154a812b5095cc8a7f Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Thu, 19 May 2022 18:29:14 -0400 Subject: [PATCH 35/37] Larger bird images upon click --- scripts/overview.php | 6 +++--- scripts/todays_detections.php | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/overview.php b/scripts/overview.php index 49c0ccb..388476a 100644 --- a/scripts/overview.php +++ b/scripts/overview.php @@ -86,7 +86,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse @@ -130,7 +130,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { - + @@ -180,7 +180,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { } function setModalText(iter, title, text, authorlink, photolink) { - document.getElementById('modalHeading').innerHTML = "Photo: \""+title+"\" Attribution"; + document.getElementById('modalHeading').innerHTML = "Photo: \""+decodeURIComponent(title).replace("+"," ")+"\" Attribution"; document.getElementById('modalText').innerHTML = "

Image link: "+text+"
Author link: "+authorlink+"
"; showDialog(); } @@ -211,7 +211,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {

Today's Detections —

-
+

Loading...

From d94a734c1b666dd8ff7b5ee88a7ae6cf93031325 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Fri, 20 May 2022 08:38:42 -0400 Subject: [PATCH 37/37] Fixing bugs I created when I was fixing bugs --- scripts/overview.php | 2 +- scripts/todays_detections.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/overview.php b/scripts/overview.php index f05ceea..a5a3bdc 100644 --- a/scripts/overview.php +++ b/scripts/overview.php @@ -199,7 +199,7 @@ body::-webkit-scrollbar { } function setModalText(iter, title, text, authorlink, photolink) { - document.getElementById('modalHeading').innerHTML = "Photo: \""+decodeURIComponent(title).replace("+"," ")+"\" Attribution"; + document.getElementById('modalHeading').innerHTML = "Photo: \""+decodeURIComponent(title.replace("+"," "))+"\" Attribution"; document.getElementById('modalText').innerHTML = "

Image link: "+text+"
Author link: "+authorlink+"
"; showDialog(); } diff --git a/scripts/todays_detections.php b/scripts/todays_detections.php index 9c2a920..bacd06f 100644 --- a/scripts/todays_detections.php +++ b/scripts/todays_detections.php @@ -180,7 +180,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { } function setModalText(iter, title, text, authorlink, photolink) { - document.getElementById('modalHeading').innerHTML = "Photo: \""+decodeURIComponent(title).replace("+"," ")+"\" Attribution"; + document.getElementById('modalHeading').innerHTML = "Photo: \""+decodeURIComponent(title.replace("+"," "))+"\" Attribution"; document.getElementById('modalText').innerHTML = "

Image link: "+text+"
Author link: "+authorlink+"
"; showDialog(); }

Occurrences:
- Max Confidence:
+ Max Confidence:
Best Recording:

Occurrences:
- Max Confidence:
+ Max Confidence:
Best Recording:
- +
@@ -198,9 +198,9 @@ body::-webkit-scrollbar { document.getElementById('attribution-dialog').close(); } - function setModalText(iter, title, text, authorlink) { + function setModalText(iter, title, text, authorlink, photolink) { document.getElementById('modalHeading').innerHTML = "Photo: \""+title+"\" Attribution"; - document.getElementById('modalText').innerHTML = "Image link: "+text+"
Author link: "+authorlink+""; + document.getElementById('modalText').innerHTML = "

Image link: "+text+"
Author link: "+authorlink+"
"; showDialog(); } diff --git a/scripts/todays_detections.php b/scripts/todays_detections.php index caa3a62..7903f0c 100644 --- a/scripts/todays_detections.php +++ b/scripts/todays_detections.php @@ -116,7 +116,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
- +
@@ -179,9 +179,9 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) { document.getElementById('attribution-dialog').close(); } - function setModalText(iter, title, text, authorlink) { - document.getElementById('modalHeading').innerHTML = "Photo "+iter+": \""+title+"\" Attribution"; - document.getElementById('modalText').innerHTML = "Image link: "+text+"
Author link: "+authorlink+""; + function setModalText(iter, title, text, authorlink, photolink) { + document.getElementById('modalHeading').innerHTML = "Photo: \""+title+"\" Attribution"; + document.getElementById('modalText').innerHTML = "

Image link: "+text+"
Author link: "+authorlink+"
"; showDialog(); } From 78b009abffb31be79dbf3e7a83a8d1ddc61057d4 Mon Sep 17 00:00:00 2001 From: ehpersonal38 <103586016+ehpersonal38@users.noreply.github.com> Date: Fri, 20 May 2022 08:34:01 -0400 Subject: [PATCH 36/37] Fixing bugs --- scripts/overview.php | 4 ++-- scripts/todays_detections.php | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/overview.php b/scripts/overview.php index 388476a..f05ceea 100644 --- a/scripts/overview.php +++ b/scripts/overview.php @@ -86,7 +86,7 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" && isse
- + @@ -199,7 +199,7 @@ body::-webkit-scrollbar { } function setModalText(iter, title, text, authorlink, photolink) { - document.getElementById('modalHeading').innerHTML = "Photo: \""+title+"\" Attribution"; + document.getElementById('modalHeading').innerHTML = "Photo: \""+decodeURIComponent(title).replace("+"," ")+"\" Attribution"; document.getElementById('modalText').innerHTML = "

Image link: "+text+"
Author link: "+authorlink+"
"; showDialog(); } diff --git a/scripts/todays_detections.php b/scripts/todays_detections.php index 7903f0c..9c2a920 100644 --- a/scripts/todays_detections.php +++ b/scripts/todays_detections.php @@ -116,13 +116,13 @@ if(isset($_GET['ajax_detections']) && $_GET['ajax_detections'] == "true" ) {
- +


- Confidence:

+ Confidence:




Confidence:
Confidence: