#!/usr/bin/perl
#
#    rip -- A command-line based CD ripper and MP3 or Ogg encoder.
#
#    rips audio CD tracks to either Motion Picture Experts
#    Group Layer 3 (MP3) files or to Ogg Vorbis files.
#
#    Copyright (C) 2001 Gregory Smethells
#
#
#    License:
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of the GNU General Public License
#    as published by the Free Software Foundation; either version 2
#    of the License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#    02111-1307, USA.
#
#
#    Disclaimer:
#    YOU SHOULD ONLY CREATE MP3/OGG FILES FROM AUDIO CDs THAT YOU OWN
#    LEGALLY. ANY OTHER USE OF THIS SCRIPT IS NOT LEGAL, IS NOT CONDONED
#    BY THIS AUTHOR, NOR IS SUCH ACTION IN THE SPIRIT OF THIS SCRIPT. THE
#    AUTHOR HEREBY REMOVES HIMSELF FROM TAKING ANY RESPONSIBILITY FOR
#    MISUSE OF THIS SCRIPT OR ANY PIECE OF SOFTWARE USED BY THIS SCRIPT.
#
#
#    Author's Notes:
#    This script requires a CD ripper such as cdparanoia and either an
#    MP3 application that can encode WAVs to MP3s such as gogo or an
#    Ogg Vorbis application that can encode WAVs to Ogg Vorbis files.
#    These tools need to be on your $PATH, since this script is essentially
#    only a wrapper. All major functionality is found in those programs.
#    However, I believe that the abstract functionality provided by
#    this script is quite useful and makes these tools all the more
#    powerful.
#
#    In the future, I hope to get more CD rippers and, mostly, more
#    encoders to work with this script. The essential changes would
#    be to add more $PATH searches for different tools of these types
#    and then set the flags available for that tool. Also flags to rip
#    for any new tool would need to be added and handled properly.
#
#    Booleans in this script use "" for FALSE and "true" for TRUE
#



##########################################################################
#                                                                        #
# DECLARATIONS                                                           #
#                                                                        #
##########################################################################

use Cwd;
use Time::localtime;


$date        = "2001-03-09";              # Date of last modification
$version     = "0.82";                    # Version number for this script

$time        = localtime(time());
$now         = $time->mon . $time->mday . $time->hour . $time->min . $time->sec;

$tempFile    = "/tmp/rip-$now";           # Default temp file

$DEFAULT_DIR = getcwd;                    # Default "default directory"
$pwd         = $DEFAULT_DIR;              # For convience's sake
$outputDir   = $DEFAULT_DIR;              # Default output directory

$out         = "rip_temp_file";           # Temp file's filename
$extension   = "mp3";                     # Filename extension -- "mp3" or "ogg"

$wavONLY     = "";                        # Default is to encode CD tracks to MP3

@PATH        = split( ":", $ENV{PATH});   # Valid @PATH for current user
$HOME        = $ENV{HOME};                # Valid $HOME for current user


# The LOWER the integer value the more PREFERABLE that tool is, i.e.,
# more likely it will be set to $ripper or $encoder.

# Rippers
$cdparanoia                    = 0;

$supportedRippers[$cdparanoia] = "cdparanoia";

$existantRipper[$cdparanoia]   = "";

# Encoders
$gogo                          = 0;
$lame                          = 1;
$bladeenc                      = 2;
$BladeEnc                      = 3;
$oggenc                        = 4;

$supportedEncoders[$gogo]      = "gogo";
$supportedEncoders[$lame]      = "lame";
$supportedEncoders[$bladeenc]  = "bladeenc";
$supportedEncoders[$BladeEnc]  = "BladeEnc";
$supportedEncoders[$oggenc]    = "oggenc";

$existantEncoder[$gogo]        = "";
$existantEncoder[$lame]        = "";
$existantEncoder[$bladeenc]    = "";
$existantEncoder[$BladeEnc]    = "";
$existantEncoder[$oggenc]      = "";

# CDDB Tools
$cddbpl                        = 0;

$supportedCDDBs[$cddbpl]       = "cddb.pl";

$existantCDDB[$cddbpl]         = "";



##########################################################################
#                                                                        #
# TOOLS AVAILABLE                                                        #
#                                                                        #
##########################################################################

# Make sure we have our tools available on the $PATH first...

# Assume there is nothing available initially
$ripper  = "";
$encoder = "";
$cddb    = "";


# Find valid CD ripper tools
for( $i = 0 ; $i < @supportedRippers ; $i++ ) {
  for( $j = 0 ; $j < @PATH ; $j++ ) {
    if( -e ("$PATH[$j]/$supportedRippers[$i]") ) {
      $existantRipper[$i] = "$PATH[$j]/$supportedRippers[$i]";

      if( !$ripper ) {
        $ripper = $existantRipper[$i];
      }
    }
  }
}


# Find valid encoder tools
for( $i = 0 ; $i < @supportedEncoders ; $i++ ) {
  for( $j = 0 ; $j < @PATH ; $j++ ) {
    if( -e ("$PATH[$j]/$supportedEncoders[$i]") ) {
      $existantEncoder[$i] = "$PATH[$j]/$supportedEncoders[$i]";

      if( !$encoder ) {
        $encoder = $existantEncoder[$i];
      }
    }
  }
}


# Find valid CDDB tools
for( $i = 0 ; $i < @supportedCDDBs ; $i++ ) {
  for( $j = 0 ; $j < @PATH ; $j++ ) {
    if( -e ("$PATH[$j]/$supportedCDDBs[$i]") ) {
      $existantCDDB[$i] = "$PATH[$j]/$supportedCDDBs[$i]";

      if( !$cddb ) {
        $cddb = $existantCDDB[$i];
      }
    }
  }
}


# Determine which encoder user likes better based on flags set in @ARGV.
# This MUST be done NOW because the next step is to set the ripper and
# encoder tool's flag variables to their proper values.

