"""Génère une vidéo MP4 carrée à partir d'une trace GPX. Contenu de la vidéo : trace blanche sur fond noir, cercle blanc figurant le véhicule, compteur de vitesse analogique à aiguille et noms des villes traversées (géocodage inverse Nominatim / OpenStreetMap, mis en cache). Usage : python gpx_video.py trace.gpx [-o sortie.mp4] [--duree 60] [--fps 30] """ import argparse import json import math import ssl import subprocess import sys import time import urllib.parse import urllib.request import xml.etree.ElementTree as ET from datetime import datetime from pathlib import Path import numpy as np from PIL import Image, ImageDraw, ImageFont TAILLE = 1024 SS = 2 # facteur de suréchantillonnage pour l'anticrénelage MARGE = 60 RAYON_COMPTEUR = 150 RAYON_VEHICULE = 11 EPAISSEUR_TRACE = 3 PAS_VILLES_M = 1000 NOMINATIM_URL = "https://nominatim.openstreetmap.org/reverse" USER_AGENT = "gpx-video-compteur/1.0 (script personnel)" CLES_VILLE = ("city", "town", "village", "municipality", "hamlet") BLANC = (255, 255, 255) GRIS = (170, 170, 170) NOIR = (0, 0, 0) # -------------------------------------------------------------------------- # Lecture GPX et calculs # -------------------------------------------------------------------------- def _local(tag): return tag.rsplit("}", 1)[-1] def charger_gpx(chemin): """Retourne des tableaux numpy lat, lon, t (secondes), vitesse (km/h ou None).""" racine = ET.parse(chemin).getroot() lats, lons, temps, vitesses = [], [], [], [] for el in racine.iter(): if _local(el.tag) != "trkpt": continue t_txt, v_txt = None, None for enfant in el.iter(): nom = _local(enfant.tag) if nom == "time": t_txt = enfant.text elif nom == "speed": v_txt = enfant.text if t_txt is None: continue lats.append(float(el.get("lat"))) lons.append(float(el.get("lon"))) temps.append(datetime.fromisoformat(t_txt.strip().replace("Z", "+00:00")).timestamp()) vitesses.append(float(v_txt) * 3.6 if v_txt else math.nan) if len(lats) < 2: sys.exit("Erreur : la trace doit contenir au moins 2 points horodatés (