Téléverser les fichiers vers "Scripts"

This commit is contained in:
2026-01-19 09:11:57 +00:00
commit 3332ec7750
4 changed files with 401 additions and 0 deletions

63
Scripts/compress.sh Normal file
View File

@@ -0,0 +1,63 @@
#!/bin/bash
shopt -s nocaseglob
# --- 1. CONFIGURATION DU RÉPERTOIRE ---
# On récupère l'argument 1 s'il existe, sinon on reste sur le répertoire courant
BASE_DIR="${1:-.}"
# Définition des dossiers par rapport à la base
SOURCE_DIR="$BASE_DIR/rushs"
DEST_DIR="$BASE_DIR/videos"
# Vérification que le dossier source existe
if [ ! -d "$SOURCE_DIR" ]; then
echo "❌ Erreur : Le dossier source $SOURCE_DIR n'existe pas."
exit 1
fi
# Création du dossier de destination s'il n'existe pas
mkdir -p "$DEST_DIR"
echo "--- DÉBUT DE LA COMPRESSION ---"
echo "📂 Répertoire racine : $BASE_DIR"
echo "📥 Source : $SOURCE_DIR"
echo "📤 Destination : $DEST_DIR"
# 2. BOUCLE DE COMPRESSION
for f in "$SOURCE_DIR"/*.{mp4,mov}; do
# Vérification si des fichiers existent
[ -e "$f" ] || continue
# Récupération du nom de fichier sans le chemin
filename=$(basename "$f")
echo "🎬 Compression de : $filename ..."
# Compression avec accélération matérielle Mac (videotoolbox)
ffmpeg -i "$f" \
-tag:v hvc1 \
-c:v hevc_videotoolbox \
-b:v 10000k \
-g 60 \
-bf 2 \
-profile:v main \
-pix_fmt yuv420p \
-r 60 \
-c:a aac \
-b:a 128k \
-movflags +faststart \
"$DEST_DIR/$filename" \
-y -loglevel error
# Comparaison de taille
if [ -f "$DEST_DIR/$filename" ]; then
orig_size=$(du -h "$f" | cut -f1)
new_size=$(du -h "$DEST_DIR/$filename" | cut -f1)
echo "✅ Terminé : $filename ($orig_size -> $new_size)"
else
echo "⚠️ Erreur lors de la compression de $filename"
fi
done
shopt -u nocaseglob
echo "--- OPÉRATION TERMINÉE ---"

View File

@@ -0,0 +1,52 @@
#!/bin/bash
# --- 1. CONFIGURATION DU RÉPERTOIRE ---
# On récupère l'argument 1 s'il existe, sinon on reste sur le répertoire courant
BASE_DIR="${1:-.}"
# Définition des dossiers et fichiers par rapport à la base
PHOTOS_DIR="$BASE_DIR/photos"
RUSHS_DIR="$BASE_DIR/rushs"
CSV_PHOTOS="$BASE_DIR/export_photos.csv"
CSV_VIDEOS="$BASE_DIR/export_videos.csv"
# On active l'insensibilité à la casse pour les jokers (*.mp4, etc.)
shopt -s nocaseglob
echo "--- DÉBUT DE L'EXTRACTION BRUTE ---"
echo "📂 Répertoire racine : $BASE_DIR"
# 2. RÉINITIALISATION DES FICHIERS CSV (Optionnel mais recommandé)
# Si tu veux cumuler sans effacer, commente les deux lignes suivantes :
> "$CSV_PHOTOS"
> "$CSV_VIDEOS"
# 3. PHOTOS (Fichiers .jpg, .jpeg)
echo "📸 Traitement des photos dans : $PHOTOS_DIR"
if [ -d "$PHOTOS_DIR" ]; then
for f in "$PHOTOS_DIR"/*.jp*g; do
[ -e "$f" ] || continue
# Utilisation du chemin complet pour exiftool mais basename pour le CSV
exiftool -n -p "$(basename "$f")"',$DateTimeOriginal,$GPSLatitude,$GPSLongitude,$GPSAltitude' "$f" >> "$CSV_PHOTOS"
done
else
echo "⚠️ Dossier photos non trouvé, étape ignorée."
fi
# 4. VIDÉOS (Fichiers .mp4, .mov avec flux GoPro)
echo "🎬 Traitement des vidéos dans : $RUSHS_DIR"
if [ -d "$RUSHS_DIR" ]; then
for f in "$RUSHS_DIR"/*.{mp4,mov}; do
[ -e "$f" ] || continue
# Ta commande exiftool exacte pour le flux GoPro
exiftool -ee -n -p "$(basename "$f")"',$GPSDateTime,$GPSLatitude,$GPSLongitude,$GPSAltitude,$GPSSpeed' "$f" >> "$CSV_VIDEOS"
done
else
echo "⚠️ Dossier rushs non trouvé, étape ignorée."
fi
# On désactive l'option nocaseglob
shopt -u nocaseglob
echo "--- TERMINÉ ---"
echo "✅ Fichiers générés : $CSV_PHOTOS et $CSV_VIDEOS"

238
Scripts/genere_carte.py Normal file
View File

@@ -0,0 +1,238 @@
#!/usr/bin/env python3
import pandas as pd
import folium
from folium import plugins
from folium.plugins import MarkerCluster
import os
import gpxpy
import re
import sys
import markdown2
import numpy as np
# --- 1. CONFIGURATION ---
base_dir = sys.argv[1] if len(sys.argv) > 1 else "./"
base_dir = os.path.abspath(base_dir)
routes_dir = os.path.join(base_dir, "routes")
html_output_dir = os.path.join(base_dir, "html")
csv_videos = os.path.join(base_dir, "export_videos.csv")
csv_photos = os.path.join(base_dir, "export_photos.csv")
file_notes = os.path.join(base_dir, "voyage.md")
if not os.path.exists(html_output_dir):
os.makedirs(html_output_dir)
COULEURS_JOURS = ['#FF5733', '#2ECC71', '#3498DB', '#9B59B6', '#F1C40F', '#E67E22', '#1ABC9C', '#34495E']
noms_colonnes = ['Fichier', 'Date_Heure', 'Latitude', 'Longitude', 'Altitude', 'Vitesse']
# --- FONCTIONS UTILES ---
def nettoyer_et_trier(df):
if df is None or df.empty:
return pd.DataFrame(columns=noms_colonnes + ['DT', 'Jour'])
df = df[(df['Latitude'].notnull()) & (df['Longitude'].notnull())]
df = df[(df['Latitude'] != 0) & (df['Longitude'] != 0)]
df['DT'] = pd.to_datetime(df['Date_Heure'].str.split('.').str[0], format='%Y:%m:%d %H:%M:%S', errors='coerce')
df = df.dropna(subset=['DT'])
df['Jour'] = df['DT'].dt.date
return df.sort_values(by='DT')
def create_pin(color, icon_name):
html_code = f"""<div style="position: relative; height: 40px; width: 24px;">
<div style="background-color: {color}; width: 24px; height: 24px; border-radius: 50%; border: 2px solid white;
display: flex; align-items: center; justify-content: center; color: white; font-size: 12px;
position: absolute; top: 0; z-index: 2; box-shadow: 0 2px 4px rgba(0,0,0,0.4);">
<i class="fa fa-{icon_name}"></i>
</div>
<div style="width: 3px; height: 18px; background-color: {color}; position: absolute; top: 22px; left: 10.5px; z-index: 1;"></div>
</div>"""
return folium.DivIcon(html=html_code, icon_anchor=(12, 40))
def inject_common_assets(m):
custom_js = """
<script>
document.addEventListener('click', function (e) {
setTimeout(function() {
var videos = document.querySelectorAll('video');
videos.forEach(function(video) {
if (video.hasAttribute('autoplay')) {
video.play().catch(function(error) { console.log("Autoplay bloqué"); });
}
});
}, 300);
}, true);
function toggleJournal(open) {
var panel = document.getElementById('journal-panel');
if (open) {
panel.classList.add('open');
localStorage.setItem('journalOpen', 'true');
} else {
panel.classList.remove('open');
localStorage.setItem('journalOpen', 'false');
}
}
// Persistance du journal sur toutes les pages
window.addEventListener('DOMContentLoaded', function() {
if (localStorage.getItem('journalOpen') === 'true') {
var panel = document.getElementById('journal-panel');
if (panel) panel.classList.add('open');
}
});
function moveSlide(btn, step) {
var container = btn.parentElement.querySelector('.slides');
var slides = container.querySelectorAll('.slide');
var activeIndex = Array.from(slides).findIndex(s => s.style.display !== 'none');
if (activeIndex === -1) activeIndex = 0;
slides[activeIndex].style.display = 'none';
var nextIndex = (activeIndex + step + slides.length) % slides.length;
slides[nextIndex].style.display = 'block';
btn.parentElement.querySelector('.slide-counter').innerText = (nextIndex + 1) + '/' + slides.length;
}
</script>
"""
custom_css = """
<style>
.leaflet-top.leaflet-left { top: auto !important; bottom: 20px !important; left: 15px !important; }
.leaflet-popup-content-wrapper { border-radius: 12px; max-width: 95vw !important; }
#journal-panel {
position: fixed; top: 0; right: -100%; width: 400px; height: 100%;
background: white; z-index: 10005; transition: 0.4s;
box-shadow: -5px 0 15px rgba(0,0,0,0.2); padding: 25px;
overflow-y: auto; font-family: sans-serif;
}
#journal-panel.open { right: 0; }
@media (max-width: 600px) { #journal-panel { width: 85%; } }
.close-btn { position: absolute; top: 15px; right: 15px; font-size: 24px; cursor: pointer; border: none; background: none; }
.file-header { background: #f0f0f0; padding: 4px 8px; border-radius: 4px; margin-bottom: 8px; font-size: 11px; font-family: monospace; display: block; overflow: hidden; text-overflow: ellipsis; }
.slider-container { position: relative; width: calc(85vw - 60px); max-width: 800px; text-align: center; min-height: 200px; }
.slide img { max-width: 100%; max-height: 65vh; border-radius: 8px; display: block; margin: auto; }
.nav-btn { position: absolute; top: 50%; transform: translateY(-50%); background: rgba(0,0,0,0.6); color: white; border: 2px solid white; padding: 12px; cursor: pointer; border-radius: 50%; z-index: 10; font-size: 18px; }
.prev { left: -10px; } .next { right: -10px; }
.slide-counter { margin-top: 8px; font-size: 13px; font-weight: bold; color: #333; font-family: sans-serif; }
</style>
"""
m.get_root().header.add_child(folium.Element(custom_css + custom_js))
def get_journal_ui():
journal_html = ""
if os.path.exists(file_notes):
with open(file_notes, 'r', encoding='utf-8') as f:
journal_html = markdown2.markdown(f.read())
return f"""
<div id="journal-panel"><button class="close-btn" onclick="toggleJournal(false)">&times;</button><div class="journal-content">{journal_html}</div></div>
<div style="position: fixed; bottom: 25px; right: 15px; z-index: 10000; display: flex; flex-direction: column; align-items: flex-end; gap: 10px;">
<button onclick="toggleJournal(true)" style="background: #FFD700; border: 2px solid white; padding: 12px 20px; border-radius: 50px; font-weight: bold; font-size: 14px; cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.3); font-family:sans-serif;">📖 Journal</button>
</div>
"""
# --- 2. CHARGEMENT ---
try:
df_v = pd.read_csv(csv_videos, names=noms_colonnes, header=None) if os.path.exists(csv_videos) else pd.DataFrame()
df_p = pd.read_csv(csv_photos, names=noms_colonnes, header=None) if os.path.exists(csv_photos) else pd.DataFrame()
df_v, df_p = nettoyer_et_trier(df_v), nettoyer_et_trier(df_p)
jours_gpx = set()
if os.path.exists(routes_dir):
for f in os.listdir(routes_dir):
match = re.search(r'(\d{4}-\d{2}-\d{2})', f)
if match: jours_gpx.add(pd.to_datetime(match.group(1)).date())
tous_les_jours = sorted(list(set(df_v['Jour'].unique()) | set(df_p['Jour'].unique()) | jours_gpx))
except Exception as e:
print(f"❌ Erreur : {e}"); sys.exit()
# --- 3. GÉNÉRATION ---
m_global = folium.Map(tiles=None)
folium.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attr='Esri', name='Satellite').add_to(m_global)
folium.TileLayer('OpenStreetMap', name='Plan').add_to(m_global)
inject_common_assets(m_global)
m_mini = folium.Map(tiles='OpenStreetMap', zoom_control=False, control_scale=False, attribution_control=False)
all_global_coords, sidebar_links_html = [], ""
for i, jour in enumerate(tous_les_jours):
day_str = jour.strftime('%Y-%m-%d')
file_name = f'carte_{day_str}.html'
color_day = COULEURS_JOURS[i % len(COULEURS_JOURS)]
sidebar_links_html += f'<a href="html/{file_name}" target="_top" style="display:block; margin-bottom:8px; padding:12px; background:{color_day}; color:white; text-decoration:none; border-radius:8px; font-weight:bold; text-align:center; font-family:sans-serif;">{day_str}</a>'
m_day = folium.Map(tiles=None)
folium.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attr='Esri', name='Satellite').add_to(m_day)
folium.TileLayer('OpenStreetMap', name='Plan').add_to(m_day)
inject_common_assets(m_day)
marker_cluster = MarkerCluster(name="Médias").add_to(m_day)
day_coords = []
current_gpx_filename = None
if os.path.exists(routes_dir):
for gpx_file in os.listdir(routes_dir):
if gpx_file.startswith(day_str) and gpx_file.endswith('.gpx'):
current_gpx_filename = gpx_file
try:
with open(os.path.join(routes_dir, gpx_file), 'r') as f:
gpx = gpxpy.parse(f)
for track in gpx.tracks:
pts = []
for segment in track.segments:
# OPTIMISATION : On ne garde qu'un point sur 5
# pts.extend([[p.latitude, p.longitude] for i, p in enumerate(segment.points) if i % 5 == 0])
pts.extend([[p.latitude, p.longitude] for p in segment.points])
if pts:
day_coords.extend(pts); all_global_coords.extend(pts)
popup_html = f"<div style='text-align:center; font-family:sans-serif;'><b>📅 {day_str}</b><br><br><a href='html/{file_name}' target='_top' style='background:{color_day}; color:white; padding:8px 12px; border-radius:4px; text-decoration:none; font-weight:bold;'>Détails</a></div>"
folium.PolyLine(pts, color=color_day, weight=7, opacity=0.8, popup=folium.Popup(popup_html, max_width=200)).add_to(m_global)
folium.PolyLine(pts, color=color_day, weight=3, opacity=0.8).add_to(m_mini)
folium.PolyLine(pts, color="#FF0000", weight=4, opacity=0.8).add_to(m_day)
except: pass
m_day.get_root().html.add_child(folium.Element('<div style="position: fixed; top: 15px; left: 15px; z-index: 10001;"><a href="../index.html" target="_top" style="text-decoration: none; background: #333; color: white; padding: 12px 20px; border-radius: 10px; font-family: sans-serif; font-weight: bold; box-shadow: 0 4px 10px rgba(0,0,0,0.5); border: 2px solid white; display: block;">🏠 Accueil</a></div>' + get_journal_ui()))
if current_gpx_filename:
m_day.get_root().html.add_child(folium.Element(f'<div style="position: fixed; bottom: 85px; right: 15px; z-index: 10001;"><a href="../routes/{current_gpx_filename}" download style="text-decoration: none; background: #28a745; color: white; padding: 12px 20px; border-radius: 50px; font-family: sans-serif; font-weight: bold; box-shadow: 0 4px 15px rgba(0,0,0,0.4); border: 2px solid white; display: block;">📥 GPX</a></div>'))
# REGROUPEMENT PHOTOS
day_p = df_p[df_p['Jour'] == jour].copy()
if not day_p.empty:
tol = 0.0002
while not day_p.empty:
ref = day_p.iloc[0]
masque = (np.abs(day_p['Latitude'] - ref['Latitude']) < tol) & (np.abs(day_p['Longitude'] - ref['Longitude']) < tol)
groupe = day_p[masque]
fichiers = groupe['Fichier'].tolist()
nb = len(fichiers)
# slides = "".join([f'<div class="slide" style="display:{"block" if idx == 0 else "none"};"><div class="file-header">{p.strip()}</div><a href="../photos/{p.strip()}" target="_blank"><img src="../photos/{p.strip()}"></a></div>' for idx, p in enumerate(fichiers)])
# OPTIMISATION : Ajout de loading="lazy" dans la balise img
slides = "".join([f'<div class="slide" style="display:{"block" if idx == 0 else "none"};"><div class="file-header">{p.strip()}</div><a href="../photos/{p.strip()}" target="_blank"><img src="../photos/{p.strip()}" loading="lazy"></a></div>' for idx, p in enumerate(fichiers)])
btns = f'<button class="nav-btn prev" onclick="moveSlide(this, -1)">&#10094;</button><button class="nav-btn next" onclick="moveSlide(this, 1)">&#10095;</button><div class="slide-counter">1/{nb}</div>' if nb > 1 else ""
folium.Marker(location=[ref['Latitude'], ref['Longitude']], popup=folium.Popup(f'<div class="slider-container"><div class="slides">{slides}</div>{btns}</div>', max_width="100%"), icon=create_pin("#FF3B30", "camera" if nb==1 else "images")).add_to(marker_cluster)
day_p = day_p[~masque]
# Vidéos
day_v = df_v[df_v['Jour'] == jour]
for v_name, group in day_v.groupby('Fichier'):
pts_v = group[['Latitude', 'Longitude']].values.tolist()
# v_pop = f'<div style="width:calc(85vw - 40px); max-width:1000px; text-align:center;"><div class="file-header">{v_name.strip()}</div><video style="width:100%; max-height:70vh; border-radius:8px; background:black;" controls playsinline webkit-playsinline autoplay muted><source src="../videos/{v_name.strip()}" type="video/mp4"></video></div>'
v_pop = f'<div style="width:calc(85vw - 40px); max-width:1000px; text-align:center;"><div class="file-header">{v_name.strip()}</div><video style="width:100%; max-height:70vh; border-radius:8px; background:black;" controls playsinline webkit-playsinline autoplay muted preload="auto"><source src="../videos/{v_name.strip()}" type="video/mp4"></video></div>'
folium.Marker(location=pts_v[0], popup=folium.Popup(v_pop, max_width="100%"), icon=create_pin("#007AFF", "play")).add_to(marker_cluster)
if day_coords: m_day.fit_bounds(day_coords, padding=(50, 50))
folium.LayerControl(position='topright').add_to(m_day)
plugins.Fullscreen(position='topright').add_to(m_day)
m_day.save(os.path.join(html_output_dir, file_name))
# --- FINALISATION ---
if all_global_coords:
m_global.fit_bounds(all_global_coords, padding=(50, 50))
m_mini.fit_bounds(all_global_coords, padding=(20, 20))
sidebar_html = f'<div style="position: fixed; top: 15px; left: 15px; width: 160px; max-height: 85vh; overflow-y: auto; background: rgba(255,255,255,0.9); z-index: 9999; padding: 12px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.2);"><a href="../" target="_top" style="display:block; margin-bottom:15px; padding:12px; background:#444; color:white; text-decoration:none; border-radius:8px; font-weight:bold; text-align:center; border: 2px solid #666; font-family:sans-serif;">⬅ Retour</a><div style="border-top: 1px solid #ccc; margin-bottom: 10px; padding-top: 10px; font-family:sans-serif; font-size:12px; font-weight:bold; color:#666; text-align:center;">PAR JOURS</div>{sidebar_links_html}</div>'
m_global.get_root().html.add_child(folium.Element(sidebar_html + get_journal_ui()))
folium.LayerControl().add_to(m_global)
m_global.save(os.path.join(base_dir, "index.html"))
m_mini.save(os.path.join(base_dir, "mini.html"))

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3
import os
EXCLURE = ['html', 'Scripts', '__pycache__', '.git']
OUTPUT_FILE = "index.html"
def generer_index_general():
road_trips = []
for d in sorted(os.listdir('.')):
if os.path.isdir(d) and d not in EXCLURE:
# On cherche mini.html en priorité
if os.path.exists(os.path.join(d, 'mini.html')):
road_trips.append(d)
html_content = f"""
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Nos Road-Trips</title>
<style>
body {{ font-family: 'Segoe UI', sans-serif; background: #f4f4f9; margin: 0; padding: 20px; }}
.grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 30px; max-width: 1200px; margin: 0 auto; }}
.card {{ background: white; border-radius: 15px; overflow: hidden; box-shadow: 0 10px 20px rgba(0,0,0,0.1); }}
.map-container {{ width: 100%; height: 300px; border: none; pointer-events: none; }}
.card-content {{ padding: 20px; text-align: center; }}
.btn {{ display: inline-block; padding: 10px 25px; background: #3498db; color: white; text-decoration: none; border-radius: 25px; font-weight: bold; }}
</style>
</head>
<body>
<h1 style="text-align:center;">🌍 Nos Road-Trips</h1>
<div class="grid">
"""
for trip in road_trips:
html_content += f"""
<div class="card">
<iframe class="map-container" src="{trip}/mini.html"></iframe>
<div class="card-content">
<h2 style="text-transform: capitalize;">{trip.replace('-', ' ')}</h2>
<a href="{trip}/index.html" class="btn">Voir le voyage</a>
</div>
</div>
"""
html_content += "</div></body></html>"
with open(OUTPUT_FILE, "w", encoding="utf-8") as f: f.write(html_content)
if __name__ == "__main__":
generer_index_general()