Vogelalbum 1.0.0
Eine Kamera am Vogelhaus, ein Rechner der erkennt wer da war — und eine Sammlung, die sich von allein fuellt. Zwei neuronale Netze: eines findet den Vogel im Bild, eines bestimmt die Art. 148 heimische Arten, auf Deutschland eingeschraenkt und neu normiert — ohne diese Einschraenkung wird aus einer Kohlmeise eine nordamerikanische Schwesterart. Daraus entsteht ein Sammelalbum: nummerierte Seiten, gefundene Arten in Farbe, fehlende als Schatten, dazu Erstfang, Punkte, Stufen und 15 Abzeichen. Laeuft vollstaendig auf eigener Hardware. Kein Cloud-Dienst, kein Konto, keine Telemetrie; die Bilder verlassen das eigene Netz nicht. Zwei getrennt deploybare Teile: die Sammlung als Django-Anwendung, die Erkennung als eigener Dienst. Der Erkennungs-Dienst fragt selbst an und braucht deshalb weder eine erreichbare Adresse noch einen offenen Port — er laeuft hinter einem gewoehnlichen Router. Dabei: Firmware fuer Raspberry Pi und ESP32-S3, Anbindung an Home Assistant ueber MQTT, Oberflaeche auf Deutsch und Englisch, Einstellungen je Kamera aus dem Browser, und ein Weg fuer Fotos von unterwegs. AGPL-3.0.
This commit is contained in:
commit
c11fe9ab04
189 files changed
+29806
No files matched your search
Executable
+131
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env bash
|
||||
# Holt drei Beispielfotos als Vorlage fuer 'detector karte' und 'detector test'.
|
||||
#
|
||||
# Sie liegen nicht im Repo, und zwar nicht aus Platzgruenden: Fotos von
|
||||
# Wikimedia Commons stehen unter eigenen Lizenzen (meist CC BY-SA) und
|
||||
# verlangen Namensnennung. Ein Bild ohne seine Herkunft ins Repo zu legen
|
||||
# waere genau der Fehler, den die Lizenz verhindern soll — deshalb kommen sie
|
||||
# samt Urheber und Lizenz hierher, nachvollziehbar in HERKUNFT.txt.
|
||||
#
|
||||
# bash scripts/fetch_testdata.sh
|
||||
#
|
||||
# ⚠️ Wikimedia drosselt hart (HTTP 429). Deshalb die Pause zwischen den
|
||||
# Abrufen; wer sie herausnimmt, steht fuer Minuten in der Strafbank.
|
||||
set -euo pipefail
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/testdata"
|
||||
API="https://de.wikipedia.org/w/api.php"
|
||||
ABSTAND=2
|
||||
|
||||
mkdir -p "$DIR"
|
||||
|
||||
# Artikelname → Dateiname. Die Arten sind so gewaehlt, dass der Detektor sie
|
||||
# sicher findet: gross im Bild, freigestellt, typische Haltung.
|
||||
ARTEN="Amsel:amsel Kohlmeise:kohlmeise Rotkehlchen:rotkehlchen"
|
||||
|
||||
: > "$DIR/HERKUNFT.txt"
|
||||
cat >> "$DIR/HERKUNFT.txt" <<'KOPF'
|
||||
Beispielfotos fuer Vogelalbum, geholt mit scripts/fetch_testdata.sh.
|
||||
|
||||
Diese Dateien gehoeren NICHT zum AGPL-lizenzierten Teil des Projekts. Jedes
|
||||
Bild behaelt die Lizenz, unter der es bei Wikimedia Commons steht. Wer eines
|
||||
davon weitergibt, nennt Urheber und Lizenz mit.
|
||||
|
||||
KOPF
|
||||
|
||||
for eintrag in $ARTEN; do
|
||||
artikel="${eintrag%%:*}"
|
||||
name="${eintrag##*:}"
|
||||
ziel="$DIR/$name.jpg"
|
||||
|
||||
if [[ -s "$ziel" ]]; then
|
||||
echo "vorhanden: $name.jpg"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "lade: $name.jpg ($artikel)"
|
||||
|
||||
# Erst das Lead-Bild des Artikels ermitteln, dann dessen Metadaten holen.
|
||||
# Beides in einem Aufruf — pageimages gibt den Dateinamen, imageinfo die
|
||||
# Lizenz und den Urheber.
|
||||
antwort=$(curl -sS --get "$API" \
|
||||
--data-urlencode "action=query" \
|
||||
--data-urlencode "format=json" \
|
||||
--data-urlencode "formatversion=2" \
|
||||
--data-urlencode "prop=pageimages" \
|
||||
--data-urlencode "piprop=thumbnail|name" \
|
||||
--data-urlencode "pithumbsize=1200" \
|
||||
--data-urlencode "titles=$artikel" \
|
||||
-H "User-Agent: vogelalbum/1.0 (https://vogelalbum.gaertner.onl)")
|
||||
|
||||
# Zwei Zeilen: Bildadresse und Dateiname.
|
||||
#
|
||||
# 🔑 Den Dateinamen NICHT aus der Adresse schneiden. Dort steht er
|
||||
# prozentkodiert ("Robin_%28950...%29.jpg"), und die API weist einen
|
||||
# Seitentitel mit "%28" als ungueltig ab — beim Rotkehlchen genau so
|
||||
# passiert, waehrend Amsel und Kohlmeise mangels Sonderzeichen
|
||||
# funktionierten. Das Feld "pageimage" liefert ihn unkodiert.
|
||||
gelesen=$(printf '%s' "$antwort" | python3 -c '
|
||||
import json, sys
|
||||
d = json.load(sys.stdin)
|
||||
seiten = d.get("query", {}).get("pages", [])
|
||||
s = seiten[0] if seiten else {}
|
||||
print(s.get("thumbnail", {}).get("source", ""))
|
||||
print(s.get("pageimage", ""))
|
||||
')
|
||||
url=$(printf '%s' "$gelesen" | sed -n 1p)
|
||||
datei=$(printf '%s' "$gelesen" | sed -n 2p)
|
||||
|
||||
if [[ -z "$url" || -z "$datei" ]]; then
|
||||
echo " FEHLER: kein Bild zu '$artikel' gefunden — uebersprungen" >&2
|
||||
continue
|
||||
fi
|
||||
curl -sSL --fail -o "$ziel" "$url" \
|
||||
-H "User-Agent: vogelalbum/1.0 (https://vogelalbum.gaertner.onl)"
|
||||
|
||||
sleep "$ABSTAND"
|
||||
|
||||
meta=$(curl -sS --get "$API" \
|
||||
--data-urlencode "action=query" \
|
||||
--data-urlencode "format=json" \
|
||||
--data-urlencode "formatversion=2" \
|
||||
--data-urlencode "prop=imageinfo" \
|
||||
--data-urlencode "iiprop=extmetadata|url" \
|
||||
--data-urlencode "titles=Datei:$datei" \
|
||||
-H "User-Agent: vogelalbum/1.0 (https://vogelalbum.gaertner.onl)")
|
||||
|
||||
printf '%s' "$meta" | python3 -c '
|
||||
import json, re, sys
|
||||
name = sys.argv[1]
|
||||
d = json.load(sys.stdin)
|
||||
seiten = d.get("query", {}).get("pages", [])
|
||||
info = (seiten[0].get("imageinfo") or [{}])[0] if seiten else {}
|
||||
m = info.get("extmetadata", {})
|
||||
|
||||
def feld(schluessel):
|
||||
wert = m.get(schluessel, {}).get("value", "")
|
||||
wert = re.sub(r"<[^>]+>", " ", wert)
|
||||
wert = re.sub(r"\s+", " ", wert).strip() # HTML aus der Angabe nehmen
|
||||
return wert or "unbekannt"
|
||||
|
||||
urheber = feld("Artist")
|
||||
lizenz = feld("LicenseShortName")
|
||||
print(name + ".jpg")
|
||||
print(" Urheber: " + urheber)
|
||||
print(" Lizenz: " + lizenz)
|
||||
print(" Quelle: " + str(info.get("descriptionurl", "unbekannt")))
|
||||
print()
|
||||
if "unbekannt" in (urheber, lizenz):
|
||||
# Namensnennung ist Lizenzbedingung. Ein stilles "unbekannt" waere der
|
||||
# Fehler, den diese Datei verhindern soll.
|
||||
sys.stderr.write(" WARNUNG: Urheber oder Lizenz fuer " + name
|
||||
+ " nicht ermittelt — bitte von Hand nachtragen\\n")
|
||||
sys.exit(3)
|
||||
' "$name" >> "$DIR/HERKUNFT.txt"
|
||||
|
||||
sleep "$ABSTAND"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Beispielfotos liegen in $DIR"
|
||||
echo "Urheber und Lizenz: $DIR/HERKUNFT.txt"
|
||||
Reference in new issue
Block a user