Si vous possédez encore des coffrets DVD de vos séries préférées, vous savez que la numérisation peut devenir une tâche fastidieuse : insérer le disque, identifier le nom, nommer chaque épisode correctement, et recommencer pour chaque DVD du coffret.
Pour simplifier ce processus, j’ai mis au point un script Bash qui automatise l’intégralité de la chaîne, de la lecture des métadonnées à l’encodage final.
Le concept
L’objectif est de rendre la numérisation presque “invisible”. Au lieu de gérer manuellement chaque fichier, le script s’occupe de tout : de la détection du disque à l’encodage final, en passant par le nommage automatique.
Fonctionnement détaillé du script
Le script fonctionne comme une boucle infinie qui surveille votre lecteur DVD. Voici précisément ce qu’il fait étape par étape :
1. Initialisation et sécurité
Le script analyse d’abord les arguments passés en ligne de commande (comme le choix du codec, l’optimisation pour les animes, le réglage manuel de la qualité de la source et le dossier de destination). Il vérifie ensuite la présence des outils indispensables : ffmpeg, ffprobe pour l’analyse et l’encodage, lsdvd pour lire les informations du disque, eject pour gérer le plateau du lecteur, awk pour le traitement des données, mediainfo pour l’analyse des fichiers VOB en dernier recours, et la bibliothèque libdvdcss pour la lecture des disques protégés. Il identifie dynamiquement votre lecteur DVD en interrogeant le système (/proc/sys/dev/cdrom/info), ce qui le rend compatible avec la plupart des distributions Linux sans configuration manuelle.
2. Surveillance et détection du média
Le script entre dans une boucle. Il tente de lire le premier secteur du DVD avec la commande dd.
- Si aucun disque n’est présent, il attend deux secondes et recommence.
- Dès qu’un disque est détecté, il passe à l’analyse.
3. Extraction des métadonnées et analyse des flux
Le script effectue une double analyse très poussée :
- Extraction globale (
lsdvd) : Il récupère le titre du disque (Disc Title) qui servira de base de nommage des fichiers, ainsi que la liste de tous les titres vidéo disponibles (les épisodes). - Analyse des pistes par épisode (
ffprobe) : Pour chaque titre vidéo, le script interroge le lecteur avecffprobeafin de lister tous les flux audio et sous-titres disponibles, ainsi que leurs codes de langue (ex:fre,eng). Cela permet de générer dynamiquement des métadonnées pour nommer proprement chaque piste (ex: “français”, “anglais [VO]”) et de définir automatiquement le français comme piste audio par défaut si elle est présente.
4. Gestion intelligente de la numérotation
Pour éviter d’écraser des fichiers ou de se tromper dans la séquence, le script analyse le dossier de destination :
- Il compte combien de fichiers correspondant à la série et encodés avec la même nomenclature existent déjà.
- S’il trouve 4 fichiers, il sait que le prochain épisode doit être le numéro 5.
- Il formate ensuite ce numéro selon le standard
S01E##(ex:S01E05) pour assurer un tri alphabétique parfait et une compatibilité avec la plupart des gestionnaires de médias.
5. Détection de résolution et encodage sans upscaling
Pour chaque épisode, le script lance ffmpeg avec une série de réglages optimisés :
- Détermination du framerate cible : Le script analyse le
r_frame_ratede la source et calcule la fréquence d’images cible (limitée à 25 fps) pour garantir un rendu fluide et constant (CFR). - Préservation de la définition native : Contrairement aux anciennes méthodes forçant un upscaling en 720p, le script conserve la résolution native du DVD, évitant ainsi le flou et réduisant la taille des fichiers.
- Estimation et analyse robuste du bitrate : Le script implémente une stratégie d’analyse du débit binaire en plusieurs étapes (combinant
ffprobe, analyse par demuxing temporaire avecffmpeg, et extraction viamediainfo) pour calibrer au mieux le débruitage. - Filtres de traitement d’image adaptatifs : Il analyse la source pour appliquer le désentrelacement (
yadif) si nécessaire, et adapte le débruitage spatial/temporel (hqdn3d) et la netteté (unsharp) en fonction du débit global (bitrate) de la piste ou de l’option-qchoisie. - Journalisation : Il enregistre le résultat dans un fichier
.mkvet redirige les erreurs dans un fichier.errtemporaire, supprimé en cas de succès.
6. Rotation des disques
Une fois que tous les titres du DVD ont été encodés avec succès, le script commande l’éjection du disque (eject). Il revient ensuite à l’étape 2 et attend patiemment que vous insériez le DVD suivant.
Le script
Voici le script complet. Il nécessite l’installation de ffmpeg, ffprobe, lsdvd, eject, awk, mediainfo et libdvdcss.
#!/bin/bash
# --- Formatage & Couleurs ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # Pas de couleur
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_err() { echo -e "${RED}[ERROR]${NC} $1"; }
log_prompt() { echo -ne "${YELLOW}[PROMPT]${NC} $1"; }
show_help() {
cat << EOF
Usage: $(basename "$0") [options] [dossier_cible]
Encode automatiquement en boucle les titres des DVD insérés vers le format MKV (sans upscaling).
Arguments :
dossier_cible Le chemin du dossier de destination pour les fichiers MKV.
Si non spécifié, le script vous demandera de le saisir.
Options :
-h, --help Affiche cette aide et quitte.
-a, --anime Optimise l'encodage pour les dessins animés (tune=animation).
-x, --hevc Utilise le codec H.265 (HEVC) pour un meilleur ratio qualité/taille.
-q, --quality Force la qualité de la source : low (faible), medium (moyenne), high (bonne).
EOF
exit 0
}
# --- Gestion des signaux ---
cleanup_and_exit() {
echo ""
log_info "Interruption détectée (Ctrl+C). Arrêt propre en cours..."
exit 1
}
trap cleanup_and_exit SIGINT SIGTERM
# --- Fonctions de vérification et détection ---
check_dependencies() {
local deps=("ffmpeg" "ffprobe" "lsdvd" "eject" "awk" "mediainfo")
local missing=()
for dep in "${deps[@]}"; do
if ! command -v "$dep" &> /dev/null; then
missing+=("$dep")
fi
done
# Vérification de la bibliothèque libdvdcss
local libcss_found=false
for libdir in /usr/lib /usr/lib64 /usr/local/lib /lib; do
if [[ -f "${libdir}/libdvdcss.so" || -f "${libdir}/libdvdcss.so.2" ]]; then
libcss_found=true
break
fi
done
[[ "$libcss_found" == "false" ]] && missing+=("libdvdcss")
if (( ${#missing[@]} > 0 )); then
log_err "Les dépendances requises suivantes sont manquantes : ${missing[*]}"
echo "Veuillez les installer avec votre gestionnaire de paquets :"
echo " - apt/deb : sudo apt install -y lsdvd eject ffmpeg gawk libdvd-pkg && sudo dpkg-reconfigure libdvd-pkg"
echo " - pacman : sudo pacman -S lsdvd util-linux ffmpeg gawk libdvdcss"
exit 1
fi
}
detect_dvd_device() {
local device_name=$(awk '/drive name:/ {print $3}' /proc/sys/dev/cdrom/info 2>/dev/null | head -n 1)
if [[ -z "$device_name" ]]; then
log_err "Aucun lecteur DVD trouvé dans /proc/sys/dev/cdrom/info."
exit 1
fi
echo "/dev/${device_name}"
}
# --- Fonctions d'analyse et de configuration ---
get_quality_settings() {
local bitrate=$1
local force_q=$2
# Valeurs par défaut pour HQDN3D et UNSHARP
local hqdn3d=""
local unsharp=""
local msg=""
if [[ "$force_q" == "low" ]]; then
hqdn3d="3:3:6:6"; unsharp="3:3:0.8:3:3:0.8"; msg="forcée (faible/low)"
elif [[ "$force_q" == "medium" ]]; then
hqdn3d="2:2:4:4"; unsharp="3:3:0.6:3:3:0.6"; msg="forcée (moyenne/medium)"
elif [[ "$force_q" == "high" ]]; then
hqdn3d="1.5:1.5:3:3"; unsharp="3:3:0.5:3:3:0.5"; msg="forcée (bonne/high)"
else
# Sélection automatique selon le bitrate source
if [[ -n "$bitrate" && "$bitrate" -lt 2000000 ]]; then
hqdn3d="3:3:6:6"; unsharp="3:3:0.8:3:3:0.8"; msg="auto (low, bitrate < 2M)"
elif [[ -n "$bitrate" && "$bitrate" -lt 5000000 ]]; then
hqdn3d="2:2:4:4"; unsharp="3:3:0.6:3:3:0.6"; msg="auto (medium, bitrate < 5M)"
else
hqdn3d="1.5:1.5:3:3"; unsharp="3:3:0.5:3:3:0.5"; msg="auto (high, par défaut)"
fi
fi
echo "$hqdn3d|$unsharp|$msg"
}
# Tableaux globaux pour stocker les arguments de métadonnées et de mapping sans risque de splitting
GLOBAL_METADATA_ARGS=()
GLOBAL_MAP_ARGS=()
get_metadata_args() {
local device=$1
local title_id=$2
# Réinitialisation des tableaux globaux
GLOBAL_METADATA_ARGS=()
GLOBAL_MAP_ARGS=("-map" "0:v:0")
local probe_out=$(ffprobe -f dvdvideo -title "${title_id}" -i "${device}" -show_entries stream=index,codec_type:stream_tags=language -of csv=p=0 2>/dev/null || echo "")
if [[ -z "$probe_out" ]]; then
GLOBAL_MAP_ARGS=("-map" "0:v:0" "-map" "0:a?" "-map" "0:s?")
return
fi
local a_idx=0
local s_idx=0
local has_fre_audio=false
# Premier passage pour détecter l'audio français
while IFS=',' read -r idx codec_type lang; do
if [[ "$codec_type" == "audio" ]]; then
local lang_lower=$(echo "$lang" | tr '[:upper:]' '[:lower:]' | xargs)
[[ "$lang_lower" =~ ^(fre|fra|fr)$ ]] && has_fre_audio=true
fi
done <<< "$probe_out"
# Deuxième passage pour construire les arguments
while IFS=',' read -r idx codec_type lang; do
[[ -z "$codec_type" ]] && continue
local lang_lower=$(echo "$lang" | tr '[:upper:]' '[:lower:]' | xargs)
if [[ "$codec_type" == "audio" ]]; then
if [[ "$lang_lower" =~ ^(eng|en)$ ]]; then
GLOBAL_METADATA_ARGS+=("-metadata:s:a:${a_idx}" "title=anglais [VO]" "-metadata:s:a:${a_idx}" "language=eng")
[[ "$has_fre_audio" == "false" && $a_idx -eq 0 ]] && GLOBAL_METADATA_ARGS+=("-disposition:a:${a_idx}" "default") || GLOBAL_METADATA_ARGS+=("-disposition:a:${a_idx}" "0")
GLOBAL_MAP_ARGS+=("-map" "0:${idx}")
(( a_idx++ ))
elif [[ "$lang_lower" =~ ^(fre|fra|fr)$ ]]; then
GLOBAL_METADATA_ARGS+=("-metadata:s:a:${a_idx}" "title=français" "-metadata:s:a:${a_idx}" "language=fre")
GLOBAL_METADATA_ARGS+=("-disposition:a:${a_idx}" "default")
GLOBAL_MAP_ARGS+=("-map" "0:${idx}")
(( a_idx++ ))
fi
elif [[ "$codec_type" == "subtitle" ]]; then
if [[ "$lang_lower" =~ ^(fre|fra|fr)$ ]]; then
GLOBAL_METADATA_ARGS+=("-metadata:s:s:${s_idx}" "title=français" "-metadata:s:s:${s_idx}" "language=fre")
elif [[ "$lang_lower" =~ ^(eng|en)$ ]]; then
GLOBAL_METADATA_ARGS+=("-metadata:s:s:${s_idx}" "title=anglais (CC)" "-metadata:s:s:${s_idx}" "language=eng")
else
local label="${lang_lower:-sub_${s_idx}}"
GLOBAL_METADATA_ARGS+=("-metadata:s:s:${s_idx}" "title=${label}" "-metadata:s:s:${s_idx}" "language=${lang_lower:-${label}}")
fi
GLOBAL_MAP_ARGS+=("-map" "0:${idx}")
(( s_idx++ ))
fi
done <<< "$probe_out"
# Si aucune piste audio française ou anglaise n'a été trouvée, on conserve toutes les pistes audio disponibles par précaution
if [[ $a_idx -eq 0 ]]; then
log_warn "Aucune piste audio anglaise ou française détectée. Conservation de toutes les pistes audio par précaution."
a_idx=0
while IFS=',' read -r idx codec_type lang; do
[[ -z "$codec_type" ]] && continue
local lang_lower=$(echo "$lang" | tr '[:upper:]' '[:lower:]' | xargs)
if [[ "$codec_type" == "audio" ]]; then
local label="${lang_lower:-audio_${a_idx}}"
GLOBAL_METADATA_ARGS+=("-metadata:s:a:${a_idx}" "title=${label}" "-metadata:s:a:${a_idx}" "language=${lang_lower:-${label}}")
[[ $a_idx -eq 0 ]] && GLOBAL_METADATA_ARGS+=("-disposition:a:${a_idx}" "default") || GLOBAL_METADATA_ARGS+=("-disposition:a:${a_idx}" "0")
GLOBAL_MAP_ARGS+=("-map" "0:${idx}")
(( a_idx++ ))
fi
done <<< "$probe_out"
fi
}
# --- Logique d'encodage ---
process_title() {
local device=$1
local title_id=$2
local out_file=$3
local err_file=$4
local vcodec=$5
local vcrf=$6
local vpreset=$7
local vprofile=$8
local vpixfmt=$9
# On place tous les arguments dans un tableau pour un accès facile par index
local args=("$@")
local total_args=${#args[@]}
# Les 3 derniers arguments sont : target_fps, vf_chain, metadata_args (ce dernier est maintenant ignoré car on utilise le tableau global)
local metadata_args_ignored="${args[$((total_args - 1))]}"
local vf_chain="${args[$((total_args - 2))]}"
local target_fps="${args[$((total_args - 3))]}"
local tune_option="${args[$((total_args - 4))]}"
# Tout ce qui se trouve entre l'argument 10 (index 9) et le 4ème dernier est une option de codec
local vcodec_opts=()
for (( i=9; i < total_args - 4; i++ )); do
vcodec_opts+=("${args[$i]}")
done
local tune_args=()
[[ -n "$tune_option" ]] && tune_args=("-tune" "$tune_option")
ffmpeg -f dvdvideo -title "${title_id}" -i "${device}" \
"${GLOBAL_MAP_ARGS[@]}" \
-c:v "${vcodec}" -crf "${vcrf}" -preset "${vpreset}" -profile:v "${vprofile}" \
-pix_fmt "${vpixfmt}" "${vcodec_opts[@]}" "${tune_args[@]}" \
-r "${target_fps}" -vsync cfr \
-vf "${vf_chain}" \
-c:a aac -ar 44100 -b:a 128k "${GLOBAL_METADATA_ARGS[@]}" -c:s copy \
-y "${out_file}" 2> "${err_file}"
return $?
}
# --- Initialisation et Arguments ---
TUNE_OPTION="film"
WORKDIR=""
USE_HEVC=false
FORCE_QUALITY=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) show_help ;;
-a|--anime) TUNE_OPTION="animation"; shift ;;
-x|--hevc|--h265) USE_HEVC=true; shift ;;
-q|--quality)
if [[ "$2" =~ ^(low|medium|high)$ ]]; then
FORCE_QUALITY="$2"; shift 2
else
log_err "Qualité invalide : '${2:-vide}'. Utilisez low, medium ou high."; exit 1
fi
;;
*) [[ -z "$WORKDIR" ]] && WORKDIR="$1"; shift ;;
esac
done
# Configuration du codec
if $USE_HEVC; then
VCODEC="libx265"; VCRF="22"; VPRESET="slow"; VPROFILE="main10"; VPIXFMT="yuv420p10le"
VCODEC_OPTS=("-x265-params" "no-sao=1"); VCODEC_SUFFIX="x265"
[[ "$TUNE_OPTION" == "film" ]] && TUNE_OPTION=""
else
VCODEC="libx264"; VCRF="18"; VPRESET="veryslow"; VPROFILE="high"; VPIXFMT="yuv420p"
VCODEC_OPTS=(); VCODEC_SUFFIX="x264"
fi
# Gestion du dossier de travail
if [[ -z "$WORKDIR" ]]; then
log_prompt "Chemin du dossier cible (Entrée pour le dossier courant) : "
read -r WORKDIR
fi
[[ -n "$WORKDIR" && "${WORKDIR}" != */ ]] && WORKDIR="${WORKDIR}/"
[[ -n "$WORKDIR" && ! -d "$WORKDIR" ]] && { log_info "Création du dossier : $WORKDIR"; mkdir -p "$WORKDIR"; }
check_dependencies
DVD_DEVICE=$(detect_dvd_device)
log_info "Lecteur utilisé : ${DVD_DEVICE}"
# --- Boucle principale ---
while true; do
if ! dd if="${DVD_DEVICE}" of=/dev/null bs=2048 count=1 status=none 2>/dev/null; then
if [[ $(dd if="${DVD_DEVICE}" of=/dev/null bs=2048 count=1 2>&1) == *"No medium found"* ]]; then
sleep 2; continue
else
log_err "Erreur lecture disque. Attente..."; sleep 5; continue
fi
fi
log_info "Disque détecté. Analyse..."
DVD_INFOS=$(lsdvd "${DVD_DEVICE}" 2>/dev/null | grep -v '00:00' || true)
[[ -z "$DVD_INFOS" ]] && { log_warn "Impossible de lire le DVD. Attente..."; sleep 5; continue; }
MEDIA_NAME=$(awk -F': ' '/^Disc Title/ {print $2}' <<< "$DVD_INFOS" | xargs)
MEDIA_TITLE_ID=$(awk -F'[:,]' '/^Title:/ {print $2}' <<< "$DVD_INFOS" | tr -d ' ')
if [[ -z "$MEDIA_NAME" || -z "$MEDIA_TITLE_ID" ]]; then
log_warn "Infos incomplètes. Éjection..."; eject "${DVD_DEVICE}"; continue
fi
log_info "Média : ${MEDIA_NAME}"
# Extraction de la saison
SEASON_STR=""
if [[ "$MEDIA_NAME" =~ S([0-9]+) ]]; then
SEASON_STR=$(printf "S%02d" "${BASH_REMATCH[1]}")
log_info "Saison détectée : ${SEASON_STR}"
else
log_prompt "Saison pour ce DVD (ex: 1 ou S01) : "
read -r USER_SEASON
if [[ "$USER_SEASON" =~ ([0-9]+) ]]; then
SEASON_STR=$(printf "S%02d" "${BASH_REMATCH[1]}")
else
SEASON_STR="S01"
log_warn "Saison invalide ou non spécifiée. Utilisation de S01 par défaut."
fi
log_info "Saison utilisée : ${SEASON_STR}"
fi
# Calcul de l'épisode de départ
EPISODE_ID=$(find "${WORKDIR:-.}" -maxdepth 1 -type f -name "${MEDIA_NAME} - ${SEASON_STR}E*.mkv" 2>/dev/null | wc -l)
for i in ${MEDIA_TITLE_ID}; do
(( EPISODE_ID++ ))
EP_STR=$(printf "E%02d" "${EPISODE_ID}")
OUT_FILE="${WORKDIR}${MEDIA_NAME} - ${SEASON_STR}${EP_STR} [MULTi DVD][AAC 2.0][${VCODEC_SUFFIX}].mkv"
ERR_FILE="${WORKDIR}${MEDIA_NAME} - ${SEASON_STR}${EP_STR} [MULTi DVD][AAC 2.0][${VCODEC_SUFFIX}].err"
log_info "Traitement du Titre ${i} -> ${OUT_FILE}"
# Analyse vidéo
SRC_VIDEO_PROPS=$(ffprobe -v error -f dvdvideo -title "${i}" -i "${DVD_DEVICE}" -select_streams v:0 -show_entries stream=field_order,r_frame_rate,bit_rate -of default=nw=1 2>/dev/null || echo "")
SRC_FIELD_ORDER=$(echo "$SRC_VIDEO_PROPS" | awk -F'=' '/field_order/ {print $2}')
SRC_FPS_RAW=$(echo "$SRC_VIDEO_PROPS" | awk -F'=' '/r_frame_rate/ {print $2}')
# Bitrate
SRC_BITRATE=$(echo "$SRC_VIDEO_PROPS" | awk -F'=' '/bit_rate/ {v=$2} END {if (v~/^[0-9]+$/) print v}')
if [[ -z "$SRC_BITRATE" ]]; then
# Estimation rapide du bitrate via ffmpeg (demuxing de 2s, gère correctement le CSS)
SRC_BITRATE_KBPS=$(ffmpeg -f dvdvideo -title "${i}" -i "${DVD_DEVICE}" -t 2 -c copy -map 0:v:0 -f mpegts -y /dev/null 2>&1 | grep -o 'bitrate=[0-9.]\+' | tail -n 1 | cut -d'=' -f2)
if [[ -n "$SRC_BITRATE_KBPS" ]]; then
KBPS_INT=$(echo "$SRC_BITRATE_KBPS" | cut -d'.' -f1)
[[ "$KBPS_INT" =~ ^[0-9]+$ ]] && SRC_BITRATE=$(( KBPS_INT * 1000 ))
fi
fi
if [[ -z "$SRC_BITRATE" ]]; then
# Tentative avec ffprobe (fallback stderr standard)
SRC_BITRATE_KBPS=$(ffprobe -f dvdvideo -title "${i}" -i "${DVD_DEVICE}" 2>&1 | awk '/bitrate: [0-9]+ kb\/s/ {for(j=1;j<=NF;j++) if ($j=="bitrate:") {print $(j+1); exit}}')
[[ "$SRC_BITRATE_KBPS" =~ ^[0-9]+$ ]] && SRC_BITRATE=$(( SRC_BITRATE_KBPS * 1000 ))
fi
# Si toujours vide, on tente d'utiliser mediainfo sur le premier VOB trouvé (en montant le DVD si nécessaire)
if [[ -z "$SRC_BITRATE" ]]; then
MOUNT_POINT=$(lsblk -no MOUNTPOINT "${DVD_DEVICE}" 2>/dev/null | head -n 1)
WAS_MOUNTED=true
if [[ -z "$MOUNT_POINT" ]]; then
WAS_MOUNTED=false
log_info "Montage du DVD pour analyse des fichiers VOB..."
udisksctl mount -b "${DVD_DEVICE}" &>/dev/null
MOUNT_POINT=$(lsblk -no MOUNTPOINT "${DVD_DEVICE}" 2>/dev/null | head -n 1)
fi
if [[ -n "$MOUNT_POINT" ]]; then
# Chercher le premier fichier .VOB (>500MB) dans VIDEO_TS pour éviter les menus
FIRST_VOB=$(find "${MOUNT_POINT}/VIDEO_TS" -name "*.VOB" -size +500M 2>/dev/null | head -n 1)
if [[ -n "$FIRST_VOB" ]]; then
MI_BITRATE=$(mediainfo --Inform="General;%OverallBitRate%" "${FIRST_VOB}" 2>/dev/null)
# Si mediainfo retourne une valeur déraisonnable (> 10 Mbps) due à l'encryption CSS, on l'ignore
if [[ "$MI_BITRATE" =~ ^[0-9]+$ && "$MI_BITRATE" -lt 10000000 ]]; then
SRC_BITRATE="$MI_BITRATE"
fi
fi
# Démontage si c'est ce script qui l'a monté pour l'analyse
if [[ "$WAS_MOUNTED" == "false" ]]; then
udisksctl unmount -b "${DVD_DEVICE}" &>/dev/null
fi
fi
fi
# Framerate cible
TARGET_FPS=25
if [[ -n "$SRC_FPS_RAW" && "$SRC_FPS_RAW" =~ ^[0-9]+/[0-9]+$ ]]; then
S_NUM=$(echo "$SRC_FPS_RAW" | cut -d'/' -f1); S_DEN=$(echo "$SRC_FPS_RAW" | cut -d'/' -f2)
[[ $S_DEN -gt 0 ]] && { S_INT=$(( S_NUM / S_DEN )); [[ $S_INT -le 25 && $S_INT -gt 0 ]] && TARGET_FPS="$S_INT"; }
fi
# Qualité et Filtres
QUALITY_DATA=$(get_quality_settings "$SRC_BITRATE" "$FORCE_QUALITY")
HQDN3D=$(echo "$QUALITY_DATA" | cut -d'|' -f1)
UNSHARP=$(echo "$QUALITY_DATA" | cut -d'|' -f2)
QUALITY_MSG=$(echo "$QUALITY_DATA" | cut -d'|' -f3)
IS_INTERLACED=false
[[ "$SRC_FIELD_ORDER" =~ ^(tt|bb|tb|bt)$ ]] && IS_INTERLACED=true
if $IS_INTERLACED; then
VF_CHAIN="yadif=mode=0:parity=-1:deint=0,hqdn3d=${HQDN3D},unsharp=${UNSHARP}"
else
VF_CHAIN="hqdn3d=${HQDN3D},unsharp=${UNSHARP}"
fi
# Métadonnées
get_metadata_args "${DVD_DEVICE}" "${i}"
log_info "Sorce: ${SRC_FIELD_ORDER:-?}, fps: ${SRC_FPS_RAW:-?}, bitrate: ${SRC_BITRATE:-?} b/s"
log_info "Paramètres: entrelacé=${IS_INTERLACED}, fps=${TARGET_FPS}, qualité=${QUALITY_MSG}, vf='${VF_CHAIN}'"
# Exécution de l'encodage
# On passe le tableau VCODEC_OPTS complet pour ne pas perdre d'arguments
process_title "${DVD_DEVICE}" "${i}" "${OUT_FILE}" "${ERR_FILE}" \
"${VCODEC}" "${VCRF}" "${VPRESET}" "${VPROFILE}" "${VPIXFMT}" \
"${VCODEC_OPTS[@]}" "${TUNE_OPTION}" "${TARGET_FPS}" "${VF_CHAIN}" "placeholder_metadata"
if [[ $? -eq 0 ]]; then
log_info "Succès : ${OUT_FILE}"; rm -f "${ERR_FILE}"
else
log_err "Échec du Titre ${i}. Voir ${ERR_FILE}"
fi
done
log_info "Terminé pour ${MEDIA_NAME}. Éjection..."
eject "${DVD_DEVICE}"
sleep 5
done
Son algorithme
Détails techniques et choix d’encodage
Le script utilise ffmpeg et ffprobe avec des paramètres dynamiques selon les options choisies, afin de maximiser la qualité tout en gardant des fichiers de taille raisonnable :
- Préservation de la résolution native : Le script détecte la hauteur exacte du flux vidéo (576 lignes pour le PAL, 480 pour le NTSC) et évite tout upscaling artificiel. L’encodage est ainsi plus rapide et le stockage plus efficace.
- Estimation robuste du bitrate source : Pour calibrer parfaitement le débruitage adaptatif (
hqdn3d), le script met en œuvre une stratégie d’analyse robuste en 4 étapes :- Lecture directe des métadonnées du flux vidéo via
ffprobe. - En cas d’échec (fréquent sur les disques chiffrés CSS), extraction et demuxing d’un échantillon de 2 secondes avec
ffmpegpour mesurer précisément le débit réel. - Si besoin, analyse complémentaire via l’extraction brute de l’en-tête
ffprobe. - En ultime recours, montage temporaire du DVD (via
udisksctl) et analyse directe du premier fichier VOB principal avecmediainfo(avec filtrage des valeurs aberrantes de débit dues à la protection anti-copie).
- Lecture directe des métadonnées du flux vidéo via
- Chaîne de filtres vidéo adaptative (désentrelacement, débruitage, netteté) : Le script analyse la vidéo source pour appliquer les traitements de manière intelligente et sur mesure :
yadif(Désentrelacement dynamique) : Appliqué uniquement si la source est détectée comme entrelacée. Si la source est progressive, ce filtre est ignoré pour éviter toute dégradation.hqdn3d(Débruitage spatial et temporel) : L’intensité du débruitage s’adapte automatiquement au débit (bitrate) de la source (ou au switch-qmanuel). Un débruitage fort (3:3:6:6) est appliqué pour les sources <2 Mbps, modéré (2:2:4:4) pour les sources <5 Mbps, et léger (1.5:1.5:3:3) pour les sources propres.unsharp(Accentuation de netteté) : Compense l’effet de flou inhérent au débruitage en redonnant un piqué net et naturel aux contours de l’image (modulé de0.5à0.8selon la qualité).
- Codecs Vidéo (H.264 8-bit ou H.265 10-bit) :
- Par défaut (H.264) : Utilise
libx264avec uncrfde 18 et le presetveryslowpour garantir une compatibilité universelle et une qualité visuelle irréprochable. - Option HEVC (H.265,
-x) : Utilise le codeclibx265avec uncrfde 22, le presetslowet une configuration en 10-bit (profile:v main10et format de pixelyuv420p10le). L’encodage en 10-bit réduit les effets de banding et améliore la compression. Le paramètreno-sao=1est activé pour préserver le grain et les textures fines.
- Par défaut (H.264) : Utilise
- Profil d’encodage (Anime/Film) : L’option
-aou--animeactive le réglage-tune animationpour optimer le rendu des dessins animés. - Gestion audio multilingue intelligente : Le script conserve uniquement les flux audio en anglais et en français (stéréo mixdown en AAC 128k) et toutes les pistes de sous-titres (
-c:s copy), avec une conservation de secours de tous les flux audio si aucun flux anglais ou français n’est présent. Il utiliseffprobepour détecter les langues et injecter des métadonnées de titre explicites (ex: “français”, “anglais [VO]”), tout en définissant automatiquement le français comme piste par défaut.
Note technique : Pourquoi le respect de la résolution native et pas l’upscaling ?
On pourrait être tenté de faire de l’upscaling (mise à l’échelle forcée en 720p ou 1080p) pour “améliorer” la qualité d’un vieux DVD, mais c’est une hérésie technique. La résolution native d’un DVD PAL est de 720x576 pixels (ou 720x480 en NTSC).
Forcer un agrandissement artificiel de l’image ne crée aucun nouveau détail. Cela oblige simplement l’encodeur à traiter et stocker des pixels interpolés (flous), augmentant inutilement la taille du fichier final et le temps d’encodage.
La meilleure approche consiste à conserver l’image dans sa définition native, en supprimant l’entrelacement (yadif), en réduisant le bruit source (hqdn3d) et en appliquant ensuite une légère accentuation de la netteté (unsharp). C’est ensuite à votre diffuseur (lecteur vidéo de votre TV, Plex ou VLC) d’effectuer la mise à l’échelle matérielle en temps réel lors du visionnage.
Ressources et Documentation
Pour aller plus loin dans la configuration de l’encodage ou comprendre le fonctionnement des outils, vous pouvez consulter les documentations officielles :
- Documentation FFmpeg : Pour tous les détails sur les codecs et les filtres vidéo.
- Guide d’encodage H.264 (FFmpeg Wiki) : Pour approfondir les options et les paramètres de l’encodeur libx264.
- Guide d’encodage H.265/HEVC (FFmpeg Wiki) : Pour optimiser l’efficacité de vos encodages avec libx265.
- Page de manuel lsdvd : Pour comprendre l’extraction des titres et des chapitres DVD.
- Projet libdvdcss : Indispensable pour la lecture des DVD commerciaux protégés.
Utilisation
- Lancez le script en spécifiant le dossier cible et vos options d’encodage. Par exemple, pour un dessin animé avec la compression H.265, et une qualité de source forcée à basse (
low) pour nettoyer un vieux DVD bruité :./dvd2mkv_ffmpeg.sh -a -x -q low /chemin/vers/dossier - Si vous n’avez pas spécifié de dossier cible en argument, le script vous demandera de le saisir.
- Insérez un DVD.
- Le script travaille (analyse, nommage et encodage automatique), puis éjecte le disque.
- Insérez le DVD suivant.
C’est la solution idéale pour ceux qui ont des dizaines de coffrets à traiter sans vouloir passer des heures à renommer manuellement chaque fichier .mkv. Et surtout, c’est le bonheur pour ceux qui ont la flemme de se lever toutes les heures pour changer le disque lors de la numérisation : vous n’avez plus qu’à attendre l’éjection et à revenir quand vous le souhaitez !
L’avantage final est surtout pour le visionnage : une fois votre collection numérisée, fini la corvée d’aller chercher le coffret dans l’étagère et de changer manuellement le DVD pour passer à l’épisode suivant. Tout est là, prêt à être lancé en un clic.