Interface : corrections à la souris et accès protégé

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
This commit is contained in:
FabriceandClaude Opus 5.5 committed 2026-10-09 14:35:43 +00:00
1 parent 06153a227e
commit 71779f6f53
17 files changed
+1047 -38

No files matched your search

+23 -8
View File
@@ -35,6 +35,8 @@ from folium import plugins
from folium.plugins import MarkerCluster
from jinja2 import Environment, FileSystemLoader
from rtg import corrections
DOSSIER_TEMPLATES = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
# Fixés par generer() : racine du site et adresse publique.
CONTEXTE = {"racine": None, "site_url": "https://roadtrip.ouaibe.fr/"}
@@ -101,13 +103,18 @@ def bornes(points, marge=0.02, robuste=True):
len(points) - len(gardes))
def lire_csv(chemin):
"""CSV d'extraction → DataFrame trié, avec colonnes DT (datetime) et Jour."""
def lire_csv(chemin, positions=None):
"""CSV d'extraction → DataFrame trié, avec colonnes DT (datetime) et Jour.
`positions` : {fichier: (lat, lon)} placés à la main, qui l'emportent."""
vide = pd.DataFrame(columns=COLONNES + ['DT', 'Jour'])
# Un voyage sans photo produit un CSV de 0 octet, que pandas refuse de lire.
if not os.path.exists(chemin) or os.path.getsize(chemin) == 0:
return vide
df = pd.read_csv(chemin, names=COLONNES, header=None)
for nom, (lat, lon) in (positions or {}).items():
lignes = df['Fichier'].astype(str).str.strip() == nom
df.loc[lignes, 'Latitude'] = lat
df.loc[lignes, 'Longitude'] = lon
df = df[df['Latitude'].notnull() & df['Longitude'].notnull()]
df = df[(df['Latitude'] != 0) & (df['Longitude'] != 0)].copy()
if df.empty:
@@ -312,12 +319,16 @@ def exporter_trace_globale(base_dir, pistes):
class Voyage:
"""Données communes à toutes les pages d'un voyage."""
def __init__(self, base_dir):
def __init__(self, base_dir, cfg=None):
self.base_dir = base_dir
self.nom = os.path.basename(base_dir.rstrip('/')) or "voyage"
self.routes_dir = os.path.join(base_dir, "routes")
self.df_v = lire_csv(os.path.join(base_dir, "export_videos.csv"))
self.df_p = lire_csv(os.path.join(base_dir, "export_photos.csv"))
cfg = cfg or {}
self.zones = corrections.masques(cfg)
pos = corrections.positions(cfg)
self.df_v = lire_csv(os.path.join(base_dir, "export_videos.csv"), pos)
self.df_p = lire_csv(os.path.join(base_dir, "export_photos.csv"), pos)
self.points_masques = 0
jours_gpx = set()
for f in fichiers_gpx(self.routes_dir):
m = re.search(r'(\d{4}-\d{2}-\d{2})', f)
@@ -343,6 +354,7 @@ class Voyage:
continue
self.gpx_du_jour[jour] = gpx_file
for track in gpx.tracks:
self.points_masques += corrections.filtrer_piste(track, self.zones)
pts = [[p.latitude, p.longitude] for seg in track.segments for p in seg.points]
if pts:
if not track.name:
@@ -396,19 +408,22 @@ def carte_du_jour(v, jour, version, journal, pre, lien_accueil, bouton_telecharg
return m_day, media_coords
def generer(base_dir, racine=None, site_url=None):
"""Pages d'un voyage. `racine` : racine du site (bouton « Retour »)."""
def generer(base_dir, racine=None, site_url=None, cfg=None):
"""Pages d'un voyage. `racine` : racine du site (bouton « Retour ») ;
`cfg` : réglages du voyage (corrections faites dans l'interface)."""
CONTEXTE["racine"] = racine
if site_url:
CONTEXTE["site_url"] = site_url.rstrip('/') + '/'
base_dir = os.path.abspath(base_dir)
html_dir = os.path.join(base_dir, "html")
os.makedirs(html_dir, exist_ok=True)
v = Voyage(base_dir)
v = Voyage(base_dir, cfg)
if not v.jours:
print(f"❌ {v.nom} : aucune date trouvée (ni GPX daté, ni média géolocalisé).")
return False
v.lire_traces()
if v.points_masques:
print(f" 🙈 {v.points_masques} point(s) de trace retiré(s) par les zones masquées")
version = installer_assets(base_dir)
journal = Journal(base_dir, v.jours)