Index: trunk/NOTES
===================================================================
--- trunk/NOTES	(revision 48)
+++ 	(revision )
@@ -1,15 +1,0 @@
-MP3 PLAYBACK FROM USB IN STEREO
-
-<http://www.hydrogenaudio.org/forums/index.php?showtopic=79667&st=0&p=695497&#entry695497>
-
-There is a utility called fatsort which will sort the files on FAT16 and FAT32 filesystems. For Windows you can use http://hem.passagen.se/chsw/fatsort/index.html and if you use OS X or BSD ot GNU/Linux or similar you can use the utility from http://fatsort.berlios.de/ In some distributions fatsort is in the repositories. I've used the fatsort utility in Debian to sort a couple of very basic generic mp3 players and it works fine. I didn't try the Windows version.
-
-At the moment your player is playing the files in the order they were written to disk with no reference to any naming scheme. Either of these fatsort utilities will rewrite the directory structure so it works how you are used to and would usually expect. 
-
-
-FAT32 UPPER LIMIT: 4GB
-
-MUSIC LIBRARY PARTS
-
-index of tracks
-index of albums (including physical details about the CDs)
Index: trunk/applymeta
===================================================================
--- trunk/applymeta	(revision 48)
+++ 	(revision )
@@ -1,77 +1,0 @@
-#!/usr/bin/perl -w
-use strict;
-
-use Getopt::Long;
-use Config::Properties;
-use YAML;
-use MP3::Tag;
-
-GetOptions(
-    'file|f=s'   => \my $META_FILE,
-    'clear'      => \my $CLEAR,
-    'tracks|t=s' => \my $TRACKS,
-);
-
-my $meta;
-
-if ($META_FILE eq '-') {
-    $meta = Config::Properties->new;
-    $meta->load(*STDIN);
-} else {
-    $meta = Config::Properties->new(
-        file => $META_FILE,
-    );
-}
-
-my $metadata = $meta->splitToTree;
-
-my $track_count = scalar grep { /^TRACK/ } keys %{ $metadata };
-
-my @track_numbers;
-if ($TRACKS) {
-    foreach (split /,/, $TRACKS) {
-        if (/^(\d+)$/) {
-            push @track_numbers, $1;
-        } elsif (/^(\d+)-(\d+)$/) {
-            push @track_numbers, ($1 .. $2);
-        } elsif (/^-(\d+)$/) {
-            push @track_numbers, (1 .. $1);
-        } elsif (/^(\d+)-$/) {
-            push @track_numbers, ($1 .. $track_count);
-        }
-    }
-} else {
-    @track_numbers = (1 .. $track_count);
-}
-
-for my $i (@track_numbers) {
-    my $audio_file = shift or die "No more audio files to process\n";
-    my $mp3 = MP3::Tag->new($audio_file);
-
-    # first clear the tags if asked
-    if ($CLEAR) {
-        warn "clearing first";
-        $mp3->delete_tag($_) foreach qw{ID3v1 ID3v2};
-    }
-
-    my $track_key = sprintf "TRACK%02d", $i;
-    my $track = $metadata->{$track_key};
-    #TODO: for reissues, what should the year/date be tagged as?
-    my ($year) = ($metadata->{ORIGINALDATE} || $metadata->{DATE} || '' =~ /^(\d{4})/);
-    $mp3->title_set($track->{TITLE});
-    $mp3->artist_set($track->{ARTIST} || $metadata->{ALBUMARTIST});
-    $mp3->album_set($track->{ALBUM}  || $metadata->{ALBUM});
-    $mp3->year_set($year) if $year;
-    $mp3->track_set($i);
-    $mp3->update_tags;
-    printf "    Apply to %s\n", $audio_file;
-
-    #TODO: full version of a make-string-filename-safe function
-    my $sort_filename = lc $track->{ARTISTSORT};
-    for ($sort_filename) {
-        s/&/and/g;
-        s/\W/-/g;
-        s/--/-/g;
-    }
-    print "Artist sort filename would be: " . $sort_filename . "\n";
-}
Index: trunk/cd2flac
===================================================================
--- trunk/cd2flac	(revision 48)
+++ trunk/cd2flac	(revision 1)
@@ -2,26 +2,132 @@
 use strict;
 
-use FindBin qw{$RealBin};
-use lib "$RealBin/lib";
+package Tracks;
 
-use Ripper;
+use Class::Std;
+use Digest::SHA1;
+use Audio::FLAC::Header;
 
