-
-
Save lisamelton/7679274 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.13 of April 8, 2015 | |
Copyright (c) 2013-2015 Don Melton | |
EOF | |
exit 0 | |
} | |
usage() { | |
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] | |
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: | |
-o, --output FILENAME|DIRECTORY | |
set output path and filename, or just path | |
(default: input filename with output format extension | |
in current working directory) | |
--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 \`--crop detect\` to invoke \`detect-crop.sh\`) | |
(use \`--crop auto\` for \`HandBrakeCLI\` behavior) | |
--480p constrain video to fit within 854x480 pixel bounds | |
(not perfect 16:9 bounds and not for DVD input) | |
--720p constrain video to fit within 1280x720 pixel bounds | |
--1080p " " " " " 1920x1080 " " | |
--2160p " " " " " 3840x2160 " " | |
--width WIDTH change pixel aspect ratio with custom width | |
--height HEIGHT " " " " " " height | |
--rate FPS[,limited] | |
set video frame rate with optional peak-limited flag | |
(default: based on input) | |
Audio options: | |
--audio TRACK[,NAME] | |
select main audio track (default: 1) | |
with optional name | |
--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) | |
--add-all-audio single|double | |
add all other audio tracks in single AAC format | |
or double AAC+multi-channel format, if avaiable | |
(doesn't apply to main audio track) | |
(not compatible with \`--add-audio\` option) | |
--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) | |
--copy-audio-names | |
passthru any original audio track names | |
(unless a name is specified with another option) | |
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) | |
--add-all-subtitles | |
add all other subtitles | |
(doesn't apply to burned subtitle track) | |
(not compatible with \`--add-subtitle\` option) | |
--find-forced burn|add | |
scan for forced subtitle in same language as main audio | |
and, if found, burn into video or add as forced track | |
(disables some other subtitle behaviors and options) | |
--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: | |
--chapter-names FILENAME | |
import chapter names from \`.csv\` text file | |
(in NUMBER,NAME format, e.g. "1,Intro") | |
--no-log don't write log file | |
--debug output diagnostic information to \`stderr\` and exit | |
(with \`HandBrakeCLI\` command line sent to \`stdout\`) | |
-h, --help display this help and exit | |
--version output version information and exit | |
Requires \`HandBrakeCLI\` executable in \$PATH. | |
Requires \`detect-crop.sh\` script in \$PATH when using \`--crop detect\`. | |
May require \`mkvmerge\` executable in \$PATH when using \`--find-forced add\`. | |
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|-h|--fullhelp) | |
usage | |
;; | |
--version) | |
about | |
;; | |
esac | |
media_title='1' | |
section_options='' | |
output='' | |
output_prefix='' | |
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' | |
custom_width='' | |
custom_height='' | |
frame_rate_options='' | |
main_audio_track='1' | |
main_audio_track_name='' | |
single_main_audio='' | |
extra_audio_tracks=() | |
add_all_audio='' | |
allow_ac3='' | |
allow_dts='' | |
allow_surround='yes' | |
ac3_bitrate='384' | |
pass_ac3_bitrate='448' | |
copy_ac3='' | |
copy_all_ac3='' | |
copy_audio_names='' | |
burned_subtitle_track='' | |
auto_burn='yes' | |
extra_subtitle_tracks=() | |
add_all_subtitles='' | |
find_forced='' | |
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='' | |
chapter_names_file='' | |
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 | |
;; | |
--output|-o) | |
output="$2" | |
shift | |
if [ -d "$output" ]; then | |
output_prefix="$(echo "$output" | sed 's|/$||')/" | |
output='' | |
else | |
case $output in | |
*.mp4|*.mkv|*.m4v) | |
container_format="${output: -3}" | |
;; | |
*) | |
die "unsupported filename extension: $output" | |
;; | |
esac | |
fi | |
;; | |
--mkv|--m4v) | |
container_format="${1:2}" | |
if [ "$output" ]; then | |
output="${output%.*}.$container_format" | |
fi | |
;; | |
--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 | |
;; | |
--480p) | |
constrain_width='854' | |
constrain_height='480' | |
;; | |
--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' | |
;; | |
--width) | |
custom_width="$(printf '%.0f' "$2" 2>/dev/null)" | |
shift | |
if (($custom_width < 1)); then | |
die "invalid custom width: $custom_width" | |
fi | |
;; | |
--height) | |
custom_height="$(printf '%.0f' "$2" 2>/dev/null)" | |
shift | |
if (($custom_height < 1)); then | |
die "invalid custom height: $custom_height" | |
fi | |
;; | |
--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) | |
audio_track_argument="$2" | |
shift | |
main_audio_track="$(printf '%.0f' "$(echo "$audio_track_argument" | sed 's/,.*$//')" 2>/dev/null)" | |
if (($main_audio_track < 1)); then | |
die "invalid main audio track: $main_audio_track" | |
fi | |
if [[ "$audio_track_argument" =~ ',' ]]; then | |
main_audio_track_name="$(echo "$audio_track_argument" | sed 's/^[^,]*,//')" | |
else | |
main_audio_track_name='' | |
fi | |
;; | |
--single) | |
single_main_audio='yes' | |
;; | |
--add-audio) | |
extra_audio_tracks=("${extra_audio_tracks[@]}" "$2") | |
shift | |
add_all_audio='' | |
;; | |
--add-all-audio) | |
add_all_audio="$2" | |
shift | |
case $add_all_audio in | |
single|double) | |
;; | |
*) | |
syntax_error "unsupported audio width: $add_all_audio" | |
;; | |
esac | |
extra_audio_tracks=() | |
;; | |
--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' | |
;; | |
--copy-audio-names) | |
copy_audio_names='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='' | |
find_forced='' | |
;; | |
--no-auto-burn) | |
auto_burn='' | |
;; | |
--add-subtitle) | |
extra_subtitle_tracks=("${extra_subtitle_tracks[@]}" "$2") | |
shift | |
add_all_subtitles='' | |
;; | |
--add-all-subtitles) | |
add_all_subtitles='yes' | |
extra_subtitle_tracks=() | |
;; | |
--find-forced) | |
find_forced="$2" | |
shift | |
case $find_forced in | |
burn|add) | |
;; | |
*) | |
syntax_error "invalid find forced argument: $find_forced" | |
;; | |
esac | |
burned_subtitle_track='' | |
auto_burn='' | |
burned_srt_file='' | |
;; | |
--burn-srt) | |
burned_srt_file="$2" | |
shift | |
burned_subtitle_track='' | |
auto_burn='' | |
find_forced='' | |
;; | |
--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" | |
;; | |
--chapter-names) | |
chapter_names_file="$2" | |
shift | |
;; | |
--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 | |
readonly version="$(HandBrakeCLI --preset-list 2>&1)" | |
if [ ! "$version" ]; then | |
die "can't determine HandBrakeCLI version" | |
fi | |
readonly release_version="$(echo "$version" | | |
sed -n 's/^HandBrake \([0-9]\{1,\}\)\.\([0-9]\{1,\}\)\.\([0-9]\{1,\}\) .*$/\1 \2 \3/p' | | |
sed 's/ 0\([0-9]\)/ \1/g')" | |
if [ "$release_version" ]; then | |
readonly version_array=($release_version) | |
if ((((${version_array[0]} * 10) + ${version_array[1]}) < 10)); then | |
die 'HandBrake version 0.10.0 or later is required' | |
fi | |
else | |
readonly svn_version="$(echo "$version" | | |
sed -n 's/^HandBrake svn\([0-9]\{1,\}\) .*$/\1/p')" | |
if [ "$svn_version" ]; then | |
if (($svn_version < 6536)); then | |
die 'HandBrake version 0.10.0 or later is required' | |
fi | |
else | |
die "can't determine HandBrakeCLI version" | |
fi | |
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 | |
if [ ! "$output" ]; then | |
output="$output_prefix$(basename "$input" | sed 's/\.[0-9A-Za-z]\{1,\}$//').$container_format" | |
fi | |
if [ -e "$output" ]; then | |
die "output file already exists: $output" | |
fi | |
if [ "$crop_values" == 'detect' ]; then | |
if ! $(which detect-crop.sh >/dev/null); then | |
die 'script not in $PATH: detect-crop.sh' | |
fi | |
crop_values="$(detect-crop.sh --title $media_title --values-only "$input")" | |
if [ ! "$crop_values" ]; then | |
die "crop ambiguous or unavailable for: $input" | |
fi | |
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 [ "$custom_width" ]; then | |
if [ "$custom_width" == "$width" ]; then | |
custom_width='' | |
else | |
width="$custom_width" | |
fi | |
fi | |
if [ "$custom_height" ]; then | |
if [ "$custom_height" == "$height" ]; then | |
custom_height='' | |
else | |
height="$custom_height" | |
fi | |
fi | |
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" | |
if [ "$custom_width" ] || [ "$custom_height" ]; then | |
size_options="$size_options --custom-anamorphic" | |
else | |
size_options="$size_options --loose-anamorphic" | |
fi | |
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 | |
elif [ "$custom_width" ] || [ "$custom_height" ]; then | |
size_options='--custom-anamorphic' | |
else | |
size_options='--strict-anamorphic' | |
fi | |
if [ "$custom_height" ]; then | |
size_options="--height $height $size_options" | |
fi | |
if [ "$custom_width" ]; then | |
size_options="--width $width $size_options" | |
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 | |
# Test for total number of pixels instead of bounds so using `--480p` option, | |
# with width up to 854 pixels, works like DVD input. | |
# | |
elif ((($width * $height) > (720 * 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 | |
if $(stat --version 2>/dev/null | grep -q 'GNU'); then | |
format_option='-c %s' | |
else | |
format_option='-f %z' | |
fi | |
# Calculate total bitrate from file size in bits divided by video | |
# duration in seconds. | |
# | |
bitrate="$((($(stat -L $format_option "$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=() | |
if [ "$copy_audio_names" ]; then | |
readonly all_audio_streams_info="$(echo "$media_info" | | |
sed -n '/^ Stream #[^:]\{1,\}: Audio: /,$p' | | |
sed '/^[^ ]\{1,\}.*$/,$d' | | |
sed '/^ Stream #[^:]\{1,\}: Subtitle: /,$d' | | |
sed '/^ Metadata:$/d' | | |
sed '$!N;s/\n/ /')" | |
fi | |
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 | |
if [ "$copy_audio_names" ] && [ ! "$main_audio_track_name" ]; then | |
track_name="$(echo "$all_audio_streams_info" | | |
sed -n ${main_audio_track}p | | |
sed -n 's/^.* \{1,\}title \{1,\}: \(.*\)$/\1/p')" | |
if [ "$track_name" ]; then | |
main_audio_track_name="$track_name" | |
fi | |
fi | |
sanitized_name="$(echo "$main_audio_track_name" | sed 's/,/_/g')" | |
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="$sanitized_name,$sanitized_name" | |
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 | |
if [ "$sanitized_name" != "$main_audio_track_name" ]; then | |
audio_track_name_edits=(1,"$main_audio_track_name" 2,"$main_audio_track_name") | |
fi | |
track_index='3' | |
else | |
audio_track_list="$main_audio_track" | |
audio_track_name_list="$sanitized_name" | |
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 | |
if [ "$sanitized_name" != "$main_audio_track_name" ]; then | |
audio_track_name_edits=(1,"$main_audio_track_name") | |
fi | |
track_index='2' | |
fi | |
if [ "$add_all_audio" ]; then | |
if [ "$add_all_audio" == 'double' ]; then | |
width_prefix="$add_all_audio," | |
else | |
width_prefix='' | |
fi | |
while read line; do | |
extra_audio_tracks=("${extra_audio_tracks[@]}" "$width_prefix$(echo "$line" | sed -n 's/^+ \([0-9]\{1,\}\), .*$/\1/p')") | |
done < <(echo "$all_audio_tracks_info" | sed ${main_audio_track}d) | |
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/^[^,]*,//')" | |
elif [ "$copy_audio_names" ]; then | |
track_name="$(echo "$all_audio_streams_info" | | |
sed -n ${track_number}p | | |
sed -n 's/^.* \{1,\}title \{1,\}: \(.*\)$/\1/p')" | |
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 | |
audio_track_name_edits=("${audio_track_name_edits[@]}" "$track_index,$track_name" "$((track_index + 1)),$track_name") | |
fi | |
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 | |
audio_track_name_edits=("${audio_track_name_edits[@]}" "$track_index,$track_name") | |
fi | |
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 | |
elif [ "$find_forced" ]; then | |
subtitle_track_list='scan' | |
fi | |
if [ "$add_all_subtitles" ]; then | |
if [ "$burned_subtitle_track" ]; then | |
tracks_info="$(echo "$all_subtitle_tracks_info" | sed ${burned_subtitle_track}d)" | |
else | |
tracks_info="$all_subtitle_tracks_info" | |
fi | |
while read line; do | |
extra_subtitle_tracks=("${extra_subtitle_tracks[@]}" "$(echo "$line" | sed -n 's/^+ \([0-9]\{1,\}\), .*$/\1/p')") | |
done < <(echo "$tracks_info") | |
fi | |
forced_subtitle_track_number='' | |
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,' ]] && [ ! "$find_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_number="$track_id" | |
else | |
forced_subtitle_track_number="$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" | |
elif [ "$find_forced" ]; then | |
subtitle_options="$subtitle_options --subtitle-forced" | |
if [ "$find_forced" == 'burn' ]; then | |
subtitle_options="$subtitle_options --subtitle-burned" | |
else | |
subtitle_options="$subtitle_options --subtitle-default" | |
fi | |
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 [ ! "$find_forced" ]; then | |
if [ "$container_format" == 'mkv' ]; then | |
forced_subtitle_track_number="$track_id" | |
else | |
forced_subtitle_track_number="$track_index" | |
fi | |
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 [ "$chapter_names_file" ]; then | |
markers_options='--markers=' | |
else | |
markers_options='--markers' | |
fi | |
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 "markers_options = $markers_options" >&2 | |
echo "chapter_names_file = $chapter_names_file" >&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_options" | sed 's/ *$//;s/ \{1,\}/ /g')" | |
if [ "$chapter_names_file" ]; then | |
command="$command$(escape_string "$chapter_names_file")" | |
fi | |
command="$command $(echo "--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_index="$(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_index --set name=$(escape_string "$track_name") $(escape_string "$output")" | |
else | |
echo "[ -f $(escape_string "$output") ] && mp4track --track-index $track_index --hdlrname $(escape_string "$track_name") $(escape_string "$output")" | |
echo "[ -f $(escape_string "$output") ] && mp4track --track-index $track_index --udtaname $(escape_string "$track_name") $(escape_string "$output")" | |
fi | |
done | |
if [ "$forced_subtitle_track_number" ]; then | |
if [ "$container_format" == 'mkv' ]; then | |
echo "[ -f $(escape_string "$output") ] && mkvpropedit --quiet --edit track:s$forced_subtitle_track_number --set flag-default=1 --set flag-forced=1 $(escape_string "$output")" | |
else | |
echo "[ -f $(escape_string "$output") ] && mp4track --track-index $forced_subtitle_track_number --enabled true $(escape_string "$output")" | |
fi | |
elif [ "$find_forced" == 'add' ] && [ "$container_format" == 'mkv' ]; then | |
echo "[ -f $(escape_string "$output") ] && [ \"\$(mkvmerge --identify-verbose $(escape_string "$output") 2>/dev/null | sed -n '/^Track ID [0-9]\\{1,\\}: subtitles .* default_track:1 .*\$/p')\" ] && mkvpropedit --quiet --edit track:s1 --set flag-forced=1 $(escape_string "$output")" | |
fi | |
exit | |
fi | |
# OUTPUT | |
# | |
if [ "$container_format" == 'mkv' ]; then | |
if [ "$find_forced" == 'add' ]; then | |
if ! $(which mkvmerge >/dev/null); then | |
die 'executable not in $PATH: mkvmerge' | |
fi | |
fi | |
tool='mkvpropedit' | |
else | |
tool='mp4track' | |
fi | |
if ( ((${#audio_track_name_edits[*]} > 0)) || [ "$forced_subtitle_track_number" ] ) && ! $(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_options"$chapter_names_file" \ | |
--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_index="$(echo "$item" | sed 's/,.*$//')" | |
track_name="$(echo "$item" | sed 's/^[^,]*,//')" | |
if [ "$container_format" == 'mkv' ]; then | |
mkvpropedit --quiet --edit track:a$track_index --set name="$track_name" "$output" || exit 1 | |
else | |
mp4track --track-index $track_index --hdlrname "$track_name" "$output" && | |
mp4track --track-index $track_index --udtaname "$track_name" "$output" || exit 1 | |
fi | |
done | |
if [ "$forced_subtitle_track_number" ]; then | |
if [ "$container_format" == 'mkv' ]; then | |
mkvpropedit --quiet --edit track:s$forced_subtitle_track_number --set flag-default=1 --set flag-forced=1 "$output" || exit 1 | |
else | |
mp4track --track-index $forced_subtitle_track_number --enabled true "$output" || exit 1 | |
fi | |
elif [ "$find_forced" == 'add' ] && [ "$container_format" == 'mkv' ] && [ "$(mkvmerge --identify-verbose "$output" 2>/dev/null | sed -n '/^Track ID [0-9]\{1,\}: subtitles .* default_track:1 .*$/p')" ]; then | |
mkvpropedit --quiet --edit track:s1 --set flag-forced=1 "$output" || exit 1 | |
fi | |
fi | |
} |
Hey @donmelton... thanks for making this, it's extremely useful.
One question — with the default settings, I'm seeing a lot of these warnings:
x264 [warning]: VBV underflow
Any idea if this is something I should worry about? Tried googling it, which just confused me... seems like an ffmpeg thing? I tried messing with the --max and --vbv-bufsize options, but wasn't able to find a setting that got rid of the errors.
Hi,
I just installed this script and handbrakecli and I get the error "transcode-video.sh: can't determine HandBrakeCLI version" and just dumps me back to the prompt. I have HandBrake svn7212 (2015052001) - Darwin x86_64, and I have it stored in /usr/local/bin which is in the $path as suggested. Any assistance is appreciated.
The --output option to specify where to save the compressed file doesn't seem to be working. No matter what I use, it names the file with the basename.mp4 in the directory that the script is called from.
I'm trying to script it by placing the output in the same folder as the input. A lot of my files have the same name in different directories.
@donmelton I'm having the same problem as @69dragons.
I'm running on Linux Debian jessie with HandBrakeCLI rev0 (2014100499) - Linux x86_64
from package handbrake-cli 0.9.9+svn6422+dfsg1-2
.
It looks like the regexes you're using for $release_version
don't match the output from the version I have.
$ HandBrakeCLI --preset-list 2>&1 | head -n1
HandBrake rev0 (2014100499) - Linux x86_64 - http://handbrake.fr
After release of HandBrake version 1, "can't determine HandBrakeCLI version" appears.
Hi, would it be possible to use the "-N" option of HandBrakeCLI instead of "--subtitle" ?
This would be easier because you wouldn't first have to search for the number of the subtitle but instead you can simply specify your language?
From the CLIGuide: