add license file
[undvd.git] / undvd
blob6c7d9060273ccf8ead03067eae3d763cdd134fc9
1 #!/usr/bin/perl
3 # Author: Martin Matusiak <numerodix@gmail.com>
4 # Licensed under the GNU Public License, version 3.
6 # <desc> DVD ripper and video converter </desc>
8 use strict;
9 use Getopt::Long qw(:config no_ignore_case);
11 use FindBin qw($RealBin);
12 use lib $RealBin;
13 use colors;
14 use common qw(:DEFAULT $suite $defaults $tools);
17 my $usage = "Usage: " . s_b($suite->{tool_name}) . " "
18 . s_b("-t") . " " . s_bb("01,02,03") . " "
19 . s_b("-a") . " " . s_bb("en") . " "
20 . s_b("-s") . " " . s_bb("es") . " "
21 . "[" . s_b("-d") . " " . s_bb($defaults->{dvd_device}) . "]"
22 . " [" . s_b("more options") . "]\n
23 -t --title title(s) to rip (comma separated)
24 -a --audio audio language (two letter code, eg. " . s_bb("en") . ", or integer id)
25 -s --subs subtitle language (two letter code or " . s_bb("off") . ", or integer id)\n
26 -d --dev dvd device to rip from (default is " . s_bb($defaults->{dvd_device}) . ")
27 -q --dir dvd directory to rip from
28 -i --iso dvd iso image to rip from\n
29 --start start after this many seconds (usually for testing)
30 -e --end end after this many seconds after start (usually for testing)\n
31 -C do sanity check (check for missing tools)
32 -z --adv <show advanced options>
33 --version show " . $suite->{suite_name} . " version\n";
35 my $adv_usage = "Advanced usage: " . s_b($suite->{tool_name}) . " "
36 . " [" . s_b("standard options") . "]"
37 . " [" . s_b("advanced options") . "]
38 -o --size output file size in mb (integer value)
39 --bpp bits per pixel (float value)
40 -1 force 1-pass encoding
41 -2 force 2-pass encoding
42 --clone clone dvd to iso image before encoding
43 --clone-x clone encrypted dvd to directory with vobcopy
44 -c --crop autocrop video
45 -r --scale scale video to x:y (integer value) or " . s_bb("off") . " to disable
46 -f --smooth use picture smoothing filter
47 -D --dryrun dry run (display encoding parameters without encoding)\n
48 --cont set container format
49 --acodec set audio codec
50 --vcodec set video codec\n";
52 my (@titles, $alang, $dvd_is_dir, $dvd_is_iso, $encrypted);
53 my ($opts_start, $opts_end, $target_size, $bpp, $autocrop);
54 my ($dry_run, $opts_acodec, $opts_vcodec, $opts_cont);
56 my $slang = "off";
57 my $dvd_device = $defaults->{dvd_device};
58 my $mencoder_source = $defaults->{dvd_device};
59 my $clone = 0;
60 my $custom_scale;
61 my $target_passes = 1;
62 my $prescale = $defaults->{prescale};
63 my $postscale = $defaults->{postscale};
65 my $parse = GetOptions(
66 "t|titles=s"=> sub { @titles = split(",", $_[1]); },
67 "a|audio=s"=>\$alang,
68 "s|subs=s"=>\$slang,
70 "d|dev=s"=> sub { $dvd_device = $_[1]; $mencoder_source = $_[1]; },
71 "q|dir=s"=> sub { $dvd_is_dir = 1; $mencoder_source = $_[1]; },
72 "i|iso=s"=> sub { $dvd_is_iso = 1; $mencoder_source = $_[1]; },
74 "start=f"=>\$opts_start,
75 "e|end=f"=>\$opts_end,
77 "C"=> sub { init_cmds(1); exit; },
78 "z|adv"=> sub { print $adv_usage; exit; },
79 "version"=>\&print_version,
81 "o|size=i"=>\$target_size,
82 "bpp=f"=>\$bpp,
83 "1"=> sub { $target_passes = 1; },
84 "2"=> sub { $target_passes = 2; },
85 "clone"=> sub { $clone = 1; },
86 "clone-x"=> sub { $clone = 1; $encrypted = 1; },
87 "c|crop"=> sub { $autocrop = 1; },
88 "r|scale=s"=>\$custom_scale,
89 "f|smooth"=> sub { $prescale .= "spp,"; $postscale = ",hqdn3d$postscale"; },
90 "D|dryrun"=> sub { $dry_run = 1; },
92 "cont=s"=>\$opts_cont,
93 "acodec=s"=>\$opts_acodec,
94 "vcodec=s"=>\$opts_vcodec,
97 print_tool_banner();
99 if (! $parse) {
100 print $usage;
101 exit 2;
105 if ((! $dvd_is_iso) and (! $dvd_is_dir) and (! -e $dvd_device)) {
106 print s_wa("=>") . " The dvd device " . s_bb($dvd_device) . " does not exist.\n";
107 print s_wa("=>") . " Supply the right dvd device to " . $suite->{tool_name}
108 . ", eg:\n";
109 print " " . s_b($suite->{tool_name}) . " " . s_b("-d") . " "
110 . s_bb($defaults->{dvd_device}) . " [" . s_b("other options") . "]\n";
111 exit 2;
114 my @startpos = ("-ss", $opts_start ? $opts_start : 0);
115 my @endpos;
116 if ($opts_end) {
117 push(@endpos, "-endpos", $opts_end);
120 if (scalar @titles < 1) {
121 nonfatal("No titles to rip, exiting");
122 print $usage;
123 exit 2;
126 my @audio_args;
127 if (! $alang) {
128 nonfatal("No audio language selected, exiting");
129 print $usage;
130 exit 2;
131 } else {
132 @audio_args = ternary_int_str($alang, "-aid", "-alang");
135 my @subs_args = ternary_int_str($slang, "-sid", "-slang");
138 init_logdir();
141 # Set container and codecs
143 my $container = $opts_cont ? $opts_cont : $defaults->{container};
144 my ($audio_codec, $video_codec, $ext, @cont_args) = set_container_opts($opts_acodec,
145 $opts_vcodec, $container);
147 print " - Output format :: "
148 . "container: " . s_it($container)
149 . " audio: " . s_it($audio_codec)
150 . " video: " . s_it($video_codec) . "\n";
153 # Clone dvd
155 if (($clone) and (! $dvd_is_iso) and (! $dvd_is_dir)) {
156 print " * Cloning dvd to disk first... ";
158 my $exit;
159 if ($encrypted) {
160 $exit = clone_vobcopy($dvd_device, $defaults->{disc_dir});
161 } else {
162 $exit = clone_dd($dvd_device, $defaults->{disc_image});
165 if ($exit) {
166 print s_err("failed, check log")."\n";
167 exit 1;
168 } else {
169 print s_ok("done")."\n";
174 # Display dry-run status
176 if ($dry_run) {
177 print " * Performing dry-run on title(s) " . s_bb(join(' ', @titles)) . "\n";
178 print_title_line(1);
181 foreach my $title_name (@titles) {
183 # Display encode status
185 if (! $dry_run) {
186 print " * Now ripping title " . s_bb($title_name) . ", with audio " .
187 s_bb($alang) . " and subtitles " . s_bb($slang) . " ";
188 if ($opts_start and $opts_end) {
189 print " [" . s_bb($opts_start) . "s - " . s_bb($opts_start+$opts_end)
190 . "s]";
191 } elsif ($opts_start) {
192 print " [" . s_bb($opts_start) . "s -> ]";
193 } elsif ($opts_end) {
194 print " [ -> " . s_bb($opts_end) . "s]";
196 print "\n";
200 # Extract information from the title
202 my $title = examine_title($title_name, $mencoder_source);
204 # Init encoding target info
206 my $ntitle = copy_hashref($title);
207 $ntitle->{aformat} = $audio_codec;
208 $ntitle->{vformat} = $video_codec;
209 $ntitle->{filename} = "$title_name.$ext";
212 # Do we need to crop?
214 my $crop_arg;
215 if ($autocrop) {
216 my $est = get_crop_eta($ntitle->{length}, $ntitle->{fps});
217 print " + Finding out how much to crop... (est: ${est}min)\r";
218 my ($width, $height);
219 ($width, $height, $crop_arg) = crop_title("dvd://$title_name",
220 $mencoder_source);
221 if (! $width or ! $height or ! $crop_arg) {
222 fatal("Crop detection failed");
224 $ntitle->{width} = $width;
225 $ntitle->{height} = $height;
228 # Find out how to scale the dimensions
230 my ($width, $height) =
231 scale_title($ntitle->{width}, $ntitle->{height}, $custom_scale);
232 $ntitle->{width} = $width;
233 $ntitle->{height} = $height;
234 my $scale_arg = "scale=$width:$height";
236 # Estimate filesize of audio
238 $ntitle->{abitrate} = set_acodec_opts($container, $ntitle->{aformat},
239 $ntitle->{abitrate}, 1);
240 my $audio_size = compute_media_size($ntitle->{length}, $ntitle->{abitrate});
241 my @acodec_args = set_acodec_opts($container, $ntitle->{aformat},
242 $ntitle->{abitrate});
244 # Decide bpp
246 if ($bpp) {
247 $ntitle->{bpp} = $bpp;
248 } elsif ($target_size) {
249 my $video_size = $target_size - $audio_size;
250 $video_size = 1 if $video_size <= 0;
251 $ntitle->{bpp} = compute_bpp($ntitle->{width}, $ntitle->{height},
252 $ntitle->{fps}, $ntitle->{length}, $video_size);
253 } else {
254 $ntitle->{bpp} = set_bpp($video_codec, $target_passes);
257 # Reset the number of passes based on the bpp
259 if ($target_passes) {
260 $ntitle->{passes} = $target_passes;
261 } else {
262 $ntitle->{passes} = set_passes($video_codec, $ntitle->{bpp});
265 # Compute bitrate
267 $ntitle->{vbitrate} = compute_vbitrate($ntitle->{width},
268 $ntitle->{height}, $ntitle->{fps}, $ntitle->{bpp});
271 # Dry run
273 if ($dry_run) {
275 # Estimate output size
277 if ($target_size) {
278 $ntitle->{filesize} = $target_size;
279 } else {
280 my $video_size = compute_media_size($ntitle->{length},
281 $ntitle->{vbitrate});
282 $ntitle->{filesize} = int($video_size + $audio_size);
285 $ntitle->{filename} = "$title_name.$container";
287 print_title_line(0, $title);
288 print_title_line(0, $ntitle);
291 # Encode video
293 } else {
295 for (my $pass = 1; $pass <= $ntitle->{passes}; $pass++) {
296 my @vcodec_args = set_vcodec_opts($ntitle->{vformat},
297 $ntitle->{passes}, $pass, $ntitle->{vbitrate});
299 my @args = (@startpos, @endpos);
300 push(@args, @audio_args, @subs_args);
301 push(@args, "-vf", "${crop_arg}${prescale}${scale_arg}${postscale}");
302 push(@args, "-oac", @acodec_args);
303 push(@args, "-ovc", @vcodec_args);
304 push(@args, "-of", @cont_args);
305 push(@args, "-dvd-device", $mencoder_source);
307 run_encode(\@args, "dvd://$title_name", $title_name, $ext,
308 $ntitle->{length}, $ntitle->{passes}, $pass);
311 if (-f "$title_name.$ext.partial") {
312 rename("$title_name.$ext.partial", "$title_name.$ext");
315 if (-f "divx2pass.log") {
316 unlink("divx2pass.log");
319 remux_container($title_name, $ext, $ntitle->{fps}, $container,
320 $ntitle->{aformat}, $ntitle->{vformat});