#!/usr/bin/perl # Create DVD from one or more mpeg transport stream files. # Author: Tom Dexter. (NO guarantees of any sort.) # Requires: # projectx # mplayer # mencoder # tcmplex-panteltje # makemenu (from the tovid package, only needed for --menu) # dvdauthor # growisofs (only needed if --burn is used) use Getopt::Long; ($help, $crop, $keep, $keepall, $title, $outdir, $burn, $nochapt, $four, $menu) = (); $dvddevice = '/dev/dvd'; $title = 'DVD'; GetOptions ( "crop|c" => \$crop, "title|t=s" => \$title, "outdir|o=s" => \$outdir, "burn|b" => \$burn, "dvd-device|d=s" => \$dvddevice, "keep|k" => \$keep, "keepall|a" => \$keepall, "43|4" => \$four, "nochapt|n" => \$nochapt, "menu|m" => \$menu, "help|h" => \$help) || &syntax(-1); &syntax(0) if ($help); $minperchapter = 5; # Might add to options later $four = 1 if ($crop); $keep = 1 if ($keepall); $outdir = 'DVD' if ($outdir eq ''); &mandprogs('projectx', 'mplayer', 'mencoder', 'tcmplex-panteltje', 'dvdauthor'); &mandprogs('makemenu') if ($menu); &mandprogs('growisofs') if ($burn); die "Directory $outdir already exists!\n" if (-e $outdir); &syntax(-1) unless (@ARGV); $starttime = time(); @vfiles = (); @titles = (); @files = @ARGV; foreach $file (@files) { die "File $file doesn't exist!\n" unless (-f $file); @temp = split(/\//, $file); $fname = pop(@temp); @temp = split(/\./, $fname); pop @temp if (@temp > 1); $fstem = join('.', @temp); $vfile = "$fstem.vob"; die "File $vfile aready exists!\n" if (-f $vfile); push @titles, $fstem; push @vfiles, $vfile; } # Create a profile file for tcmplex-panteltje. This appears to be the only way to force vbr. $tmp_prof = "tmp_${$}.prof"; open(P, ">$tmp_prof") || die "Couldn't open $tmp_prof for write!\n"; print P "vbr_mux = 1\n"; close(P); $cnt = 0; foreach $file (@files) { $vfile = $vfiles[$cnt]; $cnt++; %mid = &midentify($file); # See if we have both audio and video ids # Only use the first progran id if we have more than one. $aid = ref($mid{'ID_AUDIO_ID'}) eq 'ARRAY' ? $mid{'ID_AUDIO_ID'}[0] : $mid{'ID_AUDIO_ID'}; $vid = ref($mid{'ID_VIDEO_ID'}) eq 'ARRAY' ? $mid{'ID_VIDEO_ID'}[0] : $mid{'ID_VIDEO_ID'}; $tstem = "tmp_${$}_${cnt}_"; # ProjectX demux @cmd = ('projectx', '-demux', '-name', $tstem, '-out', '.'); push @cmd, ('-id', "$aid,$vid") if ($aid ne '' and $vid ne ''); push @cmd, $file; print "@cmd\n"; die "Error running projectx on $file!\n" if (system(@cmd)); # Re-encode video with mencoder $croptext = ''; if ($crop) { $height = $mid{'ID_VIDEO_HEIGHT'}; die "Invalid height: $height for $file??\n" if ($height eq ''); $width = int($height * 4 / 3); $croptext = "crop=$width:$height," if ($width != $mid{'ID_VIDEO_WIDTH'}); } $aspect = $four ? '4/3' : '16/9'; $cmd = sprintf("mencoder -ovc lavc -of rawvideo -mpegopts format=dvd:tsaf -vf %sscale=720:480 -lavcopts vcodec=mpeg2video:vrc_buf_size=1835:vrc_maxrate=9800:vbitrate=5000:keyint=18:vstrict=0:aspect=%s -ofps 30000/1001 -o %s %s", $croptext, $aspect, "${tstem}dvd.m2v", "$tstem.m2v"); print "$cmd\n"; die "Error running mencoder on $tstem.m2v for $file!\n" if (system($cmd)); # mplex using tcmplex-panteltje @cmd = ('tcmplex-panteltje', '-i', "${tstem}dvd.m2v", '-p', "$tstem.ac3", '-m', 'd', '-N', '-F', $tmp_prof, '-o', $vfile); print "@cmd\n"; die "Error running tcmplex-panteltje on ${tstem}dvd.m2v and $tstem.ac3 for $file!\n" if (system(@cmd)); # cleanup unlink(("$tstem.m2v", "${tstem}dvd.m2v", "$tstem.ac3", "${tstem}_log.txt")) unless ($keepall); } unlink($tmp_prof) unless ($keepall); $tstem = "tmp_$$"; if ($menu) { @cmd = ('makemenu', '-noask', '-textcolor', '#00FF00', '-fontdeco', ''); push @cmd, ('-menu-title', $title) if ($title ne ''); push @cmd, @titles; push @cmd, ('-out', "${tstem}_menu"); print "@cmd\n"; die "Error running makemenu!\n" if (system(@cmd)); } # Create dvdauthor xml open(XML, ">$tstem.xml") || die "Couldn't open $tstem.xml for write!\n"; print XML "\n"; if ($menu) { print XML < jump titleset 1 menu; XML } else { print XML " \n"; } print XML " \n"; $maxfiles = @vfiles; $jtitles = ''; for ($x = 1; $x <= $maxfiles; $x++) { $jtitles .= " \n"; } print XML < $jtitles jump cell 1; XML if ($menu); print XML " \n"; print XML " \n"; print XML " \n"; print XML "\n"; close(XML); # Create DVD with dvdauthor @cmd = ('dvdauthor', '-x', "$tstem.xml"); print "@cmd\n"; die "Error running dvdauthor!\n" if (system(@cmd)); # Delete vob files etc if (!$keep) { unlink(@vfiles); unlink("${tstem}_menu.mpg") if ($menu); unlink("$tstem.xml"); } if ($burn) { @cmd = ('growisofs', '-dvd-compat', '-Z', $dvddevice, '-dvd-video', $outdir); die "Error running growisofs\n" if (system(@cmd)); } $runtime = &fmttimediff($starttime); print "Runtime: $runtime\n"; print "Finished!\n"; exit(0); sub midentify { my ($filename) = @_; # my @out = `midentify "$filename"|sort|uniq`; my @out = `mplayer -vo null -ao null -frames 0 -identify "$filename" 2>/dev/null | grep "^ID_"|sort|uniq`; my %out = (); my $line, @temp, @list; foreach $line (@out) { chomp($line); my @temp = split(/=/, $line); if (@temp == 2 and $temp[1] ne '0') { &setmidvalue(\%out, $temp[0], $temp[1]); } } %out; } sub setmidvalue { my ($ptr, $name, $value) = @_; my @list; my $val = $$ptr{$name}; SWITCH: { if (!defined($val)) { $$ptr{$name} = $value; last SWITCH; } if (ref($val) eq "ARRAY") { @list = @$val; } else { @list = ($val); } push @list, $value; $$ptr{$name} = \@list; } } # return hh:mm:ss absolute time difference between to times. sub fmttimediff { my ($t1, $t2) = @_; $t2 = time() unless ($t2); my $d = abs($t1 - $t2); my $h = int($d / 3600); $d -= ($h * 3600); my $m = int($d / 60); $d -= ($m * 60); if ($h) { sprintf("%d:%02d:%02d", $h, $m, $d); } else { sprintf("%02d:%02d", $m, $d); } } # Check list of mandatory programs sub mandprogs { my @progs = @_; my $prog; my @bad = (); foreach $prog (@progs) { push @bad, $prog unless (`which $prog 2> /dev/null` ne ''); } die "The following program(s):\n@bad\n...don't appear to me installed or are not in your current path!\n" if (@bad); } sub syntax { my ($estat) = @_; print <