#!/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" unless($four);
foreach $vfile (@vfiles) {
if ($nochapt) {
$chapters = '';
} else {
%mid = &midentify($vfile);
$minutes = int($mid{'ID_LENGTH'} / 60);
$chapters = " chapters=\"0";
for ($x = $minperchapter; $x < $minutes; $x += $minperchapter) {
$chapters .= "," . fmttimediff(0, $x*60);
}
$chapters .= "\"";
}
print XML " \n";
print XML " \n";
print XML " call menu; \n" 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 <