#!/usr/bin/perl -w # Last Updated: 2003.02.12 (xris) use strict; use Cwd; # VCDImager XML Info: # http://www.vcdimager.org/guides/general_xml_structure.html #This program relies on pieces from the vcdimager, transcode and mpgtx packages # # vcdxminfo # vcdxbuild # mpgtx # tcmplex #mpgtx -d -b file file.mpg #tcmplex -m s -i file-0.m2v -p file-0.mp2 -o output.mpg $|++; print "\n"; #Gather in the commandline options use Getopt::Long; my ($Help, $ChapterLength, $Title, $Remux, $ThisDisk, $TotalDisks); GetOptions('chapterlength=s' => \$ChapterLength, 'title|movie=s' => \$Title, 'remux|remultiplex' => \$Remux, 'help' => \$Help); $Title ||= "Video_CD"; $ChapterLength ||= 300; # Show the help if ($Help || !@ARGV) { print <= 0.7.x tcmplex (for --remux) usage: chapterize [options] chapterize [options] options: -c --chapter= Length of chapters in minutes (default: 5) -t --title= SVCD disk title -r --remux= Remultiplex mpeg files with tcmpled? -h --help Show this message files: Run chapterize on a single mpeg file, or a list of files, and they will be combined into a single SVCD bin/cue pair, with chapter markers placed at the specified intervals. When run on a directory, chapterize expects files named according to a certain format: file.mpg or file-.mpg Where is the disk number and is the chapter number. When multiple disk numbers are used, chapterize will create multiple bin/cue pairs. Different chapters of the same disk will obviously be placed into the same bin/cue pair, and as always, chapter markers will also be inserted at the specified intervals. burning: cdrdao write [options] yourfile.cue See the cdrdao documentation for its options. EOF exit; } #Attempt to divine the volume numbers ($ThisDisk, $TotalDisks) = $Title =~ /\b(\d+)\s*of\s*(\d+)$/; $ThisDisk ||= 1; $TotalDisks ||= 1; #Scan the passed-in arguments and figure out whether we're processing directories or files my (%Files, @Files, @Dirs, $fnum, $Parts); foreach my $path (@ARGV) { unless (-e $path) { print "File $path doesn't exist.\n"; next; } if (-d $path) { push @Dirs, $path; } elsif (!@Dirs) { LoadFile($path, ++$fnum); } } #We were asked to process a directory - this requires some checking if (@Dirs) { foreach my $dir (@Dirs) { $dir =~ s/\/+$//s; my ($dirname) = $dir =~ /([^\/]+)$/; #Clean up the title, and possibly autodetect it my $title = $Title; $title =~ s/[\s_]*\d+[\s_]*of[\s_]*\d+\s*$//si; if ($title eq 'Video_CD') { $title = $dirname; $title =~ s/\bSVCD(?:\-\w+)\b//si; $title =~ s/\bDVD(?:Rip)\b//si; $title =~ s/\b(?:LIMITED|INTERNAL)\b//s; } $title =~ s/[\.\s_]+/_/sgi; #Figure out which files are in this directory my %volumes; opendir(DIR, $dir) or die "Can't open directory $dir: $!\n\n"; foreach my $file (grep(/\.mpg$/, readdir DIR)) { $file =~ /(\d+)(?:[^\w\s](\d+))?\.mpg$/ or die "Unknown mpeg name format: $file\n\n"; my $vol = $1; #my $sect = $2; #Try to calculate the number of disks in this volume $TotalDisks = $vol if ($vol > $TotalDisks); #Take note of this file push @{$volumes{$vol}}, $file; } closedir DIR; #Process each file (or group of files) foreach my $vol (sort { $a <=> $b } keys %volumes) { undef @Files; undef %Files; undef $fnum; undef $Parts; foreach my $file (sort byVolNum @{$volumes{$vol}}) { LoadFile("$dir/$file", ++$fnum); } $ThisDisk = $vol; $Title = "${title}_${vol}_of_$TotalDisks"; &WriteXML($dir); &SaveBinCue($dir); } } } #Just asked to process one disk worth of info - we've already gathered the necessary info else { &WriteXML; &SaveBinCue; } ##### ## Beware: Subroutines lurk below! ##### sub Remux { my $path = shift; my ($dir, $name, $basename) = $path =~ /^(.*?\/)?(([^\/]+?)(?:\.\w+)?)$/; $dir ||= ''; #suppress errors print "\n\nmpgtx -d -b \"$dir$basename\" \"$path\"\n\n"; system("nice -n 19 mpgtx -d -b \"$dir$basename\" \"$path\""); print "\n\ntcmplex -m s -i \"$dir$basename-0.m2v\" -p \"$dir$basename-0.mp2\" -o \"$dir$basename-remux.mpg\"\n\n"; system("nice -n 19 tcmplex -m s -i \"$dir$basename-0.m2v\" -p \"$dir$basename-0.mp2\" -o \"$dir$basename-remux.mpg\""); #delete the intermediary files unlink "$dir$basename-0.m2v"; unlink "$dir$basename-0.mp2"; #rename the old file and move the new one into its place rename $path, "$dir$basename.old.mpg"; rename "$dir$basename-remux.mpg", $path; } sub SaveBinCue { my $dir = (shift or ''); #cd into $dir my $thisdir = getcwd; chdir $dir if ($dir); my $safename = $Files[0]->{basename}; $safename =~ tr/a-zA-Z0-9/_/c; print "Writing bin/cue pair $dir$Files[0]->{basename}\n"; system("nice -n 19 vcdxbuild -p -b \"$safename.bin\" -c \"$safename.cue\" \"$Files[0]->{basename}.xml\""); print "\n"; #and back out again chdir $thisdir if ($dir); } sub WriteXML { my $dir = (shift or ''); $dir =~ s/\/*$/\// if ($dir); print "Writing XML file for \"$Title\" (vol. $ThisDisk of $TotalDisks)\n"; open FILE, ">$dir$Files[0]->{basename}.xml" or die "Can't write to $dir$Files[0]->{basename}.xml: $!\n\n"; print FILE < EOF close FILE; } sub LoadFile { my $path = shift; my $fnum = shift; #Already loaded this file return if ($Files{$path}); #Remux? Remux($path) if ($Remux); #Create a record for this item $Parts++; my %file = ('path' => $path, 'id' => $fnum, 'part' => $Parts); ($file{name}, $file{basename}) = $file{path} =~ /(([^\/]+?)(?:\.\w+)?)$/; #Get info about the mpeg, so we can have more accurate timecodes print "Analyzing $path:\n"; my $data = ''; local $/ = "\r"; open DATA, "vcdxminfo -pai \"$path\" |" or die "Can't run vcdxminfo on $path: $!\n\n"; while () { #Trap the progress meter if (s/#scan[^\r\n]+?:\s*([^\r\n]+)\s*//s) { print " processed: $1\r"; } #Don't display xml sequences, but store them to be processed later if (/\s*(\S+?)\s*<\/playing-time>/i; ($file{framerate}) = $data =~ /\s*(\S+?)\s*<\/frame-rate>/i; #Calculate the chapter info my @offsets; if ($data =~ / $b } ($data =~ /\s*(\S+?)\s*<\/aps>/sg)) { last if ($i > $file{length}); #just in case if ($i >= $last + $ChapterLength) { #Determine whether this or the previous offset is closer to the desired timecode if ($i - $last - $ChapterLength >= $last + $ChapterLength - $lasti) { push @offsets, $lasti; } else { push @offsets, $i; } $last = @offsets * $ChapterLength; } $lasti = $i; } } else { my $i; push @offsets, $i while (($i += $ChapterLength) < $file{length}); } my $cnum = 1; #We start on chapter 2, since chapter 1 is covered by the sequence id my $last = undef; foreach my $offset (@offsets) { $cnum++; $Parts++; my %chapter = ('entry' => $offset, 'id' => $cnum, 'timecode' => secs2time($offset), 'part' => $Parts); if ($last) { $file{last} = $last; $file{last}->{next} = \%chapter; } push @{$file{chapters}}, $last = \%chapter; } #Load it onto the file list $file{last} = @Files ? $Files[$#Files] : undef; $file{last}->{next} = \%file if ($file{last}); push @Files, $Files{$path} = \%file; } sub secs2time { my $seconds = shift; my $hours = int($seconds / 3600); $seconds -= 3600 * $hours; my $minutes = int($seconds / 60); $seconds -= 60 * $minutes; return sprintf("%d:%02d:%06.3f", $hours, $minutes, $seconds); } sub byVolNum { my ($av, $as) = $a =~ /(\d+)(?:[^\w\s](\d+))?\.mpg$/; my ($bv, $bs) = $b =~ /(\d+)(?:[^\w\s](\d+))?\.mpg$/; $av <=> $bv or $as <=> $bs; }