#!/usr/bin/env perl
########################################################################
#
# LICENSE
#
# Copyright (C) 2005 Felix Suwald
#
#
# 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.
#
#
########################################################################
#
# CODE
#
# ripit.pl - Rips CD Audio and encodes files in the following steps:
#           1) Query CDDB data for album/artist/track info
#           2) Rip the audio files from the CD
#           3) Encode the wav files
#           4) ID3 tag the encoded files
#           5) Optional: Create a playlist (M3U) file.
#           6) Optional: Prepares and sends a CDDB submission.
#
# Version 3.6.0 - June 16th 2007 - Felix Suwald, thanks for input:
#                                  C. Blank
#                                  G. Edwards
#                                  G. Ross
# Version 3.5.0 - June 6th 2006 - Felix Suwald, credits to
#                                 E. Riesebieter (deb-package)
#                                 C. Walter (normalize option)
#                                 S. Warten (general support & loop)
#                                 S. Warten (signal handling)
# Version 3.4 - December 3rd 2005 - Felix Suwald, credits to
#                                   M. Bright (infolog file)
#                                   M. Kaesbauer (lcdproc) and
#                                   C. Walter (config file).
# Version 2.5 - November 13th 2004 - Felix Suwald
# Version 2.2 - October 18th 2003 - Mads Martin Joergensen
# Version 1.0 - February 16th 1999 - Simon Quinn
#
#
########################################################################
#
# User configurable variables:
# Keep these values and save your own settings
# in the config file with option --save!
#
my $cddev     = "/dev/cdrom";# Path of audio CD device.
my $outputdir = "";         # Where the sound should go to.
my $ripopt    = "";         # Options for audio CD ripper.
my $ripper    = 1;          # 0 - dagrab, 1 - cdparanoia,
                            # 2 - cdda2wav, 3 - tosha, 4 - cdd.
my @coder     = (0);        # 0 - Lame, 1 - Oggenc, 2 - Flac,
                            # 3 - Faac, comma separated list.
my $bitrate   = 128;        # Bitrate for lame, if --vbrmode used,
                            # bitrate is equal to the -b option.
my $maxrate   = 0;          # Bitrate for lame using --vbrmode,
                            # maxrate is equal to the -B option.
my @quality   = (5,3,5,100);# Quality for lame in vbr mode (0-9),
                            # best quality = 0, quality for oggenc
                            # (1-10) best = 10; or compression
                            # level for Flac (0-8), lowest = 0.
my $qualame   = 5;          #
my $qualoggenc= 3;          #
my $quaflac   = 5;          #
my $quafaac   = 100;        #
my $lameopt   = "";         #
my $oggencopt = "";         #
my $flacopt   = "";         #
my $faacopt   = "";         #
my $lcd       = 0;          # Use lcdproc (1 yes, 0 no).
my $chars     = "off";      # Exclude special chars in file names.
my $verbose   = 3;          # Normal output: 3, no output: 0, minimal
                            # output: 1, normal without encoder msgs: 2,
                            # normal: 3, verbose: 4, extremly verbose: 5
my $commentag = "";         # Comment ID3 tag.
my $genre     = "";         # Genre of Audio CD for ID3 tag.
my $year      = "";         # Year of Audio CD for ID3 tag.
my $utftag    = 1;          # Keep Lame-tags in utf or decode them to
                            # ISO8895-1 (1 yes, 0 no).
my $eject     = 0;          # Eject the CD when done (1 yes, 0 no).
my $ejectcmd = "eject";     # Command to use for eject
my $ejectopt = "{cddev}";   # Options to above
my $halt      = 0;          # Shutdown machine when finished
                            # (1 yes, 0 no).
my $nice      = 0;          # Set nice for encoding process.
my $nicerip   = 0;          # Set nice for ripping process.
my $savenew   = 0;          # Saved passed parameters to new config
                            # file (1 yes, 0 no).
my $savepara  = 0;          # Save parameters passed in config file
                            # (1 yes, 0 no).
my $config    = 1;          # Use config file to read paramaters
                            # (1 yes, 0 no).
my $submission= 1;          # Send CDDB submission
                            # (1 yes, 0 no).
my $parano    = 1;          # Use paranoia mode in cdparanoia
                            # (1 yes, 0 no).
my $playlist  = 1;          # Do create the m3u playlist file
                            # (1 yes, 0 no, 2 no full path in filename).
my $resume    = 0;          # Resume a previously started session
                            # (1 yes, 0 no).
my $infolog   = "";         # InfoLog
                            # (filename)
my $interaction= 1;         # If 0 do not ask anything, take the 1st
                            # CDDB entry found or use default names!
                            # (1 yes, 0 no).
my $underscore= 0;          # Use _ instead of spaces in filenames
                            # (1 yes, 0 no).
my $lowercase = 0;          # Lowercase filenames
                            # (1 yes, 0 no).
my $archive   = 0;          # Save CDDB files in ~/.cddb dir
                            # (1 yes, 0 no).
my $mirror    = "freedb";   # The host (a freedb mirror) that
                            # shall be used instead of freedb.
my $transfer  = "cddb";     # Transfer mode, cddb or http, will set
                            # default port to 8880 or 80 (for http).
my $vbrmode   = "";         # Variable bitrate, only used with lame,
                            # (new or old), see lame-manpage.
my $proto     = 6;          # CDDB protocol level for CDDB query.
my $proxy     = "";         # Set proxy.
my $CDDB_HOST = "freedb.org"; # Set cddb host
my $mailad    = "";         # Users return e-mail address.
my @core      = 1;          # Number of encoding processes for each box
my @sshlist   = ();         # List of remote machines.
my $scp       = 0;          # Use scp to access files (1 yes, 0 no).
my $local     = 1;          # Encode on locale machine (1 yes, 0 no).
my $wav       = 0;          # Keep the wav files (1 yes, 0 no).
my $encode    = 1;          # Encode the wav files (1 yes, 0 no).
my $rip       = 1;          # Rip the CD files (1 yes, 0 no).
my $cdtoc     = 0;          # Create a cd.toc for CDRDAO (1 yes, 0 no).
my $loop      = 0;          # Restart ripit as soon as a new CD is
                            # inserted. This option implies that the CD
                            # is ejected (option --eject)!
my $ghost     = 0;          # Check and extract ghost songs from all
                            # tracks (1 yes, 0 no).
my $prepend   = 2;          # Extend ghost songs by 2 sedonds at the
                            # beginning.
my $extend    = 2;          # Extend ghost songs by 2 sedonds at the
                            # end.
my $dpermission= "0777";    # Directory permissions.
my $fpermission= "0644";    # Audio and text file permissions.
my $md5sum    = 0;          # Generate MD5 sums for evry sound file
                            # not deleted (1 yes, 0 no).
my $multi     = 0;          # Not yet official. Do NOT use!
#
#
#
# Directory and track template variables:
# Contains the format the track names will be written as.
# The '" and "' are important and must surround the template.
# Example variables to use are: $tracknum, $album, $artist, $genre,
# $trackname or $year.
# E.g. example setting of $tracktemplate produces a trackname of
# "07 The Game - Swandive" .
# $tracktemplate  = '"$tracknum $trackname - $artist"';
#
my $dirtemplate = '"$artist - $album"';
my $tracktemplate  = '"$tracknum $trackname"';
#
#
# lcdproc settings:
#
my $lcdhost   = "localhost";
my $lcdport   = "13666";
my $lcdline1  = "   [initializing]   ";
my $lcdline2  = " ripit-lcd-module     ";
my $lcdline3  = "          2005 ";
my $lcdoline1 = "";
my $lcdoline2 = "";
my $lcdoline3 = "";
my $lcdproc;
my $lcdtrackno       = 0;
#
#
#
# normalize settings:
#
my $normalize = 0;          # normalize CD, needs 'normalize' in $path
my $normopt   = "-bv";      # options for normalize
my $subdir    = "Unsorted";
#
#
########################################################################
#
# No User configurable parameters below here.
#
########################################################################
#
use strict;
use Fcntl;
use Getopt::Long qw(:config no_ignore_case);
use IO::Socket;
#use warnings;
#use Color::Output;
#Color::Output::Init;
#
# Initialize paths.
#
my $homedir = "$ENV{HOME}";
my $workdir = "$ENV{PWD}";
my $usename = "$ENV{USER}";
my $hostnam = "$ENV{HOSTNAME}";
my $charset = "$ENV{G_FILENAME_ENCODING}";
if($charset =~ /UTF-8/) {
   $charset = "UTF-8";
}
elsif($charset  =~ /ISO-8859-15/) {
   $charset = "ISO-8859-15";
}
else {
   $charset = "ISO-8859-1";
}
#print ($_,$ENV{$_},"\n") foreach (keys %ENV);
#
# Initialise global variables.
#
my $categ            = "";  # CDDB category
my $cddbid           = 0;   # Needed in several subroutines
my $lameflag         = 0;   # Flag to check if lame is used.
my $trackselection   = "";  # Passed from command line
my @tracksel         = ();  # Array of all track numbers, including
                            # those not explicitly stated.
my @seltrack         = ();  # Array of all track numbers, including
                            # those not explicitly stated and ghost
                            # songs found by ripper needed by encoder.
my @framelist        = ();  # Needed in several subroutines
my @secondlist       = ();  # Needed in several subroutines
my @tracklist        = ();  # Needed in several subroutines
my @tracktags        = ();  # Needed in several subroutines
my %cd               = ();  # Hash of all CD-data.
my $cddbsubmission   = 2;   # If zero then data for CDDB submission is
                            # incorrect, if 1: submission OK, if 2: CDDB
                            # entry not changed (edited)
my $wpreset          = "";  # Preset written into config file.
my $wcoder           = "";  # Use a comma separated string to write the
                            # coder-array into the config file.
my $wcore            = "";  # Use a comma separated string to write the
                            # core-array into the config file.
my $wsshlist         = "";  # As above for the list of remote machines.
my $sshflag          = 0;   # Ssh encoding OK if sshflag == 1.
my %sshlist          = ();  # Hash of remote machines.
my $version          = "3.6.0";
my $hiddenflag       = 0;
my $logpath          = "";  # Used with not *to-use* option --multi,
                            # is one level below sound-dir.
my $logfile          = "";  # Used with not *to-use* option --multi,
                            # is the "device-log"-file in `pwd`.
my $help             = 0;   # Print help and exit if 1.
my $printver         = 0;   # Print version and exit if 1.
my @delname          = ();  # List of tracknames being processed, i.e.
                            # ready for deletion.
my @skip             = ();  # List of merged tracks.
#
#
# Initialize subroutines without ().
#
sub check_bitrate;
sub check_cddev;
sub check_chars;
sub check_lame;
sub check_vbrmode;
sub choose_genre;
sub extract_comm;
sub get_rev;
sub init_mod;
sub init_var;
sub lame_preset;
sub main_sub;
sub skip_tracks;
#
#
# Define the variables which catch the command line input.
# The p stands for passed (from command line).
my (
   $parchive,       $pbitrate,       $pmaxrate,         $PCDDB_HOST,
   $pcddev,         $pcdtoc,         @pcoder,           $pcommentag,
   $pconfig,        $pdirtemplate,   $ptracktemplate,   $peject,
   $pencode,        $pfaacopt,       $pflacopt,         $plameopt,
   $poggencopt,     $pgenre,         $phalt,            $pinfolog,
   $pinteraction,   $plcdhost,       $plcdport,         $plcd,
   $plocal,         $ploop,          $plowercase,       $pmirror,
   $pmailad,        $pmulti,         $pnice,            $pnormalize,
   $pnormopt,       $poutputdir,     $pparano,          $pplaylist,
   $ppreset,        $pproto,         $pproxy,           @pquality,
   $pripopt,        $prip,           $pripper,          $psavenew,
   $psavepara,      $pscp,           @psshlist,         $psubdir,
   $psubmission,    $ptransfer,      $punderscore,      $putftag,
   $pvbrmode,       $pverbose,       $pwav,             $pyear,
   $presume,        $pmerge,         $pghost,           $pprepend,
   $pextend,        $pejectopt,      $pejectcmd,        $pdpermission,
   $pfpermission,   $pmd5sum,        $pnicerip,         @pcore,
);
#
# Get the parameters from the command line.
# available:     A       E F    IjJkK       O   Q             X Y
# already used: a bBcCdDe f gGhi     lLmMnNo pPq rRsStTuUvVwWx y zZ
#
GetOptions(
   "archive|a!"             => \$parchive,
   "bitrate|b=s"            => \$pbitrate,
   "maxrate|B=i"            => \$pmaxrate,
   "chars|W:s"              => \$chars,
   "cddbserver|C=s"         => \$PCDDB_HOST,
   "cdtoc=i"                => \$pcdtoc,
   "config!"                => \$pconfig,
   "coder|c=s"              => \@pcoder,
   "comment=s"              => \$pcommentag,
   "core=s"                 => \@pcore,
   "device|d=s"             => \$pcddev,
   "dirtemplate|D=s"        => \$pdirtemplate,
   "eject|e!"               => \$peject,
   "ejectcmd=s"             => \$pejectcmd,
   "ejectopt=s"             => \$pejectopt,
   "encode!"                => \$pencode,
   "extend=i"               => \$pextend,
   "faacopt=s"              => \$pfaacopt,
   "flacopt=s"              => \$pflacopt,
   "lameopt=s"              => \$plameopt,
   "oggencopt=s"            => \$poggencopt,
   "genre|g=s"              => \$pgenre,
   "ghost|G!"               => \$pghost,
   "halt"                   => \$phalt,
   "help|h"                 => \$help,
   "infolog=s"              => \$pinfolog,
   "interaction|i!"         => \$pinteraction,
   "lcd!"                   => \$plcd,
   "lcdhost=s"              => \$plcdhost,
   "lcdport=s"              => \$plcdport,
   "lowercase|l!"           => \$plowercase,
   "local!"                 => \$plocal,
   "loop!"                  => \$ploop,
   "md5sum!"                => \$pmd5sum,
   "merge=s"                => \$pmerge,
   "mirror|m=s"             => \$pmirror,
   "mail|M=s"               => \$pmailad,
   "multi"                  => \$pmulti,
   "nice|n=s"               => \$pnice,
   "nicerip=s"              => \$pnicerip,
   "normalize|N!"           => \$pnormalize,
   "normopt|z=s"            => \$pnormopt,
   "subdir=s"               => \$psubdir,
   "outputdir|o=s"          => \$poutputdir,
   "dpermission=s"          => \$pdpermission,
   "fpermission=s"          => \$pfpermission,
   "playlist|p:s"           => \$pplaylist,
   "prepend=i"              => \$pprepend,
   "preset|S=s"             => \$ppreset,
   "proxy|P=s"              => \$pproxy,
   "protocol|L=i"           => \$pproto,
   "quality|q=s"            => \@pquality,
   "resume|R"               => \$presume,
   "rip!"                   => \$prip,
   "ripper|r=s"             => \$pripper,
   "ripopt=s"               => \$pripopt,
   "savenew"                => \$psavenew,
   "save"                   => \$psavepara,
   "scp"                    => \$pscp,
   "sshlist=s"              => \@psshlist,
   "submission|s!"          => \$psubmission,
   "tracktemplate|T=s"      => \$ptracktemplate,
   "transfer|t=s"           => \$ptransfer,
   "underscore|u!"          => \$punderscore,
   "utftag|U!"              => \$putftag,
   "vbrmode|v=s"            => \$pvbrmode,
   "verbose|x=i"            => \$pverbose,
   "version|V"              => \$printver,
   "year|y=i"               => \$pyear,
   "wav|w!"                 => \$pwav,
   "disable-paranoia|Z"     => \$pparano,
)
or exit print_usage();
#
# Evaluate the command line parameters if passed. We need to do it this
# way, because passed options have to be saved (in case user wants to
# save them in the config file) before config file is read to prevent
# overriding passed options with options from config file. The passed
# options shall be stronger than the config file options!
# Problems arrise with options that can be zero. Because a usual if-test
# can not distinguish between zero or undef, use the defined-test!
#
# First for the normal options, e. g. options which are never zero.
#
# Check for array @coder will be done in the subroutine!
$faacopt = $pfaacopt if($pfaacopt);
$flacopt = $pflacopt if($pflacopt);
$lameopt = $plameopt if($plameopt);
$oggencopt = $poggencopt if($poggencopt);
$oggencopt = " " unless($oggencopt); # Oops, only to prevent warnings...
$CDDB_HOST = $PCDDB_HOST if($PCDDB_HOST);
$cddev = $pcddev if($pcddev);
# Get the default "no-argument" values.
$chars = ":*#?\$!" if($chars eq "");
# On the other hand, clean the initialization if operator
# wants to save the the default "no-argument" values.
if(defined $psavenew) {
   $chars = "" if($chars eq "off" && $psavenew == 1);
}
$commentag = $pcommentag if($pcommentag);
$dirtemplate = $pdirtemplate if($pdirtemplate);
$tracktemplate = $ptracktemplate if($ptracktemplate);
$halt = $phalt if($phalt);
$infolog = $pinfolog if($pinfolog);
$lcdhost = $plcdhost if($plcdhost);
$lcdport = $plcdport if($plcdport);
$mailad = $pmailad if($pmailad);
$mirror = $pmirror if($pmirror);
$normopt = $pnormopt if($pnormopt);
$outputdir = $poutputdir if($poutputdir);
my $preset = $ppreset if($ppreset);
$ripopt = $pripopt if($pripopt);
$dpermission = $pdpermission if($pdpermission);
$fpermission = $pfpermission if($pfpermission);
$proto = $pproto if($pproto);
$proxy = $pproxy if($pproxy);
# Check for variable $psshlist will be done in the subroutine!
# Check for variable $pcore will be done in the subroutine!
$transfer = $ptransfer if($ptransfer);
$vbrmode = $pvbrmode if($pvbrmode);
$year = $pyear if($pyear);
#
# Options which might be zero.
$bitrate = $pbitrate if($pbitrate);
$cdtoc = $pcdtoc if defined $pcdtoc;
$extend = $pextend if defined $pextend;
$genre = $pgenre if defined $pgenre;
$md5sum = $pmd5sum if defined $pmd5sum;
$maxrate = $pmaxrate if defined $pmaxrate;
$nice = $pnice if defined $pnice;
$nicerip = $pnicerip if defined $pnicerip;
$parano = 0 if defined $pparano;
$playlist = $pplaylist if defined $pplaylist;
$playlist = 1 if($playlist eq "");
$prepend = $pprepend if defined $pprepend;
$resume = $presume if defined $presume;
$ripper = $pripper if defined $pripper;
$savepara = $psavepara if defined $psavepara;
$savenew = $psavenew if defined $psavenew;
$scp = $pscp if defined $pscp;
$verbose = $pverbose if defined $pverbose;
#
# And for the negatable options.
$archive = $parchive if defined $parchive;
$config = $pconfig if defined $pconfig;
$encode = $pencode if defined $pencode;
$eject = $peject if defined $peject;
$ejectcmd = $pejectcmd if defined $pejectcmd;
$ejectopt = $pejectopt if defined $pejectopt;
$ghost = $pghost if defined $pghost;
$interaction = $pinteraction if defined $pinteraction;
$lcd = $plcd if defined $plcd;
$local = $plocal if defined $plocal;
$loop = $ploop if defined $ploop;
$lowercase = $plowercase if defined $plowercase;
$multi = $pmulti if defined $pmulti;
$normalize = $pnormalize if defined $pnormalize;
$rip = $prip if defined $prip;
$submission = $psubmission if defined $psubmission;
$underscore = $punderscore if defined $punderscore;
$utftag = $putftag if defined $putftag;
$wav = $pwav if defined $pwav;
#
#
# To have the version printed first of all other (warning-) messages,
# find out if verbosity is set off or not, either by command line or
# by config file.
#
my $ripdir = $homedir."/.ripit/config";
if(-r "$ripdir" && $config == 1) {
   open(CONF, "$ripdir") || print "Can not read config file!\n";
   my @conflines = <CONF>;
   close CONF;
   my @verbose = grep(s/^verbose=//, @conflines);
   $verbose = $verbose[0] unless defined $pverbose;
   chomp $verbose;
}
#
print "\n\n\nRipIT version $version.\n" if($verbose >= 1);
log_info("RipIT version $version infolog-file.\n");
#
#
# Then, before doing anything, check if the necessary modules are
# installed properly.
#
init_mod;
#
#
# Do some checks before writing a new config file (if wanted):
#
# First check if arguments of option merge are OK.
my @dummy = skip_tracks if($pmerge);
#
# Then the options that will be written to the config file.
if($help ne 1 && $printver ne 1) {
   check_coder();           # Check encoder array.
   check_quality();         # Check if quality is valid.
   check_proto();           # Check if protocol level is valid.
   check_sshlist();         # Check list of remote machines.
   check_preset() if($preset);     # Check preset settings.
#
# To be able to save a new config file we have to write it before
# reading the parameters not yet passed from the config file.
#
   if($savenew == 1) {
      save_config();
      print "Saved a new config file!\n\n" if($verbose >= 3);
   }
#
# Read the config file.
#
   read_config() if($config == 1);
   check_lame;
   # The xxx enables reading this value, but should be switched off
   # before writing!
   $chars = "" if($chars eq "xxx" || $chars eq "off");
#
# Security check for new options: give them default value if empty.
# This can happen, if the config file is not yet up-to date.
# This will go away again in version 3.8. This is also done to prevent
# warnings.
#
   $maxrate = 0 if($maxrate eq "" && !defined $pmaxrate);
   $normalize = 0 unless($normalize);
   $cdtoc = 0 unless($cdtoc);
   $ghost = 0 unless($ghost);
   $extend = 2 unless($extend);
   $prepend = 2 unless($prepend);
   $md5sum = 0 unless($md5sum);
   $nicerip = 0 unless($nicerip);
   $ejectopt = "{cddev}" unless($ejectopt);
   $ejectcmd = "eject" unless($ejectcmd);
   $bitrate = "off" unless($bitrate);
   $dpermission = "0777" unless($dpermission);
   $fpermission = "0644" unless($fpermission);
   @core = 1 unless($core[0]);
#
# Save the config file.
#
   save_config() if($savepara == 1);
   print "Updated the config file!\n\n"
      if($verbose >= 3 && $savepara == 1);
   check_coder();           # Check it again for lame cbr vs vbr.
   check_sshlist();         # Check it again to create the hash.
}
#
#
########################################################################
#
# MAIN ROUTINE
#
########################################################################
#
if($printver) {
   print "\n";
   exit 2;
}

if($help == 1) {
   print_help();
   exit 3;
}

if(!$pcddev) {                 # Check CD dev if none defined.
   check_cddev;
}
else {                         # Close the tray.
   my $closeopt = $cddev if($ejectopt eq '{cddev}');
   $closeopt = "-t " . $closeopt if($ejectcmd =~ /^eject$/);
   $closeopt = $closeopt . " close" if($ejectcmd =~ /cdcontrol/);
   log_system("$ejectcmd $closeopt") if($multi == 0);
}

if($chars) {
   check_chars;
}

if($lcd == 1) {                    # lcdproc
   init_lcd();
}

if($outputdir eq "") {
   $outputdir = $homedir;
   chomp $outputdir;
}

if($outputdir =~ /^\.\//){         # Change relative paths to full ones.
   $outputdir =~ s/^\./$workdir/;
}

if($outputdir =~ /^~\//){          # If one wrote ~ in the config file.
   $outputdir =~ s/^~/$homedir/;
}
$poutputdir = $outputdir; # Keep a virgin copy of that var.

if(length($year) > 0 && length($year) != 4 ) {
   print STDERR "Warning: year is not Y2K compliant - $year\n"
      if($verbose >= 1);
}

if($halt == 1 && $verbose >= 2) {
   print "Will halt machine when finished.\n";
}

if($eject == 1 && $verbose >= 2) {
   print "Will eject CD when finished.\n";
   $ejectcmd = "eject -v" if($ejectcmd =~ /eject/ && $verbose >=4 );
}

if($cdtoc >= 1 && $verbose >= 2) {
   print "Will create a cd.toc file.\n";
}

if($ghost == 1) {
   print "Will analyze tracks for ghost songs.\n"if($verbose >= 2);
}

if($playlist >= 1 && $verbose >= 2) {
   print "Will create a playlist file.\n";
}

if($resume == 1 && $verbose >= 2) {
   print "Will try to resume previous session.\n";
}

if($utftag == 0 && $verbose >= 2 && "@coder" =~ /0/) {
   print "Will change encoding of Lame-tags to ISO8859-1.\n";
}

if($wav == 1 && $verbose >= 2) {
   print "Will keep the wav files.\n";
}

if($normalize == 1 && $verbose >= 2) {
   print "Will normalize the CD.\n";
}

if($md5sum == 1 && $verbose >= 2) {
   print "Will generate MD5SUMs of sound files.\n";
}

if($pdpermission && $verbose >= 2) {
   $dpermission = sprintf("%04d", $dpermission);
   print "Will directory permission to $dpermission.\n";
}

if($pfpermission && $verbose >= 2) {
   $fpermission = sprintf("%04d", $fpermission);
   print "Will set file permission to $fpermission.\n";
}

if($loop) {
   print "Will loop and eject CD.\n" if($verbose >= 2);
   $eject = 1;
   while(1) {
      main_sub;
      init_var;
   }
}
else {
   main_sub;
}
########################################################################
#
# Main subroutine.
#
sub main_sub{

   if(@ARGV) {
      $trackselection = $ARGV[0];
   }

   if($bitrate ne "off" && $lameflag == 1) {
      check_bitrate;
   }

   if($vbrmode ne "" && $lameflag == 1) {
      check_vbrmode;
   }

   if($preset) {
      lame_preset;
   }

   unless( cd_present() ) {
      print "\nPlease insert an audio CD!\n";
      while ( not cd_present() ) {
         check_cddev;
         sleep(6);
      }
   }
   get_cdinfo();
   create_seltrack($trackselection);
   create_dirs();

   if($normalize == 1) {
      &rip_cd();
      &norm_cd();
      &enc_cd();
   }
   else {
      &rip_cd();
   }

   if($eject == 1) {
      my $ejectopt = $cddev if($ejectopt eq '{cddev}');
      $ejectopt = $ejectopt . " eject" if($ejectcmd =~ /cdcontrol/);
      log_system("$ejectcmd $ejectopt");
   }

   if($verbose >= 1 && $encode == 1) {
      print "\nWaiting for encoder to finish...\n\n";
   }

   if($sshflag == 1) {
      del_wav();
   }
   else {
      wait;
   }

   if($playlist >= 1 && $encode == 1) {
      create_m3u();
   }

   my ($riptime,$enctime,$encend,$ghostnote,$splitnote) = cal_times();
   del_erlog();

   if(-r "$outputdir/error.log") {
      if($verbose >= 1) {
         print "\nCD may NOT be complete! ",
               "Check the error.log in $outputdir!\n";
      }
      elsif($verbose >= 3){
         print "\nRipping needed $riptime min and encoding needed ",
               "$enctime min.\n\n";
      }
   }
   else {
      if($verbose >= 1) {
         print "\nAll complete!\n";
         if($ghost == 1){
            print "No ghost songs found!\n" if($ghostnote =~ /0/);
            print "Ghost song(s) found!\n" if($ghostnote =~ /1/);
            print "No tracks trimmed!\n" if($splitnote =~ /0/);
            print "Track(s) trimmed!\n" if($splitnote =~ /1/);
         }
         print "Ripping needed $riptime min and ";
         print "encoding needed $enctime min.\n\n";
      }
   }

   log_info("\nRipping needed $riptime minutes.");
   log_info("Encoding needed $enctime minutes.");

   if($lcd == 1) {                 # lcdproc
      $lcdline1 = " ";
      $lcdline2 = "   RipIT finished   ";
      $lcdline3 = " ";
      ulcd();
      close($lcdproc) || die "close: $!";
   }

   if($halt == 1 && $verbose >= 1) {
      print "\nShutdown PC...\n";
      log_system("shutdown -h now");
   }

   if($multi == 1) {
      open(SRXY,">>$logfile")
         or print "Can not append to file \"$logfile\"!";
      print SRXY "\nEncoding   ended: $encend";
      print SRXY "\nRipping  needed: $riptime min.";
      print SRXY "\nEncoding needed: $enctime min.";
      close(SRXY);
      my $cddevno = $cddev;
      $cddevno =~ s/\/dev\///;
      open(SUCC,">>$logpath/success.log")
         or print "Can not append to file \"$logpath/succes.log\"!";
      print SUCC "$cd{artist};$cd{title};$genre;$categ;$cddbid;";
      print SUCC "$cddevno;$hostnam;$riptime;$enctime\n";
      close(SUCC);
      $cddev =~ s/\/dev\//device /;
      $cddev = $cddev . " " unless($cddev =~ /\d\d$/);
      print "Encoding done in $cddev with: $cd{artist} - $cd{title}.\n";
      print "Ghost song(s) found!\n" if($ghostnote =~ /1/ && $ghost == 1);
      print "Track(s) trimmed!\n" if($splitnote =~ /1/ && $ghost == 1);
#      cprint("\x037Encoding done in $cddev with: $cd{artist} - $cd{title}.\x030");
#      cprint("\x033\nGhost song(s) found!\x030") if($ghostnote =~ /1/ && $ghost == 1);
#      cprint("\x033\nTrack(s) trimmed!\x030") if($splitnote =~ /1/ && $ghost == 1);
   }

   log_info("*" x 72, "\n");
   print "\n";
#   rename("$infolog","$outputdir/info.log") if($infolog && $loop);
   exit unless($loop);
}
########################################################################
#
# SUBROUTINES
#
########################################################################

########################################################################
#
# Read the album, artist, discID and track titles from the get_CDDB()
# generated TOC file.
#
sub get_cdinfo {
   CDDB_get->import( qw( get_cddb get_discids ) );

   my ($artist, $album, %config, $revision);
   my ($CDDB_INPUT, $CDDB_MODE, $CDDB_PORT);
   my @comment = ();

   if($pgenre) {
      $genre = $pgenre;
   }
   else {
      $genre = "";
   }
   if($pyear) {
      $year = $pyear;
   }
   else {
      $year = "";
   }

   # Get ID and number of tracks of CD.
   my $cd = get_discids($cddev);
   my ($id, $tracks, $toc) = ($cd->[0], $cd->[1], $cd->[2]);
   $cddbid = sprintf("%08x", $id);

   my $usearch = "x";
   my @categs = ();
   if($archive == 1 && $multi == 0) {
      print "\nChecking for a local DB entry, please wait...\n\n"
         if($verbose >= 1);
      log_system("mkdir -m 0777 -p $homedir/.cddb")
         or die "Can not create directory $homedir/.cddb: $!\n";
      opendir(CDDB, "$homedir/.cddb/")
         or print "Can not read in $homedir/.cddb: $!";
      @categs = grep {/\w/i} readdir(CDDB);
      close CDDB;
      my @cddbid = ();
      foreach (@categs) {
         if(-d "$homedir/.cddb/$_") {
            opendir(CATEG, "$homedir/.cddb/$_")
               or print "Can not read in $homedir/.cddb: $!";
            my @entries = grep {/$cddbid/} readdir(CATEG);
            close CATEG;
            push @cddbid, $_ if($entries[0]);
         }
         elsif(-f "$homedir/.cddb/$_" && -s "$homedir/.cddb/$_") {
            push @cddbid, $_ if($_ =~ /$cddbid/);
         }
      }
      my $count = 1;
      my @dirflag = ();
      if($cddbid[0]) {
         print "Found local entry $cddbid in $homedir/.cddb !\n"
            if($interaction == 1);
         print "This CD could be:\n\n" if($interaction == 1);
         foreach (@cddbid) {
            my $openflag = "no";
            if(-s "$homedir/.cddb/$_/$cddbid") {
               open(LOG, "$homedir/.cddb/$_/$cddbid");
               $openflag = "ok";
               $dirflag[$count-1] = 1;
            }
            elsif(-s "$homedir/.cddb/$cddbid") {
               open(LOG, "$homedir/.cddb/$cddbid");
               $_ = "no category found!";
               $openflag = "ok";
               $dirflag[$count-1] = 0;
            }
            if($openflag eq "ok") {
               my @loglines = <LOG>;
               close(LOG);
               # Here we should test if @loglines is a good entry!
               # If there are empty files, we get warnings!
               my @artist = grep(s/^DTITLE=//, @loglines);
               $artist = clean_all($artist[0]);
               chomp $artist;
               my @genre = grep(s/^DGENRE=//, @loglines);
               my $agenre = $genre[0];
               $agenre =~ s/[\015]//g;
               chomp $agenre;
               print "$count: $artist (genre: $agenre, category: $_)\n"
                  if($interaction == 1);
               $count++;
               $agenre = "";
            }
         }
         print "\n0: Use online freeDB instead!\n"
            if($interaction == 1);
         if($interaction == 0) {
            $usearch = 1;
         }
         else {
            while($usearch !~ /\d/ || $usearch >= $count) {
               print "\nChoose: (1) ";
               $usearch = <STDIN>;
               chomp $usearch;
               $usearch = 1 if($usearch eq "");
            }
         }
      }
      else {
         $usearch = 0;
      }
      if($usearch != 0) {
         if($dirflag[$usearch-1] == 1) {
            read_entry("$homedir/.cddb/$cddbid[$usearch-1]/$cddbid",
               $cddbid[$usearch-1],$tracks);
         }
         elsif($dirflag[$usearch-1] == 0) {
            read_entry("$homedir/.cddb/$cddbid",
               $cddbid[$usearch-1],$tracks);
         }
      }
   }
   else {
      $usearch = 0;
   }

   if($usearch == 0) {
      print "\nChecking for a DB entry \@ $mirror.$CDDB_HOST...\n"
         if($verbose >= 1);

      #Configure CDDB_get parameters
      $config{CDDB_HOST} = $mirror . "." . $CDDB_HOST;
      while($transfer !~ /^cddb$|^http$/) {
         print "Transfer mode not valid!\n";
         print "Enter cddb or http : ";
         $transfer = <STDIN>;
         chomp $transfer;
      }
      if($transfer eq "cddb") {
         $CDDB_PORT = 8880;
         $CDDB_MODE = "cddb";
      }
      elsif($transfer eq "http") {
         $CDDB_PORT = 80;
         $CDDB_MODE = "http";
      }
      $config{CDDB_MODE} = $CDDB_MODE;
      $config{CDDB_PORT} = $CDDB_PORT;
      $config{CD_DEVICE} = $cddev;
      $config{HTTP_PROXY}= $proxy if($proxy);
      if($interaction == 0) {
         $CDDB_INPUT = 0;
      }
      else {
         $CDDB_INPUT = 1;
      }
      $config{input} = $CDDB_INPUT;
      $config{PROTO_VERSION} = $proto;

      # Change to whatever, but be aware to enter exactly 4 words!
      # E.g. username hostname clientname version
      my $hid = "RipIT www.suwald.com/ripit/ripit.html RipIT $version";
      my @hid = split(/ /, $hid);
      if($hid[4]) {
         print "There are more than 4 words in the \"HELLO_ID\"!\n";
         print "The handshake with the freeDB-server will fail!\n\n";
      }
      $config{HELLO_ID} = $hid;

      eval {%cd = get_cddb(\%config);};
      if($@) {
         print "No connection to internet? The error message is:\n";
         $@ =~ s/at\s\//at\n\//;
         print "$@\$! is <$!> and \$? is <$?>\n" if($verbose >= 1);
         $submission = 0;
      }
   }

   # Write CDDB entry to ~/.cddb/category if there is not already
   # an entry present (then $usearch != 0).
   if($archive == 1 && $usearch == 0 && defined $cd{title}) {
      $categ = $cd{cat};
      chomp $categ;
      log_system("mkdir -m 0777 -p $homedir/.cddb/$categ/")
         or die "Can not create directory $homedir/.cddb/$categ: $!\n";
      open(TOC, "> $homedir/.cddb/$categ/$cddbid")
         or print "Can not write to $homedir/.cddb/$categ/$cddbid: $!";
      foreach (@{$cd{raw}}) {
         print TOC $_;
      }
      close TOC;
   }

   if($multi == 1) {
      my @devnameblock = split(/\//, $cddev);
      $logfile = $workdir . "/" . $devnameblock[$#devnameblock];
      read_entry($logfile);

      # This has nothing to do here, but has to be
      # done somewhere, because the bash is not able doing it.
      open(LOG, "$workdir/settings.log")
         or print "Can not open settings.log!\n";
      my @settinglines = <LOG>;
      close(LOG);
      my @commentagno=grep(s/^comment=//g, @settinglines);
      my $commentagno=$commentagno[0];
      $commentag="Rip2media" if($commentagno == 1);
      $commentag="Respect ©!" if($commentagno == 2);
   }

   if(defined $cd{title}) {
      $album = clean_all($cd{title});
      $artist = clean_all($cd{artist});
      $categ = $cd{cat};

      # Set the year if it wasn't passed on command line.
      unless($year) {
         $year = $cd{year} if($cd{year});
         $year =~ s/[\015]//g;
      }

      # Set the genre if it wasn't passed on command line.
      if(!defined $pgenre && defined $cd{genre}) {
         $genre = $cd{genre};
         $genre =~ s/[\015]//g;
      }

      @comment = extract_comm;
      $revision = get_rev;
   }
   else {
#      # We don't want tracks to be defined globally. Hack it into the
#      # cd-hash! Note that it won't exist afterwards!
#      %cd = (track => $tracks);
      if($submission == 0) {
         print "\nNo CDDB info choosen or found for this CD\n"
            if($verbose >= 1);
      }
      # Set submission OK, will be set to zero if default
      # names are used.
      $cddbsubmission = 1;
      # Don't ask for default settings, use them! ...
      if($interaction == 0) {
         create_deftrack(1);
      }
      # ... or ask whether 1) default or 2) manual entries
      # shall be used or entered.
      else {
         create_deftrack(2);
      }
      $album = $cd{title};
      $artist = $cd{artist};
      $revision = $cd{revision};
   }
   my $genreno = "";
   if($genre eq "" && $interaction == 1) {
      print "\nPlease enter a valid CDDB genre (or none): ";
      $genre = <STDIN>;
      chomp $genre;
      $cd{genre} = $genre;
   }
   if($genre) {
      $genre =~ s/[\015]//g;
      ($genre,$genreno) = check_genre($genre);
   }

   # What if someone wants underscore and/or lowercase in filenames
   # and has genre in the dir or tracktemplate? So we have to
   # lowercase genre and underscore it if it is a 2 word expression.
   if(($dirtemplate =~ /\$genre/ or $tracktemplate =~ /\$genre/)
      && $genre ne "") {
      my $tempgenre = $genre;
      $tempgenre = lower_case($tempgenre) if($lowercase == 1);
      if($genre =~ /\w\s\w/ && $underscore == 1) {
         $tempgenre =~ s/ /_/g;
         # Do it the hard way: assume we have the definitive
         # genre, so replace the string "$genre" with the
         # value of $tempgenre!
         $dirtemplate =~ s/\$genre/$tempgenre/g;
         $tracktemplate =~ s/\$genre/$tempgenre/g;
      }
   }

   if($verbose >= 1) {
      print "\n-----------------";
      print "\nCDDB and tag Info";
      print "\n-----------------\n";
      print "Artist: $artist\n";
      print "Album: $album\n";
      print "Category: $categ\n" if($verbose >= 2);
      if($genre) {
         print "ID3-Genre: $genre ($genreno)\n" if($lameflag >= 0);
         print "Genre-tag: $genre\n" if($lameflag == -1);
         if(lc($genre) ne lc($cd{'genre'})) {
            print "CDDB-Genre: $cd{genre}\n";
         }
      }
      else{
         print "ID3-Genre: none\n";
      }
      print "Year: $year\n";
      print "Revision: $revision\n" if($verbose >= 2);
      # It happens, that the ID from CDDB is NOT
      # identical to the ID calculated from the
      # frames of your CD...
      if($cddbid ne $cd{id} && defined $cd{id} ) {
         print "CDDB id: $cd{id}\n";
      }
      print "CD id: $cddbid\n";
      if(@comment && $verbose >= 2) {
         foreach (@comment) {
            print "Comment: $_\n" if($_);
         }
      }
      print "\n";
   }
   log_info("\nArtist: $artist");
   log_info("Album: $album");
   log_info("ID3-Genre: $genre ($genreno)") if($genre);
   log_info("ID3-Genre: none") unless($genre);
   log_info("Category: $categ");
   log_info("CD id: $cddbid\n");

   # Read out pregap before calculating track lengths.
   my $frames = $toc->[0]->{'frames'};
   push @framelist, "$frames";
   if ($frames > 400) {
      my $second = int($frames/75);
      my $frame = $frames - $second * 75;
      my $minute = int($second/60);
      $second = $second - $minute * 60;
      printf("%s %02d:%02d %s\n",
         "There might be a hidden track", $minute, $second,
         "long,\nbecause offset of track 01 has ", $frames,
         " frames intstead of typically 150 (2s).\n")
         if($verbose >= 1);
      my $riptrackname = "Hidden Track";
      $riptrackname = lower_case($riptrackname) if($lowercase == 1);
      $riptrackname =~ s/ /_/g if($underscore == 1);
      printf("%s: [%02d:%02d.%02d] %s\n",
         "00", $minute, $second, $frame, $riptrackname)
         if($verbose >= 1);
      $second = int($frames/75);
      $hiddenflag = 1 if($trackselection eq ""
         || $trackselection =~ /^0/
         || $trackselection =~ /\D0/);
      # We can't add this track to seltrack and framelist, because this
      # would break (re-) submission of CDDB.
      # Note: seltrack is not yet defined... But we start to fill the
      # @secondlist array (yet empty) with track lengths in seconds.
      # TODO: push the pregap seconds to the list in any case, then we
      # don't need to differentiate between the case hiddenfalg==1 or
      # hiddenflag == 0 while choosing tracknames.
      push @secondlist, "$second" if($hiddenflag == 1);
   }
   my $n = 1;
   # Print track infos.
   foreach (@{$cd{track}}) {
      $_ = clean_all($_);
      push @tracktags, $_;
      # Get frames and total time.
      my $frames = $toc->[$n]->{'frames'};
      push @framelist, "$frames";
      $frames = $frames - $framelist[$n-1];
      my $second = int($frames/75);
      push @secondlist, "$second";
      my $frame = $frames - $second * 75;
      my $minute = int($second/60);
      $second = $second - $minute * 60;
      $_ = clean_chars($_) if($chars);
      printf("%02d: [%02d:%02d.%02d] %s\n",
             $n, $minute, $second, $frame, $_)
         if($verbose >= 2);
      $_ = clean_name($_);
      $_ = lower_case($_) if($lowercase == 1);
      $_ =~ s/ /_/g if($underscore == 1);
      push @tracklist, $_;
      $n++;
   }
   print "\n\n" if($verbose >= 1);

   # Some more error checking.
   if($artist eq "") {
      die "ERROR: No Artist Found!\n";
   }

   # lcdproc
   if($lcd == 1){
      $lcdline1 = $artist . "-" . $album;
      $lcdline2 = "R00|00.0%|----------";
      $lcdline3 = "E00|00.0%|----------";
      ulcd();
   }
}
########################################################################
#
# Create the track selection from the parameters passed on the command-
# line, i. e. create an array with all track numbers including those not
# explicitly stated at the command line.
#
sub create_seltrack {
   my($tempstr,$intrack);
   ($tempstr) = @_;
   if($_[0] eq "-") {
         die "Invalid track selection \"-\"!\n\n";
   }

   if(($tempstr =~ /,/) || ($tempstr =~ /\-/)) {
      my @intrack = split(/,/ , $tempstr);
      # If last character is a , add an other item with a -
      if($tempstr =~ /,$/) {
         push @intrack, ($intrack[$#intrack]+1) . "-";
      }
      foreach $intrack (@intrack) {
         if($intrack =~ /\-/) {
            my @outrack = split(/-/ , $intrack);
            # If last character is a -, add last track to $outrack
            if($#outrack == 0) {
               $outrack[1] = $#tracklist + 1;
            }
            for(my $i = $outrack[0]; $i <= $outrack[1]; $i++) {
               push @seltrack, $i;
            }
         }
         else {
            push @seltrack, $intrack;
         }
      }
   }
   elsif($tempstr eq '') {
      for(my $i = 1; $i <= ($#tracklist + 1); $i++) {
         $seltrack[$i - 1] = $i;
      }
   }
   elsif($tempstr =~ /^[0-9]*[0-9]$/) {
      $seltrack[0] = $tempstr;
   }
   else {
      die "Track selection invalid!\n";
   }

   # Sort the tracks in order, perl is so cool :-)
   @seltrack = sort {$a <=> $b} @seltrack;

   # Check the validity of the track selection.
   foreach (@seltrack) {
      if($_ > ($#tracklist + 1)) {
         die "Track selection higher than number of tracks on CD.\n\n";
      }
      elsif($_ == 0) {
         shift @seltrack;
      }
   }
}
########################################################################
#
# Ask if CDDB submission shall be done. Either because one might change
# some settings a last time before writing to directories and files (if
# there was not DB entry and operator entered all by hand) or because
# DB entry has some typos!
# Then create the directory where the sound files shall go.
#
sub create_dirs {
   # Directory created will be: /outputdir/$dirtemplate

   my $index = 2;
   unless($cddbsubmission == 0 || $interaction == 0) {
      while($index !~ /^[0-1]$/) {
         print "\nDo you want to edit or submit the CDDB entry?";
         print "\nTo confirm each question type Enter!\n\n";
         print "1: Yes, and I know about the naming-rules of ";
         print "freedb.org!\n\n";
         print "0: No\n\nChoose [0-1]: (0) ";
         $index = <STDIN>;
         chomp $index;
         if($index eq "") {
            $index = 0;
         }
         print "\n";
      }
      if($index == 1) {
         my $revision = get_rev();
         if($revision) {
            print "\nPlease change some settings!";
            print "\nYou may confirm CDDB settings with Enter!\n";
            create_deftrack(0);
         }
      }
      elsif($index != 0) {
         print "You should choose 0 or 1!\n";
      }
   }
   if($index == 1) {
      pre_subm();
   }

   my $album = clean_all($cd{title});
   my $artist = clean_all($cd{artist});
   $album = clean_name($album);
   $artist = clean_name($artist);
   $album = clean_chars($album) if($chars);
   $artist = clean_chars($artist) if($chars);
   $album =~ s/ /_/g if($underscore == 1);
   $artist =~ s/ /_/g if($underscore == 1);

   # Check and create the full path "outputdir" where the files
   # will go. First Check the dirtemplate and use the actual year
   # as default if $year is in the template and none is given!
   if(($dirtemplate =~ /\$year/ || $tracktemplate =~ /\$year/)
       && $year eq "") {
      $year = sprintf("%04d", sub {$_[5]+1900}->(localtime));
   }
   if(($dirtemplate =~ /\$genre/ || $tracktemplate =~ /\$genre/)
       && $genre eq "") {
      $genre = "Other";
      chomp $genre;
   }
   my $dir;
   if(!eval("\$dir = $dirtemplate")) {
      die "Directory Template incorrect, caused eval to fail: $!";
   }

   $dir = lower_case($dir) if($lowercase == 1);
   $dir = clean_chars($dir) if($chars);
   $dir =~ s/ /_/g if($underscore == 1);

   $outputdir = $outputdir . "/" . $dir;
   # Delete ending . in directory name if no special characters
   # wanted!
   $outputdir =~ s/[.]+$// if($chars);
   # Check if the outputdir alread exists, if it does, try
   # "outputdir i" with i an integer until it works, unless we resume.
   my $cdexistflag = 0;
   my $i = 1;
   my $noutputdir = $outputdir;
   while(defined(opendir(TESTDIR, $noutputdir)) &&
         $rip == 1 && $resume == 0) {
      my $sfx = " " . $i if($underscore == 0);
      $sfx = "_" . $i if($underscore == 1);
      $sfx = clean_chars($sfx) if($chars);
      $noutputdir = $outputdir . $sfx;
      $i++;
      $cdexistflag = 1;
   }
   if($multi == 1) {
      my @logpath = split(/\//, $noutputdir);
      my $aadir = pop(@logpath);
      if($cdexistflag == 1){
         open(SRXY,"$logfile") or print "Can not open \"$logfile\"!\n";
         my @srxylines = <SRXY>;
         close(SRXY);
         grep(s/^album:\s(.*)$/album: $1 $i/, @srxylines)
            if($underscore == 0);
         grep(s/^album:\s(.*)$/album: $1_$i/, @srxylines)
            if($underscore == 1);
         open(SRXY,">$logfile")
            or print "Can not write to file \"$logfile\"!\n";
         print SRXY @srxylines;
         close(SRXY);
      }
      open(SRXY,">>$logfile")
         or print "Can not append to file \"$logfile\"!\n";
      print SRXY "\nArtist - Album:$aadir";
      close(SRXY);
      pop(@logpath);
      $logpath = join('/', @logpath);
   }
   $outputdir = $noutputdir . "/";


   if(!opendir(TESTDIR, $outputdir)) {
      # Explicitly log outputdir creation.
      log_info("new-mediadir: $outputdir");
      log_system("mkdir -m $dpermission -p \"$outputdir\"")
         or die "Can not create directory $outputdir: $!\n";
   }
   else {
      closedir(TESTDIR);
   }
}
########################################################################
#
# Create the full-path track file name from the tracktemplate variable.
#
sub get_trackname {
   my($trnum,$trname,$riptrname);

   ($trnum,$trname) = @_;

   my $album = clean_all($cd{title});
   my $artist = clean_all($cd{artist});
   $album = clean_name($album);
   $artist = clean_name($artist);
   $album = clean_chars($album) if($chars);
   $artist = clean_chars($artist) if($chars);
   $album =~ s/ /_/g if($underscore == 1);
   $artist =~ s/ /_/g if($underscore == 1);
   # Create the full file name from the track template, unless
   # the disk is unknown.
   if(defined $cd{title}) {
      # We do not need to lowercase the tracktemplate, because
      # all variables in are already lowercased!
      $tracktemplate =~ s/ /\\_/g if($underscore == 1);
      # We have to update tracknum and trackname because they
      # are evalueted by the tracktemplate!
      my $tracknum = sprintf("%02d", $trnum);
      my $trackname = $trname;
      if(!eval("\$riptrname = \$outputdir.$tracktemplate")) {
         die "Track Template incorrect, caused eval to fail: $!";
      }
   }
   else {
      $trname  = lower_case($trname) if($lowercase == 1);
      $trname =~ s/ /_/g if($underscore == 1);
      $riptrname = $outputdir . $trname;
   }

   return $riptrname;
}
########################################################################
#
# Rip the CD.
#
sub rip_cd {
   my($shortname, @shortname, $ripcom, $riptrackname);
   my $startenc = 0;
   my $failflag = 0;
   my $resumerip = $resume;

   # Cleaning.
   my $albumtag = clean_all($cd{title});
   my $artistag = clean_all($cd{artist});
   my $album = $albumtag;
   $album = clean_name($album);
   my $artist = $artistag;
   $artist = clean_name($artist);
   $album = clean_chars($album) if($chars);
   $artist = clean_chars($artist) if($chars);
   $album =~ s/ /_/g if($underscore == 1);
   $artist =~ s/ /_/g if($underscore == 1);

   # Define an array with intervalls and the tracks to be skipped.
   my @merge = ();
   my @skip = ();
   if($pmerge){
      @skip = skip_tracks;
      @merge = split(/,/, $pmerge);
      # If merge is used, we need to calculate the true track length for
      # the playlist file. And it would be nice, if the filename
      # reflects "missing" tracks. Define a string to concatenate the
      # track names.
      my $concat = " + ";
      $concat =~ s/ /_/g if($underscore == 1);
      $concat = clean_chars($concat) if($chars);
      foreach(@merge){
         my @bea = split(/-|\+/, $_);
         my $beg = $bea[0] - 1;
         while($bea[0] < $bea[1]) {
            $secondlist[$beg] += $secondlist[$bea[0]];
            $tracklist[$beg] = $tracklist[$beg] . $concat .
                               $tracklist[$bea[0]];
            $tracktags[$beg] = $tracktags[$beg] . " + " .
                               $tracktags[$bea[0]];
            $bea[0]++;
         }
      }
   }

   # Display info which tracks are going to be ripped. Because of option
   # merge we have to work hard to make it look nice:
   @tracksel = @seltrack; # Use a copy of @seltrack to work with.
   my @printracks;        # A new array in nice print format.
   my $trackcn;
   my $prevtcn = -1;
   foreach $trackcn (@tracksel) {
      next if($trackcn <= $prevtcn);
      my $trackno;
      # Check if next tracknumber is in the skip array of tracks being
      # merged. If so, add a hyphen.
      if($skip[0] && ($trackcn + 1) =~ /$skip[0]/){
         $trackno = $trackcn . "-";
         shift(@skip);
         $trackcn++;
         # Is the next tracknumber the last of the intervall of merged
         # tracks? If not, continue to increase the tracknumber.
         while($skip[0] && ($trackcn + 1) =~ /$skip[0]/) {
           $trackcn++;
           shift(@skip);
         }
         $trackno = $trackno . $trackcn;
         $prevtcn = $trackcn;
      }
      else {
         $trackno = $trackcn;
      }
      push(@printracks, $trackno);
   }

   if($#seltrack == 0 && $hiddenflag == 0) {
      print "Track @printracks will be ripped!\n\n" if($verbose > 0);
   }
   elsif(!@seltrack && $hiddenflag == 1) {
      print "Track 0 will be ripped!\n\n" if($verbose > 0);
   }
   elsif($hiddenflag == 1) {
      print "Tracks 0 @printracks will be ripped!\n\n" if($verbose > 0);
   }
   else {
      print "Tracks @printracks will be ripped!\n\n" if($verbose > 0);
   }

   # Get the time when ripping started, and save it in the error.log.
   my $ripstart = sprintf("%02d:%02d", sub {$_[2], $_[1]}->(localtime));
   my $date = sprintf("%04d-%02d-%02d",
      sub {$_[5]+1900, $_[4]+1, $_[3]}->(localtime));
   open(ERO,">$outputdir/error.log")
      or print "Can not append to file \"$outputdir/error.log\"!\n";
      print ERO "Ripping  started: $ripstart\n";
   close(ERO);
   if($multi == 1) {
      open(SRXY,">>$logfile")
         or print "Can not append to file \"$logfile\"!\n";
      print SRXY "\nRipping  started: $ripstart";
      close(SRXY);
   }

   # Write a toc (cue) file.
   if($cdtoc == 1){
      my $cdtocartis = oct_char($artistag);
      my $cdtocalbum = oct_char($albumtag);
      open(CDTOC ,">$outputdir/cd.toc")
         or print "Can not append to file \"$outputdir/cd.toc\"!\n";
      print CDTOC "CD_DA\n//Ripit $version cd.toc file generated ",
                  "$date at $ripstart.",
                  "\n//Use command  >cdrdao scanbus< to detect device.",
                  "\n//Assume the device found is: ATA:1,0,0, then, ",
                  "use e. g. command",
                  "\n//--> cdrdao write --device ATA:1,0,0 ",
                  "--speed 4 cd.toc <-- to burn the CD.",
                  "\n//Note: Not all CD (DVD) burners are able to burn",
                  " CD-text! Test your device!";
      print CDTOC "\n\n//CD Text:\nCD_TEXT{LANGUAGE_MAP {0 : EN}\n\t";
      print CDTOC "LANGUAGE 0 {\n\t\tTITLE \"$cdtocalbum\"\n\t\t";
      print CDTOC "PERFORMER \"$cdtocartis\"\n";
##      print CDTOC "\t\tGENRE \"$genreno\"\n" if($genreno);
      print CDTOC "\t\tDISC_ID \"$cddbid\"\n\t}\n}\n";
      close(CDTOC);
   }

   # Start to rip the hidden track if there's one: First check if
   # cdparanoia is available.
   if($ripper != 1) {
      unless(log_system("cdparanoia -V")) {
         print "Cdparanoia not installed? Can't rip the hidden track ";
         print "without cdparanoia!\n"
            if($hiddenflag == 1);
         $hiddenflag = 0;
      }
   }

   # Check if the hidden track has been done in a previous session.
   my $checknextflag = 0;
   if($resumerip){
      $riptrackname = "Hidden Track";
      $riptrackname = lower_case($riptrackname) if($lowercase == 1);
      $riptrackname =~ s/ /_/g if($underscore == 1);
      $riptrackname = get_trackname(0, $riptrackname);
      if(-r "$riptrackname.rip") {
         unlink("$riptrackname.rip");
         print "Found $riptrackname.rip.\n" if($verbose >= 1);
      }
      elsif(-r "$riptrackname.wav") {
         $checknextflag = 1;
         print "Found $riptrackname.wav.\n" if($verbose >= 1);
      }
      else{
         my $sufix;
         foreach $sufix ('flac','m4a','mp3','ogg') {
            if(-r "$riptrackname.$sufix") {
               $checknextflag = 1;
               print "Found compressed file $riptrackname.$sufix.\n";
            }
         }
      }
      if($checknextflag == 1){
         $riptrackname = "Hidden Track";
         unshift @tracktags, $riptrackname;
         unshift @seltrack, 0;
         unshift @tracklist, $riptrackname;
      }
   }

   # Process a possible hidden (first) track.
   if($hiddenflag == 1 && $checknextflag == 0) {
      $riptrackname = "Hidden Track";
      unshift @tracktags, $riptrackname;
      my $cdtocname = $riptrackname;
      $riptrackname = lower_case($riptrackname) if($lowercase == 1);
      $riptrackname =~ s/ /_/g if($underscore == 1);
      unshift @seltrack, 0;
      unshift @tracklist, $riptrackname;
      # Change riptrackname to the full path & template name.
      $riptrackname = get_trackname(0, $tracklist[0]);
      @shortname = split(/\// , $riptrackname);
      $shortname = $shortname[$#shortname];
      # What if the operator wants to merge a hidden track with the 1st
      # and so on tracks? Calculate the number of the first track not to
      # be merged with the hidden track.
      my $endtrackno = 1;
      if($pmerge) {
         my @bea = split(/-|\+/, $merge[0]);
         if($bea[0] && $bea[0] == 0) {
            $endtrackno = shift(@merge);
            $endtrackno =~ s/^0.//;
            $endtrackno++;
         }
      }
      # Assemble the command for cdparanoia to rip the hidden track.
      my $saveripopt = $ripopt;
      $ripopt = $ripopt . " -Z" if($parano == 0);
      $ripopt = $ripopt . " -q" if($verbose <= 1);
      $ripcom = "cdparanoia $ripopt -d $cddev [00:00]$endtrackno \\
                \"$riptrackname.rip\"";
      print "\nRipping \"$shortname\"...\n"
         if($verbose >= 1 && $rip == 1);
      unless(log_system("$ripcom")) {
         # If no success, shift the hidden track stuff out of arrays.
         $hiddenflag = 0;
         shift(@secondlist);
         shift(@seltrack);
         shift(@tracklist);
         shift(@tracktags);
      }
      # Write to the toc (cue) file.
      if($cdtoc == 1 && $hiddenflag == 1){
         open(CDTOC ,">>$outputdir/cd.toc")
            or print "Can not append to file \"$outputdir/cd.toc\"!\n";
         print CDTOC "\n//Track 0:\nTRACK AUDIO\nTWO_CHANNEL_AUDIO\n";
         print CDTOC "CD_TEXT {LANGUAGE 0 {\n\t\tTITLE \"$cdtocname\"";
         print CDTOC "\n\t\tPERFORMER \"$artistag\"\n\t}\n}\n";
         print CDTOC "FILE \"$shortname.wav\" 0\n";
         close(CDTOC);
      }
      # Check the hidden track for gaps. We do not care about option
      # merge... should we? Yes, we should. If option merge has been
      # choosen for this track, splitting is not allowed, while
      # extracting one chunk of sound may be desired.
      if($ghost == 1 && $hiddenflag == 1){
         split_chunks(0, $riptrackname, $shortname);
      }
      if($hiddenflag == 1) {
         rename("$riptrackname.rip", "$riptrackname.wav");
      }
      $ripopt = $saveripopt;
   }
   # End prepartion of ripping process.
   #
   #
   # Start ripping each track. Note that we have to skip a possible
   # hidden track. To prevent reripping ghost songs pushed into the
   # @seltrack array, make a copy which will not be altered.
   @tracksel = @seltrack;

   # Define some counters:
   # Because cdtoc is written in different subroutines, define a counter
   # for each track written into the toc file. This way, ghost songs are
   # sorted in the toc file, while they aren't in the @seltrack array.
   my $cdtocn = 0;
   # Encoder messages are printed into a file which will be read by the
   # ripper to prevent splitting ripper-messages. Lines already printed
   # will not be printed again.
   my $encline = 0;
   $trackcn = 0;

   foreach (@tracksel) {
      next if($_ == 0); # Skip hidden track.
      $trackcn++;
      $riptrackname = get_trackname($_, $tracklist[$_ - 1]);
      $riptrackname = get_trackname($_, $tracklist[$_])
         if($hiddenflag == 1);
      my $riptrackno = $_;
      @shortname = split(/\//, $riptrackname);
      $shortname = $shortname[$#shortname];
      # If we use option merge, skip a previously merged track:
      my $skipflag = 0;
      if($pmerge) {
         @skip = skip_tracks;
         foreach my $skip (@skip) {
            $skipflag = 1 if($_ == $skip);
         }
      }
      print "\nSkip track $_, it has been merged into previous one.\n"
         if($verbose >=1 && $skipflag == 1);
      next if($skipflag == 1);
      # Write the toc entry only if wav present.
      if($cdtoc == 1){
         $cdtocn++;
         my $cdtoctitle = $tracktags[$_ - 1];
         $cdtoctitle = $tracktags[$_] if($hiddenflag == 1);
         $cdtoctitle = oct_char($cdtoctitle);
         my $cdtocartis = oct_char($artistag);
         open(CDTOC, ">>$outputdir/cd.toc")
            or print "Can not append to file \"$outputdir/cd.toc\"!\n";
         print CDTOC "\n//Track $cdtocn:\nTRACK AUDIO\n";
         print CDTOC "TWO_CHANNEL_AUDIO\nCD_TEXT {LANGUAGE 0 {\n\t\t";
         print CDTOC "TITLE \"$cdtoctitle\"\n\t\t";
         print CDTOC "PERFORMER \"$cdtocartis\"\n\t}\n}\n";
         print CDTOC "FILE \"$shortname.wav\" 0\n";
         close(CDTOC);
      }
      # Remember: $riptrackno is the track number passed to the encoder.
      # If we want to merge, we substitute it with the interval, with a
      # hyphen for cdparanoia and a plus sign for cdda2wav.
      my $saveriptrackno = $riptrackno;
      if($pmerge && $merge[0]) {
         my @bea = split(/-|\+/, $merge[0]);
         if($bea[0] && $riptrackno == $bea[0]) {
            $riptrackno = shift(@merge);
            $riptrackno =~ s/-/\+/ if($ripper == 2);
            $riptrackno =~ s/\+/-/ if($ripper == 1);
            # TODO: check for dagrab and sox...
         }
      }
      # lcdproc
      if($lcd == 1){
         my $_lcdtracks = scalar @tracksel;
         $lcdtrackno++;
         my $lcdperc;
         if($_lcdtracks eq $lcdtrackno) {
            $lcdperc = "*100";
         }
         else {
            $lcdperc = sprintf("%04.1f", $lcdtrackno/$_lcdtracks*100);
         }
         $lcdline2 =~ s/\|\d\d.\d/\|$lcdperc/;
         my $lcdtracknoF = sprintf("%02d", $lcdtrackno);
         $lcdline2 =~ s/\r\d\d/\r$lcdtracknoF/;
         substr($lcdline2,10,10) = substr($shortname,3,13);
         ulcd();
      }

      # There is a problem with too long file names, encountered, e. g.
      # with some classical CDs. Cdparanoia cuts the length of the file
      # name, cdda2wav too...  but how should RipIT know? Therefore use
      # a shorter track name if total length (including the full path)
      # > 230 characters.
      if(length($riptrackname) > 230) {
         $riptrackname = get_trackname($_, $_ . "short");
      }

      # Check for tracks already done with option resume.
      $checknextflag = 0;
      if($resumerip){
         if ($normalize == 0) {
            # Start the encoder in the background, but only once.
            # We do it already here, because:
            # i)  if all wavs are done, the encoding porcess at the end
            #     of this subroutine will not be started at all!
            # ii) why should we wait for an wav, if others are already
            #     here and encoding could continue right away?
            if($startenc == 0 && $encode == 1) {
               $startenc = 1;
               open(ENCLOG,">$outputdir/enc.log");
               close ENCLOG;
               unless(fork) {
                  enc_cd();
               }
            }
         }

         if(-r "$riptrackname.rip") {
            unlink("$riptrackname.rip");
            print "Found $riptrackname.rip.\n" if($verbose >= 1);
         }
         elsif(-r "$riptrackname\_rip.wav" && $ripper == 2) {
            unlink("$riptrackname\_rip.wav");
            print "Found $riptrackname\_rip.wav.\n" if($verbose >= 1);
         }
         elsif(-r "$riptrackname.wav") {
            $checknextflag = 1;
            print "Found $riptrackname.wav.\n" if($verbose >= 1);
         }
         elsif($wav == 0){
            my $sufix;
            foreach $sufix ('flac','m4a','mp3','ogg') {
               if(-r "$riptrackname.$sufix") {
                  $checknextflag = 1;
                  print "Found compressed file $riptrackname.$sufix.\n"
                     if($verbose >= 1);
               }
            }
         }
         # Cdda2wav is somehow unpleasent. It dies not quick enough with
         # ^+c. I. e. even if a track has not been ripped to the end,
         # there will be a *.wav. So we have to check for the encoded
         # files and assume, that for not encoded files present, there
         # is no fully ripped file. This is not so problematic, because
         # cdda2wav is quite fast, ripping that track again doesn't
         # cost a lot of time.
         if($ripper == 2 && $checknextflag == 1) {
            my $sufix;
            foreach $sufix ('flac','m4a','mp3','ogg') {
               if(-r "$riptrackname.$sufix") {
                  $checknextflag = 1;
               }
               else {
                  $checknextflag = 0;
               }
               last if($checknextflag == 1);
            }
         }
      }
      # Skip that track, i.e. restart the foreach-loop of tracks if a
      # wav file or other (mp3, ogg, ma4, flac) was found.
      next if($checknextflag == 1);
      # Don't resume anymore, if we came until here.
      $resumerip = 0;

      # Now do the job of ripping:
      print "\nRipping \"$shortname\"...\n"
         if($verbose >= 1 && $rip == 1);
      # Choose the cdaudio ripper to use.
      #
      # TODO: Check behaviour of all rippers on data tracks.
      # Choose to use print instead of die if ripper stops itself!
      # Dagrab fails @ data-track, so don't die and create an error.log,
      # cdparanoia fails @ data-track, so don't die and create an
      # error.log.
      # cdda2wav prints errors @ data-track, therefore die!
      if($ripper == 0 && $rip == 1) {
         if($trackcn == 1) {
            $ripopt = $ripopt . " -r 3" if($parano == 0);
            $ripopt = $ripopt . " -v" if($verbose >= 2);
         }
         $ripcom = "(dagrab $ripopt -d $cddev \\
                    -f \"$riptrackname.rip\" \\
                    $riptrackno 3>&1 1>&2 2>&3 \\
                    | tee -a \"$outputdir/error.log\") 3>&1 1>&2 2>&3 ";
         $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0);
         unless(log_system("$ripcom")) {
            print "Dagrab detected some read errors on ",
                  "$tracklist[$_ - 1]\n\n";
            # Create error message in CD-directory for encoder: don't
            # wait.
            open(ERO,">>$outputdir/error.log")
               or print "Can not append to file ",
                        "\"$outputdir/error.log\"!";
            print ERO "Dagrab detected some read errors at $riptrackno";
            print ERO " on CD $artist - $album, do not worry!\n";
            close(ERO);
         }
         print "\n";
      }
      elsif($ripper == 1 && $rip == 1) {
         if($trackcn == 1) {
            $ripopt = $ripopt . " -Z" if($parano == 0);
            $ripopt = $ripopt . " -q" if($verbose <= 1);
         }
         if($multi == 0) {
            $ripcom = "cdparanoia $ripopt -d $cddev $riptrackno \\
               \"$riptrackname.rip\"";
            $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0);
            unless(log_system("$ripcom")) {
               print "cdparanoia failed on track ", $_,
                     " $tracklist[$_ - 1]\n\n" if($hiddenflag == 0);
               print "cdparanoia failed on track ", $_,
                     " $tracklist[$_]\n\n" if($hiddenflag == 1);
               # Create error message in CD-directory for encoder:
               # don't wait.
               open(ERO,">>$outputdir/error.log")
                  or print "Can not append to file ",
                           "\"$outputdir/error.log\"!";
               print ERO "Track $riptrackno on CD $artist - $album ";
               print ERO "failed!\n";
               close(ERO);
               $failflag = 1;
            }
         }
         elsif($multi == 1) {
            $ripcom = "cdparanoia $ripopt -d $cddev $riptrackno \\
               \"$riptrackname.rip\" 2>> \\
               \"$logfile.$riptrackno.txt\"";
            $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0);
            unless(log_system("$ripcom")) {
               # Apend error message to file srXY for rip2m to start
               # checktrack.
               open(SRXY,">>$logfile")
                  or print "Can not append to file \"$logfile\"!";
               print SRXY "\ncdparanoia failed on $tracklist[$_ - 1] "
                  if($hiddenflag == 0);
               print SRXY "\ncdparanoia failed on $tracklist[$_] "
                  if($hiddenflag == 1);
               print SRXY "in device $logfile";
               close(SRXY);
               # Create error message in CD-directory for encoder:
               # don't wait.
               open(ERO,">>$outputdir/error.log")
                  or print "Can not append to file ",
                           "\"$outputdir/error.log\"!";
               print ERO "Track $riptrackno on CD $artist - $album ";
               print ERO "failed!\n";
               close(ERO);
               # Kill failed CD only if it is not the last track. Last
               # track may be data/video track.
               # I.e. print error message to file srXY.Z.txt, checktrack
               # will grep for string
               # "cdparanoia failed" and kill the CD immediately!
               if($riptrackno != $tracksel[$#tracksel]) {
                  open(SRTF,">>$logfile.$riptrackno.txt")
                     or print "Can not append to file ",
                              "\"$logfile.$riptrackno.txt\"!";
                  print SRTF "cdparanoia failed on $tracklist[$_ - 1]"
                     if($hiddenflag == 0);
                  print SRTF "cdparanoia failed on $tracklist[$_ - 1]"
                     if($hiddenflag == 1);
                  print SRTF "\nin device $logfile, error !";
                  close(SRTF);
                  # Create on the fly error message in log-directory.
                  my $devnam = $cddev;
                  $devnam =~ s/.*dev.//;
                  open(ERO,">>$logpath/failed.log")
                     or print "Can not append to file ",
                              "\"$logpath/failed.log\"!";
                  print ERO "$artist;$album;$genre;$categ;$cddbid;";
                  print ERO "$devnam;$hostnam; Cdparanoia failure!\n";
                  close(ERO);
                  # Now wait to be terminated by checktrack.
                  sleep 360;
                  exit;
               }
            }
         }
      }
      elsif($ripper == 2 && $rip == 1) {
         if($trackcn == 1) {
            $ripopt = $ripopt . " -q" if($verbose <= 1);
         }
         if($multi == 0) {
            $ripcom = "cdda2wav -D $cddev -H $ripopt -t $riptrackno \\
               \"$riptrackname\_rip\"";
            $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0);
            unless(log_system("$ripcom")) {
               print "cdda2wav failed on <$tracklist[$_ - 1]>.\n"
                     if($hiddenflag == 0);
               print "cdda2wav failed on <$tracklist[$_]>.\n"
                     if($hiddenflag == 1);
               open(ERO,">>$outputdir/error.log")
                  or print "Can not append to file ",
                           "\"$outputdir/error.log\"!";
               print ERO "Track $riptrackno on CD $artist - $album ";
               print ERO "failed!\n";
               close(ERO);
               $failflag = 1;
            }
         }
         elsif($multi == 1) {
            $ripcom = "cdda2wav -D $cddev -H $ripopt -t $riptrackno \\
               \"$riptrackname\_rip\" \\
               2>> \"$logfile.$riptrackno.txt\"";
            $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0);
            unless(log_system("$ripcom")) {
               # Apend error message to file srXY for rip2m to start
               # checktrack.
               open(SRXY,">>$logfile")
                  or print "Can not append to file \"$logfile\"!";
               print SRXY "\ncdda2wav failed on $tracklist[$_ - 1] in "
                  if($hiddenflag == 0);
               print SRXY "\ncdda2wav failed on $tracklist[$_] in "
                  if($hiddenflag == 1);
               print SRXY "device $logfile";
               close(SRXY);
               # Create error message in CD-directory for encoder:
               # don't wait.
               open(ERO,">>$outputdir/error.log")
                  or print "Can not append to file ",
                           "\"$outputdir/error.log\"!";
               print ERO "Track $riptrackno on CD $artist - $album ";
               print ERO "failed!\n";
               close(ERO);
               # Kill failed CD only if it is not the last track.
               # Last track may be data/video track.
               # I.e. print error message to file srXY.Z.txt, checktrack
               # will grep for string
               # "cdparanoia failed" and kill the CD immediately!
               if($riptrackno != $tracksel[$#tracksel]) {
                  open(SRTF,">>$logfile.$riptrackno.txt")
                     or print "Can not append to file ",
                              "\"$logfile.$riptrackno.txt\"!";
                  print SRTF "cdda2wav failed on $tracklist[$_ - 1]\n"
                     if($hiddenflag == 0);
                  print SRTF "cdda2wav failed on $tracklist[$_]\n"
                     if($hiddenflag == 1);
                  print SRTF "in device $logfile, error !";
                  close(SRTF);
                  # Create on the fly error message in log-directory.
                  my $devnam = $cddev;
                  $devnam =~ s/.*dev.//;
                  open(ERO,">>$logpath/failed.log")
                     or print "Can not append to file ",
                              "\"$logpath/failed.log\"!";
                  print ERO "$artist;$album;$genre;$categ;$cddbid;";
                  print ERO "$devnam;$hostnam; Cdda2wav failure!\n";
                  close(ERO);
                  # Now wait to be terminated by checktrack.
                  sleep 360;
                  exit;
               }
            }
         }
      }
      elsif($ripper == 3 && $rip == 1) {
         $ripcom = "tosha -d $cddev -f wav -t $riptrackno \\
            -o \"$riptrackname.rip\"";
         $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0);
         unless(log_system("$ripcom")) {
            die "tosha failed on $tracklist[$_ - 1]";
         }
      }
      elsif($ripper == 4 && $rip == 1) {
         my $cdd_dev = $cddev;
         $cdd_dev =~ s/^\/dev\/r//;
         $cdd_dev =~ s/c$//;
         $ripcom = "cdd -t $riptrackno -q -f $cdd_dev - 2> /dev/null \\
                   | sox -t cdr -x - \"$riptrackname.rip\"";
         $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0);
         unless(log_system("$ripcom")) {
            die "cdd failed on $tracklist[$_ - 1]";
         }
      }
      elsif($rip == 1) {
         print "No CD Ripper defined";
      }

      # Rename rip file to a wav for encoder so that it will be picked
      # up by the encoder background process.
      #
      # Cdda2wav output is not easy to handle. Everything beyond a last
      # period . has been erased. Example: riptrackname is something
      # like "never ending...", then we assign cdda2wav in the above
      # section to rip a file called: "never ending..._rip", but
      # cdda2wav misbehaves and the file is called "never ending...".
      # Therefore we rename the ripped file to the standard name
      # riptrackname.rip first (if cdda2wav was used).
      if($ripper == 2) {
         if($riptrackname =~ /\./) {
            # But split is too clever! If a trackname ends with "bla..."
            # all points get lost, so we've to add a word at the end!
            my $cddatrackname = $riptrackname . "end";
            my @riptrackname = split(/\./, $cddatrackname);
            delete($riptrackname[$#riptrackname]);
            $cddatrackname = join('.',@riptrackname);
            rename("$cddatrackname.wav", "$riptrackname.rip");
         }
         else {
            rename("$riptrackname\_rip.wav", "$riptrackname.rip");
         }
      }
      if(-r "$riptrackname.rip") {
         $cdtocn = split_chunks($saveriptrackno, $riptrackname,
                 $shortname, $cdtocn) if($ghost == 1 && $failflag == 0);
      }
      rename("$riptrackname.rip", "$riptrackname.wav");
      md5_sum("$riptrackname.wav")
         if($md5sum == 1 && $normalize == 0 &&
            $wav == 1 && $failflag == 0);
      chmod oct($fpermission), "$riptrackname.wav" if($fpermission);
      chmod oct($fpermission), "$outputdir/cd.toc" if($fpermission);
      unlink("$logfile.$riptrackno.txt") if($multi == 1);
      $failflag = 0;

      if ($normalize == 0) {
         # Start the encoder in the background, but only once.
         if($startenc == 0 && $encode == 1) {
            my $encstart = sprintf("%02d:%02d",
               sub {$_[2], $_[1]}->(localtime));
#            my $encstart = `date \'+%R\'`;
            chomp $encstart;
            if($multi == 1) {
               open(SRXY,">>$logfile")
                  or print "Can not append to file \"$logfile\"!";
               print SRXY "\nEncoding started: $encstart";
               close(SRXY);
            }
            $startenc = 1;
            open(ENCLOG,">$outputdir/enc.log");
            close ENCLOG;
            unless(fork) {
               enc_cd();
            }
         }
      }
      # Print encoder messages saved in enc.log not to spoil the
      # ripper output.
      if($encode == 1) {
         open(ENCLOG, "< $outputdir/enc.log");
         my @loglines = <ENCLOG>;
         close ENCLOG;
         my $lincn = 0;
         my @outlines = ();
         foreach (@loglines) {
            if($verbose >= 3) {
               push(@outlines, $_)
                  if($lincn >= $encline && $_ !~ /^\n/);
            }
            elsif($verbose == 1 || $verbose == 2) {
               print $_ if($lincn >= $encline && $_ =~ /complete\./);
            }
            $lincn++;
         }
         # Compact output.
         $encline = $lincn;
         if($outlines[0] && $verbose >= 2) {
            push(@outlines, "*" x 47, "\n") if($verbose >= 3);
            unshift(@outlines, "*"x15, " Encoder reports ", "*"x15, "\n")
               if($verbose >= 3);
            print @outlines;
         }
      }
   }
   unlink("$outputdir/enc.log") if(-r "$outputdir/enc.log");

   # Hack to tell the child process that we are waiting for it to
   # finish.
   my $ripend = sprintf("%02d:%02d", sub {$_[2], $_[1]}->(localtime));
   open(ERR, ">>$outputdir/error.log")
      or print "Can not append to file error.log!\n";
   print ERR "The audio CD ripper reports: all done!\n";
   print ERR "Ripping  ended: $ripend\n";
   close(ERR);
   if($multi == 1) {
      open(SRXY,">>$logfile")
         or print "Can not append to file \"$logfile\"!";
      print SRXY "\nRipping complete: $ripend";
      close(SRXY);
   }
}
########################################################################
#
# Normalize the wav.
# Using normalize will disable parallel ripping & encoding.
#
sub norm_cd {

   print "Normalizing the wav-files...\n" if($verbose >= 1);
   my($norm,$normtrackname);

   # Generate filelist.
   foreach (@seltrack) {
      my $riptrackname = &get_trackname($_, $tracklist[$_ - 1]);
      $riptrackname = get_trackname($_, $tracklist[$_])
         if($hiddenflag == 1);
      # normalize is picky about certain characters - get them escaped!
      $riptrackname = esc_char($riptrackname);
      $normtrackname = "$normtrackname $riptrackname.wav";
   }

   $norm = "normalize $normopt -- $normtrackname";

   if(! system("$norm")) {
      print "\nNormalizing complete.\n" if($verbose >= 1);
   }
   else {
      die "\nNormalizing failed.\n" if($verbose >= 1);
   }
}
########################################################################
#
# Encode the wav.
# This runs as a separate process from the main program which
# allows it to continuously encode as the ripping is being done.
# The encoder will also wait for the ripped wav in-case the encoder
# is faster than the CDROM. In fact it will be waited 3 times the length
# of the track to be encoded.
#
sub enc_cd {

   my ($enc, $riptrackno, $riptrackname, $sufix);
   my ($albumlametag, $artislametag, $commentlametag, $tracklametag);
   my ($ripcomplete, $trackcn, $totalencs) = (0, 0, 0);
   my $lastskip = $tracksel[0];
   my $resumenc = $resume;
   my @md5tracks = ();  # List of tracks to be md5-checked.

   # Cleaning.
   my $albumtag = clean_all($cd{title});
   my $artistag = clean_all($cd{artist});
   my $album = $albumtag;
   my $artist = $artistag;
   $album = clean_name($album);
   $artist = clean_name($artist);
   $album = clean_chars($album) if($chars);
   $artist = clean_chars($artist) if($chars);
   $album =~ s/ /_/g if($underscore == 1);
   $artist =~ s/ /_/g if($underscore == 1);

   # Create special variables for Lame-tags because of UTF8 problem.
   if($utftag == 0) {
      $artislametag = back_encoding($artistag);
      $albumlametag = back_encoding($albumtag);
      $commentlametag = back_encoding($commentag);
   }
   else{
      $artislametag = $artistag;
      $albumlametag = $albumtag;
      $commentlametag = $commentag;
   }

   # Write a playlist file.
   my $playfile;
   if($playlist >= 1) {
      $playfile = "$artist" . " - " . "$album" . ".m3u";
      $playfile =~ s/ /_/g if($underscore == 1);
      open(PLIST, ">$outputdir$playfile");
      print PLIST "#EXTM3U\n";
   }


   my $ghostflag = 0;
   my $ghostcn = 0;
   # Start encoding each track.
   foreach (@tracksel) {
      # A lot of hacking for ghost songs. Remember, array @tracksel is
      # the original one, without ghost songs as long as we did not get
      # to the end. Once all tracks are donne, this array will be
      # updated if ghost songs were found.
      # Now: if only one track in the middle of the album has been
      # selected, problems occur if this track has ghostsongs. Why?
      # Because the updated array @tracksel will be e.g. 4 4 4 4 if the
      # track 4 has 3 ghost songs. But the track- and tag list arrays
      # have all tracknames in, so after the track number 4 will come
      # track number 5, but no track "04 name of track 5" exists, and
      # encoder fails! Once all tracks are done, one should set the
      # $ghostcn to the total number of tracks of the CD.
      $ghostflag = 2 if($ghostflag == 1 && $riptrackno >= $_);
      $ghostcn = $#{$cd{track}} + 1 if($ghostflag == 0);
      $riptrackno = $_;
      $trackcn++;

      $riptrackname = get_trackname($_, $tracklist[$_ - 1]);
      $riptrackname = get_trackname($_, $tracklist[$_])
         if($hiddenflag == 1);

      if($ghostflag >= 1){
         $ghostcn++;

         $riptrackname = get_trackname($_, $tracklist[$ghostcn - 1]);
         $riptrackname = get_trackname($_, $tracklist[$ghostcn])
            if($hiddenflag == 1);
      }

      # If we want to merge, skip a previously merged track:
      my $skipflag = 0;
      if($pmerge) {
         @skip = skip_tracks;
         foreach my $skip (@skip) {
            $skipflag = 1 if($_ == $skip);
         }
      }
      next if($skipflag == 1);
      $lastskip = $_;

      # Cosmetics for nice output.
      my @shortname = split(/\// , $riptrackname);
      my $shortname = $shortname[$#shortname];

      # lcdproc
      if($lcd == 1){
         my $_lcdtracks = scalar @tracksel;
         my $_lcdenctrack = $trackcn;
         my $lcdperc;
         if($_lcdtracks eq $_lcdenctrack) {
            $lcdperc = "*100";
         }
         else {
            $lcdperc = sprintf("%04.1f",$_lcdenctrack/$_lcdtracks*100);
         }
         $lcdline3=~ s/\|\d\d.\d/\|$lcdperc/;
         my $_lcdenctrackF = sprintf("%02d",$_lcdenctrack);
         $lcdline3=~ s/\E\d\d/\E$_lcdenctrackF/;
         substr($lcdline3,10,10) = substr($shortname,3,13);
         ulcd();
      }

      # Adjust encoding of tracktag for Lame.
      my $tracktag = $tracktags[$_ - 1];
      $tracktag = $tracktags[$_] if($hiddenflag == 1);
      if($ghostflag >= 1){
         $tracktag = $tracktags[$ghostcn - 1];
         $tracktag = $tracktags[$ghostcn] if($hiddenflag == 1);
      }

      if($utftag == 0) {
         $tracklametag = back_encoding($tracktag);
      }
      else{
         $tracklametag = $tracktag;
      }

      # If the file name was too long for ripper, look for special name.
      my $wavname = $riptrackname;
      if(length($riptrackname) > 230) {
         $wavname = get_trackname($_,$_."short");
      }

      # Check for tracks already done.
      my $checknextflag = 1;
      if($resumenc){
         my @sufix = ('mp3', 'ogg', 'flac', 'm4a');
         foreach my $c (@coder) {
            if(! -r "$riptrackname.$sufix[$c]") {
               $checknextflag = 0;
            }
            else{
               print "Found $riptrackname.$sufix[$c].\n"
                  if($verbose >= 1);
            }
            last if($checknextflag == 0);
         }
         if($checknextflag == 1 && $playlist >= 1){
            print PLIST "#EXTINF:$secondlist[$_ - 1],$tracktag\n"
               if($hiddenflag == 0);
            print PLIST "#EXTINF:$secondlist[$_],$tracktag\n"
               if($hiddenflag == 1);
            print PLIST "$riptrackname.suffix\n" if($playlist == 1);
            print PLIST "$shortname.suffix\n" if($playlist == 2);
            print PLIST "Add Ghost Song $_ Here.\n" if($ghost == 1);
         }
      }
      # Skip that track, i. e. restart the foreach-loop of tracks if a
      # compressed file (mp3, ogg, ma4, flac) was found.
      next if($resumenc && $checknextflag == 1);
      # Don't resume anymore, if we came until here.
      $resumenc = 0;

      # Keep looping until the wav file appears, i.e. wait for
      # ripper timeout. Timeout is 3 times the length of track
      # to rip/encode. Then leave that one and finish the job!
      my $slength = $secondlist[$_ - 1];
      my $mlength = (int($slength / 60) + 1) * 3;
      my $tlength = (int($slength / 10) + 6) * 3;

      # We don't need this for ghost songs, as they are done only when
      # the (original) last track was successfully ripped.
      my $dataflag = 0;
      my $xtime = 0;
      while(! -r "$wavname.wav" && $ghostflag == 0) {
         $xtime++;
         last if($xtime > $tlength);
         # Condition 1: Too long waiting for the track!
         if($xtime >= $tlength) {
            if($multi != 1) {
               print "Encoder waited $mlength minutes for file\n";
               print "$shortname.wav to appear, now giving up!\n";
               print "with $artist - $album in device $cddev\n";
               log_info("Encoder waited $mlength minutes for file");
               log_info("$shortname.wav to appear, now giving up!");
               log_info("with $artist - $album in device $cddev");
            }
            else {
               $xtime = 0;
               print "Encoder waited $mlength minutes for file\n";
               print "$shortname.wav to appear\n";
               print "with $artist - $album in device $cddev.\n";
               print "Don't worry, I continue the job!\n\n";
            }
         }
         sleep 10;
         # Condition 2: Check the error log!
         # If at this moment the ripper did not start with
         # the riptrackname.rip, assume it was a data track!
         # If cdparanoia failed on a data track, there will
         # be an entry in the error.log.
         # If dagrab gave error messages, but the wav file
         # was created, we won't get to this point, so don't
         # worry.
         if(-r "$outputdir/error.log") {
             open(ERR, "$outputdir/error.log")
               or print "error.log disappeared!\n";
            my @errlines = <ERR>;
            close ERR;
            my @errtrack = grep(/^Track $riptrackno /, @errlines);
            my $errtrack = "@errtrack";
            @errtrack = split(/ /, $errtrack);
            $errtrack = $errtrack[1];
            if($errtrack) {
               $xtime = $tlength + 1;
               $dataflag = 1;
               if($verbose >= 2) {
                  print "\nDid not detect track $errtrack";
                  print " ($shortname.rip), assume ripper failure!\n";
               }
               print "I will finish the job! Check the error.log!\n"
                  if($verbose >= 2 && $sshflag == 0);
            }
         }
      }
      # This is an other hack to update the track-arrays modifed by the
      # ripper if ghost songs were found. Is there another way to
      # communicate with the parent process?
      # This loop was supposed to be at the end of this sub-routine,
      # but we need it here in case of data tracks. The encoder would
      # stop here after a data track and fail to encode previously found
      # ghost songs because @tracksel has not yet been updated.
      if($ghost == 1 && $_ == $tracksel[$#tracksel]
                     && -r "$outputdir/ghost.log") {
         open(GHOST, "<$outputdir/ghost.log")
            or print "Can not read file ghost.log!\n";
         my @errlines = <GHOST>;
         close(GHOST);
         my @selines = grep(s/^Array seltrack: //, @errlines);
         @tracksel = split(/ /, $selines[$#selines]);
         chomp($_) foreach(@tracksel);
         my @seclines = grep(s/^Array secondlist: //, @errlines);
         @secondlist = split(/ /, $seclines[$#seclines]);
         chomp($_) foreach(@secondlist);
         @tracklist = grep(s/^Array tracklist: //, @errlines);
         chomp($_) foreach(@tracklist);
         @tracktags = grep(s/^Array tracktags: //, @errlines);
         chomp($_) foreach(@tracktags);
         unlink("$outputdir/ghost.log");
         $ghost = 0;
         $ghostflag = 1;
         $resumenc = $resume; # Continue to resume ghost songs.
      }

      # Jump to the next track if wav wasn't found. Note that the
      # $tlength does not exist for additional ghost songs, so don't
      # test this condition when encoding ghost songs, furthermore we
      # assume that ghost songs are present as soon as one was found.
      next if($ghostflag == 0 && $xtime >= $tlength || $dataflag == 1);

      if(length($riptrackname) > 230) {
         rename("$wavname.wav","$riptrackname.wav");
      }

      my $delwav = 0;

      my $starts = sprintf("%3d", sub {$_[1]*60+$_[0]}->(localtime));
#      my $starts = `date \'+%s\'`;
#      chomp $starts;

      if(-r "$outputdir/enc.log" && $ripcomplete == 0) {
         open(ENCLOG, ">>$outputdir/enc.log");
         print ENCLOG "\nEncoding \"$shortname\"...\n"
            if($verbose >= 3);
         close ENCLOG;
      }
      else {
         print "\nEncoding \"$shortname\"...\n" if($verbose >= 3);
      }

      my $failflag = 0;
      # Set the encoder(s) we are going to use.
      for(my $c=0; $c<=$#coder; $c++) {
         # Get the command for the encoder to use!
         if($coder[$c] == 0) {
            if($trackcn == 1) {
               if($preset) {
                  $lameopt = $lameopt . " --preset $preset";
               }
               else {
                  $lameopt = $lameopt . " --vbr-$vbrmode"
                     if($vbrmode);
                  $lameopt = $lameopt . " -b $bitrate"
                     if($bitrate ne "off");
                  $lameopt = $lameopt . " -B $maxrate"
                     if($maxrate != 0);
                  $lameopt = $lameopt . " -V $qualame"
                     if($qualame ne "off" && $vbrmode);
                  $lameopt = $lameopt . " -q $qualame"
                     if($qualame ne "off" && !$vbrmode);
               }
            }
            $enc = "lame $lameopt -S --tt \"$tracklametag\" \\
              --ta \"$artislametag\" --tl \"$albumlametag\" \\
              --ty \"$year\" --tg \"$genre\" --tn $riptrackno \\
              --tc \"$commentlametag\" --add-id3v2 \\
              \"$riptrackname.wav\" \\
              \"$riptrackname.mp3_enc\"";
            if(-r "$outputdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$outputdir/enc.log");
               print ENCLOG "\nLame $lameopt encoding track $trackcn" .
                     " of " . ($#tracksel + 1) . " " if($verbose >= 3);
               print ENCLOG "\@ quality $qualame\n"
                   if($verbose >= 3 && !$preset);
               print ENCLOG "\n" if($verbose >= 3 && $preset);
               close ENCLOG;
            }
            else {
               print "\nLame $lameopt encoding track $trackcn" .
                     " of " . ($#tracksel + 1) . " " if($verbose >= 3);
               print "\@ quality $qualame\n"
                  if($verbose >= 3 && !$preset);
               print "\n" if($verbose >= 3 && $preset);
            }
            log_info("new-mediafile: ${riptrackname}.mp3");
            $sufix = "mp3";
         }
         elsif($coder[$c] == 1) {
            if($trackcn == 1) {
               $oggencopt = $oggencopt . " -q $qualoggenc"
                  if($qualoggenc ne "off");
               $oggencopt = $oggencopt . " -M $maxrate"
                  if($maxrate != 0);
            }
            $enc = "oggenc $oggencopt -Q -t \"$tracktag\" \\
                -a \"$artistag\" -l \"$albumtag\" \\
                -d \"$year\" -G \"$genre\" \\
                -N $riptrackno -c \"DESCRIPTION=$commentag\" \\
                -o \"$riptrackname.ogg_enc\" \\
                \"$riptrackname.wav\"";
            if(-r "$outputdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$outputdir/enc.log");
               print ENCLOG "\nOggenc $oggencopt encoding track" .
                     " $trackcn of " . ($#tracksel + 1) . " " .
                     "\@ quality $qualoggenc\n" if($verbose >= 3);
               close ENCLOG;
            }
            else {
            print "\nOggenc $oggencopt encoding track $trackcn of " .
                  ($#tracksel + 1) . " \@ quality $qualoggenc\n"
               if($verbose >= 3);
            }
            log_info("new-mediafile: ${riptrackname}.ogg");
            $sufix = "ogg";
         }
         elsif($coder[$c] == 2) {
            if($trackcn == 1) {
               $flacopt = $flacopt . " -$quaflac" if($quaflac ne "off");
            }
            $enc = "flac $flacopt -s --tag=TITLE=\"$tracktag\" \\
              --tag=ARTIST=\"$artistag\" --tag=ALBUM=\"$albumtag\" \\
              --tag=DATE=\"$year\" --tag=TRACKNUMBER=\"$riptrackno\" \\
              --tag=GENRE=\"$genre\" --tag=CATEGORY=\"$categ\" \\
              --tag=DESCRIPTION=\"$commentag\" --tag=CDID=\"$cddbid\" \\
              -o \"$riptrackname.flac_enc\" \\
              \"$riptrackname.wav\"";
            if(-r "$outputdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$outputdir/enc.log");
               print ENCLOG "\nFlac $flacopt encoding track $trackcn" .
                     " of " . ($#tracksel + 1) . " " .
                     " \@ compression $quaflac\n" if($verbose >= 3);
               close ENCLOG;
            }
            else {
               print "\nFlac $flacopt encoding track $trackcn of " .
                     ($#tracksel + 1) . " \@ compression $quaflac\n"
                  if($verbose >= 3);
            }
            log_info("new-mediafile: ${riptrackname}.flac");
            $sufix = "flac";
         }
         elsif($coder[$c] == 3) {
            if($trackcn == 1) {
               $faacopt = $faacopt . " -q $quafaac"
                  if($quafaac ne "off");
            }
            $enc = "faac $faacopt -w --title \"$tracktag\" \\
              --artist \"$artist\" --album \"$album\" \\
              --year \"$year\" --genre \"$genre\" --track $riptrackno \\
              --comment \"$commentag\" \\
              -o \"$riptrackname.m4a_enc\" \\
              \"$riptrackname.wav\" ";
            if(-r "$outputdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$outputdir/enc.log");
               print ENCLOG "\nFaac $faacopt encoding track $trackcn " .
                     " of " . ($#tracksel + 1) . " " .
                     " \@ quality $quafaac\n" if($verbose >= 3);
               close ENCLOG;
            }
            else {
               print "\nFaac $faacopt encoding track $trackcn of " .
                     ($#tracksel + 1) . " \@ quality $quafaac\n"
                  if($verbose >= 3);
            }
            log_info("new-mediafile: ${riptrackname}.m4a");
            $sufix = "m4a";
         }
         $sufix = "mp3" if($sufix eq "");
         # Set "last encoding of track" - flag.
         $delwav = 1 if($wav == 0 && $c == $#coder);
         # Set nice if wished.
         $enc="nice -n $nice ".$enc if($nice != 0);
         # Make the output look nice, don't mess the messages!
         my $ripmsg = "The audio CD ripper reports: all done!";
         if($ripcomplete == 0 ) {
            if(-r "$outputdir/error.log") {
               open(ERR, "$outputdir/error.log")
                  or print "Can not open file error.log!\n";
               my @errlines = <ERR>;
               close ERR;
               my @ripcomplete = grep(/^$ripmsg/, @errlines);
               $ripcomplete = 1 if(@ripcomplete);
            }
         }

         # Finally, do the job of encoding.
         if($sshflag == 1) {
            enc_ssh($delwav,$enc,$riptrackname,$shortname,$sufix);
            if($md5sum == 1) {
               push(@md5tracks, "$riptrackname.$sufix");
               my @waitracks;
               foreach my $donetrack (@md5tracks) {
                  if( -r "$donetrack") {
                     md5_sum("$donetrack");
                     chmod oct($fpermission),
                        "${outputdir}MD5SUM_$sufix" if($fpermission);
                  }
                  else {
                     push(@waitracks, "$donetrack");
                  }
               }
               @md5tracks = @waitracks;
            }
         }
         else {
            if(log_system("$enc > /dev/null 2> /dev/null")) {
               if($ripcomplete == 0) {
                  if(-r "$outputdir/error.log") {
                     open(ERR, "$outputdir/error.log")
                        or print "Can open file error.log!\n";
                     my @errlines = <ERR>;
                     close ERR;
                     my @ripcomplete = grep(/^$ripmsg/, @errlines);
                     $ripcomplete = 1 if(@ripcomplete);
                  }
               }
               rename("$riptrackname.$sufix\_enc","$riptrackname.$sufix");
               chmod oct($fpermission), "$riptrackname.$sufix"
                  if($fpermission);
               md5_sum("$riptrackname.$sufix") if($md5sum == 1);
               chmod oct($fpermission), "${outputdir}MD5SUM_$sufix"
                  if($fpermission && $md5sum == 1);
               if(-r "$outputdir/enc.log" && $ripcomplete == 0) {
                  open(ENCLOG, ">>$outputdir/enc.log");
                  print ENCLOG "Encoding of \"$shortname.$sufix\" " .
                               "complete.\n" if($verbose >= 1);
                  close ENCLOG;
               }
               else {
                  print "Encoding of \"$shortname.$sufix\" " .
                        "complete.\n" if($verbose >= 1);
               }
            }
            else {
               print "Encoder failed on $tracklist[$_ - 1] in $cddev.",
                     "\nError message says: $?\n";
               $failflag = 1;
               if($multi == 1) {
                  # Print error message to file srXY.Z.txt, checktrack
                  # will grep for string "encoder failed" and kill the
                  # CD immediately!
                  open(SRTF,">>$logfile.$riptrackno.txt")
                     or print "Can not append to file ",
                              "\"$logfile.$riptrackno.txt\"!";
                  print SRTF "\nencoder failed on $tracklist[$_ - 1] ";
                  print SRTF "in device $cddev, error $? !";
                  close(SRTF);
                  # Create on the fly error message in log-directory.
                  my $devnam = $cddev;
                  $devnam =~ s/.*dev.//;
                  open(ERO,">>$logpath/failed.log")
                     or print "Can not append to file ",
                              "\"$logpath/failed.log\"!";
                  print ERO "$artist;$album;$genre;$categ;$cddbid;";
                  print ERO "$devnam;$hostnam; Encoder failure!\n";
                  close(ERO);
                  # Wait to be terminated by checktrack.
                  sleep 360;
               }
            }
            sleep 1;
         }
      }
      # Calculate time in seconds when encoding ended and total time
      # Encoder needed.
      my $endsec = sprintf("%3d", sub {$_[1]*60+$_[0]}->(localtime));
      $endsec += 60 while($endsec <= $starts);
      $totalencs = $totalencs + $endsec - $starts;
      # Delete the wav if not wanted.
      unlink("$riptrackname.wav") if($delwav == 1 && $sshflag == 0);
      # Write the playlist file. This is somehow tricky, if ghost songs
      # may appear. To ensure the files in the right order, introduce
      # placeholders for possible ghost songs.
      # The problem is, that the secondlist with the true track lengths
      # will only be updated, when the last track has been encoded (the
      # last track except ghost songs). But we need the true length
      # right now. So, if $ghost == 1, check for the ghost.log file at
      # any track.

      if($failflag == 0 && $playlist >= 1) {
         # Ghost songs follow after the last track, but $ghostflag was
         # set to 1 just before last track is encoded. Therefore set
         # $ghostflag to 2 after the last track has been done and
         # inserted in the playlist file as a regular file (below),
         # and insert sound files as ghost songs only when $ghostflag is
         # 2. If only the last song has been splitted into chunks and
         # the counter increased, continue to insert as regular file.
         if($ghostflag == 2) {
            print PLIST "GS$_:#EXTINF:$secondlist[$ghostcn - 1],",
                        "$tracktag\n"
               if($hiddenflag == 0);
            print PLIST "GS$_:#EXTINF:$secondlist[$ghostcn],$tracktag\n"
               if($hiddenflag == 1);
            print PLIST "GS$_:$riptrackname.suffix\n"
               if($playlist == 1);
            print PLIST "GS$_:$shortname.suffix\n" if($playlist == 2);
         }
         else {
            if($ghost == 1 && -r "$outputdir/ghost.log") {
               open(GHOST, "<$outputdir/ghost.log")
                  or print "Can not read file ghost.log!\n";
               my @errlines = <GHOST>;
               close(GHOST);
               my @seclines = grep(s/^Array secondlist: //, @errlines);
               @secondlist = split(/ /, $seclines[$#seclines]);
               chomp($_) foreach(@secondlist);
            }
            print PLIST "#EXTINF:$secondlist[$_ - 1],$tracktag\n"
               if($hiddenflag == 0);
            print PLIST "#EXTINF:$secondlist[$_],$tracktag\n"
               if($hiddenflag == 1);
            print PLIST "$riptrackname.suffix\n" if($playlist == 1);
            print PLIST "$shortname.suffix\n" if($playlist == 2);
            print PLIST "Add Ghost Song $_ Here.\n"
               if($ghost == 1 || $ghostflag == 1);
         }
      }
   }
   # Tell the mother process the encoding time.
   open(ERR, ">>$outputdir/error.log")
      or print "Can not append to file error.log!\n";
   print ERR "Encoding needed $totalencs seconds!\n";
   print ERR "md5: $_\n" foreach(@md5tracks);
   close(ERR);
   close(PLIST);
   exit ;
}
########################################################################
#
# Finish the M3U file used by players such as Amarok, Noatun, X11Amp...
#
sub create_m3u {
   my $playfile;
   my @mp3s = ();
   my $sufix = "";

   my $album = clean_all($cd{title});
   my $artist = clean_all($cd{artist});
   $album = clean_name($album);
   $artist = clean_name($artist);
   $album = clean_chars($album) if($chars);
   $artist = clean_chars($artist) if($chars);

   $playfile = "$artist" . " - " . "$album" . ".m3u";
   $playfile =~ s/ /_/g if($underscore == 1);

   open(PLIST, "<$outputdir$playfile")
      or print "Can not open file $outputdir$playfile!\n";
   my @playlines = <PLIST>;
   close(PLIST);
   my @ghosts = grep(/^GS\d+:/, @playlines);

   my @playlist = ();
   foreach (@playlines) {
      next if($_ =~ /^GS\d+:/ || $_ =~ /^$/);
      $_ =~ s/^Add Ghost Song (\d+) Here.$/$1/;
      chomp $_;
      if($_ =~ /^\d+$/) {
         foreach my $ghostsong (@ghosts) {
            if($ghostsong =~ s/^GS$_\://) { # Why not as a 1-liner?
               $ghostsong =~ s/^GS$_\://;
               chomp $ghostsong;
               push @playlist, $ghostsong;
            }
         }
      }
      else {
         push @playlist, $_;
      }
   }

   my $nplayfile;
   foreach my $c (@coder) {
      my @mp3s = @playlist;
      if($c == 1) {
         $_ =~ s/\.suffix$/.ogg/i foreach (@mp3s);
         $sufix = "ogg";
      }
      elsif($c == 2) {
         $_ =~ s/\.suffix/.flac/i foreach (@mp3s);
         $sufix = "flac";
      }
      elsif($c == 3) {
         $_ =~ s/\.suffix/.m4a/i foreach (@mp3s);
         $sufix = "m4a";
      }
      else {
         $_ =~ s/\.suffix/.mp3/i foreach (@mp3s);
         $sufix = "mp3";
      }
      if($#coder != 0){
         $nplayfile = $playfile;
         $nplayfile =~ s/\.m3u/ - $sufix\.m3u/ if($underscore == 0);
         $nplayfile =~ s/\.m3u/_-_$sufix\.m3u/ if($underscore == 1);
         unlink("$outputdir$playfile");
         open(PLST, ">$outputdir$nplayfile");
      }
      else {
         $nplayfile = $playfile;
         open(PLST, ">$outputdir$nplayfile");
      }
      print PLST "$_\n" foreach(@mp3s);
      close(PLST);
      chmod oct($fpermission), "$outputdir$nplayfile" if($fpermission);
   }
}
########################################################################
#
# Create a default or manual track list.
#
sub create_deftrack {
# Choose if you want to use default names or enter them manually.
# Do not ask if we come form CDDB submission, i.e. index == 0,
# or if $interaction == 0, then $index == 1.
   my ($i, $j, $index) = (0,1,@_);
   my ($album, $artist);

   my $tracks = substr($cddbid, 6);
   $tracks = hex($tracks);

   $album = clean_all($cd{title}) if(defined $cd{title});
   $artist = clean_all($cd{artist}) if(defined $cd{artist});

   # Preselect answer if no interaction whished.
   $index = 1 if($interaction == 0);

   while($index !~ /^[0-1]$/ ) {
      print "\nThis CD shall be labeled with:\n\n";
      print "1: Default Album, Artist and Tracknames\n\n";
      print "0: Manual input\n\nChoose [0-1]: (0) ";
      $index = <STDIN>;
      chomp $index;
      $index = 0 unless($index);
      print "\n";
   }
   # Create default tracklist and cd-hash.
   # NOTE: here we define an additional key: revision, which does not
   # exist if %cd is filled by CDDB_get. When this key exists, we know
   # that it is a new entry.
   if($index == 1) {
      $artist = "Unknown Artist";
      $album = "Unknown Album";
      %cd = (
         artist => $artist,
         title => $album,
         cat => $categ,
         genre => $genre,
         id => $cddbid,
         revision => 0,
         year => $year,
      );
      while($i < $tracks) {
         $j = $i + 1;
         $j = "0" . $j if($j < 10);
         $cd{track}[$i] = "Track " . "$j";
         ++$i;
      }
      $cddbsubmission = 0;
   }
   # Create manual tracklist.
   elsif($index == 0) {
      # In case of CDDB resubmission
      if(defined $cd{artist}) {
         print "\n   Artist ($artist): ";
      }
      # In case of manual CDDB entry.
      else {
         print "\n   Artist : ";
      }
      $artist = <STDIN>;
      chomp $artist;
      # If CDDB entry confirmed, take it.
      if(defined $cd{artist} && $artist eq "") {
         $artist = $cd{artist};
      }
      # If CDDB entry CHANGED, submission OK.
      elsif(defined $cd{artist} && $artist ne "") {
         $cddbsubmission = 1;
         $cd{artist} = $artist;
      }
      if($artist eq "") {
         $artist = "Unknown Artist";
         $cddbsubmission = 0;
      }
      if(defined $cd{title}) {
         print "\n   Album ($album): ";
      }
      else {
         print "\n   Album : ";
      }
      $album = <STDIN>;
      chomp $album;
      while($year !~ /^\d{4}$/) {
         if(defined $cd{year}) {
            print "\n   Year ($year): ";
         }
         else {
            print "\n   year : ";
         }
         $year = <STDIN>;
         chomp $year;
         last if($year eq "");
      }
      # If CDDB entry confirmed, take it.
      if(defined $cd{title} && $album eq "") {
         $album = $cd{title};
      }
      # If CDDB entry CHANGED, submission OK.
      elsif(defined $cd{title} && $album ne "") {
         $cddbsubmission = 1;
         $cd{title} = $album;
      }
      if($album eq "") {
         $album = "Unknown Album";
         $cddbsubmission = 0;
      }
      %cd = (
         artist => $artist,
         title => $album,
         cat => $categ,
         genre => $genre,
         id => $cddbid,
         revision => 0,
         year => $year,
      ) unless(defined $cd{title});
      print "\n";
      $i = 1;
      while($i <= $tracks) {
         if(defined $cd{track}[$i-1]) {
            printf("   Track %02d (%s): ", $i, $tracktags[$i-1]);
         }
         else {
            printf("   Track %02d: ", $i);
         }
         my $tracktag = <STDIN>;
         chomp $tracktag;
         $tracktag = clean_all($tracktag);
         my $track = $tracktag;
         $track = clean_name($track);
         $track = clean_chars($track) if($chars);
         $track = lower_case($_) if($lowercase == 1);
         $track =~ s/ /_/g if($underscore == 1);
         # If CDDB entry confirmed, take and replace it in tracklist.
         if(defined $cd{track}[$i-1] && $track ne "") {
            splice @tracklist, $i-1, 1, $track;
            splice @tracktags, $i-1, 1, $tracktag;
            $cddbsubmission = 1;
         }
         elsif(!$cd{track}[$i-1] && $track eq "") {
            $track = "Track " . sprintf("%02d", $i);
            $cddbsubmission = 0;
         }
         # Fill the "empty" array @{$cd{track}}.
         push @{$cd{track}}, "$track";
         $i++;
      }
      print "\n";
   }
   else {
      # I don't like die, but I don't like if-loops without else.
      # This should not happen because of previous while-loop!
      die "You should choose 0 or 1!\n\n";
   }
}
########################################################################
#
# Read the CD and generate a TOC with DiscID, track frames and total
# length. Then prepare CDDB-submission with entries from @tracklist.
#
sub pre_subm {
   my($check,$i,$ans,$genreno,$line,$oldcat,$subject) = (0,0);

   my $tracks = $#framelist;
   my $totals = int($framelist[$#framelist] / 75);

   my $album = clean_all($cd{title});
   my $artist = clean_all($cd{artist});

   my $revision = get_rev();
   if($revision) {
      # TODO: if submission fails, set revision back.
      $revision++;
   }
   elsif(defined $cd{revsision}) {
      $revision = $cd{revision};
   }
   else {
      $revision = 0;
   }

   # Check for CDDB ID vs CD ID problems.
   if($cddbid ne $cd{id} && defined $cd{id}) {
      print "\nObsolet warning: CDID ($cddbid) is not identical to ";
      print "CDDB entry ($cd{id})!";
      print "\nYou might get a collision error. Try anyway!\n";
      $revision = 0;
   }
   # Questioning to change CDDB entries and ask to fill missing fields.
   if(defined $cd{year} && $year ne "") {
      $year = get_answ("year",$year);
   }
   if(!$year) {
      while($year !~ /^\d{4}$| / || !$year ) {
      print "\nPlease enter the year (or none): ";
      $year = <STDIN>;
      chomp $year;
      $cd{year} = $year;
      last if(!$year);
      }
   }
   if($cd{year}) {
      $cddbsubmission = 1 unless($year eq $cd{year});
   }
   else {
      $cddbsubmission = 1;
   }
   # Ask if CDDB category shall be changed and check if done;
   # $categ will be an empty string if user wants to change it.
   $oldcat = $categ;
   if($cd{cat} && $categ) {
      $categ = get_answ("CDDB category",$categ);
   }

   my @categ = ();
   my @categories = (
      "blues",  "classical", "country", "data",
      "folk",   "jazz",      "misc",    "newage",
      "reggae", "rock",      "soundtrack"
   );
   if(!$categ && $submission != 0) {
      print "I check for available categories, please wait.";
      print "\n\nAvailable categories:\n";
      foreach $_ (@categories) {
         my $templines = "";
         my $source = "http://www.freedb.org/freedb/" .
                       $_ . "/" . $cddbid;
         $templines = LWP::Simple::get($source);
         # Question: what is wrong that I need to put a \n the print
         # command to force perl to print right away, and not to print
         # the whole bunch only when the foreach-loop is done???
         if($templines) {
            push @categ, $_;
         }
         else {
            print "   $_\n"
         }
      }
      if($categ[10]) {
         print "\nAll 11 categories are used, bad luck!";
         print "\nSave the file locally with --archive!\n";
         print "\nUse one of the following categories:";
         print "\nblues, classical, country, data, folk";
         print "\njazz, misc, newage, reggae, rock, soundtrack\n";
         $cddbsubmission = 0;
      }

      # Check if the $categ variable is correct.
      while($categ !~ /^blues$|^classical$|^country$|^data$|^folk$|
                      |^jazz$|^newage$|^reggae$|^rock$|^soundtrack$|
                      |^misc$/ ) {
         print "\nPlease choose one of the available CDDB categories: "
            if($categ[10]);
         print "\nPlease choose one of the categories: "
            unless($categ[10]);
         $categ = <STDIN>;
         chomp $categ;
      }
      $cddbsubmission = 1 unless($categ eq $cd{cat});
   }
   # If one changes catecory for a new submission, set Revision to 0.
   if($oldcat ne $categ && defined $cd{cat}){
      $revision = 0;
   }
   # Remind the user if genre is not ID3v2 compliant even if Lame is
   # not used! Reason: There should be no garbage genres in the DB.
   # If Lame is used, genre has already been checked!
   if($lameflag != 1 && defined $genre) {
      ($genre,$genreno) = check_genre($genre);
      $cddbsubmission = 1 unless($genre eq $cd{'genre'});
   }
   # Do not to ask if genre had been passed from command line.
   unless($pgenre) {
      $genre = get_answ("genre",$genre);
   }
   unless($genre) {
      print "\nPlease enter a valid CDDB genre (or none): ";
      $genre = <STDIN>;
      chomp $genre;
      $cd{genre} = $genre;
      # Allow to submit no genre! Else check it!
      if($genre) {
         $genre =~ s/[\015]//g;
         ($genre,$genreno) = check_genre($genre);
      }
   }
   $cddbsubmission = 1 unless($genre eq $cd{'genre'});
   my $dtitle = $artist . " / " . $album;
   substr($dtitle, 230, 0, "\nDTITLE=") if(length($dtitle) > 250);
   substr($dtitle, 460, 0, "\nDTITLE=") if(length($dtitle) > 500);

   # Start writing the CDDB submission.
   open(TOC, ">$homedir/cddb.toc")
      or die "Can not write to cddb.toc $!\n";
   print TOC "# xmcd CD database generated by RipIT\n";
   print TOC "#\n";
   print TOC "# Track frame offsets:\n";
   $i = 0;
   foreach (@framelist) {
      print TOC "# $_\n" if($i < $#framelist);
      $i++;
   }
   print TOC "#\n";
   print TOC "# Disc length: $totals seconds\n";
   print TOC "#\n";
   print TOC "# Revision: $revision\n";
   my $time = sprintf("%02d:%02d", sub {$_[2], $_[1]}->(localtime));
   my $date = sprintf("%04d-%02d-%02d",
      sub {$_[5]+1900, $_[4]+1, $_[3]}->(localtime));
   $date = $date . " at " . $time;
   print TOC "# Submitted via: RipIT $version ";
   print TOC "www.suwald.com/ripit/ripit.html on $date\n";
   print TOC "#\n";
   print TOC "DISCID=$cddbid\n";
   print TOC "DTITLE=$dtitle\n";
   print TOC "DYEAR=$year\n";
   if(defined $genre){
      print TOC "DGENRE=$genre\n";
   }
   elsif($genre eq "" && defined $categ) {
      print TOC "DGENRE=$categ\n";
   }
   $i = 0;
   foreach (@tracktags) {
      substr($_, 230, 0, "\nTTITLE$i=") if(length($_) > 250);
      substr($_, 460, 0, "\nTTITLE$i=") if(length($_) > 500);
      print TOC "TTITLE$i=$_\n";
      ++$i;
   }
   my @comment = extract_comm;
   my $commentest = "@comment";
   if($commentest) {
      $ans = "x";
      $check = 0;
      print "Confirm (Enter), delete or edit each comment line ";
      print "(c/d/e)!\n";
      foreach (@comment) {
         while($ans !~ /^c|^d|^e/) {
            print "$_ (c/d/e): ";
            $ans = <STDIN>;
            chomp $ans;
            if($ans eq "") {
               $ans = "c";
            }
         }
         if($ans =~ /^c/ || $ans eq "") {
            print TOC "EXTD=$_\\n\n";
            $check = 1;
         }
         elsif($ans =~ /^e/) {
            print "Enter a different line: \n";
            my $ans = <STDIN>;
            chomp $ans;
            substr($ans, 230, 0, "\nEXTD=") if(length($ans) > 250);
            substr($ans, 460, 0, "\nEXTD=") if(length($ans) > 500);
            print TOC "EXTD=$ans\\n\n";
            $cddbsubmission = 1;
            $check = 1;
         }
         else {
            # Don't print the line.
            $cddbsubmission = 1;
         }
         $ans = "x";
      }
      $line = "a";
      while(defined $line) {
         print "Do you want to add a line? (Enter for none or type!): ";
         $line = <STDIN>;
         chomp $line;
         $cddbsubmission = 1 if($line ne "");
         last if(!$line);
         substr($line, 230, 0, "\nEXTD=") if(length($line) > 250);
         substr($line, 460, 0, "\nEXTD=") if(length($line) > 500);
         print TOC "EXTD=$line\\n\n";
         $check = 1;
      }
      # If all lines have been deleted, add an empty EXTD line!
      if($check == 0){
         print TOC "EXTD=\n";
      }
   }
   # If there are no comments, ask to add some.
   elsif(!$comment[0]) {
      $line = "a";
      my $linecn = 0;
      while(defined $line) {
         print "Please enter a comment line (or none): ";
         $line = <STDIN>;
         chomp $line;
         $cddbsubmission = 1 if($line ne "");
         substr($line, 230, 0, "\nEXTD=") if(length($line) > 250);
         substr($line, 460, 0, "\nEXTD=") if(length($line) > 500);
         print TOC "EXTD=$line\n" if($linecn == 0);
         print TOC "EXTD=\\n$line\n" if($linecn != 0);
         $linecn++;
         # This line has to be written, so break the
         # while loop here and not before, as above.
         last if(!$line);
      }
   }
   else {
      print TOC "EXTD=\n";
   }

   # Extract the track comment lines EXTT.
   my @trackcom = grep(/^EXTT\d+=/, @{$cd{raw}});
   @trackcom = grep(s/^EXTT\d+=//, @trackcom);
   foreach (@trackcom) {
      chomp $_;
      $_ =~ s/[\015]//g;
   }
   $ans = get_answ('Track comment','existing ones');
   if($ans eq "") {
      $i = 0;
      while($i < $tracks) {
         my $track;
         if($trackcom[$i]) {
            printf("   Track comment %02d (%s):", $i+1, $trackcom[$i]);
         }
         else {
            printf("   Track comment %02d: ", $i+1);
         }
         $track = <STDIN>;
         chomp $track;
         substr($track, 230, 0, "\nEXTT$i=") if(length($track) > 250);
         substr($track, 460, 0, "\nEXTT$i=") if(length($track) > 500);

         # If CDDB entry confirmed, take and replace it in tracklist.
         if(defined $trackcom[$i] && $track eq "") {
            print TOC "EXTT$i=$trackcom[$i]\n";
         }
         elsif(defined $trackcom[$i] && $track ne "") {
            print TOC "EXTT$i=$track\n";
            $cddbsubmission = 1;
         }
         elsif($track ne "") {
            print TOC "EXTT$i=$track\n";
            $cddbsubmission = 1;
         }
         else {
            print TOC "EXTT$i=\n";
         }
         $i++;
      }
   }
   elsif(@trackcom) {
      $i = 0;
      foreach (@tracklist) {
         print TOC "EXTT$i=$trackcom[$i]\n";
         ++$i;
      }
   }
   else {
      $i = 0;
      foreach (@tracklist) {
         print TOC "EXTT$i=\n";
         ++$i;
      }
   }

   # Extract the playorder line.
   my @playorder = grep(/^PLAYORDER=/, @{$cd{raw}});
   @playorder = grep(s/^PLAYORDER=//, @playorder);
   if(@playorder) {
      my $playorder = $playorder[0];
      chomp $playorder;
      print TOC "PLAYORDER=$playorder\n";
   }
   else {
      print TOC "PLAYORDER=\n";
   }
   close(TOC);
   # Copy the *edited* CDDB file if variable set to the ~/.cddb/
   # directory.
   if($archive == 1 && $cddbsubmission != 2) {
      log_system("mkdir -m 0777 -p \"$homedir/.cddb/$categ\"")
         or print
         "Can not create directory \"$homedir/.cddb/$categ\": $!\n";
      log_system(
         "cp \"$homedir/cddb.toc\" \"$homedir/.cddb/$categ/$cddbid\""
         )
         or print
         "Can not copy cddb.toc to directory ",
         "\"$homedir/.cddb/$categ/$cddbid\": $!";
      print "Saved file $cddbid in \"$homedir/.cddb/$categ/\"";
   }
   print "\n";
   # If no connection to the internet do not submit.
   if($submission == 0) {
      $cddbsubmission = 0;
   }
   if($cddbsubmission == 1) {
      my $ans = "x";
      while($ans !~ /^y$|^n$/) {
         print "Do you really want to submit your data? [y/n] (y) ";
         $ans = <STDIN>;
         chomp $ans;
         if($ans eq ""){
            $ans = "y";
         }
      }
      if($ans =~ /^y/){
         $cddbsubmission = 1;
      }
      else{
         $cddbsubmission = 0;
      }
   }
   if($cddbsubmission == 1) {
      while($mailad !~ /.@.+[.]./) {
         print "\nReady for submission, enter a valid return ";
         print "e-mail address: ";
         $mailad = <STDIN>;
         chomp $mailad;
      }

      open TOC, "cat \"$homedir/cddb.toc\" |"
         or die "Can not open file $homedir/cddb.toc $!\n";
      my @lines = <TOC>;
      close TOC;

      $subject = "cddb " . $categ . " " . $cddbid;
      open MAIL, "|/usr/sbin/sendmail -t -r $mailad"
         or print "/usr/sbin/sendmail not installed? $!";

      # Generate the mail-header and add the toc-lines.
      print MAIL "From: $mailad\n";
      print MAIL "To: freedb-submit\@freedb.org\n";
#      print MAIL "To: test-submit\@freedb.org\n";
      print MAIL "Subject: $subject\n";
      print MAIL "MIME-Version: 1.0\n";
      print MAIL "Content-Type: text/plain; charset=$charset\n";
      print MAIL $_ foreach (@lines);
      close MAIL;
      print "Mail exit status not zero: $?" if($?);
      print "CDDB entry submitted.\n\n" unless($?);
      unlink("$homedir/cddb.toc");
   }
   elsif($cddbsubmission == 2) {
      print "\n CDDB entry created and saved in \$HOME, but not send, ";
      print "because no changes";
      print "\n were made! Please edit and send it manually to ";
      print "freedb-submit\@freedb.org";
      print "\n with subject: cddb $categ $cddbid\n\n";
      sleep (4);
   }
   else {
      print "\n CDDB entry saved in your home directory, but not send,";
      print "\n please edit it and send it manually to:";
      print "\n freedb-submit\@freedb.org with subject:";
      print "\n cddb $categ $cddbid\n\n";
   }
}
########################################################################
#
# Check if genre is correct.
#
sub check_genre {
   my $genre = $_[0];
   my $genreno = "";
   my $genrenoflag = 1;

   $genre = "  " if ($genre eq "");

   # If Lame is not used, don't die if ID3v2-tag is not compliant.
   if($lameflag == 0) {
      unless(log_system("lame --genre-list | grep -i \" $genre\$\" > /dev/null ")) {
         print "Genre $genre is not ID3v2 compliant!\n"
            if($verbose >= 1);
         print "I continue anyway!\n\n" if($verbose >= 1);
         $genreno = "not ID3v2 compliant!\n";
         chomp $genreno;
      }
      return ($genre,$genreno);
   }

   # If Lame is not installed, don't loop for ever.
   if($lameflag == -1) {
      $genreno = "Unknown.\n";
      chomp $genreno;
      return ($genre,$genreno);
   }

   # Check if (similar) genre exists. Enter a new one with interaction,
   # or take the default one.
   while(!log_system("lame --genre-list | grep -i \"$genre\" > /dev/null ")) {
      print "Genre $genre is not ID3v2 compliant!\n" if($verbose >= 1);
      if($interaction == 1) {
         print "Use \"lame --genre-list\" to get a list!\n";
         print "\nPlease enter a valid CDDB genre (or none): ";
         $genre = <STDIN>;
         chomp $genre;
         $cd{genre} = $genre;
      }
      else {
         print "Genre \"Other\" will be used instead!\n"
            if($verbose >= 1);
         $genre = "12 Other";
      }
   }

   if($genre eq "") {
      return;
   }
   elsif($genre =~ /^\d+$/) {
      $genre = `lame --genre-list | grep -i \' $genre \'`;
      chomp $genre;
   }
   else {
      # First we want to be sure that the genre from the DB, which might
      # be "wrong", e.g. wave (instead of Darkwave or New Wave) or synth
      # instead of Synthpop, will be correct. Put the DB genre to ogenre
      # and get a new right-spelled genre... Note, we might get several
      # possibilites, e.g. genre is Pop, then we get a bunch of
      # "pop-like" genres!
      # There will be a linebreak, if multiple possibilities found.
      my $ogenre = $genre;
      $genre = `lame --genre-list | grep -i \'$genre\'`;
      chomp $genre;
      # Second we want THE original genre, if it precisly exists.
      my $testgenre = `lame --genre-list | grep -i \'\^... $ogenre\$\'`;
      chomp $testgenre;
      $genre = $testgenre if($testgenre);
      # If we still have several genres:
      # Either let the operator choose, or if no interaction, take
      # default genre: "12 Other".
      if($genre =~ /\n/ && $interaction == 1) {
         print "More than one genre possibility found!\n";
         my @list = split(/\n/,$genre);
         my ($i,$j) = (0,1);
         while($i > $#list+1 || $i == 0) {
            # TODO: Here we should add the possibility to choose none!
            # Or perhaps to go back and choose something completely
            # different.
            foreach (@list) {
               printf(" %2d: $_ \n",$j);
               $j++;
            }
            $j--;
            print "\nChoose [1-$j]: ";
            $i = <STDIN>;
            chomp $i;
            $j = 1;
         }
         $genre = $list[$i-1];
         chomp $genre;
      }
      # OK, no interaction! Take the first or default genre!
      elsif($genre =~ /\n/ && $interaction != 1 && $lameflag == 1) {
         $genre = "12 Other" if($genre eq "");
         $genre =~ s/\n.*//;
      }
      # OK, the genre is not Lame compliant, and we do not care about,
      # because Lame is not used. Set the genre-number-flag to 0 to
      # prevent genre-number-extracting at the end of the subroutine.
      elsif($lameflag != 1) {
         $genre = $ogenre;
         $genrenoflag = 0;
      }
      chomp $genre;
   }

   # Extract genre-number.
   if($genre ne "" && $genrenoflag == 1){
      $genre =~ s/^\s*//;
      my @genre = split(/ /, $genre);
      $genreno = shift(@genre);
      $genre = "@genre";
   }
   return ($genre,$genreno);
}
########################################################################
#
# Check mirrors. Seems to be obsolet, therefore not used anymore.
#
sub check_host {
   while($mirror !~ /^freedb$|^at$|^au$|^ca$|^es$|^fi$|^fr$|^jp$|^jp2$|
                    |^ru$|^uk$|^uk2$|^us$/){
      print "host mirror ($mirror) not valid!\nenter freedb, ",
            "at, au, ca, es, fi, fr, jp, jp2, ru, uk, uk2 or us: ";
      $mirror = <STDIN>;
      chomp $mirror;
   }
}
########################################################################
#
# Reply to question
#
sub get_answ {
   my $ans = "x";
   while($ans !~ /^y|^n/) {
      print "Do you want to enter a different ".$_[0]." than ".$_[1];
      print "? [y/n], (n): ";
      $ans = <STDIN>;
      chomp $ans;
      if($ans eq ""){
         $ans = "n";
      }
   }
   if($ans =~ /^y/){
      return "";
   }
   return $_[1];
}
########################################################################
#
# Check quality passed from command line for oggenc, flac and lame only
# if vbr wanted (then it's encoder no 3).
#
sub check_quality {
   my $corrflag = 0;
   if($qualame ne "off") {
      while($qualame > 9) {
         print "\nThe quality $qualame is not valid for Lame!\n";
         print "Please enter a different quality (0 = best), [0-9]: ";
         $qualame = <STDIN>;
         chomp $qualame;
         $corrflag = 1;
      }
   }
   if($qualoggenc ne "off") {
      while($qualoggenc > 10 || $qualoggenc == 0) {
         print "\nThe quality $qualoggenc is not valid for Oggenc!\n";
         print "Please enter a different quality (10 = best), [1-10]: ";
         $qualoggenc = <STDIN>;
         chomp $qualoggenc;
         $corrflag = 1;
      }
   }
   if($quaflac ne "off") {
      while($quaflac > 8) {
         print "\nThe compression level $quaflac is not valid";
         print " for Flac!\n";
         print "Please enter a different compression level ";
         print "(0 = lowest), [0-8]: ";
         $quaflac = <STDIN>;
         chomp $quaflac;
         $corrflag = 1;
      }
   }
   if($quafaac ne "off") {
      while($quafaac > 500 || $quafaac < 10) {
         print "\nThe quality $quafaac is not valid for Faac!\n";
         print "Please enter a different quality (500 = max), ";
         print "[10-500]: ";
         $quafaac = <STDIN>;
         chomp $quafaac;
         $corrflag = 1;
      }
   }
   # Save the corrected values to the pquality array! Do it in
   # the same order the encoders had been passed! If no encoders
   # were passed, test if there are encoders in the config file...
   if($corrflag == 1) {
      @pquality = split(/,/, join(',', @pquality));
      for(my $index = 0; $index <= $#pquality; $index++) {
         if($coder[$index] =~ /^\d/) {
            $pquality[$index] = $qualame if($coder[$index] == 0);
            $pquality[$index] = $qualoggenc if($coder[$index] == 1);
            $pquality[$index] = $quaflac if($coder[$index] == 2);
            $pquality[$index] = $quafaac if($coder[$index] == 3);
         }
         else {
            $pquality[$index] = $qualame if($index == 0);
            $pquality[$index] = $qualoggenc if($index == 1);
            $pquality[$index] = $quaflac if($index == 2);
            $pquality[$index] = $quafaac if($index == 3);
         }
      }
      my $pquality = join(',',@pquality);
      @pquality = ();
      $pquality[0] = $pquality;
   }
}
########################################################################
#
# Check bitrate for Lame only if vbr is wanted.
#
sub check_vbrmode {
   while($vbrmode ne "new" && $vbrmode ne "old") {
      print "\nFor vbr using Lame choose *new* or *old*! (new): ";
      $vbrmode = <STDIN>;
      chomp $vbrmode;
      $vbrmode = "new" if ($vbrmode eq "");
   }
}
########################################################################
#
# Check preset for Lame only.
#
sub lame_preset {
   if($vbrmode eq "new") {
      $preset = "fast " . $preset;
   }
}
########################################################################
#
# Check if there is an other than $cddev which has a CD if no --device
# option was given.
#
sub check_cddev {
   # Try to get a list of possible CD devices.
   open(DEV, "/etc/fstab");
   my @dev = <DEV>;
   close DEV;
   @dev = grep(/^\s*\/dev/, @dev);
   @dev = grep(!/^\s*\/dev\/[f|h]d/, @dev);
   @dev = grep(!/sd/, @dev);
   my @devlist = ();
   foreach (@dev) {
      my @line = split(/\s/, $_);
      chomp $line[0];
      push(@devlist, $line[0]);
   }
   # First check some default addresses.
   if(open(CD, "$cddev")) {
      $cddev = $cddev;
      close CD;
   }
   elsif(open(CD, "/dev/cdrecorder")) {
      $cddev = "/dev/cdrecorder";
      close CD;
   }
   elsif(open(CD, "/dev/dvd")) {
      $cddev = "/dev/dvd";
      close CD;
   }
   else {
      foreach (@devlist) {
         if(open(CD, "$_")) {
            $cddev = $_;
            chomp $cddev;
            close CD;
         }
      }
   }
   # On a notebook, the tray could not be closed automatically!
   # Print error message and retry detection.
   if($cddev eq "") {
      print "Is there a CD and the tray of the device closed?\n";
      print "I pause 12 seconds.\n";
      sleep(12);
      foreach (@devlist) {
         if(open(CD, "$_")) {
            $cddev = $_;
            chomp $cddev;
            close CD;
         }
      }
   }
   if($cddev eq ""){
      print "Could not detect CD device! The default /dev/cdrom ";
      print "device will be used.\n";
      $cddev = "/dev/cdrom";
   }
}
########################################################################
#
# Check bitrate if bitrate is not zero.
#
sub check_bitrate {
   while($bitrate !~ /^32$|^40$|^48$|^56$|^64$|^80$|^96$|^112$|^128$|
                     |^160$|^192$|^224$|^256$|^320$|^off$/) {
      print "\nBitrate should be one of the following numbers or ";
      print "\"off\"! Please Enter";
      print "\n32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, ";
      print "256 or 320: (128) \n";
      $bitrate = <STDIN>;
      chomp $bitrate;
      if($bitrate eq "") {
         $bitrate = 128;
      }
   }
}
########################################################################
#
# Check protocol level for CDDB query.
#
sub check_proto {
   while($proto > 6) {
      print "Protocol level for CDDB query should be less-equal 6!\n";
      print "Enter an other value for protocol level (6): ";
      $proto =  <STDIN>;
      chomp $proto;
      $proto = 6 if($proto eq "");
   }
}
########################################################################
#
# Check, clean and sort the coder array, and then, according to, the
# qualities.
#
sub check_coder {
   my $lameindex;

   # Create encoder array if passed or read from config file.
   if(@pcoder) {
      @coder = split(/,/, join(',',@pcoder));
   }
   else {
      @coder = split /,/, join(',', @coder);
   }

   # Now check if an encoder was passed several times.
   my @scoder = sort {$a <=> $b} @coder;
   my $index = 0;
   while($index < $#scoder) {
      if($scoder[$index] == $scoder[$index+1]) {
         # TODO delete entry if encoder appears more than once.
#                    splice @coder,$index,1;
         die "Found encoder $scoder[$index] twice, confused!\n";
      }
      $index++;
   }
   # Now check, that there is no entry >= 4, find the index
   # of lame if present and calculate the number of encoders
   # that need a quality entry.
   my $nofcoders=0;
   for(my $index = 0; $index <= $#coder; $index++) {
      if($coder[$index] >= 4) {
         print "Encoder number $coder[$index] does not yet exist, ";
         print "please enter\n";
         die "0 for Lame, 1 for Oggenc, 2 for Flac and 3 for Faac!\n\n";
      }
      $nofcoders++;
      if($coder[$index] == 0 || $coder[$index] >= 4) {
         $lameindex = $index;
         $nofcoders = $nofcoders - 1;
         $lameflag = 1;
      }
   }
   # Use comma separated string to write the encoder array to the
   # config file!
   $wcoder = join(',', @coder);
   $nofcoders = $nofcoders - 1;
   # Order the qualities (if passed) in the same way as coders!
   # Note, the array of coders is NOT sorted!
   # First, check if there is a config file with an probably unusual
   # order of encoders. In this way, the check-quality subroutine
   # will ask the correct questions and not mess up the encoders!
   my $openflag="no";
   if(!@pcoder && -r "$homedir/.ripit/config") {
      open(CONF, "$homedir/.ripit/config");
      my @conflines = <CONF>;
      close CONF;
      @pcoder = grep(s/^coder=//, @conflines) unless(@pcoder);
      chomp @pcoder;
      if($pcoder[0] =~ /^\d/) {
         @coder = split(/,/, join(',',@pcoder));
      }
   }
   # Second, check if quality was passed.
   if(@pquality) {
      @quality = split(/,/, join(',', @pquality));
      # If lame was passed, but no quality for lame (because
      # user wants cbr), then add the default quality for lame
      # at the right place of array @quality to ensure that the
      # check_quality() subroutine works! NOTE: $nofcoder is
      # the number of non-lame encoders!
      if($#quality == $nofcoders && $lameflag == 1) {
         splice @quality, $lameindex, 0, 5;
      }
      for(my $index = 0; $index <= $#quality; $index++) {
         if($coder[$index] =~ /^\d/) {
            $qualame = $quality[$index] if($coder[$index] == 0);
            $qualoggenc = $quality[$index] if($coder[$index] == 1);
            $quaflac = $quality[$index] if($coder[$index] == 2);
            $quafaac = $quality[$index] if($coder[$index] == 3);
         }
         else {
            $qualame = $quality[$index] if($index == 0);
            $qualoggenc = $quality[$index] if($index == 1);
            $quaflac = $quality[$index] if($index == 2);
            $quafaac = $quality[$index] if($index == 3);
         }
      }
   }
}
########################################################################
#
# Over or re-write the config file (depends on option savenew or save).
#
sub save_config {
   log_system("mkdir -m 0777 -p $homedir/.ripit")
      or die "Can not create directory $homedir/.ripit/config: $!\n";
   my $ripdir = $homedir . "/.ripit";
   rename("$ripdir/config","$ripdir/config.old")
      if(-r "$ripdir/config");
   open(CONF, "> $ripdir/config")
      or die "Can not write to $homedir/.ripit/config: $!\n";
   print CONF "
#####
#
# RipIT $version configuration file.
#
# For further information on ripit
# configuration / parameters and examples see
# the manpage or type ripit --help
# or the README provided with ripit.


#####
#
# Ripping device & path.
#

# cddevice: define ripping device
# if other than /dev/cdrom
# Default: /dev/cdrom

cddevice=$cddev

# output: path for audio files
# Default: not set

output=$outputdir

# directory permissions: Permissions for directories
# Default: 0777

dpermission=$dpermission

# file permissions: Permissions for sound and log files
# Default: 0644

fpermission=$fpermission


#####
#
# Ripping options.
#

# ripper: select CD ripper
# 0 - dagrab
# 1 - cdparanoia
# 2 - cdda2wav
# 3 - tosha
# 4 - cdd
# Default: cdparanoia

ripper=$ripper

# ripopt: user definable options for the CD ripper
# Default: not set

ripopt=$ripopt

# paranoia: turn \"paranoia\" on or off for dagrab
# and cdparanoia
# Possible values: 0 - off, 1 - on
# Default: on

paranoia=$parano

# ghost: analyze the wavs for possible gaps and
# split the wav into chunks of sound
# Possible values: 0 - off, 1 - on
# Default: off

ghost=$ghost

# prepend: enlarge the the chunk of sound by a number of
# seconds at the beginning (if possible).
# Possible values: any positive number
# Default: 2

prepend=$prepend

# extend: enlarge the the chunk of sound by a number of
# seconds at the end (if possible).
# Possible values: any positive number
# Default: 2

extend=$extend


#####
#
# Encoding options
#

# encode: encode the wavs
# Possible values: 0 - off, 1 - on
# Default: on

encode=$encode

# coder: Select encoders for audio files
# 0 - Lame (mp3)
# 1 - Oggenc (ogg)
# 2 - Flac (flac)
# 3 - Faac (m4a)
# Multiple encoders can be selected by giving
# a comma-separated list
# Example: coder=0,1,2 encodes CD to mp3, ogg and flac files
# Default: Lame

coder=$wcoder

###
#
# lame (mp3) encoder options
#

# qualame: Sets audio quality for lame encoder
# in vbr (variable bitrate) mode
# Possible values: 0...9, off
# 0: higest quality
# 9: lowest quality
# Can be set to \"off\" if preset is used
# Default: 5

qualame=$qualame

# lameopt: Additional options for lame encoder
# Default: not set

lameopt=$lameopt

# vbrmode: Enable varibale bitrate for lame encoder
# Values: \"old\" or \"new\"
# Default: not set

vbrmode=$vbrmode

# bitrate: Sets bitrate for lame encoder
# Possible values: 32...320, off
# Should be set to \"off\" if vbr is used
# Default: 128

bitrate=$bitrate

# maxrate: Sets maximum bitrate for lame (when using vbr)
# and oggenc
# Possible values: 0 - off, 32...320
# Default: 0

maxrate=$maxrate

# preset: Use lame presets
# To set the \"fast\" switch, use --vbrmode new.
# Possible values: medium, standard, extreme, insane
#
# medium: 160kbps
# standard: 192kbps
# extreme: 256kbps
# insane: 320kbps
#
# Default: not set

preset=$wpreset

###
#
# oggenc (ogg) encoder options
#

# qualoggenc: Sets audio quality for oggenc
# Possible values: 1..10, off
# 1: lowest quality
# 10: highest quality
# Can be set to \"off\"
# Default: 3

qualoggenc=$qualoggenc

# oggencopt: Additional options for oggenc
# Default: not set

oggencopt=$oggencopt

###
#
# flac (lossless) encoder options
#

# quaflac: Sets audio quality for flac encoder
# Possible values: 0...8, off
# 0: highest quality
# 8: lowest quality
# Can be set to \"off\"
# Default: 5

quaflac=$quaflac

# flacopt: Additional options for flac encoder
# Default: not set

flacopt=$flacopt

###
#
# faac (m4a) encoder options
#

# quafaac: Sets audio quality for faac encoder
# Possible values: 10...500, off
# 500: highest quality
# 10: lowest quality
# Can be set to \"off\"
# Default: 100

quafaac=$quafaac

# faacopt: Additional options for faac encoder
# Default: not set

faacopt=$faacopt


#####
#
# Trackname and directory template
#

# dirtemplate: Template for directory structure
# The template can be created using the following
# variables (tags)
# \$album
# \$artist
# \$genre
# \$trackname
# \$tracknum
# \$year
# Example: \"\$artist - \$year\"
# The double quotes (\") are mandatory!
# Default: \"\$artist - \$album\"

dirtemplate=$dirtemplate

# tracktemplate: Template for track names
# \"tracktemplate\" is used similarly to \"dirtemplate\"
# Default:  \"\$tracknum \$trackname\"

tracktemplate=$tracktemplate

# infolog: Log certain operations to file
# (e.g. system calls, creation of dirs/files)
# Possible values: filename (full path, no ~ here!)
# Default: not set

infolog=$infolog

# lowercase: Convert filenames to lowercase
# Possible values: 0 - off, 1 - on
# Default: off

lowercase=$lowercase

# underscore: Replace blanks in filenames with undersocres
# Possible values: 0 - off, 1 - on
# Default: off

underscore=$underscore

# chars: Exclude special characters and (ending!) periods
# in file names. If no argument passed to option, then
# following characters will be purged:   :*#?\$\!   and
# (ending) periods deleted. Else only the passed ones
# will be erased, and (ending) periods. No need to escape
# the special characters here, if one enters them manually.
# Possible values: none, any (?)
# Default: not set

chars=$chars

# playlist: Create m3u playlist with or without the full path
# in the filename.
# Possible values: 0 - off,
                   1 - on with full path
#                  2 - on with no path (filename only)
# Default: on

playlist=$playlist


#####
#
# Audio file tagging
#

# year-tag: State a year (mp3, m4a) or a date (ogg, flac) tag.
# Possible values: integer
# Default: not set

year=$year

# comment-tag: State a comment (mp3, m4a) or a
# description (ogg, flac) tag.
# Possible values: any string
# Default: not set

comment=$commentag

# utftag: Use Lame-tags in UTF-8 or convert them
# (but not the filenames) from Unicode to ISO8859-1.
# Use when your mp3-audio player doesn't support Unicode tags.
# Recommended with Lame.
# Possible values: 0 - off, 1 - on
# Default: on

utftag=$utftag


#####
#
# CDDB options
#

# CDDBHOST: Specifies the CDDB server
# Note: Full name of the server used is \$mirror.\$CDDBHOST
# E.g., default server is freedb.freedb.org
# Default: freedb.org

CDDBHOST=$CDDB_HOST

# mirror: Selects freedb mirror
# Possible values: \"freedb\" or any freedb mirrors
# See www.freedb.org for mirror list
# Note: Full name of the server used is \$mirror.\$CDDBHOST
# E.g., default server is freedb.freedb.org
# Default: freedb

mirror=$mirror

# trasfer: Set transfer mode for cddb queries
# Possible values: cddb, http
# Default: cddb

transfer=$transfer

# proto: Set CDDP protocol level
# Possible values: 5,6
# Protocol level 6 supports Unicode (UTF-8)
# Default: 6

proto=$proto

# proxy: Address of http-proxy, if needed
# Default: not set

proxy=$proxy

# mailad: Mail address for cddb submissions
# Valid user email address for submitting cddb entries
# Default: not set

mailad=$mailad

# archive: Read and save cddb data on local machine
# Possible values: 0 - off, 1 - on
# Default: off

archive=$archive

# submission: Submit new or edited cddb entries to
# freeCDDB
# Possible values: 0 - off, 1 - on
# Default: on

submission=$submission

# interaction: Turns on or off user interaction in cddb dialog
# Possible values: 0 - off, 1 - on
# Default: on

interaction=$interaction


#####
#
# LCD options
#

# lcd: Use lcdproc to display status on LCD
# Possible values: 0 - off, 1 - on
# Default: off

lcd=$lcd

# lcdhost: Specify the lcdproc host
# Default: localhost

lcdhost=$lcdhost

# lcdport: Specify port number for $lcdhost
# Default: 13666

lcdport=$lcdport


#####
#
# Distributed ripping options
#

# sshlist: Comma separated list of remote machines
# that ripit shall use for encoding
# The output path must be the same for all machines.
# Specify the login (login\@machine) only if not the
# same for the remote machine. Else just state the
# machine names.
# Default: not set

sshlist=$wsshlist

# scp: Copy files to encode to the remote machine
# Use if the fs can not be accessed on the remote machines
# Possible values: 0 - off, 1 - on
# Default: off

scp=$scp

# local: Turn off encoding on local machine,
# e.g. use only remote machines
# Possible values: 0 - off, 1 - on
# Example: local=0 (off) turns off encoding on the
# local machine
# Default: on

local=$local


#####
#
# Misc. options
#

# verbosity: Run silent (do not output comments, status etc.) (0), with
# minimal (1), normal without encoder msgs (2), normal (3), verbose (4)
# or extremly verbose (5)
# Possible values: 0...5
# Default: 3 - normal

verbose=$verbose

# eject: Eject cd after finishing encoding
# Possible values: 0 - off, 1 - on
# Default: off

eject=$eject

# ejectcmd: Command used to eject and close CD tray
# Possible values: string
# Example: /usr/sbin/cdcontrol for FreeBSD
# Default: eject

ejectcmd=$ejectcmd

# ejectopt: Options to command used to eject or close CD
# Possible values: string
# Note: Don't use options -t / close or eject,
#       RipIT knows when to eject or load the tray
# Default: the cddevice

ejectopt=$ejectopt

# loop: Continue as soon a new CD is inserted,
# implies that the CD is ejected when done!
# Possible values: 0 - off, 1 - on
# Default: off

loop=$loop

# halt: Powers off machine after finishing encoding
# Possible values: 0 - off, 1 - on
# Default: off

halt=$halt

# nice: Sets \"nice\" value for the encoding process
# Possible values: 0..19 for normal users,
#                  -20..19 for user \"root\"
# Default: 0

nice=$nice

# nicerip: Sets \"nice\" value for the ripping process
# Possible values: 0..19 for normal users,
#                  -20..19 for user \"root\"
# Default: 0

nicerip=$nicerip

# core: Comma separated list of numbers giving maximum
# of allowed encoder processes to run at the same time
# (on each machine when using sshlist).
# Possible values: comma separated integers
# Default: 1

core=$wcore

# md5sum: Create file with md5sums for each type of sound files
# Possible values: 0 - off, 1 - on
# Default: off

md5sum=$md5sum

# wav: Don't delete wave-files after encoding
# Possible values: 0 - off, 1 - on
# Default: off

wav=$wav

# normalize: Normalizes the wave-files to a given dB-value
# (Default: -12dB)
# See http://normalize.nongnu.org for details
# Possible values: 0 - off, 1 - on
# Default: off

normalize=$normalize

# normopt: Options to pass to normalize
# Possible values: -a -nndB   : Normalize to -nn dB, default is -12dB,
#                  Value range: All values <= 0dB
#                  Example    : normalize -a -20dB *.wav
#                  -b         : Batch mode - loudness differences
#                               between individual tracks of a CD are
#                               maintained
#                  -m         : Mix mode - all track are normalized to
#                               the same loudness
#                  -v         : Verbose operation
#                  -q         : Quiet operation
# For further options see normalize documentation.
# Default: -bv

normopt=$normopt

# cdtoc: Create a toc (cue) file to burn the wavs with
# cd-text using cdrdao or cdrecord (in dao mode)
# Possible values: 0 - off, 1 - on
# Default: off

cdtoc=$cdtoc
\n";
   close CONF;
}
########################################################################
#
# Read the config file, take the parameters only if NOT yet defined!
#
sub read_config {
   my $ripdir = $homedir."/.ripit/config";
   if(-r "$ripdir" ) {
      open(CONF, "$ripdir") || print "Can not read config file!\n";
      my @conflines = <CONF>;
      close CONF;
      my @confver = grep(s/^# RipIT //, @conflines);
      @confver = split(/ /, $confver[0]) if($confver[0] =~ /^\d/);
      my $confver = $confver[0] if($confver[0] =~ /^\d/);
      $confver = 0 unless($confver);
      chomp $confver;
      if($version ne $confver && $savepara == 0) {
         $verbose = 3 if($verbose <= 1);
         print "\nPlease update your config-file with option --save";
         print "\nto ensure correct settings! I pause 3 seconds!\n\n";
         grep(s/^chars=[01]\s*$/chars=/, @conflines);
         sleep(3);
      }
      elsif($version ne $confver) {
         grep(s/^chars=[01]\s*$/chars=/, @conflines);
      }
      my @archive = grep(s/^archive=//, @conflines);
      $archive = $archive[0] unless defined $parchive;
      chomp $archive;
      my @bitrate = grep(s/^bitrate=//, @conflines);
      $bitrate = $bitrate[0] unless($pbitrate);
      chomp $bitrate;
      my @maxrate = grep(s/^maxrate=//, @conflines);
      $maxrate = $maxrate[0] unless($pmaxrate);
      chomp $maxrate;
      my @cddev = grep(s/^cddevice=//, @conflines);
      $cddev = $cddev[0] unless($pcddev);
      chomp $cddev;
      my @cdtoc = grep(s/^cdtoc=//, @conflines);
      $cdtoc = $cdtoc[0] unless($pcdtoc);
      chomp $cdtoc;
      my @chars = grep(s/^chars=//, @conflines) if($chars eq "xxx");
      $chars = $chars[0] if($chars[0]);
      chomp $chars;
      my @commentag = grep(s/^comment=//, @conflines);
      $commentag = $commentag[0] unless($pcommentag);
      chomp $commentag;
      my @CDDB_HOST = grep(s/^CDDBHOST=//, @conflines);
      $CDDB_HOST = $CDDB_HOST[0] unless($PCDDB_HOST);
      chomp $CDDB_HOST;
      @pcoder = grep(s/^coder=//, @conflines) unless(@pcoder);
      # NOTE: all coders are in array entry $pcoder[0]!
      # NOTE: we have to fill the w_RITE_coder variable!
      $wcoder = $pcoder[0] if(@pcoder);
      chomp $wcoder;
      my @dirtemplate = grep(s/^dirtemplate=//, @conflines);
      $dirtemplate = $dirtemplate[0] unless($pdirtemplate);
      chomp $dirtemplate;
      my @dpermission = grep(s/^dpermission=//, @conflines);
      $dpermission = $dpermission[0] unless($pdpermission);
      chomp $dpermission if($dpermission);
      my @eject = grep(s/^eject=//, @conflines);
      $eject = $eject[0] unless defined $peject;
      chomp $eject;
      my @ejectcmd = grep(s/^ejectcmd=//, @conflines);
      $ejectcmd = $ejectcmd[0] unless defined $pejectcmd;
      chomp $ejectcmd if($ejectcmd);
      my @ejectopt = grep(s/^ejectopt=//, @conflines);
      $ejectopt = $ejectopt[0] unless defined $pejectopt;
      chomp $ejectopt if($ejectopt);
      my @encode = grep(s/^encode=//, @conflines);
      $encode = $encode[0] unless defined $pencode;
      chomp $encode;
      my @extend = grep(s/^extend=//, @conflines);
      $extend = $extend[0] unless defined $pextend;
      chomp $extend if($extend);
      my @fpermission = grep(s/^fpermission=//, @conflines);
      $fpermission = $fpermission[0] unless($pfpermission);
      chomp $fpermission if($fpermission);
      my @ghost = grep(s/^ghost=//, @conflines);
      $ghost = $ghost[0] unless defined $pghost;
      chomp $ghost if($ghost);
      my @halt = grep(s/^halt=//, @conflines);
      $halt = $halt[0] unless($phalt);
      chomp $halt;
      my @infolog = grep(s/^infolog=//, @conflines);
      $infolog = $infolog[0] unless($pinfolog);
      chomp $infolog;
      my @interaction = grep(s/^interaction=//, @conflines);
      $interaction = $interaction[0] unless defined $pinteraction;
      chomp $interaction;
      my @lcd = grep(s/^lcd=//, @conflines);
      $lcd = $lcd[0] unless defined $plcd;
      chomp $lcd;
      my @lcdhost = grep(s/^lcdhost=//, @conflines);
      $lcdhost = $lcdhost[0] unless($plcdhost);
      chomp $lcdhost;
      my @lcdport = grep(s/^lcdport=//, @conflines);
      $lcdport = $lcdport[0] unless($plcdport);
      chomp $lcdport;
      my @local = grep(s/^local=//, @conflines);
      $local = $local[0] unless defined $plocal;
      chomp $local;
      my @loop = grep(s/^loop=//, @conflines);
      $loop = $loop[0] unless defined $ploop;
      chomp $loop;
      my @lowercase = grep(s/^lowercase=//, @conflines);
      $lowercase = $lowercase[0] unless defined $plowercase;
      chomp $lowercase;
      my @mailad = grep(s/^mailad=//, @conflines);
      $mailad = $mailad[0] unless($pmailad);
      chomp $mailad;
      my @mirror = grep(s/^mirror=//, @conflines);
      $mirror = $mirror[0] unless($pmirror);
      chomp $mirror;
      my @normalize = grep(s/^normalize=//, @conflines);
      $normalize = $normalize[0] unless defined $pnormalize;
      chomp $normalize;
      my @normopt = grep(s/^normopt=//, @conflines);
      $normopt = $normopt[0] unless($pnormopt);
      chomp $normopt;
      my @nice = grep(s/^nice=//, @conflines);
      $nice = $nice[0] unless defined $pnice;
      chomp $nice;
      my @nicerip = grep(s/^nicerip=//, @conflines);
      $nicerip = $nicerip[0] unless defined $pnicerip;
      chomp $nicerip if($nicerip);
      my @outputdir = grep(s/^output=//, @conflines);
      $outputdir = $outputdir[0] unless($poutputdir);
      chomp $outputdir;
      my @parano = grep(s/^paranoia=//, @conflines);
      $parano = $parano[0] unless defined $pparano;
      chomp $parano;
      my @playlist = grep(s/^playlist=//, @conflines);
      $playlist = $playlist[0] unless defined $pplaylist;
      chomp $playlist;
      my @prepend = grep(s/^prepend=//, @conflines);
      $prepend = $prepend[0] unless defined $pprepend;
      chomp $prepend if($prepend);
      my @preset = grep(s/^preset=//, @conflines);
      $preset = $preset[0] unless($ppreset);
      # NOTE: we have to fill the w_RITE_preset variable!
      $wpreset = $preset[0] unless($ppreset);
      chomp $preset;
      chomp $wpreset;
      my @proto = grep(s/^proto=//, @conflines);
      $proto = $proto[0] unless($pproto);
      chomp $proto;
      my @proxy = grep(s/^proxy=//, @conflines);
      $proxy = $proxy[0] unless($pproxy);
      chomp $proxy;
      my @quafaac = grep(s/^quafaac=//, @conflines) unless(@pquality);
      $quafaac = $quafaac[0] unless(@pquality);
      chomp $quafaac;
      my @quaflac = grep(s/^quaflac=//, @conflines) unless(@pquality);
      $quaflac = $quaflac[0] unless(@pquality);
      chomp $quaflac;
      my @qualame = grep(s/^qualame=//, @conflines) unless(@pquality);
      $qualame = $qualame[0] unless(@pquality);
      chomp $qualame;
      # This search-string is stared to prevent warnings when used with
      # older config files. Should be changed to ^qualoggenc= in 3.8.0.
      # Introduced like this in 3.6.0.
      my @qualoggenc = grep(s/^qualogg.*=//, @conflines)
         unless(@pquality);
      $qualoggenc = $qualoggenc[0] unless(@pquality);
      chomp $qualoggenc;
      my @faacopt = grep(s/^faacopt=//, @conflines);
      $faacopt = $faacopt[0] unless($pfaacopt);
      chomp $faacopt;
      my @flacopt = grep(s/^flacopt=//, @conflines);
      $flacopt = $flacopt[0] unless($pflacopt);
      chomp $flacopt;
      my @lameopt = grep(s/^lameopt=//, @conflines);
      $lameopt = $lameopt[0] unless($plameopt);
      chomp $lameopt;
      # This search-string is stared to prevent warnings when used with
      # older config files. Should be changed to ^oggencopt= in 3.8.0.
      # Introduced like this in 3.6.0.
      my @oggencopt = grep(s/^ogg.*opt=//, @conflines);
      $oggencopt = $oggencopt[0] unless($poggencopt);
      chomp $oggencopt;
      my @ripper = grep(s/^ripper=//, @conflines);
      $ripper = $ripper[0] unless defined $pripper;
      chomp $ripper;
      my @ripopt = grep(s/^ripopt=//, @conflines);
      $ripopt = $ripopt[0] unless defined $pripopt;
      chomp $ripopt;
      my @clist = grep(s/^core=//, @conflines) unless($pcore[0]);
      chomp @clist;
      # NOTE: all core numbers are in array entry $clist[0]!
      @core = split(/,/, join(',',@clist));
      my @rlist = grep(s/^sshlist=//, @conflines) unless($psshlist[0]);
      chomp @rlist;
      # NOTE: all machine names are in array entry $rlist[0]!
      @sshlist = split(/,/, join(',',@rlist));
      my @scp = grep(s/^scp=//, @conflines);
      $scp = $scp[0] unless defined $pscp;
      chomp $scp;
      my @submission = grep(s/^submission=//, @conflines);
      $submission = $submission[0] unless defined $psubmission;
      chomp $submission;
      my @transfer = grep(s/^transfer=//, @conflines);
      $transfer = $transfer[0] unless($ptransfer);
      chomp $transfer;
      my @tracktemplate = grep(s/^tracktemplate=//, @conflines);
      $tracktemplate = $tracktemplate[0] unless($ptracktemplate);
      chomp $tracktemplate;
      my @underscore = grep(s/^underscore=//, @conflines);
      $underscore = $underscore[0] unless defined $punderscore;
      chomp $underscore;
      my @utftag = grep(s/^utftag=//, @conflines);
      $utftag = $utftag[0] unless defined $putftag;
      chomp $utftag;
      my @vbrmode = grep(s/^vbrmode=//, @conflines);
      $vbrmode = $vbrmode[0] unless($pvbrmode);
      chomp $vbrmode;
      my @year = grep(s/^year=//, @conflines);
      $year = $year[0] unless($pyear);
      chomp $year;
      my @wav = grep(s/^wav=//, @conflines);
      $wav = $wav[0] unless defined $pwav;
      chomp $wav;
   }
   else {
      print "\nNo config file found! Use option --save to create one.\n"
         if($verbose >= 2);
   }
}

########################################################################
#
# Change encoding of tags back to iso-8859-1.
#
sub back_encoding {
   my $temp_file = "/tmp/utf8-$$\.txt";
   open(TMP, ">$temp_file") or print "$temp_file $!";
   print TMP $_[0];
   close TMP;
   my $decoded = `/usr/bin/iconv -f UTF-8 -t ISO-8859-1 $temp_file`;
   unlink("/tmp/utf8-$$\.txt");
   return $decoded;
}
########################################################################
#
# Check the preset options.
#
sub check_preset {
   if($preset !~ /^\d/) {
      while($preset !~ /^insane$|^extreme$|^standard$|^medium$/) {
         print "\nPreset should be one of the following words! Please";
         print " Enter \ninsane (320\@CBR), extreme (256), standard";
         print " (192) or medium (160), (standard): ";
         $preset = <STDIN>;
         chomp $preset;
         if($preset eq "") {
            $preset = "standard";
         }
      }
   }
   else {
      while($preset !~ /^32$|^40$|^48$|^56$|^64$|^80$|^96$|^112$|^128$|
                       |^160$|^192$|^224$|^256$|^320$/) {
         print "\nPreset should be one of the following numbers!",
               " Please Enter \n32, 40, 48, 56, 64, 80, 96, 112, 128,",
               " 160, 192, 224, 256 or 320, (128):\n";
         $preset = <STDIN>;
         chomp $preset;
         if($preset eq "") {
            $preset = 128;
         }
      }
   }
   $preset = "medium" if($preset =~ /\d+/ && $preset == 160);
   $preset = "standard" if($preset =~ /\d+/ && $preset == 192);
   $preset = "extreme" if($preset =~ /\d+/ && $preset == 256);
   $preset = "insane" if($preset =~ /\d+/ && $preset == 320);
   $wpreset = $preset;
}
########################################################################
#
# Check sshlist of remote machines and create a hash.
#
sub check_sshlist {
   if(@psshlist) {
      @sshlist = split(/,/, join(',', @psshlist));
   }
   if(@pcore) {
      @core = split(/,/, join(',', @pcore));
   }
   $wcore = join(',', @core);
   if(@sshlist || $core[0] > 1) {
      $sshflag = 1;
      $wsshlist = join(',', @sshlist);
      # Create a hash with all machines and the number of encoding
      # processes each machine is able to handle.
      $sshlist{'local'} = $core[0] if($local == 1);
      my $corecn = 1;
      foreach (@sshlist) {
         $core[$corecn] = 1 unless($core[$corecn]);
         $sshlist{$_} = $core[$corecn];
         $corecn++;
      }
   }
   else {
      $sshflag = 0;
   }
}
########################################################################
#
# Dispatcher for encoding on remote machines. If there are no .lock
# files, a ssh command will be passed, else the dispatcher waits until
# an already passed ssh command terminates and removes the lock file.
# The dispatcher checks all machines all 6 seconds until a machine is
# available. If option --scp is used, the dispatcher will not start an
# other job while copying. In this situation, it looks like nothing
# would happen, but it's only during scp.
#
sub enc_ssh {
   my $machine;
   my @codwav = ();
   my $delwav = $_[0];
   my $enccom = $_[1];
   my $ripnam = $_[2];
   my $shortname = $_[3];
   my $sufix = $_[4];
   my $old_outputdir = $outputdir;
   my $old_ripnam = $ripnam;
   my $esc_name;
   my $esc_dir;
   my $corecn;

   $sshflag = 2;
   while ($sshflag == 2) {
      # Start on the local machine first.
      $corecn = 1;
      for($corecn = 1; $corecn <= $core[0]; $corecn++) {
         if(! -r "$outputdir/local.lock_$corecn") {
            if($local == 1) {
               $sshflag = 1;
               $machine = "local";
               push @codwav, "$shortname";
            }
         }
         last if($sshflag == 1);
      }
      last if($sshflag == 1);
      $corecn = 1;
      foreach $_ (keys %sshlist) {
         $machine = $_; # Why this?
         for($corecn = 1; $corecn <= $sshlist{$_}; $corecn++) {
            if(! -r "$outputdir/$machine.lock_$corecn") {
               $sshflag = 1;
            }
            # Prepare array @codwav with all tracknames in, which are
            # still in progress, i. e. either being ripped or encoded.
            else {
               open(LOCK, "$outputdir/$machine.lock_$corecn");
               my @locklines = <LOCK>;
               close LOCK;
               my $locklines = $locklines[0] if($locklines[0]);
               chomp $locklines;
               # Push trackname into array only if not yet present.
               my @presence = grep(/$locklines/, @codwav);
               my $presence = $presence[0];
               push @codwav, "$locklines" if(!$presence);
            }
            last if($sshflag == 1);
         }
         last if($sshflag == 1);
      }
      last if($sshflag == 1);
      sleep 3;
   }
   # Untested patch by joni-@gmx.de
   # He reported problems while deleting the local lock files on a nfs.
   # Could not reproduce problems.
   #$esc_name = esc_char($shortname.".".$sufix);
   #$esc_dir  = esc_char($outputdir$machine.".lock");
   #log_system("ssh $machine 'echo \"$esc_name\" > $esc_dir'");
   if(-r "$outputdir/enc.log" && $verbose >= 3) {
      open(ENCLOG, ">>$outputdir/enc.log");
      print ENCLOG "...on machine $machine.\n"
         if($#core > 0);
      print ENCLOG "Executing scp command to $machine.\n"
         if($scp && $machine !~ /^local$/);
      close ENCLOG;
   }
   elsif($verbose >= 3) {
      print "...on machine $machine.\n"
         if($scp && $machine !~ /^local$/);
   }
   open(LOCKF, ">$outputdir$machine.lock_$corecn");
   print LOCKF "$shortname.$sufix\n";
   close (LOCKF);

   # We need more quotes for the commands (faac,flac,lame,ogg)
   # passed to the remote machine. NOTE: But now pay attention
   # to single quotes in tags. Quote them outside of single quotes!
   # TODO: Please tell me how to quote leading periods, thanks!!!
   if($machine !~ /^local$/) {
      $enccom =~ s/'/'\\''/g;
      $enccom = "'".$enccom."'";
      $enccom = "ssh $machine " . $enccom;
      if($scp) {
         # CREATE the directory:
         # Please tell me why one has to quote the double quotes
         # with a backslash when using ssh!
         $outputdir = esc_char($outputdir);
         log_info("new-outputdir: $outputdir on $machine created.");
         log_system("ssh $machine mkdir -p \\\"$outputdir\\\"");
         # COPY the File:
         # Please tell me why one should NOT quote a file name
         # with blanks when using scp!
         $ripnam = esc_char($ripnam);
         log_system("scp $ripnam.wav \\
           $machine:\"$ripnam.wav\" \\
           > /dev/null 2> /dev/null");
      }
   }
   $enccom = $enccom . " > /dev/null";
   # Because Lame comes with the "Can't get "TERM" environment string"
   # error message, I decided to switch off all error output. This is
   # not good, if ssh errors appear, then RipIT may hang with a message
   # "Checking for lock files". If this happens, switch to vervosity 4
   # and look what's going on.
   $enccom = $enccom . " 2> /dev/null" if($verbose <= 3);
   if($machine !~ /^local$/ && $scp) {
      $enccom = $enccom . " && \\
              scp $machine:\"$ripnam.$sufix\_enc\" \\
              $ripnam.$sufix > /dev/null 2> /dev/null && \\
              ssh $machine rm \"$ripnam.*\" ";
   }
   $enccom = $enccom . " && \\
              mv \"$ripnam.$sufix\_enc\" \"$ripnam.$sufix\""
      if($machine eq "local" || ($machine !~ /^local$/ && !$scp));
   $enccom = $enccom . " && \\
              rm \"$old_outputdir$machine.lock_$corecn\" &";
   print "\nWill execute command on machine $machine and try to encode",
         "\n$ripnam.$sufix\_enc.\n" if($verbose >= 4);
   log_system("$enccom");
   sleep 3; # Don't mess up with possible error-msgs from remote hosts.

   $outputdir = $old_outputdir;
   $ripnam = $old_ripnam;
   # Delete the wav only if all encodings of this track are done!
   # When the (last) encoding of a track started, its name is pushed
   # into the array @delname. Then the first (oldest) entry of the same
   # array (@delname) will be compared to the @codwav array. If this
   # entry is still present in the codewav-array, nothing happens, else
   # the wav file will be deleted and the trackname shifted out of the
   # @delname.
   if($delwav == 1) {
      push @delname, "$shortname";
      my $delflag = 0;
      while ($delflag == 0) {
         my $delname = $delname[0];
         my @delwav = grep(/$delname/, @codwav);
         if(!$delwav[0] && $#delname > 1) {
            unlink("$outputdir/$delname.wav");
            log_info("File $outputdir$delname.wav deleted.");
            shift(@delname);
            # Prevent endless loop if array is empty.
            $delflag = 1 if(!$delwav[0]);
         }
         else {
            $delflag = 1;
         }
      }
   }
}
########################################################################
#
# Delete wavs if sshlist was used. TODO: Improve code for following
# situation: if no .lock files are found, but the encoder did not yet
# finish, don't delete the wavs. Do it only after 3*4 seconds timeout
# with no .lock file.
#
sub del_wav {
   my $waitflag = 1;
   sleep 3;
   print "\nChecking for remaining lock files.\n" if($verbose >= 2);
   while ($waitflag <= 3) {
      sleep 3;
      opendir(DIR, "$outputdir");
      my @locks = readdir(DIR);
      closedir(DIR);
      @locks = grep { /\.lock_\d+$/ } @locks;
      $waitflag++ if(! @locks);
      $waitflag = 0 if(@locks);
   }
   if($wav == 0) {
      print "Deleting the wavs...\n" if($verbose >= 1);
      opendir(DIR, "$outputdir");
      my @wavs = readdir(DIR);
      closedir(DIR);
      @wavs = grep { /\.wav$/ } @wavs;
      foreach (@wavs) {
         unlink("$outputdir$_");
         log_info("File $outputdir$_ deleted.");
      }
   }
   if($scp) {
      my $old_outputdir = $outputdir;
      $outputdir = esc_char($outputdir);
      foreach $_ (keys %sshlist) {
         my $machine = $_;
         log_system("ssh $machine rmdir \\\"$outputdir\\\" > /dev/null 2> /dev/null")
            if($machine !~ /^local$/);
      }
      $outputdir = $old_outputdir;
   }
}
########################################################################
#
# LCDproc subroutines, all credits to Max Kaesbauer. For comments and
# questions contact max [dot] kaesbauer [at] gmail [dot] com.
#

# print

sub plcd {
   my ($data) = @_;
   print $lcdproc $data."\n";
   my $res = <$lcdproc>;
}

# update

sub ulcd {
   if($lcdoline1 ne $lcdline1) {
      $lcdoline1 = $lcdline1;
      plcd("widget_set ripitlcd line1 1 2 {$lcdline1}");
       }
   if($lcdoline2 ne $lcdline2) {
      $lcdoline2 = $lcdline2;
      plcd("widget_set ripitlcd line2 1 3 {$lcdline2}");
   }
   if($lcdoline3 ne $lcdline3) {
      $lcdoline3 = $lcdline3;
      plcd("widget_set ripitlcd line3 1 4 {$lcdline3}");
   }
}

# init

sub init_lcd {
   $lcdproc = IO::Socket::INET->new(
      Proto     => "tcp",
      PeerAddr  => $lcdhost,
      PeerPort  => $lcdport,
   ) || die "Can not connect to LCDproc port\n";
   $lcdproc->autoflush(1);
   sleep 1;

   print $lcdproc "hello\n";
   my @lcd_specs = split(/ /,<$lcdproc>);
   my %screen;

   $screen{wid} = $lcd_specs[7];
   $screen{hgt} = $lcd_specs[9];
   $screen{cellwid} = $lcd_specs[11];
   $screen{cellhgt} = $lcd_specs[13];

   $screen{pixwid} = $screen{wid}*$screen{cellwid};
   $screen{pixhgt} = $screen{hgt}*$screen{cellhgt};

   fcntl($lcdproc, F_SETFL, O_NONBLOCK);

   plcd("client_set name {ripit.pl}");
   plcd("screen_add ripitlcd");
   plcd("screen_set ripitlcd name {ripitlcd}");

   plcd("widget_add ripitlcd title title");
   plcd("widget_set ripitlcd title {RipIT $version}");

   plcd("widget_add ripitlcd line1 string");
   plcd("widget_add ripitlcd line2 string");
   plcd("widget_add ripitlcd line3 string");
}
########################################################################
#
# Read the CDDB on the local machine.
#
sub read_entry {
   my ($album, $artist, $trackno);
   my $logfile = $_[0];
   open(LOG, "<$logfile") || print "Can't open $logfile\n";
   my @cddblines = <LOG>;
   close(LOG);
   %cd = ();
   # Note that long lines may be splited into several lines
   # all starting with the same keyword, e.g. DTITLE.
   if($multi == 0) {
      $cd{raw} = \@cddblines;
      my @artist = grep(s/^DTITLE=//g, @cddblines);
      $artist = join(' / ',@artist);
      $artist =~ s/[\015]//g;
      $artist =~ s/\n\s\/\s//g;
      chomp $artist;
      # Artist is just the first part before first occurrence of
      # the slash (/), album gets all the rest!
      my @disctitle = split(/\s\/\s/, $artist);
      $artist = shift(@disctitle);
      $album = join(' / ',@disctitle);
      chomp $artist;
      chomp $album;
      $categ = $_[1];
      my @genre = grep(s/^DGENRE=//, @cddblines);
      $genre = $genre[0] if($genre eq "");
      $genre =~ s/[\015]//g;
      chomp $genre;
      my @year = grep(s/^DYEAR=//, @cddblines);
      $year = $year[0] if($year eq "");
      $year =~ s/[\015]//g;
      chomp $year;
      $trackno = $_[2];
   }
   else {
      my @artist = grep(s/^artist:\s//i, @cddblines);
      $artist = $artist[0];
      chomp $artist;
      my @album = grep(s/^album:\s//i, @cddblines);
      $album = $album[0];
      chomp $album;
      my @category = grep(s/^category:\s//i, @cddblines);
      $categ = $category[0];
      chomp $categ;
      my @genre = grep(s/^genre:\s//i, @cddblines);
      $genre = $genre[0];
      chomp $genre;
      my @year = grep(s/^year:\s//i, @cddblines);
      $year = $year[0];
      chomp $year;
      my @cddbid = grep(s/^cddbid:\s//i, @cddblines);
      $cddbid = $cddbid[0];
      chomp $cddbid;
      my @trackno = grep(s/^trackno:\s//i, @cddblines);
      $trackno = $trackno[0];
      chomp $trackno;
   }
   $cd{artist} = $artist;
   $cd{title} = $album;
   $cd{cat} = $categ;
   $cd{genre} = $genre;
   $cd{id} = $cddbid;
   $cd{year} = $year;
   my $i=1;
   my $j=0;
   while($i <= $trackno){
      my @track = ();
      @track = grep(s/^TTITLE$j=//, @cddblines) if($multi == 0);
      @track = grep(s/^track\s$i:\s//i, @cddblines) if($multi == 1);
      my $track = join(' / ',@track);
      $track =~ s/[\015]//g;
      $track =~ s/\n\s\/\s//g;
      chomp $track;
      $cd{track}[$j]=$track;
      $i++;
      $j++;
   }
}
########################################################################
#
# Delete error.log if there is no Track comment in!
#
sub del_erlog {
   if(-r "$outputdir/error.log") {
      open(ERR, "$outputdir/error.log")
        or print "error.log disappeared!\n";
      my @errlines = <ERR>;
      close ERR;
      my @md5tracks = grep(s/^md5: //, @errlines) if($md5sum == 1);
      if(@md5tracks){
         md5_sum("$_") foreach (@md5tracks);
      }
      my @ulink = grep(/^Track /, @errlines);
      if(!@ulink && $multi == 0) {
         unlink("$outputdir/error.log");
      }
      elsif($fpermission) {
         chmod oct($fpermission), "$outputdir/error.log";
      }
   }
}
########################################################################
#
# Escape special characters when using scp.
#
sub esc_char {
   $_[0] =~ s/\(/\\\(/g;
   $_[0] =~ s/\)/\\\)/g;
   $_[0] =~ s/\[/\\\[/g;
   $_[0] =~ s/\]/\\\]/g;
   $_[0] =~ s/\&/\\\&/g;
   $_[0] =~ s/\!/\\\!/g;
   $_[0] =~ s/\?/\\\?/g;
   $_[0] =~ s/\'/\\\'/g;
   $_[0] =~ s/ /\\ /g;
   return $_[0];
}
########################################################################
#
# Calculate how much time ripping and encoding needed.
#
sub cal_times{
   my $encend = `date \'+%R\'`;
   chomp $encend;
   # Read times from the file $outputdir/error.log.
   open(ERR, "$outputdir/error.log")
      or print "error.log disappeared!\n";
   my @errlines = <ERR>;
   close ERR;
   my @enctime = grep(s/^Encoding needed //, @errlines);
   my @ripstart = grep(s/^Ripping  started: //, @errlines);
   my @ripend = grep(s/^Ripping  ended: //, @errlines);
   my @ghostnote = grep(s/^Ghostflag = //, @errlines);
   my @splitnote = grep(s/^Splitflag = //, @errlines);
   $ghostnote[0] = 0 unless($ghostnote[0]);
   $splitnote[0] = 0 unless($splitnote[0]);

   @ripstart = split(/:/, $ripstart[0]);
   @ripend = split(/:/, $ripend[0]);
   my $riptime = ($ripend[0] * 60 + $ripend[1]) -
                 ($ripstart[0] * 60 + $ripstart[1]);

   my $enctime = "@enctime";
   chomp $enctime;
   if($encode == 1) {
      @enctime = split(/ /, $enctime);
      $enctime = int($enctime[0]/60);
   }
   else {
      $enctime = 0;
   }
   return ($riptime,$enctime,$encend,$ghostnote[0],$splitnote[0]);
}
########################################################################
#
# Thanks to mjb: log info to file.
#
sub log_info {
   if(!defined($infolog)){ return; }
   elsif($infolog eq ""){ return; }
   open(SYSLOG, ">> $infolog") || die "Can't open info log file\n";
   print SYSLOG "@_\n";
   close(SYSLOG);
}
########################################################################
#
# Thanks to mjb and Stefan Wartens improvements:
# used throughout in place of system().
#
sub log_system {
   my $P_command=shift;

   if($verbose >= 4){
      print "system: $P_command\n\n";
   }
   log_info("system: $P_command");

   system($P_command);

   # system() returns several pieces of information about the launched
   # subprocess squeezed into a 16-bit integer:
   #
   #     7  6  5  4  3  2  1  0  7  6  5  4  3  2  1  0
   #   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   #   |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  [ $? ]
   #   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   #    \_____________________/ \/ \__________________/
   #          exit code        core    signal number
   #
   # To get the exit code, use        ($? >> 8)
   # To get the signal number, use    ($? & 127)
   # To get the dumped core flag, use ($? & 128)

   # Subprocess has been executed successfully.
   return 1 if $? == 0;

   # Subprocess was killed by SIGINT (CTRL-C). Exit RipIT.
   die "\n\nRipit caught a SIGINT.\n" if(( $? & 127) == 2);

   # Subprocess could not be executed or failed.
   return 0;
}
########################################################################
#
# Special characters in cd.toc file won't be written correctly by
# cdrdao, so change them to octal! Please let me know how to do it in
# a better way!
#
sub oct_char{
   $_[0] =~ s/À/\\300/g;
   $_[0] =~ s/Á/\\301/g;
   $_[0] =~ s/Â/\\302/g;
   $_[0] =~ s/Ã/\\303/g;
   $_[0] =~ s/Ä/\\304/g;
   $_[0] =~ s/Å/\\305/g;
   $_[0] =~ s/Æ/\\306/g;
   $_[0] =~ s/Ç/\\307/g;
   $_[0] =~ s/È/\\310/g;
   $_[0] =~ s/É/\\311/g;
   $_[0] =~ s/Ê/\\312/g;
   $_[0] =~ s/Ë/\\313/g;
   $_[0] =~ s/Ì/\\314/g;
   $_[0] =~ s/Í/\\315/g;
   $_[0] =~ s/Î/\\316/g;
   $_[0] =~ s/Ï/\\317/g;
   $_[0] =~ s/Ð/\\320/g;
   $_[0] =~ s/Ñ/\\321/g;
   $_[0] =~ s/Ò/\\322/g;
   $_[0] =~ s/Ó/\\323/g;
   $_[0] =~ s/Ô/\\324/g;
   $_[0] =~ s/Õ/\\325/g;
   $_[0] =~ s/Ö/\\326/g;
   $_[0] =~ s/×/\\327/g;
   $_[0] =~ s/Ø/\\330/g;
   $_[0] =~ s/Ù/\\331/g;
   $_[0] =~ s/Ú/\\332/g;
   $_[0] =~ s/Û/\\333/g;
   $_[0] =~ s/Ü/\\334/g;
   $_[0] =~ s/Ý/\\335/g;
   $_[0] =~ s/Þ/\\336/g;
   $_[0] =~ s/ß/\\337/g;
   $_[0] =~ s/à/\\340/g;
   $_[0] =~ s/á/\\341/g;
   $_[0] =~ s/â/\\342/g;
   $_[0] =~ s/ã/\\343/g;
   $_[0] =~ s/ä/\\344/g;
   $_[0] =~ s/å/\\345/g;
   $_[0] =~ s/æ/\\346/g;
   $_[0] =~ s/ç/\\347/g;
   $_[0] =~ s/è/\\350/g;
   $_[0] =~ s/é/\\351/g;
   $_[0] =~ s/ê/\\352/g;
   $_[0] =~ s/ë/\\353/g;
   $_[0] =~ s/ì/\\354/g;
   $_[0] =~ s/í/\\355/g;
   $_[0] =~ s/î/\\356/g;
   $_[0] =~ s/ï/\\357/g;
   $_[0] =~ s/ð/\\360/g;
   $_[0] =~ s/ñ/\\361/g;
   $_[0] =~ s/ò/\\362/g;
   $_[0] =~ s/ó/\\363/g;
   $_[0] =~ s/ô/\\364/g;
   $_[0] =~ s/õ/\\365/g;
   $_[0] =~ s/ö/\\366/g;
   $_[0] =~ s/÷/\\367/g;
   $_[0] =~ s/ø/\\370/g;
   $_[0] =~ s/ù/\\371/g;
   $_[0] =~ s/ú/\\372/g;
   $_[0] =~ s/û/\\373/g;
   $_[0] =~ s/ü/\\374/g;
   $_[0] =~ s/ý/\\375/g;
   $_[0] =~ s/þ/\\376/g;
   return $_[0];
}
########################################################################
#
# Check if there is a CD in the CD device.
#
sub cd_present {
#   require "ioctl.ph";
   sysopen(CD, $cddev, O_RDONLY | O_NONBLOCK)
      or die "Can not open device $cddev: $!\n";
   my $os = `uname -s`;
   my $CDROMREADTOCHDR=0x5305;            # Linux
   if($os eq "SunOS") {
      $CDROMREADTOCHDR=0x49b;
   }
   elsif($os =~ /BSD/i) {
      $CDROMREADTOCHDR=0x40046304;
   }
   my $tochdr = "";
   my $err = ioctl(CD, $CDROMREADTOCHDR, $tochdr);
   close(CD);
   return $err;
}
########################################################################
#
# A hack to reinitialize global variables before starting a new loop.
#
sub init_var {
   $categ            = "";
   $cddbid           = 0;
   @framelist        = ();
   @secondlist       = ();
   @tracklist        = ();
   @tracktags        = ();
   @seltrack         = ();
   @tracksel         = ();
   %cd               = ();
   $cddbsubmission   = 2;
   $hiddenflag       = 0;
   $outputdir        = $poutputdir;
}
########################################################################
#
# Get the revision number of the CDDB entry.
#
sub get_rev {
   my @revision = grep(/^\#\sRevision:\s/, @{$cd{raw}});
   @revision = grep(s/^\#\sRevision:\s//, @revision);
   my $revision = $revision[0];
   chomp $revision if($revision);
   return $revision;
}
########################################################################
#
# Lowercase.
#
sub lower_case{
   $_[0] = lc($_[0]);
   $_[0] =~ tr/[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ]/[àáâãäåæçèéêëìíîï]/;
   $_[0] =~ tr/[ÐÑÒÓÔÕÖØÙÚÛÜÝÞ]/[ðñòóôõöøùúûüýþ]/;
   return $_[0];
}
########################################################################
#
# Strip dodgey chars I. This will be done for file names and tags.
#
# TODO: Do we really have to erase all of them? Maybe we should keep
# some for the tags...
#
sub clean_all{
   $_[0] =~ s/[;|><"\015]//g;
   $_[0] =~ s/\`/\'/g;
   $_[0] =~ s/´/\'/g;
   $_[0] =~ s/\s+/ /g;
   return $_[0];
}
########################################################################
#
# Strip dodgey chars II. This will only be done for file names.
#
sub clean_name{
   $_[0] =~ s/[*]//g;
   $_[0] =~ s/\// - /g;
   $_[0] =~ s/\s+/ /g;
   return $_[0];
}
########################################################################
#
# Strip dodgey chars III. This will optionally be done for file names.
# Remember the default chars to be erased are:   :*#?$!  .
#
sub clean_chars{
   $_[0] =~ s/$chars//g;
   return $_[0];
}
########################################################################
#
# Put all chars in brackets and escape some.
#
sub check_chars {
   $chars =~ s/-/\\-/;
   $chars =~ s/]/\\]/;
   $chars =~ s/\s/\\s/;
   $chars = "[" . $chars . "]";
}
########################################################################
#
# Extract the CDDB comment lines starting with EXTD=.
# NOTE: Each EXTD line my have \n's, but two EXTD lines do NOT
# mean that there's a linebreak in between! So, what we have to do
# is, put all comment lines into a string and split the string
# according to the explicitly \n's (i.e. use \\n).and add a \n at the
# end of each line!
#
sub extract_comm {
   my @comment = grep(/^EXTD=/, @{$cd{raw}});
   @comment = grep(s/^EXTD=//, @comment);
   my $line = "@comment";
   $line =~ s/[\015]//g;
   @comment = split(/\\n/, $line);
   foreach (@comment) {
      chomp $_;
      $_ =~ s/^\s+//g;
   }
   return (@comment);
}
########################################################################
#
# Display a help page and exit!
#
sub print_help {
   print <<END

SYNOPSIS:
   ripit [options]

OPTIONS:
 [track_selection]
              If not specified, all tracks will be ripped. Type a number
              or a selection of tracks using numbers separated by commas
              or hyphens, default: not set.
 --merge ordered list of comma separated intervalls
              Place a hyphen or a + between first and last tracknumber
              to be merged, default: not set.
 -o, --outputdir directory
              Where the sound should go, default: not set.
 --dpermission number
              Define directory permissions, default: 0777.
 --fpermission number
              Define permissions of sound and log files, default: 0644.
 -d, --device cddevice
              Path of audio CD device, default: /dev/cdrom.
 -r, --ripper number
              0 dagrab, 1 cdparanoia, 2 cdda2wav, 3 tosha, 4 cdd,
              default: 1.
 --ripopt options
              Additional options for specific ripper. Default: not set.
 --nicerip value
              Set niceness of ripping process, default: 0.
 -Z, --disable-paranoia
              When using dagrab, the number of retries will be set to 3,
              with cdparanoia this option is equal to the -Z option.
              Default: off.
 -G, --ghost  Analyze wav and split into possible chunks of sound. This
              overrides option merge! Default: off.
 --extend seconds
              Enlarge splitted chunk by number of seconds if possible,
              default: 2.
 --prepend seconds
              Enlarge splitted chunk by number of seconds if possible,
              default: 2.
 -c, --coder encoder
              0 Lame, 1 Oggenc, 2 Flac,  3 Faac, a comma separated list!
 --faacopt Faac-options
              Pass other options to the encoder,  quote them with double
              quotes if needed; default: not set.
 --flacopt Flac-options
              Same as above.
 --lameopt Lame-options
              Same as above.
 --oggencopt Oggenc-options
              Same as above.
 -q, --quality quality
              A comma separated list of values or the word \"off\", passed
              in the same order as the list of encoders! If no  encoders
              passed, follow the order of the config file!
 -v, --vbrmode mode
              Variable bitrate, only used with Lame, mode is new or old,
              see Lame manpage.  The Lame-option quality will be changed
              to -V instead of -q if vbr-mode is used; default: not set.
 -b, --bitrate rate
              Encode \"mp3\" at this bitrate for Lame. If option --vbrmode
              used, bitrate is equal to the -b option, so one might want
              to set it \"off\", default: 128.
 -B, --maxrate rate
              maxrate (Bitrate) for Lame using --vbrmode is equal to the
              -B option for Lame or the -M option for Oggenc,
              default: 0.
 -S, --preset mode
              Use the preset switch when encoding with Lame. With otpion
              --vbrmode new --preset fast will be used. Default: off.
 -W, --chars [list]
              Exclude special characters and  (ending!)  periods in file
              names. The argument is optional. Following characters will
              be erased, if no argument stated: :*#?\$\!  else only ending
              periods and all passed ones.  Default: off.
 --comment comment
              Specify a comment tag (mp3, m4a), or a description tag for
              (ogg, flac), default: not set.
 -g, --genre genre
              Specify (and  override CDDB)  genre,  must be a valid ID3-
              genre name  if using Lame, can (but shouldn't) be anything
              if using other encoders, default: not set.
 -y, --year year
              Specify (and override CDDB) year tag (mp3, m4a), or a date
              tag (ogg, flac), default: not set.
 -D, --dirtemplate '\" foo \$parameters \"'
              Use single and double quotes to pass the parameters of the
              templates. Default: '\"\$artist - \$album\"'
 -T, --tracktemplate '\" foo \$parameters \"'
              See above. Default: '"\$tracknum \$trackname"'.
 --sshlist list
              Comma separated list of remote machines where RipIT should
              encode. Default: not set.
 --scp        If the filesystem can not be accessed on the remote
              machines, copy the wavs to the remote machines,
              default: off.
 --local      Only used with option --sshlist; if all encodings shall be
              done on remote machines, use --nolocal, default: on.
 -C, --cddbserver server
              CDDB server, default freedb.org. Note, the full address is
              \"mirror\".freedb.org, i. e. default is freedb.freedb.org.
 -m, --mirror mirror
              Choose \"freedb\" or one of the possible freedb
              mirrors, default: freedb.
 -L, --protocol level
              CDDB protocol level for CDDB query. Level 6 supports UTF-8
              and level 5 not. Default: 6
 -P, --proxy address
              The http proxy to use when accessing the cddb server.  The
              CDDB protocol must be http! Default: not set.
 -t, --transfer mode
              Transfer mode, cddb or http, will set default port to 8880
              or 80 (for http), default: cddb.
 -n, --nice value
              Set niceness of encoding process, default: not set.
 -a, --archive
              Read and save CDDB files in  \$HOME/.cddb/\"category\"
              directory. Default: off.
 -e, --eject  Ejects the CD when finished, default off.
 --ejectcmd cmd
              Command to use for ejecting CD (see --eject), default:
              eject.
 --ejectopt options
              Arguments to the ejecting CD command (see --ejectcmd),
              default: path of CD device.
 --halt       Powers off  the machine when finished if the configuration
              supports it, default: off.
 -s, --submission
              Specify --nosubmission if the computer is  offline and the
              created file cddb.toc shall be saved in the home directory
              instead of being submitted. With option  --archive it will
              also be saved in the \$HOME/.cddb directory. Default: on.
 -M, --mail address
              Users return email address, needed for submitting an entry
              to freedb.org. Default: not set.
 -p, --playlist number
              Create a m3u playlist file, or use --playlist 0. For
              filenames without paths use --playlist 2. Default: 1 - on.
 -i, --interaction
              Specify --nointeraction if ripit shall take the first CDDB
              entry found and rip without any questioning. Default: on.
 --lcd        Use lcdproc to display status, default: on.
 --lcdhost    Specify the lcdproc host, default: localhost.
 --lcdport    Specify the lcdport, default: 13666.
 --infolog file
              Log operations (system calls,  file/directory creation) to
              file, given with full path; default: not set.
 -l, --lowercase
              Lowercase filenames, default: off.
 -u, --underscore
              Use underscores _ instead of spaces in filenames, default:
              off.
 -U, --utftag If negated decodes Lame-tags to ISO8859-1. Default: off.
 --rip        Rip the CD, to be used as --norip if wavs are present.
 --encode     Prevent encoding (generate only wavs) with --noencode.
              Default: on.
 -w, --wav    Keep the wav files after encoding instead of deleting them
              default: off.
 -N, --normalize
              Normalizes the wave-files to a given dB-value (default:
              -12dB). Default: off.
 -z, --normopt
              Options to pass to normalize . For further options see
              normalize documentation (http://normalize.nongnu.org).
              Keeping the default value of -12dB is recommended.
              Default: -bv
 --cdtoc n
              n=1: Create a toc file to burn the wavs with cd-text using
              cdrdao or cdrecord (in dao mode), default off.
 -h, --help   Print this and exit.
 -V, --version
              Print version and exit.
 -x, --verbose number
              Run silent (0), with minimal (1), normal without encoder
              msgs (2), normal (3), verbose (4) or extremly verbose (5).
              Default 3
 --config     Read parameters from config file or specify  --noconfig to
              prevent reading it; default: on.
 --save       Add parameters passed on command line to config file. This
              options does not  overwrite other  settings.  An  existing
              config file will be saved as config.old. Default: off.
 --savenew    Save all parameters passed on command line to a new config
              file, backup an existing file to config.old. Default: off.
 --loop       Continue to  ripp and encode as soon as a new  audio CD is
              inserted.  This option implies that the CD is ejected when
              done. Default off.
 --resume     Resume a previously started session. Default: not set.
 --md5sum     Create a MD5-sum file for each type of sound files.
              Default: not set.
 --core number
              Comma separated list of numbers giving maximum of allowed
              encoders to run at the same time, default: 1.


SEE ALSO
       ripit(1), cdparanoia(1), lame(1), oggenc(1), flac(1),
       normalize(1), cdda2wav(1).

AUTHORS
       RipIT is now maintained by Felix Suwald, please send bugs, wishes
       comments to ripit _[at]_ suwald _[dot]_ com. For bugs, wishes and
       comments about lcdproc, contact max.kaesbauer [at] gmail[dot]com.
       Former maintainer:  Mads Martin Joergensen;  RipIT was originally
       developed by Simon Quinn.

DATE
       16 June 2007

END
}
########################################################################
#
# Display available options exit!
#
sub print_usage {
   print <<END

Usage:
ripit [--device|d cd-device] [--outputdir|o path] [--ripper|r cdripper]
      [--dpermission number] [--fpermission number]  [--nicerip number]
      [--ripopt ripper-options] [--rip] [--disable-paranoia|Z] [--wav|w]
      [--ghost|G]  [--extend seconds]  [--prepend seconds]   [--encode]
      [--coder|c encoders]  [--faacopt options]  [--flacopt options]
      [--lameopt options] [--quality qualities-list] [--bitrate|b rate]
      [--maxrate|B rate]   [--vbrmode|v old or new]   [--preset|S mode]
      [--oggencopt options] [--comment id3-comment] [--genre|g genre-tag]
      [--utftag|U] [--year|y year-tag] [--lowercase|l] [--underscore|u]
      [--dirtemplate '\"\$parameters\"'] [--tracktemplate '\"\$parameters\"']
      [--cddbserver|C server] [--mirror|m mirror]  [--protocol|L level]
      [--proxy|P path]  [--transfer|t cddb or http]  [--mail|M address]
      [--submission|s] [--interaction|i] [--nice|n adjustment] [--halt]
      [--eject|e] [--ejectcmd command] [--ejectopt options for command]
      [--help|h]  [--lcd] [--lcdhost host] [--lcdport port]  [--config]
      [--save] [--savenew]  [--sshlist remote hosts] [--local]  [--scp]
      [--archive|a] [--playlist|p number]  [--infolog path]  [--md5sum]
      [--cdtoc number] [--loop] [--verbose|x number] [--chars|W [list]]
      [--normalize|N] [--normopt|z options] [--version|V]  [--resume|R]
      [--merge list] [--core numbers] [track_selection]



For general usage information see the manpage or type:
       ripit --help | less
or try to run
       ripit
without any options.

END
}
########################################################################
#
# Define the tracks to be skipped, change the passed values of the form
# 2+3,5-7 to an array @skip with 3,6,7. Note that we use the untouched
# variable $pmerge to determine the tracks to be skiped.
# In the same time, the intervals have to be tested if valid.
#
sub skip_tracks{
   my @merge = split(/,/, $pmerge);
   foreach (@merge) {
      # Split each intervall into a BeginEndArray.
      my @bea = split(/-|\+/, $_);
      my $i = $bea[0] + 1;
      # Missing seperator in command line argument?
      if($#bea > 1) {
         print "\nStrange interval in argument of option merge ($_)!",
               "\nIs there a comma missing?\n\n";
         exit;
      }
      # Track number larger than number of tracks on CD?
      if($#tracklist > 0) {
         if($bea[0] > $#tracklist + 1 || $bea[1] > $#tracklist + 1) {
            print "\nStrange interval in argument of option merge ($_)!",
                  "\nHigher track number than tracks on CD?\n\n";
            exit;
         }
      }
      while($i <= $bea[$#bea]){
         push(@skip, $i);
         $i++;
      }
   }
   return(@skip);
}
########################################################################
#
# Split the wav into chunks of sound and rename them.
#
# Analyze the wav for chunks and gaps. Fill an array @times with two
# blank separated numbers in each entry. These two numbers are the
# time in seconds of the starting point of sound and the duration of
# that chunk. This is important because this number will be used to seek
# to that point of sound from the beginning of the file, not form the
# end point of the previous cycle. For each chunk we start to seek from
# zero; this is not a large time loss, seeking is fast.
#
# There were weeks of testing to manage Audio::FindChunks-0.03, gave up!
# The behaviour seems inconsistent. For example: ripping all tracks of
# the CD: Lamb - What Sound gave *no* gaps. When ripping only the last
# track, gaps were immediately detected.
# First, changing the sec_per_chunk value gave better results, but
# suddenly no gaps at all were found. The threshold stayed at zero.
# So then I introduced a loop where the sec_per_chunk increases from
# 0.1 - 0.8 in steps of 0.1, and in the same time, the threshold from
# 0.1 in steps of 0.2 only if the resulting threshold is less than 100.
# You say that this is ugly? No, it is extremly ugly. And all this
# because there might be a caching problem in Audio::FindChunks-0.03?
# Then, testing on a 64bit machine was a drawback, no gaps at all.
#
# So I gave up this sophisticated and "fully documented" PM, and coded a
# few lines to solve the problem. This code might not be usefull to
# split manually recorded vinyl, but the results for ripped CDs are
# much more precise than with the PM. Of course, I can test only on a
# limited range of CDs, and I have no classical or Death-Metal to test.
# But for the following CDs (and hundreds of CDs with no gaps -->
# thousands of tracks and not one was erroneously splitted) this
# snipplet works. (See below for explanation!)
#
#
# Testreport (CDs with ghost songs):
# OK: 2raumwohnung: in wirklich
# OK: Archive: Londonium
# OK: Archive: Take My Head
# OK: Aromabar: 1 (has 2 ghost songs!)
# OK: Autour de Lucie: L'échappée belle
# OK: Cibelle: Cibelle
# OK: Dining Rooms: Experiments In Ambient Soul
# OK: Distain!: [li:quíd]
# OK: Helena: Née dans la nature
# OK: Jay-Jay Johanson: Antenna
# OK: Laika: Sound Of The Satellites
# OK: Lamb: Debut
# OK: Lamb: Fear of Fours
# OK: Lamb: What Sound
# OK: Lamb: What Sound Limited Edition
# OK: Lunik: Weather
# OK: Massive Attack: 100th Window
# OK: Moloko: Do You Like My Tight Sweater?
# OK: Olive: Trickle
# OK: Qntal: III
# OK: Samia Farah: Samia Farah
# OK: Stereo Total: Musique Automatique
#
#
sub split_chunks {
   my $ghostflag = 0;
   my ($tcn, $trn, $shrt, $cdtocn) = @_;
   my @times = ();
   $trn = $trn . ".rip";

   # Read the header of the wav file.
   open(IN, "< $trn") or print "Can't open $trn: $!";
   binmode(IN);
   my $H = {};
   $H->{header_size} = 44;
   my $wavheader;
   print "Can not read full WAV header!\n"
      if($H->{header_size} != read(IN, $wavheader, $H->{header_size}));

   # Unpack the wave header and fill all values into a hashref.
   ($H->{RIFF_header},     $H->{file_size_8},      $H->{WAV_header},
    $H->{FMT_header},      $H->{WAV_chunk_size},   $H->{WAV_type},
    $H->{channels},        $H->{sample_rate},      $H->{byte_per_sec},
    $H->{block_align},     $H->{bit_per_sample},   $H->{data_header},
    $H->{data_size}) = unpack("a4Va4a4VvvVVvva4V", $wavheader);
   $H->{sample_size} = ($H->{channels} * $H->{bit_per_sample})>>3;
   if($verbose >= 4) {
      print "\nThe wav header has following entries:\n";
      print "$_ \t -> $H->{$_} \n" foreach (keys %$H);
      print "\n";
   }

# How do I analyze the chunks? I calculate a threshold value called
# $thresh of the actual chunk by summing up its binary values - perl is
# so cool! Then this value is used to calculate a kind of mean value
# with the previous one --> $deltathre, but only at every 5th value, to
# cancel short fluctuations. If the actual $thresh value lies
# within a small range compared to the deltathre, a weight (counter)
# will be increased and deltathre will be weighten to cancel (not so)
# short peak changes (not only) at the end of a track (gap).
# Silence is defined as (not so) significant drop of the $thresh value
# compared to the $deltathre one. Use an upper cut-off value $maxthresh
# (70% of maximum value) to allow deltathre to grow (quickly) in the
# beginning but prevent to baile out immediately. During the track, a
# weight will help to prevent the same.
#
   my $bindata;
   my $bytecn = 0;
   my $silencecn = 0;
   my $chunkcn = 0;
   my $chunksize = 0.1; # Chunk size in seconds.
   my $chunkbyte = $H->{byte_per_sec} * $chunksize;
   my $chunklen = 0;
   my $startsnd = 0;
   my $soundflag = 0;
   my $deltathre = $H->{byte_per_sec} * $chunksize;
   my $weight = 1;
   my $maxthresh = $deltathre * 8 * 0.7;
   while(read(IN, $bindata, $chunkbyte)){
         $chunkcn ++;
      my $thresh += unpack( '%32b*', $bindata );
      $weight++
         if($thresh > 0.9 * $deltathre && $thresh < 1.1 * $deltathre);
      if($thresh > 0.8 * $deltathre &&
         $thresh < $maxthresh &&
         $chunkcn =~ /[05]$/) {
         $deltathre = ($deltathre * $weight + $thresh) / (1 + $weight);
      }
#      if(unpack("h*", $bindata) !~ /[1-9abcdef]/){
      if($thresh < 0.8 * $deltathre){
         $silencecn += $chunkbyte;
         if ($silencecn == $H->{byte_per_sec} * 6) {
            $chunklen = ($bytecn - $silencecn) / $H->{byte_per_sec};
            $chunklen -= $startsnd;
            if ($chunklen < 4) {
               $chunklen = 0;
            }
            else {
               push (@times, "$startsnd $chunklen");
            }
            $soundflag = 1;
         }
      }
      else {
         $silencecn = 0;
         if ($soundflag == 1) {
            $startsnd = $bytecn / $H->{byte_per_sec};
            $soundflag = 0;
         }
      }
      $bytecn += $chunkbyte;
   }

   $chunklen = ($bytecn - $silencecn) / $H->{byte_per_sec};
   $chunklen -= $startsnd;
   push (@times, "$startsnd $chunklen") unless($startsnd == 0);
   push (@times, "$startsnd $chunklen") unless(@times);

   my $tracklen = $secondlist[$tcn - 1];
   $tracklen = $secondlist[$tcn] if($hiddenflag == 1);

   if($verbose >= 2) {
      print "\nRipIT found following chunks for track\n";
      print "$shrt (${tracklen}s long):\nstart duration (in seconds)\n";
      foreach(@times){
         my @interval = split(/ /, $_);
         printf("%3d %9.1f\n", $interval[0], $interval[1]);
      }
   }

   # Split wav into chunks.
   $chunkcn = 0;
   my @truetracklen = ();
   foreach (@times){
      # Remember: each entry of @times has the form: "start duration"
      # where start is the beginning of sound in seconds, and duration
      # the time in seconds.
      my @interval = split(/ /, $_);
      if($interval[0] >= $prepend) {
         $interval[0] -= $prepend;
         $interval[1] += $prepend;
      }
      $interval[1] += $extend;

      # Don't split if interval is larger than tracklength from cdtoc.
      # Shorten the tracklength from cdtoc by a threshold of 10s.
      # Reasonable?
      my $tracklen = $secondlist[$tcn - 1];
      $tracklen = $secondlist[$tcn] if($hiddenflag == 1);

      if($interval[0] + $interval[1] > $tracklen){
         $interval[1] = $tracklen - $interval[0];
      }
      $tracklen = $tracklen - 10;
      $interval[0] = int($interval[0]);
      $interval[1] = int($interval[1] + 0.5);
      if($verbose >= 4) {
         print "Using this values:\n";
         printf("%4d %4d\n", $interval[0], $interval[1]);
      }
      log_info("Using this values:");
      log_info("@interval");
      if($tracklen <= $interval[1] || $interval[1] <= 2) {
         print "Track $tcn not splitted.\n\n" if($verbose >= 1);
         log_info("Track $tcn not splitted.\n");
         return($cdtocn);
      }

      open(ERO,">>$outputdir/error.log")
         or print "Can not append to file ",
                  "\"$outputdir/error.log\"!";
      print ERO "Splitflag = 1\n" unless($times[1]);
      print ERO "Ghostflag = 1\n" if($times[1]);
      close(ERO);

      # Use array @truetracklen to save new track lengths. They will be
      # spliced into array @secondlist to allow the ripper process to
      # write correct playlist files and will be printed to ghost.log
      # for encoder process, see below.
      push(@truetracklen, $interval[1]);

      # From now on count in bytes instead of seconds:
      $interval[0] = $interval[0] * $H->{byte_per_sec} + 44;
      $interval[1] = $interval[1] * $H->{byte_per_sec};

      # Seek from beginning of file to start of sound of chunk.
      print "I seek to: $interval[0] B, starting from 0 B.\n"
         if($verbose >= 4);
      seek(IN, $interval[0], 0) or
         print "\nCould not seek in file IN: $!\n";

      # Prepare the filename for output.
      print "Splitting \"$shrt\" into " . ($#times + 1) . " chunks.\n"
         if($verbose >= 2 && $chunkcn == 0);
      my $outr = "Ghost Song $chunkcn";
      $outr = get_trackname($tcn, $outr) . ".rip";
      open(OUT, "> $outr");
      binmode(OUT);

      # Edit header according to size of the chunk.
      $H->{data_size} = $interval[1];
      $H->{file_size_8} =  $H->{data_size} + 36;
      substr($wavheader, 4, 4) = pack("V", $H->{file_size_8});
      substr($wavheader, 40, 4) = pack("V", $H->{data_size});
      print(OUT $wavheader);
      if($verbose >= 5) {
         print "\nThe new wav header of\n$outr";
         print "\nwill have following entries:\n";
         print "$_ \t -> $H->{$_} \n" foreach (keys %$H);
         print "\n";
      }

      # I don't know if it is good to read so many bytes a time, but it
      # is faster than reading byte by byte.
      $interval[1] += $interval[0];
      while(read(IN, $bindata, $chunkbyte) &&
            $interval[0] <= $interval[1]){
         $interval[0] += $chunkbyte;
         print(OUT $bindata);
      }
      close(OUT);
      $chunkcn++;
   }
   close(IN);
   print "Track $tcn successfully splitted.\n\n" if($verbose >= 1);

######
# Do not read this, it is brainstorming only, not the way it is
# implemented.

   # The idea would be to splice the new file into all arrays. But this
   # would break the merge option! Therefore we assume, that ghost songs
   # will rather appear on last tracks than somewhere else. We push the
   # filename to the arrays @tracklist, @tracktag and @seltrack. But
   # this will confuse the ripper, once all tracks are ripped. The key
   # to distinguish between a regular (last) track and ghost songs will
   # be, that @framelist will not have an entry. NOTE that @secondlist
   # needs also to be updated because playlist needs the extact values.

   # Another difference is that the track numbers pushed to  @seltrack
   # will not increase, e. g. @seltrack = (0,1,2,3) will then look like
   # (0,1,2,3,0,3) if track  0 and 3 both have a ghost song (not very
   # common). So: if the ripper encounters a number less-equal than the
   # previous one (from @seltrack), then it must be a ghost song,
   # therefore quit ripping. If the encoder encounters a number less-
   # equal than the previous one, then an internal counter increases to
   # get the right file names and tags from the array, but the filename-
   # counter is not changed. This did not work as wanted.

######
# Here we go for renaming the chunks.

   # The ripper gets a copy of the initial @seltrack array, called
   # @tracksel. Ghost songs will be added in @seltrack, but not in array
   # @tracksel. This means: the ripper will behave as usual, and not
   # care about additional songs.

   # Rename the chunks called "Ghost Song $chunkcn" to the appropriate
   # file name.

   # If there is only one chunk, this chunk gets the true trackname.
   # If there are more than one chunk, the first chunk gets the true
   # track name; this might be wrong, but who knows?

   # Another problem is with data tracks. Then the track-counter will
   # not increase for ghost songs, as we expect for ghost songs that
   # appear in the last track, sad. (See below!)

   $chunkcn = 0;
   my $outr = "Ghost Song $chunkcn";
   $outr = get_trackname($tcn, $outr) . ".rip";
   rename("$outr", "$trn");

   # If there are two chunks, i. e. only one ghost song, don't use a
   # counter in the filename. The suffix can now be wav instead of rip.
   $trn = $tracklist[$tcn - 1];
   $trn = $tracklist[$tcn] if($hiddenflag == 1);

   $chunkcn++;
   shift(@times);
   $secondlist[$tcn - 1] = $truetracklen[0];
   $secondlist[$tcn] = $truetracklen[0] if($hiddenflag == 1);
   shift(@truetracklen);
   $outr = "Ghost Song $chunkcn";
   my $trt = "$trn - Ghost Song";
   $trn = $trt;
   $trn = clean_name("$trn");
   $trn = lower_case($trn) if($lowercase == 1);
   $trn =~ s/ /_/g if($underscore == 1);
   # Here we assume, that the trackname template is
   # "track-counter track-name". If not, then there should be something
   # to worry, tracks might be overridden, although this is very
   # unlikely: TODO: check if outputname already exist.
   # So, the trackname of a new ghost song shall have the same leading
   # tracknumber to identify its origin, except if it comes from
   # the last track, then the leading number may increase! Define a new
   # ghost counter $gcn.
   my $gcn = $tcn;
   $ghostflag = 1 if($tcn == $#framelist);
   $ghostflag = 1 if($hiddenflag == 1 && $tcn == $#framelist - 1);
   $gcn++ if($ghostflag == 1);
   if($#times == 0) {
      $outr = get_trackname($tcn, $outr);
      push(@secondlist, $truetracklen[0]);
      push(@seltrack, $gcn);
      push(@tracklist, $trn);
      push(@tracktags, "$trt");
      $trn = get_trackname($gcn, $trn);
      rename("$outr.rip", "$trn.wav");
      if($cdtoc == 1){
         $cdtocn++;
         my $artistag = clean_all($cd{artist});
         my @shortname = split(/\//, $trn);
         my $shortname = $shortname[$#shortname];
         my $cdtocartis = oct_char($artistag);
         open(CDTOC, ">>$outputdir/cd.toc")
            or print "Can not append to file \"$outputdir/cd.toc\"!\n";
         print CDTOC "\n//Track $cdtocn:\nTRACK AUDIO\n";
         print CDTOC "TWO_CHANNEL_AUDIO\nCD_TEXT {LANGUAGE 0 {\n\t\t";
         print CDTOC "TITLE \"Ghost Song\"\n\t\t";
         print CDTOC "PERFORMER \"$cdtocartis\"\n\t}\n}\n";
         print CDTOC "FILE \"$shortname.wav\" 0\n";
         close(CDTOC);
      }
      shift(@times);
      shift(@truetracklen);
   }
   # If there was only one ghost song, array @times is empty now. We're
   # done.

   # Do the same procedure, if there are more than 2 chunks, i. e. more
   # than 1 ghost song. Ghost songs now need a internal counter if they
   # are not form the last track.
   foreach (@times){
      $trn = $tracklist[$tcn - 1];
      $trn = $tracklist[$tcn] if($hiddenflag == 1);
      my $artistag = clean_all($cd{artist});
      # Remember: $outr is the ripper's output track name of the wav.
      $outr = "Ghost Song $chunkcn";
      # The name for the tags will be with originating track name as
      # prefix.
      my $trt = $trn . " - Ghost Song $chunkcn";
      # The actual track name will be slightly different.
      $trn = $trt;
      $trn = clean_name($trn);
      $trn = lower_case($trn) if($lowercase == 1);
      $trn =~ s/ /_/g if($underscore == 1);
      push(@secondlist, $truetracklen[0]);
      push(@seltrack, $gcn);
      push(@tracklist, $trn);
      push(@tracktags, "$trt");
      $outr = get_trackname($tcn, $outr);
      $trn = get_trackname($gcn, $trn);
      rename("$outr.rip", "$trn.wav");
      if($cdtoc == 1){
         $cdtocn++;
         my @shortname = split(/\//, $trn);
         my $shortname = $shortname[$#shortname];
         my $cdtocartis = oct_char($artistag);
         open(CDTOC, ">>$outputdir/cd.toc")
            or print "Can not append to file \"$outputdir/cd.toc\"!\n";
         print CDTOC "\n//Track $cdtocn:\nTRACK AUDIO\n";
         print CDTOC "TWO_CHANNEL_AUDIO\nCD_TEXT {LANGUAGE 0 {\n\t\t";
         print CDTOC "TITLE \"Ghost Song\"\n\t\t";
         print CDTOC "PERFORMER \"$cdtocartis\"\n\t}\n}\n";
         print CDTOC "FILE \"$shortname.wav\" 0\n";
         close(CDTOC);
      }
      $gcn++ if($ghostflag == 1);
      shift(@truetracklen);
      $chunkcn++;
   }
   print "\n\n" if($verbose >= 2);
   log_info("\n");

   # Is there another way to communicate with the encoder process (child
   # precess) than writing log files?
   open(GHOST, ">$outputdir/ghost.log")
      or print "Can not append to file ghost.log!\n";
   print GHOST "Array seltrack: @seltrack\n";
   print GHOST "Array secondlist: @secondlist\n";
   print GHOST "Array tracklist: $_\n" foreach(@tracklist);
   print GHOST "Array tracktags: $_\n" foreach(@tracktags);
   close(GHOST);
   return($cdtocn);
}
########################################################################
#
# Check if the necessary modules are available.
#
sub init_mod {
   print "\n" if($verbose >= 1);
   eval { require CDDB_get };
   if($@) {
      print "\nPerl module CDDB_get not found. Needed for",
            "\nchecking the CD-ID and retrieving the CDDB",
            "\nentry from freeDB.org!",
            "\nPlease install CDDB_get from your closest",
            "\nCPAN mirror before trying again.",
            "\nInstall by hand or e.g. type as root:",
            "\nperl -MCPAN -e 'install CDDB_get'\n\n";
      exit 0;
   }
   $@ = ();
   eval { require LWP::Simple };
   if($@) {
      print "\nPerl module LWP::Simple not found. Needed for",
            "\nchecking free categories before submitting CDDB",
            "\nentries to freeDB.org!",
            "\nPlease install LWP::Simple and dependendencies ",
            "\nfrom your closest CPAN mirror or submission will fail.",
            "\nInstall by hand or e.g. type as root:",
            "\nperl -MCPAN -e 'install LWP::Simple'\n\n";
      sleep 2;
   }
   $@ = ();
   eval { require Digest::MD5 } if($md5sum == 1);
   if($@) {
      print "\nPlease install Digest::MD5 and dependendencies ",
            "\nfrom your closest CPAN mirror before trying again with",
            "\noption --md5sum. Install by hand or e.g. type as root:",
            "\nperl -MCPAN -e 'install Digest::MD5'\n\n";
      exit 0;
   }
   print "\n\n" if($verbose >= 1);
}
########################################################################
#
# Check if lame is installed.
#
sub check_lame {
   unless(log_system("lame --version > /dev/null 2> /dev/null")) {
      if(!@pcoder && "@coder" =~ /0/ || "@pcoder" =~ /0/) {
         print "\nLame not found (needed to encode mp3)!",
               "\nUse oggenc instead (to generate ogg)?\n";
         my $ans = "x";
         while($ans !~ /^y$|^n$/) {
            print "Do you want to try oggenc? [y/n] (y) ";
            $ans = <STDIN>;
            chomp $ans;
            if($ans eq ""){
               $ans = "y";
            }
         }
         if($ans eq "y") {
            my $coders = "@coder";
            my $pcoders = "@pcoder";
            if($coders !~ /1/) {
               $coders =~ s/0/1/g;
            }
            else {
               $coders =~ s/0//g;
            }
            if($pcoders !~ /1/) {
               $pcoders =~ s/0/1/g;
            }
            else {
               $pcoders =~ s/0//g;
            }
            $lameflag = -1;
            @coder = split(/ /, $coders);
            @pcoder = split(/ /, $pcoders);
         }
         else {
            print "\n",
                  "Install lame or choose another encoder with option",
                  "\n",
                  "-c 1 for oggenc, -c 2 for flag or -c 3 for faac.",
                  "\n\n",
                  "Type ripit --help or check the manpage for info.",
                  "\n\n";
            exit;
         }
      }
      else {
         $lameflag = -1;
      }
   }
}
########################################################################
#
# Create MD5sum file of sound files.
#
sub md5_sum {
   my $filename = shift;
   my @shortname = split(/\// , $filename);
   my $shortname = $shortname[$#shortname];
   my $sufix = $shortname;
   $sufix =~ s/^.*\.//;
   chomp($sufix);
   my $md5file =  $shortname[$#shortname - 1] . " - " . $sufix . ".md5";
   $md5file =~ s/ /_/g if($underscore == 1);

   open(SND,"<$filename") or print "Can not open $filename: $!";
   binmode SND;
   print "\nCalculating MD5-sum for $shortname..."
      if($verbose >= 4);
   my $md5 = Digest::MD5->new->addfile(*SND)->hexdigest;
   close SND;
   print "\nThe MD5-sum for $shortname is: $md5.\n"
      if($verbose >= 4);
   open(MD5SUM,">>$outputdir$md5file")
      or print "Can not append to file \"$outputdir$md5file\"!";
   print MD5SUM "$md5 *$shortname\n";
   close MD5SUM;
}
