Prevent grep failing over binary data in logfile
[undvd.git] / lib.sh
blob7cbebaad23e7742edbf3c536c69ee7dfb32966e9
1 # Author: Martin Matusiak <numerodix@gmail.com>
2 # Licensed under the GNU Public License, version 3.
4 ### DECLARATIONS
6 # undvd version
7 version=0.4.4
9 # initialize colors if the terminal can support them
10 if [[ "$TERM" && "$TERM" != "dumb" ]]; then
11 p=$(dirname $(readlink -f $0)); . $p/colors.sh
14 # constants
15 nominal_width="720"
16 nominal_height="576"
17 standard_ratio="2/3"
18 scale_baseline="$nominal_width*$nominal_height*($standard_ratio)^2" # in pixels
20 h264_1pass_bpp=.195
21 h264_2pass_bpp=.150
23 xvid_1pass_bpp=.250
24 xvid_2pass_bpp=.200
26 standard_audio_bitrate=160
28 # audio encoding options
29 lame="mp3lame -lameopts vbr=2:q=3"
31 # codec defaults
32 video_codec="h264"
33 acodec="$lame"
35 # mplayer filters
36 prescale=
37 postscale=
39 # sources
40 dvd_device="/dev/dvd"
41 disc_image="disc.iso"
42 mencoder_source="$disc_image"
44 # seconds to pause between updating rip status line
45 timer_refresh=5
47 # tools we need
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"
55 mplayer_acodecs="ac3"
56 mplayer_vcodecs="mpeg-2"
59 ### FUNCTIONS
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() {
69 local verbose="$1"
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"
79 eval "$tool=$path"
80 done
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() {
92 local type="$1"
93 local cmd="$2"
94 local arg="$3"
95 local codecs="$4"
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"
102 else
103 echo -e " ${ok}*${r} $codec"
105 done
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;
115 echo "
116 echo -en \${e};
117 opts=\`\$getopt -o \"$shorts\" --long \"$longs\" -n \"$tool_name\" -- \"\$@\"\`;
118 if [ \$? != 0 ]; then
119 echo -en \${r};
120 echo -e \"\$usage\";
121 exit 1;
122 else
123 echo -en \${r};
125 eval set -- \$opts"
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"
136 else
137 echo "$str_key $value"
141 # clone disc to iso image
142 function clone_dd() {
143 local dvd_device="$1"
144 local img="$2"
146 cmd="time \
147 $nice -n20 \
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)
156 local dir="$2"
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
168 cmd="time \
169 $nice -n20 \
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() {
176 local file="$1"
177 local mencoder_source="$2"
178 local title="$3"
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
198 length=-1
199 else
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
205 bitrate=1
206 else
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
212 format=0
213 else
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"
223 local title="$2"
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() {
240 local width="$1"
241 local height="$2"
242 local fps="$3"
243 local length="$4"
244 local video_size="$5" # in mb
245 local bitrate="$6" # kbps
247 if [[ "$bitrate" ]]; then
248 bitrate=$( echo "scale=5; $bitrate*1024" | $bc )
249 else
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 )
255 echo $bpp
258 # set bpp based on the codec and number of passes
259 function set_bpp() {
260 local video_codec="$1"
261 local twopass="$2"
263 if [[ "$video_codec" = "h264" ]]; then
264 local bpp="$h264_1pass_bpp"
265 [[ "$twopass" ]] && bpp="$h264_2pass_bpp"
266 else
267 local bpp="$xvid_1pass_bpp"
268 [[ "$twopass" ]] && bpp="$xvid_2pass_bpp"
271 echo $bpp
274 # set the number of passes based on codec and bpp
275 function set_passes() {
276 local video_codec="$1"
277 local bpp="$2"
279 local passes=1
281 if [[ "$video_codec" = "h264" ]]; then
282 [[ "$bpp" < "$h264_1pass_bpp" ]] && passes=2
283 else
284 [[ "$bpp" < "$xvid_1pass_bpp" ]] && passes=2
287 echo $passes
290 # compute video bitrate based on title length
291 function compute_bitrate() {
292 local width="$1"
293 local height="$2"
294 local fps="$3"
295 local length="$4" # in seconds
296 local bpp="$5"
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 )
302 echo $bitrate
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 )
312 # display a title
313 function display_title() {
314 local width="$1"
315 local height="$2"
316 local fps="$3"
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
320 local passes="$7"
321 local format="$8"
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
331 function fill() {
332 str=${1:0:$2}
333 local f=$(( $2-${#1} ))
334 pad=""
335 for i in $($seq 1 $f); do
336 pad=" $pad"
337 done
338 echo "$pad$str"
341 # set formatting of bpp output depending on value
342 function format_bpp() {
343 local bpp="$1"
344 local video_codec="$2"
346 if [[ "$video_codec" = "h264" ]]; then
347 if [[ "$bpp" < "$h264_2pass_bpp" ]]; then
348 bpp="${e}$bpp${r}"
349 elif [[ "$bpp" > "$h264_1pass_bpp" ]]; then
350 bpp="${wa}$bpp${r}"
351 else
352 bpp="${bb}$bpp${r}"
354 elif [[ "$video_codec" = "xvid" ]]; then
355 if [[ "$bpp" < "$xvid_2pass_bpp" ]]; then
356 bpp="${e}$bpp${r}"
357 elif [[ "$bpp" > "$xvid_1pass_bpp" ]]; then
358 bpp="${wa}$bpp${r}"
359 else
360 bpp="${bb}$bpp${r}"
362 else
363 bpp="${b}$bpp${r}"
366 echo "$bpp"
369 # print one line of title display, whether header or not
370 function display_title_line() {
371 local header="$1"
372 local dimensions="$2"
373 local fps="$3"
374 local length="$4"
375 local bpp="$5"
376 local bitrate="$6"
377 local passes="$7"
378 local format="$8"
379 local filesize="$9"
380 local filename="${10}"
382 if [[ "$header" ]]; then
383 dimensions="dim"
384 fps="fps"
385 length="len"
386 bpp="bpp"
387 bitrate="bitrate"
388 passes="p"
389 format="codec"
390 filesize="size"
391 filename="title"
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)
404 fps=$(fill "$fps" 6)
405 length=$(fill "$length" 3)
406 bpp=$(fill "$bpp" 4)
407 bitrate=$(fill "$bitrate" 4)
408 passes=$(fill "$passes" 1)
409 format=$(fill "$format" 4)
410 filesize=$(fill "$filesize" 4)
412 pre=
413 post=
414 if [[ "$header" ]]; then
415 pre="${b}"
416 post="${r}"
417 else
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() {
425 local width="$1"
426 local height="$2"
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
439 else
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"
452 # scale by factor
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
478 function scale16() {
479 local orig_width="$1"
480 local orig_height="$2"
481 local width="$3"
482 local height="$4"
483 local divisor=16
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
488 width="$orig_width"
489 height="$orig_height"
490 else
491 local ratio="$orig_height/$orig_width"
493 step=-1
494 unset completed
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
504 completed="y"
505 width=$x_width
506 height=$x_height
508 done
509 done
512 echo "$width $height"
515 # get video codec options
516 function vcodec_opts() {
517 local codec="$1"
518 local twopass="$2"
519 local pass="$3"
520 local bitrate="$4"
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
529 opts="pass=2:$opts"
533 opts="x264 -x264encopts $opts:partitions=all:weight_b:bitrate=$bitrate:threads=auto"
534 elif [[ "$codec" = "xvid" ]]; then
535 local opts=
537 if [[ "$twopass" ]]; then
538 if [[ $pass -eq 1 ]]; then
539 opts="pass=1:"
540 elif [[ $pass -eq 2 ]]; then
541 opts="pass=2:"
545 opts="xvid -xvidencopts ${opts}bitrate=$bitrate"
547 echo $opts
550 # run encode and print updates
551 function run_encode() {
552 local cmd="$1"
553 local title="$2"
554 local twopass="$3"
555 local pass="$4"
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"
569 else
570 pass="-"
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 &
583 local pid=$!
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
594 done)
596 # Report exit code
598 wait $pid
599 if [[ $? = 0 ]]; then
600 echo -e "${status}[ ${ok}done${r} ] "
601 else
602 echo -e "${status}[ ${e}failed${r} ] check log"
607 # initialize command variables
608 init_cmds