Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
This is the shell script I use to drive HandBrakeCLI to re-encode video files in a format suitable for playback on Apple TV, Roku 3, iOS, OS X, etc.
# Copyright (c) 2013 Don Melton
# This version published on June 7, 2013.
# Re-encode video files in a format suitable for playback on Apple TV, Roku 3,
# iOS, OS X, etc.
# Input is assumed to be a single file readable by HandBrakeCLI and mediainfo,
# e.g. just about any .mkv, .avi, .mpg, etc. file.
# The script automatically calculates output video bitrate based on input. For
# Blu-ray Disc-quality input that's always 5000 Kbps. For DVD-quality input
# that's always 1800 Kbps. For other files that will vary.
# The script also automatically calculates video frame rates and audio channel
# configuration.
# If the input contains a VobSub (DVD-style) or PGS (Blu-ray Disc-style)
# subtitle, then it is burned into the video.
# Optional frame rate overrides and soft subtitles in .srt format are read
# from separate fixed locations in the `$frame_rates_location` and
# `$subtitles_location` variables defined below. Edit this script to redefine
# them.
# If your input file is named "foobar.mkv" then the optional frame rate file
# should be named "foobar.txt". And all it should contain is the frame rate
# number, e.g. "25" followed by a carriage return.
# If your input file is named "foobar.mkv" then the optional soft subtitle
# file should be named "".
# Output is an MP4 container with H.264 video, AAC audio and possibly AC-3
# audio if the input has more than two channels.
# No scaling or cropping is performed on the output. This is a good thing.
# The output .mp4 file and a companion .log file are written to the current
# directory.
# This script depends on two separate command line tools:
# HandBrakeCLI
# mediainfo
# Make sure both are in your `$PATH` or redefine the variables below.
# Usage:
# ./ [input file]
die() {
echo "$program: $1" >&2
exit ${2:-1}
escape_string() {
echo "$1" | sed "s/'/'\\\''/g;/ /s/^\(.*\)$/'\1'/"
readonly program="$(basename "$0")"
readonly input="$1"
if [ ! "$input" ]; then
die 'too few arguments'
frame_rates_location="/path/to/Frame Rates"
# My advice is: do NOT change these HandBrake options. I've encoded over 300
# Blu-ray Discs, 30 DVDs and numerous other files with these settings and
# they've never let me down.
handbrake_options="--markers --large-file --encoder x264 --encopts vbv-maxrate=25000:vbv-bufsize=31250:ratetol=inf --crop 0:0:0:0 --strict-anamorphic"
width="$(mediainfo --Inform='Video;%Width%' "$input")"
height="$(mediainfo --Inform='Video;%Height%' "$input")"
if (($width > 1280)) || (($height > 720)); then
elif (($width > 720)) || (($height > 576)); then
min_bitrate="$((max_bitrate / 2))"
bitrate="$(mediainfo --Inform='Video;%BitRate%' "$input")"
if [ ! "$bitrate" ]; then
bitrate="$(mediainfo --Inform='General;%OverallBitRate%' "$input")"
bitrate="$(((bitrate / 10) * 9))"
if [ "$bitrate" ]; then
bitrate="$(((bitrate / 5) * 4))"
bitrate="$((bitrate / 1000))"
bitrate="$(((bitrate / 100) * 100))"
if (($bitrate > $max_bitrate)); then
elif (($bitrate < $min_bitrate)); then
handbrake_options="$handbrake_options --vb $bitrate"
frame_rate="$(mediainfo --Inform='Video;%FrameRate_Original%' "$input")"
if [ ! "$frame_rate" ]; then
frame_rate="$(mediainfo --Inform='Video;%FrameRate%' "$input")"
frame_rate_file="$(basename "$input")"
if [ -f "$frame_rate_file" ]; then
handbrake_options="$handbrake_options --rate $(cat "$frame_rate_file")"
elif [ "$frame_rate" == '29.970' ]; then
handbrake_options="$handbrake_options --rate 23.976"
handbrake_options="$handbrake_options --rate 30 --pfr"
channels="$(mediainfo --Inform='Audio;%Channels%' "$input" | sed 's/[^0-9].*$//')"
if (($channels > 2)); then
handbrake_options="$handbrake_options --aencoder ca_aac,copy:ac3"
elif [ "$(mediainfo --Inform='General;%Audio_Format_List%' "$input" | sed 's| /.*||')" == 'AAC' ]; then
handbrake_options="$handbrake_options --aencoder copy:aac"
if [ "$frame_rate" == '29.970' ]; then
handbrake_options="$handbrake_options --detelecine"
srt_file="$(basename "$input")"
if [ -f "$srt_file" ]; then
subtitle_format="$(mediainfo --Inform='Text;%Format%' "$input" | sed q)"
if [ "$subtitle_format" == 'VobSub' ] || [ "$subtitle_format" == 'PGS' ]; then
handbrake_options="$handbrake_options --subtitle 1 --subtitle-burned"
trap '[ "$tmp" ] && rm -rf "$tmp"' 0
trap '[ "$tmp" ] && rm -rf "$tmp"; exit 1' SIGHUP SIGINT SIGQUIT SIGTERM
mkdir -m 700 "$tmp" || exit 1
cp "$srt_file" "$temporary_srt_file" || exit 1
handbrake_options="$handbrake_options --srt-file $(escape_string "$temporary_srt_file") --srt-codeset UTF-8 --srt-lang eng --srt-default 1"
output="$(basename "$input")"
echo "Encoding: $input" >&2
time "$handbrake" \
$handbrake_options \
--input "$input" \
--output "$output" \
2>&1 | tee -a "${output}.log"

This comment has been minimized.

Copy link

@dennisoderwald dennisoderwald commented Jun 15, 2014

Hi, i using Ubuntu 14.04 LTS - what dependencies need to be installed on my system?

I've always installed HandbrakeCLI & mediainfo (with ffmpeg, ..), when i try convert my mkv file:

[15:30:56] add_ffmpeg_subtitle: unknown subtitle stream type: 0x53526970
[15:30:56] scan: decoding previews for title 1




This comment has been minimized.

Copy link

@notthenewsreader notthenewsreader commented May 12, 2015

Hi, is it possible to get this to batch convert a load of .mpg files in a directory without having to run the script with each video filename on the end?


This comment has been minimized.

Copy link

@racinrandall racinrandall commented Nov 23, 2015

I really like the script, however it looks like it uses the first audio track from what I can tell. Some of my videos have a different language as the first audio track than the one I want. I'm looking to modify it to pull the English audio track and copy DTS with a fall back of aac.

I found another script on the Internet that would encode everything in a given folder. I modified it to call this script rather than HandBrakeCLI directly. This allowed me to do all files using this script. Now to get the right language and foreign audio scan.


This comment has been minimized.

Copy link

@xlr5 xlr5 commented Jan 3, 2016

racinrandall, can you give us your script?


This comment has been minimized.

Copy link

@BubonicPestilence BubonicPestilence commented Aug 8, 2016

For people who need to batch run:

find . -maxdepth 1 -type f -iname "*.*" -exec {} \;

You can replace *.* with file pattern


This comment has been minimized.

Copy link

@itsBREX itsBREX commented Jan 26, 2017

racinrandall - care to share your script?


This comment has been minimized.

Copy link

@fahadshery fahadshery commented Oct 11, 2017

great script.

However, I am getting the following error:

: not found
: not found 58: Syntax error: Bad fd number

I saved the script and called as:

sh /path/to/video_file.m2ts

I am using FreeNas which is a BSD based distro


This comment has been minimized.

Copy link

@auswalk auswalk commented Feb 14, 2018

To preserve copying files (in this case all m4v and mkv files) found recursively into the same directory:
$ find . -type f -path *.m4v -o -path *.mkv -exec {} ;

update lines 179-181

outputdir=$(dirname "${input}")
output="$(basename "$input")"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.