From 83608ea5056cced1edff45e9a111d11174839f4e Mon Sep 17 00:00:00 2001 From: Markus Birth Date: Mon, 23 Sep 2019 01:50:40 +0200 Subject: [PATCH] Started Python script plexoptimise.py that tries to automatically find best ffmpeg parameters for conversion. --- plexoptimise.py | 137 ++++++++++++++++++++++++++++++++++++++++++++++++ plexoptimize.sh | 11 +++- 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100755 plexoptimise.py diff --git a/plexoptimise.py b/plexoptimise.py new file mode 100755 index 0000000..f9a390a --- /dev/null +++ b/plexoptimise.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from collections import OrderedDict +from json import loads +from pprint import pprint +import subprocess +import sys + +# Found: https://superuser.com/questions/852400/properly-downmix-5-1-to-stereo-using-ffmpeg +# and http://forum.doom9.org/showthread.php?t=168267 +ATSC_MODE="pan=stereo|FL<1.0*FL+0.707*FC+0.707*BL|FR<1.0*FR+0.707*FC+0.707*BR" +NIGHT_MODE="pan=stereo|FL=FC+0.30*FL+0.30*BL|FR=FC+0.30*FR+0.30*BR" +STEREO="pan=stereo|c0=FL|c1=FR" + +FIRST_LANGUAGE="eng" # first audio language +DEFAULT_LANGUAGE="eng" # assume this language if not set in metadata +CRF="24" # 18 - almost lossless, 22 - medium, 28 - low +KEEP_VIDEO=["h264"] + +# https://stackoverflow.com/questions/3844430/how-to-get-the-duration-of-a-video-in-python +def ffprobe(filepath): + command = [ + "ffprobe", + "-loglevel", "quiet", + "-print_format", "json", + "-show_format", + "-show_streams", + filepath + ] + + pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + out, err = pipe.communicate() + return loads(out) + +def add_video_params(params, out_idx, stream): + if int(stream["height"]) > 720 or stream["codec_name"] != "h264": + params["c:v"] = "h264" + params["preset"] = "fast" + params["crf"] = CRF + # TODO: Might need some tweaking, not sure if it's really "mpeg4" + if stream["codec_name"] == "mpeg4": + params["bsf:v"] = "mpeg4_unpack_bframes" + if int(stream["height"]) > 720: + # Correct aspect ratio and rescale to 720 + params["filter_complex"].append("[0:{}]scale=iw*sar:ih,scale=-2:720,setsar=1:1".format(stream["index"])) + else: + # Just correct aspect ratio if needed + params["filter_complex"].append("[0:{}]scale=iw*sar:ih,setsar=1:1".format(stream["index"])) + else: + params["filter_complex"].append("[0:{}]null".format(stream["index"])) + params["c:v"] = "copy" + params["movflags"] = "+faststart" + if stream["field_order"] != "progressive": + fc = params["filter_complex"].pop() + fc += ",yadif=1" # 1 = use detected field_order, doubles framerate / 0 = merge both fields into frame, keeps framerate + # To force field_order: 1:0 (top field first) or 1:1 (bottom field first) / -field-dominance 0 or -field-dominance 1 + params["filter_complex"].append(fc) + +def add_audio_params(params, out_idx, stream): + params["c:a"] = "aac" + params["b:a"] = "96k" + params["ar"] = "44100" + params["ac"] = "2" + if stream["channel_layout"] != "stereo": + params["filter_complex"].append("[0:{}]{}".format(stream["index"], NIGHT_MODE)) + else: + params["filter_complex"].append("[0:{}]{}".format(stream["index"], STEREO)) + params["metadata:s:{}".format(out_idx)] = "language={}".format(stream["tags"]["language"]) + +def add_sub_params(params, out_idx, stream): + pass + +def build_ffmpeg_cmd(params): + cmd = ["nice", "ffmpeg", "-i", "\"{}\"".format(FILENAME)] + for p, v in params.items(): + cmd.append("-{}".format(p)) + if p == "filter_complex": + cmd.append("\"{}\"".format(";".join(v))) + else: + cmd.append(v) + cmd.append("\"{}.mkv\"".format(FILENAME)) + return cmd + +FILENAME = sys.argv[1] + +data = ffprobe(FILENAME) +pprint(data) + +video_streams = [] +audio_streams = [] +other_streams = [] +for s in data["streams"]: + codec = s["codec_type"] + if not "tags" in s: + s["tags"] = { + "language": DEFAULT_LANGUAGE + } + elif not "language" in s["tags"]: + s["tags"]["language"] = DEFAULT_LANGUAGE + if codec == "video": + video_streams.append(s) + elif codec == "audio": + audio_streams.append(s) + else: + other_streams.append(s) + +# Make sure first audio is FIRST_LANGUAGE +if audio_streams[0]["tags"]["language"] != FIRST_LANGUAGE: + for i, s in enumerate(audio_streams): + if s["tags"]["language"] == FIRST_LANGUAGE: + del audio_streams[i] + audio_streams.insert(0, s) + break + +output_streams = video_streams + audio_streams + other_streams + +# Now process streams in order to build command line +params = OrderedDict({ + "map": "0", + "filter_complex": [] +}) +for i, s in enumerate(output_streams): + codec = s["codec_type"] + if codec == "video": + add_video_params(params, i, s) + elif codec == "audio": + add_audio_params(params, i, s) + elif codec == "subtitle": + add_sub_params(params, i, s) + else: + print("Unknown codec_type: {}".format(codec)) + +cmd = build_ffmpeg_cmd(params) + +pprint(output_streams) +print(" ".join(cmd)) diff --git a/plexoptimize.sh b/plexoptimize.sh index 874cdf8..fc098ec 100755 --- a/plexoptimize.sh +++ b/plexoptimize.sh @@ -3,6 +3,13 @@ if [ -z "$1" ]; then echo "Syntax: $0 VIDEOFILE [SUBTITLEFILE]" exit 1 fi + +# Found: https://superuser.com/questions/852400/properly-downmix-5-1-to-stereo-using-ffmpeg +# and http://forum.doom9.org/showthread.php?t=168267 +ATSC_MODE="pan=stereo|FL<1.0*FL+0.707*FC+0.707*BL|FR<1.0*FR+0.707*FC+0.707*BR" +NIGHT_MODE="pan=stereo|FL=FC+0.30*FL+0.30*BL|FR=FC+0.30*FR+0.30*BR" +STEREO="pan=stereo|c0=FL|c1=FR" + SUBPARAMS=( ) if [ -n "$2" ]; then # http://stackoverflow.com/questions/8672809/use-ffmpeg-to-add-text-subtitles @@ -20,6 +27,8 @@ fi #ffmpeg -i "$1" "${SUBPARAMS[@]}" -c:v h264 -c:a libvo_aacenc -preset fast -movflags +faststart -crf 18 -b:a 192k -ac 2 "$1.mp4" #ffmpeg -i "$1" "${SUBPARAMS[@]}" -c:v h264 -c:a aac -preset fast -movflags +faststart -vf yadif=0,scale=-2:720 -crf 18 -b:a 160k -ar 44100 -ac 2 "$1.mp4" #ffmpeg -i "$1" "${SUBPARAMS[@]}" -c:v h264 -c:a aac -preset fast -movflags +faststart -vf scale=-2:720 -crf 20 -b:a 160k -ar 44100 -ac 2 "$1.mp4" +#ffmpeg -i "$1" "${SUBPARAMS[@]}" -c:v h264 -c:a aac -preset fast -movflags +faststart -vf scale=-2:720 -crf 24 -b:a 96k -ar 44100 -ac 2 "$1.mp4" #ffmpeg -i "$1" "${SUBPARAMS[@]}" -c:v h264 -c:a aac -preset fast -movflags +faststart -crf 20 -b:a 160k -ar 44100 -ac 2 "$1.mp4" -ffmpeg -i "$1" "${SUBPARAMS[@]}" -c:v h264 -c:a aac -preset fast -movflags +faststart -crf 25 -b:a 96k -ar 44100 -ac 2 "$1.mp4" +ffmpeg -i "$1" "${SUBPARAMS[@]}" -c:v h264 -c:a aac -preset fast -movflags +faststart -crf 24 -b:a 96k -ar 44100 -ac 2 "$1.mp4" #ffmpeg -i "$1" "${SUBPARAMS[@]}" -c:v libx265 -c:a aac -preset fast -movflags +faststart -crf 20 -b:a 160k -ar 44100 -ac 2 "$1.x265.mp4" +#ffmpeg -i "$1" "${SUBPARAMS[@]}" -map 0 -c:v h264 -c:a aac -preset fast -movflags +faststart -filter_complex "[0:v]scale=-2:720;[0:a:1]$NIGHT_MODE;[0:a:0]$STEREO" -metadata:s:a:0 language=eng -metadata:s:a:1 language=ger -crf 24 -b:a 96k -ar 44100 -ac 2 "$1.mkv"