#!/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
#    IF THE AFOREMENTIONED CONDITIONS ARE NOT MET.
#
#
#    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 MP3::Info;
use CDDB_get qw( get_cddb get_discids );
use sigtrap 'handler', \&signalHandler, 'normal-signals';



# Some info to differentiate this rip from others 
my $date              = "2001-06-30";             # Date of last modification
my $version           = "0.98";                   # Version number for this script



#
# VARIABLES YOU CAN CHANGE IF YOU FEEL LIKE YOU KNOW WHAT YOU ARE DOING...
#

# Set this so that backspace works correctly for you
# Some common choices include "^H" or "^?"
my $backspace = "^H";


# Default bitrate and read rate
my $kbps              = 128;                      # Kilobits per second  (used by -b/--bitrate)
my $speed             = 32;                       # CD read rate         (used by -s/--speed)


# CDDB and CD device set-up. If you have a DVD player: trying /dev/dvd
my %config;
my $dev            = "/dev/cdrom";                # CDROM device to read (used by -d/--dev)
$config{CD_DEVICE} = $dev;                        # Device that has the audio CD
$config{CDDB_HOST} = "freedb.freedb.org";         # CDDB host to find the server on
$config{CDDB_PORT} = 888;                         # CDDB port the CDDB server is using
$config{CDDB_MODE} = "cddb";                      # CDDB mode can be: cddb, http
$config{input}     = 1;                           # User interaction: 1 = true, 0 = false
$config{HELLO_ID}  = "root my.net rip $version";  # HELLO string to give CDDB server


# Buffer space to use in megabytes
my $buffer            = "5M";                     # Buffer space to use while ripping (uses "dd")


# The smallest number is the default encoder, but really this is order 
# encoders are searched for if you do not specify one at the command line.
# Currently this is set to GOGO, just 'cause.
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



#
# MOST OF THESE FLAGS ARE SET LATER ON BY DIFFERENT SUBROUTINES IN THE
# SCRIPT, SO SETTING THEM HERE WOULD LIKELY DO LITTLE GOOD.
#

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

my $STDERRFile        = "/tmp/rip-$now-stderr";   # DEBUG file (stderr redirected here)

my $DEFAULT_DIR       = getcwd;                   # Default directory to place output into
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;                       # So I can tell if encoders are found in search

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 @supportedEncoder  = ();                       # list of supported encoders
my @encoderPath       = ();                       # List of paths to installed encoders

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 cd tray
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 $tagIt             = "";                       # True if we should tag output files (MP3s only)
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 @properNameList    = ();                       # List of proper names for 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


# Remember what STDERR was before we overwrote it  
open(OLDERR, ">&STDERR") or print("cannot dup stderr: $!\n") and exit(1);

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




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

##########################################################################
#                                                                        #
# SUB: signalHandler                                                     #
#                                                                        #
#   Handles "normal" signals such as SIGINT, SIGQUIT, etc, and simply    #
#   removes the temporary file that is used during ripping before        #
#   exiting the script.                                                  #
#                                                                        #
##########################################################################

sub signalHandler {
  # Were we in the subroutine encode, hence may have gotten a SIGINT?
  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 and STDERR copy
  close( STDERR );
  close( OLDERR );

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

  exit(1);
}



##########################################################################
#                                                                        #
# SUB: adjustARGVFormat                                                  #
#                                                                        #
#   Allows flags of the form "--move=DIR" as well as "--move DIR".       #
#   Does this by replacing '=' with ' ' and re-parses ARGV into itself.  #
#   Also replace '~' with user's $HOME environment variable.             #
#                                                                        #
##########################################################################

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 and supportedEncoder                    #
#   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.           #
#                                                                        #
#   If another ripper or encoder is added to rip, an additional change   #
#   is needed in the subroutine setupFlags() to set ripperXXX or         #
#   encoderXXX flags properly for that 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]          = "";
}



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

sub setupSystem {
  # Maybe the user only has a DVD device but no CDROM only device
  if( (-e ("/dev/dvd")) and !(-e ("/dev/cdrom")) ) { 
    $config{CD_DEVICE} = "/dev/dvd";  # Device that has the audio CD
    print( "rip:  The file /dev/cdrom does not exist, but /dev/dvd does.\n" );
    print( "rip:  Proceeding assuming audio CD is in your DVD drive.\n\n" );
    print( "rip:  If /dev/dvd is the device you are ripping from you could\n" );
    print( "rip:  specify this explicitly with \"rip -d /dev/dvd\"\n" );
  }
  # Sanity check on /dev/cdrom
  elsif( !(-e ("/dev/cdrom")) ) { 
    print( "\nrip:  The file /dev/cdrom does not exist.\n" );
    print( "rip:  /dev/cdrom should be pointing to your CDROM\n" );
    print( "rip:  device, which might be /dev/hdc or the like.\n" );
    print( "rip:  If /dev/hdc were your CDROM device you could\n" );
    print( "rip:  specify this explicitly with \"rip -d /dev/hdc\"\n" );
  }
  else {
    # Do nothing
  }

  # Make sure backspace is set to something that might work
  system( "stty erase $backspace" );
}



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

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;
        }
      }
    }
  }
}



##########################################################################
#                                                                        #
# 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;
          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 indices 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! WHERE IS CDPARANOIA?\n" if $debug;
     print( "rip:  Did not find cdparanoia on your system!\n" );
     exit( 1 );
  }


  # Set the encoder's flags
  if( $encoder == $bladeenc or $encoder == $BladeEnc ) {
    $extension        = "mp3";

    # 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 ) {
    $extension        = "mp3";

    # 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 ) {
    $extension        = "mp3";

    # 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 ) {
    $extension        = "ogg";

    # 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! WHERE IS OGGENC?\n" if $debug;
     print( "rip:  No encoder found on your system!\n" );
     exit( 1 );
  }
}



##########################################################################
#                                                                        #
# 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] =~ /^\-\-tag$/       )  &&  "tag"
           || ( $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];            # Device that has the audio CD
      $config{CD_DEVICE} = $dev;  # Alert CDDB lookup subroutine to this 
      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 =~ /T/  )  &&  "tag"
               || ( $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.  #
#                                                                        #
##########################################################################

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


  # Finished parsing the @ARGV array. Now determine how to flag
  # the $ripper and the $encoder. 

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

  # Hopefully this is self explanatory and reads somewhat like English
  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 "tag" ) {
        $tagIt        = "true";
        print STDERR "DEBUG: set to tag with info\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 tracks 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'} = \&signalHandler;
    print STDERR "DEBUG: queried CD for track info\n" if $debug;
  }

  $cdLength = 0;

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

  $_ = " ";

  # Start counting the lines in the file (output from cdparanoia)
  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 ) {
     if( $cdLength ge 1 ) {
       @trackList = (1..$cdLength);
     }
     else {
       print STDERR "DEBUG: an attempt was made to rip $cdLength tracks\n" if $debug;
       print( "rip:  There are no tracks to rip.\n" );
       print( "rip:  Perhaps there is no CD in the drive $dev\n" );
       exit( 9 );
     }
  }
  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
      # for the tracks he/she wanted ripped

      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;
    }
  }

  # Sort the list NUMERICALLY: $a and $b are passed to the mini-subroutine
  # by perl and are local to that block
  @trackList = sort { $a <=> $b } @trackList;
  print STDERR "DEBUG: sorted trackList is @trackList\n" if $debug;
  
  # Let the user know what tracks we plan to rip
  if( @trackList != 0 and not($quiet) ) {
    print( "\n\nRipping these track(s):  @trackList\n\n" );
    print STDERR "\nDEBUG: Ripping 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 or the files will
  # overwrite each other and we will lose any previous ones of that 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, and -a/--all was not flagged, then just print CDDB #
#   info to the screen.                                                  #
#                                                                        #
##########################################################################

sub cddbRename {
  my $answer;
  my $newName;
  my $char;
  my $n;
  my $result;
  my $diskid;
  my $total;
  my $toc;
  my %cd;


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

  print STDERR "DEBUG: getting CDDB info\n" if $debug;
  print( "Connecting to CDDB..." );

  # Allow CDDB_get.pm to have access to STDERR on the terminal
  # i.e., don't redirect it's error messages to the /tmp/rip-XYZ-stderr file,
  # let the user see them.
  open(ERRFILE, ">&STDERR") or print("cannot dup error file: $!\n") and exit(3);
  open(STDERR, ">&OLDERR" ) or print("cannot move STDERR: $!\n") and exit(3);

  # Use CDDB_get/CDDB to do the dirty work here
  %cd = get_cddb( \%config );

  # Redirect STDERR to the file again
  open(STDERR, ">&ERRFILE") or print("cannot reset STDERR: $!\n") and exit(3);
  close(ERRFILE);

  print( " Done.\n" );

  # Assume no title means CDDB lookup failed 
  if( !defined $cd{title} ) {
    print( "rip:  Tried to do a CDDB lookup for a CD in $dev\n" );
    print( "rip:  No info received from CDDB.\n" );
    print( "rip:  Connection to CDDB may have failed.\n" );
    print( "rip:  CDDB lookup has been aborted.\n" );

    # Let user choose how to handle this failure if possible
    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 {
    # If there are no track args and we aren't just being lazy, then
    # the user wanted to know what CDDB entry rip would have used if
    # the rip had actually proceeded
    # Otherwise, grab the info and get back to ripping
    if(  0 == @trackList and not($lazy)  ) {
      print( "Artist:   $cd{artist}\n" );
      print( "Title:    $cd{title}\n" );
      print( "Category: $cd{cat}\n\n" );

      $n = 1;

      foreach my $item ( @{$cd{track}} ) {
        if( $n < 10 ) {
          print( "Track  $n: $item\n" );
        }
        else {
          print( "Track $n: $item\n" );
        }

        $n++;
      }
    }
    else {
      # All non-word chars are removed in subroutine beLazy() to produce
      # the subdirectory names like "TheDoors" from "The Doors"
      # and are NOT removed here because the "-T/--tag" needs full, proper names

      $artist  = $cd{artist};
      $title   = $cd{title};
      $trackno = $cd{tno};

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

      $n = 1;
      @renameList = ();

      # Create the @renameList
      foreach my $item ( @{$cd{track}} ) {
        # Remember what the name originally was
        $properNameList[ $n - 1 ] = $item;

        $_ = $item;  
        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
        # so that it does not overflow an inodes' max filename length
        # This is easily done by classical music CDDB entries!
        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[ $n - 1 ] = $newName;

        $n++;
      }
    }	

    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 there exists a non-empty trackList, prompt for names for that list
  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> );

    # This won't work without an "eject" version >= 2.0.x or so
    if( not($answer =~ /^[Nn]/) ) {
      system( "eject --cdrom" );
    }

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

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

      foreach $num (@trackList) {
        # Pretty printing (aligns the >'s)
        if( $num < 10 ) {
          print( "<Rename track $num >  " );
        }
        else {
          print( "<Rename track $num>  " );
        }

        chop( $newName = <STDIN> );

        # Remember what the user originally entered
        $properNameList[ $num - 1 ] = $newName;

        # Alter name given so that it's in a nice format
        $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[ $num - 1 ] = $newName;
      }

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

      $num = 0;

      # Print the list we formed to the user
      for( my $i = 0 ; $i < @properNameList ; $i++ ) {
	if( $properNameList[$i] ) {      
          $num = $i + 1;
          print( "Track $num: $properNameList[$i]\n" );
	}
      }

      # Confirm the list with the user
      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 info, make 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 {
  my $tempArtist = $artist;
  my $tempTitle  = $title;

  
  # Alter the copies of $artist and $title, making them useful for dir names
  $tempArtist =~ s/ //g;
  $tempArtist =~ s/\&/And/g;
  $tempArtist =~ s/\#/Number/g;
  $tempArtist =~ s/[\(\)\[\]\{\}\'\"\`\.\?\/\\,]//g;
  $tempTitle  =~ s/ //g;
  $tempTitle  =~ s/\&/And/g;
  $tempTitle  =~ s/\#/Number/g;
  $tempTitle  =~ s/[\(\)\[\]\{\}\'\"\`\.\?\/\\,]//g;

  # 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 = "$tempArtist/$tempTitle/";
  }
  else {
    $subDir = "$tempArtist/";
  }

  $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;
  my $titleTemp;


  # 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 {
    $titleTemp = $title;
    $titleTemp =~ s/ //g;
    
    open(OUTFILE, ">$pwd/$titleTemp.playlist") or print("rip:  can't open playlist: $!\n") and exit(5);

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


  # Create playlist file using a common format
  for( my $i = 0 ; $i < @renameList ; $i++ ) {
    # If no manual rename or CDDB use, renameList created using trackNum above
    $nextTrack = $renameList[$i];
    $value     = $i + 1;

    $nextTrack =~ s/\.$extension$//;
    $nextTrack =~ s/ /_/g;
    $nextTrack =~ s/\W//g;
    $nextTrack .= ".$extension";

    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 $encoderFlagsPrefix;
  my $song;
  my $trackName;
  my $track;
  my $result;
  my $answer;
  my $year;
  my $comment   = "";
  my $commentIt = "";
  my $genre;


  # Used by Ogg Vorbis tagging code so that it can swap in
  # different tagging args, but have the same prefix each time.
  $encoderFlagsPrefix = $encoderFlags;

  print "\nNeed Tag Info:\n" if $tagIt;

  # Prompt for tag info if $artist or $title not set
  if( $tagIt and (!$artist or !$title) ) {
    print( "Artist?  -> " );
    chop( $artist  = <STDIN> );
    print( "Album?   -> " );
    chop( $title   = <STDIN> );
    print STDERR "DEBUG: \$artist set to $artist\n";
    print STDERR "DEBUG: \$title set to $title\n";
  }
  elsif( $tagIt and $artist and $title ) {
    print( "Artist?  -> $artist\n" );
    print( "Album?   -> $title\n" );
    print STDERR "DEBUG: \$artist set to $artist\n";
    print STDERR "DEBUG: \$title set to $title\n";
  }
  else {
    print STDERR "DEBUG: no tagging will be done\n";
  }

  # Prompt for tag info that is only needed by MP3s
  if( $tagIt and $extension eq "mp3" ) {
    print( "Year?    -> " );
    chop( $year    = <STDIN> );
    print( "Genre?   -> " );
    chop( $genre   = <STDIN> );
    print STDERR "DEBUG: \$year set to $year\n";
    print STDERR "DEBUG: \$genre set to $genre\n";
  }

  # Ask user if they want to use the comment tag 
  # Some people just do not want to use it
  # because it requires user intervention during the rip
  if( $tagIt ) {
    print( "\nWould you like to tag each track with comments (y/N)? " );
    chop( $answer = <STDIN> );

    if( $answer =~ /^[Yy]/ ) {
      $commentIt = "true";
    }
  }
  
  # Setup rip's temp filename
  $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: 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   "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;
    }

    # Redirect STDOUT to STDERR (which is itself redirected to a file)
    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}$/ ) {
      # Prompt for comment tag for this track if user wants to
      if( $tagIt and $commentIt ) {
        print OUT "\nComment tag for track $track? -> ";
        chop( $comment = <STDIN> );
	print OUT "\n";
      }

      # Let the user know what is being ripped this iteration
      if( $track < 10 ) {
        # This print has an extra space at the end for pretty printing purposes
        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);
      }

      # Add track numbering to comments
      if( $tagIt and $numberThem and $comment ) {
	$comment = "Track #${track}: " . $comment;
      }
      elsif( $tagIt and $numberThem ) {
        $comment = "Track #${track}"; 
      }
  
      # Get the track's proper name (output file name)
      if( $renameTracks or $checkDatabase ) {
        $trackName = $renameList[ $track - 1 ];
      }
      else {
        $trackName = "track" . $track . "." . $extension;
      }

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

      # Strip out non-word characters still existant in the 
      # filename $trackName perhaps due to an odd manual rename
      # or a forced filename length reduction
      $trackName =~ s/\.$extension$//;
      $trackName =~ s/ /_/g;
      $trackName =~ s/\W//g;
      $trackName .= ".$extension";

      # Determine the proper human-readable name for the song
      if( @properNameList > 0 ) {
        $song = $properNameList[ $track - 1 ];
      }
      else {
        $song = $trackName;
      }

      $song =~ s/_/ /g;
      $song =~ s/\.$extension$//;

      # If we are tagging an Ogg Vorbis file, set up the flags now (oggenc only)
      if( $tagIt and $extension eq "ogg" ) {
        $encoderFlags = $encoderFlagsPrefix . " -t \"" . $song . "\" -a \"" . 
	                  $artist . "\" -l \"" . $title . "\" -c \"" . 
			  $comment . "\"";
      }

      $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}$trackName\n" if $debug;
        $result = system( "$ripperPath[$ripper] $ripperFlags $track $out && " .
                          " mv $out $pwd/${subDir}$trackName" );
      }
      else {
        # ...MP3 or Ogg Vorbis (blocking system call)
        print STDERR "\nDEBUG: $ripperPath[$ripper] $ripperFlags $track - | " .
                              " dd ibs=$buffer | " .
                              " $encoderPath[$encoder] $encoderFlags $in $out && " .
                              " mv $out $pwd/${subDir}$trackName\n" if $debug;
        $result = system( "( $ripperPath[$ripper] $ripperFlags $track - | " .
                          " dd ibs=$buffer | " .
                          " $encoderPath[$encoder] $encoderFlags $in $out ) && " .
                          " mv $out $pwd/${subDir}$trackName" );
      }

      # 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;
      }

      # Tag an MP3 with ID3v1 info if flagged
      if( $tagIt and $extension eq "mp3" ) {
        my $file = "$pwd/${subDir}$trackName";

        print STDERR "DEBUG: tagging \"$file\"\n" if $debug;
        print STDERR "DEBUG: \"$song\", \"$artist\", \"$title\", \"$year\", 
                             \"$comment\", \"$genre\"\n" if $debug;

        set_mp3tag( $file, $song, $artist, $title, $year, $comment, $genre );
      }

      # Play the ripped file if so flagged
      if( $play and $track == $trackList[0] ) {
        print OUT "\n" if $verbose;
        print OUT "Playing   $song\n" if not($quiet);
        system( "xmms --play $pwd/${subDir}$trackName &" );
        print STDERR "DEBUG: doing:  xmms --play $pwd/${subDir}$trackName & \n" if $debug;
      }
      elsif( $play ) {
        print OUT "\n" if $verbose;
        print OUT "Enqueuing $song\n" if not($quiet);
        system( "xmms --enqueue $pwd/${subDir}$trackName &" );
        print STDERR "DEBUG: doing:  xmms --enqueue $pwd/${subDir}$trackName & \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 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("  -T   --tag                  tag the output file with artist/song/album info\n"   );
  print("  -v   --verbose              print lots of 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/dvd 6           rip using /dev/dvd as the input device\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 that 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 -STP                    laziness at its best IMHO (must be online)\n"        );
  print("  rip -STP -m ~/mp3           similar: moves/changes \$PWD to this dir: ~/mp3\n"   );
  print("  rip -STPO                   similar: makes sure oggenc is used\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("  If no specific encoder is flagged, rip uses the first encoder it finds.\n"       );
  print("  Encoders are searched for in this order: gogo, lame, bladeenc, oggenc.\n"        );
  print("  Though you can always alias rip to a specific encoder if you wish.\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;

# If either a ripper or an encoder is not found, abort the rip
($ripper == $NOT_FOUND or $encoder == $NOT_FOUND) and &abort;

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

&usage            if not(@ARGV);

&parseFlags       if @ARGV;
&handleFlags;

&parseTracks;

&cddbRename       if $checkDatabase;
&manualRename     if $renameTracks;
&beLazy           if $lazy;
&generatePlaylist if $generate;

&encode;

close( STDERR );
close( OLDERR );

&terminate;

