update compression parameters and resolution choices
This commit is contained in:
@@ -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 60 \
|
-r 60000/1001 \
|
||||||
-c:a aac \
|
-vf "scale=1920:-2:flags=lanczos" \
|
||||||
-b:a 128k \
|
-c:a aac -b:a 96k -ac 1 \
|
||||||
|
-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."
|
||||||
|
|||||||
@@ -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)">❮</button><button class="nav-btn next" onclick="moveSlide(this, 1)">❯</button><div class="slide-counter">1/{nb}</div>' if nb > 1 else ""
|
btns = f'<button class="nav-btn prev" onclick="moveSlide(this, -1)">❮</button><button class="nav-btn next" onclick="moveSlide(this, 1)">❯</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"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user