#!/bin/bash # ============================================================================= # EXTRACTION DES POSITIONS GPS — photos (EXIF) et rushs GoPro (GPS embarqué) # # Produit deux CSV sans en-tête, lus par genere_carte.py : # export_photos.csv : fichier, date, lat, lon, alt # export_videos.csv : fichier, date GPS, lat, lon, alt, vitesse # # Usage : ./extraction_infos.sh [dossier_voyage] # # Variables : # VIDEO_GPS=first (défaut) premier point GPS de chaque rush, c'est là que # genere_carte.py pose le marqueur. Rapide. # VIDEO_GPS=full toute la trace de chaque rush. Lit les fichiers en entier : # plusieurs minutes par rush sur un disque externe. # VARIANTE=D|A|B|C force le mode d'extraction vidéo (voir plus bas) # ============================================================================= set -uo pipefail shopt -s nocaseglob nullglob BASE_DIR="${1:-.}" PHOTOS_DIR="$BASE_DIR/photos" RUSHS_DIR="$BASE_DIR/rushs" CSV_PHOTOS="$BASE_DIR/export_photos.csv" CSV_VIDEOS="$BASE_DIR/export_videos.csv" VIDEO_GPS="${VIDEO_GPS:-first}" VARIANTE="${VARIANTE:-}" if ! command -v exiftool >/dev/null 2>&1; then echo "❌ exiftool introuvable. Installe-le : brew install exiftool" exit 1 fi echo "--- EXTRACTION DES MÉTADONNÉES ---" echo "📂 Répertoire : $BASE_DIR" TMPD="$(mktemp -d "${TMPDIR:-/tmp}/extr.XXXXXX")" ERRLOG="$TMPD/err.log" : > "$ERRLOG" trap 'rm -rf "$TMPD"' EXIT nlines() { awk 'END{print NR+0}' "$1" 2>/dev/null || echo 0; } # ============================================================================= # FORMATS # # `exiftool -p` supprime toute la ligne dès qu'un seul tag du format manque. # Sur un rush GoPro, le document principal porte la position mais pas # GPSDateTime (qui est dans [Doc1]) : un format qui réclame la date ne # produit alors rien. Ne PAS ajouter -f pour forcer : avec -ee, -f fait perdre # l'itération par document et ne sort plus qu'une ligne vide. # # D'où quatre modes, essayés sur les premiers rushs ; on garde le premier qui # produit quelque chose : # D = lecture des tags un par un, analysée par awk (atteint [Doc1]) # A = format complet (date, lat, lon, alt, vitesse) # B = format réduit (date, lat, lon) # C = format réduit + -ee3 (sous-documents détaillés) # ============================================================================= FMT_PHOTOS='"$FileName",$DateTimeOriginal,$GPSLatitude,$GPSLongitude,$GPSAltitude' FMT_PHOTOS_MIN='"$FileName",$DateTimeOriginal,$GPSLatitude,$GPSLongitude' FMT_V_COMPLET='"$FileName",$GPSDateTime,$GPSLatitude,$GPSLongitude,$GPSAltitude,$GPSSpeed' FMT_V_MINIMAL='"$FileName",$GPSDateTime,$GPSLatitude,$GPSLongitude' # Reconstruit des lignes CSV depuis la sortie « Tag : valeur » d'exiftool # (noms longs ou courts, avec ou sans préfixe de groupe [Doc1]). PARSE_GPS=' { ligne = $0 sub(/^\[[^]]*\][ \t]*/, "", ligne) if (index(ligne, ":") == 0) next tag = ligne; sub(/[ \t]*:.*/, "", tag); gsub(/[ \t]/, "", tag) val = ligne; sub(/^[^:]*:[ \t]*/, "", val); gsub(/[ \t]+$/, "", val) if (tag == "GPSDateTime" && dt == "") dt = val else if (tag == "CreateDate" && cd == "") cd = val else if (tag == "GPSLatitude") lat = val else if (tag == "GPSLongitude") { if (lat != "") { n++ if (mode == "full") { plat[n] = lat; plon[n] = val } else if (flat == "") { flat = lat; flon = val } lat = "" } } else if (tag == "GPSAltitude") { if (mode == "full" && n > 0) palt[n] = val } else if (tag == "GPSSpeed") { if (mode == "full" && n > 0) pspd[n] = val } # Mode rapide : on sort dès qu on a une date et une position ; exiftool # reçoit alors SIGPIPE et arrête de lire le fichier. if (mode != "full" && flat != "" && dt != "") { print f "," dt "," flat "," flon ",,"; emis = 1; exit } } END { d = (dt != "" ? dt : cd) if (d == "") exit if (mode == "full") { for (i = 1; i <= n; i++) print f "," d "," plat[i] "," plon[i] "," palt[i] "," pspd[i] } else if (!emis && flat != "") print f "," d "," flat "," flon ",," } ' # extrait_video -> lignes CSV sur stdout extrait_video() { local m="$1" f="$2" nom nom=$(basename "$f") case "$m" in D) exiftool -ee -n -q -s -api largefilesupport=1 \ -GPSDateTime -GPSLatitude -GPSLongitude -GPSAltitude -GPSSpeed -CreateDate \ "$f" 2>>"$ERRLOG" \ | awk -v f="\"$nom\"" -v mode="$VIDEO_GPS" "$PARSE_GPS" ;; A) exiftool -ee -n -q -api largefilesupport=1 -p "$FMT_V_COMPLET" "$f" 2>>"$ERRLOG" ;; B) exiftool -ee -n -q -api largefilesupport=1 -p "$FMT_V_MINIMAL" "$f" 2>>"$ERRLOG" | awk '{print $0 ",,"}' ;; C) exiftool -ee3 -n -q -api largefilesupport=1 -p "$FMT_V_MINIMAL" "$f" 2>>"$ERRLOG" | awk '{print $0 ",,"}' ;; esac } # --- 1. PHOTOS --------------------------------------------------------------- : > "$CSV_PHOTOS" if [ -d "$PHOTOS_DIR" ]; then echo "📸 Photos : analyse en cours…" # Un seul appel exiftool pour tout le dossier : chaque lancement coûte # ~200 ms (Perl). Seconde passe au format réduit pour rattraper les photos # sans altitude, écartées par la première. exiftool -n -q -ext jpg -ext jpeg -p "$FMT_PHOTOS" "$PHOTOS_DIR" \ 2>>"$ERRLOG" > "$TMPD/p1.csv" exiftool -n -q -ext jpg -ext jpeg -p "$FMT_PHOTOS_MIN" "$PHOTOS_DIR" \ 2>>"$ERRLOG" | awk '{print $0 ","}' > "$TMPD/p2.csv" # Fusion : tout p1, plus les photos de p2 absentes de p1. cat "$TMPD/p1.csv" > "$CSV_PHOTOS" awk -F, 'NR==FNR {vu[$1]=1; next} !($1 in vu)' "$TMPD/p1.csv" "$TMPD/p2.csv" >> "$CSV_PHOTOS" n1=$(nlines "$TMPD/p1.csv"); nt=$(nlines "$CSV_PHOTOS") printf "📸 Photos : %d ligne(s)" "$nt" [ "$nt" -gt "$n1" ] && printf " (dont %d récupérée(s) sans altitude)" "$((nt - n1))" echo else echo "⚠️ Dossier photos non trouvé, étape ignorée." fi # --- 2. VIDÉOS --------------------------------------------------------------- : > "$CSV_VIDEOS" if [ ! -d "$RUSHS_DIR" ]; then echo "⚠️ Dossier rushs non trouvé, étape ignorée." else rushs=() for f in "$RUSHS_DIR"/*.mp4 "$RUSHS_DIR"/*.mov; do [ -e "$f" ] || continue # « ._nom » : métadonnées que macOS dépose sur les disques non-HFS (exFAT). case "$(basename "$f")" in ._*) continue ;; esac rushs+=("$f") done total="${#rushs[@]}" if [ "$total" -eq 0 ]; then echo "⚠️ Aucun rush trouvé dans $RUSHS_DIR" else if [ "$VIDEO_GPS" = "full" ]; then echo "🎬 Vidéos : $total rush(s), trace GPS COMPLÈTE" echo " ⏳ Chaque fichier est lu intégralement." else echo "🎬 Vidéos : $total rush(s), premier point GPS seulement" echo " (VIDEO_GPS=full pour dérouler la trace entière)" fi # Choix du mode, testé sur 3 rushs au plus. if [ -z "$VARIANTE" ]; then echo " 🔎 recherche du mode d'extraction…" for essai in D A B C; do k=0 for f in "${rushs[@]}"; do k=$((k + 1)); [ "$k" -gt 3 ] && break if [ -n "$(extrait_video "$essai" "$f" | head -n 1)" ]; then VARIANTE="$essai"; break fi done [ -n "$VARIANTE" ] && break done fi case "${VARIANTE:-}" in D) echo " ✔ mode D : lecture directe des tags (atteint [Doc1])" ;; A) echo " ✔ mode A : format complet (date, lat, lon, alt, vitesse)" ;; B) echo " ✔ mode B : format réduit (date, lat, lon)" ;; C) echo " ✔ mode C : format réduit + -ee3 (sous-documents détaillés)" ;; *) echo " ⚠️ Aucun mode ne donne de résultat sur les 3 premiers rushs." echo " Diagnostic : exiftool -ee -G3 -n | grep -i gps" VARIANTE=A ;; esac i=0; avec=0; sans=0 T0=$(date +%s) for f in "${rushs[@]}"; do i=$((i + 1)) printf " [%3d/%3d] %-28s " "$i" "$total" "$(basename "$f")" avant=$(nlines "$CSV_VIDEOS") if [ "$VIDEO_GPS" = "full" ]; then extrait_video "$VARIANTE" "$f" >> "$CSV_VIDEOS" else # head ferme le tuyau : exiftool s'arrête sans lire la fin du fichier. extrait_video "$VARIANTE" "$f" | head -n 1 >> "$CSV_VIDEOS" fi ajoutes=$(( $(nlines "$CSV_VIDEOS") - avant )) if [ "$ajoutes" -gt 0 ]; then if [ "$VIDEO_GPS" = "full" ]; then printf "✓ %d points\n" "$ajoutes"; else echo "✓"; fi avec=$((avec + 1)) else echo "— pas de GPS"; sans=$((sans + 1)) fi done T1=$(date +%s); EL=$((T1 - T0)) printf "🎬 Vidéos : %d point(s) GPS, %d rush(s) localisé(s)" "$(nlines "$CSV_VIDEOS")" "$avec" [ "$sans" -gt 0 ] && printf ", %d sans GPS" "$sans" printf " — %d min %02d s\n" $((EL / 60)) $((EL % 60)) fi fi echo "✅ Générés : $CSV_PHOTOS et $CSV_VIDEOS"