#!/bin/env perl

use strict;
use warnings;

use DVD::Read::DVDCSS qw(dvdcss_open dvdcss_seek dvdcss_read dvdcss_close dvdcss_error);

my $opt_mode = 'iso';
my $opt_title = '';
my $opt_anal = 100;
my $opt_decss = 1;
my $opt_v = 1;
my $inf;
my @files;

my $title = 'dvd';
my $sysid = 'dvd';
my $volid = 'dvd';
my $vsetid = 'dvd';
my $vsetn = '1';
my $vsets = '1';
my $cdate = '';
my $ofile = '%t:s-%N:u.%x:s';
my $odir  = '%t:s-%N:u';
my $ifile = '%t:s-%N:u.%x:s';
my $dvdcss_v = 0;
my $dvdcss_m = 'key';
my $dvdcss_c = 'off';
my $dvdcss_r = undef;
my %iso = ();
my $fnn = 0;
my $has_ifile = 0;

my $help = 
"dvdiso.pl [-m mode] [-v] [...] <device|isofile> [device|isofile]
  Dump and decrypt DVD video discs or iso files.
    -m mode       where mode is \"info\", \"iso\", \"file\" or
                    \"directory\".
                    \"info\": print informations
                    \"iso\": create ISO file
                    \"file\" or \"directory\": create directory structure
    -t title      set \"title\" as title instead of VOLUMEID
                    note: does not change DVD title, just use this text
                    for filenames (%t in template)
    -a <no|off|false|disable|nn>
                  analyze DVD to get scrambled status where nn the number of
                    checked sector for each titleset or disable analyze
                    default: analyze 100 sector on each titleset or menu
    -s:t newtitle iso mode change Volume ID and set 'title'
    -s:s sysid    iso mode change System ID
    -s:v volsetid iso mode change Volume Set ID
    -s:p publish  iso mode change Publisher ID
    -s:d preparer iso mode change Data Preparer ID
    -s:a app      iso mode change Application ID
    -s:c date     iso mode change Creation date YYYY-MM-DDTHH:MM:SS.hh in UTC
    -s:C          iso mode clear modification, expiration and effective date
    -o tmpl       output ISO file name or directory name template
                    default %t:s-%N:u.%x:s (.iso) for ISO and
                    %t:s-%N:u for directory structure
                    where %t - title, %x - extension, %m - mode,
                    %M - mode in simple form: iso, file or inf,
                    %n - No. of device or iso file, %s - sysid, %v - volid
                    %V - volsetid, %d - creation date, %N - volset no.,
                    %S - volset size
    -i tmpl       info file name template
                    default %t:s-%N:u.%x:s (.txt)
    -e:v <0|1|2>  set dvdcss verbosity (DVDCSS_VERBOSE environment var)
                    0 - quiet, 1 - log, 2 or greater - debug
    -e:m <method> set dvdcss method (DVDCSS_METHOD environment var)
                    method one of key, title or disc (key is default)
    -e:c <dir>    set dvdcss cache directory (DVDCSS_CACHE environment var)
                    the 'off' value disable key caching
    -e:r <device> set dvdcss raw device (DVDCSS_RAW_DEVICE environment var)
    -I            set \"info\" mode
    -P            disable terminal pretty print
    -D            disable decrypt DVD video sectors
    -q            quiet
    -v            verbose
    -h/-?         print this help and exit
    -hh           print long help (usage) and exit

";
my $longhelp = "

";

sub at_exit
{
    # cleanup
    rename ".dvdiso-$$-info", template_fill($ifile, 'txt') or warn "$!\n" if $has_ifile and $ifile;
}

END {
  at_exit();
}

$SIG{INT} = sub { die "Caught a sigint $!"; at_exit() };
$SIG{TERM} = sub { die "Caught a sigterm $!"; at_exit() };
$SIG{QUIT} = sub { die "Caught a sigquit $!"; at_exit() };

my $stderr_istty = -t STDERR;
my $colors_tty = `tput colors`;
my $opt_pp = 1; #do prety print

my %tt;

while(@ARGV) {
  $_ = shift @ARGV;
  if($_ eq '-h' or $_ eq '-?') {print $help; exit 0}
  elsif($_ eq '-hh') {print $help . $longhelp; exit 0}
  elsif($_ eq '-t') {$opt_title = shift @ARGV}
  elsif($_ eq '-m') {$opt_mode = shift @ARGV}
  elsif(substr($_,0,2) eq '-m') {$opt_mode = substr($_,2)}
  elsif($_ eq '-o') {$ofile = $odir = shift @ARGV}
  elsif(substr($_,0,2) eq '-o') {$ofile = $odir = substr($_,2)}
  elsif($_ eq '-a') {$opt_anal = shift @ARGV}
  elsif(substr($_,0,2) eq '-a') {$opt_anal = substr($_,2)}
  elsif($_ eq '-i') {$ifile = shift @ARGV }
  elsif(substr($_,0,2) eq '-i') {$ifile = substr($_,2)}
  elsif($_ eq '-e:v') {$dvdcss_v = shift @ARGV }
  elsif(substr($_,0,4) eq '-e:v') {$dvdcss_v = substr($_,4)}
  elsif($_ eq '-e:m') {$dvdcss_m = shift @ARGV }
  elsif(substr($_,0,4) eq '-e:m') {$dvdcss_m = substr($_,4)}
  elsif($_ eq '-e:c') {$dvdcss_c = shift @ARGV }
  elsif(substr($_,0,4) eq '-e:c') {$dvdcss_c = substr($_,4)}
  elsif($_ eq '-e:r') {$dvdcss_r = shift @ARGV }
  elsif(substr($_,0,3) eq '-s:') {
    $_ = substr($_,3);
    if   ($_ eq 't') {$iso{-t} = shift @ARGV}
    elsif(substr($_,0,1) eq 't') {$iso{-t} = substr($_,1)}
    elsif($_ eq 's') {$iso{-s} = shift @ARGV}
    elsif(substr($_,0,1) eq 's') {$iso{-s} = substr($_,1)}
    elsif($_ eq 'v') {$iso{-s} = shift @ARGV}
    elsif(substr($_,0,1) eq 'v') {$iso{-v} = substr($_,1)}
    elsif($_ eq 'p') {$iso{-v} = shift @ARGV}
    elsif(substr($_,0,1) eq 'p') {$iso{-p} = substr($_,1)}
    elsif($_ eq 'd') {$iso{-d} = shift @ARGV}
    elsif(substr($_,0,1) eq 'd') {$iso{-d} = substr($_,1)}
    elsif($_ eq 'a') {$iso{-a} = shift @ARGV}
    elsif(substr($_,0,1) eq 'a') {$iso{-a} = substr($_,1)}
    elsif($_ eq 'c') {$iso{-c} = shift @ARGV}
    elsif(substr($_,0,1) eq 'c') {$iso{-c} = substr($_,1)}
    elsif($_ eq 'C') {$iso{-m} = $iso{-x} = $iso{-e} = 16x' '."\0"}
  }
  elsif(substr($_,0,4) eq '-e:r') {$dvdcss_r = substr($_,2);}
  elsif($_ eq '-I') {$opt_mode = 'info'}
  elsif($_ eq '-P') {$opt_pp = 0}
  elsif($_ eq '-D') {$opt_decss = 0}
  elsif($_ eq '-v') {$opt_v = 2}
  elsif($_ eq '-q') {$opt_v = $opt_pp = 0}
  else { push @files, $_}
}

sub template_fill
{
  my $tmpl = $_[0];
  my $ext  = $_[1];
  my @arg;
  my %args = (-t => $title, -x => $ext, -m => $opt_mode, -d => $cdate,
              -n => $fnn, -s => $sysid, -v => $volid, -V => $vsetid,
              -N => $vsetn, -S => $vsets );
  $args{-m} =~ tr!/!_!; $args{-M} = $args{-m}; $args{-M} =~ s{((inf)o|(iso)|(file).*)}{($2//q[]).($3//q[]).($5//q[])}e; #magic // q[] to avoid unitialized
  while($tmpl =~ s/%([txmMnsvdVNS]):/%/) {
    push @arg, $args{'-'.$1};
  }
  return sprintf($tmpl, @arg);
}

#print info to stdout and file
# $_[0] min verbose, $_[1] format $_... args
sub print_info
{
    my $v = abs(my $vv = $_[0]);
    return if $v gt $opt_v;
    shift;
    my $fmt = shift;
    my $str = sprintf($fmt, @_);
    print $str if ($vv > 0);
    print $inf $str;
}

sub change_iso
{
  if(scalar keys %iso) { #change some 
    print_info(3,"Change some ISO9660 property.\n");
    foreach my $k (keys %iso) {
      my %ii = (-s=>8, -t=>40, -vss=>120, -vsn=>124, -v=>190, -p=>318, -d=>446, -a=>574,
                -c=>813, -m=>830, -x=>847, -e=>864);
      my $l = length $iso{$k};
      substr($_[0], $ii{$k}, $l, $iso{$k});
#      substr($_[0], 0, 2048, ' 'x2048);
    }
  }
}

sub sec2k
{
  my $n = $_[0];
  my @s = ('B', 'kB', 'MB', 'GB');
  $n <<= 11;
  return $n .= 'B' if $n < 1536;

  $n *= 10;
  while($n > 15360) {
    $n >>= 10;
    shift @s;
  }
  substr($n,-1,0) = '.';
  return $n .= $s[0];
}

#$_[0] vob/raw; $_[1] -> $_; 
sub pp_status
{
  if($_[3] == 100) {
    if( $opt_pp ) {
      print STDERR sprintf "$tt{-ci}  %s sector %08d/%08d $tt{-ao}%10u%%        $tt{-an} (%s/%s)       \r$tt{-cn}", @_;
    } else {
      print STDERR sprintf "  %s sector %08d/%08d %3u%%         (%s/%s)       \r", @_;
    }
  } else {
    if( $opt_pp ) {
      my $progr = sprintf '% 10u%%        ', $_[3];
      my $i = $_[3] < 1 ? 1 : ($_[3] - 1) * 19 / 100 + 1;
      substr($progr, $i, 0, "$tt{-am}");
      $_[3] = $progr;
      print STDERR sprintf "$tt{-ci}  %s sector %08d/%08d $tt{-ap}%s$tt{-an} (%s/%s)       \r$tt{-cn}", @_;
    } else {
      print STDERR sprintf "  %s sector %08d/%08d %3u%% (%s/%s)       \r", @_;
    }
  }
}

# $_[0] - no. of file, $_[1] - %, $_[2] - size
sub pp_progrs
{
  return unless $opt_pp;
  my $cu = $tt{-cu}; $cu =~ s/9999/$_[0]/;
  my $cd = $tt{-cd}; $cd =~ s/9999/$_[0]/;
  if($_[1] == 100) {
    print STDERR sprintf "$tt{-ci}$cu$tt{-cr}$tt{-ao}% 11s    $tt{-an}\r$cd$tt{-cn}", sec2k($_[2]);
  } else {
  #       %%%%######
  #  0-10-^I         1
  #  11-20-+           2
  #  21-30               3 .. 91-100 10
    my $progr = sprintf '% 8u%%      ', $_[1];
    my $i = $_[1] < 1 ? 1 : ($_[1] - 1) * 3 / 20 + 1;
    substr($progr, $i, 0, "$tt{-am}");
    print STDERR sprintf "$tt{-ci}$cu$tt{-cr}$tt{-ap}%s$tt{-an}\r$cd$tt{-cn}", $progr;
  }
}

$opt_mode =~ s/^in(fo?)?$/info/i or $opt_mode =~ s/^i(so?)?$/iso/i or $opt_mode =~ s/^f(i(l(e)?)?)?$/file\/dir/i or
  $opt_mode =~ s/^d(i(r(e(c(t(o(ry?)?)?)?)?)?)?)?$/file\/dir/i or die "Unknown mode: $opt_mode\n";

$opt_anal = 0 if $opt_anal =~ m/^(of{0,2}|f(a(l(s)?e?)?)?|no?|d(i(s(a(b(le?)?)?)?)?)?)$/i;
$opt_anal =~m/^[0-9]+$/ or die "Unknown argument for analyze: $opt_anal\n";

my $nf = '';
my $iso_change = 0;

print $help and exit 1 unless @files;

$iso{-t} = substr($iso{-t}.' 'x32,0,32) if exists $iso{-t};
$iso{-s} = substr($iso{-s}.' 'x32,0,32) if exists $iso{-s};
$iso{-v} = substr($iso{-v}.' 'x34,0,34) if exists $iso{-v};
$iso{-p} = substr($iso{-p}.' 'x128,0,128) if exists $iso{-p};
$iso{-d} = substr($iso{-d}.' 'x128,0,128) if exists $iso{-d};
$iso{-a} = substr($iso{-a}.' 'x128,0,128) if exists $iso{-a};
if(defined $iso{-c}) {delete $iso{-c} unless $iso{-c} =~ s/^([0-9]{4})-([0-9]{2})-([0-9]{2})[ Tt_]([0-9]{2}):([0-9]{2}):([0-9]{2}).([0-9]{2})$/$1$2$3$4$5$6$7\0/};
$iso_change = 1 if(scalar keys %iso);

if($opt_pp and $colors_tty >= 8) {
  %tt = (-ci => `tput civis`, -cn => `tput cnorm`, -cu => `tput cuu 9999`, -cd => `tput cud 9999`, -cr => `tput cuf 60`,
         -an => `tput sgr0`, -ap => `tput setab 6; tput setaf 0`, -am => `tput setab 3; tput setaf 0`,
         -ao => `tput setab 2; tput setaf 0`, -ae => `tput setab 1; tput setaf 7` );
} else {
  $opt_pp = 0;
}


#print %iso ."\n i:$iso_change\n\n";
#exit 0;
# DVD Serial number 41038a99
#$file = $files[0];
$ENV{'DVDCSS_VERBOSE'} = $dvdcss_v if not exists $ENV{'DVDCSS_VERBOSE'};
$ENV{'DVDCSS_METHOD'} = $dvdcss_m if not exists $ENV{'DVDCSS_METHOD'};
$ENV{'DVDCSS_CACHE'} = $dvdcss_c if not exists $ENV{'DVDCSS_CACHE'};
$ENV{'DVDCSS_RAW_DEVICE'} = $dvdcss_r if not exists $ENV{'DVDCSS_RAW_DEVICE'} and defined $dvdcss_r;
foreach my $file (@files) {
    $fnn++;
    my $desc = {-input=>$file, -mode=>$opt_mode};
    open $inf, '>', ".dvdiso-$$-info" or die "$!\n" if $ifile;
    $has_ifile = 1;
    print_info(1,"Processing: %s\n", $file);
#we open with dvdcss to authenticate the drive and get disc keys or something
    my $vbc = DVD::Read::DVDCSS->new($file) or die "libdvdcss cannot open DVD device or iso file: $file $!\n";
    $vbc->seek(16,0); #skip 'unused area'
    my $sector;
    my $rdir;
    print_info(1,"Parse ISO9660 Volume...\n");
    while($sector = $vbc->read(1, 0)) {
      my $dtype = unpack('C', $sector);
      last if $dtype == 255;
      next if $dtype != 1;
      print_info(2,"Found ISO9660 Primary Volume Descriptor.\n");
      (@{$desc}{-sysid, -volid, -vs_size, -vset_size, -vset_num, -lb_size, -pt_size, -loc_lpt, -loc_lpt1, -loc_mpt, -loc_mpt1}, $rdir,
          @{$desc}{-volsetid, -publid, -prepid, -appid, -copyr, -abstr, -bibl, -cdate, -mdate, -xdate, -edate}) =
           unpack('x8 a32 a32 x8 x4 N x32 x2 n x2 n x2 n x4 N V V N N a34 a128 a128 a128 a128 a37 a37 a37 a17 a17 a17 a17', $sector);
    }
    next unless exists $desc->{-loc_lpt};
    map {s/ +$//; $_ = $_} @{$desc}{-sysid, -volid, -volsetid, -publid, -prepid, -appid};
    map {s/^0{16}.*/-/; s/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2}).*/$1-$2-$3 $4:$5:$6.$7/; $_ = $_}
          @{$desc}{-cdate, -mdate, -xdate, -edate};
    ($sysid, $volid, $cdate, $vsetid, $vsetn, $vsets) = @{$desc}{-sysid, -volid, -cdate, -volsetid, -vset_num, -vset_size};
    print_info(3," System ID: %s\n", $desc->{-sysid});
    print_info(1," Volume ID: %s\n Volume Set ID: %s\n Volume Space Size: %d\n", @{$desc}{-volid, -volsetid, -vs_size});
    print_info(2," Volume Seq. No.: %d\n Volume Set Size: %d\n", @{$desc}{-vset_num, -vset_size});
    print_info(3," Publisher ID: %s\n Data Preparer ID: %s\n", @{$desc}{-publid, -prepid});
    print_info(2," Application ID: %s\n", $desc->{-appid});
    print_info(3," Copyright File ID: %s\n Abstaract File ID: %s\n Bibliographic File ID: %s\n",
                   @{$desc}{-copyr, -abstr, -bibl});
    print_info(1," Creation date: %s\n", $desc->{-cdate});
    print_info(3," Modification date: %s\n Expiration date: %s\n Effective date: %s\n",
                   @{$desc}{-mdate, -xdate, -edate});

    @{$desc->{-dir}}{-lendr, -lenear, -locext, -lendata, -ff, -fu_size, -lenfid, -fid} = unpack('C C x4 N x4 N x7 C C x5 C a1', $rdir);

    $desc->{-volid} = join("", map{$_ = sprintf "%X", rand 16} split //, "."x32) if $desc->{-volid} eq '';
    $title = $opt_title;
    $title = $desc->{-filedir} = $desc->{-volid} unless $title;
    print_info(2,"  Title: %s\n", $title);
    $vbc->seek($desc->{-loc_lpt},0); #skip LE path table
    $sector = $vbc->read(($desc->{-pt_size} >> 11) + 1, 0);
    $sector = substr($sector, 0, $desc->{-pt_size});
    my $path;
    print_info(1,"Parse ISO9660 filesystem paths...\n");
    while (length $sector) {
      $path = {};
      (@{$path}{-lendr, -lenear, -locext, -no_parent}) = unpack('C C V v ', $sector);
      ($path->{-dir_name}, $sector) = unpack('x8 a'.($path->{-lendr}).' '.('x' x($path->{-lendr} % 2)).' a*', $sector);
      last if $path->{-dir_name} eq 'VIDEO_TS';
    }
    next unless $path->{-dir_name} eq 'VIDEO_TS';
    print_info(2,"Found 'VIDEO_TS' directory, parse records...\n");
    my $rec = {};
    $vbc->seek($path->{-locext},0); #dir rec
    $sector = $vbc->read(1, 0);

    (my $lendr, my $lenear, my $lendata) = unpack('C C x8 V', $sector); # . -> this dir

    $vbc->seek($path->{-locext},0); #dir rec
    $sector = $vbc->read(($lendata >> 11) + 1, 0); # reread the whole dir
    $sector = substr($sector, 0, $lendata);

    my @subfiles;
    my $nn = 0;
    while(length $sector) {
      my $rec = {};
      (@{$rec}{-lendr, -lenear, -locext, -lendata, -ff, -fu_size, -lenfid}) = unpack('C C x4 N x4 N x7 C C x5 C', $sector);
      last if $rec->{-lendr} == 0; # hack???
      ($rec->{-fid}) = unpack('x33 a'.$rec->{-lenfid}, $sector);
      ($sector) = unpack('x'.($rec->{-lendr}).' a*', $sector);
      next if $nn++ < 2; # skip . and .. dir
      if(($rec->{-ff} & 0x02) == 0 && $rec->{-fid} =~ m/./) {
          $rec->{-filename} = substr($rec->{-fid},0,12);
          $rec->{-sec_start} = $rec->{-locext};
          $rec->{-sec_last}  = ($rec->{-locext} * 2048 + $rec->{-lendata} - 2048) / 2048; ## DVD file length always exact sector size!!
          push @subfiles, [$rec->{-sec_start}, $rec->{-sec_last}, $rec->{-sec_last}-$rec->{-sec_start}+1,$rec->{-filename},
                            $rec->{-filename} =~ m/\.vob$/i ? '?Yes' : 'No'];
          print_info(2,"  Found file: %s\n", $rec->{-filename});
      }
    }
    $desc->{-files} = [];
    $desc->{-vts} = {};
    my $sec = 0;
    foreach my $rec_ (sort { ${$a}[0] <=> ${$b}[0] } @subfiles) {
      my @rec = @{$rec_};
      if($rec[0] > $sec) {
        push @{$desc->{-files}},
          ['rawcopy', $sec, $rec[0]-1, $rec[0] - $sec, '*other data*', 'No'];
      }
      $sec = $rec[1] + 1;
      if($rec[3] =~ m/\.vob$/i) {
        push @{$desc->{-files}},
          ['vobcopy', @rec];
          if( $rec[3] =~ m/vts_([0-9][1-9])_([1-9]).vob/i) {
            $desc->{-vts}->{"$1"}->{-len} = 0 unless exists $desc->{-vts}->{"$1"};
            $desc->{-vts}->{"$1"}->{-len} += $rec[2];
          }
      } else {
        push @{$desc->{-files}},
          ['rawcopy', @rec];
      }
    }
#    $vbc->seek(100, 2); #
#do analyze sectors.... (default 100) for scrambed status in titlesets
    if( $opt_anal > 0) {
      my $vts = 0;
      my $sn = 0;
      my $count = 0;
      print_info(1,"Analyze sectors ($opt_anal\/titleset)\n");
      foreach my $rec (@{$desc->{-files}}) {
        $rec->[4] =~ m/^vts_([0-9]+)_([0-9]).vob$/i or $rec->[4] =~ /^video_(ts).vob$/i or next;
        if($vts ne $1 or $2 == 1) {
          $vts = lc($1) eq 'ts' ? 'main-menu' : $1;
          $count = $opt_anal;
          $sn = $rec->[1];
          $desc->{-vts}->{$vts}->{-scrambled} = 'No' if not exists $desc->{-vts}->{$vts}->{-scrambled};
          $vbc->seek($sn, 0);
        }
        while($count and $sn <= $rec->[2]) {
          if( not defined ($sector = $vbc->read(1, 0)) or unpack( 'x20 C', $sector) & 0x30 ) { #scrambled
            $desc->{-vts}->{$vts}->{-scrambled} = 'Yes';
            $sn = 99999999; # greater than any last sector no. on DVD
          }
          $sn++; #next sector
          $count--;
        }
        $rec->[5] = $desc->{-vts}->{$vts}->{-scrambled};
      }
    }

    print_info(1, "File table:\n-----------------------------------------------------------%s\n".
                               "  copy     start      last    length        name  scrambled%s\n",
                                 $opt_pp ? '----------------' : '', $opt_pp ? '    progress' : '');
    my $tlen = 0;
    foreach my $rec (@{$desc->{-files}}) {
      print_info(1, "%s  % 8d  % 8d  % 8d  %s  %s\n", @{$rec});
      $tlen += $rec->[3];
    }
    $tlen = sec2k($tlen);
    next if $opt_mode eq 'info';
#    print Dumper($desc);
#exit 0;
    print_info(-1, "Processing files...\n");
    my $iso;
    my $filedir;
    my $ls; #last sector
    if( $opt_mode eq 'iso' ) { # create iso file
      open $iso, '>:raw', template_fill($ofile, 'iso') or die "$!\n";
      print_info(-1, "Create iso file: %s\n", template_fill($ofile, 'iso'));
    } else {               #dump files
      $filedir = template_fill($odir);
      mkdir $filedir or die "$!\n" if(! -e $filedir);
      mkdir "$filedir/VIDEO_TS" or die "$!\n" if(! -e "$filedir/VIDEO_TS");
      print_info(-1, "Create output directory: %s\n", "$filedir/VIDEO_TS");
    }
    $ls = ${$desc->{-files}[@{$desc->{-files}}-1]}[2]; #last sector
    my $nf = scalar @{$desc->{-files}}; #number of files
    foreach my $fset (@{$desc->{-files}}) {
      my @f1 = @{$fset};  #($met, $ss, $se, $sl, $fn)
      my $fn = '';
      pp_progrs($nf, 0) if $stderr_istty; #$f1[0] rawcopy/vobcopy

      $vbc->seek($f1[1], $f1[0] eq 'rawcopy' ? 0 : 3);
      for ($_ = $f1[1]; $_ <= $f1[2]; $_++) {
        if( $fn ne $f1[4] ) {
          $fn = $f1[4];
          if( $fn ne '*other data*' && $opt_mode ne 'iso' ) {
            open $iso, '>:raw', "$filedir/VIDEO_TS/$fn" or die "Error at creating $fn: $!\n";
            print_info(-1, "Create output file: %s\n", $fn);
          }
#           print STDERR sprintf ' 'x40 ."\rrawcopy %s %d .. %d\n", $fn , $f1[1], $f1[2];
        }
        if( $fn eq '*other data*' && $opt_mode ne 'iso' ) {
          print_info(-1, "Skip *other data*\n");
          $vbc->seek($f1[2] + 1, 0);
          $_ = $f1[2];
        } else {

# iso volume descriptor is *other data* so, we search here
# it after 32k, so we count 16 sector
# descriptor type == 1 (first byte of the sector)

          die sprintf "%sPartial read: %s\n", (' 'x40 ."\r")x$stderr_istty, $vbc->error()
            if not defined ($sector = $vbc->read(1, $f1[0] eq 'rawcopy' ? 0 : 1));

          if($opt_mode eq 'iso' && $iso_change && $_ > 15 && unpack('C', $sector) == 1) {
            change_iso($sector);
            $iso_change = 0;
          }
          print $iso $sector or die "!!Cannot write sector data at $_ sec.\n";
          pp_progrs($nf, ($_-$f1[1])*100/$f1[3], $f1[3]) if(( $f1[3] < 1000 || $f1[3] < 100000 && $_ % 37 == 1 || $_ % 197) and $stderr_istty); # len < 1000 -> /1, <100000 /37, /197
          pp_status($f1[0], $_, $ls, ($_ * 100 / $ls), sec2k($_), $tlen) if( $_ % 97 == 1 and $stderr_istty);
        }
        if( $_ == $f1[2] ) {
          print_info(-2, " File data dumped %s\n", sec2k($f1[3])) if( $fn ne '*other data*' && $opt_mode ne 'iso' );
          print_info(-2, " %s %s data dumped\n", $f1[4], sec2k($f1[3])) if( $opt_mode eq 'iso' );
          close $iso if( $fn ne '*other data*' && $opt_mode ne 'iso' );
        }
      }
      pp_progrs($nf, 100, $f1[3]) if $stderr_istty;
      $nf--;
    }
    @_ = @{$desc->{-files}}[@{$desc->{-files}} - 1]; $_--;
    pp_status($_[0] eq 'vobcopy' ? 'vob' : 'raw', $_, $ls, 100, sec2k($_), $tlen), print STDERR "\n$tt{-cn}" if $stderr_istty;
    close $iso if $iso;
    $vbc->close;
    print_info(1, "File/device no.: %d finished\n", $fnn);
    close $inf if $ifile;
    $inf = 1;
};

1;
__END__

=head1 NAME

dvdiso.pl - Dump and decrypt VideoDVD

=head1 SYNOPSIS

dvdiso.pl [-m mode] [-v] [...] <device|isofile> [device|isofile]

  Dump and decrypt DVD video discs or iso files.

    -m mode       where mode is "info", "iso", "file" or

                    "directory".

                    "info": print informations

                    "iso": create ISO file

                    "file" or "directory": create directory structure

    -t title      set "title" as title instead of VOLUMEID

                    note: does not change DVD title, just use this text

                    for filenames (%t in template)

    -a <no|off|false|disable|nn>

                  analyze DVD to get scrambled status where nn the number of

                    checked sector for each titleset or disable analyze

                    default: analyze 100 sector on each titleset or menu

    -s:t newtitle iso mode change Volume ID and set 'title'

    -s:s sysid    iso mode change System ID

    -s:v volsetid iso mode change Volume Set ID

    -s:p publish  iso mode change Publisher ID

    -s:d preparer iso mode change Data Preparer ID

    -s:a app      iso mode change Application ID

    -s:c date     iso mode change Creation date YYYY-MM-DDTHH:MM:SS.hh in UTC

    -s:C          iso mode clear modification, expiration and effective date

    -o tmpl       output ISO file name or directory name template

                    default %t:s-%N:u.%x:s (.iso) for ISO and

                    %t:s-%N:u for directory structure

                    where %t - title, %x - extension, %m - mode,

                    %M - mode in simple form: iso, file or inf,

                    %n - No. of device or iso file, %s - sysid, %v - volid

                    %V - volsetid, %d - creation date, %N - volset no.,

                    %S - volset size

    -i tmpl       info file name template

                    default %t:s-%N:u.%x:s (.txt)

    -e:v <0|1|2>  set dvdcss verbosity (DVDCSS_VERBOSE environment var)

                    0 - quiet, 1 - log, 2 or greater - debug

    -e:m <method> set dvdcss method (DVDCSS_METHOD environment var)

                    method one of key, title or disc (key is default)

    -e:c <dir>    set dvdcss cache directory (DVDCSS_CACHE environment var)

                    the 'off' value disable key caching

    -e:r <device> set dvdcss raw device (DVDCSS_RAW_DEVICE environment var)

    -I            set "info" mode

    -P            disable terminal pretty print

    -D            disable decrypt DVD video sectors

    -q            quiet

    -v            verbose

    -h/-?         print this help and exit

    -hh           print long help (usage) and exit

=cut