Initial commit (from svn r3)
+VORBIS_Q = 5
+LAME_BITRATE = 192
+
+flac => ogg : oggenc --quiet -q $VORBIS_Q $<
+flac => mp3 : lame --quiet -h -b $LAME_BITRATE $< $@
+flac => wav : flac -s -d $<
+Conv - convert files according to rules
+
+Conv is a utility similar to "make" -- it uses a set of rules (by
+default a file called Convfile) to transform files via program
+execution. Conv is considerably simpler than make, though. Conv
+doesn't:
+
+* Recursively resolve inferences - Conv specifies a single command to
+ convert from one file format to another.
+* Work on arbitrary patterns - Conv's rules only specify how to convert
+ from one file extension to another.
+* Support a large set of variable manipulation functions - You get
+ variable substitution, and that's it.
+
+Which is not to say that conv is useless. Far from it. Conv does:
+
+* Support spaces in filenames - Make doesn't do this (well), and this
+ limitation was the primary impetus for creating conv.
+* Provide a simple way to batch convert files - A single conv invocation
+ can convert all files in a directory to a specified format.
+* Support multiprocessing - Conv uses the familiar -j switch to execute
+ multiple conversions simultaneously.
+* Tell you what it's thinking - The -v switch will shed some light on
+ how conv is finding files to convert.
+* Do nothing (if you want) - The -n switch will show you what would be
+ executed, but stop short of actually executing anything.
+* Use alternate rules - The -f <file> switch will use a rule file other
+ than Convfile.
+* Output to a different directory - The -o <dir> switch will make conv
+ put output files in another directory, even if the file you're
+ specifying is not in the current directory.
+
+== Invocation ==
+
+conv [-f <file>] [-o <dir>] [-j <procs>] [-n] [-v] <file1> [<file2> ... <filen>]
+
+ -f <file> Use the specified rule file instead of Convfile
+ -o <dir> Place output files in the specified directory
+ -j <procs> Spawn <procs> conversions at once, for
+ multiprocessor machines.
+ -n Don't actually do anything. Useful with -v to
+ get an idea what conv is thinking.
+ -v Be more verbose.
+
+It is important to realize that, just like make, the files you are
+specifying are the _output_ files, not the input files. The input files
+are found automatically from the first matching rule where a file
+exists. This means that if you have foo.ogg, and you want to produce a
+mp3, you should do this:
+
+$ conv foo.mp3
+
+As a special case, if one of the files specified is exactly '*.<ext>',
+conv will try to convert every file in the current directory into a
+<ext> file, as though you had typed out "<file>.<ext>" manually for each
+file. Do note that if you actually have any files with extension <ext>
+in the current directory, it is likely that your shell will expand the
+glob before conv gets the command line. This might not be what you
+expected, so if in doubt, escape the *.
+
+== Examples ==
+
+1. Convert a directory full of flac files to mp3s using lame.
+
+ --Convfile--
+ flac => mp3 : lame --quiet -h $< $@
+ --cut--
+
+ $ conv *.mp3
+
+2. Convert a movie in AVI format to something an iPod can chew on (but
+ you would probably want to use a separate script for the actual
+ conversion).
+
+ --Convfile--
+ avi => mp4 : ffmpeg -i $< -s 320x240 -vcodec xvid -b 900k -acodec mp3 -ab 96 $@
+ --cut--
+
+ $ conv "drift bible.mp4"
+
+== Convfile format ==
+
+A Convfile has a simple format, which some might say looks like a
+makefile mated with a perl script. This was intentional. :) Every
+line in a Convfile is either blank, or one of two types:
+
+* A rule, which looks like:
+
+ foo => bar : program $< $@
+
+ Which says "Convert files with extension 'foo' to a file with
+ extension 'bar' using program 'program'. $< and $@ mean "input" and
+ "output," respectively, and are substituted with the input and output
+ filenames, just like make does. Note that conv considers an extension
+ to be made up of letters and numbers ONLY. If this isn't sufficient,
+ go hack the regex yourself. :P
+
+* A variable declaration, which looks like:
+
+ foo = bar
+
+ Which says "Set the variable foo to the value 'bar'". Later, the
+ variable can be used by saying $foo. Unlike make, we don't require
+ you to do something silly like put parentheses around the variable
+ name. Variables are case-sensitive, and can be made up of any
+ sequence of letters, numbers, and underscores, except that it can't
+ start with a number (Why? To be consistent with other things. Again,
+ the regex is there). Variables are not substituted into commands
+ until execution, so you could put the variables at the end of the
+ file, though I have no idea why you would do that.
+
+You can put comments anywhere in the file as well, they're simply a #
+followed by whatever you want up until the end of the line.
+#!/usr/bin/perl
+use Getopt::Std;
+use File::Basename;
+use strict;
+use warnings;
+
+my %rules;
+my %vars;
+my %opts = (
+ j => 1
+);
+
+getopts('vnj:f:o:s:', \%opts);
+
+print "Using $opts{j} processors\n" if $opts{v};
+
+my $var_re = qr/[A-Za-z_][A-Za-z0-9_]*/;
+
+sub convert {
+ my ($base, $iext, $oext) = @_;
+ my $cmd = $rules{$oext}{$iext};
+ my $infile = "$base.$iext";
+ $infile =~ s/"/\\"/g;
+ $cmd =~ s!\$\<!"$infile"!g;
+ my $outfile;
+ if ($opts{o}) {
+ # Strip leading path from base so we can stick the file in
+ # another dir.
+ $base = basename($base);
+ $outfile = "$opts{o}/$base.$oext";
+ } else {
+ $outfile = "$base.$oext";
+ }
+ $outfile =~ s/"/\\"/g;
+ $cmd =~ s!\$\@!"$outfile"!g;
+ $cmd = expand($cmd);
+ print "$cmd\n";
+ return if $opts{n};
+ my $pid = fork();
+ return $pid if $pid;
+
+ exec($cmd);
+}
+
+sub expand {
+ local $_ = shift;
+ s/\$($var_re)/(exists $vars{$1} ? $vars{$1} : '$' . $1)/eg;
+ return $_;
+}
+
+open CONF, ($opts{f} || 'Convfile') or die "Could not open Convfile";
+while (<CONF>) {
+ s/#.*$//;
+ s/^\s+//;
+ s/\s+$//;
+ next if /^$/;
+
+ if (/^(\w+)\s*=>\s*(\w+)\s*:\s*(.*)$/) {
+ $rules{$2}{$1} = $3;
+ } elsif (/^($var_re)\s*=\s*(.*)$/) {
+ $vars{$1} = expand($2);
+ } else {
+ warn "Error in Convfile line $.: $_\n";
+ }
+}
+close CONF;
+
+my @queue;
+
+# If we specify reading from stdin, do this.
+if (exists $opts{s}) {
+ while (<STDIN>) {
+ next if /^#/;
+ chomp;
+
+ unless (-e $_) {
+ print "File specified on stdin, $_, does not exist.\n";
+ next;
+ }
+
+ if (/^(.+)\.(\w+)$/) {
+ my $base = $1;
+ my $iext = $2;
+ print "Considering $base.$iext\n" if $opts{v};
+ if (exists $rules{$opts{s}}{$iext}) {
+ print "Using $base.$iext\n" if $opts{v};
+ push @queue, [$base, $iext, $opts{s}];
+ }
+ } else {
+ print "I don't see an extension on $_, so I don't know what to do with it.\n";
+ }
+ }
+}
+
+# Not reading filenames from stdin.
+else {
+ # Do something fun
+ my @tmpARGV;
+ foreach (@ARGV) {
+ if (/^\*\.(\w+)$/) {
+ my $extsub = $1;
+ opendir D, '.';
+ my @files =
+ map { s/\.(\w+)$/\.$extsub/; $_ }
+ grep { /^[^.]/ }
+ readdir D;
+ closedir D;
+ push @tmpARGV, @files;
+ } else {
+ push @tmpARGV, $_;
+ }
+ }
+ @ARGV = @tmpARGV;
+
+ # I like to call this the TOWER OF POWER
+ for my $ofile (@ARGV) {
+ my $ofound = 0;
+ for my $oext (keys %rules) {
+ if ($ofile =~ /^(.*)\.$oext$/) {
+ my $base = $1;
+ my $ifound = 0;
+ for my $iext (keys %{$rules{$oext}}) {
+ print "Considering $base.$iext\n" if $opts{v};
+ if (-e "$base.$iext") {
+ print "Using $base.$iext\n" if $opts{v};
+ push @queue, [$base, $iext, $oext];
+ $ifound = 1;
+ last;
+ }
+ }
+ unless ($ifound) {
+ print "No suitable input file found for $ofile\n";
+ }
+ $ofound = 1;
+ last;
+ }
+ }
+ unless ($ofound) {
+ print "No rule to make $ofile\n";
+ }
+ }
+}
+
+print scalar @queue, " items to process\n" if $opts{v};
+
+my $procs = 0;
+foreach my $chunk (@queue) {
+ convert(@$chunk);
+ $procs++;
+ if ($procs == $opts{j}) {
+ wait;
+ $procs--;
+ }
+}
+wait while $procs--;
+
+# vim:set ts=4 sts=4 sw=4 expandtab: