package Tracks; use strict; use warnings; 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-'; } # module return 1;