#!/usr/bin/perl -w
#
#    rip -- A command-line based CD ripper (supports MP3, Ogg Vorbis, and WAV)
#
#    rips audio CD tracks to either Motion Picture Experts Group Layer 3 (MP3)
#    files, to Ogg Vorbis files, or to WAV files with no user intervention
#    between steps of ripping and encoding.
#
#    Copyright (C) 2001 Gregory J. 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
#    @supportedEncoder entries and more checks to set the flags available for
#    that tool in the subroutine setFlags(). Also new flags for rip to setup
#    any new tool would require changes to checkEncoderFlags(), parseFlags(),
#    and handledFlags().
#
#    Booleans in this script use "" for FALSE and "true" for TRUE.
#    Logical constructs use "and", "or", and "not".
#
#    The standard file descriptor STDERR is closed and redirected to a
#    a debugging file, therefore the use of "die()" is not suggested.
#    Instead we will do a "print() and exit()" so that output is to
#    STDOUT where the user can actually see it and read it.



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

use strict;
use Cwd;
use Time::localtime;
use sigtrap 'handler', \&signalHandler, 'normal-signals';


# Most of these flags are set later on by different subroutines in the
# script, so setting them here would likely do little good in most cases.

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

my $date              = "2001-03-23";             # Date of last modification
my $version           = "0.92";                   # Version number for this script

my $STDERRFile        = "/tmp/rip-$now-stderr";   # DEBUG file (stderr redirected here)
my $CDDBFile          = "/tmp/rip-$now-cddb";     # Temp file for CDDB output

my $DEFAULT_DIR       = getcwd;                   # Default "default directory"
my $pwd               = $DEFAULT_DIR;             # Where all output will be placed
my $subDir            = "";                       # Default output subdirectory

my $in                = "";                       # How to denote to take from stdin
my $out               = "rip_temp_file";          # What to name temp file
my $extension         = "mp3";                    # Filename extension: "mp3", "ogg", or "wav"

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

my $NOT_FOUND         = -1;

my $ripper            = $NOT_FOUND;               # Index for CD ripper to use
my $cdparanoia        = 0;                        # Index for cdparanoia
my @supportedRipper   = ();                       # List of supported rippers
my @ripperPath        = ();                       # List of paths to installed rippers

my $encoder           = $NOT_FOUND;               # Index for encoder to use
my $gogo              = 0;                        # Index for GOGO
my $lame              = 1;                        # Index for LAME
my $bladeenc          = 2;                        # Index for bladeenc
my $BladeEnc          = 3;                        # Index for BladeEnc
my $oggenc            = 4;                        # Index for oggenc
my @supportedEncoder  = ();                       # list of supported encoders
my @encoderPath       = ();                       # List of paths to installed encoders

my $cddb              = $NOT_FOUND;               # Index for CDDB tool to use
my $cddbpl            = 0;                        # Index for cddb.pl
my @supportedCDDB     = ();                       # List of supported CDDB tools
my @CDDBPath          = ();                       # List of paths to installed CDDB tools

my $kbps              = 128;                      # Kilobits per second  (used by -b/--bitrate)
my $speed             = 32;                       # CD read rate         (used by -s/--speed)
my $dev               = "/dev/cdrom";             # CDROM device to read (used by -d/--dev)
my $newPWD            = $pwd;                     # New PWD path         (used by -m/--move)

my $ripperFlags       = "";                       # Ripper's accumulated flag line (set later)
my $ripperDEVICE      = "";                       # Device flag (set later)
my $ripperPARANOIA    = "";                       # Paranoia flag (set later)
my $ripperQUIET       = "";                       # Quiet flag (set later)
my $ripperSPEED       = "";                       # Speed flag (set later)
my $ripperVERBOSE     = "";                       # Verbose flag (set later)

my $encoderFlags      = "";                       # Encoder's accumulated flag line (set later)
my $encoderBITRATE    = "";                       # Bitrate flag (set later)
my $encoderPARANOIA   = "";                       # Paranoia flag (set later)
my $encoderQUIET      = "";                       # Quiet flag (set later)

my @flagsToCheck      = ();                       # List of flags to check (accumlated later)

my $numFlags          = @flagsToCheck;            # Number of flags initially
my $mayNotEncode      = "";                       # True if a flag may not initiate encoding alone

my $ripEntireCD       = "";                       # True if entire CD should be ripped
my $checkDatabase     = "";                       # True if CDDB should be used
my $debug             = "";                       # True if debugging should be on
my $eject             = "";                       # True if we should eject
my $generate          = "";                       # True if we should generate a playlist
my $lazy              = "";                       # True if lazy should be done
my $numberThem        = "";                       # True if names should be numbered
my $play              = "";                       # True if play output files should be played
my $quiet             = "";                       # True if tools should be quiet
my $renameTracks      = "";                       # True if manually renaming tracks
my $superlazy         = "";                       # True if superlazy should be done
my $trayclose         = "";                       # True if we should close CD tray
my $verbose           = "";                       # True if tools hsould be verbose
my $wavONLY           = "";                       # True if we should rip to WAV only

my $inSubEncode       = "";                       # True if we enter subroutine encode()

my @trackList         = ();                       # List of tracks to rip
my @renameList        = ();                       # List of names to rename the tracks

my $artist            = "";                       # Artist's name
my $title             = "";                       # Album's title
my $trackno           = "";                       # Number of tracks on CD
my $playlist          = "";                       # Name of the playlist file



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

##########################################################################
#                                                                        #
# SUB: signalHandler                                                     #
#                                                                        #
#   Handlers signals returned from the main system() call that rips the  #
#   requested CD tracks straight to MP3 or Ogg Vorbis.                   #
#                                                                        #
##########################################################################

sub signalHandler {
  if( $inSubEncode ) {
    print OUT "\n\n\nError occurred or kill signal received.\nAborting.\n\n";
  }
  else {
    print( "\n\n\nKill signal received.\nAborting.\n\n" );
  }

  # Close $STDERRFile output file
  close( STDERR );

  # Do not leave the temp MP3/Ogg output file laying around
  system( "rm -f $out" );

  exit(1);
}



##########################################################################
#                                                                        #
# SUB: adjustARGVFormat                                                  #
#                                                                        #
#   Allow flags of the form "--move=DIR" as well as "--move DIR".        #
#   Removes "=" and re-parses ARGV into itself.                          #
#                                                                        #
##########################################################################

sub adjustARGVFormat {
  my $argv = " ";


  foreach (@ARGV) {
    s/=/ /g;
    s/~/$HOME/g;
  }

  for( my $i = 0 ; $i < @ARGV ; $i++ ) {
    $argv = $argv . $ARGV[$i] . " ";
  }

  @ARGV = split( " ", $argv);
}



##########################################################################
#                                                                        #
# SUB: createSupportLists                                                #
#                                                                        #
#   Fills up the supportedRipper, supportedEncoder, and supportedCDDB    #
#   lists with tools that rip can currently manage.                      #
#   The LOWER the integer value the more preferable that tool is,        #
#   meaning more likely it will be set to $ripper or $encoder.           #
#                                                                        #
##########################################################################

sub createSupportLists {
  # Rippers
  $supportedRipper[$cdparanoia]  = "cdparanoia";
  $ripperPath[$cdparanoia]       = "";

  # Encoders
  $supportedEncoder[$gogo]       = "gogo";
  $supportedEncoder[$lame]       = "lame";
  $supportedEncoder[$bladeenc]   = "bladeenc";
  $supportedEncoder[$BladeEnc]   = "BladeEnc";
  $supportedEncoder[$oggenc]     = "oggenc";
  $encoderPath[$gogo]            = "";
  $encoderPath[$lame]            = "";
  $encoderPath[$bladeenc]        = "";
  $encoderPath[$BladeEnc]        = "";
  $encoderPath[$oggenc]          = "";

  # CDDB Tools
  $supportedCDDB[$cddbpl]        = "cddb.pl";
  $CDDBPath[$cddbpl]             = "";
}



##########################################################################
#                                                                        #
# SUB: setupSystem                                                       #
#                                                                        #
#   Makes sure the system is prepared for the rest of the script.        #
#                                                                        #
##########################################################################

sub setupSystem {
  # Redirect stderr output to a temp file $STDERRFile
  close(STDERR) and open(STDERR, ">$STDERRFile") or print("cannot open $STDERRFile\n") and exit(2);

  # Make sure backspace is set to '^H'
  system( "stty erase ^H" );
}



##########################################################################
#                                                                        #
# SUB: findTools                                                         #
#                                                                        #
#   Finds any installed tools on the current system that are listed in   #
#   @supportRipper, @supportedEncoder, and @supportedCDDB. It then       #
#   records the path to that tool as a string in the lists named         #
#   @ripperPath, @encoderPath, and @CDDBPath.                            #
#                                                                        #
##########################################################################

sub findTools {
  # Assume there is nothing available initially
  # Leave $ripper and $encoder "undef" to allow rip to fail properly
  # if no supported ripper or encoder is found on the system

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

        if( $ripper < 0 ) {
          $ripper = $i;
          print STDERR "DEBUG: using ripper $ripperPath[$ripper]\n" if $debug;
        }
      }
    }
  }


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

        if( $encoder < 0 ) {
          $encoder = $i;
          print STDERR "DEBUG: using encoder $encoderPath[$encoder]\n" if $debug;
        }
      }
    }
  }


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

        if( $cddb < 0 ) {
          $cddb = $i;
          print STDERR "DEBUG: using CDDB tool $CDDBPath[$cddb]\n" if $debug;
        }
      }
    }
  }
}



##########################################################################
#                                                                        #
# SUB: checkEncoderFlags                                                 #
#                                                                        #
#   Alters $encoder index depending on whether a specific encoder has    #
#   been flagged in @ARGV. Any such flag is removed from @ARGV.          #
#                                                                        #
##########################################################################

sub checkEncoderFlags {
  my @shortEncoderFlag = ();
  my @longEncoderFlag = ();


  $shortEncoderFlag[$gogo]      = "G";
  $shortEncoderFlag[$lame]      = "L";
  $shortEncoderFlag[$bladeenc]  = "B";
  $shortEncoderFlag[$BladeEnc]  = "B";
  $shortEncoderFlag[$oggenc]    = "O";

  $longEncoderFlag[$gogo]       = "--gogo";
  $longEncoderFlag[$lame]       = "--lame";
  $longEncoderFlag[$bladeenc]   = "--bladeenc";
  $longEncoderFlag[$BladeEnc]   = "--bladeenc";
  $longEncoderFlag[$oggenc]     = "--oggenc";

  # Check ARGV to see if any of the above flags are found
  # If they are, set $encoder to the correct value
  for( my $j = 0 ; $j < @shortEncoderFlag ; $j++ ) {
    for( my $i = 0 ; $i < @ARGV ; $i++ ) {
      my $shortFlag = $shortEncoderFlag[$j];
      my $longFlag  = $longEncoderFlag[$j];

      if( $ARGV[0] =~ /^\-.*$shortFlag.*/ or $ARGV[0] =~ /$longFlag/ ) {
        if( $encoderPath[$j] ) {
          $encoder = $j;

          if( $encoder == $oggenc ) {
            $extension = "ogg";
          }

          print STDERR "DEBUG: specific encoder flagged: $encoderPath[$encoder]\n" if $debug;
        }
        else {
          print( "rip:  No \"$supportedEncoder[$j]\" on your \$PATH though a\n" );
          print( "rip:  -$shortFlag or a $longFlag flag was specified.\n" );
          print( "rip:  Aborting.\n" );
          &abort;
        }

        if( $ARGV[0] =~ /^$longFlag$/ or $ARGV[0] =~ /^\-$shortFlag$/ ) {
          shift( @ARGV );
        }
        else {
          $ARGV[0] =~ s/$shortFlag//g;
        }
      }

      my $temp = shift( @ARGV );

      # Catch debug flag early on
      $debug = "true" if $temp =~ /^\-.*D.*/ or $temp =~ /^\-\-debug$/;

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



##########################################################################
#                                                                        #
# SUB: setFlags                                                          #
#                                                                        #
#   Sets ripper and encoder flag insertion values based on what $ripper  #
#   and $encoder indexes are set to.                                     #
#                                                                        #
##########################################################################

sub setFlags {
  # 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 ";

    $ripperFlags      = " ";            # Default cdparanoia flags
  }
  else {
     print STDERR "DEBUG: UNKNOWN RIPPER!\n" if $debug;
  }


  # Set the encoder's flags
  if( $encoder == $bladeenc or $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     = " --quiet ";
    $encoderBITRATE   = " --bitrate ";
    $encoderPARANOIA  = " --bitrate 160 ";

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

    # Bit of a kludge because we need "-" before "-o"
    $in               = " -o ";
  }
  else {
     print STDERR "DEBUG: UNKNOWN ENCODER!\n" if $debug;
  }
}



##########################################################################
#                                                                        #
# 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 a CD tray close       #
#   are run.                                                             #
#                                                                        #
##########################################################################

sub parseFlags {
  my $item;
  my $check;


  # Exhaustively check args in @ARGV until the array is empty.
  while( @ARGV > 0 and $ARGV[0] =~ /^\-/ ) {
    print STDERR "DEBUG: \@ARGV: @ARGV\n" if $debug;

    # Parse flags of the form:  --eject
    $item =   ( $ARGV[0] =~ /^\-\-all$/       )  &&  "all"
           || ( $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] =~ /^\-\-debug$/     )  &&  "debug"
           || ( $ARGV[0] =~ /^\-\-eject$/     )  &&  "eject"
           || ( $ARGV[0] =~ /^\-g$/           )  &&  "generate"
           || ( $ARGV[0] =~ /^\-\-generate$/  )  &&  "generate"
           || ( $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] =~ /^\-\-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";

    print STDERR "DEBUG: parsing as long flag type: flag is $item\n" if $debug;

    # Parse out value given to bitrate, dev, move, or speed
    if( $item eq "bitrate" ) {
      shift( @ARGV );
      $kbps = $ARGV[0];
      print STDERR "DEBUG: \$kbps: $kbps\n" if $debug;
    }
    elsif( $item eq "device" ) {
      shift( @ARGV );
      $dev = $ARGV[0];
      print STDERR "DEBUG: \$dev: $dev\n" if $debug;
    }
    elsif( $item eq "move" ) {
      shift( @ARGV );
      $newPWD = $ARGV[0];
      $newPWD =~ s/\/\/$//;
      $newPWD =~ s/\/$//;
      print STDERR "DEBUG: \$newPWD: $newPWD\n" if $debug;
    }
    elsif( $item eq "cdspeed" ) {
      shift( @ARGV );
      $speed = $ARGV[0];
      print STDERR "DEBUG: \$speed: $speed\n" if $debug;
    }
    elsif( $item eq "generate" ) {
      shift( @ARGV );
      $playlist = $ARGV[0];
      print STDERR "DEBUG: \$playlist: $playlist\n" if $debug;
    }


    if( $item ne "unknown" ) {
      # Accumulate the $item to be dealt with later on or...
      @flagsToCheck = ( @flagsToCheck, $item );
    }
    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 =~ /a/  )  &&  "all"
               || ( $check =~ /B/  )  &&  "bladeenc"
               || ( $check =~ /c/  )  &&  "cddb"
               || ( $check =~ /D/  )  &&  "debug"
               || ( $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/  )  &&  "quiet"
               || ( $check =~ /r/  )  &&  "rename"
               || ( $check =~ /S/  )  &&  "superlazy"
               || ( $check =~ /t/  )  &&  "trayclose"
               || ( $check =~ /V/  )  &&  "version"
               || ( $check =~ /v/  )  &&  "verbose"
               || ( $check =~ /w/  )  &&  "wav"
               ||                         "default";

        print STDERR "DEBUG: parsing as short flag type: flag is $item\n" if $debug;
        @flagsToCheck = ( @flagsToCheck, $item );
      }
    }

    print STDERR "DEBUG: \@flagsToCheck: @flagsToCheck\n" if $debug;
    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, calling usage/exiting, and printing out version info.  #
#   Many switches only set flags and do not exit the script.             #
#                                                                        #
##########################################################################

sub handleFlags {
  my $answer;
  my $bitrateSet;
  my $okaySpeed;
  my $flag;


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

  $numFlags = @flagsToCheck;
  print STDERR "DEBUG: $numFlags flags to check\n" if $debug;

  foreach $flag ( @flagsToCheck ) {

    if( $flag eq "all" ) {
        $ripEntireCD = "true";
        print STDERR "DEBUG: set to rip entire CD\n" if $debug;
    }
    elsif( $flag eq "bitrate" ) {
        if( not($bitrateSet) ) {
          if( not($kbps =~ /\d{1,3}/) ) {
            print( "rip:  Invalid bitrate ${kbps}.\n" ) and &terminate;
          }

          $encoderFlags = $encoderFlags . $encoderBITRATE . $kbps;
          $bitrateSet   = "true";

          print STDERR "DEBUG: set bitrate to $kbps\n" if $debug;
        }
    }
    elsif( $flag eq "bladeenc" ) {
        if( $encoderPath[$bladeenc] ) {
          $encoder = $bladeenc;
          print STDERR "DEBUG: set encoder to $encoderPath[$encoder]\n" if $debug;
        }
        elsif( $encoderPath[$BladeEnc] ) {
          $encoder = $BladeEnc;
          print STDERR "DEBUG: set encoder to $encoderPath[$encoder]\n" if $debug;
        }
        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";
        $renameTracks  = "";
        $mayNotEncode  = "true";
        print STDERR "DEBUG: set to check CDDB for naming info\n" if $debug;
    }
    elsif( $flag eq "device" ) {
        if( $dev =~ /^\/dev\/\w+/ and ( -e $dev ) ) {
          $ripperFlags = $ripperFlags . $ripperDEVICE . $dev;
          print STDERR "DEBUG: set device to $dev\n" if $debug;
        }
        else {
          print( "rip:  Invalid device: ${dev}. Aborting.\n" ) and &terminate;
        }
    }
    elsif( $flag eq "debug" ) {
        $debug = "true";
        print STDERR "DEBUG: set to leave debug file\n" if $debug;
    }
    elsif( $flag eq "eject" ) {
        $eject        = "true";
        $mayNotEncode = "true";
        print STDERR "DEBUG: set to eject\n" if $debug;
    }
    elsif( $flag eq "generate" ) {
        $generate = "true";
        print STDERR "DEBUG: set to generate playlist $playlist\n" if $debug;
    }
    elsif( $flag eq "gogo" ) {
        if( $encoderPath[$gogo] ) {
          $encoder = $gogo;
          print STDERR "DEBUG: set encoder to $encoderPath[$encoder]\n" if $debug;
        }
        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" or $flag eq "default" ) {
        print STDERR "DEBUG: set to display usage\n" if $debug;
        &usage;
    }
    elsif( $flag eq "lazy" ) {
        $kbps         = 160;
        @flagsToCheck = (@flagsToCheck, "trayclose");
        @flagsToCheck = (@flagsToCheck, "bitrate"  );
        @flagsToCheck = (@flagsToCheck, "cddb"     );
        @flagsToCheck = (@flagsToCheck, "generate" );
        @flagsToCheck = (@flagsToCheck, "all"      );
        @flagsToCheck = (@flagsToCheck, "eject"    );
        $lazy         = "true";
        $superlazy    = "";

        print STDERR "DEBUG: set to do lazy rip\n" if $debug;
    }
    elsif( $flag eq "lame" ) {
        if( $encoderPath[$lame] ) {
          $encoder = $lame;
          print STDERR "DEBUG: set encoder to $encoderPath[$encoder]\n" if $debug;
        }
        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(  not( -e $newPWD )  ) {
          system( "mkdir -p $newPWD" );
          print STDERR "DEBUG: tried to create dir $newPWD\n" if $debug;
        }

        if(  not( -e $newPWD ) or not( -w $newPWD )  ) {
          print( "Either the output directory $newPWD cannot be created\n" );
          print( "or it is not writable. Output will be to $pwd instead.\n" );
        }
        else {
          chdir( "$newPWD" );
          $pwd         = getcwd;
          $DEFAULT_DIR = $pwd;
          print STDERR "DEBUG: set \$pwd to $pwd\n" if $debug;
        }
    }
    elsif( $flag eq "number" ) {
        $numberThem = "true";
        print STDERR "DEBUG: set to number output files\n" if $debug;
    }
    elsif( $flag eq "oggenc" ) {
        if( $encoderPath[$oggenc] ) {
          $encoder   = $oggenc;
          $extension = "ogg";
          print STDERR "DEBUG: set encoder to $encoderPath[$encoder]\n" if $debug;
        }
        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" ) {
        $ripperFlags  = $ripperFlags  . $ripperPARANOIA;

        if( not($bitrateSet) ) {
          $encoderFlags = $encoderFlags . $encoderPARANOIA;
          $bitrateSet   = "true";
        }

        print STDERR "DEBUG: set paranoia\n" if $debug;
    }
    elsif( $flag eq "play" ) {
        $play = "true";
        print STDERR "DEBUG: set to play output files\n" if $debug;
    }
    elsif( $flag eq "quiet" ) {
        $quiet        = "true";
        $verbose      = "";
        $ripperFlags  = $ripperFlags  . $ripperQUIET;
        $encoderFlags = $encoderFlags . $encoderQUIET;

        print STDERR "DEBUG: set to be quiet\n" if $debug;
    }
    elsif( $flag eq "rename" ) {
        $renameTracks  = "true";
        $checkDatabase = "";
        print STDERR "DEBUG: set to prompt for output file names\n" if $debug;
    }
    elsif( $flag eq "cdspeed" ) {
        $okaySpeed = "true";

        if(  not( $speed =~ /^\d+$/ )  ) {
          print( "rip:  Invalid CD read speed: ${speed}. Aborting.\n" ) and &terminate;
        }
        else {
          if( $speed < 0 or $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;

          print STDERR "DEBUG: set CD speed to $speed\n" if $debug;
        }
    }
    elsif( $flag eq "superlazy" ) {
        $kbps         = 160;
        @flagsToCheck = (@flagsToCheck, "trayclose");
        @flagsToCheck = (@flagsToCheck, "bitrate"  );
        @flagsToCheck = (@flagsToCheck, "cddb"     );
        @flagsToCheck = (@flagsToCheck, "generate" );
        @flagsToCheck = (@flagsToCheck, "all"      );
        @flagsToCheck = (@flagsToCheck, "eject"    );
        $lazy         = "true";
        $superlazy    = "true";

        print STDERR "DEBUG: set to be super lazy\n" if $debug;
    }
    elsif( $flag eq "trayclose" ) {
        $trayclose    = "true";
        $mayNotEncode = "true";
        print STDERR "DEBUG: set to close CD tray\n" if $debug;
    }
    elsif( $flag eq "verbose" ) {
        $verbose      = "true";
        $quiet        = "";
        $mayNotEncode = "true";
        $ripperFlags = $ripperFlags . $ripperVERBOSE;

        print STDERR "DEBUG: set to be verbose\n" if $debug;
    }
    elsif( $flag eq "version" ) {
        $mayNotEncode = "true";

        print( "\n########################################################\n"  );
        print( "#                     [>  rip  <]                      #\n" );
        print( "#       Created by Gregory J. 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( "#                 smethegj\@cs.wisc.edu                 #\n" );
        print( "#              http://rip.sourceforge.net              #\n"  );
        print( "########################################################\n\n"  );

        &terminate;
    }
    elsif( $flag eq "wav" ) {
        $wavONLY   = "true";
        $extension = "wav";
        print STDERR "DEBUG: set to encode to WAV only\n" if $debug;
    }
    else {
        print( "rip:  Unknown flag \"${flag}\". It will be ignored.\n" );
        print STDERR "DEBUG: unknown flag $flag.\n" if $debug;
    }
  }


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



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

sub parseTracks {
  my $start;
  my $end;
  my $char;
  my $cdLength;


  # Figure out how many track there are on the CD ($cdLength)
  # just in case the user used the "-a/--all" flag
  if( $ripper == $cdparanoia and (not(($numFlags == 1) and $mayNotEncode) or $lazy) ) {
    $SIG{'INT'} = 'IGNORE';
    system( "cdparanoia -sQ" );
    $SIG{'INT'} = \&catchHandler;
    print STDERR "DEBUG: querying CD for track info\n" if $debug;
  }

  $cdLength = 0;

  open(TEMP, "<$STDERRFile") or print( "rip: cannot open temp file: $!\n" ) and exit(3);

  $_ = " ";

  while( not(eof TEMP) and not($_ =~ /=====/) ) {
    $_ = <TEMP>;
  }

  while( not(eof TEMP) and not($_ =~ /TOTAL/) ) {
    $_ = <TEMP>;

    if( not($_ =~ /TOTAL/ ) ) {
      $cdLength++;
    }
  }

  close( TEMP );


  # Parse the tracks list given after the flags
  if( $ripEntireCD ) {
     @trackList = (1..$cdLength);
  }
  else {
    print STDERR "DEBUG: \@ARGV: @ARGV\n" if $debug;

    while( @ARGV > 0 and $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 );

      print STDERR "DEBUG: updating track list now: @trackList\n" if $debug;
      print STDERR "DEBUG: \@ARGV: @ARGV\n" if $debug;
    }
  }

  if( @trackList != 0 and not($quiet) ) {
    print( "\n\nRipping these track(s):  @trackList\n\n" );
    print STDERR "\n\nRipping these track(s):  @trackList\n\n" if $debug;
  }
}



##########################################################################
#                                                                        #
# SUB: renameIfDuplicate                                                 #
#                                                                        #
#   Takes input in $_ and if the string already exists in @renameList    #
#   a new name is created to replace the name found in $_. The return    #
#   value is left in $_.                                                 #
#                                                                        #
##########################################################################

sub renameIfDuplicate {
  my $tempName;
  my $newNum;
  my $oldNum;
  my $changed;
  my $item;


  $tempName = $_;
  $newNum   = "2";
  $oldNum   = "1";

  print STDERR "DEBUG: \$tempName originally: $tempName\n" if $debug;

  # Do not allow tracks to have the same name
  do {
    $changed  = "";

    foreach $item (@renameList) {
      if( $tempName eq $item ) {
        $tempName =~ s/\.$extension$//;
        $tempName =~ s/_$oldNum$//;
        $tempName = $tempName . "_" . $newNum . "." . $extension;
        $oldNum   = $newNum;
        $newNum++;
        $changed  = "true";
      }
    }

    if( $changed ) {
      print STDERR "DEBUG: \$newNum is $newNum and \$oldNum is $oldNum\n" if $debug;
      print STDERR "DEBUG: \$tempName now: $tempName\n" if $debug;
    }
  } while( $changed );

  $_ = $tempName;
}



##########################################################################
#                                                                        #
# 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 {
  my $result;


  # Get the names to rename MP3s/Oggs to, via CDDB, if so requested (flagged)
  if( not($CDDBPath[$cddb]) ) {
    print( "The -c/--cddb flag you used requires CDDB/CDDB_get's \"cddb.pl\" script.\n" );
    print( "You can get this perl module from http://armin.emx.at/cddb/\n" );
  }
  elsif(  0 == @trackList and not($lazy)  ) {
    print( "Connecting to freedb.freedb.org on port 888...\n\n" );
    $result = system( "$CDDBPath[$cddb] > /dev/tty" );

    if( $result != 0 ) {
      print( "rip:  No info received from CDDB.\n" );
      print( "rip:  Connection may have been refused.\n" );
      print( "rip:  CDDB lookup has been aborted.\n" );
    }
  }
  else {
    print STDERR "DEBUG: dumping CDDB info to $CDDBFile\n" if $debug;

    print( "Connecting to freedb.freedb.org on port 888... " ) if not($quiet);
    system( "echo 1 | $CDDBPath[$cddb] > $CDDBFile" );

    @renameList = ();

    open(INFILE, "<$CDDBFile") or print( "rip:  cannot open cddb file: $!\n") and exit(4);
    &parseCDDBFile;
    close( INFILE );

    system( "rm -f $CDDBFile" );
  }
}



##########################################################################
#                                                                        #
# SUB: parseCDDBFile                                                     #
#                                                                        #
#   Parses the information found in the $CDDBFile file and creates the   #
#   correct information in the global vars $artist, $title, $trackno,    #
#   and @renameList.                                                     #
#                                                                        #
##########################################################################

sub parseCDDBFile {
  my $answer;
  my $doItAgain;
  my $newName;
  my $char;


  # Check for an empty file (CDDB lookup failed if empty)
  if( eof INFILE ) {
    print( "\n\nrip:  No info received from CDDB.\n" );
    print( "rip:  Connection may have been refused.\n" );
    print( "rip:  CDDB lookup has been aborted.\n" );

    if( $lazy ) {
      print( "rip:  Cannot complete lazy rip. Exiting script.\n" );
      &terminate;
    }
    else {
      print( "\nrip:  Abort the rip or attempt to recover (a/R)? " );
      $answer = <STDIN>;
      &abort if $answer =~ /^[Aa]/;

      print( "rip:  Use GENERIC names or setup names MANUALLY (M/g)? " );
      $answer = <STDIN>;

      if( $answer =~ /^[Gg]/ ) {
        print( "rip:  Recovering by using generic naming convention.\n" );
        $checkDatabase = "";
        $renameTracks  = "";
      }
      else {
        $checkDatabase = "";
        $renameTracks  = "true";
      }
    }
  }
  else {
    # Read the artist name (used by beLazy() if it's called)
    while(  not($_ =~ /artist/)  ) {
      $_ = <INFILE>;
    }

    s/\n//g;
    s/ //g;
    s/\&/And/g;
    s/\#/Number/g;
    s/[\(\)\[\]\{\}\'\"\`\.\?\/\\,]//g;

    # May get back "Choose: artist: NAME" instead of "artist: NAME"
    # if forced to choose between CDDB entries: prompt the user to choose
    if( $_ =~ /Choose/ ) {
      print( "\nThere is more than one entry in the CDDB\n\n" );

      $doItAgain = "true";

      while( $doItAgain ) {
        system( "rm -f $CDDBFile" );
        system( "$CDDBPath[$cddb] tee $CDDBFile" );
        print( "\n\nWould you like to use this entry (Y/n)?: ");

        $answer = <STDIN>;

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

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

          s/\n//g;
          s/ //g;
          s/\&/And/g;
          s/\#/Number/g;
          s/[\(\)\[\]\{\}\'\"\`\.\?\/\\,]//g;
        }
      }
    }
    else {
      print( "Done.\n" ) if not($quiet);
    }

    /.*artist:(.*)$/m;
    $artist = $1;

    print STDERR "DEBUG: \$artist name set to $artist\n" if $debug;


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

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

    /.*title:(.*)$/m;
    $title = $1;

    print STDERR "DEBUG: album \$title set to $title\n" if $debug;


    # Read the number of tracks (used by beLazy() to rip entire CD)
    while(  not($_ =~ /trackno/)  ) {
      $_ = <INFILE>;
    }

    chop( $_ );
    s/ //g;
    /.*trackno:(.*)$/m;
    $trackno = $1;

    print STDERR "DEBUG: \$trackno set to $trackno\n" if $debug;


    # Create the @renameList
    for( my $i = 0 ; $i < $trackno ; $i++ ) {
      $_ = <INFILE>;
      chop( $_ );
      s/^track \d{1,2}: //g;
      s/ /_/g;
      s/\&/And/g;
      s/\#/Number_/g;
      s/[\(\)\[\]\{\}\'\"\`\.\?\/\\,:]//g;
      s/__/_/g;
      s/\.$extension$//;

      $_ = $_ . "." . $extension; # Put argument in $_
      &renameIfDuplicate;
      $newName = $_;              # Grab return value from $_

      # Make sure name is less than an arbitrary number of chars long
      if( length( $newName ) > 50 ) {
        while( length($newName) > 50 ) {
          $char = chop( $newName );
        }

        while( $char ne "_" ) {
          $char = chop( $newName );
        }

        $newName = $newName . "." . $extension;
      }

      # Finally we can add the new name to the rename list
      @renameList = ( @renameList, $newName );
    }

    print STDERR "DEBUG: CDDB rename list: @renameList\n" if $debug;
  }
}



##########################################################################
#                                                                        #
# 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 {
  my $answer;
  my $newName;
  my $num;
  my $item;


  if( @trackList > 0 ) {
    print( "\n*****************************************************\n"      );
    print( "Now taking the proper names for the output $extension files.\n" );
    print( "AN EXAMPLE:    Riders On The Storm\n"                           );
    print( "TURNS INTO:    Riders_On_The_Storm.$extension\n\n"              );

    print( "Do you want your CD back for a moment (Y/n)? " );
    chop( $answer = <STDIN> );

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

    print( "\n" );
    my $doItAgain = "true";

    # Prompt the user to complete the rename list
    while( $doItAgain ) {
      @renameList = ();

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

        chop( $newName = <STDIN> );

        $newName =~ s/\.$extension$//;
        $newName =~ s/ /_/g;
        $newName =~ s/\&/And/g;
        $newName =~ s/\#/Number_/g;
        $newName =~ s/\W//g;
        $newName = $newName . "." . $extension;

        $_ = $newName;      # Put argument in $_
        &renameIfDuplicate;
        $newName = $_;      # Grab return value from $_

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

      print( "\nDone.\n\n" );

      $num = 0;

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

      print( "\nAre these the right names (Y/n)?  " );
      chop( $answer = <STDIN> );
      print( "\n" );

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

    print STDERR "DEBUG: manual rename list: @renameList\n" if $debug;

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

    system( "eject --trayclose" );

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



##########################################################################
#                                                                        #
# 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 {
  # Rip the entire CD
  @trackList = (1..$trackno);

  # Setup the output subdir based on the $artist's name and/or album $title
  # Note that $subDir MUST end in "/" so that naming works properly
  if( $superlazy ) {
    $subDir = "$artist/$title/";
  }
  else {
    $subDir = "$artist/";
  }

  $subDir =~ s/\/\//\//g;
  $subDir =~ s/\/\//\//g;

  if(  not( -e "$pwd/$subDir" ) ) {
    system( "mkdir -p $pwd/$subDir" );

    print STDERR "DEBUG: tried to create dir $pwd/$subDir\n" if $debug;

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

  print STDERR "DEBUG: \$pwd/\$subdir/ set to $pwd/$subDir\n" if $debug;
}



##########################################################################
#                                                                        #
# SUB: generatePlaylist                                                  #
#                                                                        #
#   Creates a playlist using either the @renameList or the @trackList.   #
#                                                                        #
##########################################################################

sub generatePlaylist {
  my $trackNum;
  my $nextTrack;
  my $value;


  # Figure out what the playlist file should be named
  # Bow to user if $playlist has been set by -g/--generate flag
  if( $playlist ) {
    open(OUTFILE, ">$playlist") or print("rip:  can't open playlist: $!\n") and exit(5);

    # Create a renameList from trackList if renameList is empty
    if( 0 == @renameList ) {
      foreach $trackNum (@trackList) {
        @renameList = (@renameList, "track" . $trackNum . "." . $extension);
      }
    }

    print STDERR "DEBUG: playlist filename: $playlist\n" if $debug;
  }
  else {
    open(OUTFILE, ">$pwd/$title.playlist") or print("rip:  can't open playlist: $!\n") and exit(5);

    print STDERR "DEBUG: playlist filename: $pwd/$title.playlist\n" if $debug;
  }


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

    if( $numberThem and $value < 10 ) {
      print OUTFILE ( "$pwd/${subDir}0${value}_$nextTrack\n" );
      print STDERR "DEBUG: added $pwd/${subDir}0${value}_$nextTrack\n" if $debug;
    }
    elsif( $numberThem ) {
      print OUTFILE ( "$pwd/${subDir}${value}_$nextTrack\n" );
      print STDERR "DEBUG: added $pwd/${subDir}${value}_$nextTrack\n" if $debug;
    }
    else {
      print OUTFILE ( "$pwd/${subDir}$nextTrack\n" );
      print STDERR "DEBUG: added $pwd/${subDir}$nextTrack\n" if $debug;
    }
  }

  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 {
  my $properName;
  my $track;
  my $result;


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

  # Copy (do not alias!) the STDOUT file descriptor
  open(OUT, ">&STDOUT") or print( "rip:  cannot alias stdout: $!\n" ) and exit(6);

  if( $debug ) {
    print STDERR   "DEBUG: tracklist is:  @trackList\n";
    print STDERR "\nDEBUG: Ripper:        $ripperPath[$ripper]\n";
    print STDERR   "DEBUG: Ripper flags:  $ripperFlags\n";
    print STDERR   "DEBUG: Encoder:       $encoderPath[$encoder]\n";
    print STDERR   "DEBUG: Encoder flags: $encoderFlags\n";
    print STDERR   "DEBUG: CDDB tool:     $CDDBPath[$cddb]\n";
    print STDERR   "DEBUG: Output dir:    $pwd/$subDir\n\n";
  }

  # If in verbose mode, output flags in use by cdparanoia and bladeenc
  if( $verbose ) {
    print OUT "\nRipper:        $ripperPath[$ripper]\n";
    print OUT   "Ripper flags:  $ripperFlags\n";
    print OUT   "Encoder:       $encoderPath[$encoder]\n";
    print OUT   "Encoder flags: $encoderFlags\n";
    print OUT   "CDDB tool:     $CDDBPath[$cddb]\n";
    print OUT   "Output dir:    $pwd/$subDir\n\n";
  }

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

    # Make sure $encoder is quiet
    if( not($encoderFlags =~ /$encoderQUIET/) ) {
      $encoderFlags = $encoderFlags . $encoderQUIET;
    }

    close(STDOUT) and open(STDOUT, ">&STDERR") or print("cannot copy STDERR\n") and exit(2);
  }

  print OUT "\n" if not($quiet) and (@trackList > 0);


  # Rip CD tracks to MP3, Ogg Vorbis, or WAV depending on flags set
  foreach $track ( @trackList ) {
    if( $track =~ /^\d{1,2}$/ ) {
      if( $track < 10 ) {
        print OUT "Now ripping CD track $track to $extension...  " if not($quiet);
        print OUT "\n" if $verbose and not($encoder == $lame);
      }
      else {
        print OUT "Now ripping CD track $track to $extension... "  if not($quiet);
        print OUT "\n" if $verbose and not($encoder == $lame);
      }

      # 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 and $track < 10 ) {
        $properName = "0" . $track . "_" . $properName;
      }
      elsif( $numberThem ) {
        $properName = $track . "_" . $properName;
      }

      # Strip out non-word characters still existant in the filename $properName
      $properName =~ s/\.$extension$//;
      $properName =~ s/ /_/g;
      $properName =~ s/\&/And/g;
      $properName =~ s/\#/Number_/g;
      $properName =~ s/\W//g;
      $properName .= ".$extension";

      $inSubEncode = "true";

      # Do the actual CD rip to...
      if( $wavONLY ) {
        # ...WAV format (blocking system call)
        print STDERR "\nDEBUG: $ripperPath[$ripper] $ripperFlags $track $out && " .
                              " mv $out $pwd/${subDir}$properName\n" if $debug;
        $result = system( "$ripperPath[$ripper] $ripperFlags $track $out && " .
                          " mv $out $pwd/${subDir}$properName" );
      }
      else {
        # ...MP3 or Ogg Vorbis (blocking system call)
        print STDERR "\nDEBUG: $ripperPath[$ripper] $ripperFlags $track - | " .
                              " $encoderPath[$encoder] $encoderFlags $in $out && " .
                              " mv $out $pwd/${subDir}$properName\n" if $debug;
        $result = system( "( $ripperPath[$ripper] $ripperFlags $track - | " .
                          " $encoderPath[$encoder] $encoderFlags $in $out ) && " .
                          " mv $out $pwd/${subDir}$properName" );
      }

      # Call signal handler if system call did not finish error free
      if( $result != 0 ) {
        print STDERR "DEBUG: system call went down on signal $result\n" if $debug;
        &signalHandler;
      }

      # Play the ripped file if so flagged
      if( $play and $track == $trackList[0] ) {
        print OUT "\n" if $verbose;
        print OUT "Playing   $properName\n" if not($quiet);
        system( "xmms --play $pwd/${subDir}$properName &" );
        print STDERR "DEBUG: doing:  xmms --play $pwd/${subDir}$properName & \n" if $debug;
      }
      elsif( $play ) {
        print OUT "\n" if $verbose;
        print OUT "Enqueuing $properName\n" if not($quiet);
        system( "xmms --enqueue $pwd/${subDir}$properName &" );
        print STDERR "DEBUG: doing:  xmms --enqueue $pwd/${subDir}$properName & \n" if $debug;
      }
      else {
        # Do not play anything
      }

      print OUT "Done.\n" if( not($verbose) and not($quiet) and not($play) );
      print OUT "\n" if( $verbose and not($play) );
    }
    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";
       &terminate;
    }
  }

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

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



##########################################################################
#                                                                        #
# 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("  -a   --all                 rip and encode all the tracks on the CD\n"          );
  print("  -b   --bitrate NUM         set bitrate for encoding to NUM kbps\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             force input to come from CD-ROM device DEV\n"       );
  print("  -D   --debug               leave debug info in /tmp/rip-MMDDHHMMSS-stderr\n"   );
  print("  -e   --eject               eject CD tray after doing everything else\n"        );
  print("  -g   --generate DIR/LIST   playlist LIST created in DIR (absolute pathname)\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                uses -t, -a, -c, -g, -b, -m, and -e implicitly,\n"  );
  print("                             creates a playlist in \$PWD in a common format,\n"  );
  print("                             moves output audio files to \$PWD/ArtistName/\n"    );
  print("  -L   --lame                use LAME for MP3 \"encoding\"\n"                    );
  print("  -m   --move DIR            change the \$PWD to 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 do not accept skips on rip\n"      );
  print("  -P   --play                play finished files in XMMS during rip\n"           );
  print("  -Q   --quiet               rip and encode tracks without any visible output\n" );
  print("  -r   --rename              ask for proper name of all tracks before ripping\n" );
  print("  -s   --speed NUM           force CD-ROM device to read at speed NUM\n"         );
  print("  -S   --superlazy           same functionality as -l/--lazy except that all\n"  );
  print("                             output is to \$PWD/ArtistName/AlbumTitle/ instead\n");
  print("  -t   --trayclose           close CD tray before doing anything else\n"         );
  print("  -v   --verbose             print verbose info 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 ~/mp3 6         simliar: moves output MP3 files to dir ~/mp3\n"     );
  print("  rip -s 32 -et 6            close tray, rip track 6, read at 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("  rip -a -g ~/Crash -m ~/mp3 rip all, make playlist ~/Crash, output to ~/mp3/\n" );
  print("\nNORMAL USAGE:\n\n"                                                             );
  print("  rip -c                     print CDDB info rip would use during rip\n\n"       );
  print("  rip -Par                   rip entire CD to MP3, rename, and play\n"           );
  print("  rip -Pater                 similar: also closes and ejects CD tray\n"          );
  print("  rip -Patec -m ~/mp3        similar: sets \$PWD to ~/mp3, renames via CDDB\n"   );
  print("  rip -LPatec -m ~/mp3       similar: makes sure LAME is used\n\n"               );
  print("  rip -SP                    laziness at its best IMHO (must be online)\n"       );
  print("  rip -SP -m ~/mp3           similar: moves/changes \$PWD to this dir: ~/mp3\n"  );
  print("  rip -SPO                   similar: rip CD to Ogg Vorbis instead\n\n\n"        );
  print("  The default output directory is the Present Working Directory (\$PWD).\n"      );
  print("  Use -m/--move to alter/change the \$PWD to a different directory, DIR.\n"      );
  print("  If the DIR you give -m/--move doesn't exists, rip will create it for you.\n\n" );
  print("  The default output format is MP3. To rip to Ogg Vorbis, use the -O/--oggenc\n" );
  print("  flag. To rip to WAV, use the -w/--wav flag.\n\n"                               );
  print("  If no specific encoder is flagged, rip uses the first encoder it finds.\n\n"   );
  print("                     http://rip.sourceforge.net\n\n"                             );

  &terminate;
}



##########################################################################
#                                                                        #
# 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 == $NOT_FOUND ) {
    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 == $NOT_FOUND ) {
    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(8);
}



##########################################################################
#                                                                        #
# SUB: terminate                                                         #
#                                                                        #
#   Exits script normally. Removes debug file if it is not to be kept.   #
#                                                                        #
##########################################################################

sub terminate {
  if( not($debug) ) {
    system( "rm -f $STDERRFile" );
  }

  exit(0);
}



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

# First we make sure ARGV is in the proper format
&adjustARGVFormat;

# Next we setup the supported ripper/encoder lists
&createSupportLists;

# Next we make sure the system is ready for what we are about to do
&setupSystem;

# Make sure we have our tools available on the $PATH first
&findTools;

# Determine if user flagged a prefered encoder in @ARGV.
&checkEncoderFlags;

# Set ripper and encoder tool's flag variables to their proper values
&setFlags;

($ripper == $NOT_FOUND or $encoder == $NOT_FOUND) and &abort;

&usage            if not(@ARGV);
&parseFlags       if @ARGV;
&handleFlags;
&parseTracks;
&cddbRename       if $checkDatabase;
&manualRename     if $renameTracks;
&beLazy           if $lazy;
&generatePlaylist if $generate;
&encode;

close( STDERR );

&terminate;
