1 # Author: Martin Matusiak <numerodix@gmail.com>
2 # Licensed under the GNU Public License, version 3.
12 our @EXPORT_OK = qw($suite $defaults $tools);
27 examine_dvd_for_titlecount
43 tool_name
=> basename
(grep(-l
, $0) ?
readlink $0 : $0),
49 dvd_device
=> "/dev/dvd",
50 disc_image
=> "disc.iso",
51 mencoder_source
=> "disc.iso",
53 framesize_baseline
=> 720*576*(2/3)^2, # frame size in pixels
55 h264_1pass_bpp
=> .195,
56 h264_2pass_bpp
=> .150,
58 xvid_1pass_bpp
=> .250,
59 xvid_2pass_bpp
=> .200,
65 my @videoutils = qw(lsdvd mencoder mplayer);
66 my @shellutils = qw(awk bash bc grep egrep getopt mount ps sed xargs);
67 my @coreutils = qw(cat date dd dirname head mkdir mv nice readlink rm seq sleep sort tail tr true);
68 my @extravideoutils = qw(mp4creator mkvmerge ogmmerge vobcopy);
70 my @mencoder_acodecs = qw(copy faac lavc mp3lame);
71 my @mencoder_vcodecs = qw(copy lavc x264 xvid);
73 my @mplayer_acodecs = qw(ac3);
74 my @mplayer_vcodecs = qw(mpeg-2);
90 while ($s =~ m/(%%%.*?%%%)/g) {
91 $ms .= $p->(substr($s, 0, @
-[0]));
93 $s = substr($s, @
+[0]);
98 print $p->("Error:") . " $ms\n";
109 my ($width, $side, $s, $fill) = @_;
111 my $trunc_len = length($s) - $width;
112 $s = substr($s, 0, $width);
114 substr($s, length($s) - length($fill), length($fill), $fill)
115 if (($trunc_len > 0) and $fill);
117 my $pad_len = abs($width - length($s));
118 my $pad = " " x
$pad_len;
120 $s = $pad . $s if $side == -1;
121 $s = $s . $pad if $side == 1;
129 foreach my $this (@these) {
130 if (ref $this eq "ARRAY") {
131 print "dump: ".join(" , ", @
$this)."\n";
144 } elsif (ref $this eq "ARRAY") {
145 [map deep_copy
($_), @
$this];
146 } elsif (ref $this eq "HASH") {
147 +{map { $_ => deep_copy
($this->{$_}) } keys %$this};
148 } else { die "what type is $_?" }
152 # create directory for logging
154 if (! -e
$defaults->{logdir
} and ! mkdir($defaults->{logdir
})) {
155 fatal
("Could not write to %%%$ENV{PWD}%%%, exiting");
156 } elsif (-e
$defaults->{logdir
} and ! -w
$defaults->{logdir
}) {
157 fatal
("Logging directory %%%".$defaults->{logdir
}."%%% is not writable");
161 # extremely suspicious
165 my ($out, $exit, $err);
166 print join(' ', @_)."\n" if $ENV{"DEBUG"};
169 my $pid = open3
(\
*WRITER
, \
*READER
, \
*ERROR
, @args);
173 while (my $output = <READER
>) { $out .= $output; }
174 while (my $output = <ERROR
>) { $err .= $output; }
179 return ($out, $exit, $err);
182 # check for missing dependencies
186 print " * Checking for tool support...\n" if $verbose;
187 foreach my $tool (@videoutils, @shellutils, @coreutils, @extravideoutils) {
188 my ($tool_path, $exit, $err) = run
("which", $tool);
189 $tools->{$tool} = $tool_path;
191 print " " . s_ok
("*") . " $tool_path\n" if $verbose;
193 print " " . s_wa
("*") . " $tool missing\n" if $verbose;
203 print " * Checking for $tool $type codec support...\n";
205 unshift(@args, $tools->{$tool});
206 my ($out, $exit, $err) = run
(@args);
207 foreach my $codec (@
$codecs) {
208 if ($out . $err =~ /$codec/i) {
209 print " " . s_ok
("*") . " $codec\n";
211 print " " . s_wa
("*") . " $codec missing\n";
217 codec_check
("audio", \
@mplayer_acodecs, "mplayer", qw(-ac help));
218 codec_check
("video", \
@mplayer_vcodecs, "mplayer", qw(-vc help));
219 codec_check
("audio", \
@mencoder_acodecs, "mencoder", qw(-oac help));
220 codec_check
("video", \
@mencoder_vcodecs, "mencoder", qw(-ovc help));
224 # print standard common banner
225 sub print_tool_banner
{
226 print "{( --- " . $suite->{tool_name
} . " " . $suite->{version
} . " --- )}\n";
229 # print package version and versions of tools
236 my ($tool_path, $exit) = run
("which", $tool);
238 print " [" . s_err
("!") . "] $tool missing\n";
240 unshift(@args, $tool_path);
241 my ($out, $exit, $err) = run
(@args);
242 my $version = $1 if ($out . $err) =~ /$re/ms;
243 print " [" . s_ok
("*") . "] $tool $version\n";
246 print $suite->{name
} . " " . $suite->{version
} . "\n";
247 check_tool
("mplayer", "^MPlayer ([^ ]+)", qw());
248 check_tool
("mencoder", "^MEncoder ([^ ]+)", qw(-oac help));
249 check_tool
("lsdvd", "^lsdvd ([^ ]+)", qw(-V));
250 check_tool
("vobcopy", "^Vobcopy ([^ ]+)", qw(--version));
251 check_tool
("mp4creator", ".* version ([^ ]+)", qw(-version));
252 check_tool
("mkvmerge", "^mkvmerge ([^ ]+)", qw(--version));
253 check_tool
("ogmmerge", "^ogmmerge ([^ ]+)", qw(--version));
257 # compute bits per pixel
263 my $video_size = shift; # in mb
264 my $bitrate = shift; # kbps
267 $bitrate = $bitrate * 1024;
269 $video_size = $video_size * 1024 * 1024;
270 $bitrate = (8 * $video_size)/( $length != 0 ?
$length : 1 );
272 my $bpp = ($bitrate)/( $width*$height*$fps != 0 ?
$width*$height*$fps : 1);
277 # set bpp based on the codec and number of passes
279 my ($video_codec, $passes) = @_;
282 if ($video_codec eq "h264") {
283 $bpp = $defaults->{h264_1pass_bpp
} if $passes == 1;
284 $bpp = $defaults->{h264_2pass_bpp
} if $passes > 1;
286 $bpp = $defaults->{xvid_1pass_bpp
} if $passes == 1;
287 $bpp = $defaults->{xvid_2pass_bpp
} if $passes > 1;
293 # set the number of passes based on codec and bpp
295 my ($video_codec, $bpp) = @_;
298 if ($video_codec eq "h264") {
299 $passes = 2 if $bpp < $defaults->{h264_1pass_bpp
};
301 $passes = 2 if $bpp < $defaults->{xvid_1pass_bpp
};
307 # compute video bitrate based on title length
308 sub compute_vbitrate
{
309 my ($width, $height, $fps, $bpp) = @_;
311 my $bitrate = int( ($width * $height * $fps * $bpp) / 1024);
316 # extract number of titles from dvd
317 sub examine_dvd_for_titlecount
{
320 my @args = ($tools->{mplayer
}, "-ao", "null", "-vo", "null");
321 push(@args, "-frames", "0", "-identify");
322 push(@args, "-dvd-device", $source, "dvd://");
324 my ($out, $exit, $err) = run
(@args);
325 my $titles = $1 if ($out . $err) =~ /^ID_DVD_TITLES=([^\s]+)/ms;
330 # extract information from file or dvd title
333 my $dvd_device = shift;
335 my @source = ($file);
337 push (@source, "-dvd-device", $dvd_device);
339 my @args = ($tools->{mplayer
}, "-ao", "null", "-vo", "null");
340 push(@args, "-frames", "0", "-identify");
341 push(@args, @source);
343 my ($out, $exit, $err) = run
(@args);
350 my @match = map { /^${re}$/ } split('\n', $s);
352 @match = sort {$b <=> $a} @match;
353 return shift(@match);
354 } else { return $default; }
360 width
=> find
(0, $s, "ID_VIDEO_WIDTH=(.+)"),
361 height
=> find
(0, $s, "ID_VIDEO_HEIGHT=(.+)"),
362 fps
=> find
(0, $s, "ID_VIDEO_FPS=(.+)"),
363 length => find
(0, $s, "ID_LENGTH=(.+)"),
364 abitrate
=> find
(0, $s, "ID_AUDIO_BITRATE=(.+)"),
365 aformat
=> lc(find
(0, $s, "ID_AUDIO_CODEC=(.+)")),
366 vbitrate
=> find
(0, $s, "ID_VIDEO_BITRATE=(.+)"),
367 vformat
=> lc(find
(0, $s, "ID_VIDEO_FORMAT=(.+)")),
370 $data->{abitrate
} = int($data->{abitrate
} / 1024); # to kbps
371 $data->{vbitrate
} = int($data->{vbitrate
} / 1024); # to kbps
372 $data->{bpp
} = compute_bpp
($data->{width
}, $data->{height
}, $data->{fps
},
373 $data->{len
}, 0, $data->{vbitrate
});
376 $data->{filesize
} = int(
377 ($data->{abitrate
} + $data->{vbitrate
}) * $data->{length} / 8 / 1024);
379 $data->{filesize
} = int( (stat($file))[7] / 1024 / 1024 );
385 # figure out how much to crop
387 my ($file, $dvd_device) = @_;
389 my @source = ($file);
391 push (@source, "-dvd-device", $dvd_device);
393 my @args = ($tools->{mplayer
}, "-quiet", "-ao", "null", "-vo", "null");
394 push(@args, "-fps", "10000", "-vf", "cropdetect");
395 push(@args, @source);
397 my ($out, $exit, $err) = run
(@args);
399 my @cropdata = map { /^(\[CROP\].*)$/ } split("\n", $out . $err);
400 my $cropline = pop(@cropdata);
402 my ($w, $h, $x, $y) =
403 map { /-vf crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)/ } $cropline;
405 my @cropfilter = ("-vf", "crop=$w:$h:$x:$y");
407 return ($w, $h, @cropfilter);
410 # set formatting of bpp output depending on value
413 my $video_codec = shift;
415 if (($video_codec =~ "(h264|avc)")) {
416 if ($bpp < $defaults->{h264_2pass_bpp
}) {
418 } elsif ($bpp > $defaults->{h264_1pass_bpp
}) {
423 } elsif (($video_codec =~ "xvid")) {
424 if ($bpp < $defaults->{xvid_2pass_bpp
}) {
426 } elsif ($bpp > $defaults->{xvid_1pass_bpp
}) {
438 # print one line of title display, whether header or not
439 sub print_title_line
{
440 my $is_header = shift;
443 my ($dim, $fps, $length, $bpp, $passes, $vbitrate, $vformat, $abitrate, $aformat);
444 my ($filesize, $filename);
452 $vbitrate = "vbitrate";
454 $abitrate = "abitrate";
459 my $x = $data->{width
} > 0 ?
$data->{width
} : "";
460 my $y = $data->{height
} > 0 ?
$data->{height
} : "";
461 $dim = $x."x".$y ne "x" ?
$x."x".$y : "";
462 $fps = $data->{fps
} > 0 ?
$data->{fps
} : "";
463 $length = $data->{length} > 0 ?
int($data->{length} / 60) : "";
464 $bpp = $data->{bpp
} < 1 ?
substr($data->{bpp
}, 1) : $data->{bpp
};
465 $passes = $data->{passes
} > 0 ?
$data->{passes
} : "";
466 $vbitrate = $data->{vbitrate
} > 0 ?
$data->{vbitrate
} : "";
467 $vformat = $data->{vformat
} ne "0" ?
$data->{vformat
} : "";
468 $abitrate = $data->{abitrate
} > 0 ?
$data->{abitrate
} : "";
469 $aformat = $data->{aformat
} ne "0" ?
$data->{aformat
} : "";
470 $filesize = $data->{filesize
};
471 $filename = $data->{filename
};
474 $dim = trunc
(9, -1, $dim);
475 $fps = trunc
(6, -1, $fps);
476 $length = trunc
(3, -1, $length);
477 $bpp = trunc
(4, 1, $bpp);
478 $passes = trunc
(1, -1, $passes);
479 $vbitrate = trunc
(4, -1, $vbitrate);
480 $vformat = trunc
(4, -1, $vformat);
481 $abitrate = trunc
(4, -1, $abitrate);
482 $aformat = trunc
(4, -1, $aformat);
483 $filesize = trunc
(4, -1, $filesize);
485 if ($filename =~ /dvd:\/\
//) {
486 $filesize = s_est
($filesize);
489 $bpp = markup_bpp
($bpp, $vformat) unless $is_header;
491 my $line = "$dim $fps $length $bpp $passes $vbitrate $vformat "
492 . "$abitrate $aformat $filesize $filename\n";
493 $line = s_b
($line) if $is_header;
497 # compute title scaling
499 my ($width, $height, $custom_scale) = @_;
501 my ($nwidth, $nheight) = ($width, $height);
503 if ($custom_scale ne "off") { # scaling isn't disabled
505 # scale to the width given by user (upscaling permitted)
510 if ($custom_scale =~ /^([0-9]+)$/) {
512 } elsif ($custom_scale =~ /^([0-9]*):([0-9]*)$/) {
513 ($nwidth, $nheight) = ($1, $2);
515 fatal
("Failed to read a pair of positive integers from scaling "
516 . "%%%$custom_scale%%%");
519 if ( $nwidth > 0 and ! $nheight > 0) {
520 $nheight = int($height * $nwidth / ($width > 0 ?
$width : 1) );
521 } elsif (! $nwidth > 0 and $nheight > 0) {
522 $nwidth = int($width * $nheight / ($height > 0 ?
$height : 1) );
525 # apply default scaling heuristic
527 # compute scaling factor based on baseline value
528 my $framesize = $width*$height > 0 ?
$width*$height : 1;
529 my $factor = sqrt($defaults->{framesize_baseline
}/$framesize);
531 # scale by factor, do not upscale
533 $nwidth = int($width*$factor);
534 $nheight = int($height*$factor);
538 # dimensions have been changed, make sure they are multiples of 16
539 ($nwidth, $nheight) = scale_by_x
($width, $height, $nwidth, $nheight);
541 # make sure the new dimensions are sane
542 if ($nwidth * $nheight <= 0) {
543 ($nwidth, $nheight) = ($width, $height);
547 return ($nwidth, $nheight);
550 # scale dimensions to nearest (lower/upper) multiple of 16
552 my ($orig_width, $orig_height, $width, $height) = @_;
555 # if the original dimensions are not multiples of 16, no amount of scaling
556 # will bring us to an aspect ratio where the smaller dimensions are
557 if (($orig_width % $divisor) + ($orig_height % $divisor) != 0) {
558 $width = $orig_width;
559 $height = $orig_height;
563 while (! $completed) {
566 my $up_step = $width + ($step * $divisor);
567 my $down_step = $width - ($step * $divisor);
568 foreach my $x_step ($up_step, $down_step) {
569 my $x_width = int($x_step - ($x_step % $divisor));
570 my $x_height = int($x_width *
571 ($orig_height/ ($orig_width > 0 ?
$orig_width : 1) ));
572 if (($x_width % $divisor) + ($x_height % $divisor) == 0) {
581 return ($width, $height);
584 # compute size of media given length and bitrate
585 sub compute_media_size
{
586 my ($length, $bitrate) = @_;
587 return ($bitrate / 8) * ($length / 1024);
590 # get container options and decide on codecs
591 sub set_container_opts
{
592 my ($acodec, $vcodec, $container) = @_;
594 my $audio_codec = "mp3";
595 my $video_codec = "h264";
599 if ($container =~ /(avi|mkv|ogm)/) {
600 } elsif ($container eq "mp4") {
601 $audio_codec = "aac";
602 $video_codec = "h264";
606 if ($container =~ "(asf|au|dv|flv|ipod|mov|mpg|nut|rm|swf)") {
608 @opts = ("lavf", "-lavfopts", "format=$container");
610 if ($container eq "flv") {
611 $audio_codec = "mp3";
612 $video_codec = "flv";
615 fatal
("Unrecognized container %%%$container%%%");
619 $audio_codec = $acodec if $acodec;
620 $video_codec = $vcodec if $vcodec;
622 return ($audio_codec, $video_codec, $ext, @opts);
625 # get audio codec options
626 sub set_acodec_opts
{
627 my ($container, $codec, $orig_bitrate, $get_bitrate) = @_;
630 if ($container eq "flv"){
631 push(@opts, "-srate", "44100"); # flv supports 44100, 22050, 11025
635 if ($codec eq "copy") {
636 $bitrate = $orig_bitrate;
638 } elsif ($codec eq "mp3") {
640 push(@opts, "mp3lame", "-lameopts", "vbr=3:$bitrate:q=3");
641 } elsif ($codec eq "aac") {
643 push(@opts, "faac", "-faacopts", "br=$bitrate:mpeg=4:object=2",
648 $bitrate = 224; # mencoder manpage default
649 my $cs = "ac3|flac|g726|libamr_nb|libamr_wb|mp2|roq_dpcm|sonic|sonicls|"
650 . "vorbis|wmav1|wmav2";
651 if ($codec =~ /($cs)/) {
652 push(@opts, "lavc", "-lavcopts",
653 "abitrate=$bitrate:acodec=$codec");
655 fatal
("Unrecognized audio codec %%%$codec%%%");