Script pour un beau transcodage en MPEG-4 AVC

../../../../2010/05/01/script-pour-un-beau-transcodage-en-mpeg-4-avc/
Antoine Van-Elstraete (antoine@van-elstraete.net)
dimanche 13 novembre 2016





8 min.

Image d'illustration

Mise à jour du 12 novembre 2016 :

J’ai écrit un script python pour convertir mes vidéos,

  • vidéo en h.264
  • audio en opus
  • sous-titres
  • conteneur mkv
#!/bin/python3.5
import subprocess
import argparse
import logging


parser = argparse.ArgumentParser()
parser.add_argument("infile")
parser.add_argument("outfile")
parser.add_argument("--debug", dest='debug', action='store_true')
parser.add_argument("--logfile", dest='logfile', action='store_true')
parser.set_defaults(debug=False, logfile=False)
args = parser.parse_args()

if args.debug:
    logging.basicConfig(format='[%(asctime)s] %(message)s', level=logging.DEBUG, datefmt='%d/%m/%Y %H:%M:%S')
else:
    logging.basicConfig(format='[%(asctime)s] %(message)s', level=logging.INFO, datefmt='%d/%m/%Y %H:%M:%S')

fichier = str(args.infile)
s_fichier = str(args.outfile)

if args.logfile:
    logfile_name = fichier + ".log"
    logging.basicConfig(filename=logfile_name, filemode='w', level=logging.DEBUG)

video_param, audio_param, subtitle_param = [], [], []

audio_codec = "libopus"
video_codec = "libx264 -crf 20.5 -profile:v high -level 4.1 -preset slower -pix_fmt yuv420p"

'''                                                                                                                                                   [91/139]
Infos générales
'''
infos_cmd = "ffmpeg -loglevel info -i '" + fichier + "'"
logging.info("\033[32m>>> Recherche des pistes dans le fichier...\033[0m")
infos = subprocess.getoutput(infos_cmd).splitlines()
video_stream, audio_stream, subtitle_stream, other_stream = [], [], [], []
for line in infos:
    if "Stream" in line:
        if "Video" in line:
            video_stream.append(line)
            logging.debug(line)
        elif "Audio" in line:
            audio_stream.append(line)
            logging.debug(line)
        elif "Subtitle" in line:
            subtitle_stream.append(line)
            logging.debug(line)
        else:
            other_stream.append(line)
            logging.debug(line)

'''
interlaced ?
'''

logging.info("\033[32m>>> Recherche des trames entrelacées...\033[0m")
interlaced = subprocess.getoutput("ffmpeg -loglevel info -i '" + fichier + "' -t 300 -filter:v idet -f null -y /dev/null")
for line in interlaced.splitlines():
    if "Multi" in line:
        line = line.split()
        TFF = int(line[(line.index("TFF:") + 1)])
        BFF = int(line[(line.index("BFF:") + 1)])
        Progressive = int(line[(line.index("Progressive:") + 1)])
if (TFF + BFF) > Progressive:
    deinterlace = True
    logging.debug("    Trames entrelacées (E=" + str(TFF + BFF) + " > P=" + str(Progressive) + ")")
else:
    deinterlace = False
    logging.debug("    Trames progressives (E=" + str(TFF + BFF) + " ≤ P=" + str(Progressive) + ")")



'''                                                                                                                                                   [48/139]
crop
'''
video_map = (video_stream[0].split('#')[1].split('(')[0].split('[')[0].split(':')[1])
cropdetect_cmd = "ffmpeg -loglevel info -i '" + fichier + "' -map 0:" + video_map + " -t 900 -f null -vf cropdetect -y /dev/null"
logging.info("\033[32m>>> Détection de la découpe de l'image...\033[0m")
cropsize = subprocess.getoutput(cropdetect_cmd).splitlines()[-3].split()[-1]
logging.debug("    Paramétres de découpe : " + cropsize.split('=')[1])
if deinterlace:
    filter_chain = "yadif," + cropsize
else:
    filter_chain = cropsize
video_param.append(
        " -map 0:"
        + video_map
        + " -c:v "
        + video_codec
        + " -vf "
        + filter_chain
        + " "
        )