for( $i = 0 ; $i < @ARGV ; $i++ ) {
  # Test for BladeEnc flag
  # The check for $ARGV[0] not having a "=" in it prevents accidental s/B//g
  # on flags like "-m=/home/greg/mp3/BBKing" which as a "B" in it.
  if( $ARGV[0] =~ /B/ && !($ARGV[0] =~ /=/) || $ARGV[0] =~ /--bladeenc/ ) {
    if( $existantEncoder[$bladeenc] ) {
      $encoder = $existantEncoder[$bladeenc];
    }
    elsif( $existantEncoder[$BladeEnc] ) {
      $encoder = $existantEncoder[$BladeEnc];
    }
    else {
      print( "rip:  No \"bladeenc\" and no \"BladeEnc\" on your \$PATH\n" );
      print( "rip:  though a -B or a --bladeenc flag was specified. Aborting.\n" );
      &abort;
    }

    if( $ARGV[0] =~ /^--bladeenc$/ || $ARGV[0] =~ /^-B$/ ) {
      shift( @ARGV );
    }
    else {
      $ARGV[0] =~ s/B//g;
    }
  }

  # Test for LAME flag
  # The check for $ARGV[0] not having a "=" in it prevents accidental s/L//g
  # on flags like "-m=/home/greg/mp3/LynyrdSkynyrd" which as an "L" in it.
  if( $ARGV[0] =~ /L/ && !($ARGV[0] =~ /=/) || $ARGV[0] =~ /--lame/ ) {
    if( $existantEncoder[$lame] ) {
      $encoder = $existantEncoder[$lame];
    }
    else {
      print( "rip:  No \"lame\" on your \$PATH though a -L or a\n" );
      print( "rip:  --lame flag was specified. Aborting.\n" );
      &abort;
    }

    if( $ARGV[0] =~ /^--lame$/ || $ARGV[0] =~ /^-L$/ ) {
      shift( @ARGV );
    }
    else {
      $ARGV[0] =~ s/L//g;
    }
  }

  # Test for GOGO flag
  # The check for $ARGV[0] not having a "=" in it prevents accidental s/G//g
  # on flags like "-m=/home/greg/mp3/GratefulDead" which as a "G" in it.
  if( $ARGV[0] =~ /G/ && !($ARGV[0] =~ /=/) || $ARGV[0] =~ /--gogo/ ) {
    if( $existantEncoder[$gogo] ) {
      $encoder = $existantEncoder[$gogo];
    }
    else {
      print( "rip:  No \"gogo\" on your \$PATH though a -G or a\n" );
      print( "rip:  --gogo flag was specified. Aborting.\n" );
      &abort;
    }

    if( $ARGV[0] =~ /^--gogo$/ || $ARGV[0] =~ /^-G$/ ) {
      shift( @ARGV );
    }
    else {
      $ARGV[0] =~ s/G//g;
    }
  }


  # Test for OggEnc flag
  # The check for $ARGV[0] not having a "=" in it prevents accidental s/O//g
  # on flags like "-m=/home/greg/mp3/OtisRedding" which as a "O" in it.
  if( $ARGV[0] =~ /O/ && !($ARGV[0] =~ /=/) || $ARGV[0] =~ /--oggenc/ ) {
    if( $existantEncoder[$oggenc] ) {
      $encoder  = $existantEncoder[$oggenc];

      # Setup vars to encode to Ogg-Vorbis instead of MP3
      $extension = "ogg";
    }
    else {
      print( "rip:  No \"oggenc\" on your \$PATH though a -O or a\n" );
      print( "rip:  --oggenc flag was specified. Aborting.\n" );
      &abort;
    }

    if( $ARGV[0] =~ /^--oggenc$/ || $ARGV[0] =~ /^-O$/ ) {
      shift( @ARGV );
    }
    else {
      $ARGV[0] =~ s/O//g;
    }
  }


  # Cycle through @ARGV flags
  $temp = shift( @ARGV );
  @ARGV = ( @ARGV, $temp );
}


# Setup rip's temp file name
$out = $out . "." . $extension;



##########################################################################
#                                                                        #
# FLAGS AVAILABLE                                                        #
#                                                                        #
##########################################################################

# The flags should be set dependent on which tool was found
# on the $PATH for either the purpose of being a CD ripper
# or being a MP3 or Ogg encoder.


# Set the ripper's flags
if( $ripper =~ /cdparanoia/ ) {
  # Used by parseFlags() when appropriate
  $ripperVERBOSE    = " -v ";
  $ripperQUIET      = " --quiet ";
  $ripperSPEED      = " --force-read-speed ";
  $ripperDEVICE     = " --force-cdrom-device ";
  $ripperPARANOIA   = " --never-skip ";
  $ripperQUERY      = "cdparanoia -sQ";

  $ripperFlags      = " ";            # Default cdparanoia flags
}


# Set the encoder's flags
if( $encoder =~ /bladeenc/ || $encoder =~ /BladeEnc/ ) {
  # Used by parseFlags() when appropriate
  $encoderQUIET     = " -quiet ";
  $encoderBITRATE   = " -br ";
  $encoderPARANOIA  = " -br 160 ";

  $encoderFlags     = " ";            # Default BladeEnc flags

  # How to denote standard input
  $in                  = " STDIN ";
}
elsif( $encoder =~ /gogo/ ) {
  # Used by parseFlags() when appropriate
  $encoderQUIET     = " -silent ";
  $encoderBITRATE   = " -b ";
  $encoderPARANOIA  = " -b 160 ";

  $encoderFlags     = " ";            # Default GOGO flags

  # How to denote standard input
  $in                  = " stdin ";
}
elsif( $encoder =~ /lame/ ) {
  # Used by parseFlags() when appropriate
  $encoderQUIET     = " -S ";
  $encoderBITRATE   = " -b ";
  $encoderPARANOIA  = " -b 160 ";

  $encoderFlags     = " ";            # Default LAME flags

  # How to denote standard input
  $in                  = " - ";
}
elsif( $encoder =~ /oggenc/ ) {
  # Used by parseFlags() when appropriate
  $encoderQUIET     = " -q ";
  $encoderBITRATE   = " -m ";
  $encoderPARANOIA  = " -m 3 ";

  # How to denote standard input
  $encoderFlags     = " - ";          # Default OGGENC flags

  # Bit of a kludge because we need "-" before "-o"
  $in               = " -o ";
}



