#!/usr/bin/perl # ripcd, by Anthony DiSante # http://nodivisions.com/contact/ # Note: cda uses the files in /usr/lib/X11/xmcd/config/* to # know which CD devices are present. Run the script located # at /usr/lib/X11/xmcd/config/config.sh to set up your devices. my $version = 20050527; # 20050527: # # Fixed bug that was causing cda's output to appear multiple # times in the album-info files. Also we now write the album # info more sanely (and more easily-parseable) to the files. # # 20041008: # # Made a little more user-friendly. Also be more picky when # parsing cda's output, as they now throw some random junk # into there about security and suid on cda, etc. # # 20040917: # # Restrict length of the $ant_discid to 256 chars since it # has to become a filename. (DMB's Under The Table And Dreaming) use strict; # User-definable variables: set these to whatever you want. my $disc_info_dir = '/home/falling/adata/programs/cd_info'; my $music_dir = '/music/cds'; my $text_editor = 'a'; my $file_manager = 'rox'; #my @cddrives = ('/dev/cdroms/cdrom0','/dev/cdroms/cdrom1'); my @cddrives = ('/dev/dvd1','/dev/dvdrw'); my ($drive, $cmd, @output, $choice, $discinfo_file, $ant_discid, %songtitles, $genre, $band, $album, $length, $year, $tracknum_noleadingzeros, $tracknum, $trackname, $wavfile, $mp3file, $band_noquotes, $album_noquotes, $kidpid, $start_at, $end_at); my $make_mp3s = 1; my $drive_num = 0; my $debug = 0; for(@ARGV) { $make_mp3s = 0 if /--no-mp3s/; $debug = 1 if /--debug/; if(/^(\d+)-(\d+)$/) { $start_at = $1; $end_at = $2; } elsif(/^(\d+)-$/) { $start_at = $1; } elsif(/^-(\d+)$/) { $end_at = $1; } } print " ripcd v$version - Anthony DiSante - http://nodivisions.com/software/ripcd/ Extracts the audio from a CD into WAV files, then makes MP3 files from them. Uses cda (part of xmcd) to access CDDB info for each disc, and uses that info to name the WAV and MP3 files. Uses cdparanoia to rip the audio to WAVs, and uses lame (with --r3mix (super quality) setting) to make MP3s from the WAVs. ID3 tags in the MP3 files are filled with the band, album, genre, year, and track titles obtained from CDDB. This information is stored locally once it is downloaded from CDDB, and you will be given the option to edit this info before it's used to create your WAVs and MP3s. Usage: ripcd [-] where i-j indicates the range of tracks to rip, and either one may be omitted to indicate the start/end of the disc. If both are omitted (and the dash), the whole disc is ripped. You may also pass --no-mp3s. This program should be invoked from a terminal that has a scrollback buffer of at least 2000 lines (xterm -sl 2000, for example) so that you can scroll up and check the cdparanoia progress bars when it's all done, to make sure that each track got extracted without any problems. See the cdparanoia FAQ at xiph.org/paranoia/faq.html, but basically, you don't need to worry about any symbols in the progress bar except for Vs, or lots of !s. If you have those, try cleaning the disc and re-ripping the problem track(s). Requires cda (xmcd), cdparanoia, and lame; cda might need to be suid root. "; exit if $ARGV[0] =~ /^--?h(elp)?$/i; sub choose_drive() { my $current_drive = 0; my $num_drives = $#cddrives + 1; foreach my $dev (@cddrives) { die "ERROR: the device \"$dev\" does not exist.\n" unless -e $dev; $current_drive++; print "\n\nTrying $dev (drive $current_drive of $num_drives)...\n"; $cmd = "cda -dev $dev -offline on"; print "$cmd\n"; `$cmd 1>/dev/null 2>/dev/null`; $cmd = "cda -dev $dev -batch -offline toc"; print "$cmd (may take a minute or two)\n"; @output = (); my @rawoutput = `$cmd`; my $output_is_good = 0; for(@rawoutput) { if(/Accessing CDDB/) { $output_is_good = 1; } if($output_is_good) { push @output, $_; } } if($output[0] =~ /Accessing CDDB\.\.\./) { print "Audio CD found in $dev:\n"; $year = 'Year: '; push @output, $year; print @output; print "\nExtract this disc, which is in $dev? [enter=yes, n=no] "; $choice = ; print "Stopping cda for $dev...\n"; $cmd = "cda -dev $dev off"; print "$cmd\n"; `$cmd`; if($choice =~ /n/i) { if($current_drive < $num_drives) { print "Try the next drive? [enter=yes, q=quit] "; $choice = ; if($choice =~ /q/i) { die "Quitting.\n"; } else { next; } } else { die "No more drives to check.\n"; } } else { $drive = $dev; return; } } else { print "No audio CD found in $dev.\n"; print "Stopping cda for $dev...\n"; $cmd = "cda -dev $dev off"; print "$cmd\n"; `$cmd`; if($current_drive < $num_drives) { print "Try the next drive? [enter=yes, q=quit] "; $choice = ; if($choice =~ /q/i) { die "Quitting.\n"; } else { next; } } else { die "No more drives to check.\n"; } } } } sub download_disc_info() { print "Downloading disc info from internet CDDB...\n"; $cmd = "cda -dev $drive on"; print "$cmd\n"; `$cmd 1>/dev/null 2>/dev/null`; $cmd = "cda -dev $drive -batch toc"; print "$cmd (may take a minute or two)\n"; @output = (); my @rawoutput = `$cmd`; my $output_is_good = 0; for(@rawoutput) { if(/Accessing CDDB/) { $output_is_good = 1; } if($output_is_good) { push @output, $_; } } $year = `cda -dev $drive -batch extinfo|grep Year:`; push @output, $year; print "Stopping cda for $drive...\n"; $cmd = "cda -dev $drive off"; print "$cmd\n"; `$cmd`; } sub get_ant_discid() { # my custom disc ID will be a string of all the tracknums and durations. for(@output) { if(/(\d+)\s+(\d+):(\d+)\s+/) { $ant_discid .= "$1-$2.$3-"; } } chop $ant_discid; # make sure it's <256 chars: $ant_discid =~ s/^(.{250}).*/$1/; $ant_discid .= '.txt'; } sub save_disc_info() { my $info = parse_disc_info(\@output); my $file = "$disc_info_dir/$ant_discid"; open(OUT,">$file") or die "$0: couldn't open $file for writing: $!\n"; flock OUT, 2; seek OUT, 0, 0; print OUT $info; close OUT or die "$0: couldn't close $file: $!\n"; } sub load_disc_info() { my $file = "$disc_info_dir/$ant_discid"; open(IN,"<$file") or die "$0: couldn't open $file for reading: $!\n"; flock IN, 1; while() { if(/^Artist:\s*(.+)/) { $band = $1; } elsif(/^Album:\s*(.+)/) { $album = $1; } elsif(/^(?:Length|Total Time):\s*(\d+:\d+)/) { $length = $1; } elsif(/^Year:\s*(\d+)/) { $year = $1; } elsif(/^Genre:\s*(.+)/) { $genre = $1; # We used to just dump the CD info into the text file in the # format that cda displayed it. In this format it looks like: # # Genre: foo # Artist / Album # # But in our new format, artist and album come before genre in # the file. So if we get to here (setting the genre) and the # artist and album still aren't set, then we know we're reading # a file in the old format, so parse it appropriately. unless($band && $album) { my $nextline = ; ($band,$album) = ($nextline =~ /^(.+) \/ (.+)$/); strip_leading_and_trailing_space($genre,$band,$album); } } elsif(/(\d+)\s+(\d+):(\d+)\s+(.+)/) { $songtitles{$1} = $4; chomp $songtitles{$1}; strip_leading_and_trailing_space($songtitles{$1}); } } close IN or die "$0: couldn't close $file: $!\n"; strip_leading_and_trailing_space($band,$album,$genre); } sub parse_disc_info { my $array_ref = shift; my $i = 0; my @tracks = (); for(@$array_ref) { if(/^Genre:\s+(.+)/) { $genre = $1; ($band,$album) = ($$array_ref[$i+1] =~ /(.+) \/ (.+)/); strip_leading_and_trailing_space($genre,$band,$album); } elsif(/^Year: (\d+)/) { $year = $1; } elsif(/^Total Time: (\d+:\d+)/) { $length = $1; } elsif(/(\d+)\s+(\d+):(\d+)\s+(.+)/) { $songtitles{$1} = $4; chomp $songtitles{$1}; strip_leading_and_trailing_space($songtitles{$1},$_); push @tracks, $_; } $i++; } return "Artist: $band\nAlbum: $album\nLength: $length\nYear: $year\nGenre: $genre\n\n" . join("\n",@tracks) . "\n"; } sub print_disc_info() { print "\nArtist: $band" . "\nAlbum: $album" . "\nLength: $length" . "\nYear: $year" . "\nGenre: $genre" . "\n\n"; foreach my $key ( sort (keys(%songtitles))) { print "$key $songtitles{$key}\n"; } print "\n"; } sub strip_leading_and_trailing_space { for(@_) { s/^\s+//; s/\s+$//; } } choose_drive(); get_ant_discid(); if(-e "$disc_info_dir/$ant_discid") { print "\n\nWell, I already have information on file for this disc:\n"; load_disc_info(); print_disc_info(); print "Press enter to use this info, or type N to get new info from internet CDDB: "; $choice = ; if($choice =~ /n/i) { download_disc_info(); save_disc_info(); } } else { download_disc_info(); save_disc_info(); } print "\n\nThe album info resides in the file: \n$disc_info_dir/\n$ant_discid." . "\nPress Enter now to display that file, and you can edit it if you'd like." . "\n(But only edit the band/album/track names, the year, and the genre; and" . "\nthe genre must be one of the genres listed in the output of the command" . "\nlame --genre-list.)"; ; my $kidpid; if($kidpid = fork) { # parent. } else { `$text_editor "$disc_info_dir/$ant_discid" &`; exit; } print "Now press Enter to continue, using the contents of that file.\nI'll pop up your file manager, too."; ; load_disc_info(); sleep 1; $cmd = "mkdir -p \"$music_dir/$band/$album\""; print "\n\nCreating output directory...\n$cmd\n"; `$cmd`; if($kidpid = fork) { # parent. } else { `$file_manager \"$music_dir/$band/$album\"`; exit; } # now our disc info is correct. print "\n\nUsing this disc info:\n"; print_disc_info(); print "\nPress Enter to begin ripping and encoding."; ; my $comment = 'lame --r3mix'; my $quote; if($^O =~ /MSWin32/) { $quote = "\""; } else { $quote = "\""; } # Put quotes around these since they'll be args to lame: $band_noquotes = $band; $album_noquotes = $album; $band = $quote . $band . $quote; $album = $quote . $album . $quote; $genre = $quote . $genre . $quote; $year = $quote . $year . $quote; $comment = $quote . $comment . $quote; foreach my $key ( sort (keys(%songtitles))) { $tracknum_noleadingzeros = $key; $tracknum_noleadingzeros =~ s/^0+//; # If we were passed a limited range of tracks to rip, then skip to the first one # in that range, and/or skip past all the ones after the last one in that range. if( ($start_at =~ /\d/) && ($tracknum_noleadingzeros < $start_at) ) { next; } if( ($end_at =~ /\d/) && ($tracknum_noleadingzeros > $end_at) ) { last; } print '#' x 78; print "\nProcessing track $tracknum_noleadingzeros of " . (scalar keys %songtitles) . "...\n"; $cmd = "cdparanoia -d $drive -B ${tracknum_noleadingzeros}-${tracknum_noleadingzeros} \"$music_dir/$band_noquotes/$album_noquotes/\""; print "$cmd\n" if $debug; `$cmd`; $cmd = "mv \"$music_dir/$band_noquotes/$album_noquotes/track${key}.cdda.wav\" \"$music_dir/$band_noquotes/$album_noquotes/$band_noquotes - $key $songtitles{$key}.wav\""; print "$cmd\n" if $debug; `$cmd`; if($make_mp3s) { $tracknum = $quote . $key . $quote; $trackname = $quote . $songtitles{$key} . $quote; $wavfile = $quote . "$music_dir/$band_noquotes/$album_noquotes/$band_noquotes - $key $songtitles{$key}.wav" . $quote; $mp3file = $quote . "$music_dir/$band_noquotes/$album_noquotes/$band_noquotes - $key $songtitles{$key}.mp3" . $quote; $cmd = "lame --r3mix --nohist --ta $band --tl $album --tn $tracknum --tt $trackname --ty $year --tg $genre --tc $comment $wavfile $mp3file"; #print "xterm -e \"$cmd\" &\n" if $debug; #`xterm -e "$cmd"`; print "$cmd\n" if $debug; `$cmd`; } }