'''                                                                                                                                                   [26/139]
volume
'''
for audio in audio_stream:
    audio_map = (audio.split('#')[1].split('(')[0].split('[')[0].split(":")[1])
    logging.info("\033[32m>>> Détection du volume de la piste audio "
            + audio_map + "...\033[0m")
    volume_detect_cmd = "ffmpeg -loglevel info -i '" + fichier + "' -map 0:" + audio_map + " -af volumedetect -f null -y /dev/null"
    volumedetect = subprocess.getoutput(volume_detect_cmd)
    for line in volumedetect.splitlines():
        if "max_volume" in line:
            volume = line.split()[-2]
    volume = str(-float(volume)) + "dB"
    logging.debug("    Ajustement du volume de " + volume)
    aid = str(int(audio_map) - 1)
    audio_param.append(
            " -map 0:"
            + audio_map
            + " -c:a:"
            + aid
            + " "
            + audio_codec
            + " -filter:a:"
            + aid
            + " volume="
            + volume
            + " "
            )
'''
sous titres
'''
for sub in subtitle_stream:
    sub_map = (sub.split('#')[1].split('(')[0].split('[')[0].split(":")[1])
    subtitle_param.append(" -map 0:" + sub_map + " -c:s copy ")


'''
ffmpeg
'''
if args.logfile:
    ffmpeg_cmd = "ffmpeg -hide_banner -loglevel warning -nostats -i "
elif args.debug:
    ffmpeg_cmd = "ffmpeg -hide_banner -loglevel warning -stats -i "
else:
    ffmpeg_cmd = "ffmpeg -hide_banner -loglevel error -stats -i "
ffmpeg_cmd += "" + fichier + ""
for line in video_param:
    ffmpeg_cmd += line
for line in audio_param:
    ffmpeg_cmd += line
for line in subtitle_param:
    ffmpeg_cmd += line
ffmpeg_cmd += "-y "
ffmpeg_cmd += s_fichier + ""

logging.debug("    ==========")
logging.debug("\033[1m" + ffmpeg_cmd + "\033[0m")
logging.debug("    ==========")

logging.info("\033[32m>>> Transcodage avec ffmpeg...\033[0m")

with subprocess.Popen(ffmpeg_cmd.split(), stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) as p:
    for line in p.stdout:
        logging.info(line)

logging.info("\033[32m>>> Traitement de %s terminé.\033[0m", fichier)

'''
mediainfo
'''
if args.logfile:
    logging.info(">>> Mediainfo :\033[0m")
    logging.info(subprocess.getoutput('mediainfo \'' + s_fichier + '\''))

Modification du 07 juin 2014 :

ffmpeg -i "FILM.mkv" -i "SOUS-TITRES.srt" -metadata title="Titre du film" -map 0:0 -vf crop=000:000:00:00 -c:v libx264 -crf 18 -preset veryslow -tune film -profile:v high -level 4.1 -maxrate 48M -bufsize 36M -map 0:2 -af:a:0 volume=+6dB -c:a:0 ac3 -b:a:0 192k  -metadata:s:a:0 language=fre -metadata:s:a:0 description="DVD AC3 2.0" -map 0:1 -af:a:1 volume=+6dB -c:a:1 ac3 -b:a:1 384k -metadata:s:a:1 language=eng  -metadata:s:a:1 description="DVD AC3 5.1" -metadata:s:v:0 description="DVD  MPEG2 576p" -map 1:0 -c:s copy -metadata:s:s:0 language=fre -metadata:s:s:0 description="Import sous-titres" -metadata:s:s:0 title="Sous-titres FR par ..." -y "H264/FILM.mkv"

Modification du 08 avril 2014 :

J’utilise désormais ffmpeg avec :

ffmpeg -i BLURAY.m2ts -vf scale=-1:720 -sws_flags lanczos -map 0:0 -map 0:3 -map 0:1 -c:v libx264 -crf 22 -preset veryslow -tune film -threads 4 -af volume=+6dB -c:a libfdk_aac -cutoff 20000 -b:a 384k -y FILM.mkv

Pour un DVD, un crf de 19 rend mieux. Pour le bitrate audio, j’utilise entre 64 et 96 (selon le film), multiplié par le nombre de canaux (soit 384k à 576k pour du 5.1) avec le codec libfdk_aac. Lire la doc de ffmpeg pour les autres options.

Article original (mai 2010)

Et voilà, j’ai enfin terminé mon script qui permet de transcoder n’importe quelle vidéo (du moment qu’elle soit lisible par mplayer) au format MPEG-4 AVC. C’est le nouveau format à la mode, comme le fut le DivX il y a quelques années. Donc la vidéo se retrouve au format H.264 et le son en AAC. Question qualité, c’est du joli tout de même et ça gère la HD (720 ou 1080) sans soucis. Après pour ce qui est de la lecture n’importe quelle lecteur multimédia qui connait le standard MPEG-4 AVC le gère très bien. Côté matériel, pas trop de soucis non plus et la Freebox HD[1] le lit sans soucis. Personnellement j’utilise ce script pour transcoder mes DVD afin de les lire sur la Freebox HD via le protocole UPNP.

Ce script dépend de mplayer (avec mencoder), de faac et de mkvmerge.

Notes

[1] http://www.free.fr

Le script

#!/bin/bash
# Script pour encoder une vidéo en H.264 + AAC (MPEG-4 AVC)
# Ici 'FICHIER' désigne le nom de la vidéo # Le script est simple : ./encodeur.sh Vidéo.avi
# Vous pouvez changer la qualité de la vidéo grâce au paramètre "crf=" dans la ligne de mencoder
# De même la qualité du son avec "-q " das la ligne de faac
# Personnellement, si vous encodez un DVD ou un Blu-ray, je vous conseille de conserver la piste DTS ou AC3 !
# Ce script est sous licence Licence WTFPL
# plus d'information : http://fr.wikipedia.org/wiki/WTF_Public_License
# De plus je décline toute responsabilité si ce script provoque divers effets comme par exemple
# le réchauffement climatique, un accroissement du montant de votre facture d'électricité,
# si votre femme (ou par corolaire votre homme) vous quitte, si votre ordinateur prend feu, etc.

FICHIER=$1 FICHIER=${FICHIER%.*} # On récupère juste le nom de fichier
echo "Transcodage de $1"
mkdir "$FICHIER" #On crée un répertoire temporaire pour le travail
#On extrait l'audio
echo -n "Étape 1 : extraction audio ; en cours..."
mplayer -ao pcm:file="$FICHIER/$FICHIER.wav" -vo null -vc dummy "$1" &>/dev/null
echo -e "\b\b\b\b\b\b\b\b\b\b\bterminée"
#Encodage en AAC
echo -n "Étape 2 : encodage audio ; en cours..."
faac -q 350 -o "$FICHIER/$FICHIER.aac" "$FICHIER/$FICHIER.wav" &>/dev/null
rm "$FICHIER/$FICHIER.wav" #On supprime le wav temporaire
echo -e "\b\b\b\b\b\b\b\b\b\b\bterminée"
echo -n "Étape 3 : encodage vidéo ; en cours..." #On convertit la vidéo (et avec des supers paramètres qui ont coûté bien du temps et des cycles CPU en R&D ! ^_^ )
mencoder -nosound -nosubcc -ovc x264 -x264encopts frameref=6:bframes=3:deblock:cabac:weight_b:partitions=all:8x8dct:me=umh:subq=7:mixed_refs:threads=auto:crf=21 -vf hqdn3d -o "$FICHIER/$FICHIER.264" "$1" &>/dev/null
echo -e "\b\b\b\b\b\b\b\b\b\b\bterminée"
#Bon si on transcode depuis un mkv, faudrait pas ré-écrire par dessus...
if [ -f '$FICHIER.mkv' ];
    then
    FINAL=$FICHIER.new
    else
    FINAL=$FICHIER
fi
echo -n "Étape 4 : audio et vidéo fusionnés ; en cours..."
#On merge en matroska
mkvmerge -o "$FINAL.mkv" "$FICHIER/$FICHIER.aac" "$FICHIER/$FICHIER.264" &>/dev/null
echo -e "\b\b\b\b\b\b\b\b\b\b\bterminée"
#On nettoie le bazar
rm "$FICHIER/$FICHIER.aac"
rm "$FICHIER/$FICHIER.264"
rmdir "$FICHIER"
echo "Le fichier $FINAL.mkv est prêt !"