##########################################################################
#                                                                        #
# MAIN SCRIPT                                                            #
#                                                                        #
##########################################################################

# Main control structure from here on out. When this if is
# finished, then the script is done and will exit.
if( !($ripper) || !($encoder) ) {
  # Prints an error message and exits the script NOW
  &abort;
}
else {
  # All cases must remain here, even "encode" so that an @ARGV
  # of only digits to rip does not cause usage() to be called.
  $case =    ( $ARGV[0] =~ /^-/ )  &&  "flag"     # Either there're flags
          || ( $ARGV[0] =~ /\d/ )  &&  "encode"   # or just digits
          ||                           "default"; # or no args at all

  # Prints a usage message and EXITS the script if there are NO args
  if( $case eq "default" ) {
    &usage;
  }

  # Parse any flags if they exist in the argument list (sets GLOBAL vars)
  if( $case eq "flag" ) {
    &parseFlags;
    &handleFlags;
  }

  # Encode the requested tracks accordingly
  &parseTracks;
  &setNames;
  &encode;
}

exit( 0 );



##########################################################################
#                                                                        #
# SUBROUTINES                                                            #
#                                                                        #
##########################################################################


##########################################################################
#                                                                        #
# SUB: parseFlags                                                        #
#                                                                        #
#   Parses arguments (flags) given to rip. Once it is determined what    #
#   flags have been given, either GLOBAL variables that the subroutine   #
#   "encode" looks at are set or functions such as cdparanoia's query    #
#   are run.                                                             #
#                                                                        #
##########################################################################

sub parseFlags {
  # Exhaustively check args in @ARGV until the array is empty.
  # We shift() out each arg after we are completely finished with it.
  while( $ARGV[0] =~ /^\-/ ) {

    # Parse flags of the form:  --eject

    $item =   ( $ARGV[0] =~ /^-b=/         )  &&  "bitrate"
           || ( $ARGV[0] =~ /^--bitrate=/  )  &&  "bitrate"
           || ( $ARGV[0] =~ /^--bladeenc/  )  &&  "bladeenc"
           || ( $ARGV[0] =~ /^--cddb/      )  &&  "cddb"
           || ( $ARGV[0] =~ /^-d=/         )  &&  "device"
           || ( $ARGV[0] =~ /^--dev=/      )  &&  "device"
           || ( $ARGV[0] =~ /^--eject/     )  &&  "eject"
           || ( $ARGV[0] =~ /^--gogo/      )  &&  "gogo"
           || ( $ARGV[0] =~ /^--help/      )  &&  "help"
           || ( $ARGV[0] =~ /^--lazy/      )  &&  "lazy"
           || ( $ARGV[0] =~ /^--lame/      )  &&  "lame"
           || ( $ARGV[0] =~ /^-m=/         )  &&  "move"
           || ( $ARGV[0] =~ /^--move=/     )  &&  "move"
           || ( $ARGV[0] =~ /^--number/    )  &&  "number"
           || ( $ARGV[0] =~ /^--oggenc/    )  &&  "oggenc"
           || ( $ARGV[0] =~ /^--paranoia/  )  &&  "paranoia"
           || ( $ARGV[0] =~ /^--play/      )  &&  "play"
           || ( $ARGV[0] =~ /^--query/     )  &&  "query"
           || ( $ARGV[0] =~ /^--quiet/     )  &&  "quiet"
           || ( $ARGV[0] =~ /^--rename/    )  &&  "rename"
           || ( $ARGV[0] =~ /^-s=/         )  &&  "cdspeed"
           || ( $ARGV[0] =~ /^--speed=/    )  &&  "cdspeed"
           || ( $ARGV[0] =~ /^--superlazy/ )  &&  "superlazy"
           || ( $ARGV[0] =~ /^--trayclose/ )  &&  "trayclose"
           || ( $ARGV[0] =~ /^--version/   )  &&  "version"
           || ( $ARGV[0] =~ /^--verbose/   )  &&  "verbose"
           || ( $ARGV[0] =~ /^--wav/       )  &&  "wav"
           ||                                     "unknown";


    # Parse out value given to bitrate=, move=, speed=,
    # or dev= when argument is in one of those formats.

    $revArg = reverse( $ARGV[0] );

    if( $ARGV[0] =~ /^-b=/ ) {
      for( $i = 0 ; $i < 3 ; $i++ ) { chop( $revArg ); }
      $kbps = reverse( $revArg );
    }
    elsif( $ARGV[0] =~ /^--bitrate=/ ) {
      for( $i = 0 ; $i < 10 ; $i++ ) { chop( $revArg ); }
      $kbps = reverse( $revArg );
    }
    elsif( $ARGV[0] =~ /^-d=/ ) {
      for( $i = 0 ; $i < 3 ; $i++ ) { chop( $revArg ); }
      $dev = reverse( $revArg );
    }
    elsif( $ARGV[0] =~ /^--dev=/ ) {
      for( $i = 0 ; $i < 6 ; $i++ ) { chop( $revArg ); }
      $dev = reverse( $revArg );
    }
    elsif( $ARGV[0] =~ /^-m=/ ) {
      for( $i = 0 ; $i < 3 ; $i++ ) { chop( $revArg ); }
      $outputDir = reverse( $revArg ) . "\/";;
    }
    elsif( $ARGV[0] =~ /^--move=/ ) {
      for( $i = 0 ; $i < 7 ; $i++ ) { chop( $revArg ); }
      $outputDir = reverse( $revArg ) . "\/";;
    }
    elsif( $ARGV[0] =~ /^-s=/ ) {
      for( $i = 0 ; $i < 3 ; $i++ ) { chop( $revArg ); }
      $speed = reverse( $revArg );
    }
    elsif( $ARGV[0] =~ /^--speed=/ ) {
      for( $i = 0 ; $i < 8 ; $i++ ) { chop( $revArg ); }
      $speed = reverse( $revArg );
    }



    if( $item ne "unknown" ) {
      # Accumulate the $item to be dealt with later on or...

      @flagsToCheck = ( $item, @flagsToCheck );
    }
    else {
      # Parse flags of the form:  -SPn

      # Make sure we get every last char in $ARGV[0]
      while( $ARGV[0] =~ /^-\w/ ) {
        $check = chop( $ARGV[0] );

        $item =   ( $check =~ /B/  )  &&  "bladeenc"
               || ( $check =~ /c/  )  &&  "cddb"
               || ( $check =~ /e/  )  &&  "eject"
               || ( $check =~ /G/  )  &&  "gogo"
               || ( $check =~ /h/  )  &&  "help"
               || ( $check =~ /l/  )  &&  "lazy"
               || ( $check =~ /L/  )  &&  "lame"
               || ( $check =~ /n/  )  &&  "number"
               || ( $check =~ /O/  )  &&  "oggenc"
               || ( $check =~ /p/  )  &&  "paranoia"
               || ( $check =~ /P/  )  &&  "play"
               || ( $check =~ /q/  )  &&  "query"
               || ( $check =~ /Q/  )  &&  "quiet"
               || ( $check =~ /r/  )  &&  "rename"
               || ( $check =~ /S/  )  &&  "superlazy"
               || ( $check =~ /t/  )  &&  "trayclose"
               || ( $check =~ /V/  )  &&  "version"
               || ( $check =~ /v/  )  &&  "verbose"
               || ( $check =~ /w/  )  &&  "wav"
               ||                         "default";

        @flagsToCheck = ( $item, @flagsToCheck );
      }
    }

    shift( @ARGV );
  }
}



