Skip to content

Instantly share code, notes, and snippets.

@Cygon
Last active May 31, 2023 09:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Cygon/b32dd658abd5591cb8a3743724e7f972 to your computer and use it in GitHub Desktop.
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
#!/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