#!/usr/bin/perl -w # # mydvdrip.pl - encode DVD/vobs to AVI (wraps mencoder) # # $Id: mydvdrip.pl,v 1.6 2003/02/26 14:10:54 jear Exp $ # # (c) 2003 Jens Arnfast # # LICENSE # # This library is released under the same conditions as Perl, that # is, either of the following: # # a) the GNU General Public License as published by the Free # Software Foundation; either version 1, or (at your option) any # later version. # # b) the Artistic License. # ################################################## use strict; use Getopt::Long; use Pod::Usage; my $VERSION = sprintf("%d.%02d", q$Revision: 1.6 $ =~ /(\d+)\.(\d+)/); my %config = ( 'debug' => -1, 'help' => 0, 'configfile' => $ENV{HOME} . '/.mydvdrip_conf', 'enc_dvd' => 0, 'enc_vobs' => 0, 'do_audio' => 1, 'do_pass1' => 1, 'do_pass2' => 1, 'bitrate' => -1, 'sublang' => -1, 'aulang' => -1, 'workdir' => -1, 'outfile' => -1, 'mencoder' => -1, 'dvddevice' => -1, 'dvdtitle' => 0, 'dvdchapter' => '', 'crop' => -1, 'cropparam' => '', 'cropsamples' => -1, 'verbose' => 0, 'vobs' => [] ); GetOptions ( 'help|?' => \$config{help}, 'configfile=s' => \$config{configfile}, 'debug=i' => \$config{debug}, 'bitrate=i' => \$config{bitrate}, 'sublang=s' => \$config{sublang}, 'aulang=s' => \$config{aulang}, 'workdir=s' => \$config{workdir}, 'outfile=s' => \$config{outfile}, 'mencoder=s' => \$config{mencoder}, 'dvddevice=s' => \$config{dvddevice}, 'dvdtitle=i' => \$config{dvdtitle}, 'dvdchapter=s' => \$config{dvdchapter}, 'vobs=s' => \@{$config{vobs}}, 'crop!' => \$config{crop}, 'do_audio!' => \$config{do_audio}, 'do_pass1!' => \$config{do_pass1}, 'do_pass2!' => \$config{do_pass2}, 'cropparam=s' => \$config{cropparam}, 'cropsamples=i' => \$config{cropsamples}, 'verbose' => \$config{verbose} ); pod2usage(1) if $config{help}; $| = 1; &log(1, "mydvdrip.pl Version: $VERSION"); @{$config{vobs}} = split(/,/,join(',',@{$config{vobs}})); $config{enc_vobs} = 1 if defined($config{vobs}) && scalar @{$config{vobs}}; $config{enc_dvd} = 1 if defined($config{dvdtitle}) && $config{dvdtitle}; if ($config{enc_vobs} && $config{enc_dvd}) { print STDERR "ERROR: You cannot select both VOBS and DVDTITLE.\n"; exit(8); } &read_config(\%config); chdir($config{workdir}); # clean up (only if full run) if ($config{do_audio} && $config{do_pass1} && $config{do_pass2}) { &log(1, "Cleaning workdir."); unlink $config{workdir} . '/frameno.avi'; unlink $config{workdir} . '/divx2pass.log'; } if ($config{enc_dvd}) { &log(1, "Ripping DVD." . ($config{dvdtitle} ? " Title: " . $config{dvdtitle} : '') . ($config{dvdchapter} ? " Chapter(s): " . $config{dvdchapter} : '')); } elsif ($config{enc_vobs}) { &log(1, "Ripping VOB stream."); } &enc_audio(\%config) if $config{do_audio}; &crop_find_params(\%config) if $config{crop}; &enc_pass1(\%config) if $config{do_pass1}; &enc_pass2(\%config) if $config{do_pass2}; &log(1, "Finished AVI file: $config{workdir}/$config{outfile}"); if ($config{debug}) { foreach my $key (sort keys %config) { my $val = $config{$key}; if (ref($config{$key}) eq 'ARRAY') { $val = 'array(' . join(', ', @{$config{$key}}) . ')'; } printf("'%s' => '%s'\n", $key, $val); } } sub enc_pass1 ($) { my $c = shift; &log(1, "Encoding video [PASS 1]."); my $cmd = &mencoder_command($c, '1pass'); &log(2, "CMD: $cmd") if $$c{debug}; my $videosize = 0; open(PASS1, "$cmd 2>&- |") || die "Couldn't open pipe to mencoder ($!)\n"; select(PASS1); $| = 1; select(STDOUT); my $raw; my $ltmp = ''; while( read PASS1, $raw, 512 ) { my @lines = split(/[\r\n]/, $raw); if ($ltmp) { $lines[0] = $ltmp . $lines[0]; $ltmp = ''; } my $last = substr($raw, -1); &log(2, "LAST CHAR: >>$last<<\n") if $$c{debug} > 3; $ltmp = pop(@lines) unless $last =~ /[\r\n]/; foreach my $line (@lines) { &log(2, $line) if $$c{debug} > 3; if ($$c{verbose} && $line =~ /^\s*Pos:/) { print "$line\r"; } if ($line =~ /^\s*Video stream: .*?size: (\d+) /) { $videosize = $1; } } } close(PASS1); print "\n" if $$c{verbose}; &log(1, sprintf("Encoded video size: %.2f MB (%d bytes)", $videosize/1024/1024, $videosize)); &log(1, "Finished encoding video [PASS 1]."); } sub enc_pass2 ($) { my $c = shift; &log(1, "Encoding video [PASS 2]."); my $cmd = &mencoder_command($c, '2pass'); &log(2, "CMD: $cmd") if $$c{debug}; my ($videosize, $videolen) = (0, 0); open(PASS2, "$cmd 2>&- |") || die "Couldn't open pipe to mencoder ($!)\n"; select(PASS2); $| = 1; select(STDOUT); my $raw; my $ltmp = ''; while( read PASS2, $raw, 512 ) { my @lines = split(/[\r\n]/, $raw); if ($ltmp) { $lines[0] = $ltmp . $lines[0]; $ltmp = ''; } my $last = substr($raw, -1); &log(2, "LAST CHAR: >>$last<<\n") if $$c{debug} > 3; $ltmp = pop(@lines) unless $last =~ /[\r\n]/; foreach my $line (@lines) { &log(2, $line) if $$c{debug} > 3; if ($$c{verbose} && $line =~ /^\s*Pos:/) { print "$line\r"; } if ($line =~ /^\s*Video stream: .*?size: (\d+) .*?(\d+)\.\d+\s+secs/) { ($videosize, $videolen) = ($1, $2); } } } close(PASS2); print "\n" if $$c{verbose}; &log(1, sprintf("Encoded video size: %.2f MB (%d bytes)", $videosize/1024/1024, $videosize)); &log(1, sprintf("Encoded video length: %02d:%02d:%02d (%d seconds)", $videolen/3600, ($videolen%3600)/60, $videolen%3600%60, $videolen)); &log(1, "Finished encoding video [PASS 2]."); } sub enc_audio ($) { my $c = shift; &log(1, "Encoding audio track [$$c{aulang}]."); my $cmd = &mencoder_command($c, 'audio'); &log(2, "CMD: $cmd") if $$c{debug}; my $audiosize = 0; open(ENCAUDIO, "$cmd 2>&- |") || die "Couldn't open pipe to mencoder ($!)\n"; select(ENCAUDIO); $| = 1; select(STDOUT); my $raw; my $ltmp = ''; while( read ENCAUDIO, $raw, 512 ) { my @lines = split(/[\r\n]/, $raw); if ($ltmp) { $lines[0] = $ltmp . $lines[0]; $ltmp = ''; } my $last = substr($raw, -1); &log(2, "LAST CHAR: >>$last<<\n") if $$c{debug} > 3; $ltmp = pop(@lines) unless $last =~ /[\r\n]/; foreach my $line (@lines) { &log(2, $line) if $$c{debug} > 3; if ($$c{verbose} && $line =~ /^\s*Pos:/) { print "$line\r"; } if ($line =~ /^\s*Audio stream: .*?size: (\d+) /) { $audiosize = $1; } } } close(ENCAUDIO); print "\n" if $$c{verbose}; &log(1, sprintf("Encoded audio size: %.2f MB (%d bytes)", $audiosize/1024/1024, $audiosize)); &log(1, "Finished encoding audio track."); } sub crop_find_params ($) { &log(1, "Auto-detecting crop parameters."); my $c = shift; my $ret; my $cmd = &mencoder_command($c, 'cropdetect'); my $ocrop_param = ''; my $count = 0; &log(2, "CMD: $cmd") if $$c{debug}; my $raw; my $ltmp = ''; open(CROP, "$cmd 2>&- |") || die "Couldn't open pipe to mencoder ($!)\n"; CROPLOOP: while ( read CROP, $raw, 512 ) { my @lines = split(/[\r\n]/, $raw); if ($ltmp) { $lines[0] = $ltmp . $lines[0]; $ltmp = ''; } my $last = substr($raw, -1); &log(2, "LAST CHAR: >>$last<<\n") if $$c{debug} > 3; $ltmp = pop(@lines) unless $last =~ /[\r\n]/; foreach my $line (@lines) { if ($line =~ /^.*crop area: .*?\(-vop (crop.+?)\)/i) { &log(2, $line) if $$c{debug} > 3; my $crop_param = $1; &log(2, sprintf("%3d: cropparam: %s\n", $count, $crop_param)) if $$c{debug} > 2; if ($crop_param ne $ocrop_param) { $ocrop_param = $crop_param; $count = 0; } else { $count++; } if ($count > $$c{cropsamples}) { &log(2, "got $count crop samples") if $$c{debug}; last CROPLOOP; } } } } close(CROP); $$c{cropparam} = $ocrop_param; &log(1, "Setting crop parameters to: " . $$c{cropparam}); return $ret; } sub mencoder_command ($$) { my $c = shift; my $type = shift; my $cmd; my @opts; if ($type eq 'audio') { # mencoder -dvd 3 -dvd-device /dev/dvd -alang ja -oac mp3lame -lameopts br=128:cbr:vol=6 -ovc frameno -o $RIPDIR/frameno.avi @opts = ('-oac mp3lame', '-lameopts br=128:cbr:vol=6', '-ovc frameno', '-o ' . $$c{workdir} . '/frameno.avi'); push(@opts, '-alang ' . $$c{aulang}) if $$c{aulang} =~ /[^\d]+/; push(@opts, '-aid ' . $$c{aulang}) if $$c{aulang} =~ /\d+/; } elsif ($type eq 'cropdetect') { # mencoder -dvd 3 -oac copy -ovc lavc -vop cropdetect,pp=lb -o /dev/null @opts = ('-oac copy', '-ovc lavc', '-vop cropdetect,pp=lb', '-o /dev/null'); } elsif ($type eq '1pass') { # mencoder -dvd 3 -dvd-device /dev/dvd -slang en -sws 2 -oac copy -ovc lavc # -lavcopts vcodec=mpeg4:vhq:vbitrate=$BITRATE:vpass=1 -vop pp=lb -o /dev/null @opts = ('-sws 2', '-oac copy', '-ovc lavc', '-lavcopts vcodec=mpeg4:vhq:vbitrate=' . $$c{bitrate} . ':vpass=1', '-o /dev/null', '-passlogfile ' . $$c{workdir} . '/divx2pass.log'); push(@opts, '-vop pp=lb' . ($$c{cropparam} ? ','.$$c{cropparam} : '')); push(@opts, '-slang ' . $$c{sublang}) if $$c{sublang} =~ /[^\d]+/; push(@opts, '-sid ' . $$c{sublang}) if $$c{sublang} =~ /\d+/; } elsif ($type eq '2pass') { # mencoder -dvd 3 -dvd-device /dev/dvd -slang en -sws 2 -oac copy -ovc lavc # -lavcopts vcodec=mpeg4:vhq:vbitrate=$BITRATE:vpass=2 -vop pp=lb -o $RIPDIR/$OUTPUT @opts = ('-sws 2', '-oac copy', '-ovc lavc', '-lavcopts vcodec=mpeg4:vhq:vbitrate=' . $$c{bitrate} . ':vpass=2', '-o /dev/null', '-passlogfile ' . $$c{workdir} . '/divx2pass.log', '-o ' . $$c{workdir} . '/' . $$c{outfile}); push(@opts, '-vop pp=lb' . ($$c{cropparam} ? ','.$$c{cropparam} : '')); push(@opts, '-slang ' . $$c{sublang}) if $$c{sublang} =~ /[^\d]+/; push(@opts, '-sid ' . $$c{sublang}) if $$c{sublang} =~ /\d+/; } if ($$c{enc_vobs}) { # cat *.vob | mencoder - $cmd = 'cat ' . join(' ', @{$$c{vobs}}) . ' | ' . $$c{mencoder} . ' ' . join(' ', @opts) . ' -'; } elsif ($$c{enc_dvd}) { # see above push(@opts, '-dvd ' . $$c{dvdtitle}) if $$c{dvdtitle}; push(@opts, '-chapter ' . $$c{dvdchapter}) if $$c{dvdchapter}; push(@opts, '-dvd-device ' . $$c{dvddevice}) if $$c{dvddevice}; $cmd = $$c{mencoder} . ' ' . join(' ', @opts); } else { $cmd = ''; } return $cmd; } sub log ($$) { my ($type, $msg) = @_; my @t = localtime(); $msg =~ s/\n//sg; if ($type == 1) { printf("[%4d/%02d/%02d-%02d:%02d:%02d] %s\n", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0], $msg); } elsif ($type == 2) { printf("[%4d/%02d/%02d-%02d:%02d:%02d] [DEBUG] %s\n", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0], $msg); } elsif ($type == 3) { printf("[%4d/%02d/%02d-%02d:%02d:%02d] [ERROR] %s\n", $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0], $msg); exit(8); } } sub read_config ($) { my $c = shift; &log(1, "Reading configuration from '$$c{configfile}'"); my %cfg = map { $_ => -1 } qw(debug bitrate sublang aulang outfile mencoder dvddevice workdir crop cropparam cropsamples); # set default values $cfg{bitrate} = 1000; $cfg{aulang} = 'en'; $cfg{outfile} = 'dvdrip.avi'; $cfg{mencoder} = '/usr/local/bin/mencoder'; $cfg{dvddevice} = '/dev/dvd'; $cfg{cropsamples} = 200; $cfg{workdir} = '/home/dvd'; $cfg{debug} = 0; $cfg{crop} = 1; $cfg{cropparam} = ''; $cfg{sublang} = ''; if (! -e $$c{configfile}) { if ($$c{configfile} eq $ENV{HOME}.'/.mydvdrip_conf') { &log(1, 'User has no configuration file, creating.'); open(NEWCONF, ">$$c{configfile}") || die "Couldn't open '" . $$c{configfile} . "' for writing ($!)\n"; print NEWCONF "\# file automagically generated by mydvdrip.pl v. $VERSION\n"; foreach my $key (sort keys %cfg) { &log(1, sprintf("Setting: %s = %s", uc($key), $cfg{$key})); print NEWCONF uc($key) . "=$cfg{$key}\n"; } close(NEWCONF); } else { &log(1, "Couldn't find configfile '" . $$c{configfile} . "'. Using defaults."); } } else { open(CFG, "<" . $$c{configfile}) || die "Couldn't open '" . $$c{configfile} . "' for reading ($!)\n"; while (my $line = ) { next if $line =~ /^\s*\#/; if ($line =~ /^\s*(.+?)\s*=\s*(.+)\s*$/) { my ($key, $val) = (lc($1), $2); &log(2, "Config '$key' => '$val'") if $$c{debug} > 0; $cfg{$key} = $val; } } close(CFG); } # merge configs foreach my $key (keys %cfg) { if (!defined($$c{$key}) || $$c{$key} eq '-1') { &log(2, "Setting '$key' to '$cfg{$key}'") if $$c{debug} > 0; $$c{$key} = $cfg{$key}; } } # set -1 to 0 foreach my $key (keys %$c) { $$c{$key} = 0 if $$c{$key} eq '-1'; } } __END__ =head1 NAME mydvdrip.pl - frontend for mencoder =head1 SYNOPSIS mydvdrip.pl [options] Options: -help brief help message -configfile specify config-file to use -debug turn on debug information -bitrate set target bitrate -sublang choose subtitle language -aulang choose audio language -workdir set workdir -outfile set output-file (relative to workdir) -mencoder path to mencoder (default: /usr/local/bin/mencoder) -dvddevice set the device to encode from -dvdtitle select which DVD title to encode -dvdchapter select which DVD chapters to encode -vobs select which VOBS to encode -crop turn on auto-detection of crop parameters -cropparam specify crop parameters -cropsamples specify number of samples crop performs -nodo_audio turn off audio processing -nodo_pass1 turn off pass1 processing -nodo_pass2 turn off pass2 processing -verbose turn on more verbose encoding =head1 OPTIONS =over 8 =item B<-help> Print a brief help message and exits. =item B<-configfile> Specify config-file to use. (default: ~/.mydvdrip_conf) =item B<-debug> Turn on debugging information. =item B<-bitrate> Set the encoding bitrate. =item B<-sublang> Choose which subtitle langauge to extract. =item B<-aulang> Choose which audio langauge to extract. =item B<-workdir> Set the working dir. =item B<-outfile> Set the output-file. (relative to workdir) =item B<-mencoder> Set the path to mencoder. =item B<-dvddevice> Set the device to rip DVDs from. =item B<-dvdtitle> Select which DVD title to encode. =item B<-dvdchapter> Select which DVD chapter(s) to encode. =item B<-vobs> Select which VOBS to encode. =item B<-crop> Turn on auto-detection of crop parameters. =item B<-cropparam> Specify crop parameters to mencoder. =item B<-cropsamples> Set number of samples crop requires. (for use with -crop) =item B<-nodo_audio> Don't process audio. =item B<-nodo_pass1> Don't process video (pass 1). =item B<-nodo_pass2> Don't process video (pass 2). =back =head1 DESCRIPTION This program will encode a DVD or bunch of VOB-files to an AVI. =head1 EXAMPLES =item B Encode title 1 of the DVD and output in foobar.avi =item B Encode title 1, chapter 4 to 7 of the DVD =head1 AUTHOR Jens Arnfast I =head1 AVAILABILITY The lastest version of this script is found at I =cut