##########################################################################
#                                                                        #
# SUB: handleFlags                                                       #
#                                                                        #
#   Once parseFlags sets up the @flagsToCheck array, handleFlags may     #
#   run through the array, taking appropriate action. These actions can  #
#   include, and are not limited to: setting ripperFlags or setting      #
#   encoderFlags, querying & exiting, calling usage & exiting, and       #
#   printing out version info. Many switches only set flags and do not   #
#   exit the script.                                                     #
#                                                                        #
##########################################################################

sub handleFlags {
  # Finished parsing the @ARGV array. Now determine how to flag
  # $ripper and $encoder; possibly reacting quickly, calling
  # subroutines abort() or usage(), even die() or exit().

  foreach $flag ( @flagsToCheck ) {

    if( $flag eq "bitrate" ) {
        if( !$biterateSet && $encoder ne $existantEncoder[$oggenc] ) {
          if( !($kbps =~ /\d{1,3}/) ) {
            die( "rip:  Invalid bitrate ${kbps}. Try 128, 160, 192, 224, 256, or 320.\n" );
          }

          $encoderFlags = $encoderFlags . $encoderBITRATE . $kbps;
          $bitrateSet   = "true";
        }
        elsif( !$biterateSet && $encoder eq $existantEncoder[$oggenc] ) {
          if( !($kbps =~ /^[23456]$/) ) {
            die( "rip:  Invalid oggenc mode ${kbps}. Choices include 2, 3, 4, 5, or 6.\n" );
          }

          $encoderFlags = $encoderFlags . $encoderBITRATE . $kbps;
          $bitrateSet   = "true";
        }
    }
    elsif( $flag eq "bladeenc" ) {
        if( $existantEncoder[$bladeenc] ) {
          $encoder = $existantEncoder[$bladeenc];
        }
        elsif( $existantEncoder[$BladeEnc] ) {
          $encoder = $existantEncoder[$BladeEnc];
        }
        else {
          print( "rip:  No \"bladeenc\" and no \"BladeEnc\" on your \$PATH\n" );
          print( "rip:  though a -B or a --bladeenc flag was specified. Aborting.\n" );
          &abort;
        }
    }
    elsif( $flag eq "cddb" ) {
        $checkDatabase = "true";
    }
    elsif( $flag eq "device" ) {
        if( $dev =~ /^\/dev\/\w+/ && ( -e $dev ) ) {
          $ripperFlags = $ripperFlags . $ripperDEVICE . $dev;
        }
        else {
          die( "rip:  Invalid device: ${dev}. Aborting.\n" ) ;
        }
    }
    elsif( $flag eq "eject" ) {
        $eject = "true";
    }
    elsif( $flag eq "gogo" ) {
        if( $existantEncoder[$gogo] ) {
          $encoder = $existantEncoder[$gogo];
        }
        else {
          print( "rip:  No \"gogo\" on your \$PATH though a -G or a\n" );
          print( "rip:  --gogo flag was specified. Aborting.\n" );
          &abort;
        }
    }
    elsif( $flag eq "help" || $flag eq "default" ) {
        &usage;
    }
    elsif( $flag eq "lazy" ) {
        if( $encoder ne $existantEncoder[$oggenc] ) {
          $kbps = 160;
        }
        else {
          $kbps = 3;  # This is a mode not a rate
        }

        @flagsToCheck = (@flagsToCheck, "trayclose");
        @flagsToCheck = (@flagsToCheck, "bitrate"  );
        @flagsToCheck = (@flagsToCheck, "cddb"     );
        @flagsToCheck = (@flagsToCheck, "eject"    );
        $lazy         = "true";
        $superlazy    = "";
    }
    elsif( $flag eq "lame" ) {
        if( $existantEncoder[$lame] ) {
          $encoder = $existantEncoder[$lame];
        }
        else {
          print( "rip:  No \"lame\" on your \$PATH though a -L or a\n" );
          print( "rip:  --lame flag was specified. Aborting.\n" );
          &abort;
        }
    }
    elsif( $flag eq "move" ) {
        if( $outputDir =~ /~/ ) {
          $outputDir =~ s/~/$HOME/g;
        }

        if(  !( -e $outputDir )  ) {
          `mkdir $outputDir`;

          if(  !( -e $outputDir ) || !( -w $outputDir )  ) {
            print( "Either the output directory $outputDir cannot be created\n" );
            $outputDir = $DEFAULT_DIR;
            print( "or it is not writable. Output will be to $outputDir instead.\n" );
          }
          else {
            $moveIt = "true";
          }
        }
    }
    elsif( $flag eq "number" ) {
        $numberThem = "true";
    }
    elsif( $flag eq "oggenc" ) {
        if( $existantEncoder[$oggenc] ) {
          $encoder = $existantEncoder[$oggenc];
        }
        else {
          print( "rip:  No \"oggenc\" on your \$PATH though a -O or a\n" );
          print( "rip:  --oggenc flag was specified. Aborting.\n" );
          &abort;
        }
    }
    elsif( $flag eq "paranoia" ) {
        if( !$biterateSet ) {
          $ripperFlags  = $ripperFlags  . $ripperPARANOIA;
          $encoderFlags = $encoderFlags . $encoderPARANOIA;
          $bitrateSet   = "true";
        }
    }
    elsif( $flag eq "play" ) {
        $play = "true";
    }
    elsif( $flag eq "query" ) {
        system( "$ripperQUERY" );

        exit( 0 );
    }
    elsif( $flag eq "quiet" ) {
        $quiet        = "true";
        $verbose      = "";
        $ripperFlags  = $ripperFlags  . $ripperQUIET;
        $encoderFlags = $encoderFlags . $encoderQUIET;
    }
    elsif( $flag eq "rename" ) {
        $renameTracks = "true";
    }
    elsif( $flag eq "cdspeed" ) {
        $okaySpeed = "true";

        if(  !( $speed =~ /^\d+$/ )  ) {
          die( "rip:  Invalid CD read speed: ${speed}. Aborting.\n" );
        }
        else {
          if( $speed < 0 || $speed > 80 ) {
            $okaySpeed = "";

            print( "\nCD read speed should be ${speed}?\n" );
            print( "Are you sure (y/N)? " );
            chop( $answer = <STDIN> );

            if( $answer =~ /^[Yy]/ ) {
              $okaySpeed = "true";
            }
          }
        }

        if( $okaySpeed ) {
          $ripperFlags = $ripperFlags . $ripperSPEED . $speed;
        }
    }
    elsif( $flag eq "superlazy" ) {
        if( $encoder ne $existantEncoder[$oggenc] ) {
          $kbps = 160;
        }
        else {
          $kbps = 3;  # This is a mode not a rate
        }

        @flagsToCheck = (@flagsToCheck, "trayclose");
        @flagsToCheck = (@flagsToCheck, "bitrate"  );
        @flagsToCheck = (@flagsToCheck, "cddb"     );
        @flagsToCheck = (@flagsToCheck, "eject"    );
        $lazy         = "true";
        $superlazy    = "true";
    }
    elsif( $flag eq "trayclose" ) {
        $trayclose = "true";
    }
    elsif( $flag eq "verbose" ) {
        $verbose     = "true";
        $quiet       = "";
        $ripperFlags = $ripperFlags . $ripperVERBOSE;
    }
    elsif( $flag eq "version" ) {
        print( "\n########################################################\n"  );
        print( "#     rip  --  Created by Greg Smethells (c) 2001      #\n"  );
        print( "########################################################\n"  );
        print( "#      Version $version was last modified on $date    #\n"  );
        print( "# Please report bugs and/or email your suggestions to  #\n"  );
        print( "#             gsmethells\@linuxfreak.com                #\n" );
        print( "#    http://www.linuxfreak.com/~gsmethells/rip/        #\n"  );
        print( "########################################################\n\n"  );

        exit( 0 );
    }
    elsif( $flag eq "wav" ) {
        $wavONLY = "true";
    }
    else {
        print( "rip:  Unknown flag \"${flag}\". It will be ignored.\n" );
    }
  }


  # Only trayclose if requested and after all flags such
  # as --quiet have been fully parsed.
  if( $trayclose ) {
    !$quiet && print( "Loading CD tray... " );
    system( "eject --trayclose" );
    !$quiet && print( "Done.\n" );
  }
}



##########################################################################
#                                                                        #
# SUB: parseTracks                                                       #
#                                                                        #
#   Parses the track list argument to rip. Then set @trackList to show   #
#   what tracks to handle.                                               #
#                                                                        #
##########################################################################

sub parseTracks {

  # Parse the tracks list given after the flags

  while( $ARGV[0] =~ /\d/ ) {

    # Determine if the user used a dash or spaced number format

    if( $ARGV[0] =~ /^\d{1,2}\-\d{1,2}$/ ) {      # USED DASHED
      $char  = chop( $ARGV[0] );
      $start = "";
      $end   = "";

      while( $char ne '-' ) {
        $end  = $char . $end;
        $char = chop( $ARGV[0] );
      }

      $start = $ARGV[0];

      @trackList = ( @trackList, ${start}..${end} );
    }
    elsif( $ARGV[0] =~ /^\d{1,2}$/ ) {           # USED SPACED
      @trackList = ( @trackList, $ARGV[0] );
    }
    else {
      print( "rip:  Sorry, cannot rip \"$ARGV[0]\"\n" );
      &usage;
    }

    shift( @ARGV );
  }

  if( @trackList != 0 && !$quiet ) {
    print( "\n\nRipping the following track(s):  @trackList\n\n" );
  }
}



##########################################################################
#                                                                        #
# SUB: setNames                                                          #
#                                                                        #
#   If the track are to be renamed after encoding, then request or set   #
#   the names to be used for each track now.                             #
#                                                                        #
##########################################################################

sub setNames {
  if( $checkDatabase && $renameTracks ) {
    print( "\nOnly one of -c/--cddb and -r/--rename can be used at a time\n\n" );
    &abort;
  }

  if( $renameTracks  && !$wavONLY ) {
    &manualRename;
  }

  if( $checkDatabase && !$wavONLY ) {
    &cddbRename;
  }

  if( $lazy ) {
    &beLazy;
  }
}



##########################################################################
#                                                                        #
# SUB: manualRename                                                      #
#                                                                        #
#   Prompt the user, manually, to give the proper name for each track    #
#   which is to be ripped to MP3 or to Ogg Vorbis.                       #
#                                                                        #
##########################################################################

sub manualRename {
  print( "\n****************************************************\n" );
  print( "We will now take the proper names for the $extension files.\n" );
  print( "EXAMPLE:  Riders_On_The_Storm.$extension\n\n"                  );
  print( "You may remove the CD for a moment if you need it.\n\n" );

  print( "Should I eject the CD tray (Y/n)? " );
  chop( $answer = <STDIN> );

  if( !($answer =~ /^[Nn]/) ) {
    system( "eject --cdrom" );
  }

  print( "\n" );

  $doItAgain = "true";

  while( $doItAgain ) {
    @renameList = ();

    foreach $num (@trackList) {
      $newName = "";

      if( $num < 10 ) {
        print( "<Rename track $num >  " );
      }
      else {
        print( "<Rename track $num>  " );
      }

      chop( $newName = <STDIN> );
      @renameList = ( @renameList, $newName );
    }

    print( "\n\n\n" );

    $num = 0;
    foreach $item (@renameList) {
      print( "track $trackList[$num]: $item\n" );
      $num++;
    }

    print( "\nIs this right (Y/n)?  " );
    chop( $answer = <STDIN> );
    print( "\n" );

    if( $answer =~ /^[Nn]/ ) {
      $doItAgain = "true";
    }
    else {
      $doItAgain = "";
    }
  }

  print( "\nMake sure the CD is in the tray and hit <ENTER>." );
  <STDIN>;  # Block until user hits <enter>

  system( "eject --trayclose" );

  print( "****************************************************\n\n" );
}



##########################################################################
#                                                                        #
# SUB: cddbRename                                                        #
#                                                                        #
#   Setup the renameList variable by way of CDDB. If there are no track  #
#   arguments to rip, just print CDDB info to the screen.                #
#                                                                        #
##########################################################################

sub cddbRename {

  # Get the names to rename MP3s/Oggs to, via CDDB, if so requested (flagged)

  if( !($cddb) ) {
    print( "The -c or --cddb flag you used requires CDDB/CDDB_get\n" );
    print( "You can find this on http://freshmeat.com by searching\n" );
    print( "on the keyword:  CDDB_get\n" );
  }
  else {
    if(  0 == @trackList && !$lazy  ) {
      print( "Connecting to freedb.freedb.org...\n\n" );
      `$cddb > /dev/tty`;
    }
    else {
      # Redirect the CDDB info from freedb.freedb.org to a temp file
      # If forced to choose between different CDDB entries, we will
      # always choose entry 1, as evident by the "echo 1" input to $cddb

      `rm -f /tmp/rip-*`;

      !$quiet && print( "Connecting to freedb.freedb.org... " );
      system( "echo 1 | $cddb > $tempFile" );
      !$quiet && print( "Done.\n" );


      # Open the temp file and parse we got back from freedb.freedb.org

      open( INFILE, "<$tempFile") || die( "rip:  cannot open cddb info file: $!\n");

      if( eof INFILE ) {
        print( "rip:  Cannot connect to CDDB: Connection refused.\n" );
        print( "rip:  CDDB lookup has been aborted.\n" );

        if( $lazy ) {
          die( "rip:  Cannot complete lazy rip. Exiting script.\n" );
        }
      }
      else {
        # Read the artist name (used by subroutine beLazy() if it's called)
        while(  !($_ =~ /artist/)  ) {
          $_ = <INFILE>;
        }

        chop( $_ );
        s/ //g;
        s/\&/And/g;
        s/\#/Number/g;
        s/[\(\)\[\]\{\}\'\"\`\.\?\/\\,]//g;
        @line = split( ":", $_ );

        # May get back "Choose: artist: SomeNameHere"
        # instead of "artist: SomeNameHere" if forced to choose
        # between CDDB entries (we choose first one no matter what).
        if( $line[1] =~ /artist/ ) {
          $artist = $line[2];
        }
        else {
          $artist = $line[1];
        }


        # Read the album name (used by subroutine beLazy() if it's called)
        while(  !($_ =~ /title/)  ) {
          $_ = <INFILE>;
        }

        chop( $_ );
        s/ //g;
        s/\&/And/g;
        s/\#/Number/g;
        s/[\(\)\[\]\{\}\'\"\`\.\?\/\\,]//g;
        @line = split( ":", $_ );
        $title = $line[1];


        # Read the num of tracks
        while(  !($_ =~ /trackno/)  ) {
          $_ = <INFILE>;
        }

        chop( $_ );
        s/ //g;
        @line = split( ":", $_ );
        $trackno = $line[1];

        $tempNum      = 2;
        $previousName = "";


        # Create the @renameList
        for( $i = 0 ; $i < $trackno ; $i++ ) {
          $_ = <INFILE>;
          chop( $_ );
          s/ /_/g;
          s/\&/And/g;
          s/\#/Number_/g;
          s/[\(\)\[\]\{\}\'\"\`\.\?\/\\,]//g;
          @line = split( ":", $_);
          $newName = reverse( $line[1] );
          chop( $newName );
          $newName = reverse( $newName );

          # Do not allow tracks to have the same name
          if( $newName eq $previousName ) {
            $previousName = $newName;
            $newName      = $newName . "_" . $tempNum . "." . $extension;
            $tempNum++;
          }
          else {
            $previousName = $newName;
            $newName      = $newName . "." . $extension;
          }

          @renameList = ( @renameList, $newName );
        }
      }
    }
  }

  close( INFILE );
}



##########################################################################
#                                                                        #
# SUB: beLazy                                                            #
#                                                                        #
#   Using cddb.pl, create a dir in the current working dir with the name #
#   given in the artist field, setup the trackList to rip the entire CD, #
#   and create a playlist that XMMS could use in the "artist's" dir.     #
#   This entire section of code assumes you have already called the      #
#   subroutine "cddbRename" prior to this point.                         #
#                                                                        #
##########################################################################

sub beLazy {
  # Setup the output dir based on the artist's name
  $outputDir = "$pwd/$artist/";

  if(  !( -e $outputDir ) ) {
    `mkdir $outputDir`;

    if(  !( -e $outputDir ) || !( -w $outputDir )  ) {
      print( "Either the output directory $outputDir cannot be created\n" );
      $outputDir = $DEFAULT_DIR;
      print( "or it is not writable. Output will be to $outputDir instead.\n" );
      print( "Likely there is a file with the name $artist already in $pwd.\n" );
    }
  }

  # Setup the output dir based on the artist's name
  # and the album's title if we are being super lazy
  if( $superlazy ) {
    $outputDir = "$pwd/$artist/$title/";

    if(  !( -e $outputDir ) ) {
      `mkdir $outputDir`;

      if(  !( -e $outputDir ) || !( -w $outputDir )  ) {
        print( "Either the output directory $outputDir cannot be created\n" );
        $outputDir = $DEFAULT_DIR;
        print( "or it is not writable. Output will be to $outputDir instead.\n" );
        print( "Likely there is a file with the name $title already in $pwd/$artist.\n" );
      }
    }
  }

  # Rip the ENTIRE CD
  @trackList = (1..$trackno);

  # Create an empty playlist file
  `rm -f $pwd/$title`;
  `touch $pwd/$title`;

  open( OUTFILE, ">$pwd/$title" ) || die( "rip:  cannot open playlist file: $!\n" );

  # Create each playlist file entry using a format XMMS likes
  for( $i = 0 ; $i < $trackno ; $i++ ) {
    $nextTrack = $renameList[$i];
    $value     = $i + 1;

    if( $numberThem && $value < 10 ) {
      print OUTFILE ( "${outputDir}0${value}_${nextTrack}\n" );
    }
    elsif( $numberThem ) {
      print OUTFILE ( "${outputDir}${value}_${nextTrack}\n" );
    }
    else {
      print OUTFILE ( "${outputDir}${nextTrack}\n" );
    }
  }

  close( OUTFILE );
}



##########################################################################
#                                                                        #
# SUB: encode                                                            #
#                                                                        #
#   Based upon what GLOBAL variables have been set, encode the given     #
#   tracks to MP3, Ogg Vorbis, or WAV one at a time. This saves hard     #
#   drive space compared to ripping all tracks to WAV first and then     #
#   encoding.                                                            #
#                                                                        #
##########################################################################

sub encode {
  # If in verbose mode, output flags in use by cdparanoia and bladeenc
  if( $verbose ) {
    print( "\nRipper:        $ripper\n"       );
    print(   "Ripper flags:  $ripperFlags\n"  );
    print(   "Encoder:       $encoder\n"      );
    print(   "Encoder flags: $encoderFlags\n" );
    print(   "CDDB tool:     $cddb\n"         );
    print(   "Output dir:    $outputDir\n\n"  );
  }

  # Warn that users of "lazy" rips cannot do a "move" also (its implicit)
  if( $lazy && $moveIt ) {
    print( "rip:  Cannot use -m/--move and -l/--lazy or -S/--superlazy together\n" );
    print( "rip:  since -m/--move is implicit in a -l/--lazy or a -S/--superlazy\n" );
  }

  # Copy (do not alias!) the STDOUT file descriptor
  open( OUT, ">&STDOUT" );

  # Make child processes (ripper and encoder) quiet if not in verbose mode
  if( !$verbose ) {
    # Make sure $ripper is quiet
    if( !($ripperFlags =~ /$ripperQUIET/) ) {
      $ripperFlags = $ripperFlags  . $ripperQUIET;
    }

    # Make sure $encoder is quiet
    # Ogg Encoder oggenc crashes if you do not set
    # the "-q" flag and you close STDERR
    if( !($encoderFlags =~ /$encoderQUIET/) ) {
      $encoderFlags = $encoderFlags . $encoderQUIET;
    }

    close( STDOUT );
    close( STDERR );
  }

  if( @trackList > 0 ) {
    !$quiet && print OUT "\n";
  }


  # Rip all the way to MP3 (or Ogg Vorbis) unless the WAV only flags is set
  foreach $track ( @trackList ) {
    if( $track =~ /^\d{1,2}$/ ) {
      if( $track < 10 ) {
        !$quiet && print OUT "Now ripping CD track $track...  ";
      }
      else {
        !$quiet && print OUT "Now ripping CD track $track... ";
      }


      if( $wavONLY ) {
        system( "$ripper $ripperFlags $track" );
      }
      else {
        # Get the track's proper name (output file name)
        if( $renameTracks ) {
          $properName = shift( @renameList );
        }
        elsif( $checkDatabase ) {
          $properName = $renameList[ $track - 1 ];
        }
        else {
          $properName = "track" . $track . "." . $extension;
        }

        # Possibly add numbering to proper name (output file name)
        if( $numberThem && $track < 10 ) {
          $properName = "0" . $track . "_" . $properName;
        }
        elsif( $numberThem ) {
          $properName = $track . "_" . $properName;
        }

        # Strip out non-word characters in the filename $properName
        # that are still hanging around (just in case)
        $properName =~ s/\.$extension$//;
        $properName =~ s/ /_/g;
        $properName =~ s/\&/And/g;
        $properName =~ s/\#/Number_/g;
        $properName =~ s/\W//g;
        $properName .= ".$extension";

        # This is the actual CD rip to MP3 or Ogg Vorbis (blocking system call)
        system( "$ripper $ripperFlags $track - | $encoder $encoderFlags $in $out && mv $out $outputDir/$properName" );

        # Play the ripped file if so flagged
        if( $play && $track == $trackList[0] ) {
          !$quiet && print OUT "Playing ${properName}...  ";
          system( "xmms --play $outputDir/$properName &" );
        }
        elsif( $play ) {
          !$quiet && print OUT "Enqueuing ${properName}...  ";
          system( "xmms --enqueue $outputDir/$properName &" );
        }
        else {
          # Do not play anything
        }

        !$quiet && print OUT "Done.\n";
      }
    }
    else {
      print OUT "Track to rip argument was syntactically incorrect: $track\n\n";
      print OUT "An example of legal syntax is:   rip 2-5 7 8 10-13 15\n";
      exit( 2 );
    }
  }


  if( @trackList > 0 ) {
    !$quiet && print OUT "\n";
  }

  # Finished with CD. It is safe to now eject the CD tray, if so flagged
  if( $eject ) {
    !$quiet && print OUT "Ejecting CD tray... ";
    system( "eject --cdrom" );
    !$quiet && print OUT "Done.\n";
  }
}



##########################################################################
#                                                                        #
# SUB: usage                                                             #
#                                                                        #
#   Prints a usage message for rip and exits.                            #
#                                                                        #
##########################################################################

sub usage {
  print( "\nUSAGE: rip [option(s)] <track(s)>\n"                                                  );
  print( "\nOPTIONS:\n\n"                                                                         );
  print( "  -b=  --bitrate=NUM           set bitrate for MP3 encoding to NUM kbps  -or-\n"        );
  print( "                               set mode for Ogg Vorbis encoding to mode NUM\n"          );
  print( "  -B   --bladeenc              use BladeEnc for MP3 encoding\n"                         );
  print( "  -c   --cddb                  use CDDB to rename output files (must be online)\n"      );
  print( "  -d=  --dev=DEV               set input cdrom device to be DEV\n"                      );
  print( "  -e   --eject                 eject CD tray after doing everything else\n"             );
  print( "  -G   --gogo                  use GOGO for MP3 encoding\n"                             );
  print( "  -h   --help                  print this help to the screen and exit\n"                );
  print( "  -l   --lazy                  rip entire CD, use CDDB to rename output filenames,\n"   );
  print( "                               move to \$PWD/Artist/ after file is encoded,\n"          );
  print( "                               create an XMMS compatible playlist in \$PWD,\n"          );
  print( "                               uses -t, -c, -b=160, -m, and -e flags implicitly,\n"     );
  print( "                               will load and eject CD tray automatically\n"             );
  print( "  -L   --lame                  use LAME for MP3 \"encoding\"\n"                         );
  print( "  -m=  --move=DIR              move output files into DIR (absolute pathname)\n"        );
  print( "  -n   --number                append 01_, 02_, 03_, etc to start of filename\n"        );
  print( "  -O   --oggenc                use oggenc and encode to Ogg Vorbis files\n"             );
  print( "  -p   --paranoia              use 160 kbps and don't accept skips on rip\n"            );
  print( "  -P   --play                  play completed MP3s/Oggs in XMMS during rip\n"           );
  print( "  -q   --query                 print track info for the CD in the drive and exit\n"     );
  print( "  -Q   --quiet                 rip and encode tracks without any visible output\n"      );
  print( "  -r   --rename                ask for proper name of CD tracks before beginning rip\n" );
  print( "  -s=  --speed=NUM             set CD read speed to NUM during ripping \n"              );
  print( "  -S   --superlazy             same functionality as -l/--lazy except that output\n"    );
  print( "                               is placed in \$PWD/Artist/Album/ instead\n"              );
  print( "  -t   --trayclose             close CD tray before doing anything\n"                   );
  print( "  -v   --verbose               print verbose information about what script is doing\n"  );
  print( "  -V   --version               print rip's version information\n"                       );
  print( "  -w   --wav                   rip CD tracks to WAV files and no further\n"             );
  print( "\nEXAMPLES:\n\n"                                                                        );
  print( "  rip 1 3-5 7 9-11 12 14       rip tracks 1 3 4 5 7 9 10 11 12 and 14 to MP3\n"         );
  print( "  rip 6                        rip track 6 to MP3 using default encoder\n"              );
  print( "  rip -c 6                     rip track 6 and rename its MP3 file via CDDB\n"          );
  print( "  rip -cp 6                    simliar: also uses 160 kbps and accepts no skips\n"      );
  print( "  rip -cp -m=/home/mp3 6       simliar: also moves MP3 file to /home/mp3\n"             );
  print( "  rip -et -s=32 6              trayclose, using a CD read speed of 32x, eject\n"        );
  print( "  rip -vL -b=160 6             verbose rip, using LAME and 160 kbps encoding\n"         );
  print( "  rip -d=/dev/hdd 6            rip using /dev/hdd as the input CDROM drive\n"           );
  print( "\nNORMAL USAGE:\n\n"                                                                    );
  print( "  rip -q                       print track info for CD in drive\n"                      );
  print( "  rip -c                       print CDDB info rip would use during rip and exit\n\n"   );
  print( "  rip -Ppr 1-3 6 8-11          rip to MP3 with paranoia, rename, and play\n"            );
  print( "  rip -Ppc 1-3 6 8-11          similar: renames via CDDB (must be online)\n"            );
  print( "  rip -Ppc -m=/home/mp3 1-11   similar: also does a move (must be online)\n\n"          );
  print( "  rip -SP                      laziness at its best IMHO (must be online)\n"            );
  print( "  rip -SPO                     similar: rip CD to Ogg Vorbis instead\n\n"               );
  print( "\n  The default output directory is the present working directory.\n"                   );
  print( "  This goes for --lazy and --superlazy in that they output to a subdirectory\n"         );
  print( "  \$PWD/ArtistName/ and \$PWD/ArtistName/AlbumTitle/ respectively.\n"                   );
  print( "\n  The default output type is MP3, but if you specify -O or --oggenc then output\n"    );
  print( "  is in Ogg Vorbis instead. All flags except -B, -L, and -G work with -O/--oggenc.\n"   );
  print( "  When using Ogg Vorbis, the -b=/--bitrate=NUM flag sets the oggenc mode to NUM.\n"     );
  print( "  Paranoia still uses approximately 160 kbps on the average (oggenc mode 3).\n"         );
  print( "\n  To use the CDDB features of this script, you need the perl module\n"                );
  print( "  CDDB/CDDB_get installed. Available on http://freshmeat.net.\n\n"                      );
  print( "             http://www.linuxfreak.com/~gsmethells/rip/\n\n"                            );

  exit( 0 );
}



##########################################################################
#                                                                        #
# SUB: abort                                                             #
#                                                                        #
#   Prints error messages if either a CD Ripper or an Encoder is not     #
#   present on the $PATH, then exits.                                    #
#                                                                        #
##########################################################################

sub abort {
  if( !($ripper) ) {
    print( "\nrip:  WARNING: A CD Ripper was *not* found on your \$PATH\n" );
    print ("rip:  you can download one from http://www.xiph.org/paranoia/\n\n" );
  }

  if( !($encoder) ) {
    print( "\nrip:  WARNING: An MP3 Encoder was *not* found on your \$PATH\n" );
    print ("rip:  you can download one from http://bladeenc.mp3.no\n\n" );
  }

  exit( 1 );
}
