commit:8120b2e16647cef00185b91430455fd4995c8000
author:Chip Black
committer:Chip Black
date:Wed Apr 2 17:02:53 2008 -0500
parents:
Initial commit (from svn r3)
diff --git a/Convfile.example b/Convfile.example
line changes: +6/-0
index 0000000..a8cc6bd
--- /dev/null
+++ b/Convfile.example
@@ -0,0 +1,6 @@
+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 $<

diff --git a/README b/README
line changes: +113/-0
index 0000000..18d753c
--- /dev/null
+++ b/README
@@ -0,0 +1,113 @@
+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.

diff --git a/conv.pl b/conv.pl
line changes: +157/-0
index 0000000..2f5a609
--- /dev/null
+++ b/conv.pl
@@ -0,0 +1,157 @@
+#!/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: