1 # Author: Martin Matusiak <numerodix@gmail.com>
2 # Licensed under the GNU Public License, version 3.
9 # initialize colors if the terminal can support them
10 if [[ "$TERM" && "$TERM" != "dumb" ]]; then
11 p
=$
(dirname $
(readlink
-f $0)); .
$p/colors.sh
18 scale_baseline
="$nominal_width*$nominal_height*($standard_ratio)^2" # in pixels
26 standard_audio_bitrate
=160
28 # audio encoding options
29 lame
="mp3lame -lameopts vbr=2:q=3"
42 mencoder_source
="$disc_image"
44 # seconds to pause between updating rip status line
48 videoutils
="lsdvd mencoder mplayer vobcopy"
49 shellutils
="awk bash bc grep egrep getopt mount ps sed xargs"
50 coreutils
="cat date dd dirname mkdir mv nice readlink rm seq sleep tail tr"
52 mencoder_acodecs
="mp3lame"
53 mencoder_vcodecs
="xvid x264"
56 mplayer_vcodecs
="mpeg-2"
61 tool_name
=$
(basename $0)
63 function display_tool_banner
() {
64 echo -e "${h1}{( --- ${tool_name} $version --- )}${r}"
67 # check for missing dependencies
68 function init_cmds
() {
71 [[ $verbose ]] && echo -e " * Checking for tool support... "
72 for tool
in $coreutils $shellutils $videoutils; do
73 local path
=$
(which $tool 2>/dev
/null
)
74 if [[ ! "$path" && $verbose ]]; then
75 echo -e " ${wa}*${r} $tool missing"
76 elif [[ $verbose ]]; then
77 echo -e " ${ok}*${r} $path"
82 if [[ $verbose ]]; then
83 codec_check
"audio" "mencoder" "-oac help" "$mencoder_acodecs"
84 codec_check
"video" "mencoder" "-ovc help" "$mencoder_vcodecs"
85 codec_check
"audio" "mplayer" "-ac help" "$mplayer_acodecs"
86 codec_check
"video" "mplayer" "-vc help" "$mplayer_vcodecs"
90 # check for codec support in player/encoder
91 function codec_check
() {
97 echo -e " * Checking for $cmd $type codec support... "
98 for codec
in $codecs; do
99 local c
=$
($cmd $arg 2>/dev
/null |
$grep -i $codec)
100 if [[ ! "$c" ]]; then
101 echo -e " ${wa}*${r} $codec missing"
103 echo -e " ${ok}*${r} $codec"
108 # generate parse command to execute in caller
109 # note: $usage and $@ variables evaluated in calling context!
110 function get_parsecmd
() {
111 local tool_name
="$1"; shift;
112 local shorts
="$1"; shift;
113 local longs
="$1"; shift;
117 opts=\`\$getopt -o \"$shorts\" --long \"$longs\" -n \"$tool_name\" -- \"\$@\"\`;
118 if [ \$? != 0 ]; then
128 # prepend with int key if int, otherwise with string key
129 function ternary_int_str
() {
130 local value
="$1"; shift;
131 local int_key
="$1"; shift;
132 local str_key
="$1"; shift;
134 if [[ "$value" =~ ^
[0-9]+$
]]; then
135 echo "$int_key $value"
137 echo "$str_key $value"
141 # clone disc to iso image
142 function clone_dd
() {
143 local dvd_device
="$1"
148 $dd if=${dvd_device} of=$img.partial && \
149 $mv $img.partial $img"
150 ( echo "$cmd"; $bash -c "$cmd" ) &> logs
/clone.log
153 # clone encrypted disc to directory
154 function clone_vobcopy
() {
155 local dvd_device
=$
($readlink -f $1)
158 mnt_point
=$
($mount |
$grep $dvd_device |
$awk '{ print $3 }')
160 if [[ ! "$mnt_point" ]]; then
161 echo -e "\n${wa}=>${r} Your dvd device ${bb}$dvd_device${r} has to be mounted for this."
162 echo -e "${wa}=>${r} Mount the dvd and supply the device to $(basename $0), eg:"
163 echo -e " ${b}sudo mount ${bb}${dvd_device}${b} /mnt/dvd -t iso9660${r}"
164 echo -e " ${b}$(basename $0) -d ${bb}${dvd_device}${r} [${b}other options${r}]"
167 [[ -d "$dir" ]] && rm -rf $dir
170 $vobcopy -f -l -m -F 64 -i $mnt_point -t $dir"
171 ( echo "$cmd"; $bash -c "$cmd" ) &> logs
/clone.log
174 # extract information from file or dvd
175 function examine_title
() {
177 local mencoder_source
="$2"
180 local src
="\"$file\""
181 if [[ "$mencoder_source" && "$title" ]]; then
182 src
="-dvd-device \"$mencoder_source\" dvd://$title"
184 local cmd
="mplayer -ao null -vo null -frames 0 -identify $src 2>&1"
185 local mplayer_output
=$
($bash -c "$cmd")
187 local width
=$
( echo "$mplayer_output" |
$grep ID_VIDEO_WIDTH |
$sed "s|ID_VIDEO_WIDTH=\(.*\)|\1|g" )
188 [[ $?
!= 0 ||
! "$width" ||
"$width" = "0" ]] && width
=1
190 local height
=$
( echo "$mplayer_output" |
$grep ID_VIDEO_HEIGHT |
$sed "s|ID_VIDEO_HEIGHT=\(.*\)|\1|g" )
191 [[ $?
!= 0 ||
! "$height" ||
"$height" = "0" ]] && height
=1
193 local fps
=$
( echo "$mplayer_output" |
$grep ID_VIDEO_FPS |
$sed "s|ID_VIDEO_FPS=\(.*\)|\1|g" )
194 [[ $?
!= 0 ||
! "$fps" ||
"$fps" = "0.000" ]] && fps
=1
196 local length
=$
( echo "$mplayer_output" |
$grep ID_LENGTH |
$sed "s|ID_LENGTH=\(.*\)|\1|g" )
197 if [[ $?
!= 0 ||
! "$length" ||
"$length" = "0.00" ]]; then
200 length
=$
( echo "scale=0; $length/1"|
$bc )
203 local bitrate
=$
( echo "$mplayer_output" |
$grep ID_VIDEO_BITRATE |
$sed "s|ID_VIDEO_BITRATE=\(.*\)|\1|g" )
204 if [[ $?
!= 0 ||
! "$bitrate" ||
"$bitrate" = "0" ]]; then
207 bitrate
=$
( echo "scale=0; $bitrate/1"|
$bc )
210 local format
=$
( echo "$mplayer_output" |
$grep ID_VIDEO_FORMAT |
$sed "s|ID_VIDEO_FORMAT=\(.*\)|\1|g" )
211 if [[ $?
!= 0 ||
! "$format" ]]; then
214 format
=$
( echo $format |
$tr "[:upper:]" "[:lower:]" )
217 echo "$width $height $fps $length $bitrate $format"
220 # extract information from file or dvd
221 function crop_title
() {
222 local mencoder_source
="$1"
225 local src
="-dvd-device \"$mencoder_source\" dvd://$title"
226 local cmd
="mplayer -ao null -vo null -fps 10000 -vf cropdetect $src 2>&1"
227 local mplayer_output
=$
($bash -c "$cmd")
229 local crop_filter
=$
(echo "$mplayer_output" |\
230 awk '/CROP/' |
tail -n1 |
sed 's|.*(-vf crop=\(.*\)).*|\1|g')
232 local width
=$
(echo "$crop_filter" |
sed "s|\(.*\):.*:.*:.*|\1|g")
233 local height
=$
(echo "$crop_filter" |
sed "s|.*:\(.*\):.*:.*|\1|g")
235 echo "$width $height $crop_filter"
238 # compute bits per pixel
239 function compute_bpp
() {
244 local video_size
="$5" # in mb
245 local bitrate
="$6" # kbps
247 if [[ "$bitrate" ]]; then
248 bitrate
=$
( echo "scale=5; $bitrate*1024" |
$bc )
250 video_size
=$
(( $video_size *1024*1024 )) # in mb
251 bitrate
=$
( echo "scale=5; (8*$video_size)/$length" |
$bc )
253 local bpp
=$
( echo "scale=3; ($bitrate)/($width*$height*$fps)" |
$bc )
258 # set bpp based on the codec and number of passes
260 local video_codec
="$1"
263 if [[ "$video_codec" = "h264" ]]; then
264 local bpp
="$h264_1pass_bpp"
265 [[ "$twopass" ]] && bpp
="$h264_2pass_bpp"
267 local bpp
="$xvid_1pass_bpp"
268 [[ "$twopass" ]] && bpp
="$xvid_2pass_bpp"
274 # set the number of passes based on codec and bpp
275 function set_passes
() {
276 local video_codec
="$1"
281 if [[ "$video_codec" = "h264" ]]; then
282 [[ "$bpp" < "$h264_1pass_bpp" ]] && passes
=2
284 [[ "$bpp" < "$xvid_1pass_bpp" ]] && passes
=2
290 # compute video bitrate based on title length
291 function compute_bitrate
() {
295 local length
="$4" # in seconds
297 local output_size
="$6" # in mb
298 local audio_bitrate
=$
(( $standard_audio_bitrate * 1024 )) # kbps
300 local bitrate
=$
( echo "scale=0; ($width*$height*$fps*$bpp)/1024." |
$bc )
305 # compute size of media given length and bitrate
306 function compute_media_size
() {
307 local length
="$1" # in seconds
308 local bitrate
="$2" # kbps
309 echo $
( echo "scale=0; ($bitrate/8)*$length/1024" |
$bc )
313 function display_title
() {
317 local length
="$4" # in seconds
318 local bpp
=$
( echo "scale=3; $5/(1)" |
$bc )
319 local bitrate
=$
( echo "scale=0; $6/(1)" |
$bc ) # kbps
322 local filesize
=$
( echo "scale=0; $9/(1)" |
$bc ) # in mb
323 local filename
="${10}"
325 [[ "$length" != "-1" ]] && length
=$
( echo "scale=0; $length/60" |
$bc )
327 display_title_line
"" "${width}x${height}" "$fps" "$length" "$bpp" "$bitrate" "$passes" "$format" "$filesize" "$filename"
330 # truncate string and pad with whitespace to fit the desired length
333 local f
=$
(( $2-${#1} ))
335 for i
in $
($seq 1 $f); do
341 # set formatting of bpp output depending on value
342 function format_bpp
() {
344 local video_codec
="$2"
346 if [[ "$video_codec" = "h264" ]]; then
347 if [[ "$bpp" < "$h264_2pass_bpp" ]]; then
349 elif [[ "$bpp" > "$h264_1pass_bpp" ]]; then
354 elif [[ "$video_codec" = "xvid" ]]; then
355 if [[ "$bpp" < "$xvid_2pass_bpp" ]]; then
357 elif [[ "$bpp" > "$xvid_1pass_bpp" ]]; then
369 # print one line of title display, whether header or not
370 function display_title_line
() {
372 local dimensions
="$2"
380 local filename
="${10}"
382 if [[ "$header" ]]; then
394 [[ "$dimensions" = "1x1" ]] && unset dimensions
395 [[ "$fps" = "1" ]] && unset fps
396 [[ "$length" = "-1" ]] && unset length
397 [[ "$bpp" = "0" ]] && unset bpp
398 [[ "$bitrate" = "0" ]] && unset bitrate
399 [[ "$filesize" = "-1" ]] && unset filesize
400 [[ "$format" = "0" ]] && unset format
401 [[ "$passes" = "0" ]] && unset passes
403 dimensions
=$
(fill
"$dimensions" 9)
405 length
=$
(fill
"$length" 3)
407 bitrate
=$
(fill
"$bitrate" 4)
408 passes
=$
(fill
"$passes" 1)
409 format
=$
(fill
"$format" 4)
410 filesize
=$
(fill
"$filesize" 4)
414 if [[ "$header" ]]; then
418 bpp
=$
(format_bpp
"$bpp" "$8")
420 echo -e "${pre}$dimensions $fps $length $bpp $bitrate $passes $format $filesize $filename${post}"
423 # compute title scaling
424 function title_scale
() {
427 local custom_scale
="$3"
429 local nwidth
="$width"
430 local nheight
="$height"
431 if [[ "$custom_scale" != "0" ]]; then # scaling isn't disabled
433 # scale to the width given by user (upscaling permitted)
434 if [[ "$custom_scale" ]]; then
435 nwidth
=$
(( $width * $custom_scale/$width ))
436 nheight
=$
(( $height * $custom_scale/$width ))
438 # apply default scaling heuristic
440 # compute scaling factor based on baseline value
441 local sbaseline
="$scale_baseline"
442 local scurrent
="$width*$height"
443 local sfactor
="sqrt($sbaseline/($scurrent))"
445 # evaluate factor*1000 to integer value
446 local factor=$
( echo "scale=40; $sfactor*1000/1" |
$bc )
447 local factor=$
( echo "scale=0; $factor/1" |
$bc )
449 # if multiplier is less than 1 we will downscale
450 (( $factor < 1000 )) && local need_scaling
="y"
453 if [[ "$need_scaling" ]]; then
454 nwidth
=$
( echo "scale=40; $width*$sfactor" |
$bc )
455 nwidth
=$
( echo "scale=0; ($nwidth+1)/1" |
$bc )
456 nheight
=$
( echo "scale=40; $height*$sfactor" |
$bc )
457 nheight
=$
( echo "scale=0; ($nheight+1)/1" |
$bc )
461 # dimensions have been changed, make sure they are multiples of 16
462 scale_info
=( $
(scale16
"$width" "$height" "$nwidth" "$nheight") )
463 nwidth
=${scale_info[0]}
464 nheight
=${scale_info[1]}
466 # make sure the new dimensions are sane
467 if (( $nwidth * $nheight <= 0 )); then
468 local nwidth
="$width"
469 local nheight
="$height"
473 echo "$nwidth $nheight"
477 # scale dimensions to nearest (lower/upper) multiple of 16
479 local orig_width
="$1"
480 local orig_height
="$2"
485 # if the original dimensions are not multiples of 16, no amount of scaling
486 # will bring us to an aspect ratio where the smaller dimensions are
487 if (( ($orig_width%$divisor) + ($orig_height%$divisor) != 0 )); then
489 height
="$orig_height"
491 local ratio
="$orig_height/$orig_width"
495 while [[ ! "$completed" ]]; do
496 step
=$
(( $step + 1 ))
498 local up_step
=$
(( $width + ($step * $divisor) ))
499 local down_step
=$
(( $width - ($step * $divisor) ))
500 for x_step
in $down_step $up_step; do
501 local x_width
=$
(( $x_step - ($x_step % $divisor) ))
502 local x_height
=$
( echo "scale=0; $x_width*$ratio/1" |
$bc )
503 if (( ($x_width % $divisor) + ($x_height % $divisor) == 0 )); then
512 echo "$width $height"
515 # get video codec options
516 function vcodec_opts
() {
522 if [[ "$codec" = "h264" ]]; then
523 local opts
="subq=5:frameref=2"
525 if [[ "$twopass" ]]; then
526 if [[ $pass -eq 1 ]]; then
527 opts
="pass=1:subq=1:frameref=1"
528 elif [[ $pass -eq 2 ]]; then
533 opts
="x264 -x264encopts $opts:partitions=all:weight_b:bitrate=$bitrate:threads=auto"
534 elif [[ "$codec" = "xvid" ]]; then
537 if [[ "$twopass" ]]; then
538 if [[ $pass -eq 1 ]]; then
540 elif [[ $pass -eq 2 ]]; then
545 opts
="xvid -xvidencopts ${opts}bitrate=$bitrate"
550 # run encode and print updates
551 function run_encode
() {
557 # Set output and logging depending on number of passes
559 local output_file
="${title}.avi.partial"
560 local logfile
="logs/${title}.log"
562 if [[ "$twopass" ]]; then
563 if [[ $pass -eq 1 ]]; then
564 output_file
="/dev/null"
565 logfile
="$logfile.pass1"
566 elif [[ $pass -eq 2 ]]; then
567 logfile
="$logfile.pass2"
573 cmd
="$cmd -o $output_file"
575 # Print initial status message
577 local status
="${r}[$pass] Encoding, to monitor log: tail -F $logfile "
578 echo -en "${status}\r"
580 # Execute encoder in the background
582 ( echo $cmd; $bash -c "$cmd" ) &> $logfile &
585 # Write mencoder's ETA estimate
587 local start_time
=$
($date +%s
)
588 (while $ps $pid &> /dev
/null
; do
589 local eta
=$
([[ -e $logfile ]] && $tail -n15 $logfile | \
590 $grep -a "Trem:" |
$tail -n1 |
$sed 's|.*\( .*min\).*|\1|g' |
$tr " " "-")
591 local ela
=$
(( ( $
($date +%s
) - $start_time ) / 60 ))
592 echo -ne "${status}${cela}+${ela}min${r} ${ceta}${eta}${r} \r"
593 $sleep $timer_refresh
599 if [[ $?
= 0 ]]; then
600 echo -e "${status}[ ${ok}done${r} ] "
602 echo -e "${status}[ ${e}failed${r} ] check log"
607 # initialize command variables