use Fcntl qw(O_CREAT O_WRONLY);
use File::Temp;
use Getopt::Long qw(:config gnu_getopt no_ignore_case);
+use List::Util qw(min);
use POSIX ':sys_wait_h';
use Pod::Usage;
+use MusicBrainz::Client;
+use MusicBrainz::Queries qw(
+ MBQ_GetCDTOC
+ MBE_TOCGetFirstTrack
+ MBE_TOCGetLastTrack
+ MBE_TOCGetTrackSectorOffset
+);
use MusicBrainz::Client::Simple;
-sub run_or_die {
- my $command = shift;
- my $status;
-
- $status = system($command);
+sub mkcue {
+ my $device = shift;
+ my $trackcount = shift;
- if (WIFEXITED($status)) {
- if (($status = WEXITSTATUS($status)) != 0) {
- die("$command exited with status $status");
+ my $mb = MusicBrainz::Client->new;
+ defined($device) and $mb->set_device($device);
+
+ $mb->query(MBQ_GetCDTOC) or die($mb->get_query_error);
+
+ open(my $fh, '>cue') or die("open('>cue'): $!");
+ print($fh "FILE \"dummy.wav\" WAVE\n");
+ print($fh " TRACK 01 AUDIO\n");
+ print($fh " INDEX 01 00:00:00\n");
+
+ my $first = $mb->get_result_data(MBE_TOCGetFirstTrack) + 1;
+ $trackcount = min($trackcount, $mb->get_result_data(MBE_TOCGetLastTrack));
+ # There is frequently (always?) an offset of 150 sectors, so
+ # we'll subtract this offset from each track offset.
+ my $something = $mb->get_result_data1(MBE_TOCGetTrackSectorOffset, 2);
+
+ for my $track ($first .. $trackcount) {
+ my $off = $mb->get_result_data1(MBE_TOCGetTrackSectorOffset, $track+1);
+ $off -= $something;
+
+ my ($minutes,$seconds)=(0,0);
+ my $sectors = $off % 75;
+ if ($off >= 75) {
+ $seconds = $off / 75;
+ if ($seconds >= 60) {
+ $minutes = $seconds / 60;
+ $seconds = $seconds % 60;
+ }
}
- } elsif (WIFSIGNALED($status)) {
- die("$command killed with signal ", WTERMSIG($status));
- } elsif (WIFSTOPPED($status)) {
- die("$command stopped with signal ", WSTOPSIG($status));
- } else {
- die("Major horkage on system($command): \$? = $? \$! = $!");
+
+ printf($fh " TRACK %02d AUDIO\n", $track);
+ printf($fh " INDEX 01 %02d:%02d:%02d\n",
+ $minutes, $seconds, $sectors);
}
+
+ close($fh) or die("close(>cue): $!");
+
+ return $trackcount;
}
-sub mkcue {
- my $device = shift;
+sub tags_file {
+ my $fn = shift;
my $trackcount = shift;
- my @command;
-
- push(@command, 'mkcue');
+ my $various = shift;
+ my $artist = shift;
+ my $album = shift;
+ my $release_dates = shift;
+ my $fh;
+ my $i;
+ my $track;
+ my $name;
- if (defined($trackcount)) {
- push(@command, "-t $trackcount");
- }
+ open($fh, '>', $fn) or die("open('>$fn'): $!");
+ print($fh 'ARTIST=', (defined($artist) and $artist or ''), "\n");
+ print($fh 'ALBUM=', (defined($album) and $album or ''), "\n");
- if (defined($device)) {
- push(@command, $device);
+ if (defined($release_dates) and %$release_dates) {
+ while (my ($country, $date) = each(%$release_dates)) {
+ print($fh "DATE[$country]=$date\n");
+ }
+ } else {
+ print($fh "DATE=\n");
}
- push(@command, '> cue');
- run_or_die(join(' ', @command));
-
- if (not defined($trackcount)) {
- open(F, 'cue') or die("open(cue): $!");
- $trackcount = grep(/TRACK.*AUDIO/, <F>);
- close(F);
+ for $i (1 .. $trackcount) {
+ $various and print($fh "ARTIST[$i]=\n");
+ if ($track = shift(@_)) {
+ $name = $track->get_name;
+ } else {
+ $name = '';
+ }
+ print($fh "TITLE[$i]=$name\n");
}
- return $trackcount;
+ close($fh) or die("close(>$fn): $!");
}
sub tags {
my $device = shift;
my $trackcount = shift;
+ my $no_mb = shift;
my $mb;
my @results;
my $album;
my $i;
- my @tracks;
- my $name;
- my $track;
- my $j;
+ my $various;
+ my $seen_various;
+
+ tags_file('candidate-tags-0', $trackcount, 0);
+
+ defined($no_mb) and $no_mb and return;
if (defined($device)) {
$mb = new MusicBrainz::Client::Simple (device=>$device);
die($mb->get_error);
}
- open(F, '>candidate-tags-0') or die("open('>candidate-tags-0'): $!");
- print(F "$_=\n") for ('ARTIST', 'ALBUM', 'DATE');
- print(F "TITLE=\n") for 1 .. $trackcount;
- close(F) or die("close('>candidate-tags-0'): $!");
-
for $album (@results) {
$i++;
- open(F, ">candidate-tags-$i") or die("open(>candidate-tags-$i): $!");
- print(F 'ARTIST=', $album->get_artist->get_name, "\n");
- print(F 'ALBUM=', $album->get_name, "\n");
-
- # MusicBrainz doesn't have dates yet; these are usually wrong anyway.
- print(F "DATE=\n");
-
- @tracks = $album->get_tracks;
- for $j (1 .. $trackcount) {
- if ($track = shift(@tracks)) {
- $name = $track->get_name;
- } else {
- $name = '';
+ if ($various = $album->has_various_artists) {
+ if (not $seen_various) {
+ $seen_various = 1;
+ tags_file('candidate-tags-0v', $trackcount, 1);
}
- print(F "TITLE=$name\n");
}
- close(F) or die("close(>candidate-tags-$i): $!");
+ my %dates = $album->get_release_dates;
+ tags_file("candidate-tags-$i", $trackcount, $various,
+ $album->get_artist->get_name, $album->get_name,
+ \%dates, $album->get_tracks);
}
}
sub rip {
my $device = shift;
my $trackcount = shift;
+ my $single_file = shift;
+ my @output;
$device ||= '/dev/cdrom';
+ if ($single_file) {
+ @output = ("1-$trackcount", 'wav');
+ } else {
+ @output = ('-B');
+ }
- exec('cdparanoia', '-d', $device, "1-$trackcount", 'wav');
- # exec prints its own error message so just
+ exec('cdparanoia', '-d', $device, @output);
die;
}
}
MAIN: {
+ my $no_mb;
my $post_processor;
- my $trackcount;
+ my $single_file;
+ my $trackcount = 99;
my $help;
my $tempdir;
GetOptions(
'device|d=s' => \$CDDEV,
+ 'no-musicbrainz|m' => \$no_mb,
'post-processor|p=s', \$post_processor,
+ 'single-file|s' => \$single_file,
'tracks|t=i' => \$trackcount,
'help|h|?' => \$help,
) or pod2usage();
make_post_processor($post_processor);
$trackcount = mkcue($CDDEV, $trackcount);
- tags($CDDEV, $trackcount);
- rip($CDDEV, $trackcount);
+ tags($CDDEV, $trackcount, $no_mb);
+ rip($CDDEV, $trackcount, $single_file);
}
\f
=head1 DESCRIPTION
B<fa-rip> creates a temporary directory for storage of its
-intermediate files, runs C<mkcue(1)> to create the "cue" file, uses
-MusicBrainz to generate candidate tags files, and runs
-C<cdparanoia(1)> to rip the CD to the "wav" file.
+intermediate files, uses MusicBrainz to create the "cue" file and
+candidate tags files, and runs C<cdparanoia(1)> to rip the CD to the
+"wav" file.
In order for this CD to be processed by B<fa-flacd>, you must create a
"tags" file. This is usually done by renaming one of the
=item CDDEV
-B<fa-rip> uses this to rip audio and save the cuesheet for a CD. It
-makes some effort to check some common device names for FreeBSD,
-Linux, and NetBSD by default.
+B<fa-rip> uses this to rip audio and save the cuesheet for a CD.
+MusicBrainz::Client can usually figure this out automatically.
=back