update compression parameters and resolution choices

This commit is contained in:
2026-02-12 21:40:37 +01:00
parent 28eaf9feaa
commit e4baa929f2
2 changed files with 148 additions and 56 deletions

View File

@@ -1,63 +1,83 @@
#!/bin/bash #!/bin/bash
shopt -s nocaseglob shopt -s nocaseglob
# --- 1. CONFIGURATION DU RÉPERTOIRE --- # --- 1. CONFIGURATION ---
# On récupère l'argument 1 s'il existe, sinon on reste sur le répertoire courant # Utilise l'argument 1 comme dossier racine, ou le dossier courant par défaut
BASE_DIR="${1:-.}" BASE_DIR="${1:-.}"
# Définition des dossiers par rapport à la base
SOURCE_DIR="$BASE_DIR/rushs" SOURCE_DIR="$BASE_DIR/rushs"
DEST_DIR="$BASE_DIR/videos" DEST_DIR="$BASE_DIR/videos"
# Vérification que le dossier source existe # Vérification du dossier source
if [ ! -d "$SOURCE_DIR" ]; then if [ ! -d "$SOURCE_DIR" ]; then
echo "❌ Erreur : Le dossier source $SOURCE_DIR n'existe pas." echo "❌ Erreur : Dossier source $SOURCE_DIR introuvable."
exit 1 exit 1
fi fi
# Création du dossier de destination s'il n'existe pas # Création du dossier de destination
mkdir -p "$DEST_DIR" mkdir -p "$DEST_DIR"
echo "--- DÉBUT DE LA COMPRESSION ---" echo "--- COMPRESSION ROAD-TRIP (CPU OPTIMISÉ) ---"
echo "📂 Répertoire racine : $BASE_DIR" echo "📂 Source : $SOURCE_DIR"
echo "📥 Source : $SOURCE_DIR"
echo "📤 Destination : $DEST_DIR" echo "📤 Destination : $DEST_DIR"
# 2. BOUCLE DE COMPRESSION # 2. BOUCLE DE TRAITEMENT
for f in "$SOURCE_DIR"/*.{mp4,mov}; do for f in "$SOURCE_DIR"/*.{mp4,mov}; do
# Vérification si des fichiers existent
[ -e "$f" ] || continue [ -e "$f" ] || continue
# Récupération du nom de fichier sans le chemin # Extraction du nom de base (ex: GH011182.mp4 -> GH011182)
filename=$(basename "$f") filename=$(basename "$f")
base="${filename%.*}"
echo "🎬 Compression de : $filename ..." # Conventions de nommage validées
desktop_out="$DEST_DIR/${base}.mp4"
mobile_out="$DEST_DIR/${base}_mobile.mp4"
# Compression avec accélération matérielle Mac (videotoolbox) echo "🎬 Traitement de : $filename ..."
# --- A. VERSION DESKTOP (1080p60 - Validé 233 Mo) ---
# Réglages : CRF 28, Maxrate 12M, GOP 120, FPS source exact
ffmpeg -i "$f" \ ffmpeg -i "$f" \
-tag:v hvc1 \ -c:v libx264 -preset slow -profile:v high \
-c:v hevc_videotoolbox \ -crf 28 \
-b:v 10000k \ -tune film \
-g 60 \ -g 120 -keyint_min 120 -sc_threshold 0 \
-bf 2 \ -maxrate 15M -bufsize 30M \
-profile:v main \ -pix_fmt yuv420p \
-pix_fmt yuv420p \ -r 60000/1001 \
-r 60 \ -vf "scale=1920:-2:flags=lanczos" \
-c:a aac \ -c:a aac -b:a 96k -ac 1 \
-b:a 128k \ -map_metadata -1 \
-movflags +faststart \ -movflags +faststart \
"$DEST_DIR/$filename" \ -y -loglevel error \
-y -loglevel error "$desktop_out"
# Comparaison de taille # --- B. VERSION MOBILE (720p60 - Optimisé 4G/Smartphone) ---
if [ -f "$DEST_DIR/$filename" ]; then # Réglages : On baisse le maxrate à 6M pour la fluidité mobile
orig_size=$(du -h "$f" | cut -f1) ffmpeg -i "$f" \
new_size=$(du -h "$DEST_DIR/$filename" | cut -f1) -c:v libx264 -preset slow -profile:v high \
echo "✅ Terminé : $filename ($orig_size -> $new_size)" -crf 28 \
else -tune film \
echo "⚠️ Erreur lors de la compression de $filename" -g 120 -keyint_min 120 -sc_threshold 0 \
-maxrate 6M -bufsize 12M \
-pix_fmt yuv420p \
-r 60000/1001 \
-vf "scale=1280:-2:flags=lanczos" \
-c:a aac -b:a 64k -ac 1 \
-map_metadata -1 \
-movflags +faststart \
-y -loglevel error \
"$mobile_out"
# Petit bilan de taille pour confirmer l'économie
if [ -f "$desktop_out" ]; then
d_size=$(du -h "$desktop_out" | cut -f1)
m_size=$(du -h "$mobile_out" | cut -f1)
echo " ✅ Desktop : $d_size (${base}.mp4)"
echo " ✅ Mobile : $m_size (${base}_mobile.mp4)"
fi fi
echo "---------------------------------------------------"
done done
shopt -u nocaseglob shopt -u nocaseglob
echo "--- OPÉRATION TERMINÉE ---" echo "✅ TOUTES LES VIDÉOS SONT PRÊTES."

View File

@@ -9,6 +9,7 @@ import re
import sys import sys
import markdown2 import markdown2
import numpy as np import numpy as np
import html
# --- 1. CONFIGURATION --- # --- 1. CONFIGURATION ---
base_dir = sys.argv[1] if len(sys.argv) > 1 else "./" base_dir = sys.argv[1] if len(sys.argv) > 1 else "./"
@@ -51,16 +52,47 @@ def create_pin(color, icon_name):
def inject_common_assets(m): def inject_common_assets(m):
custom_js = """ custom_js = """
<script> <script>
document.addEventListener('click', function (e) { // --- GESTION DES BOUTONS 720p / 1080p ---
setTimeout(function() { function changeQuality(videoId, newSrc, btnElement) {
var videos = document.querySelectorAll('video'); var video = document.getElementById(videoId);
videos.forEach(function(video) {
if (video.hasAttribute('autoplay')) { if (video.src.includes(newSrc)) return;
video.play().catch(function(error) { console.log("Autoplay bloqué"); });
var savedTime = video.currentTime;
var wasPlaying = !video.paused; // On regarde si c'était en lecture
video.src = newSrc;
// On attend que les métadonnées soient prêtes pour remettre le temps
video.onloadedmetadata = function() {
this.currentTime = savedTime;
if (wasPlaying) {
var playPromise = this.play();
if (playPromise !== undefined) {
playPromise.catch(error => { console.log("Lecture bloquée"); });
} }
}); }
}, 300); this.onloadedmetadata = null;
}, true); };
video.load();
// Mise à jour visuelle des boutons
var container = btnElement.parentElement;
var buttons = container.getElementsByClassName('quality-btn');
for (var i = 0; i < buttons.length; i++) {
buttons[i].classList.remove('active-720', 'active-1080');
buttons[i].style.opacity = "0.6";
}
btnElement.style.opacity = "1";
if (btnElement.innerText.includes("720p")) {
btnElement.classList.add('active-720');
} else {
btnElement.classList.add('active-1080');
}
}
// ----------------------------------------
function toggleJournal(open) { function toggleJournal(open) {
var panel = document.getElementById('journal-panel'); var panel = document.getElementById('journal-panel');
@@ -73,7 +105,6 @@ def inject_common_assets(m):
} }
} }
// Persistance du journal sur toutes les pages
window.addEventListener('DOMContentLoaded', function() { window.addEventListener('DOMContentLoaded', function() {
if (localStorage.getItem('journalOpen') === 'true') { if (localStorage.getItem('journalOpen') === 'true') {
var panel = document.getElementById('journal-panel'); var panel = document.getElementById('journal-panel');
@@ -95,6 +126,24 @@ def inject_common_assets(m):
""" """
custom_css = """ custom_css = """
<style> <style>
.header-row {
display: flex; justify-content: space-between; align-items: center;
background: #f0f0f0; padding: 5px 10px; border-radius: 4px; margin-bottom: 5px;
}
.file-name {
font-size: 11px; font-family: monospace; font-weight: bold; color: #333;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 60%;
}
.quality-controls { display: flex; gap: 6px; }
.quality-btn {
padding: 3px 8px; border-radius: 12px; font-size: 10px; font-weight: bold; cursor: pointer;
border: 1px solid #ccc; background: #fff; opacity: 0.6; transition: 0.2s; font-family: sans-serif;
text-transform: uppercase;
}
.quality-btn:hover { opacity: 1; background: #eee; }
.active-720 { background-color: #2ECC71 !important; color: white; border-color: #27ae60 !important; opacity: 1 !important; }
.active-1080 { background-color: #007AFF !important; color: white; border-color: #0056b3 !important; opacity: 1 !important; }
.leaflet-top.leaflet-left { top: auto !important; bottom: 20px !important; left: 15px !important; } .leaflet-top.leaflet-left { top: auto !important; bottom: 20px !important; left: 15px !important; }
.leaflet-popup-content-wrapper { border-radius: 12px; max-width: 95vw !important; } .leaflet-popup-content-wrapper { border-radius: 12px; max-width: 95vw !important; }
#journal-panel { #journal-panel {
@@ -106,7 +155,6 @@ def inject_common_assets(m):
#journal-panel.open { right: 0; } #journal-panel.open { right: 0; }
@media (max-width: 600px) { #journal-panel { width: 85%; } } @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; } .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; } .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; } .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; } .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; }
@@ -178,8 +226,6 @@ for i, jour in enumerate(tous_les_jours):
for track in gpx.tracks: for track in gpx.tracks:
pts = [] pts = []
for segment in track.segments: 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]) pts.extend([[p.latitude, p.longitude] for p in segment.points])
if pts: if pts:
day_coords.extend(pts); all_global_coords.extend(pts) day_coords.extend(pts); all_global_coords.extend(pts)
@@ -204,9 +250,6 @@ for i, jour in enumerate(tous_les_jours):
groupe = day_p[masque] groupe = day_p[masque]
fichiers = groupe['Fichier'].tolist() fichiers = groupe['Fichier'].tolist()
nb = len(fichiers) 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)]) 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 "" 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) 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)
@@ -216,8 +259,34 @@ for i, jour in enumerate(tous_les_jours):
day_v = df_v[df_v['Jour'] == jour] day_v = df_v[df_v['Jour'] == jour]
for v_name, group in day_v.groupby('Fichier'): for v_name, group in day_v.groupby('Fichier'):
pts_v = group[['Latitude', 'Longitude']].values.tolist() 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>' base_name = os.path.splitext(v_name.strip())[0]
clean_id = re.sub(r'[^a-zA-Z0-9]', '', base_name)
video_id = f"vid_{clean_id}"
desktop_file = f"../videos/{base_name}.mp4"
mobile_file = f"../videos/{base_name}_mobile.mp4"
# Ajout 'autoplay muted' pour le lancement au clic
# Ajout 'preload=none' pour ne pas charger les autres
v_pop = (
f'<div style="width:calc(85vw - 40px); max-width:1000px; text-align:center;">'
f'<div class="header-row">'
f'<span class="file-name" title="{v_name.strip()}">{v_name.strip()}</span>'
f'<div class="quality-controls">'
f'<span class="quality-btn active-720" onclick="changeQuality(\'{video_id}\', \'{mobile_file}\', this)">720p</span>'
f'<span class="quality-btn" onclick="changeQuality(\'{video_id}\', \'{desktop_file}\', this)">1080p</span>'
f'</div>'
f'</div>'
f'<video id="{video_id}" style="width:100%; max-height:70vh; border-radius:8px; background:black;" '
f'controls playsinline webkit-playsinline autoplay muted preload="none" '
f'src="{mobile_file}">'
f'</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) 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)) if day_coords: m_day.fit_bounds(day_coords, padding=(50, 50))
@@ -236,3 +305,6 @@ folium.LayerControl().add_to(m_global)
m_global.save(os.path.join(base_dir, "index.html")) m_global.save(os.path.join(base_dir, "index.html"))
m_mini.save(os.path.join(base_dir, "mini.html")) m_mini.save(os.path.join(base_dir, "mini.html"))