diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64777da --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +.DS_Store diff --git a/README.md b/README.md index f540da9..28b584e 100644 --- a/README.md +++ b/README.md @@ -1,145 +1,140 @@ -Ce repo *RoadTripsGenerator* est un ensemble de scripts (bash et python) permettant **de** générer automatiquement des sites web complets avec cartes interactives à partir de vos photos, vidéos, trace GPS et récits de voyages. +*RoadTripsGenerator* est un ensemble de scripts (bash et Python) qui génèrent un site web statique de road-trips : une carte interactive par voyage et par jour, avec les photos, les vidéos, les traces GPS et le récit du voyage. -C'est sans prétention et il faudrait probablement faire des développements bien plus sérieux pour obtenir un logiciel "pousse boutons" agréable à utiliser, mais si cela peut donner des idées... +C'est sans prétention et il faudrait probablement des développements bien plus sérieux pour obtenir un logiciel « presse-bouton », mais si cela peut donner des idées… -Je me suis en effet rendu compte que je passais des heures à faire des montages vidéo de mes road trips pour faire des films que finalement je ne regardais pas moi-**même** : trop longs **mais** pas assez d'infos, pas interactif, etc. - -Alors je me suis demandé sous quelle forme il faudrait mettre en forme mes road trips, pour pouvoir me les remémorer en famille et les faire partager à d'autres. - -C'est comme **cela** que ce petit projet a vu le jour. +Je me suis rendu compte que je passais des heures à monter des films de mes road-trips que finalement je ne regardais pas moi-même : trop longs mais pas assez d'infos, pas interactifs, etc. Je me suis donc demandé sous quelle forme présenter mes voyages pour pouvoir me les remémorer en famille et les partager. C'est comme cela que ce petit projet a vu le jour. ## Le principe -Le principe est très simple : je possède des photos et vidéos avec informations GPS de position et de temps (dans mon cas : photos iPhone et films GOPRO), des traces ou parcours GPS quotidiens et un récit écrit en Markdown. +Pour chaque voyage, je dispose de photos et de vidéos géolocalisées (photos iPhone, films GoPro), des traces GPS de chaque journée et d'un récit écrit en Markdown. -Les scripts vont alors générer automatiquement **une** page html avec une carte interactive **permettant** de visualiser les photos, les vidéos **et** les traces GPS du road trip. Voir par exemple mon [road trip de l'été 2025](https://roadtrip.ouaibe.fr/Ete-2025/). +Les scripts lisent les positions GPS des photos et des vidéos, les placent sur une carte avec la trace du parcours découpée par jour, et construisent des pages web où l'on peut, en parallèle, lire le récit du voyage. Voir par exemple mon [road-trip de l'été 2025](https://roadtrip.ouaibe.fr/Ete-2025/). -Les scripts récupèrent les coordonnées GPS des photos, **vidéos** et les placent sur une carte avec la trace du parcours, assurant un découpage quotidien pour marquer les étapes**,** et construisent des pages web interactives **où** l'on peut, en parallèle, consulter le récit du voyage. +Ils préparent aussi les médias pour le web : -Les scripts s'occupent également éventuellement de compresser les films pour alourdir le moins possible le chargement des pages. Actuellement la compression produit **du** 1080p en 60 images par seconde de haute qualité (10Mbps) pour une exécution locale optimale mais peut être trop lourde pour **une** diffusion sur le web. Les vidéos ainsi compressées occupent environ 25% de leur place initiale. +* **vidéos** : un 720p H.264 lisible partout, chargé par défaut, et un 1080p HEVC proposé à la demande (environ 40 % plus léger que du H.264 à qualité égale) ; +* **photos** : une version allégée (1600 px, quelques centaines de Ko) affichée dans les diaporamas, le clic ouvrant l'original ; +* **aperçus** : une image tirée de chaque vidéo, affichée avant la lecture. -Les sites produits étant statiques, une fois générés ils sont facilement **transportables** par simple copie. +Les originaux ne sont jamais modifiés. Le site produit est statique : une simple copie suffit pour le publier ou le transporter. ## Les prérequis -Normalement les scripts doivent fonctionner très facilement sous OSX et Linux, probablement facilement dans un Windows PowerShell ou dans un environnement Cygwin, mais je n'ai pas testé. - -Il faudra installer toutefois : +Les scripts fonctionnent sous macOS et Linux. * obligatoirement : -* python3 et ses packages pandas, folium, **gpxpy**, markdown2 et **numpy** -* exiftool - - -* optionnellement -* ffmpeg (pour la compression des MP4) - - - -## Préparation des données - -Je crée un **répertoire** pour l'ensemble de mes roadtrips à l'**intérieur** duquel je place le sous-**répertoire** `Scripts` de ce dépôt. - -Puis, pour chaque road-trip je crée un sous-**répertoire**, par exemple Ete-2025, Ete-2024, etc. - -Dans le sous-**répertoire** de chaque road-trip, je crée : - -* un sous-**répertoire** `photos` avec mes photos -* un sous-**répertoire** `rushs` avec mes vidéos -* un sous-**répertoire** `routes` avec mes traces GPS **quotidiennes** dans des fichiers au format gpx que je nomme AAAA-MM-JJ.gpx (par exemple 2025-08_02.gpx) -* un fichier `voyage.md` avec le récit du voyage au format markdown - -Sur mon disque, je dois voir une structure qui ressemble à : + * Python 3 et les paquets `pandas folium gpxpy markdown2 numpy` (Jinja2, utilisé pour la mise en page, est installé avec folium) + * `exiftool` +* conseillé : + * `ffmpeg` (compression des vidéos et images d'aperçu) + * `pillow` pour les photos allégées (sous macOS, l'outil `sips` du système prend le relais s'il est absent) ``` -MesVoyages -|-Scripts -|-Ete-2025 - |-photos - |-rushs - |-routes - voyage.md -|-Ete-2024 -... +brew install exiftool ffmpeg +pip3 install pandas folium gpxpy markdown2 numpy pillow +``` +## Organisation des dossiers + +Le dépôt et le site sont deux dossiers voisins. Un voyage est un dossier qui contient au moins un des sous-dossiers `photos`, `rushs` ou `routes`. Les voyages rangés à la racine du site forment la rubrique « Voyages » de la page d'accueil. Ceux rangés un niveau plus bas, dans un dossier de regroupement (ici `Balades`), forment une rubrique du nom de ce dossier. L'accueil montre les 2 plus récents de chaque rubrique (`ACCUEIL_NB=3` pour en montrer plus), avec un lien vers la liste complète. + +``` +CLEROADTRIPS +|-RoadTripsGenerator ce dépôt +| |-Scripts +| |-templates mise en page (HTML, CSS, JavaScript) +|-Road-Trips le site + |-Ete-2025 + | |-photos photos (JPEG) + | |-rushs vidéos d'origine (peut être un lien vers un disque externe) + | |-routes une trace GPX par jour, date dans le nom : 2025-08-02.gpx + | |-voyage.md le récit + |-Balades + |-Aisne-06-2026 + |-... +``` + +Dans le récit `voyage.md`, deux raccourcis évitent les liens écrits en dur (qui ne marcheraient pas à la fois en local et en ligne) : + +``` +[Carte du jour](jour:2025-08-02) +[Le col](media:photos/IMG_4831.jpeg) +[La descente](media:videos/GH011272.mp4) ``` ## Mode d'emploi -Une fois les données préparées, tout est simple, mais un site peut être long à générer à cause de l'étape de compression. +### Tout le site, ou quelques voyages -### 1 road trip (3 ou 4 commandes selon que l'on compresse ou non les vidéos) - -Par exemple [https://roadtrip.ouaibe.fr/Ete-2025](https://roadtrip.ouaibe.fr/Ete-2025) - -On se place dans le **répertoire** du road-trip que l'on veut générer (celui qui contient les **répertoires** photos, rushs, etc.) et on lance : +Depuis le dossier du site : ``` -../Scripts/extraction_infos.sh - +cd Road-Trips +../RoadTripsGenerator/Scripts/genere_site.sh # tous les voyages +../RoadTripsGenerator/Scripts/genere_site.sh Balades/Vexin-10-2026 # un seul +SKIP_COMPRESS=1 ../RoadTripsGenerator/Scripts/genere_site.sh # sans réencoder les vidéos ``` -Cela produit deux fichiers : `export_photos.csv` et `export_videos.csv` qui contiennent les extractions d'infos GPS des photos et **vidéos**. +Pour chaque voyage, `genere_site.sh` enchaîne les étapes ci-dessous, puis régénère la page d'accueil et la carte globale. Un voyage d'une seule journée (une balade, souvent) n'a pas de carte générale intermédiaire : sa page est directement la carte du jour, avec photos et vidéos. Chaque étape ne refait que ce qui a changé : relancer le script est rapide, sauf quand de nouveaux rushs sont à encoder. -Vient ensuite la compression optionnelle des **vidéos** qui peut être assez longue mais que je conseille vivement, même pour une utilisation locale. +### Étape par étape + +Depuis le dossier d'un voyage (ici `Road-Trips/Balades/Vexin-10-2026`, d'où les `../../../`) : + +| Commande | Rôle | Produit | +|---|---|---| +| `../../../RoadTripsGenerator/Scripts/extraction_infos.sh` | positions GPS des photos et vidéos | `export_photos.csv`, `export_videos.csv` | +| `../../../RoadTripsGenerator/Scripts/compress.sh` | vidéos web (long) | `videos/` | +| `../../../RoadTripsGenerator/Scripts/prepare_web.py` | photos allégées, aperçus | `photos_web/`, `apercus/` | +| `../../../RoadTripsGenerator/Scripts/genere_carte.py` | pages du voyage | `index.html`, `mini.html`, `html/`, `assets/`, `.kml` | + +Puis, depuis le dossier du site, la page d'accueil : ``` -../Scripts/compress.sh - +../RoadTripsGenerator/Scripts/genere_index_general.py ``` -Les **vidéos** compressées se trouveront alors dans le répertoire `videos` au **même** niveau que le **répertoire** `rushs`. +Sans compression, il faut faire de `videos` un lien vers `rushs` : `ln -s rushs videos`. -Si votre choix est de ne pas compresser, alors il faut faire un lien symbolique `videos` sur le sous-**répertoire** `rushs` : +### Réglages utiles -``` -ln -s rushs videos +* `compress.sh` : `ENCODER=vt` (encodeur matériel du Mac, bien plus rapide, un peu moins bon à taille égale), `JOBS=N`, `CRF_HEVC`, `CRF_H264`, `FORCE=1` pour tout réencoder. Une vidéo déjà encodée mais illisible (encodage interrompu) est refaite automatiquement. +* `prepare_web.py` : `WEB_PX=1600` (taille des photos), `WEB_QUALITE=80`, `FORCE=1`. +* `bench_crf.sh` : mesure la qualité (VMAF ou SSIM) d'un extrait de rush à plusieurs CRF, pour choisir `CRF_HEVC` / `CRF_H264` sur ses propres images. -``` +### Carte globale de l'accueil -Vient alors la génération de la carte : +Sous les rubriques, l'accueil affiche une grande carte (`carte_globale.html`, fond OpenStreetMap, sans Google) qui réunit le contenu récupéré de nos cartes Google My Maps et les voyages du site (rubrique « Voyages »), un calque et une couleur par voyage ; un clic sur une trace mène au voyage. Un fichier `moto.txt` (une ligne) dans un voyage ajoute la moto à son nom dans la légende, par exemple « Ete 2026 (BMW R 1300 GS Adventure) ». -``` -../Scripts/genere_carte.py +On peut y ajouter des cartes Google My Maps : mettre leurs liens, un par ligne, dans `mymaps.txt` à la racine du site (les cartes doivent être partagées en « Tous les utilisateurs disposant du lien »). Le KML de chaque carte est récupéré une seule fois dans `_mymaps/` ; ensuite la carte ne dépend plus de Google (`MYMAPS_MAJ=1` pour le reprendre après avoir modifié la carte Google). On peut aussi déposer à la main des exports `.kml` ou `.kmz` dans `_mymaps/`. Chaque calque My Maps devient un calque de la carte, avec ses couleurs d'origine. +Chaque voyage produit aussi un `.kml` (tout le voyage en une seule trace, avec sa distance et un lien vers sa page, sous les 5 Mo acceptés par My Maps), pour qui voudrait l'importer dans une carte My Maps. Régler l'adresse publique du site avec `SITE_URL` (par défaut `https://roadtrip.ouaibe.fr/`) pour des liens corrects. -``` +### Modifier l'apparence -### **Générer** un index de road-trips +Toute la mise en page est dans `Scripts/templates/` : -Par exemple [https://roadtrip.ouaibe.fr](https://roadtrip.ouaibe.fr) +* `carte.css`, `carte.js` : style et comportement des pages de voyage (copiés dans `assets/` de chaque voyage à la génération) ; +* `fragments.html` : les morceaux de HTML (popups photo et vidéo, journal, boutons, barre des jours) ; +* `fragments_globale.html` : les popups et la légende de la carte globale ; +* `base_accueil.html`, `accueil.html`, `liste.html` : la page d'accueil (rubriques Voyages et Balades, les 2 plus récents de chacune) et les listes complètes `voyages.html`, `balades.html`. -On se place dans le **répertoire** dont les sous-**répertoires** sont les road-trips et on lance : +Après une modification, `SKIP_COMPRESS=1 ../RoadTripsGenerator/Scripts/genere_site.sh` régénère tout en quelques dizaines de secondes. -``` -./Scripts/genere_index_general.py +## Publier -``` +Il suffit de copier le dossier du site. Les sous-dossiers `rushs` (originaux, très lourds) et les fichiers `export_*.csv` sont inutiles en ligne ; `photos` reste nécessaire pour l'ouverture des originaux. -### Tout automatique - -En fait si on a bien respecté l'arborescence des **répertoires**, on peut automatiser la **génération** de tous les road-trips présents en lançant : - -``` -./Scripts/genere_site.sh - -``` - -Il conviendra de désactiver dans le script le lancement du script "compress.sh" si on ne désire pas compresser (attention par contre à bien mettre un lien dynamique sur les répertoires `rushs`, comme expliqué plus haut). - -## Contribuer : +## Contribuer ### Pour pouvoir faire des push : ``` -git remote set-url origin https://n@git.ouaibe.fr/Korrigan/RoadTripsGenerator.git - +git remote set-url origin https://@git.ouaibe.fr/Korrigan/RoadTripsGenerator.git ``` -Pour **éviter** de re-taper le mot de passe pendant une heure pour les -autres push : +Pour éviter de retaper le mot de passe pendant une heure pour les autres push : ``` git config --global credential.helper 'cache --timeout=3600' - -``` \ No newline at end of file +``` diff --git a/Scripts/bench_crf.sh b/Scripts/bench_crf.sh index 33f4dfb..2546c58 100755 --- a/Scripts/bench_crf.sh +++ b/Scripts/bench_crf.sh @@ -4,7 +4,7 @@ # # Encode un même extrait de rush à plusieurs CRF et mesure la qualité # objective (VMAF si dispo, sinon SSIM) + la taille obtenue. -# Objectif : choisir CRF_HD / CRF_SD sur TES images plutôt qu'au doigt mouillé. +# Objectif : choisir CRF_HEVC / CRF_H264 de compress.sh sur TES images. # # Usage : # ./bench_crf.sh rushs/GH011182.MP4 [start_sec] [duree_sec] @@ -95,7 +95,6 @@ echo "🎯 Palier testé : ${HEIGHT}p en $(echo "$CODEC" | tr a-z A-Z)" printf "%-6s %10s %12s %10s %10s\n" "CRF" "Taille" "Débit" "$(echo "$METRIC" | tr a-z A-Z)" "Encodage" printf "%s\n" "--------------------------------------------------------" -BEST_LINE="" for crf in $CRFS; do OUT="$WORK/test_crf${crf}.mp4" diff --git a/Scripts/compress.sh b/Scripts/compress.sh index 047984f..70ac390 100755 --- a/Scripts/compress.sh +++ b/Scripts/compress.sh @@ -1,51 +1,37 @@ #!/bin/bash # ============================================================================= -# COMPRESSION ROAD-TRIP — STRATÉGIE HYBRIDE + ENCODAGE PARALLÈLE -# Source : GoPro Hero 9, rushs en HEVC 1080p +# COMPRESSION DES RUSHS POUR LE WEB # -# Sorties (noms de fichiers INCHANGÉS par rapport à l'ancien script) : -# .mp4 1080p natif, HEVC/H.265, tag hvc1 -# → gros fichier, là où les 40 % d'économie comptent -# _mobile.mp4 720p, H.264, tag avc1, profil High -# → fichier chargé par défaut, se lit sur TOUS -# les navigateurs sans exception +# Pour chaque rush de /rushs, deux fichiers dans /videos : +# .mp4 1080p HEVC (tag hvc1) — qualité, ~40 % plus léger que H.264 +# _mobile.mp4 720p H.264 (tag avc1) — chargé par défaut, lisible partout # -# PARALLÉLISME -# Deux niveaux, tous deux actifs par défaut : -# 1. Les deux paliers d'un même rush sont indépendants → lancés ensemble. -# 2. JOBS rushs traités simultanément (pool géré par xargs -P). -# Le nombre de threads par encodeur est borné pour ne pas surcharger : -# x265 sature vite (scaling sublinéaire au-delà de ~6-8 threads), donc -# plusieurs encodages courts valent mieux qu'un seul très parallélisé. +# Un rush dont les deux sorties existent et sont lisibles est sauté +# (FORCE=1 pour refaire). # -# Variables d'environnement : -# JOBS=N nombre de rushs en parallèle -# (défaut : cœurs performance / 4, minimum 1) -# THREADS=N threads par encodeur (défaut : cœurs / JOBS) -# ENCODER=cpu (défaut) libx265 + libx264 — meilleure qualité/octet -# ENCODER=vt VideoToolbox — ~10x plus rapide sur Apple Silicon -# (⚠️ le parallélisme n'y sert à rien : le bloc média -# de la puce est une ressource fixe → JOBS=1 forcé) -# PRESET=medium preset CPU : ultrafast…placebo -# CRF_HEVC=26 qualité du 1080p HEVC (échelle x265) -# CRF_H264=25 qualité du 720p H.264 (échelle x264 — PAS la même !) +# Variables : +# JOBS=N rushs encodés en parallèle (défaut : cœurs performance / 4) +# THREADS=N threads par encodeur (défaut : cœurs / JOBS) +# ENCODER=cpu libx265 + libx264, meilleure qualité par octet (défaut) +# ENCODER=vt VideoToolbox, ~10× plus rapide sur Apple Silicon, un peu +# moins bon à taille égale ; JOBS=1 (un seul bloc matériel) +# PRESET=medium preset x264/x265 +# CRF_HEVC=26 qualité du 1080p (échelle x265) +# CRF_H264=25 qualité du 720p (échelle x264, PAS comparable à x265) # VTQ_HD=60 VTQ_SD=58 qualité VideoToolbox (1-100, plus haut = meilleur) -# FPS_MAX=60 plafonne le framerate (un rush 30p reste en 30p, -# un rush 120p/240p est ramené à 60) -# FORCE=1 réencode même si le fichier de sortie existe +# FPS_MAX=60 plafond de fréquence d'images (un rush 120p est ramené à 60) +# FORCE=1 réencode même si les sorties existent # -# Exemples : -# ./compress.sh mon-voyage -# JOBS=4 ./compress.sh mon-voyage -# JOBS=1 ./compress.sh mon-voyage # séquentiel + barre de progression -# ENCODER=vt ./compress.sh mon-voyage +# Exemples (depuis le dossier du voyage) : +# ../../RoadTripsGenerator/Scripts/compress.sh +# JOBS=1 ../../RoadTripsGenerator/Scripts/compress.sh # avec barre de progression +# ENCODER=vt ../../RoadTripsGenerator/Scripts/compress.sh # ============================================================================= set -uo pipefail shopt -s nocaseglob nullglob -# --- 0. MODE INTERNE --------------------------------------------------------- -# Quand le script est rappelé par xargs pour traiter UN rush, il arrive ici. +# --- Mode interne : rappelé par xargs pour traiter UN rush --------------------- if [ "${1:-}" = "--encode-one" ]; then INTERNAL=1 BASE_DIR="${RT_BASE_DIR:-.}" @@ -58,7 +44,7 @@ fi SOURCE_DIR="$BASE_DIR/rushs" DEST_DIR="$BASE_DIR/videos" -# --- 1. CONFIGURATION -------------------------------------------------------- +# --- Configuration ----------------------------------------------------------- ENCODER="${ENCODER:-cpu}" PRESET="${PRESET:-medium}" CRF_HEVC="${CRF_HEVC:-26}" @@ -68,9 +54,8 @@ VTQ_SD="${VTQ_SD:-58}" FPS_MAX="${FPS_MAX:-60}" FORCE="${FORCE:-0}" -# Détection des cœurs. Sur Apple Silicon on ne compte que les cœurs -# PERFORMANCE : les cœurs d'efficience sont si lents qu'ils deviennent des -# traînards dans un pool x265 et pénalisent l'encodage entier. +# Sur Apple Silicon on ne compte que les cœurs performance : les cœurs +# d'efficience ralentiraient tout le pool de threads x265. detect_cores() { local c="" c=$(sysctl -n hw.perflevel0.logicalcpu 2>/dev/null); [ -n "$c" ] && { echo "$c"; return; } @@ -80,8 +65,6 @@ detect_cores() { } CORES="${CORES:-$(detect_cores)}" -# En VideoToolbox, le parallélisme ne sert à rien (un seul moteur d'encodage -# matériel sur les puces M de base) — sauf si l'utilisateur force JOBS. JOBS_EXPLICIT=0 [ -n "${JOBS+x}" ] && JOBS_EXPLICIT=1 if [ "$ENCODER" = "vt" ] && [ "$JOBS_EXPLICIT" = "0" ]; then @@ -89,64 +72,49 @@ if [ "$ENCODER" = "vt" ] && [ "$JOBS_EXPLICIT" = "0" ]; then else JOBS="${JOBS:-$(( CORES / 4 ))}" fi -[ "$JOBS" -lt 1 ] 2>/dev/null && JOBS=1 [ -z "$JOBS" ] && JOBS=1 +[ "$JOBS" -lt 1 ] 2>/dev/null && JOBS=1 THREADS="${THREADS:-$(( CORES / JOBS ))}" [ "$THREADS" -lt 2 ] 2>/dev/null && THREADS=2 -# Le palier 720p H.264 est 3 à 5× plus rapide que le 1080p HEVC : on lui donne -# moitié moins de threads. Il finit largement avant, et la fenêtre pendant -# laquelle les deux paliers tournent ensemble ne surcharge pas la machine. +# Le 720p H.264 est 3 à 5× plus rapide que le 1080p HEVC : moitié moins de threads. THREADS_SD=$(( THREADS / 2 )) [ "$THREADS_SD" -lt 2 ] && THREADS_SD=2 -# --- 2. FONCTION D'ENCODAGE -------------------------------------------------- +# --- Encodage d'un palier ---------------------------------------------------- # encode