Files
2026-08-17 23:50:11 +02:00

178 lines
7.0 KiB
Bash
Executable File
Raw Permalink 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
# =============================================================================
# CALIBRATION CRF — HEVC
#
# Encode un même extrait de rush à plusieurs CRF et mesure la qualité
# objective (VMAF si dispo, sinon SSIM) + la taille obtenue.
# Objectif : choisir CRF_HD / CRF_SD sur TES images plutôt qu'au doigt mouillé.
#
# Usage :
# ./bench_crf.sh rushs/GH011182.MP4 [start_sec] [duree_sec]
#
# Variables :
# CODEC=hevc palier 1080p HEVC (défaut)
# CODEC=h264 palier 720p H.264
# HEIGHT=1080 hauteur cible du test (1080 ou 720)
# CRFS="22 24 26 28 30 32"
# PRESET=medium
# ENCODER=cpu|vt (en mode vt, CRFS est interprété comme des valeurs -q:v)
# KEEP=1 conserve les fichiers encodés pour comparaison visuelle
#
# Les deux paliers du site se calibrent séparément — les échelles de CRF
# de x264 et x265 ne sont PAS comparables :
# ./bench_crf.sh rushs/GH011182.MP4 30 20 # 1080p HEVC
# CODEC=h264 HEIGHT=720 CRFS="20 22 24 26" ./bench_crf.sh rushs/GH011182.MP4 30 20
#
# Choisis un extrait REPRÉSENTATIF : du mouvement, de la végétation, du
# ciel dégradé. Un plan fixe sur un mur donnera un CRF trop optimiste.
# =============================================================================
set -uo pipefail
SRC="${1:?Usage: ./bench_crf.sh <fichier_rush> [start_sec] [duree_sec]}"
START="${2:-30}"
DUR="${3:-20}"
CODEC="${CODEC:-hevc}"
HEIGHT="${HEIGHT:-1080}"
if [ "$CODEC" = "h264" ]; then
CRFS="${CRFS:-18 20 22 24 26 28}"
else
CRFS="${CRFS:-22 24 26 28 30 32}"
fi
PRESET="${PRESET:-medium}"
ENCODER="${ENCODER:-cpu}"
KEEP="${KEEP:-0}"
[ -f "$SRC" ] || { echo "❌ Fichier introuvable : $SRC"; exit 1; }
WORK="$(mktemp -d "${TMPDIR:-/tmp}/bench_crf.XXXXXX")"
cleanup() { [ "$KEEP" = "1" ] || rm -rf "$WORK"; }
trap cleanup EXIT
# --- Détection de la métrique disponible -------------------------------------
FILTERS_LIST="$(ffmpeg -hide_banner -filters 2>/dev/null)"
if printf '%s' "$FILTERS_LIST" | grep -q "libvmaf"; then
METRIC="vmaf"
else
METRIC="ssim"
echo "️ libvmaf absent de ton ffmpeg → repli sur SSIM."
echo " (VMAF est bien plus parlant : brew install ffmpeg l'inclut normalement)"
fi
# --- Référence : extrait copié sans réencodage -------------------------------
REF="$WORK/ref.mp4"
echo "✂️ Extraction de la référence (${DUR}s à partir de ${START}s)…"
ffmpeg -nostdin -hide_banner -loglevel error -y \
-ss "$START" -i "$SRC" -t "$DUR" \
-map 0:v:0 -c copy -tag:v hvc1 "$REF" 2>/dev/null
if [ ! -s "$REF" ]; then
echo "⚠️ Copie directe impossible, réencodage sans perte visible de la référence…"
ffmpeg -nostdin -hide_banner -loglevel error -y \
-ss "$START" -i "$SRC" -t "$DUR" \
-map 0:v:0 -c:v libx265 -preset ultrafast -x265-params lossless=1 \
-tag:v hvc1 "$REF"
fi
# Si on teste une hauteur inférieure à la source, la référence de comparaison
# doit être à la MÊME définition que le test : on fabrique une référence scalée.
SRC_H=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=p=0 "$REF" | head -1)
REF_CMP="$REF"
if [ "$SRC_H" != "$HEIGHT" ]; then
REF_CMP="$WORK/ref_${HEIGHT}.mp4"
echo "📐 Référence de comparaison rescalée en ${HEIGHT}p…"
ffmpeg -nostdin -hide_banner -loglevel error -y -i "$REF" \
-vf "scale=-2:${HEIGHT}:flags=lanczos" \
-c:v libx265 -preset ultrafast -x265-params lossless=1 -tag:v hvc1 "$REF_CMP"
fi
REF_DUR=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$REF" | cut -d. -f1)
REF_DUR=${REF_DUR:-$DUR}
echo
echo "🎯 Palier testé : ${HEIGHT}p en $(echo "$CODEC" | tr a-z A-Z)"
printf "%-6s %10s %12s %10s %10s\n" "CRF" "Taille" "Débit" "$(echo "$METRIC" | tr a-z A-Z)" "Encodage"
printf "%s\n" "--------------------------------------------------------"
BEST_LINE=""
for crf in $CRFS; do
OUT="$WORK/test_crf${crf}.mp4"
if [ "$CODEC" = "h264" ]; then
if [ "$ENCODER" = "vt" ]; then
VOPTS=(-c:v h264_videotoolbox -profile:v high -q:v "$crf" -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)
fi
else
if [ "$ENCODER" = "vt" ]; then
VOPTS=(-c:v hevc_videotoolbox -profile:v main -q:v "$crf" -allow_sw 1 -realtime 0 -tag:v hvc1)
else
VOPTS=(-c:v libx265 -preset "$PRESET" -profile:v main -crf "$crf"
-x265-params "keyint=120:min-keyint=24:aq-mode=3" -tag:v hvc1)
fi
fi
T0=$(date +%s)
ffmpeg -nostdin -hide_banner -loglevel error -y -i "$REF" \
-map 0:v:0 "${VOPTS[@]}" \
-vf "scale=-2:${HEIGHT}:flags=lanczos" \
-pix_fmt yuv420p \
-color_primaries bt709 -color_trc bt709 -colorspace bt709 \
-an "$OUT" 2>/dev/null
T1=$(date +%s)
ENC_TIME=$((T1 - T0))
[ -s "$OUT" ] || { printf "%-6s %10s\n" "$crf" "ÉCHEC"; continue; }
BYTES=$(stat -f%z "$OUT" 2>/dev/null || stat -c%s "$OUT")
MB=$(awk -v b="$BYTES" 'BEGIN{printf "%.1f", b/1048576}')
KBPS=$(awk -v b="$BYTES" -v d="$REF_DUR" 'BEGIN{printf "%.0f", b*8/1000/d}')
# --- Mesure de qualité ---
if [ "$METRIC" = "vmaf" ]; then
SCORE=$(ffmpeg -nostdin -hide_banner -loglevel info -i "$OUT" -i "$REF_CMP" \
-lavfi "[0:v]setpts=PTS-STARTPTS[d];[1:v]setpts=PTS-STARTPTS[r];[d][r]libvmaf" \
-f null - 2>&1 | grep -oE "VMAF score: [0-9.]+" | tail -1 | grep -oE "[0-9.]+")
SCORE=$(awk -v s="${SCORE:-0}" 'BEGIN{printf "%.2f", s}')
else
SCORE=$(ffmpeg -nostdin -hide_banner -loglevel info -i "$OUT" -i "$REF_CMP" \
-lavfi "[0:v]setpts=PTS-STARTPTS[d];[1:v]setpts=PTS-STARTPTS[r];[d][r]ssim" \
-f null - 2>&1 | grep -oE "All:[0-9.]+" | tail -1 | cut -d: -f2)
SCORE=$(awk -v s="${SCORE:-0}" 'BEGIN{printf "%.4f", s}')
fi
printf "%-6s %9s M %9s k %10s %8s s\n" "$crf" "$MB" "$KBPS" "$SCORE" "$ENC_TIME"
done
echo
echo "📖 Comment lire ce tableau"
if [ "$METRIC" = "vmaf" ]; then
cat <<'EOF'
VMAF ≥ 95 : indiscernable de la source à distance normale
VMAF 90-95 : excellent, cible raisonnable pour un site de voyage
VMAF 85-90 : bon, quelques artefacts visibles en plein écran
VMAF < 85 : dégradation nette sur les scènes chargées
→ Prends le CRF le plus ÉLEVÉ qui reste au-dessus de ton seuil.
Le gain de taille entre deux CRF consécutifs s'écrase vite ;
dès que la courbe de qualité plonge, tu es allé trop loin.
EOF
else
cat <<'EOF'
SSIM ≥ 0.98 : très bon
SSIM 0.96-0.98 : correct pour du web
SSIM < 0.95 : dégradation visible
→ Prends le CRF le plus ÉLEVÉ qui reste au-dessus de ton seuil.
⚠️ SSIM est moins fiable que VMAF sur des images très texturées
(feuillage, eau, gravier) : vérifie AUSSI à l'œil avec KEEP=1.
EOF
fi
if [ "$KEEP" = "1" ]; then
echo
echo "📁 Fichiers conservés dans : $WORK"
echo " Compare à l'œil, en plein écran, sur les plans les plus chargés."
fi