Onglet « Carte et corrections » : glisser une photo ou une vidéo, placer les médias sans position, masquer des zones de trace (pour un voyage, ou zone privée pour tous). Corrections notées dans voyage.toml ([positions], [corrections]) ou site.toml ([confidentialite]), appliquées par les pages ; les originaux ne changent pas. L'extraction garde les médias datés sans position pour qu'on puisse les placer. Accès : clé d'accès aléatoire à chaque lancement, session par cookie HttpOnly/SameSite=Strict, mot de passe facultatif (rtg mot-de-passe, empreinte scrypt hors du site, blocage après 5 échecs), accès réseau seulement avec mot de passe (--distant), HTTPS possible (--certificat, --cle), Content-Security-Policy et interdiction des cadres. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
108 lines
3.7 KiB
Python
108 lines
3.7 KiB
Python
"""Corrections faites à la main (interface, onglet « Carte ») et appliquées à
|
|
la génération des pages ; les fichiers d'origine ne sont jamais modifiés.
|
|
|
|
voyage.toml
|
|
[positions] média déplacé ou placé à la main
|
|
"IMG_4831.jpeg" = [48.85, 2.35]
|
|
[corrections]
|
|
masques = [[48.85, 2.35, 300]] zones (centre, rayon en m) retirées des traces
|
|
|
|
site.toml
|
|
[confidentialite]
|
|
zones = [[48.80, 2.10, 500]] zones retirées des traces de tous les voyages
|
|
(autour de chez soi, par exemple)
|
|
"""
|
|
import csv
|
|
import math
|
|
import os
|
|
|
|
import gpxpy.gpx
|
|
|
|
|
|
def masques(cfg):
|
|
"""[(lat, lon, rayon_m)] : zones du voyage et zones privées du site."""
|
|
res = []
|
|
for liste in (cfg.get("corrections", {}).get("masques", []),
|
|
cfg.get("confidentialite", {}).get("zones", [])):
|
|
for m in liste:
|
|
try:
|
|
res.append((float(m[0]), float(m[1]), float(m[2])))
|
|
except (TypeError, ValueError, IndexError):
|
|
pass
|
|
return res
|
|
|
|
|
|
def positions(cfg):
|
|
"""{nom de fichier: (lat, lon)} des médias placés à la main."""
|
|
res = {}
|
|
for nom, pos in cfg.get("positions", {}).items():
|
|
try:
|
|
res[nom] = (float(pos[0]), float(pos[1]))
|
|
except (TypeError, ValueError, IndexError):
|
|
pass
|
|
return res
|
|
|
|
|
|
def distance_m(lat1, lon1, lat2, lon2):
|
|
x = math.radians(lon2 - lon1) * math.cos(math.radians((lat1 + lat2) / 2))
|
|
y = math.radians(lat2 - lat1)
|
|
return 6371000 * math.hypot(x, y)
|
|
|
|
|
|
def dans_un_masque(lat, lon, zones):
|
|
return any(distance_m(lat, lon, m[0], m[1]) <= m[2] for m in zones)
|
|
|
|
|
|
def filtrer_piste(piste, zones):
|
|
"""Retire d'une piste GPX les points situés dans les zones ; un segment
|
|
traversé par une zone est coupé en deux. Renvoie le nombre de points retirés."""
|
|
if not zones:
|
|
return 0
|
|
retires = 0
|
|
nouveaux = []
|
|
for seg in piste.segments:
|
|
courant = gpxpy.gpx.GPXTrackSegment()
|
|
for p in seg.points:
|
|
if dans_un_masque(p.latitude, p.longitude, zones):
|
|
retires += 1
|
|
if courant.points:
|
|
nouveaux.append(courant)
|
|
courant = gpxpy.gpx.GPXTrackSegment()
|
|
else:
|
|
courant.points.append(p)
|
|
if courant.points:
|
|
nouveaux.append(courant)
|
|
piste.segments = [s for s in nouveaux if len(s.points) > 1]
|
|
return retires
|
|
|
|
|
|
def lire_medias(voyage, cfg):
|
|
"""Photos et vidéos des exports CSV, positions corrigées comprises.
|
|
|
|
Renvoie une liste de dicts {fichier, type, date, lat, lon, deplace} ; lat et
|
|
lon valent None pour un média sans position."""
|
|
corr = positions(cfg)
|
|
res = []
|
|
for nom_csv, type_ in (("export_photos.csv", "photo"), ("export_videos.csv", "video")):
|
|
chemin = os.path.join(voyage, nom_csv)
|
|
if not os.path.exists(chemin):
|
|
continue
|
|
vus = set()
|
|
with open(chemin, encoding="utf-8", errors="replace") as f:
|
|
for ligne in csv.reader(f):
|
|
if len(ligne) < 4 or ligne[0] in vus:
|
|
continue
|
|
vus.add(ligne[0])
|
|
try:
|
|
lat, lon = float(ligne[2]), float(ligne[3])
|
|
if lat == 0 and lon == 0:
|
|
lat = lon = None
|
|
except ValueError:
|
|
lat = lon = None
|
|
m = {"fichier": ligne[0], "type": type_, "date": ligne[1], "lat": lat, "lon": lon,
|
|
"deplace": ligne[0] in corr}
|
|
if m["deplace"]:
|
|
m["lat"], m["lon"] = corr[ligne[0]]
|
|
res.append(m)
|
|
return res
|