+use constant SECTOR_OFFSET => 150;
+
+my %tracks_for :ATTR( get => 'tracks' );
+
+sub _get_tracks_from_cdinfo {    
+    my $device = shift;
+    my @tracks;
+    open my $CD_INFO, 'cd-info -q |' or die "Unable to run cd-info: $!";
+    while (<$CD_INFO>) {
+        next unless /^\s*([0-9]+): \d\d:\d\d:\d\d  (\d{6})/;
+        my ($num, $sector) = ($1, $2);
+        my $track = {
+            number => $num,
+            sector => $sector,
+        };
+        # place leadout track (170) at index 0
+        $num != 170 ? $tracks[$num] = $track : $tracks[0] = $track;
+    }
+    close $CD_INFO;
+
+    return @tracks;
+}
+
+sub _get_tracks_from_cdparanoia {
+    my $device = shift;
+    my @tracks;
+    open my $CDP, 'cdparanoia -d ' . $device . ' -Q 2>&1 |' or die "Unable to run cdparanoia: $!";
+    while (<$CDP>) {
+        if (m{
+            ^\s+(\d+)\.               # track number
+            \s+(\d+)                  # length
+            \s+\[(\d\d:\d\d\.\d\d)\]  # length (MSF)
+            \s+(\d+)                  # start
+            \s+\[(\d\d:\d\d\.\d\d)\]  # start (MSF)
+        }x) {
+            my ($track, $length, $length_msf, $start, $start_msf) = ($1, $2, $3, $4, $5);
+            $start_msf =~ s/\./:/;
+            $tracks[$track] = {
+                number => $track,
+                sector => $start,
+                msf    => $start_msf,
+            };
+        } elsif (m{TOTAL\s+(\d+)}) {
+            my $total = $1;
+            my $leadout = $total + $tracks[1]{sector};
+            $tracks[0] = {
+                number => 170,
+                sector => $leadout,
+            };        
+        }    
+    }
+    close $CDP;
+
+    return @tracks;
+}
+
+sub read_disc {
+    my ($self, $device) = @_;
+    $tracks_for{ident $self} = [ _get_tracks_from_cdparanoia($device) ];
+}
+
+sub get_mbz_discid {
+    my ($self) = @_;
+
+    my @tracks = @{ $tracks_for{ident $self} };
+
+    return unless @tracks;
+
+    my $sha1 = Digest::SHA1->new;
+
+    $sha1->add(sprintf('%02X', $tracks[1]{number}));
+    $sha1->add(sprintf('%02X', $tracks[-1]{number}));
+    for my $i (0 .. 99) {
+        my $offset = (defined $tracks[$i]{sector} ? ($tracks[$i]{sector} + SECTOR_OFFSET) : 0);
+        $sha1->add(sprintf('%08X', $offset));
+    }
+
+    my $digest = $sha1->b64digest;
+    $digest =~ tr{+/=}{._-};
+    $digest .= '-';  ## why do we need to manually add this?
+
+    return $digest;
+}
+
+
+sub get_cuesheet {
+    my ($self) = @_;
+    my @tracks = @{ $tracks_for{ident $self} };
+    my @cuesheet;
+    push @cuesheet, qq{FILE "cdda.wav" WAVE};
+    for my $i (1 .. @tracks - 1) {
+        my $track = $tracks[$i];
+        push @cuesheet, sprintf('  TRACK %02d AUDIO', $i);
+        if ($i == 1 && $track->{sector} != 0) {
+            push @cuesheet, '    INDEX 00 00:00:00';
+        }
+        push @cuesheet, '    INDEX 01 ' . $track->{msf};
+    }
+    return join('', map { "$_\n" } @cuesheet);
+}
+
+sub get_cdparanoia_span {
+    my ($self) = @_;
+    # use a msf start unless track 1 begins at sector
+    return $tracks_for{ident $self}[1]{sector} == 0 ? '1-' : '00:00.00-';
+}
+
+package main;
+use File::Temp qw{tempdir};
 use File::Spec::Functions qw{catfile splitpath};
+use File::Copy;
 use File::Path qw{mkpath};
 use Getopt::Long qw{:config no_ignore_case no_auto_abbrev};
 use Cwd;
-use Audio::FLAC::Header;
-use MusicBrainz;
 
 GetOptions(
-    'device|D=s'  => \my $CD_DEVICE,
-    'output|o=s'  => \my $OUTPUT_NAME,
-    'force|f'     => \my $FORCE,
-    'barcode|b=s' => \my $BARCODE,
-    'properties'  => \my $PROPERTIES,
+    'device|D=s' => \my $CD_DEVICE,
+    'output|o=s' => \my $OUTPUT_NAME,
+    'force|f'    => \my $FORCE,
 );
 
 # output file
-die "Usage: $0 -o <output.flac> [-D <device>]\n" unless $OUTPUT_NAME;
 my (undef, $out_dir, $out_file) = splitpath($OUTPUT_NAME);
 # automatically add ".flac"
@@ -38,23 +144,34 @@
 # get the CD info
 $CD_DEVICE ||= '/dev/cdrom';
+my $tracks = Tracks->new;
+$tracks->read_disc($CD_DEVICE);
 
-my $tags = $BARCODE ? { BARCODE => $BARCODE } : {};
-my $ripper = Ripper->new({ device => $CD_DEVICE });
-my $flac_disc = $ripper->rip_to_flac($archive_flac, $tags);
+die "No tracks found; is there a CD in the drive?\n" unless @{ $tracks->get_tracks };
 
-if ($PROPERTIES) {
-    my $info = get_musicbrainz_info({
-        discid  => $flac_disc->discid,
-        barcode => $flac_disc->barcode,
-    });
-    if ($info) {
-        (my $properties_file = $archive_flac) =~ s/\.flac$/.properties/;
-        open my $fh, '>', $properties_file or die "Cannot write $properties_file\n";
-        print $fh "$_=$$info{$_}\n" foreach sort keys %{ $info };
-        close $fh;
-        print "Properties written as $properties_file\n";
-    }
-}
+my $tempdir = tempdir(CLEANUP => 1);
 
+my $wav_file  = catfile($tempdir, 'cdda.wav');
+my $flac_file = catfile($tempdir, 'cdda.flac');
+my $cue_file  = catfile($tempdir, 'cdda.cue');
+
+# rip
+my $span = $tracks->get_cdparanoia_span;
+system 'cdparanoia', '-d', $CD_DEVICE, $span, $wav_file;
+die "\nRipping canceled\n" if ($? & 127);
+
+
+# encode + cuesheet
+open my $CUE, "> $cue_file";
+print $CUE $tracks->get_cuesheet;
+close $CUE;
+system 'flac', '-o', $flac_file, '--cuesheet', $cue_file, $wav_file;
+die "\nFLAC encoding canceled\n" if ($? & 127);
+
+# MusicBrainz discid metadata
+my $discid = $tracks->get_mbz_discid;
+
+# copy to permanent location
+copy($flac_file, $archive_flac);
+system 'metaflac', '--set-tag', "MBZ_DISCID=$discid", $archive_flac;
 print "Rip saved as $archive_flac\n";
 system 'eject', $CD_DEVICE;
Index: trunk/cuesheet2tocdata
===================================================================
--- trunk/cuesheet2tocdata	(revision 48)
+++ trunk/cuesheet2tocdata	(revision 1)
@@ -2,10 +2,40 @@
 use strict;
 
-use FindBin qw{$RealBin};
-use lib "$RealBin/lib";
+# XXX: does this every vary?
+# or is the lead-in always 88200 samples (88200 / 588 = 150)?
+use constant SECTOR_OFFSET => 150;  # the lead-in, in frames
 
-use Tracks;
+# conversion factors
+use constant FRAMES_PER_SECOND => 75;
+use constant SECONDS_PER_MINUTE => 60;
 
-my $tracks = Tracks->new;
-$tracks->read_cue(shift || '-');
-print "http://musicbrainz.org/bare/cdlookup.html?toc=" . join("+", $tracks->get_musicbrainz_tocdata) . "\n";
+# see also http://flac.sourceforge.net/format.html#cuesheet_track
+# 588 samples/frame = 44100 samples/sec / 75 frames/sec
+use constant SAMPLES_PER_FRAME => 588;
+
+my @sectors;
+my $total_sectors;
+while (<>) {
+    if (/INDEX 01/) {
+        my ($m,$s,$f) = /INDEX 01 (\d\d):(\d\d):(\d\d)/;
+        push @sectors, ($m * SECONDS_PER_MINUTE * FRAMES_PER_SECOND) + ($s * FRAMES_PER_SECOND) + $f + SECTOR_OFFSET;
+    } elsif (/lead-out/) {
+        my ($total_samples) = /lead-out \d+ (\d+)/;
+        $total_sectors = ($total_samples / SAMPLES_PER_FRAME) + SECTOR_OFFSET;
+    }
+}
+
+# this is a CD TOC suitable for submitting to MusicBrainz as a CD Stub
+# http://musicbrainz.org/doc/XML_Web_Service#Submitting_a_CDStub
+my @toc_data = (
+    1,                # first track number
+    scalar(@sectors), # last track number
+    $total_sectors,   # last frame (sector?)
+    @sectors,         # start frame for each track
+);
+
+my $url = q{http://musicbrainz.org/bare/cdlookup.html?toc=} . join('+', @toc_data);
+
+print "$url\n";
+#print join(' ', @toc_data) . "\n";
+#print "$_\n" foreach @toc_data;
Index: trunk/flac2mp3s
===================================================================
--- trunk/flac2mp3s	(revision 48)
+++ 	(revision )
@@ -1,9 +1,0 @@
-#!/bin/bash
-FLAC=$1
-DIR=`echo $FLAC | sed s/\.flac$//`
-mkdir -p $DIR
-metaflac --export-cuesheet-to - $FLAC | \
-    grep 'TRACK' | cut -d' ' -f4 | \
-    while read TRACKNUM; \
-	do flactrack -D $TRACKNUM=$DIR/$TRACKNUM $FLAC; \
-    done;
Index: trunk/flac2properties
===================================================================
--- trunk/flac2properties	(revision 48)
+++ 	(revision )
@@ -1,12 +1,0 @@
-#!/bin/bash
-
-for FLAC in "$@"; do
-    if [ -e "$FLAC" ]; then
-	PROPERTIES=$(sed -e's/\.flac$/.properties/' <<< "$FLAC")
-	echo $FLAC
-	mbz --flac "$FLAC" > "$PROPERTIES"
-	if [ ! -s "$PROPERTIES" ]; then
-	    rm "$PROPERTIES"
-	fi
-    fi
-done
Index: trunk/flacout
===================================================================
--- trunk/flacout	(revision 1)
+++ trunk/flacout	(revision 1)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl -w
+use strict;
+
+use Audio::FLAC::Decoder;
+
+my $FLAC_FILE = shift;
+
+my $decoder = Audio::FLAC::Decoder->open($FLAC_FILE);
+#warn $decoder->bitrate;
+#exit;
+my $buffer;
+while (my $len = $decoder->sysread($buffer, 1) > 0) {
+    print $buffer; #$len; # ord $buffer . "\n";
+}
Index: trunk/flacplay
===================================================================
--- trunk/flacplay	(revision 1)
+++ trunk/flacplay	(revision 1)
@@ -0,0 +1,154 @@
+#!/usr/bin/perl -w
+use strict;
+
+use IPC::Open2;
+use IO::Prompt;
+
+my $FLAC_FILE = shift;
+my $TRACK = shift || 1;
+
+
+# get the track start positions from the embedded cuesheet
+open my $CUESHEET, "metaflac --export-cuesheet-to - $FLAC_FILE |";
+
+my @position_for_track;
+my $i = 1;
+while (<$CUESHEET>) {
+    if (my ($m,$s,$f) = /INDEX 01 (\d\d):(\d\d):(\d\d)/) {
+	$position_for_track[$i] = ($m * 60) + $s + ($f / 75);
+	$i++;
+    }
+}
+
+close $CUESHEET;
+
+
+my $track_count = scalar @position_for_track - 1;
+print "$track_count tracks\n";
+
+die "There is no track $TRACK" if $TRACK < 1 or $TRACK > $track_count;
+
+    
+# launch the flac123 player as a daemon
+my $flac123_pid = open2(my $PLAYER_OUT, my $PLAYER_IN, qw{flac123 -R});
+
+print "flac123 PID = $flac123_pid\n";
+
+my $current_track = $TRACK;
+my $next_track_start = $position_for_track[$current_track + 1];
+
+print "Track $current_track\n";
+
+my %commands = (
+    z => sub {
+        return if $current_track <= 1;
+        $current_track--;
+        my $position = $position_for_track[$current_track];
+        print $PLAYER_IN "JUMP $position\n";
+        print "Track $current_track\n";
+    },
+    x => sub {},
+    c => sub {},
+    v => sub {},
+    b => sub {
+        return if $current_track >= $track_count;
+        # note, don't increment or print $current_track here, 
+        # since it will be automatically detected by the other 
+        # process watching the output of flac123
+        my $position = $position_for_track[$current_track + 1];
+        print $PLAYER_IN "JUMP $position\n";
+    },
+    q => sub {
+        print $PLAYER_IN "QUIT\n";
+        exit;
+    },
+);
+
+# track has advanced automatically
+$SIG{USR1} = sub {
+    # detect track change
+    my $time = read_time();
+    if (defined $next_track_start && $time >= $next_track_start) {
+        $current_track++;
+        $next_track_start = $position_for_track[$current_track + 1];
+        print "Track $current_track\n";
+    }
+};
+
+my $parent_pid = $$;
+my $pid = fork;
+
+
+if ($pid) {
+    # parent
+    while (my $cmd = prompt('', -one_char, -echo => '')) {
+        if (exists $commands{$cmd}) {
+            $commands{$cmd}->();
+        }
+    }
+        
+} elsif (defined $pid) {
+    # child
+
+    print $PLAYER_IN "LOAD $FLAC_FILE\n";
+    print $PLAYER_IN "JUMP $position_for_track[$TRACK]\n";
+
+    while (<$PLAYER_OUT>) {
+        if (/\@F (\d+) (\d+) (\d+\.\d\d) (\d+\.\d\d)/) {
+            my ($frame, $frames_left, $time, $time_left) = ($1, $2, $3, $4);
+            # note the current time and alert the parent
+            write_time($time);
+            kill 'USR1', $parent_pid;
+        }
+    }
+    waitpid $flac123_pid, 0;
+
+} else {
+    die "Unable to fork: $!";
+}
+
+sub write_time {
+    my ($time) = @_;
+    open my $TIME, '>', 'current_time';
+    print $TIME "$time\n";
+    close $TIME;
+}
+
+sub read_time {
+    open my $TIME, '<', 'current_time';
+    my $time = <$TIME>;
+    close $TIME;
+    chomp $time;
+    return $time;
+}
+
+=begin flac123 -R outputs
+
+@R FLAC123
+flac123 tagline. Output at startup.
+
+@I ID3:<a><b><c><d><e><f>
+Prints out the metadata information after loading the flac file.
+a = title (30 chars)
+b = artist (30 chars)
+c = album (30 chars)
+d = year (4 chars)
+e = comment (30 chars)
+f = genre (30 chars)
+
+@I filename
+Prints out the filename of the flac file, minus the extension. Happens after
+a flac file has been loaded and there is no metadata available.
+
+@F <current-frame> <frames-remaining> <current-time> <time-remaining>
+Frame decoding status updates (once per frame).
+Current-frame and frames-remaining are integers; current-time and
+time-remaining floating point numbers with two decimal places.
+
+@P {0, 1, 2}
+Stop/pause status.
+0 - playing has stopped. When 'STOP' is entered, or the flac file is finished.
+1 - Playing is paused. Enter 'PAUSE' or 'P' to continue.
+2 - Playing has begun again.
+
+=cut
Index: trunk/flactrack
===================================================================
--- trunk/flactrack	(revision 48)
+++ trunk/flactrack	(revision 1)
@@ -7,17 +7,5 @@
 # TODO: separate sox filter for each track!
 
-use FindBin;
-use lib "$FindBin::RealBin/lib";
-
-use Getopt::Long qw{:config no_ignore_case};
-use File::Spec::Functions qw{catfile splitpath};
-use File::Path;
-use Audio::FLAC::Header;
-use MusicBrainz;
-use Text::Unidecode;
-use Cwd;
-
-# default to using tags
-my $TAGS = 1;
+use Getopt::Long;
 
 GetOptions(
@@ -25,9 +13,4 @@
     't=s' => \my $TYPE,
     'x=s' => \my $SOX_FILTER,
-    'all|a' => \my $ALL,
-    'dir|d=s' => \my $DIRECTORY,
-    'ascii-tags' => \my $ASCII_TAGS,
-    'tags!' => \$TAGS,
-    'force' => \my $FORCE,
 );
 
@@ -37,66 +20,21 @@
 $TYPE ||= 'mp3';
 
-# default to the current directory
-$DIRECTORY ||= cwd;
+# for getting track metadata from MusicBrainz
+my %info;
+if ($TYPE eq 'mp3') {
+    require Audio::FLAC::Header;
+    require LWP;
+    require XML::XPath;
+    require XML::XPath::XMLParser;
 
-my $flac = Audio::FLAC::Header->new($FLAC_FILE) or die "Can't read FLAC header from $FLAC_FILE\n";
+    my $flac = Audio::FLAC::Header->new($FLAC_FILE) or warn "Can't read FLAC header from $FLAC_FILE\n";
+    my $discid = $flac->tags('MBZ_DISCID') or warn "No MBZ_DISCID tag in $FLAC_FILE\n" if $flac;
+    #TODO: calculate TOC and DISCID from cuesheet if there is no MBZ_DISCID tag present
 
-# for getting track metadata from MusicBrainz
-my $info;
-if ($ALL || ($TYPE eq 'mp3' && $TAGS)) {
-    (my $properties_file = $FLAC_FILE) =~ s/\.flac$/.properties/;
-    if (-e $properties_file) {
-        require Config::Properties;
-
-        # the properties are in UTF-8; mark them as such so unidecode works correctly later
-        my $properties = Config::Properties->new(file => $properties_file, encoding => 'utf8');
-        $info = $properties->getProperties;
-    } else {
-
-        my $discid = $flac->tags('MUSICBRAINZ_DISCID') or warn "No MUSICBRAINZ_DISCID tag in $FLAC_FILE\n" if $flac;
-        #TODO: calculate TOC and DISCID from cuesheet if there is no MUSICBRAINZ_DISCID tag present
-
-        $info = get_musicbrainz_info($discid);
-    }
-    exit unless $info;
+    #TODO: use the functions in mbz instead of repeating them here)
+    %info = get_musicbrainz_info($discid);
 }
 
-if ($ALL) {
-    # if we are converting an entire album file, create a directory to hold the mp3s
-    # name the directory ARTIST.DATE.ALBUM, so it will sort nicely
-    my $base_dir = $DIRECTORY;
-    my $album_dir = join('.', map { to_filename($_) } @{$info}{qw{ALBUMARTISTSORT ORIGINALDATE ALBUM}});
-    # need to append "disc#" if this is a multidisc album
-    if ($info->{DISCTOTAL} > 1) {
-        $album_dir .= '.disc_' . $info->{DISCNUMBER};
-    }
-    $DIRECTORY = catfile($base_dir, $album_dir);
-    if (-e $DIRECTORY and not $FORCE) {
-        die "$DIRECTORY already exists, skipping. (Use --force to overwrite)\n";
-    }
-    #die $DIRECTORY;
-}
-
-if ($ALL) {
-    die "Use of --all requires a --directory\n" unless $DIRECTORY;
-    die "No track info found on MusicBrainz for $FLAC_FILE\n" unless $info;
-    use YAML;
-    my $cuesheet = $flac->cuesheet;
-    my $count = scalar grep { /TRACK \d\d/ } @{ $flac->cuesheet };
-    print "Found $count tracks\n";
-    #TODO: default to just 01, 02, etc. if there is no $info
-    %TRACKS = map {
-        $_ => catfile($DIRECTORY, sprintf('%02d.%s', $_, to_filename($info->{sprintf 'TRACK%02d.TITLE', $_})))
-    } (1 .. $count);
-    #print Dump(\%TRACKS);
-    for my $tracknum (sort { $a <=> $b } keys %TRACKS) {
-        printf "%2d: %s\n", $tracknum, $TRACKS{$tracknum};
-    }
-    mkpath($DIRECTORY);
-}
-
-#TODO: all the option of sorting by tracknum or not
-#while (my ($tracknum, $title) = each %TRACKS) {
-for my $tracknum (sort { $a <=> $b } keys %TRACKS) {
+while (my ($tracknum, $title) = each %TRACKS) {
     if ($tracknum !~ /^\d+$/) {
 	warn "Don't know what to do with track number '$tracknum'";
@@ -111,16 +49,15 @@
     }
 
-    my $title = quotemeta($TRACKS{$tracknum});
+    $title = quotemeta($title);
     if ($TYPE eq 'mp3') {
         # bitrate of 192
         $cmd .= qq{| lame -b 192};
         # if there is track info, add it as ID3 tags
-        if ($info) {
-            my $track_key = sprintf 'TRACK%02d', $tracknum;
-            $cmd .= sprintf q{ --tt %s --ta %s --tl %s --ty %d --tn %d},
-                quote($ASCII_TAGS ? unidecode($info->{"$track_key.TITLE"}) : $info->{"$track_key.TITLE"}),
-                quote($ASCII_TAGS ? unidecode($info->{"$track_key.ARTIST"}) : $info->{"$track_key.ARTIST"}),
-                quote($ASCII_TAGS ? unidecode($info->{ALBUM}) : $info->{ALBUM}),
-                ($info->{ORIGINALDATE} =~ /^(\d\d\d\d)/)[0],
+        if (%info) {
+            my $track = $info{TRACKS}[$tracknum];
+            $cmd .= sprintf q{ --tt %s --ta %s --tl %s --tn %d},
+                quote($$track{TITLE}),
+                quote($$track{ARTIST}),
+                quote($info{ALBUM}),
                 $tracknum;
         }
@@ -133,5 +70,4 @@
     #die $cmd;
     system $cmd;
-    die "\nFLAC decoding canceled\n" if ($? & 127);
 
     print "\n" if $SOX_FILTER;
@@ -144,12 +80,50 @@
 }
 
-sub to_filename {
-    my @strings = @_;
-    return map {
-        s/&/ and /g;
-        unidecode($_);
-        s/[^a-z0-9-_ ]+//gi;
-        s/ +/_/g;
-        lc;
-    } @strings;
+
+# make the output terminal handle UTF-8 characters
+#binmode STDOUT, ':utf8';
+#my $info = get_musicbrainz_info($discid);
+#for my $key (sort keys %{ $info }) {
+#    print "$key=$$info{$key}\n";
+#}
+
+sub lookup_release {
+    my ($discid) = @_;
+    my $ua = LWP::UserAgent->new;
+
+    my $uri = URI->new('http://musicbrainz.org/ws/1/release/');
+    $uri->query_form(type => 'xml', discid => $discid);
+
+    my $res = $ua->get($uri);
+    return $res->decoded_content;
 }
+
+sub get_musicbrainz_info {
+    my ($discid) = @_;
+    my %info;
+
+    $info{MBZ_DISCID} = $discid;
+
+    my $xpath = XML::XPath->new();
+
+    $xpath->set_xml(lookup_release($discid));
+
+    # TODO: check for more than 1 release?
+
+    $info{MB_RELEASE_ID} = $xpath->findvalue('//release/@id');
+    $info{ALBUM}         = $xpath->findvalue('//release/title');
+    $info{ARTIST}        = $xpath->findvalue('//release/artist/name');
+    $info{TRACKS}        = [];
+
+    # TODO: get release date
+
+    my $tracknum = 1;
+    for my $track_node ($xpath->findnodes('//track-list/track')) {
+        $info{TRACKS}[$tracknum]{MB_TRACKID} = $xpath->findvalue('@id', $track_node);
+        $info{TRACKS}[$tracknum]{TITLE}      = $xpath->findvalue('title', $track_node);
+        $info{TRACKS}[$tracknum]{ARTIST}     = $xpath->findvalue('artist/name', $track_node) || $info{ARTIST};
+        $tracknum++;
+    }
+
+    return %info;
+}
Index: trunk/id3ascii
===================================================================
--- trunk/id3ascii	(revision 48)
+++ 	(revision )
@@ -1,9 +1,0 @@
-#!/bin/bash
-
-# converts the ID3 tag info in files given as arguments to ASCII-only,
-# using the Perl Unidecode module
-
-for FILE in "$@"; do
-    echo "$FILE"
-    ./id3dump "$FILE" | unidecode | ./id3load "$FILE"
-done
Index: trunk/id3dump
===================================================================
--- trunk/id3dump	(revision 48)
+++ 	(revision )
@@ -1,23 +1,0 @@
-#!/usr/bin/perl -w
-use strict;
-
-# prints ID3 tag info from an MP3 file in Vorbis tag format to STDOUT
-
-use MP3::Tag;
-
-my $audio_file = shift or die "Usage: $0 <mp3-file>\n";
-my $mp3 = MP3::Tag->new($audio_file);
-
-my %key_for = (
-    ALBUM       => 'album',
-    ARTIST      => 'artist',
-    DATE        => 'year',
-    TITLE       => 'title',
-    TRACKNUMBER => 'track',
-);
-
-my $info = $mp3->autoinfo;
-
-for my $vorbis_tag (sort keys %key_for) {
-    print "$vorbis_tag=" . ($info->{$key_for{$vorbis_tag}} || '') . "\n";
-}
Index: trunk/id3load
===================================================================
--- trunk/id3load	(revision 48)
+++ 	(revision )
@@ -1,29 +1,0 @@
-#!/usr/bin/perl -w
-use strict;
-
-# reads tags from STDIN and applies them to the MP3 file given as the first argument
-
-use MP3::Tag;
-
-my $audio_file = shift or die "Usage: $0 <mp3-file>\n";
-my $mp3 = MP3::Tag->new($audio_file);
-
-my %method_for = (
-    ALBUM       => sub { $_[0]->album_set($_[1]) },
-    ARTIST      => sub { $_[0]->artist_set($_[1]) },
-    DATE        => sub { $_[0]->year_set($_[1]) },
-    TITLE       => sub { $_[0]->title_set($_[1]) },
-    TRACKNUMBER => sub { $_[0]->track_set($_[1]) },
-);
-
-while (<>) {
-    chomp;
-    next if /\s*#/;
-    next unless /\S/;
-    my ($key, $value) = split(/=/, $_, 2);
-    if (exists $method_for{$key}) {
-        $method_for{$key}->($mp3, $value);
-    }
-}
-
-$mp3->update_tags;
Index: trunk/mbz
===================================================================
--- trunk/mbz	(revision 48)
+++ trunk/mbz	(revision 1)
@@ -4,28 +4,25 @@
 # one use case: for f in ~/cds/*.flac; do mbid=`./mbz --flac $f --get-release-id`; echo $f $mbid; done;
 
-use FindBin;
-use lib "$FindBin::RealBin/lib";
+use Getopt::Long;
+#use WebService::MusicBrainz;
 
-use Getopt::Long;
-use MusicBrainz;
-use DiscFlacFile;
+use LWP;
+use XML::XPath;
+use XML::XPath::XMLParser;
 
 GetOptions(
-    'flac=s'         => \my $FLAC_FILE,
-    'barcode=s'      => \my $BARCODE,
+    'flac=s' =>\my $FLAC_FILE,
     'get-release-id' => \my $GET_RELEASE_ID,
-    'xml'            => \my $GET_XML,
+    'xml' =>\my $GET_XML,
 );
 
 my $discid;
-my $barcode;
 
 if ($FLAC_FILE) {
-    my $flac_disc = DiscFlacFile->new({ file => $FLAC_FILE });
-    $discid  = $flac_disc->discid;
-    $barcode = $flac_disc->barcode;
+    require Audio::FLAC::Header;
+    my $flac = Audio::FLAC::Header->new($FLAC_FILE) or die "Can't read FLAC header from $FLAC_FILE\n";
+    $discid = $flac->tags('MBZ_DISCID') or die "No MBZ_DISCID tag in $FLAC_FILE\n";
 } else {
-    $discid  = shift;
-    $barcode = $BARCODE;
+    $discid = shift;
 }
 
@@ -33,20 +30,10 @@
 binmode STDOUT, ':utf8';
 
-# just dump the XML, if requested
-if ($GET_XML) {
-    print lookup_release($discid);
-    exit;
-}
-
-# otherwise, do the full parsing of the data
-my $info = get_musicbrainz_info({
-    discid  => $discid,
-    barcode => $barcode,
-});
+my $info = get_musicbrainz_info($discid);
 
 exit unless $info;
 
 if ($GET_RELEASE_ID) {
-    print "$$info{MUSICBRAINZ_ALBUMID}\n";
+    print "$$info{RELEASE_MBID}\n";
 } else {
     for my $key (sort keys %{ $info }) {
@@ -54,2 +41,148 @@
     }
 }
+
+sub lookup_release {
+    my ($discid) = @_;
+    my $ua = LWP::UserAgent->new;
+
+    #my $uri = URI->new('http://musicbrainz.org/ws/1/release/');
+    #$uri->query_form(type => 'xml', discid => $discid);
+    my $uri = URI->new("http://musicbrainz.org/ws/2/discid/$discid");
+    $uri->query_form(inc => 'artists+labels+recordings+release-groups+artist-credits');
+
+    my $res = $ua->get($uri);
+    # pause for a second, so we don't run afoul of the MusicBrainz API TOS
+    sleep 1;
+
+    warn $res->status_line, "\n" if $res->code != 200;
+    return if $res->code >= 400;
+    #TODO: if we get a 5xx error, retry?
+
+    return $res->decoded_content;
+}
+
+sub get_musicbrainz_info {
+    my ($discid) = @_;
+    my %info;
+
+    $info{MBZ_DISCID} = $discid;
+
+    my $xpath = XML::XPath->new();
+    my $xml = lookup_release($discid) || return;
+    
+    # just dump the XML, if requested
+    if ($GET_XML) {
+        print $xml;
+        exit;
+    }
+
+    $xpath->set_xml($xml);
+
+    # get the release; if there is more than one, take the first one
+    # TODO: configurable release selection criteria
+    my $release_count = $xpath->findvalue('count(//release)');
+    my ($release) = $xpath->findnodes('//release[1]');
+    $info{RELEASE_MBID} = $xpath->findvalue('@id', $release);
+    $info{ALBUM}        = $xpath->findvalue('title', $release);
+    $info{ARTIST}       = $xpath->findvalue('artist-credit/name-credit/artist/name', $release);
+
+    # TODO: get release date
+
+    my $ua = LWP::UserAgent->new;
+    my $tracknum = 1;
+    for my $track_node ($xpath->findnodes('.//track-list/track', $release)) {
+        my $prefix = sprintf('TRACK%02d', $tracknum);
+        #$info{"$prefix.MB_TRACKID"} = $xpath->findvalue('@id', $track_node);
+        my $recording_mbid = $info{"$prefix.RECORDING_MBID"} = $xpath->findvalue('recording/@id', $track_node);
+        $info{"$prefix.TITLE"}          = $xpath->findvalue('recording/title', $track_node);
+        $info{"$prefix.ARTIST"}         = $xpath->findvalue('recording/artist-credit/name-credit/artist/name', $track_node) || $info{ARTIST};
+        #my $uri = URI->new("http://musicbrainz.org/ws/2/recording/$recording_mbid");
+        #$uri->query_form(inc => 'artists');
+        #my $res = $ua->get($uri);
+        #die $res->decoded_content;
+
+        #TODO: get track relations (Covers, etc.)
+
+        $tracknum++;
+    }
+
+=begin
+
+    my $ws_artists = WebService::MusicBrainz->new_artist;
+    my $ws_releases = WebService::MusicBrainz->new_release;
+    my $ws_tracks = WebService::MusicBrainz->new_track;
+
+    # search on the discid
+    my $response = $ws_releases->search({ DISCID => $discid });
+
+    # save this object, since WS::MBZ deletes it when you fetch it
+    # TODO: bug report to WS::MBZ?
+    my $release = $response->release;
+
+    # return undef if there is no matching release for this DiscID
+    return unless defined $release;
+
+    # search again, using the MBID of the first release found
+    # TODO: deal with multiple releases found?
+    # include tracks and artist info
+    $response = $ws_releases->search({
+        MBID => $release->id, 
+        INC => 'discs tracks artist release-events counts',
+    });
+
+    # get the fully filled out Release object (that represents the disc)
+    $release = $response->release;
+
+    if (defined $release->artist) {
+        $info{ARTIST} = $release->artist->name;
+    }
+    if (defined $release->title) {
+        $info{ALBUM} = $release->title;
+    }
+
+    # this is ID3v2:TDRL = Release Date
+    # (for now we just take the first date)
+    my $release_date = eval { @{ $release->release_event_list->events }[0]->date };
+    $release_date = '' if $@;
+
+    $info{DATE} = $release_date;
+
+    # get full info on each of the tracks
+    my @tracks;
+    my $track_num = 1;
+    for my $track_id (map { $_->id } @{ $release->track_list->tracks }) {
+        my $response = $ws_tracks->search({ 
+            MBID => $track_id,
+            INC => 'artist track-rels',
+        });
+        my $track = $response->track;
+        my $prefix = sprintf('TRACK%02d', $track_num);
+        $info{"$prefix.TITLE"} = $track->title;
+        #if (defined $track->artist && $track->artist->name ne $release->artist->name) {
+            $info{"$prefix.ARTIST"} = $track->artist->name;
+            $info{"$prefix.DATE"} = $release_date;
+        #}
+        push @tracks, $track;
+
+
+        if (defined $track->relation_list) {
+            for my $relation (@{ $track->relation_list->relations }) {
+                #warn $relation->type, $relation->target;
+                my $response = $ws_tracks->search({
+                    MBID => $relation->target,
+                    INC => 'artist releases',
+                });
+                my $track = $response->track;
+                $info{"$prefix.ORIGINAL_ARTIST"} = $track->artist->name;
+                $info{"$prefix.ORIGINAL_ALBUM"} = 
+                    ( (@{ $track->release_list->releases })[0]->title );
+            }
+        }
+
+        $track_num++;
+    }
+
+=cut
+
+    return \%info;
+}
Index: trunk/mbz2id3
===================================================================
--- trunk/mbz2id3	(revision 48)
+++ trunk/mbz2id3	(revision 1)
@@ -200,5 +200,5 @@
 my $flac_file = shift;
 
-my $discid = `metaflac --show-tag MUSICBRAINZ_DISCID $flac_file | cut -d = -f 2`;
+my $discid = `metaflac --show-tag MBZ_DISCID $flac_file | cut -d = -f 2`;
 chomp $discid;
 
Index: trunk/mkmp3iso
===================================================================
--- trunk/mkmp3iso	(revision 48)
+++ 	(revision )
@@ -1,29 +1,0 @@
-#!/usr/bin/perl -w
-use strict;
-
-use Getopt::Long;
-use YAML;
-use File::Basename;
-
-GetOptions(
-    'o=s' => \my $ISO_FILE,
-    'n'   => \my $DRY_RUN,
-);
-
-die "Usage: $0 -o <iso_file> dir [dir...]\n" unless $ISO_FILE;
-
-my @DIRS = @ARGV;
-
-#my %graft_points = map { my $dir = $_; s/^.*?\.//; "/$_" => $dir } @DIRS;
-#my %graft_points = map { "/" . basename($_) => $_ } @DIRS;
-my %graft_points = map { my $dir = $_; $_ = basename($_); s{\.}{/}g; "/$_" => $dir } @DIRS;
-
-my @graft_points = map { "$_=$graft_points{$_}" } sort keys %graft_points;
-
-print Dump(\@graft_points);
-
-exit if $DRY_RUN;
-
-system qw{mkisofs -r -graft-points -o}, $ISO_FILE, @graft_points;
-
-# then use cdrecord -v $ISO_FILE
Index: trunk/playflac
===================================================================
--- trunk/playflac	(revision 1)
+++ trunk/playflac	(revision 1)
@@ -0,0 +1,141 @@
+#!/usr/bin/perl -w
+use strict;
+
+use IPC::Open2;
+use IO::Prompt;
+use Term::ReadKey;
+
+my $FLAC_FILE = shift;
+my $TRACK = shift || 1;
+
+$|++;
+
+# get the track start positions from the embedded cuesheet
+open my $CUESHEET, "metaflac --export-cuesheet-to - $FLAC_FILE |";
+
+my @position_for_track;
+my $i = 1;
+while (<$CUESHEET>) {
+    if (my ($m,$s,$f) = /INDEX 01 (\d\d):(\d\d):(\d\d)/) {
+	$position_for_track[$i] = ($m * 60) + $s + ($f / 75);
+	$i++;
+    }
+}
+
+close $CUESHEET;
+
+
+my $track_count = scalar @position_for_track - 1;
+print "$track_count tracks\n";
+
+die "There is no track $TRACK" if $TRACK < 1 or $TRACK > $track_count;
+
+    
+# launch the flac123 player as a daemon
+my $flac123_pid = open2(my $PLAYER_OUT, my $PLAYER_IN, qw{flac123 -R});
+
+print "flac123 PID = $flac123_pid\n";
+
+my $current_track = $TRACK;
+my $next_track_start = $position_for_track[$current_track + 1];
+
+print "Track $current_track\n";
+
+my %commands = (
+    z => sub {
+        return if $current_track <= 1;
+        $current_track--;
+        $next_track_start = $position_for_track[$current_track + 1];
+        my $position = $position_for_track[$current_track];
+        print $PLAYER_IN "JUMP $position\n";
+        print "Track $current_track\n";
+    },
+    x => sub {},
+    c => sub {},
+    v => sub {},
+    b => sub {
+        return if $current_track >= $track_count;
+        $current_track++;
+        $next_track_start = $position_for_track[$current_track + 1];
+        my $position = $position_for_track[$current_track];
+        print $PLAYER_IN "JUMP $position\n";
+        print "Track $current_track\n";
+    },
+    q => sub {
+        print $PLAYER_IN "QUIT\n";
+        ReadMode 'restore';
+        exit;
+    },
+);
+
+
+print $PLAYER_IN "LOAD $FLAC_FILE\n";
+print $PLAYER_IN "JUMP $position_for_track[$TRACK]\n";
+
+ReadMode 4;
+
+while (<$PLAYER_OUT>) {
+    # do a non-blocking read to see if the user pressed a key
+    my $cmd = ReadKey -1;
+    if (defined $cmd && exists $commands{$cmd}) {
+        $commands{$cmd}->();
+    }
+
+    if (/\@F (\d+) (\d+) (\d+\.\d\d) (\d+\.\d\d)/) {
+        my ($frame, $frames_left, $time, $time_left) = ($1, $2, $3, $4);
+        if (defined $next_track_start && $time >= $next_track_start) {
+            $current_track++;
+            $next_track_start = $position_for_track[$current_track + 1];
+            print "Track $current_track\n";
+        }
+
+=begin
+
+        for (my $track_number = $track_count; $track_number > 0; $track_number--) {
+            #warn $track_number;
+            if ($position_for_track[$track_number] <= $time) {
+                $current_track = $track_number;
+                #print "Track $current_track\n";
+                last;
+            }
+        }        
+
+=cut
+
+    }
+}
+    
+waitpid $flac123_pid, 0;
+
+ReadMode 'restore';
+
+=begin flac123 -R outputs
+
+@R FLAC123
+flac123 tagline. Output at startup.
+
+@I ID3:<a><b><c><d><e><f>
+Prints out the metadata information after loading the flac file.
+a = title (30 chars)
+b = artist (30 chars)
+c = album (30 chars)
+d = year (4 chars)
+e = comment (30 chars)
+f = genre (30 chars)
+
+@I filename
+Prints out the filename of the flac file, minus the extension. Happens after
+a flac file has been loaded and there is no metadata available.
+
+@F <current-frame> <frames-remaining> <current-time> <time-remaining>
+Frame decoding status updates (once per frame).
+Current-frame and frames-remaining are integers; current-time and
+time-remaining floating point numbers with two decimal places.
+
+@P {0, 1, 2}
+Stop/pause status.
+0 - playing has stopped. When 'STOP' is entered, or the flac file is finished.
+1 - Playing is paused. Enter 'PAUSE' or 'P' to continue.
+2 - Playing has begun again.
+
+=cut
Index: trunk/renametag
===================================================================
--- trunk/renametag	(revision 48)
+++ 	(revision )
@@ -1,11 +1,0 @@
-#!/bin/bash
-
-OLD_TAG=$1
-NEW_TAG=$2
-FLAC=$3
-
-# to change the name of a FLAC tag
-# e.g., from MBZ_DISCID to MUSICBRAINZ_DISCID
-metaflac --show-tag "$OLD_TAG" "$FLAC" \
-    | sed "s/^$OLD_TAG=/$NEW_TAG=/" \
-    | metaflac --remove-tag "$OLD_TAG" --import-tags-from - "$FLAC"
Index: trunk/setbarcode
===================================================================
--- trunk/setbarcode	(revision 48)
+++ 	(revision )
@@ -1,17 +1,0 @@
-#!/bin/bash
-
-FLAC=$1
-
-echo "$FLAC"
-
-if [ -z $(metaflac --show-tag BARCODE "$FLAC") ]; then
-    read BARCODE
-    if [ ! -z $BARCODE ]; then
-	echo Set BARCODE to $BARCODE
-	metaflac --set-tag "BARCODE=$BARCODE" "$FLAC"
-    else
-	echo No barcode enered, skipping
-    fi
-else
-    echo Barcode already set, skipping
-fi
Index: trunk/submiturl
===================================================================
--- trunk/submiturl	(revision 48)
+++ 	(revision )
@@ -1,6 +1,0 @@
-#!/bin/bash
-# see also https://github.com/metabrainz/libdiscid/blob/master/src/disc.c#L358
-
-FLACFILE=$1
-
-metaflac --export-cuesheet-to - $FLACFILE | ./cuesheet2tocdata
Index: trunk/umountsort
===================================================================
--- trunk/umountsort	(revision 48)
+++ 	(revision )
@@ -1,7 +1,0 @@
-#!/bin/bash
-
-MOUNT_POINT=$1
-DEVICE=$(mount | grep "$MOUNT_POINT"| cut -d' ' -f1)
-
-umount "$DEVICE"
-fatsort "$DEVICE"
Index: trunk/use-cases
===================================================================
--- trunk/use-cases	(revision 48)
+++ trunk/use-cases	(revision 1)
@@ -19,6 +19,2 @@
 
 re-encode a mix as an archived flac for quick burning later
-
-submit a CD TOC to MusicBrainz
-    => submiturl
-    => metaflac --extract-cuesheet-to - | cuesheet2tocdata
