Last active
May 31, 2023 09:53
-
-
Save Cygon/b32dd658abd5591cb8a3743724e7f972 to your computer and use it in GitHub Desktop.
Shell script that encodes an HEVC / H.265 `.mkv` movie with the highest possible quality
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# Helper script to transcode to high-quality x265 clips via ffmpeg | |
# | |
# Uses best possible settings with two-pass encode | |
# See below for additional options. | |
# | |
# File from which the video is taken | |
inputFile=$1 | |
# File to which the final generated output video will be written | |
outputFile=${inputfile%.*}.x265transcoded.mkv | |
# Enable this if video is 640x480 or larger | |
# | |
# It allows decoders to decode single frames with up to 4 threads | |
# Might not be necessary for modern decoders (which simply decode | |
# 4 frames in parallel, thereby achieving the same without tiles. | |
# | |
: ${useFourTiles:=false} | |
# Bits per second to use for the video and audio streams | |
# | |
# For archiving, use double the input bitrate upped to next power of two | |
# 768 = 640 video + 128 audio | |
# 1024 = 896 video + 128 audio 1024 = 768 video + 256 audio | |
# 1536 = 1408 video + 128 audio 1536 = 1280 video + 256 audio | |
# 2048 = 1920 video + 128 audio 2048 = 1792 video + 256 audio | |
# 3072 = 2944 video + 128 audio 3072 = 2816 video + 256 audio | |
# 4096 = 3968 video + 128 audio 4096 = 3840 video + 256 audio | |
# 6144 = 5888 video + 256 audio | |
: ${videoBitRate:=2304k} | |
: ${audioBitRate:=260k} | |
# After how many frames to force a keyframe | |
# | |
# More keyframes worsen overall compression but allow better seeking. | |
# Many encoders recommend one keyframe per second, but one every two | |
# seconds is much more sensible imho. | |
# | |
: ${keyFrameInterval:=72} | |
# ### Careful, only enable one of the options below at a time. | |
# ## ## | |
# ## | ## If multiple -filter:v statements are passed to ffmpeg, it only | |
# ## ' ## takes the first (or last?) one, so if multiple filters are needed, | |
# ########### you have to manually combine the -filter:v statements into one. | |
# | |
# Set to crop the video | |
# | |
# Should be set to "width:height:offsetX:offsetY" if used | |
# Leave set to false in order to perform no cropping at all | |
# | |
: ${cropBoundaries:=false} | |
# Whether to sharpen the video | |
# | |
: ${sharpenVideo:=false} | |
# Whether to rescale the output to 1920x1080 | |
# | |
: ${rescale1920x1080:=false} | |
# ----------------------------------------------------------------------------------------------- # | |
# Delete intermediate files from previous encodes if already there | |
# | |
if [[ -f ffmpeg2pass-0.log ]] | |
then | |
rm ffmpeg2pass-0.log | |
fi | |
if [[ -f x265_2pass.log ]] | |
then | |
rm x265_2pass.log | |
fi | |
if [[ -f x265_2pass.log.temp ]] | |
then | |
rm x265_2pass.log.temp | |
fi | |
if [[ -f x265_2pass.log.cutree ]] | |
then | |
rm x265_2pass.log.cutree | |
fi | |
if [[ -f x265_2pass.log.cutree.temp ]] | |
then | |
rm x265_2pass.log.cutree.temp | |
fi | |
# ----------------------------------------------------------------------------------------------- # | |
ffmpegParameters=() | |
# If the user has specified cropping bounds, set up the respective | |
# ffmpeg parameters | |
# | |
if [ "$cropBoundaries" = false ] | |
then | |
echo Cropping disabled. | |
else | |
echo Cropping to $cropBoundaries | |
ffmpegParameters+=(-filter:v crop=$cropBoundaries) | |
fi | |
# Rescale the output to 1920x1080 from whatever it had before and just claim | |
# the aspect ratio is 16:9. This is useful if the image was slightly off from | |
# 1920x1080 (i.e. 1912x1076) and you want it to avoid playback scaling | |
# (and the aspect ratio still matches or is off by very little!) | |
# | |
if $rescale1920x1080 ; then | |
echo Rescaling to 1920x1080 | |
ffmpegParameters+=(-filter:v scale=1920x1080:flags=lanczos,setsar=16/9) | |
ffmpegParameters+=(-sws_flags lanczos) | |
fi | |
# Apply a light sharpening filter over the video. This may help improve image | |
# quality when transcoding movies that are at a decent bitrate, but where | |
# the image has (from recording or studio-internal processing) sufferent a loss | |
# of sharpness. Doesn't do anything good to transcoded low-bitrate movies. | |
# | |
if $sharpenVideo ; then | |
echo Sharpening enabled | |
ffmpegParameters+=(-filter:v "smartblur=lr=2.0:ls=-0.5:lt=-3.5:cr=1.0:cs=-0.25:ct=-1.5") | |
fi | |
# ----------------------------------------------------------------------------------------------- # | |
# Video encoder settings | |
# | |
ffmpegParameters+=(-c:v libx265) | |
ffmpegParameters+=(-b:v $videoBitRate) | |
ffmpegParameters+=(-g $keyFrameInterval) | |
# We use the 'main' rather than the 'high' profile. The profile does | |
# not enable any advanced encoding technology, it merely switches between | |
# two lanes of allowed bitrates. For out goals, the 'main' profile is | |
# more than enough and isn't outright rejected by cheap TVs. | |
if [ "$tenBit" = true ] | |
then | |
echo 10-bit encode | |
ffmpegParameters+=(-pix_fmt yuv420p10le) | |
ffmpegParameters+=(-profile:v main10) | |
else | |
echo 8-bit encode | |
ffmpegParameters+=(-pix_fmt yuv420p) | |
ffmpegParameters+=(-profile:v main) | |
fi | |
x265Parameters='' | |
if [ "$useFourTiles" = true ] | |
then | |
echo Experimental 4-slice encoding | |
x265Parameters="slices=4:no-wpp=1:fps=$videoFps:ref=6:rd=6:rdoq-level=1:rect=1:amp=1:psy-rd=2.5:psy-rdoq=1.5:fades=1:hevc-aq=1:aq-motion=1" | |
else | |
echo Single slice encoding | |
x265Parameters="slices=1:no-wpp=1:fps=$videoFps:ref=6:rd=6:rdoq-level=1:rect=1:amp=1:psy-rd=2.5:psy-rdoq=1.5:fades=1:hevc-aq=1:aq-motion=1" | |
fi | |
# ----------------------------------------------------------------------------------------------- # | |
# Audio encoder settings | |
# | |
# No subtitles, no audio, nothing | |
# | |
# Audio should either be Opus (for PC playback) or AAC, encoded via QAAC and not | |
# by ffmpeg (for TV playback), so audio transcoding is simply disabled here. | |
# My workflow is to remux the transcoded H.265 video with QAAC-transcoded audio | |
# tracks in MKVToolNix after I have all the invidiual tracks transcoded. | |
# | |
ffmpegParameters+=(-an -sn -dn) | |
# This would transcode to Opus (for PC playback) | |
# | |
#ffmpegParameters+=(-c:a libopus) | |
#ffmpegParameters+=(-b:a $audioBitRate) | |
#ffmpegParameters+=(-ar 48000) | |
# ----------------------------------------------------------------------------------------------- # | |
# Other settings | |
# | |
# Use only 1 thread for encoding. | |
# | |
# This increases transcoding time to about 1 day per 2-4 minutes of movie but improves | |
# compression efficiency vs. multiple threads. So on a multi-core CPU, simply transcode | |
# a few movies in parallel, each with 1 thread and you'll lose no time overall. | |
# | |
ffmpegParameters+=(-threads 1) | |
# Parameters kept from other codecs, they probably don't translate to x265 | |
# | |
#ffmpegParameters+=(-auto-alt-ref 1) | |
#ffmpegParameters+=(-lag-in-frames 25) | |
#ffmpegParameters+=(-quality best) | |
#ffmpegParameters+=(-deadline best) | |
#ffmpegParameters+=(-speed 1) | |
#ffmpegParameters+=(-cpu-used 0) | |
#ffmpegParameters+=(-frame-parallel 1) | |
# ----------------------------------------------------------------------------------------------- # | |
echo "ffmpeg options: ${ffmpegParameters[@]}" | |
echo "libx265 tuning: $x265Parameters" | |
echo ========================================================================== | |
echo "" | |
echo "Encoding first pass" | |
echo "" | |
ffmpeg \ | |
-hide_banner \ | |
-i "$inputFile" \ | |
-shortest \ | |
"${ffmpegParameters[@]}" \ | |
-preset veryslow \ | |
-x265-params "$x265Parameters:pass=1" \ | |
-pass 1 \ | |
-f null \ | |
/dev/null | |
echo ========================================================================== | |
echo "" | |
echo "Encoding second pass" | |
echo "" | |
# Second pass | |
ffmpeg \ | |
-hide_banner \ | |
-i "$inputFile" \ | |
-shortest \ | |
"${ffmpegParameters[@]}" \ | |
-preset veryslow \ | |
-x265-params "$x265Parameters:pass=2" \ | |
-pass 2 \ | |
-y \ | |
transcoded-x265.mkv |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment