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;
