#!/usr/bin/perl #H# Modified to follow my file name convention and to NOT encode to mp3. #H# The modifications are marked with "#K#" # # choad # # a command-line front end to cdparanoia and lame # # copyright 2001 pete gamache gamache@ftso.org # , # all rights reversed (K) # # This program is free software; you can redistribute it # and/or modify it under the same terms as Perl itself. # # 0.72: format string parsing improvements and code cleanup # by Darrell Bishop # # 0.721: fixed typo in clean_up_string() # # 0.8: now follows strict syntax; tweaked around with query_CD and # get_CDDB_info(); fixed capitalization bug in parse_format_string() # added -submit option # 0.81: avoided lame --tg problems # 0.82: added "--" to cdparanoia command line in rip_track_with_name() # fixed exactly-one-CDDB-match ID3 bug # 0.822: added track numbering support for the kids require 5; use strict; ### constants ####################################################### my $true = 22; my $false = 0; my $choad = $0; $choad =~ s/.*\/([^\/]+)$/$1/; my $choad_version = "0.822"; my $HOME = $ENV{HOME}; my $USER = $ENV{USER}; my $HOST = `hostname`; $HOST =~ s/\n//; my $lameflags_default = "-b 128"; #K# Added these flags for additional retries if there is a read error my $cdpflags_default = "-d /dev/cdrom --never-skip=200 -X"; #K# Follow MY naming system my $trackname_default = '%A - %L - %NN - %T.wav'; ### defaults ######################################################## my $cdparanoia = `which cdparanoia`; $cdparanoia =~ s/\n//; my $cdpflags = $cdpflags_default; my $lame = `which lame`; $lame =~ s/\n//; my $lameflags = $lameflags_default; #my $cddb_host = "freedb.freedb.org"; my $cddb_host = "gnudb.gnudb.org"; my $cddb_port = 8880; my $auto = $false; my $nocddb = $false; my $we_have_names = $false; # if we have artist/title/track list my $maketracks = $false; my $encode = $true; my $submit = $false; # new to 0.8 my $trackname = ""; my $strip_the = $true; my $from = 1; my $artist = "artist-$$"; my $album = "album-$$"; my $genre = ""; my @tracks = (); my @onlyv = (); my $how_many_tracks = 0; ### main routine #################################################### parse_config_file ("/etc/choadrc"); parse_config_file ("$HOME/.choadrc"); parse_command_line (@ARGV); my $MSF_info = query_CD(); get_CDDB_info() unless $nocddb == $true; if ($we_have_names == $true) { print make_tracks_list(); submit_CD() unless ($submit == $false); } else { print "$how_many_tracks tracks on disc.\n"; } $trackname = $trackname_default unless ($trackname ne ""); # now do the encoding if ($encode == $true) { if (scalar @onlyv > 0) { while (<@onlyv>) { my $i = shift @onlyv; my $n = shift @onlyv; if ($n eq "auto") {$n = $trackname;} make_appropriate_dirs ($n); rip_track_with_name ($i, parse_format_string($n, $i)); } } else { #batch mode my $curdir = make_appropriate_dirs ($trackname); if ($maketracks == $true) { my $tracksfile = ">" . $curdir . "tracks"; open TRACKS, $tracksfile or die $!; print TRACKS "# autogenerated by $choad $choad_version\n"; print TRACKS make_tracks_list(); close TRACKS; } for (my $i=$from; $i <= $how_many_tracks; $i++) { rip_track_with_name ($i, parse_format_string($trackname, $i)); } } } # and that's all ####################################################################### # # do-shit routines # ####################################################################### sub get_CDDB_info { use CDDB; $submit = $false; # don't resubmit data my $cddb = new CDDB( Host => $cddb_host, Port => $cddb_port, Login => "$USER\@$HOST", ) or die $!; # print $MSF_info; my @toc = split /\//, $MSF_info; my ($cddb_id, $track_numbers, $track_lengths, $track_offsets, $total_seconds) = $cddb->calculate_id(@toc); my @discs = $cddb->get_discs($cddb_id, $track_offsets, $total_seconds); my $discchoice = 1; my $i=0; if (scalar @discs > 1) { if ($auto == $false) { foreach my $disc (@discs) { $i++; ($genre, my $cddb_id, my $title) = @{$disc}; print "$i $title\n"; } ASK: print "\nWhich disc? "; while ((my $c .= getc STDIN) ne "\n") { $discchoice = $c; } if ($discchoice > scalar @discs) { print "Too high; try again."; goto ASK; } } $we_have_names = $true; } elsif (scalar @discs == 0) { print STDERR "No CDDB entry for this disc (id=0x$cddb_id)\n"; $we_have_names = $false; } else { $we_have_names = $true; } # my ($genre_, $title); if (scalar @discs > 0) { ($genre, my $cddb_id, my $title) = @{$discs[$discchoice-1]}; $title=~/([^\/]*) \/ (.*)/; $artist = $1; $album = $2; my $disc_info = $cddb->get_disc_details($genre, $cddb_id); @tracks = @{$disc_info->{ttitles}}; } } sub submit_CD { # submits CD info to CDDB # use in conjunction with -tracks use CDDB; # use MailTools; my @toc = split /\t/, $MSF_info; my $cddb = new CDDB ( Host => $cddb_host, Port => $cddb_port, Login => "$USER\@$HOST", ); if (scalar @tracks != $how_many_tracks) { print STDERR "Track list has too many or too few songs. Not submitting.\n"; return; } my ($cddb_id, # used for further CDDB queries $track_numbers, # padded with 0's provided as a convenience $track_lengths, # length of each track, in MM:SS format $track_offsets, # absolute offsets (used for further CDDB queries) $total_seconds # total play time, in MM:SS format ) = $cddb->calculate_id(@toc); my $YN = "n"; ASK: print "Are you sure the above info is correct? (y/N) "; while ((my $c .= getc STDIN) ne "\n") { $YN = $c; }; if (uc $YN eq "N") { print "\nOkay, not submitting.\n"; } elsif (uc $YN ne "Y") { print "\n\"$YN\" not understood. Try again.\n"; goto ASK; } $cddb->submit_disc ( Genre => $genre, Id => $cddb_id, Artist => $artist, DiscTitle => $album, Offsets => $track_offsets, TrackTitles => \@tracks, From => "$USER\@$HOST" ); } sub make_appropriate_dirs { # takes a format string as input # returns directory name my $fmtstring = $_[0]; my $dir = parse_format_string ($fmtstring, 1); if (! ($dir=~/\//)) {return "";} #no dirs to create $dir =~s/(.*)\/[^\/]+$/$1/; #strip mp3 name from end my @dirs = split /\//, $dir; my $curdir = ""; foreach $dir (@dirs) { if (! $dir=~/^$/) { `mkdir "$curdir$dir"`; # if $dir's already there, no harm done } $curdir .= $dir . "/"; } return $curdir; } sub rip_track_with_name { my $tracknumber = $_[0]; my $mp3name = $_[1]; if ($mp3name eq "") {return}; my $lameoptions = $lameflags; if ($we_have_names == $true) { $lameoptions .= " --tt \"$tracks[$tracknumber-1]\" --ta \"$artist\" --tl \"$album\" --tn $tracknumber"; # why i left that "--tn $tracknumber" out in the first damn place, i dunno. } my $command = #K# Modified this to output directly to a .wav file instead of | lame "$cdparanoia $cdpflags -- $tracknumber \"$mp3name\""; #K# print STDERR "$command\n"; `$command`; } ####################################################################### # # file parsing routines # ####################################################################### sub parse_config_file { my $CHOADRC = $_[0]; my $i=0; open RC, $CHOADRC or return; while () { $i++; if (! (/^[\#;]/ || /^\s*$/)) { # ignore comments and blank lines /^\s*(\S*)\s*=?\s*(.*)\s*$/; # grab useful bits from line my @line = ("-$1", $2); parse_command_line (@line); } } } sub parse_tracks_file { my $tracksfile = $_[0]; open TRACKS, $tracksfile or die "Could not open $tracksfile: $!\n"; while () { my $j; $j++; if (! (/^[\#;]/ || /^\s*$/)) { # ignore comments and blank lines if (/^artist\s*(.*)\s*$/) { $artist = clean_up_string($1); } elsif (/^title\s*(.*)\s*$/) { $album = clean_up_string($1); } elsif (/^genre\s*(.*)\s*$/) { $genre = clean_up_string($1); } elsif (/^track\s*([0-9]+)\s(.*)\s*$/) { $tracks[$1-1] = $2; } else { print STDERR "\"$tracksfile\", line $j ignored:\n$_\n"; } } } close TRACKS; $how_many_tracks = scalar @tracks; $we_have_names = $true; } ####################################################################### # # command-line parsing routine # ####################################################################### sub parse_command_line { while (@_) { if ($_[0] eq "-cdp") { shift; $cdparanoia = shift; } elsif ($_[0] eq "-cdpflags") { shift; $cdpflags = shift; } elsif ($_[0] eq "-lame") { shift; $lame = shift; } elsif ($_[0] eq "-lameflags") { shift; $lameflags = shift; } elsif ($_[0] eq "-choadrc") { shift; parse_config_file (shift); } elsif ($_[0] eq "-server") { shift; $cddb_host = shift; } elsif ($_[0] eq "-port") { shift; $cddb_port = shift; } elsif ($_[0] eq "-auto") { shift; $auto = $true; } elsif ($_[0] eq "-nocddb") { shift; $nocddb = $true; } elsif ($_[0] eq "-tracks") { shift; $nocddb = $true; parse_tracks_file (shift); } elsif ($_[0] eq "-trackname") { shift; $trackname = shift; } elsif ($_[0] eq "-nomaketracks") { shift; $maketracks = $false; } elsif ($_[0] eq "-noencode") { shift; $encode = $false; } elsif ($_[0] eq "-submit") { shift; $submit = $true; } elsif ($_[0] eq "-the") { shift; $strip_the = $false; } elsif ($_[0] eq "-from") { shift; $from = shift; } elsif ($_[0] eq "-h") { print usage(); exit 0; } elsif ($_[0] eq "-V") { print version(); exit 0; } elsif ($_[0] eq "-longhelp") { print longhelp(); exit 0; } elsif ($_[0] eq "-only") { shift; while (@_) {@onlyv = (@onlyv, shift);} } else { print STDERR "Unrecognized option: $_[0]\n"; print STDERR usage(); exit 1; } $strip_the = $true; } } ####################################################################### # # string-twiddling routines # ####################################################################### sub parse_format_string { my $format_string = $_[0]; my $tracknumber = $_[1]; # only used for %t ###Darrell Bishop cleaned this part up a LOT -- thanks! # Expand format variables in-place. my $i = 0; while ($i < length($format_string)) { my $char = substr($format_string, $i, 1); my $width = 2; # at least '%' and a letter if ($char eq "%") { # We hit a variable; expand it # (forward look-ahead sanity check) my $type = substr($format_string, $i+1, 1) || ""; my $replacement_text; if (uc $type eq "A") { $replacement_text = ($type eq "a") ? lc $artist : $artist; } elsif (uc $type eq "L") { $replacement_text = ($type eq "l") ? lc $album : $album; } elsif (uc $type eq "T") { $replacement_text = scalar @tracks ? ($type eq "t")? lc $tracks[$tracknumber-1] : $tracks[$tracknumber-1] : sprintf("track %02d", $tracknumber); } elsif (uc $type eq "N") { my $tn_format_string = "%d"; if (uc substr($format_string, $i+2, 1) eq "N") { $tn_format_string = "%02d"; $width++; } $replacement_text = sprintf($tn_format_string, $tracknumber); } elsif (uc $type eq "P") { $replacement_text = $$; } else { # Wasn't a format variable after all... *sigh* $i++; next; } if ($i+$width < length $format_string and substr($format_string, $i+$width, 1) eq "_") { $replacement_text =~ s/\s/_/g; $width++; } if ($i+$width < length $format_string and substr($format_string, $i+$width, 1) eq "[") { my ($j, $trunc) = ($i+$width+1, ""); while ($j < length $format_string and substr($format_string, $j, 1) =~ /\d/) { $trunc .= substr($format_string, $j, 1); $j++; } if ($trunc =~ /^\d+$/o and $j < length $format_string and substr($format_string, $j, 1) eq "]") { $replacement_text = substr($replacement_text, 0, $trunc); $width += length($trunc) + 2; } } # error fixed here $replacement_text = clean_up_string($replacement_text); substr($format_string, $i, $width) = $replacement_text; $i += length $replacement_text; } else { $i++; } } return $format_string; } sub clean_up_string { # removes: trailing whitespace, unfinished parenthetical phrases # replaces colons, slashes with "-" my $string = $_[0]; $string=~s/\([^\)]*$//; $string=~s/\s*$//; $string=~s/[:\/\\]/-/g; return $string; } ####################################################################### # # string-building routines # ####################################################################### sub make_tracks_list { my $list = "artist $artist\ntitle $album\n"; $list .= ($genre eq "") ? "\n" : "genre $genre\n\n"; my $i=1; foreach my $track (@tracks) { $list .= sprintf "track %d %s\n", $i, $tracks[$i-1]; $i++; } return $list; } sub query_CD { # returns tab-delimited string list of: # # also sets $how_many_tracks my $toc = ""; my $command = "$cdparanoia $cdpflags -Q 2>/tmp/choad-$$"; `$command`; open QUERY, "/tmp/choad-$$" or die $!; while () { print $_; if (/\s*([0-9]+)\.\s+[0-9]+\s+\[[0-9]+:[0-9]+\.[0-9]+\]\s+[0-9]+\s+\[([0-9]+):([0-9]+)\.([0-9]+)\]/) { # that's a mouthful $toc .= "$1 $2 $3 $4/"; $how_many_tracks = $1; } elsif (/^TOTAL\s+[0-9]+\s+\[([0-9]+):([0-9]+)\.([0-9]+)\]/) { $toc .= "999 $1 $2 $3"; # lead-out track } } close QUERY; #print "made it past query\n"; `/bin/rm /tmp/choad-$$`; return $toc; } sub version { return sprintf < read as choadrc configuration file -cdp /path/to/cdparanoia specify path to cdparanoia [default=$cdparanoia] -cdpflags "flags" pass "flags" to cdparanoia [default="$cdpflags_default"] -lame /path/to/lame specify path to lame [default=$lame] -lameflags "flags" pass "flags" to lame [default="$lameflags_default"] -server use as CDDB server [default=$cddb_host] -port use as CDDB server port [default=$cddb_port] -nocddb do not attempt CDDB lookup -tracks use for artist/title/track list instead of CDDB lookup (implies -nocddb) -nomaketracks do not make "tracks" file when encoding CD -submit submit track list to CDDB (use with -tracks) -noencode do not encode CD -auto ask user no questions (Do The Right Thing) -trackname "string" use "string" as track name format string [default="$trackname_default"] -the do not strip leading "the" from artist name -from start encoding at track -h print help message -V print version -longhelp print long help message If "-only ..." is present, $choad will encode each track , using as the format string for the filename. If == "auto", then the default format string is used. If "-only ..." is not present, $choad will encode the entire CD. END ; } sub longhelp { my $usage = usage(); my $version_string = version(); return sprintf <" command-line option to specify other file(s) to process. Commands in this file correspond directly to command line options; lines in choadrc files of the form "option" or "option = value" are equivalent to command line options "-option" and "-option value", respectively. These commands are processed as if they were inserted onto the command line, either before the other options (in the case of /etc/choadrc and ~/.choadrc) or in the place where "-choadrc " occurs. Later options override earlier options on the command line. Blank lines are ok, and comment lines must start with ";" or "#". Format strings: You can customize the destination of the mp3 files (directory and filenames) if you like. $choad provides the following variables and options: \%A artist name with leading "the" removed (use "-the" command-line option to prevent this) \%L album name \%T track name \%N track number \%NN two-digit track number \%P $choad process ID (use for uniqueness) In \%A, \%L, and \%T, the characters "/", "\\", and ":" are changed to "-" to prevent confusion with directory paths on Unix, Windows, and Mac systems. You may place "_" after a variable name ("\%A_") to replace all spaces with "_" characters. You may place "[n]" after a variable name (and optional "_") to truncate at n characters. You may use the lowercase form of the variable name to force the output to lowercase. For instance, given a song named "Unsatisfied" by The Replacements (track 7 on album "Let It Be"): Format string "%A[30]/%l_[30]/%NN - %t_[10].mp3" yields "Replacements/let_it_be/07 - unsatisfie.mp3". If the format string refers to directories that don't exist, $choad will try to create them. "tracks" file: Unless the "-nomaketracks" or "-only" option is given, $choad will create a file called "tracks" in the same directory as the mp3s, of the form: artist Some Cool Band title Album Title track 1 First Song Title track 2 Second Song Title ...and so on. If the "-tracks " option is given, $choad will read the specified file in the above format to specify the artist, title, and track names instead of performing a CDDB lookup. END ; }