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

+27 -5
View File
@@ -13,6 +13,7 @@ import subprocess
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
from rtg import corrections
from rtg.outils import dire, exiger
FMT_PHOTOS = '"$FileName",$DateTimeOriginal,$GPSLatitude,$GPSLongitude,$GPSAltitude'
@@ -56,14 +57,29 @@ def extraire_photos(voyage, cfg, analyse):
if pos:
lignes.append(f"{nom},{date},{pos[0]},{pos[1]},")
deduites += 1
# Photos datées restées sans position : lignes sans coordonnées, ignorées
# par les pages mais listées par l'interface pour être placées à la main.
vus = {l.split(",")[0] for l in lignes}
sans_position = [l.split(",", 1) for l in _exiftool(commun + ["-p", FMT_PHOTOS_DATE, dossier])
if l.split(",")[0] not in vus]
lignes += [f"{nom},{date},,," for nom, date in sans_position]
with open(csv, "w", encoding="utf-8") as f:
f.write("".join(l + "\n" for l in lignes))
msg = f"📸 Photos : {len(lignes)} ligne(s)"
if len(lignes) > len(p1) + deduites:
msg += f" (dont {len(lignes) - len(p1) - deduites} sans altitude)"
localisees = len(lignes) - len(sans_position)
msg = f"📸 Photos : {localisees} localisée(s)"
if localisees > len(p1) + deduites:
msg += f" (dont {localisees - len(p1) - deduites} sans altitude)"
if deduites:
msg += f", {deduites} position(s) déduite(s) de la trace"
if sans_position:
placees = corrections.positions(cfg)
a_la_main = sum(1 for nom, _ in sans_position if nom.strip('"') in placees)
reste = len(sans_position) - a_la_main
if a_la_main:
msg += f", {a_la_main} placée(s) à la main"
if reste:
msg += f", {reste} sans position (à placer dans l'interface)"
dire(msg)
@@ -141,6 +157,8 @@ def _premier_point(video):
date = dt or cd
if premier and date:
return date, premier[0], premier[1]
if date:
return date, None, None # datée mais sans GPS : à placer à la main
return None
@@ -169,15 +187,19 @@ def extraire_videos(voyage, analyse):
reprises += 1
continue
r = _premier_point(v)
if r:
if r and r[1] is not None:
lignes.append(f'"{nom}",{r[0]},{r[1]},{r[2]},,')
etat = "✓"
else:
sans += 1
etat = "— pas de GPS"
if r:
lignes.append(f'"{nom}",{r[0]},,,,')
dire(f" [{i:3d}/{len(analyse.videos):3d}] {nom:<28} {etat}")
if reprises:
dire(f" {reprises} rush(s) inchangé(s), positions reprises du dernier export")
with open(csv, "w", encoding="utf-8") as f:
f.write("".join(l + "\n" for l in lignes))
dire(f"🎬 Vidéos : {len(lignes)} rush(s) localisé(s)" + (f", {sans} sans GPS" if sans else ""))
sans_pos = sum(1 for l in lignes if l.split(",")[2:4] == ["", ""])
dire(f"🎬 Vidéos : {len(lignes) - sans_pos} rush(s) localisé(s)"
+ (f", {sans_pos} sans GPS (à placer dans l'interface)" if sans_pos else ""))