From 680dbb78f541e6ec5a893651b687932d9a85fd7b Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 17 Sep 2026 10:21:28 +0200 Subject: [PATCH] version initiale --- .gitignore | 4 + README.md | 63 ++++++++ gpx_video.py | 395 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 + 4 files changed, 464 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 gpx_video.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d047da --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.mp4 +*.gpx +*.pyc +*.villes.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc3032e --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# GPX - Compteur de vitesse + +Script Python qui transforme une trace GPX en vidéo MP4 carrée 1024×1024, prête à être incrustée dans un autre montage : trace blanche sur fond noir, cercle blanc qui suit le trajet, compteur de vitesse à aiguille et noms des villes traversées. + +## Table des matières + +- [Fonctionnalités](#fonctionnalités) +- [Installation](#installation) +- [Utilisation](#utilisation) +- [Structure du projet](#structure-du-projet) +- [Développement](#développement) + +## Fonctionnalités + +- Lecture GPX 1.0 / 1.1 (points `trkpt` horodatés). +- Vitesse issue de l'extension `gpxtpx:speed` (m/s, ex. GeoRide) si présente, sinon calculée à partir des distances et des temps ; lissage léger. +- Vidéo accélérée à durée fixe (60 s par défaut) : le trajet complet est parcouru du début à la fin. +- Compteur analogique 0-200 km/h (paramétrable), aiguille et vitesse numérique, placé automatiquement dans le coin le moins encombré par la trace. +- Villes traversées obtenues par géocodage inverse [Nominatim](https://nominatim.org/) (OpenStreetMap), affichées sur la carte dès le début, avec placement des étiquettes qui évite chevauchements et trace. +- Cache des villes dans `.villes.json` : les exécutions suivantes n'accèdent plus au réseau. +- Rendu anticrénelé (suréchantillonnage ×2), encodage H.264 `yuv420p` via ffmpeg, sans fichiers intermédiaires. + +## Installation + +Prérequis : Python 3.10+ et [ffmpeg](https://ffmpeg.org/) accessible dans le `PATH`. + +```powershell +python -m venv .venv +.venv\Scripts\Activate.ps1 +pip install -r requirements.txt +``` + +## Utilisation + +```powershell +python gpx_video.py Trajet_16-09-2026_15h38.gpx +``` + +Produit `Trajet_16-09-2026_15h38.mp4` à côté du GPX. + +| Option | Défaut | Rôle | +|---|---|---| +| `-o`, `--sortie` | nom du GPX en `.mp4` | fichier de sortie | +| `--duree` | `60` | durée de la vidéo (secondes) | +| `--fps` | `30` | images par seconde | +| `--vmax` | `200` | graduation max du compteur (km/h) | +| `--sans-villes` | — | ne pas géocoder ni afficher les villes | + +Exemple : `python gpx_video.py trace.gpx -o balade.mp4 --duree 90 --fps 25` + +Notes : +- Le premier lancement interroge Nominatim (1 requête/s, un point par km de trajet) : compter environ 1 s par km. +- En cas d'erreurs réseau, la vidéo est générée sans villes et le cache n'est pas écrit. Supprimer `.villes.json` pour forcer un nouveau géocodage. + +## Structure du projet + +``` +gpx_video.py script principal (lecture GPX, villes, rendu, encodage) +requirements.txt dépendances Python (numpy, Pillow) +Trajet_16-09-2026_15h38.gpx trace d'exemple (GeoRide) +Trajet_16-09-2026_15h38.villes.json cache des villes de l'exemple +Trajet_16-09-2026_15h38.mp4 vidéo générée pour l'exemple +``` diff --git a/gpx_video.py b/gpx_video.py new file mode 100644 index 0000000..7f7fb98 --- /dev/null +++ b/gpx_video.py @@ -0,0 +1,395 @@ +"""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 (