Files
RoadTripsGenerator/Scripts/compress.sh
T
FabriceandClaude Opus 5.5 97b936fd3e Refonte : templates, balades, photos web, aperçus, carte globale
- Mise en page sortie du Python : Scripts/templates/ (Jinja2 + CSS/JS
  communs copiés dans assets/ de chaque voyage)
- Voyages rangés dans un sous-dossier (Balades/…) : rubriques sur
  l'accueil, 2 plus récents par rubrique, pages voyages.html et
  balades.html
- prepare_web.py : photos allégées (photos_web/) et images d'aperçu
  des vidéos (apercus/)
- Voyage d'une seule journée : la page du voyage est la carte du jour
- Carte globale de l'accueil (OpenStreetMap) : voyages du site + cartes
  Google My Maps récupérées (mymaps.txt, _mymaps/), moto par voyage
  (moto.txt)
- KML par voyage pour My Maps
- compress.sh : réencode une sortie illisible (encodage interrompu) ;
  repli automatique sur l'autre qualité dans la page
- Corrections : CSV vide (voyage sans photo), racine de genere_site.sh,
  fichiers ._ de macOS
- README réécrit, commentaires allégés

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
2026-10-09 11:08:01 +00:00

275 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# =============================================================================
# COMPRESSION DES RUSHS POUR LE WEB
#
# Pour chaque rush de <voyage>/rushs, deux fichiers dans <voyage>/videos :
# <base>.mp4 1080p HEVC (tag hvc1) — qualité, ~40 % plus léger que H.264
# <base>_mobile.mp4 720p H.264 (tag avc1) — chargé par défaut, lisible partout
#
# Un rush dont les deux sorties existent et sont lisibles est sauté
# (FORCE=1 pour refaire).
#
# 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 plafond de fréquence d'images (un rush 120p est ramené à 60)
# FORCE=1 réencode même si les sorties existent
#
# 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
# --- Mode interne : rappelé par xargs pour traiter UN rush ---------------------
if [ "${1:-}" = "--encode-one" ]; then
INTERNAL=1
BASE_DIR="${RT_BASE_DIR:-.}"
TARGET="${2:?--encode-one attend un fichier}"
else
INTERNAL=0
BASE_DIR="${1:-.}"
fi
SOURCE_DIR="$BASE_DIR/rushs"
DEST_DIR="$BASE_DIR/videos"
# --- Configuration -----------------------------------------------------------
ENCODER="${ENCODER:-cpu}"
PRESET="${PRESET:-medium}"
CRF_HEVC="${CRF_HEVC:-26}"
CRF_H264="${CRF_H264:-25}"
VTQ_HD="${VTQ_HD:-60}"
VTQ_SD="${VTQ_SD:-58}"
FPS_MAX="${FPS_MAX:-60}"
FORCE="${FORCE:-0}"
# 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; }
c=$(sysctl -n hw.logicalcpu 2>/dev/null); [ -n "$c" ] && { echo "$c"; return; }
c=$(nproc 2>/dev/null); [ -n "$c" ] && { echo "$c"; return; }
echo 4
}
CORES="${CORES:-$(detect_cores)}"
JOBS_EXPLICIT=0
[ -n "${JOBS+x}" ] && JOBS_EXPLICIT=1
if [ "$ENCODER" = "vt" ] && [ "$JOBS_EXPLICIT" = "0" ]; then
JOBS=1
else
JOBS="${JOBS:-$(( CORES / 4 ))}"
fi
[ -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 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
# --- Encodage d'un palier ----------------------------------------------------
# encode <entrée> <sortie> <filtre_vf|none> <hevc|h264> <crf> <vtq> <maxrate> <bufsize> <audio>
encode() {
local in="$1" out="$2" vf="$3" codec="$4" crf="$5" vtq="$6" maxrate="$7" bufsize="$8" abr="$9"
local -a vopts vf_args statopt
if [ "$vf" = "none" ]; then vf_args=(); else vf_args=(-vf "$vf"); fi
# Progression seulement en séquentiel : à plusieurs jobs, les lignes s'entremêlent.
if [ "$JOBS" = "1" ]; then statopt=(-stats); else statopt=(); fi
if [ "$codec" = "hevc" ]; then
if [ "$ENCODER" = "vt" ]; then
vopts=(-c:v hevc_videotoolbox -profile:v main -q:v "$vtq" -allow_sw 1 -realtime 0 -tag:v hvc1)
else
# keyint=120 : une image-clé toutes les 2 s à 60 fps.
# pools=N borne le pool de threads x265 pour cohabiter avec les autres jobs.
# -tag:v hvc1 : sans lui ffmpeg écrit « hev1 », que Safari refuse.
vopts=(-c:v libx265 -preset "$PRESET" -profile:v main -crf "$crf"
-x265-params "keyint=120:min-keyint=24:aq-mode=3:pools=$THREADS"
-maxrate "$maxrate" -bufsize "$bufsize" -tag:v hvc1)
fi
else
if [ "$ENCODER" = "vt" ]; then
vopts=(-c:v h264_videotoolbox -profile:v high -q:v "$vtq" -allow_sw 1 -realtime 0)
else
vopts=(-c:v libx264 -preset "$PRESET" -profile:v high -level 4.0 -crf "$crf"
-g 120 -keyint_min 24 -threads "$THREADS_SD"
-maxrate "$maxrate" -bufsize "$bufsize")
fi
fi
# ${a[@]+"${a[@]}"} : un tableau vide sous `set -u` fait planter le bash 3.2
# livré avec macOS.
ffmpeg -nostdin -hide_banner -loglevel error ${statopt[@]+"${statopt[@]}"} \
-i "$in" \
-map 0:v:0 -map 0:a:0\? \
"${vopts[@]}" \
${vf_args[@]+"${vf_args[@]}"} \
-pix_fmt yuv420p \
-fpsmax "$FPS_MAX" \
-color_primaries bt709 -color_trc bt709 -colorspace bt709 \
-c:a aac -b:a "$abr" -ac 1 -ar 48000 \
-map_metadata -1 \
-movflags +faststart \
-y "$out"
}
fsize() { stat -f%z "$1" 2>/dev/null || stat -c%s "$1"; }
# Une sortie existante n'est gardée que si ffprobe sait la lire : un encodage
# interrompu laisse un gros fichier sans index (« moov atom not found »).
# Lecture rapide, l'index est en tête de fichier grâce à +faststart.
lisible() {
[ -s "$1" ] && ffprobe -v error -show_entries format=duration -of csv=p=0 "$1" >/dev/null 2>&1
}
# --- Traitement d'un rush : les deux paliers en parallèle --------------------
process_one() {
local f="$1"
local filename base desktop_out mobile_out src_h vf_hd
filename=$(basename "$f")
base="${filename%.*}"
desktop_out="$DEST_DIR/${base}.mp4"
mobile_out="$DEST_DIR/${base}_mobile.mp4"
if [ "$FORCE" != "1" ] && lisible "$desktop_out" && lisible "$mobile_out"; then
echo "⏭️ $filename — déjà encodé (FORCE=1 pour refaire)"
return 0
fi
src_h=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=p=0 "$f" 2>/dev/null | head -1)
src_h=${src_h:-1080}
# 1080p natif conservé tel quel ; une source 2.7K/4K est ramenée à 1080p.
if [ "$src_h" -gt 1080 ]; then vf_hd="scale=-2:1080:flags=lanczos"; else vf_hd="none"; fi
echo "🎬 $filename (${src_h}p) — encodage des 2 paliers en parallèle…"
local pid_hd pid_sd rc_hd rc_sd
encode "$f" "$desktop_out" "$vf_hd" hevc "$CRF_HEVC" "$VTQ_HD" 8M 16M 96k &
pid_hd=$!
encode "$f" "$mobile_out" "scale=-2:720:flags=lanczos" h264 "$CRF_H264" "$VTQ_SD" 5M 10M 64k &
pid_sd=$!
wait "$pid_hd"; rc_hd=$?
wait "$pid_sd"; rc_sd=$?
if [ "$rc_hd" = "0" ] && [ "$rc_sd" = "0" ] && [ -s "$desktop_out" ] && [ -s "$mobile_out" ]; then
printf " ✅ %-24s %s → 1080p %s (HEVC) + 720p %s (H.264)\n" \
"$filename" "$(du -h "$f" | cut -f1)" \
"$(du -h "$desktop_out" | cut -f1)" "$(du -h "$mobile_out" | cut -f1)"
[ -n "${RT_STATS:-}" ] && \
echo "$(fsize "$f") $(( $(fsize "$desktop_out") + $(fsize "$mobile_out") ))" >> "$RT_STATS"
return 0
else
echo " ❌ Échec sur $filename"
return 1
fi
}
if [ "$INTERNAL" = "1" ]; then
process_one "$TARGET"
exit $?
fi
# =============================================================================
# MODE PRINCIPAL
# =============================================================================
if [ ! -d "$SOURCE_DIR" ]; then
echo "❌ Erreur : Dossier source $SOURCE_DIR introuvable."
exit 1
fi
mkdir -p "$DEST_DIR"
# Liste stockée plutôt que `ffmpeg … | grep -q` : le SIGPIPE de grep -q,
# combiné à pipefail, donnerait un faux « encodeur absent ».
ENCODERS_LIST="$(ffmpeg -hide_banner -encoders 2>/dev/null)"
has_enc() { printf '%s' "$ENCODERS_LIST" | grep -q "$1"; }
if [ "$ENCODER" = "vt" ]; then
has_enc "hevc_videotoolbox" || { echo "❌ hevc_videotoolbox indisponible. Utilise ENCODER=cpu."; exit 1; }
has_enc "h264_videotoolbox" || { echo "❌ h264_videotoolbox indisponible. Utilise ENCODER=cpu."; exit 1; }
else
has_enc "libx265" || { echo "❌ libx265 absent de ton ffmpeg (brew install ffmpeg)."; exit 1; }
has_enc "libx264" || { echo "❌ libx264 absent de ton ffmpeg (brew install ffmpeg)."; exit 1; }
fi
files=()
for f in "$SOURCE_DIR"/*.mp4 "$SOURCE_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
files+=("$f")
done
echo "--- COMPRESSION ROAD-TRIP (HYBRIDE HEVC / H.264) ---"
echo "📂 Source : $SOURCE_DIR"
echo "📤 Destination : $DEST_DIR"
if [ "$ENCODER" = "vt" ]; then
echo "⚙️ Encodeur : VideoToolbox (q=$VTQ_HD / $VTQ_SD)"
else
echo "⚙️ Encodeur : CPU preset=$PRESET (HEVC CRF $CRF_HEVC / H.264 CRF $CRF_H264)"
fi
echo "🧵 Parallélisme : $JOBS rush(s) à la fois × 2 paliers ($THREADS threads en 1080p, $THREADS_SD en 720p)"
echo " ($CORES cœur(s) performance détecté(s))"
echo " 1080p → HEVC (hvc1) | 720p → H.264 (avc1, lisible partout)"
echo "📼 ${#files[@]} rush(s) à traiter"
echo
if [ "${#files[@]}" -eq 0 ]; then
echo "ℹ️ Rien à faire."
exit 0
fi
RT_STATS="$(mktemp "${TMPDIR:-/tmp}/rtstats.XXXXXX")"
trap 'rm -f "$RT_STATS"' EXIT
SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
export RT_BASE_DIR="$BASE_DIR" RT_STATS
export ENCODER PRESET CRF_HEVC CRF_H264 VTQ_HD VTQ_SD FPS_MAX FORCE JOBS THREADS THREADS_SD CORES
T0=$(date +%s)
# xargs -P gère le pool de jobs : portable, y compris avec le bash 3.2 de macOS
# qui ne connaît pas `wait -n`.
printf '%s\0' "${files[@]}" | xargs -0 -P "$JOBS" -n1 "$SCRIPT_PATH" --encode-one
rc=$?
T1=$(date +%s)
ELAPSED=$((T1 - T0))
echo
if [ -s "$RT_STATS" ]; then
awk -v el="$ELAPSED" '
{ n++; s += $1; o += $2 }
END {
if (n > 0) {
printf "✅ %d vidéo(s) traitée(s) en %d min %02d s\n", n, el/60, el%60
printf "📊 Rushs : %d Mo → Web : %d Mo (%.0f%% du volume source)\n", \
s/1048576, o/1048576, o*100/s
}
}' "$RT_STATS"
else
printf "ℹ️ Aucun nouvel encodage (durée : %d min %02d s)\n" $((ELAPSED/60)) $((ELAPSED%60))
fi
echo
echo "🔍 Contrôle des tags (attendu : hvc1 pour les 1080p, avc1 pour les _mobile) :"
echo " ffprobe -v error -select_streams v:0 -show_entries stream=codec_tag_string \\"
echo " -of csv=p=0 $DEST_DIR/*.mp4"
exit $rc