Files
christian c11fe9ab04 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.
2026-10-15 14:30:02 +00:00

150 lines
6.6 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "dex/base.html" %}
{% load dex_extras i18n %}
{% block title %}{{ species.name }} — Vogelalbum{% endblock %}
{% block content %}
{# Die Typfarbe faerbt die ganze Seite: Hero, Balken, Initiale, Diagramm. #}
<div style="{{ species.typ_vars }}">
<div class="blaettern">
{% if vorher %}<a href="{% url 'species' vorher.slug %}">← {{ vorher.dex_nr }} {{ vorher.name }}</a>{% else %}<span></span>{% endif %}
{% if nachher %}<a href="{% url 'species' nachher.slug %}">{{ nachher.dex_nr }} {{ nachher.name }} →</a>{% endif %}
</div>
<div class="species-hero">
<div class="hero-grid">
<div>
<div class="hero-photo">
{% if entry and entry.best_sighting and entry.best_sighting.image %}
<img src="{{ entry.best_sighting.image.url }}" alt="{{ species.name }}">
{% elif species.reference_image %}
<img src="{{ species.reference_image.url }}" alt="{{ species.german }}"
style="filter: saturate(.7) brightness(.72)">
{% endif %}
</div>
<div class="sub" style="margin-top:9px">
{% if entry and entry.best_sighting and entry.best_sighting.image %}
{% trans "Bestes eigenes Foto" %} · {{ entry.best_sighting.started_at|date:"SHORT_DATETIME_FORMAT" }}
{% elif species.reference_image %}
{% trans "Referenzfoto — diese Art war noch nicht vor unserer Kamera." %}
{% if species.reference_credit %}
{% trans "Foto" %}: <a href="{{ species.reference_source }}" rel="nofollow noopener" target="_blank"
style="text-decoration:underline">{{ species.reference_credit }}</a>{% if species.reference_license %}, {{ species.reference_license }}{% endif %}
{% endif %}
{% endif %}
</div>
</div>
<div>
<div class="dexnr">{{ species.dex_nr }}</div>
<h1>{{ species.name }}</h1>
{% if species.kategorie_lokal %}<div class="kategorie">{{ species.kategorie_lokal }}</div>{% endif %}
<div class="sci">{{ species.scientific }}{% if species.zweitname %} · {{ species.zweitname }}{% endif %}</div>
<div class="badge-row">
{% if species.typ %}<span class="pill">{{ species.typ_label }}</span>{% endif %}
<span class="pill rar" style="background:{{ species.rarity_color }}">{{ species.rarity_label }}</span>
{% if species.zug %}<span class="pill ghost">{{ species.zug_label }}</span>{% endif %}
<span class="pill ghost">{{ species.xp }} XP</span>
{% if entry %}<span class="pill ghost">{% blocktrans count n=entry.count %}{{ n }}× seen{% plural %}{{ n }}× seen{% endblocktrans %}</span>{% endif %}
</div>
{% if species.balken %}
<div class="werte">
{% for b in species.balken %}
<div class="wert">
<div class="k">{{ b.label }}</div>
<div class="track"><div class="fill" style="width:{{ b.pct }}%"></div></div>
<div class="v">{{ b.wert }}</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
{% if species.absaetze %}
<h2>{% trans "Steckbrief" %}</h2>
<div class="prose">
{% for p in species.absaetze %}<p>{{ p }}</p>{% endfor %}
</div>
{% elif species.fact %}
<h2>{% trans "Steckbrief" %}</h2>
<div class="prose"><p>{{ species.fact }}</p></div>
{% endif %}
<div class="fakten">
{% if species.lebensraum_lokal %}
<div class="fakt"><div class="k">{% trans "Lebensraum" %}</div><div class="v">{{ species.lebensraum_lokal }}</div></div>
{% endif %}
{% if species.nahrung_lokal %}
<div class="fakt"><div class="k">{% trans "Nahrung" %}</div><div class="v">{{ species.nahrung_lokal }}</div></div>
{% endif %}
{% if species.stimme_lokal %}
<div class="fakt"><div class="k">{% trans "Stimme" %}</div><div class="v">{{ species.stimme_lokal }}</div></div>
{% endif %}
{% if species.zug %}
<div class="fakt"><div class="k">{% trans "Zugverhalten" %}</div><div class="v">{{ species.zug_label }}</div></div>
{% endif %}
</div>
{% if aehnliche %}
<h2>{% trans "Verwechslungsgefahr" %} <span class="count">{% trans "Arten, die man nicht auf Anhieb unterscheidet" %}</span></h2>
<div class="verwechslung">
{% for a in aehnliche %}
<a class="vw" href="{% url 'species' a.slug %}">
{% if a.reference_image %}<img src="{{ a.reference_image.url }}" alt="" loading="lazy">{% endif %}
<span class="n">{{ a.name }}</span>
</a>
{% endfor %}
</div>
{% endif %}
{% if entry %}
<h2>{% blocktrans with n=species.name %}Wann ist {{ n }} da?{% endblocktrans %} <span class="count">{% trans "Besuche über den Tag" %}</span></h2>
<div class="hours">
{% for h in hours %}<div style="height:{{ h.pct }}%" title="{{ h.h }} Uhr: {{ h.n }}"></div>{% endfor %}
</div>
<div class="hours-axis"><span>{% trans "0 Uhr" %}</span><span>6</span><span>12</span><span>18</span><span>23</span></div>
{% endif %}
{% if sightings %}
<h2>{% trans "Alle Besuche" %} <span class="count">{% blocktrans with n=entry.count %}{{ n }} insgesamt{% endblocktrans %}</span></h2>
<div class="rows">
{% for s in sightings %}
<div class="row">
{% if s.kachel_url %}
<img class="shot" src="{{ s.kachel_url }}" alt="" loading="lazy"
{% if s.image %}data-gross="{{ s.image.url }}"
data-beschriftung="{{ species.name }} · {{ s.started_at|date:"SHORT_DATETIME_FORMAT" }}{% if s.camera %} · {{ s.camera.name }}{% endif %}"
style="cursor:zoom-in"{% endif %}>
{% else %}<div class="shot"></div>{% endif %}
<div>
<div class="who">
{{ s.started_at|date:"DATETIME_FORMAT" }}
{% if s.is_first_of_species %}<span class="tag-new">{% trans "Erstfang" %}</span>{% endif %}
</div>
<div class="when">
{{ s.duration_s|floatformat:0 }}s · {% blocktrans with a=s.frames_with_bird b=s.frames_total %}{{ a }} von {{ b }} Bildern mit Vogel{% endblocktrans %}
{% if s.camera %} · {{ s.camera.name }}{% endif %}
</div>
</div>
<div class="right">
<div class="conf">{{ s.confidence|prozent }}%</div>
{% if s.xp_awarded %}<div class="xp">+{{ s.xp_awarded }} XP</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
{% elif not entry %}
<div class="empty" style="margin-top:24px">
<strong style="color:var(--text); font-size:17px">{% trans "Noch nicht gesammelt." %}</strong><br>
{% blocktrans with n=species.name %}Sobald {{ n }} vor der Kamera landet, wird dieser Eintrag freigeschaltet — mit eurem eigenen Foto.{% endblocktrans %}
</div>
{% endif %}
</div>
{% endblock %}