#! /usr/bin/env perl # $Id$ # $URL$ =head1 NAME B - rip a CD for B =head1 SYNOPSIS B [B<-d> I] [B<-p> I [B<-t> I] =cut use strict; use warnings; use Env qw( CDDEV ); 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 mkcue { my $device = shift; my $trackcount = shift; 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; } } 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 tags_file { my $fn = shift; my $trackcount = shift; my $various = shift; my $artist = shift; my $album = shift; my $release_dates = shift; my $fh; my $i; my $track; my $name; 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($release_dates) and %$release_dates) { while (my ($country, $date) = each(%$release_dates)) { print($fh "DATE[$country]=$date\n"); } } else { print($fh "DATE=\n"); } 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"); } 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 $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); } else { $mb = new MusicBrainz::Client::Simple; } @results = $mb->lookup_cd; if (not $mb->success) { die($mb->get_error); } for $album (@results) { $i++; if ($various = $album->has_various_artists) { if (not $seen_various) { $seen_various = 1; tags_file('candidate-tags-0v', $trackcount, 1); } } 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, @output); die; } sub make_post_processor { my $command = shift; defined($command) or return; sysopen(F, 'post-processor', O_CREAT | O_WRONLY, 0555) or die("sysopen(post-processor, O_CREAT | O_WRONLY, 0555): $!"); print(F $command, ' "$@"', "\n"); close(F) or die("close(post-processor, O_CREAT | O_WRONLY, 0555): $!"); } MAIN: { my $no_mb; my $post_processor; 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(); $help and pod2usage(-exitstatus=>0, -verbose=>1); # File::Temp::tempdir calls die on error. $tempdir = File::Temp::tempdir('flac-archive.XXXXXXXXXX'); chdir($tempdir) or die("chdir($tempdir): $!"); make_post_processor($post_processor); $trackcount = mkcue($CDDEV, $trackcount); tags($CDDEV, $trackcount, $no_mb); rip($CDDEV, $trackcount, $single_file); } __END__ =head1 DESCRIPTION B creates a temporary directory for storage of its intermediate files, uses MusicBrainz to create the "cue" file and candidate tags files, and runs C to rip the CD to the "wav" file. In order for this CD to be processed by B, you must create a "tags" file. This is usually done by renaming one of the candidate-tags files and deleting the others. Don't forget to fill in the DATE tag in the selected candidate before renaming it. If B could not find any tag information from MusicBrainz, you'll have to fill out the candidate-tags-0 template. =head1 OPTIONS =over 4 =item B<-d> [B<--device>] I Use I as the CD-ROM device, instead of the default "/dev/cdrom" or the environment variable CDDEV. =item B<-p> [B<--post-processor>] I Create a "post-processor" file in the temporary directory containing the line 'I "$@"'. See B's man page for information about this hook. =item B<-t> [B<--tracks>] I Archive only the first I tracks. This is handy for ignoring data tracks. =back =head1 ENVIRONMENT =over 4 =item CDDEV B uses this to rip audio and save the cuesheet for a CD. MusicBrainz::Client can usually figure this out automatically. =back =head1 AUTHORS Written by Eric Gillespie . flac-archive is free software; you may redistribute it and/or modify it under the same terms as Perl itself. =cut # Local variables: # cperl-indent-level: 4 # perl-indent-level: 4 # indent-tabs-mode: nil # End: # vi: set tabstop=4 expandtab: