Sprint 12+13: Tagebuch Day-One-Redesign, Notiz-Feature, Icon-Fixes, SW by-v405

Tagebuch:
- Day-One-Listenansicht: Wochentag + Tageszahl + Meta-Zeile (Zeit/Ort/Wetter)
- 4 Ansichten: Liste, Medien-Mosaik, Kalender (mit Sprungbuttons), Karte (GPS-Marker)
- Detail-Ansicht inline im Content-Bereich (kein Fullscreen-Overlay mehr)
- Hero-Bild vollständig sichtbar (object-fit:contain), Lightbox mit Safe-Area
- 2-Spalten-Layout Desktop: Text + Leaflet-Karte + POI-Liste
- EXIF-GPS-Extraktion bei Foto-Upload, historisches Wetter via Archive-API
- NoteStation-Import: Fotos in diary_media (80 Einträge migriert, 94 Medien)
- Stats-Endpoints: /diary/stats, /diary/calendar, /diary/locations

Notiz-Feature:
- Generische notes-Tabelle (parent_type + parent_id + meta_json)
- 📝-Button in 8 Bereichen, Notizblock-Seite mit KI-Analyse
- KI-Toggle in Einstellungen, notes_ki_enabled in User-Profil

Icons & Design:
- fill:currentColor Fix für welcome/onboarding/friends.js
- --c-icon Variable, --c-text-muted Dark Mode aufgehellt
- 15+ neue Phosphor-Icons aus lokaler Kopie
- CSS Network-First im SW, Cache-Control-Middleware

Infrastruktur:
- Wiki-Anreicherungs-Scheduler-Jobs entfernt (abgeschlossen)
- auth.py: notes_ki_enabled + is_social_media im User-Response
This commit is contained in:
rene 2026-04-25 20:44:46 +02:00
parent 95f91fdc00
commit 553e9e7854
35 changed files with 4558 additions and 370 deletions

View file

@ -117,6 +117,36 @@ def to_mp4_if_needed(data: bytes, filename: str) -> Tuple[bytes, str]:
pass
def extract_gps_from_exif(data: bytes) -> tuple | None:
"""EXIF-GPS aus Bilddaten lesen. Gibt (lat, lon) zurück oder None."""
try:
from PIL import Image
img = Image.open(io.BytesIO(data))
exif = img._getexif()
if not exif:
return None
gps = exif.get(34853) # GPSInfo tag
if not gps:
return None
lat_dms = gps.get(2)
lon_dms = gps.get(4)
lat_ref = gps.get(1, 'N')
lon_ref = gps.get(3, 'E')
if not lat_dms or not lon_dms:
return None
def dms(v):
return float(v[0]) + float(v[1]) / 60 + float(v[2]) / 3600
lat = dms(lat_dms) * (-1 if lat_ref == 'S' else 1)
lon = dms(lon_dms) * (-1 if lon_ref == 'W' else 1)
if not (-90 <= lat <= 90 and -180 <= lon <= 180):
return None
return round(lat, 6), round(lon, 6)
except Exception:
return None
def convert_media(data: bytes, filename: str) -> Tuple[bytes, str]:
"""Convert HEIC→JPEG and MOV/AVI/M4V→MP4; pass everything else through."""
ext = os.path.splitext(filename or "")[1].lower()