Skip to content

Instantly share code, notes, and snippets.

@SalemHarrache
Forked from lisamelton/transcode-video.sh
Last active August 29, 2015 14:13
Show Gist options
  • Save SalemHarrache/d1267d4a7d33d39625eb to your computer and use it in GitHub Desktop.
Save SalemHarrache/d1267d4a7d33d39625eb to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# transcode-video.sh
#
# Copyright (c) 2013-2015 Don Melton
#
about() {
cat <<EOF
$program 5.6 of January 19, 2015
Copyright (c) 2013-2015 Don Melton
EOF
exit 0
}
usage_prologue() {
cat <<EOF
Transcode video file or disc image directory into format and size similar to
popular online downloads. Works best with Blu-ray or DVD rip.
Automatically determines target video bitrate, number of audio tracks, etc.
WITHOUT ANY command line options.
Usage: $program [OPTION]... [FILE|DIRECTORY]
--help display basic options and exit
--fullhelp display ALL options and exit
EOF
}
usage() {
usage_prologue
cat <<EOF
--title NUMBER select numbered title in video media (default: 1)
(\`0\` to scan media, list title numbers and exit)
--mkv output Matroska format instead of MP4
--big raise default limits for both video and AC-3 audio bitrates
(always increases output size)
--fast, --faster, --veryfast
use x264 encoder preset to trade precision for speed
--slow, --slower, --veryslow
use x264 encoder preset to trade speed for compression
--crop T:B:L:R set video crop values (default: 0:0:0:0)
(use \`detect-crop.sh\` script for optimal bounds)
(use \`--crop auto\` for \`HandBrakeCLI\` behavior)
--720p constrain video to fit within 1280x720 pixel bounds
--audio TRACK select main audio track (default: 1)
--burn TRACK burn subtitle track (default: first forced track, if any)
--version output version information and exit
Requires \`HandBrakeCLI\` executable in \$PATH.
Output and log file are written to current working directory.
EOF
exit 0
}
usage_full() {
usage_prologue
cat <<EOF
Input options:
--title NUMBER select numbered title in video media (default: 1)
(\`0\` to scan media, list title numbers and exit)
--chapters NUMBER[-NUMBER]
select chapters, single or range (default: all)
--start-at,--stop-at UNIT:VALUE
start or stop at \`frame\`, \`duration\` or \`pts\`
(\`duration\` in seconds, \`pts\` on 90 kHz clock)
Output options:
--mkv output Matroska format instead of MP4
--m4v output MP4 with \`.m4v\` extension instead of \`.mp4\`
Quality options:
--big raise default limits for both video and AC-3 audio bitrates
(always increases output size)
--fast, --faster, --veryfast
use x264 encoder preset to trade precision for speed
--slow, --slower, --veryslow
use x264 encoder preset to trade speed for compression
Video options:
--crop T:B:L:R set video crop values (default: 0:0:0:0)
(use \`detect-crop.sh\` script for optimal bounds)
(use \`--crop auto\` for \`HandBrakeCLI\` behavior)
--720p constrain video to fit within 1280x720 pixel bounds
--1080p " " " " " 1920x1080 " "
--2160p " " " " " 3840x2160 " "
--rate FPS[,limited]
set video frame rate with optional peak-limited flag
(default: based on input)
Audio options:
--audio TRACK select main audio track (default: 1)
--single don't create secondary main audio track
--add-audio [double,]TRACK[,NAME]
add audio track in AAC format
with optional "double" flag to include the track again
in multi-channel format if available
with optional name
(can be used multiple times)
--allow-ac3 allow multi-channel AC-3 format in additional audio tracks
--allow-dts allow multi-channel DTS formats in all audio tracks
(also allows AC-3 format in additional audio tracks)
--no-surround don't output multi-channel formats in any audio track
--ac3 BITRATE set AC-3 audio bitrate to 384|448|640 kbps (default: 384)
--pass-ac3 BITRATE
set passthru AC-3 audio <= 384|448|640 kbps (default: 448)
(only applies to multi-channel)
--copy-ac3 always passthru AC-3 audio in main track
(including mono and stero, regardless of bitrate)
--copy-all-ac3 always passthru AC-3 audio in all tracks
(including mono and stero, regardless of bitrate)
Subtitle options:
--burn TRACK burn subtitle track (default: first forced track, if any)
--no-auto-burn don't automatically burn first forced subtitle
--add-subtitle [forced,]TRACK
add subtitle track with optional forced playback flag
(can be used multiple times)
--burn-srt [ENCODING,][OFFSET,]FILENAME
burn subtitle track from SubRip-format \`.srt\` text file
with optional character set encoding (default: latin1)
with optional +/- offset in milliseconds (default: 0)
(values before filename can appear in any order)
--add-srt [ENCODING,][OFFSET,][LANGUAGE,][forced,]FILENAME
add subtitle track from SubRip-format \`.srt\` text file
with optional character set encoding (default: latin1)
with optional +/- offset in milliseconds (default: 0)
with optional ISO 639-2 language code (default: und)
with optional forced playback flag
(values before filename can appear in any order)
(can be used multiple times)
Advanced options:
--preset NAME use x264 ...|fast|medium|slow|... preset (default: medium)
(refer to \`HandBrakeCLI --help\` for complete list)
--tune NAME use x264 film|animation|grain|... tune
(refer to \`HandBrakeCLI --help\` for complete list)
--max BITRATE set maximum video bitrate (default: based on input)
(can be exceeded to maintain video quality)
--buffer SIZE set video buffer size (default: 50% of maximum bitrate)
--add-encopts OPTION=VALUE[:OPTION=VALUE...]
specify additional x264 encoder options
--crf FACTOR set constant rate factor (default: 16)
--crf-max FACTOR
set maximum constant rate factor (default: 25)
(use \`--crf-max none\` to disable)
--filter NAME[=SETTINGS]
apply \`HandBrakeCLI\` video filter with optional settings
(default: \`deinterlace\` for some 29.97 fps input)
(refer to \`HandBrakeCLI --help\` for more information)
(can be used multiple times)
Passthru options:
--angle, --normalize-mix, --drc, --gain,
--no-opencl, --optimize, --use-opencl, --use-hwd
all passed through to \`HandBrakeCLI\` unchanged
(refer to \`HandBrakeCLI --help\` for more information)
Other options:
--no-log don't write log file
--debug output diagnostic information to \`stderr\` and exit
(with \`HandBrakeCLI\` command line sent to \`stdout\`)
--version output version information and exit
Requires \`HandBrakeCLI\` executable in \$PATH.
May require \`mp4track\` and \`mkvpropedit\` executables in \$PATH for some options.
Output and log file are written to current working directory.
EOF
exit 0
}
syntax_error() {
echo "$program: $1" >&2
echo "Try \`$program --help\` for more information." >&2
exit 1
}
die() {
echo "$program: $1" >&2
exit ${2:-1}
}
deprecated() {
echo "$program: deprecated option: $1" >&2
}
deprecated_and_replaced() {
deprecated $1
echo "$program: use this option instead: $2" >&2
}
escape_string() {
echo "$1" | sed "s/'/'\\\''/g;s/^\(.*\)$/'\1'/"
}
readonly program="$(basename "$0")"
# OPTIONS
#
case $1 in
--help)
usage
;;
--fullhelp)
usage_full
;;
--version)
about
;;
esac
media_title='1'
section_options=''
container_format='mp4'
default_max_bitrate_2160p='10000'
default_max_bitrate_1080p='5000'
default_max_bitrate_720p='4000'
default_max_bitrate_480p='2000'
preset='medium'
crop_values='0:0:0:0'
constrain_width='4096'
constrain_height='2304'
frame_rate_options=''
main_audio_track='1'
single_main_audio=''
extra_audio_tracks=()
allow_ac3=''
allow_dts=''
allow_surround='yes'
ac3_bitrate='384'
pass_ac3_bitrate='448'
copy_ac3=''
copy_all_ac3=''
burned_subtitle_track=''
auto_burn='yes'
extra_subtitle_tracks=()
burned_srt_file=''
extra_srt_files=()
tune_options=''
max_bitrate=''
vbv_bufsize=''
extra_encopts_options=''
rate_factor='16'
max_rate_factor='25'
filter_options=''
auto_deinterlace='yes'
passthru_options=''
write_log='yes'
debug=''
while [ "$1" ]; do
case $1 in
--title)
media_title="$(printf '%.0f' "$2" 2>/dev/null)"
shift
if (($media_title < 0)); then
die "invalid media title number: $media_title"
fi
;;
--chapters|--start-at|--stop-at)
section_options="$section_options $1 $2"
shift
;;
--mkv|--m4v)
container_format="${1:2:3}"
;;
--preset|--veryfast|--faster|--fast|--slow|--slower|--veryslow)
if [ "$1" == '--preset' ]; then
preset="$2"
shift
else
preset="${1:2}"
fi
;;
--big|--better)
[ "$1" == '--better' ] && deprecated_and_replaced "$1" '--big'
default_max_bitrate_2160p='16000'
default_max_bitrate_1080p='8000'
default_max_bitrate_720p='6000'
default_max_bitrate_480p='3000'
ac3_bitrate='640'
;;
--crop)
crop_values="$2"
shift
;;
--720p|--resize)
[ "$1" == '--resize' ] && deprecated_and_replaced "$1" '--720p'
constrain_width='1280'
constrain_height='720'
;;
--1080p)
constrain_width='1920'
constrain_height='1080'
;;
--2160p)
constrain_width='3840'
constrain_height='2160'
;;
--rate)
frame_rate_argument="$2"
shift
frame_rate_options="--rate $(printf '%.3f' "$(echo "$frame_rate_argument" | sed 's/,.*$//')" 2>/dev/null | sed 's/0*$//;s/\.$//')"
if [[ "$frame_rate_argument" =~ ',limited'$ ]]; then
frame_rate_options="$frame_rate_options --pfr"
elif [[ "$frame_rate_argument" =~ ',' ]]; then
die "invalid frame rate argument: $frame_rate_argument"
fi
;;
--audio)
main_audio_track="$(printf '%.0f' "$2" 2>/dev/null)"
shift
if (($main_audio_track < 1)); then
die "invalid main audio track: $main_audio_track"
fi
;;
--single)
single_main_audio='yes'
;;
--add-audio)
extra_audio_tracks=("${extra_audio_tracks[@]}" "$2")
shift
;;
--allow-ac3)
allow_ac3='yes'
allow_surround='yes'
;;
--allow-dts)
allow_ac3='yes'
allow_dts='yes'
allow_surround='yes'
;;
--no-surround|--no-ac3)
[ "$1" == '--no-ac3' ] && deprecated_and_replaced "$1" '--no-surround'
allow_ac3=''
allow_dts=''
allow_surround=''
copy_ac3=''
copy_all_ac3=''
;;
--ac3)
ac3_bitrate="$2"
shift
case $ac3_bitrate in
384|448|640)
;;
*)
syntax_error "unsupported AC-3 audio bitrate: $ac3_bitrate"
;;
esac
;;
--pass-ac3)
pass_ac3_bitrate="$2"
shift
case $pass_ac3_bitrate in
384|448|640)
;;
*)
syntax_error "unsupported AC-3 audio passthru bitrate: $pass_ac3_bitrate"
;;
esac
;;
--copy-ac3)
copy_ac3='yes'
;;
--copy-all-ac3)
copy_ac3='yes'
copy_all_ac3='yes'
;;
--burn)
burned_subtitle_track="$(printf '%.0f' "$2" 2>/dev/null)"
shift
if (($burned_subtitle_track < 1)); then
die "invalid burn subtitle track: $burned_subtitle_track"
fi
burned_srt_file=''
;;
--no-auto-burn)
auto_burn=''
;;
--add-subtitle)
extra_subtitle_tracks=("${extra_subtitle_tracks[@]}" "$2")
shift
;;
--burn-srt)
burned_srt_file="$2"
burned_subtitle_track=''
auto_burn=''
shift
;;
--add-srt|--srt)
[ "$1" == '--srt' ] && deprecated_and_replaced "$1" '--add-srt'
extra_srt_files=("${extra_srt_files[@]}" "$2")
shift
;;
--tune)
tune_options="$tune_options --encoder-tune $2"
shift
;;
--max|--vbv-maxrate|--abr)
[ "$1" == '--abr' ] && deprecated_and_replaced "$1" '--max'
max_bitrate="$(printf '%.0f' "$2" 2>/dev/null)"
shift
if (($max_bitrate < 1)); then
die "invalid maximum video bitrate: $max_bitrate"
fi
;;
--buffer|--vbv-bufsize)
vbv_bufsize="$(printf '%.0f' "$2" 2>/dev/null)"
shift
if (($vbv_bufsize < 1)); then
die "invalid video buffer size: $vbv_bufsize"
fi
;;
--add-encopts)
extra_encopts_options="$2"
shift
;;
--crf)
rate_factor="$(printf '%.2f' "$2" 2>/dev/null | sed 's/0*$//;s/\.$//')"
shift
if (($rate_factor < 0)); then
die "invalid constant rate factor: $rate_factor"
fi
;;
--crf-max)
max_rate_factor="$2"
shift
case $max_rate_factor in
none)
max_rate_factor=''
;;
*)
max_rate_factor="$(printf '%.2f' "$max_rate_factor" 2>/dev/null | sed 's/0*$//;s/\.$//')"
if (($max_rate_factor < 0)); then
die "invalid maximum constant rate factor: $max_rate_factor"
fi
;;
esac
;;
--filter)
filter="$2"
shift
filter_name="$(echo "$filter" | sed 's/=.*$//')"
case $filter_name in
deinterlace|decomb|detelecine)
auto_deinterlace=''
;;
denoise|nlmeans|nlmeans-tune|deblock|rotate|grayscale)
;;
*)
syntax_error "unsupported video filter: $filter_name"
;;
esac
filter_options="$filter_options --$filter"
;;
--angle|--start-at|--stop-at|--normalize-mix|--drc|--gain)
passthru_options="$passthru_options $1 $2"
shift
;;
--no-opencl|--optimize|--use-opencl|--use-hwd)
passthru_options="$passthru_options $1"
;;
--no-log)
write_log=''
;;
--debug)
debug='yes'
;;
--hq)
deprecated "$1"
;;
--with-original-audio)
deprecated_and_replaced "$1" '--allow-dts'
allow_dts='yes'
audio_track="$(printf '%.0f' "$1" 2>/dev/null)"
if (($audio_track > 0)); then
main_audio_track="$audio_track"
shift
fi
;;
--detelecine)
deprecated_and_replaced "$1" '--filter detelecine'
filter_options="$filter_options --detelecine"
;;
--no-auto-detelecine)
deprecated "$1"
;;
--srt-burn)
deprecated "$1"
srt_number="$(printf '%.0f' "$2" 2>/dev/null)"
if (($srt_number > 0)); then
shift
fi
;;
-*)
syntax_error "unrecognized option: $1"
;;
*)
break
;;
esac
shift
done
# INPUT
#
readonly input="$1"
if [ ! "$input" ]; then
syntax_error 'too few arguments'
fi
if [ ! -e "$input" ]; then
die "input not found: $input"
fi
if ! $(which HandBrakeCLI >/dev/null); then
die 'executable not in $PATH: HandBrakeCLI'
fi
if [ "$media_title" == '0' ]; then
echo "Scanning: $input" >&2
fi
# Leverage `HandBrakeCLI` scan mode to extract all file- or directory-based
# media information. Significantly speed up scan with `--previews 2:0` option
# and argument.
#
readonly media_info="$(HandBrakeCLI --title $media_title --scan --previews 2:0 --input "$input" 2>&1)"
if [ "$media_title" == '0' ]; then
echo "$media_info"
exit
fi
if [ "$debug" ]; then
echo "$media_info" >&2
fi
if ! $(echo "$media_info" | grep -q '^+ title '$media_title':$'); then
echo "$program: media title number $media_title not found in: $input" >&2
echo "Try \`$program --title 0 [FILE|DIRECTORY]\` to scan for media titles." >&2
echo "Try \`$program --help\` for more information." >&2
exit 1
fi
readonly output="$(basename "$input" | sed 's/\.[0-9A-Za-z]\{1,\}$//').$container_format"
if [ -e "$output" ]; then
die "output file already exists: $output"
fi
# VIDEO
#
readonly size_array=($(echo "$media_info" | sed -n 's/^ + size: \([0-9]\{1,\}\)x\([0-9]\{1,\}\).*$/\1 \2/p'))
if ((${#size_array[*]} != 2)); then
die "video size not found: $input"
fi
width="${size_array[0]}"
height="${size_array[1]}"
if [ "$crop_values" != '0:0:0:0' ] && [ "$crop_values" != 'auto' ]; then
readonly crop_array=($(echo "$crop_values" |
sed -n 's/^\([0-9]\{1,\}\):\([0-9]\{1,\}\):\([0-9]\{1,\}\):\([0-9]\{1,\}\)$/ \1 \2 \3 \4 /p' |
sed 's/ 0\([0-9]\)/ \1/g'))
width="$((width - ${crop_array[2]} - ${crop_array[3]}))"
height="$((height - ${crop_array[0]} - ${crop_array[1]}))"
if (($width < 1)) || (($height < 1)); then
die "invalid crop: $crop_values"
fi
fi
if ((($width > $constrain_width)) || (($height > $constrain_height))); then
size_options="--maxWidth $constrain_width --maxHeight $constrain_height --loose-anamorphic"
adjusted_height="$(ruby -e 'printf "%.0f", '$height' * ('$constrain_width'.0 / '$width')')"
adjusted_height=$((adjusted_height - (adjusted_height % 2)))
if (($adjusted_height > $constrain_height)); then
width="$(ruby -e 'printf "%.0f", '$width' * ('$constrain_height'.0 / '$height')')"
width=$((width + (width % 2)))
height="$constrain_height"
else
width="$constrain_width"
height="$adjusted_height"
fi
else
size_options='--strict-anamorphic'
fi
# Limit `x264` video buffer verifier (VBV) size to values appropriate for
# H.264 level with High profile:
#
# 300000 for level 5.1 (e.g. 2160p input)
# 25000 for level 4.0 (e.g. Blu-ray input)
# 17500 for level 3.1 (e.g. 720p input)
# 12500 for level 3.0 (e.g. DVD input)
#
level_options=''
if (($width > 1920)) || (($height > 1080)); then
vbv_maxrate="$default_max_bitrate_2160p"
max_bufsize='300000'
elif (($width > 1280)) || (($height > 720)); then
vbv_maxrate="$default_max_bitrate_1080p"
max_bufsize='25000'
case $preset in
slow|slower|veryslow|placebo)
level_options="--encoder-level 4.0"
;;
esac
elif (($width > 720)) || (($height > 576)); then
vbv_maxrate="$default_max_bitrate_720p"
max_bufsize='17500'
else
vbv_maxrate="$default_max_bitrate_480p"
max_bufsize='12500'
fi
if [ "$max_bitrate" ]; then
vbv_maxrate="$max_bitrate"
if (($vbv_maxrate > $max_bufsize)); then
vbv_maxrate="$max_bufsize"
fi
elif [ -f "$input" ]; then
readonly duration_array=($(echo "$media_info" |
sed -n 's/^ + duration: \([0-9][0-9]\):\([0-9][0-9]\):\([0-9][0-9]\)$/ \1 \2 \3 /p' |
sed 's/ 0/ /g'))
if ((${#duration_array[*]} == 3)); then
# Calculate total bitrate from file size in bits divided by video
# duration in seconds.
#
bitrate="$((($(stat -L -f %z "$input") * 8) / ((duration_array[0] * 60 * 60) + (duration_array[1] * 60) + duration_array[2])))"
if [ "$bitrate" ]; then
# Convert to kbps and round to nearest thousand.
#
bitrate="$((((bitrate / 1000) / 1000) * 1000))"
if (($bitrate < $vbv_maxrate)); then
readonly min_bitrate="$((vbv_maxrate / 2))"
if (($bitrate < $min_bitrate)); then
vbv_maxrate="$min_bitrate"
else
vbv_maxrate="$bitrate"
fi
fi
fi
fi
fi
if [ "$vbv_bufsize" ]; then
if (($vbv_bufsize > $max_bufsize)); then
vbv_bufsize="$max_bufsize"
fi
else
# The `x264` video buffer verifier (VBV) size must always be less than the
# maximum rate to maintain quality in constant rate factor (CRF) mode.
#
vbv_bufsize="$((vbv_maxrate / 2))"
fi
if [ ! "$frame_rate_options" ]; then
frame_rate_options='--rate 30 --pfr'
readonly frame_rate="$(echo "$media_info" | sed -n 's/^.* scan: 2 previews, .* \([0-9]\{1,\}\.[.0-9]\{1,\}\) fps, .*$/\1/p')"
if [ ! "$frame_rate" ]; then
die "no video frame rate information in: $input"
fi
readonly video_stream_info="$(echo "$media_info" | sed -n '/^ Stream #[^:]\{1,\}: Video: /p' | sed -n 1p)"
force_frame_rate=''
if [ "$video_stream_info" ]; then
$(echo "$video_stream_info" | grep -q 'mpeg2video') && force_frame_rate='yes'
else
$(echo "$media_info" | grep -q '^ + vts ') && force_frame_rate='yes'
fi
if [ "$frame_rate" == '29.970' ]; then
if [ "$force_frame_rate" ]; then
frame_rate_options='--rate 23.976'
elif [ "$auto_deinterlace" ]; then
filter_options="$filter_options --deinterlace"
fi
elif [ "$force_frame_rate" ]; then
case $frame_rate in
'23.976'|'24.000'|'25.000')
frame_rate_options="--rate $(echo "$frame_rate" | sed 's/\.000$//')"
;;
esac
fi
fi
# AUDIO
#
if (($pass_ac3_bitrate < $ac3_bitrate)); then
pass_ac3_bitrate="$ac3_bitrate"
fi
if [ "$ac3_bitrate" == '640' ]; then
ac3_bitrate=''
fi
track_index='1'
audio_track_list=''
audio_encoder_list=''
audio_bitrate_list=''
audio_track_name_list=''
audio_track_name_edits=()
readonly all_audio_tracks_info="$(echo "$media_info" |
sed -n '/^ + audio tracks:$/,/^ + subtitle tracks:$/p' |
sed -n '/^ + /p')"
audio_track_info="$(echo "$all_audio_tracks_info" | sed -n ${main_audio_track}p)"
if [ "$audio_track_info" ]; then
audio_track_list="$main_audio_track"
if $(HandBrakeCLI --help 2>/dev/null | grep -q 'ca_aac'); then
aac_encoder='ca_aac'
else
aac_encoder='av_aac'
fi
surround_audio_encoder=''
surround_audio_bitrate=''
stereo_audio_encoder="$aac_encoder"
if [ "$copy_ac3" ] && [[ "$audio_track_info" =~ '(AC3)' ]]; then
surround_audio_encoder='copy'
elif (($(echo "$audio_track_info" | sed 's/^.*(\([0-9]\{1,\}\)\.\([0-9]\{1,\}\) ch).*$/\1\2/;s/^$/0/') > 20)); then
if [ "$allow_surround" ]; then
if ( [[ "$audio_track_info" =~ '(AC3)' ]] && ((($(echo "$audio_track_info" | sed -n 's/^.* \([0-9]\{1,\}\)bps$/\1/p' | sed 's/^$/640/') / 1000) <= $pass_ac3_bitrate)) ) || ( [ "$allow_dts" ] && [[ "$audio_track_info" =~ '(DTS' ]] ); then
surround_audio_encoder='copy'
else
surround_audio_encoder='ac3'
surround_audio_bitrate="$ac3_bitrate"
fi
fi
elif [[ "$audio_track_info" =~ '(AAC)' ]]; then
stereo_audio_encoder='copy'
fi
if [ "$surround_audio_encoder" ] && [ ! "$single_main_audio" ]; then
audio_track_list="$main_audio_track,$main_audio_track"
audio_track_name_list=','
if [ "$container_format" == 'mkv' ]; then
audio_encoder_list="$surround_audio_encoder,$stereo_audio_encoder"
audio_bitrate_list="$surround_audio_bitrate,"
else
audio_encoder_list="$stereo_audio_encoder,$surround_audio_encoder"
audio_bitrate_list=",$surround_audio_bitrate"
fi
track_id='3'
track_index='3'
else
audio_track_list="$main_audio_track"
if [ "$surround_audio_encoder" ]; then
audio_encoder_list="$surround_audio_encoder"
audio_bitrate_list="$surround_audio_bitrate"
else
audio_encoder_list="$stereo_audio_encoder"
fi
track_id='2'
track_index='2'
fi
for item in "${extra_audio_tracks[@]}"; do
if [ "$(echo "$item" | sed 's/,.*$//')" == 'double' ]; then
double_audio="$allow_surround"
item="$(echo "$item" | sed 's/^[^,]*,//')"
else
double_audio=''
fi
track_number="$(printf '%.0f' "$(echo "$item" | sed 's/,.*$//')" 2>/dev/null)"
if (($track_number < 1)); then
die "invalid additional audio track: $item"
fi
audio_track_info="$(echo "$all_audio_tracks_info" | sed -n ${track_number}p)"
if [ ! "$audio_track_info" ]; then
die "missing additional audio track: $input"
fi
if [[ "$item" =~ ',' ]]; then
track_name="$(echo "$item" | sed 's/^[^,]*,//')"
else
track_name=''
fi
sanitized_name="$(echo "$track_name" | sed 's/,/_/g')"
surround_audio_encoder=''
surround_audio_bitrate=''
stereo_audio_encoder="$aac_encoder"
if [ "$copy_all_ac3" ] && [[ "$audio_track_info" =~ '(AC3)' ]]; then
stereo_audio_encoder='copy'
elif (($(echo "$audio_track_info" | sed 's/^.*(\([0-9]\{1,\}\)\.\([0-9]\{1,\}\) ch).*$/\1\2/;s/^$/0/') > 20)); then
if [ "$allow_ac3" ] || [ "$double_audio" ]; then
if ( [[ "$audio_track_info" =~ '(AC3)' ]] && ((($(echo "$audio_track_info" | sed -n 's/^.* \([0-9]\{1,\}\)bps$/\1/p' | sed 's/^$/640/') / 1000) <= $pass_ac3_bitrate)) ) || ( [ "$allow_dts" ] && [[ "$audio_track_info" =~ '(DTS' ]] ); then
surround_audio_encoder='copy'
else
surround_audio_encoder='ac3'
surround_audio_bitrate="$ac3_bitrate"
fi
fi
elif [[ "$audio_track_info" =~ '(AAC)' ]]; then
stereo_audio_encoder='copy'
fi
if [ "$surround_audio_encoder" ] && [ "$double_audio" ]; then
audio_track_list="$audio_track_list,$track_number,$track_number"
audio_track_name_list="$audio_track_name_list,$sanitized_name,$sanitized_name"
if [ "$container_format" == 'mkv' ]; then
audio_encoder_list="$audio_encoder_list,$surround_audio_encoder,$stereo_audio_encoder"
audio_bitrate_list="$audio_bitrate_list,$surround_audio_bitrate,"
else
audio_encoder_list="$audio_encoder_list,$stereo_audio_encoder,$surround_audio_encoder"
audio_bitrate_list="$audio_bitrate_list,,$surround_audio_bitrate"
fi
if [ "$sanitized_name" != "$track_name" ]; then
if [ "$container_format" == 'mkv' ]; then
audio_track_name_edits=("${audio_track_name_edits[@]}" "$track_id,$track_name" "$((track_id + 1)),$track_name")
else
audio_track_name_edits=("${audio_track_name_edits[@]}" "$track_index,$track_name" "$((track_index + 1)),$track_name")
fi
fi
track_id="$((track_id + 2))"
track_index="$((track_index + 2))"
else
audio_track_list="$audio_track_list,$track_number"
audio_track_name_list="$audio_track_name_list,$sanitized_name"
if [ "$surround_audio_encoder" ]; then
audio_encoder_list="$audio_encoder_list,$surround_audio_encoder"
audio_bitrate_list="$audio_bitrate_list,$surround_audio_bitrate"
else
audio_encoder_list="$audio_encoder_list,$stereo_audio_encoder"
audio_bitrate_list="$audio_bitrate_list,"
fi
if [ "$sanitized_name" != "$track_name" ]; then
if [ "$container_format" == 'mkv' ]; then
audio_track_name_edits=("${audio_track_name_edits[@]}" "$track_id,$track_name")
else
audio_track_name_edits=("${audio_track_name_edits[@]}" "$track_index,$track_name")
fi
fi
track_id="$((track_id + 1))"
track_index="$((track_index + 1))"
fi
done
elif (($main_audio_track > 1)) || ((${#extra_audio_tracks[*]} > 0)); then
die "missing audio track: $input"
fi
if [ "$audio_track_list" ]; then
audio_options="--audio $audio_track_list --aencoder $audio_encoder_list"
if [ "$(echo "$audio_bitrate_list" | sed 's/,//g')" ]; then
audio_options="$audio_options --ab $audio_bitrate_list"
fi
if [ "$(echo "$audio_track_name_list" | sed 's/,//g')" ]; then
audio_options="$audio_options --aname"
else
audio_track_name_list=''
fi
else
audio_options=''
fi
# SUBTITLES
#
subtitle_track_list=''
readonly all_subtitle_tracks_info="$(echo "$media_info" |
sed -n '/^ + subtitle tracks:$/,$p' |
sed -n '/^ + /p')"
if [ "$burned_subtitle_track" ]; then
subtitle_track_info="$(echo "$all_subtitle_tracks_info" | sed -n ${burned_subtitle_track}p)"
if [ ! "$subtitle_track_info" ]; then
die "missing subtitle track: $input"
fi
subtitle_track_list="$burned_subtitle_track"
elif [ "$auto_burn" ]; then
readonly all_subtitle_streams_info="$(echo "$media_info" | sed -n '/^ Stream #[^:]\{1,\}: Subtitle: /p')"
subtitle_track='1'
while : ; do
subtitle_track_info="$(echo "$all_subtitle_tracks_info" | sed -n ${subtitle_track}p)"
if [ ! "$subtitle_track_info" ]; then
break
fi
if [[ "$(echo "$all_subtitle_streams_info" | sed -n ${subtitle_track}p)" =~ '(forced)' ]]; then
burned_subtitle_track="$subtitle_track"
subtitle_track_list="$burned_subtitle_track"
break
fi
subtitle_track="$((subtitle_track + 1))"
done
fi
forced_subtitle_track_id=''
track_id='1'
for item in "${extra_subtitle_tracks[@]}"; do
track_number="$(printf '%.0f' "$(echo "$item" | sed 's/^forced,//')" 2>/dev/null)"
if (($track_number < 1)); then
die "invalid additional subtitle track: $item"
fi
subtitle_track_info="$(echo "$all_subtitle_tracks_info" | sed -n ${track_number}p)"
if [ ! "$subtitle_track_info" ]; then
die "missing additional subtitle track: $input"
fi
if [ "$container_format" != 'mkv' ] && [[ "$subtitle_track_info" =~ '(PGS)' ]]; then
die "incompatible additional subtitle track for MP4 format: track_number"
fi
if [ ! "$subtitle_track_list" ]; then
subtitle_track_list="$track_number"
else
subtitle_track_list="$subtitle_track_list,$track_number"
fi
if [[ "$item" =~ ^'forced,' ]]; then
if [ "$track_number" == "$burned_subtitle_track" ]; then
die "forced subtitle track is already burned: $track_number"
fi
if [ "$container_format" == 'mkv' ]; then
forced_subtitle_track_id="$track_id"
else
forced_subtitle_track_id="$track_index"
fi
fi
track_id="$((track_id + 1))"
track_index="$((track_index + 1))"
done
if [ "$subtitle_track_list" ]; then
subtitle_options="--subtitle $subtitle_track_list"
if [ "$burned_subtitle_track" ]; then
subtitle_options="$subtitle_options --subtitle-burned"
fi
else
subtitle_options=''
fi
# OTHER SUBTITLES
#
tmp=''
if [ "$burned_srt_file" ] || ((${#extra_srt_files[*]} > 0)); then
trap '[ "$tmp" ] && rm -rf "$tmp"' 0
trap '[ "$tmp" ] && rm -rf "$tmp"; exit 1' SIGHUP SIGINT SIGQUIT SIGTERM
tmp="/tmp/${program}.$$"
mkdir -m 700 "$tmp" || exit 1
fi
srt_file_list=''
srt_codeset_list=''
srt_offset_list=''
srt_lang_list=''
if [ "$burned_srt_file" ]; then
srt_file="$burned_srt_file"
srt_offset=''
srt_codeset=''
while [[ "$srt_file" =~ ',' ]]; do
srt_prefix="$(echo "$srt_file" | sed 's/,.*$//')"
if [ ! "$srt_offset" ] && [[ "$srt_prefix" =~ ^[+-]?[0-9][0-9]*$ ]]; then
srt_offset="$(echo "$srt_prefix" | sed 's/^+//')"
srt_file="$(echo "$srt_file" | sed 's/^[^,]*,//')"
elif [ ! "$srt_codeset" ] && [[ "$srt_prefix" =~ ^[0-9A-Za-z] ]] && [[ ! "$srt_prefix" =~ [\ /\\] ]] && [ ! -f "$srt_file" ]; then
srt_codeset="$srt_prefix"
srt_file="$(echo "$srt_file" | sed 's/^[^,]*,//')"
else
break
fi
done
# Force filename expansion with `eval` but first escape the string
# to hide ", $, &, ', (, ), ;, <, >, \, ` and |.
srt_file="$(eval echo "$(echo "$srt_file" | sed 's/\(["$&'\''();<>\\`|]\)/\\\1/g')")"
if [ ! "$srt_file" ]; then
syntax_error "missing burned subtitle filename"
fi
if [ ! -f "$srt_file" ]; then
die "burned subtitle not found: $srt_file"
fi
tmp_srt_file_link="$tmp/burned-subtitle.srt"
ln -s "$(cd "$(dirname "$srt_file")" 2>/dev/null && echo "$(pwd)/$(basename "$srt_file")")" "$tmp_srt_file_link"
srt_file_list="$tmp_srt_file_link"
srt_codeset_list="$srt_codeset"
srt_offset_list="$srt_offset"
fi
for item in "${extra_srt_files[@]}"; do
srt_file="$item"
srt_lang=''
srt_offset=''
srt_codeset=''
while [[ "$srt_file" =~ ',' ]]; do
srt_prefix="$(echo "$srt_file" | sed 's/,.*$//')"
if [ "$srt_prefix" == 'forced' ]; then
if [ "$container_format" == 'mkv' ]; then
forced_subtitle_track_id="$track_id"
else
forced_subtitle_track_id="$track_index"
fi
srt_file="$(echo "$srt_file" | sed 's/^[^,]*,//')"
elif [ ! "$srt_lang" ] && [[ "$srt_prefix" =~ ^[a-z][a-z][a-z]$ ]]; then
srt_lang="$srt_prefix"
srt_file="$(echo "$srt_file" | sed 's/^[^,]*,//')"
elif [ ! "$srt_offset" ] && [[ "$srt_prefix" =~ ^[+-]?[0-9][0-9]*$ ]]; then
srt_offset="$(echo "$srt_prefix" | sed 's/^+//')"
srt_file="$(echo "$srt_file" | sed 's/^[^,]*,//')"
elif [ ! "$srt_codeset" ] && [[ "$srt_prefix" =~ ^[0-9A-Za-z] ]] && [[ ! "$srt_prefix" =~ [\ /\\] ]] && [ ! -f "$srt_file" ]; then
srt_codeset="$srt_prefix"
srt_file="$(echo "$srt_file" | sed 's/^[^,]*,//')"
else
break
fi
done
# Force filename expansion with `eval` but first escape the string
# to hide ", $, &, ', (, ), ;, <, >, \, ` and |.
srt_file="$(eval echo "$(echo "$srt_file" | sed 's/\(["$&'\''();<>\\`|]\)/\\\1/g')")"
if [ ! "$srt_file" ]; then
syntax_error "missing subtitle filename"
fi
if [ ! -f "$srt_file" ]; then
die "subtitle not found: $srt_file"
fi
tmp_srt_file_link="$tmp/subtitle-$track_id.srt"
ln -s "$(cd "$(dirname "$srt_file")" 2>/dev/null && echo "$(pwd)/$(basename "$srt_file")")" "$tmp_srt_file_link"
if [ ! "$srt_file_list" ]; then
srt_file_list="$tmp_srt_file_link"
srt_codeset_list="$srt_codeset"
srt_offset_list="$srt_offset"
srt_lang_list="$srt_lang"
else
srt_file_list="$srt_file_list,$tmp_srt_file_link"
srt_codeset_list="$srt_codeset_list,$srt_codeset"
srt_offset_list="$srt_offset_list,$srt_offset"
srt_lang_list="$srt_lang_list,$srt_lang"
fi
track_id="$((track_id + 1))"
track_index="$((track_index + 1))"
done
if [ "$srt_file_list" ]; then
srt_options="--srt-file $srt_file_list"
if [ "$(echo "$srt_codeset_list" | sed 's/,//g')" ]; then
srt_options="$srt_options --srt-codeset $srt_codeset_list"
fi
if [ "$(echo "$srt_offset_list" | sed 's/,//g')" ]; then
srt_options="$srt_options --srt-offset $srt_offset_list"
fi
if [ "$(echo "$srt_lang_list" | sed 's/,//g')" ]; then
srt_options="$srt_options --srt-lang $srt_lang_list"
fi
if [ "$burned_srt_file" ]; then
srt_options="$srt_options --srt-burn"
fi
else
srt_options=''
fi
# OTHER OPTIONS
#
if [ "$media_title" == '1' ]; then
title_options=''
else
title_options="--title $media_title"
fi
section_options="$(echo "$section_options" | sed 's/^ *//')"
if [ "$preset" == 'medium' ]; then
preset_options=''
else
preset_options="--encoder-preset $preset"
fi
tune_options="$(echo "$tune_options" | sed 's/^ *//')"
encopts_options="vbv-maxrate=$vbv_maxrate:vbv-bufsize=$vbv_bufsize"
# Limit reference frames for playback compatibility with popular devices.
#
case $preset in
slower|veryslow|placebo)
encopts_options="ref=5:$encopts_options"
;;
esac
if [ "$max_rate_factor" ]; then
encopts_options="$encopts_options:crf-max=$max_rate_factor"
fi
if [ "$extra_encopts_options" ]; then
encopts_options="$(echo "$encopts_options:$extra_encopts_options" | sed 's/:*$//;s/:\{1,\}/:/g')"
fi
if [ "$crop_values" == 'auto' ]; then
crop_options=''
else
crop_options="--crop $crop_values"
fi
filter_options="$(echo "$filter_options" | sed 's/^ *//;s/ *$//;s/ \{1,\}/ /g')"
passthru_options="$(echo "$passthru_options" | sed 's/^ *//')"
# DEBUG OUTPUT
#
if [ "$debug" ]; then
echo >&2
echo "title_options = $title_options" >&2
echo "section_options = $section_options" >&2
echo "preset_options = $preset_options" >&2
echo "tune_options = $tune_options" >&2
echo "encopts_options = $encopts_options" >&2
echo "level_options = $level_options" >&2
echo "rate_factor = $rate_factor" >&2
echo "frame_rate_options = $frame_rate_options" >&2
echo "audio_options = $audio_options" >&2
echo "audio_track_name_list = $audio_track_name_list" >&2
echo "crop_options = $crop_options" >&2
echo "size_options = $size_options" >&2
echo "filter_options = $filter_options" >&2
echo "subtitle_options = $subtitle_options" >&2
echo "srt_options = $srt_options" >&2
echo "passthru_options = $passthru_options" >&2
echo "input = $input" >&2
echo "output = $output" >&2
echo >&2
command="$(echo "HandBrakeCLI $title_options $section_options --markers --encoder x264 $preset_options $tune_options --encopts $encopts_options $level_options --quality $rate_factor $frame_rate_options $audio_options" | sed 's/ *$//;s/ \{1,\}/ /g')"
if [ "$audio_track_name_list" ]; then
command="$command $(escape_string "$audio_track_name_list")"
fi
command="$command $(echo "$crop_options $size_options $filter_options $subtitle_options $srt_options $passthru_options" | sed 's/^ *//;s/ *$//;s/ \{1,\}/ /g') --input $(escape_string "$input") --output $(escape_string "$output")"
if [ "$write_log" ]; then
command="$command 2>&1 | tee -a $(escape_string "${output}.log")"
fi
echo "$command"
for item in "${audio_track_name_edits[@]}"; do
track_id="$(echo "$item" | sed 's/,.*$//')"
track_name="$(echo "$item" | sed 's/^[^,]*,//')"
if [ "$container_format" == 'mkv' ]; then
echo "[ -f $(escape_string "$output") ] && mkvpropedit --quiet --edit track:a$track_id --set name=$(escape_string "$track_name") $(escape_string "$output")"
else
echo "[ -f $(escape_string "$output") ] && mp4track --track-index $track_id --hdlrname $(escape_string "$track_name") $(escape_string "$output")"
echo "[ -f $(escape_string "$output") ] && mp4track --track-index $track_id --udtaname $(escape_string "$track_name") $(escape_string "$output")"
fi
done
if [ "$forced_subtitle_track_id" ]; then
if [ "$container_format" == 'mkv' ]; then
echo "[ -f $(escape_string "$output") ] && mkvpropedit --quiet --edit track:s$forced_subtitle_track_id --set flag-default=1 --set flag-forced=1 $(escape_string "$output")"
else
echo "[ -f $(escape_string "$output") ] && mp4track --track-index $forced_subtitle_track_id --enabled true $(escape_string "$output")"
fi
fi
exit
fi
# OUTPUT
#
if [ "$container_format" == 'mkv' ]; then
tool='mkvpropedit'
else
tool='mp4track'
fi
if ( ((${#audio_track_name_edits[*]} > 0)) || [ "$forced_subtitle_track_id" ] ) && ! $(which $tool >/dev/null); then
die "executable not in \$PATH: $tool"
fi
if [ "$write_log" ]; then
log_file="${output}.log"
else
log_file='/dev/null'
fi
echo "Transcoding: $input" >&2
time {
HandBrakeCLI \
$title_options \
$section_options \
--markers \
--encoder x264 \
$preset_options \
$tune_options \
--encopts $encopts_options \
$level_options \
--quality $rate_factor \
$frame_rate_options \
$audio_options "$audio_track_name_list" \
$crop_options \
$size_options \
$filter_options \
$subtitle_options \
$srt_options \
$passthru_options \
--input "$input" \
--output "$output" \
2>&1 | tee -a "$log_file"
if [ -f "$output" ]; then
for item in "${audio_track_name_edits[@]}"; do
track_id="$(echo "$item" | sed 's/,.*$//')"
track_name="$(echo "$item" | sed 's/^[^,]*,//')"
if [ "$container_format" == 'mkv' ]; then
mkvpropedit --quiet --edit track:a$track_id --set name="$track_name" "$output" || exit 1
else
mp4track --track-index $track_id --hdlrname "$track_name" "$output" &&
mp4track --track-index $track_id --udtaname "$track_name" "$output" || exit 1
fi
done
if [ "$forced_subtitle_track_id" ]; then
if [ "$container_format" == 'mkv' ]; then
mkvpropedit --quiet --edit track:s$forced_subtitle_track_id --set flag-default=1 --set flag-forced=1 "$output" || exit 1
else
mp4track --track-index $forced_subtitle_track_id --enabled true "$output" || exit 1
fi
fi
fi
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment