#!/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 audio CD and encodes files, following steps can be
#            performed (unsorted list):
#            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) Extract possible hidden tracks
#            6) Optional: Create a playlist (M3U) file.
#            7) Optional: Prepares and sends a CDDB submission.
#            8) Optional: saves the CDDB file
#            9) Optional: creates a toc (cue) file to burn a CD in DAO
#               with text
#           10) Optional: analyze the wavs for gaps and splits them into
#               chunks and/or trim lead-in/out (experimental)
#           11) Optional: merges wavs for gapless encoding
#           12) Optional: creates a md5sum for each type of sound files.
#           13) Optional: normalizes the wavs before encoding.
#
#
# Version 3.8.0 - September 28th 2009 - Felix Suwald
#
# Version 3.7.0 - May 6th 2009 - Felix Suwald, thanks for input:
#                                C. Blank
#                                A. Gillis
#                                and to all the bug-reporters!
# 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 a config file with
# option --save!
#
my $cddev     = "/dev/cdrom";# Path of CD device.
my $scsi_cddev = "";         # Path of CD device for non audio commands.
my $outputdir = "";         # Where the sound should go to.
my $ripopt    = "";         # Options for audio CD ripper.
my $span      = "";         # Options for track spans.
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,0,5);# 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, quality for Faac
                            # (10-500), best = 500
my $qualame   = 5;          # Same as above, more intuitive. Use quotes
my $qualoggenc= 3;          # if values shall be comma separated lists.
my $quaflac   = 5;          #
my $quafaac   = 100;        #
my $quamp4als = 0;          #
my $quamuse   = 5;          #
my $lameopt   = "";         #
my $oggencopt = "";         #
my $flacopt   = "";         #
my $faacopt   = "";         #
my $mp4alsopt = "";         #
my $museopt   = "";         #
my $musenc    = "mpcenc";   # The default Musepack encoder.
my $lcd       = 0;          # Use lcdproc (1 yes, 0 no).
my $chars     = "XX";       # 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, extremely 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 $quitnodb  = 0;          # Quit if no CDDB entry found (1 yes, 0 no).
my $overwrite = "n";        # Overwrite existing directory / rip
                            # (n no, y yes, e eject if directory exists)
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 parameters
                            # (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 $book      = 0;          # Merge all tracks into a single file and
                            # write a chapter file (1 yes, 0 no).
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 $uppercasefirst = 0;     # Uppercase first filenames
                            # (1 yes, 0 no).
my $archive   = 0;          # Save CDDB files in ~/.cddb dir
                            # (1 yes, 0 no).
my $mb        = 0;          # Use the musicbrainz DB instead of freedb
                            # (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 @threads   = 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 $inf       = 0;          # Create inf files for wodim (1 yes, 0 no).
my $loop      = 0;          # Restart ripit as soon as the previous CD
                            # is done. This option will force ejection!
                            # (1 yes, 0 no, 2 immediate restart after
                            # ripping, experimental, do not use!).
my $ghost     = 0;          # Check and extract ghost songs from all
                            # tracks (1 yes, 0 no).
my $prepend   = 2.0;        # Extend ghost songs by 2 seconds at the
                            # beginning.
my $extend    = 2.0;        # Extend ghost songs by 2 seconds at the
                            # end.
my $dpermission = "0755";   # Directory permissions.
my $fpermission = "";       # Audio and text file permissions.
my $md5sum    = 0;          # Generate MD5 sums for every sound file
                            # not deleted (1 yes, 0 no).
my @suffix    = ();         # Array of suffixes according to coders.
my $execmd      = "";         # Execute a command when done.
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 $normcmd   = "normalize"; # This might differ for other distros.
my $normopt   = "-b";        # Additional options for normalize.
my $subdir    = "Unsorted";
#
#
########################################################################
#
# System variables, no user configurable variables below.
#
use Encode;                 # Needed for decode_utf8 calls.
use Fcntl;                  # Needed for sysopen calls.
use Getopt::Long qw(:config no_ignore_case);
use IO::Socket;
use strict;
use warnings;

#
# Initialize paths.
#
my $homedir = "$ENV{HOME}";
my $workdir = "$ENV{PWD}";
my $usename = "$ENV{USER}";
# The hostname is not so important and not available on Ubuntu(s) (?).
my $hostnam = "";
if($ENV{HOSTNAME}) {
   $hostnam = "$ENV{HOSTNAME}";
}
elsif($ENV{HOST}) {
   $hostnam = "$ENV{HOST}";
}
my $charset = "";
if($ENV{G_FILENAME_ENCODING}) {
   $charset = "$ENV{G_FILENAME_ENCODING}";
}
else {
   $charset = "$ENV{LANG}";
}
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 $version          = "3.8.0";
my $album_utf8       = "";
my $artist_utf8      = "";
my $distro           = "";  # Linux distribution
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               = ();  # HoH 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 $wthreads         = "";  # Use a comma separated string to write the
                            # threads-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 $hiddenflag       = 0;
my $logfile          = "";  # Used with not *to-use* option --multi.
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.
my @globopt          = ();  # All encoder options sorted according to
                            # encoder.
my @sepdir           = ();  # Array of sound directories sorted
                            # according to encoder.
my $wavdir           = "";  # (Default) directory for sound.
#
#
# Initialize subroutines without ().
#
sub ask_subm;
sub check_bitrate;
sub check_cddev;
sub check_chars;
sub check_vbrmode;
sub choose_genre;
sub disp_info;
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,         @pthreads,
   $pnormcmd,       $pmb,            $puppercasefirst,  $pexecmd,
   $pspan,          $poverwrite,     $pquitnodb,        $pbook,
   $pmusenc,        $pmp4alsopt,     $pmuseopt,         $pinf,
   $pscsi_cddev,
);
########################################################################
#
# Get the parameters from the command line.
#
# available:             E F     jJkK           Q               Y
# already used: aAbBcCdDe f gGhiI    lLmMnNoOpPq rRsStTuUvVwWxXy zZ
#
GetOptions(
   "archive|a!"             => \$parchive,
   "bitrate|b=s"            => \$pbitrate,
   "book|A=i"               => \$pbook,
   "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,
   "threads=s"              => \@pthreads,
   "device|d=s"             => \$pcddev,
   "dirtemplate|D=s"        => \@pdirtemplate,
   "eject|e!"               => \$peject,
   "ejectcmd=s"             => \$pejectcmd,
   "ejectopt=s"             => \$pejectopt,
   "encode!"                => \$pencode,
   "execmd|X=s"             => \$pexecmd,
   "extend=f"               => \$pextend,
   "faacopt=s"              => \$pfaacopt,
   "flacopt=s"              => \$pflacopt,
   "lameopt=s"              => \$plameopt,
   "oggencopt=s"            => \$poggencopt,
   "mp4alsopt=s"            => \$pmp4alsopt,
   "musenc=s"               => \$pmusenc,
   "museopt=s"              => \$pmuseopt,
   "genre|g=s"              => \$pgenre,
   "ghost|G!"               => \$pghost,
   "halt"                   => \$phalt,
   "help|h"                 => \$help,
   "inf=i"                  => \$pinf,
   "infolog=s"              => \$pinfolog,
   "interaction|i!"         => \$pinteraction,
   "lcd!"                   => \$plcd,
   "lcdhost=s"              => \$plcdhost,
   "lcdport=s"              => \$plcdport,
   "lowercase|l!"           => \$plowercase,
   "uppercasefirst!"        => \$puppercasefirst,
   "local!"                 => \$plocal,
   "loop=i"                 => \$ploop,
   "mb!"                    => \$pmb,
   "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,
   "normcmd=s"              => \$pnormcmd,
   "normopt|z=s"            => \$pnormopt,
   "subdir=s"               => \$psubdir,
   "outputdir|o=s"          => \$poutputdir,
   "overwrite|O=s"          => \$poverwrite,
   "dpermission=s"          => \$pdpermission,
   "fpermission=s"          => \$pfpermission,
   "playlist|p:s"           => \$pplaylist,
   "prepend=f"              => \$pprepend,
   "preset|S=s"             => \$ppreset,
   "proxy|P=s"              => \$pproxy,
   "protocol|L=i"           => \$pproto,
   "quality|q=s"            => \@pquality,
   "quitnodb=i"             => \$pquitnodb,
   "resume|R"               => \$presume,
   "rip!"                   => \$prip,
   "ripper|r=s"             => \$pripper,
   "ripopt=s"               => \$pripopt,
   "savenew"                => \$psavenew,
   "save"                   => \$psavepara,
   "scp"                    => \$pscp,
   "scsidevice=s"           => \$pscsi_cddev,
   "sshlist=s"              => \@psshlist,
   "span|I=s"               => \$pspan,
   "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:i"   => \$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 arise 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.
#
# The check of array @coder will be done in the subroutine!
$faacopt = $pfaacopt if($pfaacopt);
$flacopt = $pflacopt if($pflacopt);
$lameopt = $plameopt if($plameopt);
$oggencopt = $poggencopt if($poggencopt);
$mp4alsopt = $pmp4alsopt if($pmp4alsopt);
$musenc = $pmusenc if($pmusenc);
$museopt = $pmuseopt if($pmuseopt);
$oggencopt = " " unless($oggencopt); # Oops, only to prevent warnings...
$CDDB_HOST = $PCDDB_HOST if($PCDDB_HOST);
$cddev = $pcddev if($pcddev);
$overwrite = $poverwrite if($poverwrite);
#
# Get the default "no-argument" values.
# Note, that subroutine clean_all already purges ;><" and \015.
$chars = "NTFS" if($chars eq "");
$chars = "" if($chars eq "off");
$commentag = $pcommentag if($pcommentag);
@dirtemplate = @pdirtemplate if($pdirtemplate[0]);
$tracktemplate = $ptracktemplate if($ptracktemplate);
$execmd = $pexecmd if($pexecmd);
$halt = $phalt if($phalt);
$infolog = $pinfolog if($pinfolog);
$lcdhost = $plcdhost if($plcdhost);
$lcdport = $plcdport if($plcdport);
$mailad = $pmailad if($pmailad);
$mirror = $pmirror if($pmirror);
$normcmd = $pnormcmd if($pnormcmd);
$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 $pthreads 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);
$book = $pbook if($pbook);
$cdtoc = $pcdtoc if defined $pcdtoc;
$extend = $pextend if defined $pextend;
$genre = $pgenre if defined $pgenre;
$inf = $pinf if defined $pinf;
$loop = $ploop if defined $ploop;
$md5sum = $pmd5sum if defined $pmd5sum;
$maxrate = $pmaxrate if defined $pmaxrate;
$nice = $pnice if defined $pnice;
$nicerip = $pnicerip if defined $pnicerip;
$parano = $pparano if defined $pparano;
$playlist = $pplaylist if defined $pplaylist;
$playlist = 1 if($playlist eq "");
$prepend = $pprepend if defined $pprepend;
$quitnodb = $pquitnodb if defined $pquitnodb;
$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;
$scsi_cddev = $pscsi_cddev if defined $pscsi_cddev;
$span = $pspan if defined $pspan;
$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;
$lowercase = $plowercase if defined $plowercase;
$mb = $pmb if defined $pmb;
$uppercasefirst = $puppercasefirst if defined $puppercasefirst;
$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;
#
########################################################################
#
# Preliminary start: print version, read (and write) config file.
#
# 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";
$ripdir = "/etc/ripit/config" unless(-r "$ripdir");
if(-r "$ripdir" && $config == 1) {
   open(CONF, "$ripdir") || print "Can not read config file!\n";
   my @conflines = <CONF>;
   close CONF;
   chomp($verbose = join('', grep(s/^verbose=//, @conflines)))
      unless($pverbose);
   chomp($infolog = join('', grep(s/^infolog=//, @conflines)))
      unless($pinfolog);
}
#
print "\n\n\nRipIT version $version.\n" if($verbose >= 1);
# Preliminary creation of the infolog path.
# No log_system call here because this would try to write to the infolog
# file not yet created.
if($infolog) {
   my($log_path, $filename) = $infolog =~ m/(.*\/)(.*)$/;
   system("mkdir -m 0755 -p \"$log_path\"") and
   print "Can not create directory \"$log_path\": $!\n";
}
log_info("RipIT version $version infolog-file.\n");
#
#
# 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.
#
   $chars = "" if($chars eq "XX" && ($savenew == 1 || $savepara == 1));
   if($savenew == 1) {
      $verbose = 3; # Set back to default value.
      $infolog = ""; # Set back to default value.
      save_config();
      print "Saved a new config file!\n\n" if($verbose >= 3);
   }
#
# Read the config file.
#
   read_config() if($config == 1);
   check_enc("lame", "mp3");
   # check_enc("faac", "m4a");
#
# Check if the necessary modules are installed properly.
#
   init_mod;
#
#
# 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.9. This is also done to prevent
# warnings. And to avoid problems when updating from versions like 3.3.
#
   $ejectopt = "{cddev}" unless($ejectopt);
   $ejectcmd = "eject" unless($ejectcmd);
   $normcmd = "normalize" unless($normcmd);
   $bitrate = "off" unless($bitrate);
   $dpermission = "0755" unless($dpermission);
   $fpermission = "" unless($fpermission);
   $local = 1 unless($local);
   $encode = 1 unless($encode);
   $wav = 0 unless($wav);
   @threads = 1 unless($threads[0]);
   $uppercasefirst = 0 unless($uppercasefirst);
   $mb = 0 unless($mb);
   $execmd = "" unless($execmd);
   $overwrite = "n" unless($overwrite);
   $quitnodb = 0 unless($quitnodb);
   $book = 0 unless($book);
   $inf = 0 unless($inf);
   $resume = 0 unless($resume);
   $musenc = "mpcenc" unless($musenc);
#
# Save the config file.
#
   save_config() if($savepara == 1);
   print "Updated the config file!\n\n"
      if($verbose >= 3 && $savepara == 1);
#
# It might be a good to x-check settings from config file because they
# can be edited manually.
#
   check_coder();           # Check it again for lame cbr vs vbr.
   check_quality();         # Check if again if quality is valid.
   check_sshlist();         # Check it again to create the hash.
   check_options();         # Sort the options according to the encoder.
   check_distro();          # Check for the distribution used.
}
#
########################################################################
#
# 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 > /dev/null 2>&1");
}

if($scsi_cddev eq "") {
   $scsi_cddev = $cddev;
}

if($chars) {
   check_chars;
}

if($lcd == 1) {
   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/;
}

if($outputdir =~ /^\$HOME/) {          # If one wrote $HOME in the config file.
   $outputdir =~ s/^\$HOME/$homedir/;
}

if(length($year) > 0 && length($year) != 4 ) {
   print STDERR "Warning: year should be in 4 digits - $year\n"
      if($verbose >= 1);
}

if($pdpermission && $verbose >= 2) {
   # Print this message only, if a dpermission has been passed on CL.
   $dpermission = sprintf("%04d", $dpermission);
   print "Will set directory permission to $dpermission.\n";
}

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

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

if($span && $verbose >= 2) {
   print "Will rip only partial wav files.\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($book >= 1 && $verbose >= 2) {
   print "Will merge all tracks into one file and write a chapter file.\n";
   $pmerge = "1-";
   $ghost = 0;
}

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

if($inf >= 1 && $verbose >= 2) {
   print "Will create a inf files for each track.\n";
}

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

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

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

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

if($parano >= 3 && $verbose >= 2 ) {
   print "Warning: paranoia argument unknown, will use paranoia.\n";
   $parano = 1;
}

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

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

if($loop >= 1) {
   print "Will loop and eject CD.\n" if($verbose >= 2);
   while($loop >= 1) {
      main_sub;
      last if($loop == 0);
      init_var;
      print "Please insert a new CD!\n" if($verbose >= 1);
      while( not cd_present() ) {
         sleep(6);
      }
   }
}
else {
   main_sub;
}
exit;
#
########################################################################
#
# 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" if($verbose > 0);
      while( not cd_present() ) {
         check_cddev;
         sleep(12);
      }
   }
   if($archive == 1 && $multi == 0) {
      get_arch()
   }
   else {
      get_cdinfo() if($mb == 0);
      get_mb() if($mb == 1);
   }
   disp_info;
   create_seltrack($trackselection);
   ask_subm();
   my $answer = create_dirs();

   if($answer eq "go") {
      if($normalize == 1) {
         rip_cd();
         norm_cd();
         enc_cd();
      }
      else {
         rip_cd();
      }
   }
   elsif($answer eq "next") {
      print "\nRelease $cd{artist} - $cd{title} already done, ",
            "giving up.\n" if($verbose > 0);
      log_info("Directory already present, giving up.");
      log_info("*" x 72, "\n");
   }
   elsif($answer eq "unknown") {
      print "\nRelease unknown, giving up.\n" if($verbose > 0);
      log_info("Release unknown, giving up.");
      log_info("*" x 72, "\n");
   }

   if($eject == 1 or $loop >= 1
                  or $overwrite eq "e" and $answer eq "next") {
      my $ejectopt = $cddev if($ejectopt eq '{cddev}');
      $ejectopt = $ejectopt . " eject" if($ejectcmd =~ /cdcontrol/);
      log_system("$ejectcmd $ejectopt");
   }

   return if($answer eq "next" or $answer eq "unknown");

   if($loop == 2) {

      my $pid = fork();
      if (not defined $pid) {
         print "\nResources not avilable, will quit.\n";
      }
      elsif($pid != 0) {
         finish_process($pid);
      }
      else {
         # Child: restart process.
         return;
         # Problem: being recursive, we won't come back! Hello zombie.
         exit(0);
      }
   }
   else {
      finish_process();
   }
   $loop = 0 if($loop == 2);
   return;
}
#
########################################################################
#
# SUBROUTINES
#
########################################################################
#
########################################################################
#
# Check local .cddb directory for cddb files with album, artist, discID
# and track titles.
#
sub get_arch {
   # Get cddbid and number of tracks of CD.
   my $trackno;
   ($cddbid, $trackno) = get_cddbid();

   my ($artist, $album);
   my @comment = ();

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

   my $usearch = "x";
   my @categs = ();


   print "\nChecking for a local DB entry, please wait...\n\n"
      if($verbose > 1);
   log_system("mkdir -m 0755 -p $homedir/.cddb")
      or print "Can not create directory $homedir/.cddb: $!\n";
   opendir(CDDB, "$homedir/.cddb/")
      or print "Can not read in $homedir/.cddb: $!\n";
   @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: $!\n";
         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!
            chomp(my $artist = join(' ', grep(s/^DTITLE=//, @loglines)));
            $artist = clean_all($artist);
            chomp(my $agenre = join(' ', grep(s/^DGENRE=//, @loglines)));
            $agenre =~ s/[\015]//g;
            $agenre = "none" unless($agenre);
            print "$count: $artist (genre: $agenre, category: $_)\n"
               if($interaction == 1);
            $count++;
            $agenre = "";
         }
      }
      print "\n0: Search online DB 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 "");
            print "\n";
         }
      }
   }
   else {
      get_cdinfo() if($mb == 0);
      get_mb() if($mb == 1);
      return;
   }

   if($usearch != 0) {
      # We use "musicbrainz" as key when reading entry if coming from
      # MB section (below). So if we have a ~/.cddb/musicbrainz
      # directory this will give problems, use word "archive" for the
      # category name instead.
      my $ctg = $cddbid[$usearch-1];
      $ctg = "archive" if($ctg =~ /musicbrainz/);
      if($dirflag[$usearch-1] == 1) {
         read_entry("$homedir/.cddb/$cddbid[$usearch-1]/$cddbid",
            $ctg, $trackno);
      }
      elsif($dirflag[$usearch-1] == 0) {
         read_entry("$homedir/.cddb/$cddbid", $ctg, $trackno);
      }
      $categ = $cd{cat} = $cddbid[$usearch-1];
   }
   else {
      get_cdinfo() if($mb == 0);
      get_mb() if($mb == 1);
      return;
   }

   if($mb == 1) {
      open(DISCID, "discid $scsi_cddev|");
      my @response = <DISCID>;
      close DISCID;
      chomp($cd{discid} = join("", grep(s/^DiscID\s*:\s//, @response)));
   }
}
########################################################################
#
# Read the album, artist, discID and track titles from the get_CDDB()
# generated TOC file.
#
sub get_cdinfo {
   # Get cddbid and number of tracks of CD.
   my $trackno;
   ($cddbid, $trackno) = get_cddbid();

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


   #Configure CDDB_get parameters
   if($CDDB_HOST eq "freedb2.org") {
      $config{CDDB_HOST} = $CDDB_HOST;
   }
   elsif($CDDB_HOST eq "musicbrainz.org") {
      $config{CDDB_HOST} = "freedb." . $CDDB_HOST;
   }
   else {
      $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} = $scsi_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",
            "The handshake with the freeDB-server will fail!\n\n";
   }
   $config{HELLO_ID} = $hid;

   print "\nChecking for a DB entry \@ $config{CDDB_HOST}...\n"
      if($verbose >= 1);
   eval {%cd = get_cddb(\%config);};
   if($@) {
      $@ =~ s/db:\s/db:\n/;
      $@ =~ s/at\s\//at\n\//;
      print "No connection to internet? The error message is:\n",
            "$@\n" if($verbose >= 1);
      $submission = 0;
   }

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

   if($multi == 1) {
      my $cddevno = $cddev;
      $cddevno =~ s/\/dev\///;
      $cddevno =~ s/(\d)/0$1/ unless($cddevno =~ /\d\d/);
      $logfile = $outputdir . "/" . $cddevno;
      read_entry($logfile, "multi");
   }
}
########################################################################
#
# Read the album, artist, discID and track titles from MusicBrainz.
#
sub get_mb {

   print "Will check MusicBrainz." if($verbose > 2);
   my ($cddbid, $trackno, $totaltime) = get_cddbid();


   # Using the perl module to retrieve the MB discid.
   my $discid = 0;
   my $submit_url = 0;
   my $disc;
   eval{ $disc = new MusicBrainz::DiscID($scsi_cddev);};
   if($@) {
      # Use the libdiscid command discid to retrieve the MB discid.
      open(DISCID, "discid $scsi_cddev|");
      my @response = <DISCID>;
      close DISCID;
      chomp($discid = join("", grep(s/^DiscID\s*:\s//, @response)));
      chomp($submit_url = join("", grep(s/^Submit\svia\s*:\s//, @response)));
   }
   else {
      if($disc->read() == 0) {
         print "Error: %s\n", $disc->error_msg();
         get_cdinfo();
         return;
      }
      $discid = $disc->id();
      $submit_url = $disc->submission_url();
   }

   # Get the xml contents of that file, parse the MB-ID and do another
   # lookup at http://musicbrainz.org/ws/1/release/MBID, parse this data
   # again and you're done.

   print "\nChecking for a DB entry \@ MusicBrainz...\n"
      if($verbose >= 1);
   my $service;
   eval {$service = WebService::MusicBrainz::Release->new();};
   my $discid_response;
   eval {$discid_response = $service->search({ DISCID => $discid });};
   if($@){
      print "\nMusicBrainz lookup failed... 2nd try in 3s.",
            "\nError message is: $@.\n" if($verbose > 3);
      sleep 3;
      eval {$discid_response = $service->search({ DISCID => $discid });};
      if($@){
         print "\nMusicBrainz lookup failed! Will use freedb instead.",
               "\nError message is: $@.\n" if($verbose > 3);
         get_cdinfo();
         return;
      }
   }
   else {
      print "Connection OK.\n" if($verbose > 4);
      print "Discid is: $discid.\n" if($verbose > 3);
   }
   # Print the data for further queries.
#   use Data::Dumper;
#   print Dumper($discid_response);
#   print "*" x 72, "\n\n";

   my $mbid;
   eval {$mbid = $discid_response->release()->id();};
   if($@){
      print "\nMusicBrainz does not know this discid! Use\n",
            "$submit_url for submission!\n" if($verbose > 1);
      get_cdinfo();
      return;
   }
   # This should not happen.
   elsif($mbid eq "") {
      print "\nNo discid $discid found at MusicBrainz! Use\n",
            "$submit_url for submission!\n" if($verbose > 1);
   }

   my $mbid_response;
   eval {$mbid_response = $service->search({ MBID => $mbid, INC => 'artist' });};
   if($@){
      print "\nMusicBrainz artist lookup failed... 2nd try in 3s.",
            "\nError message is: $@.\n" if($verbose > 3);
      sleep 3;
      eval {$mbid_response = $service->search({ MBID => $mbid, INC => 'artist' });};
      if($@){
         print "\nMusicBrainz lookup artist failed!",
               "\nWill use freedb instead.",
               "\nError message is: $@.\n" if($verbose > 3);
         get_cdinfo();
         return;
      }
   }
   else {
      print "Connection OK.\n" if($verbose > 4);
   }


   my $release = $mbid_response->release();
   my $artist = $release->artist()->name();
   my $album = $release->title();
   my $asin = $release->asin();
   my $language = $release->text_rep_language();
   $artist =~ s/^The\s+// unless($artist =~ /^The\sThe/);
   $language = "English" unless($language);
   $language = "English" if($language eq "ENG");
   $language = "French" if($language eq "FRA");
   $language = "German" if($language eq "DEU");

   # Retrieve the year and barcode:
   eval {$mbid_response = $service->search({ MBID => $mbid, INC => 'release-events' });};
   if($@){
      print "\nMusicBrainz lookup failed... 2nd try in 3s:\n" if($verbose > 3);
      sleep 3;
      eval {$mbid_response = $service->search({ MBID => $mbid, INC => 'release-events' });};
      if($@){
         print "\nMusicBrainz lookup failed! Will use freedb instead.\n"
            if($verbose > 3);
         get_cdinfo();
         return;
      }
   }
   else {
      print "Connection OK.\n" if($verbose > 4);
   }
   $release = $mbid_response->release();
   my $content = $release->release_event_list();
   my $reldate = ${$content->events()}[0]->date() if($content);
   my $barcode = ${$content->events()}[0]->barcode() if($content);
   $year = $reldate;
   $year =~ s/-.*$// if($year);

   # Why this? Actually, I don't know. Thought that in the 21st
   # century there should be no UTF-8 problem anymore... But getting
   # e.g. tracknames with pure ascii in one track, latin chars in an
   # other track and even true wide chars in a third one will totally
   # mess up encoder and even the file names used by the ripper.
   my $temp_file = "/tmp/ripit-MB-$$\.txt";
   open(TMP, '>:utf8', "$temp_file") or print "$temp_file $!";
   print TMP "artist: $artist\n";
   print TMP "album: $album\n";
   print TMP "genre: $genre\n" if($genre);
   print TMP "category: musicbrainz\n";
   print TMP "cddbid: $cddbid\n";
   print TMP "discid: $discid\n";
   print TMP "asin: $asin\n" if($asin);
   print TMP "year: $year\n" if($year);
   print TMP "trackno: $trackno\n";
   print TMP "language: $language\n";
   print TMP "reldate: $reldate\n" if($reldate);
   print TMP "barcode: $barcode\n" if($barcode);

   # Retrieve the track list.
   eval {$mbid_response = $service->search({ MBID => $mbid, INC => 'tracks' });};
   if($@){
      print "\nMusicBrainz lookup failed... 2nd try in 3s:\n" if($verbose > 3);
      sleep 3;
      eval {$mbid_response = $service->search({ MBID => $mbid, INC => 'tracks' });};
      if($@){
         print "\nMusicBrainz lookup failed! Will use freedb instead.\n"
            if($verbose > 3);
         close TMP;
         unlink("$temp_file");
         get_cdinfo();
         return;
      }
   }
   else {
      print "Connection OK.\n" if($verbose > 4);
   }
   $release = $mbid_response->release();
   my $track_list = $release->track_list();
   my $mb_trackno = $#{$track_list->tracks()} + 1;

   my $i=1;
   my $j=0;
   foreach my $track (@{$track_list->tracks()}) {
      $_ = $track->title();
      # Various artist style
      if($track->artist) {
         print TMP "track $i: $_ / ", $track->artist->name(), "\n";
      }
      # Normal tracklist style.
      else {
         print TMP "track $i: $_\n";
      }
      $i++;
      $j++;
   }

   close TOC;
   # MusicBrainz does not state data tracks. Let's continue to fill up
   # the tracklist in the %cd-hash.
   while($i <= $trackno) {
   print TMP "track $i: data\n";
      $i++;
      $j++;
   }
   close TMP;
   read_entry("$temp_file", "musicbrainz", $trackno);
   unlink("$temp_file");
}
########################################################################
#
# Display CDDB info.
#
sub disp_info {
   my $latinflag = 0;
   my $wideflag = 0;
   my $utf_latinflag = 0;
   my $utf_wideflag = 0;
   my ($artist, $album, %config, $revision);
   my @comment = ();

   CDDB_get->import( qw( get_cddb get_discids ) );
   my $cd = get_discids($scsi_cddev);
   my ($id, $trackno, $toc) = ($cd->[0], $cd->[1], $cd->[2]);
   my $cddbid = sprintf("%08x", $id);
   my $totaltime = sprintf("%02d:%02d",$toc->[$trackno]->{min},$toc->[$trackno]->{sec});

   if(defined $cd{title}) {
      $album = clean_all($cd{title});
      $artist = clean_all($cd{artist});
      # Remember: use of lowercase was supposed for file names only,
      # tags should not be lowercase (what for?). But option
      # ucfirst is useful if DB entry is in uppercase only, and tags
      # in uppercase are rather ugly.
      $album = change_case($cd{title}) if($uppercasefirst == 1);
      $artist = change_case($cd{artist}) if($uppercasefirst == 1);
      $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 if($year);
      }

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

      @comment = extract_comm;
      $revision = get_rev() unless($cd{discid});
      # In case of corrupted (local) DB files.
      $revision = "unknown" unless($revision);
   }
   else {
      if($submission == 0) {
         print "\nNo CDDB info chosen or found for this CD\n"
            if($verbose >= 1);
      }
      # Set submission OK, will be set to 0 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};
   }

   if($cd{discid}) {
      # We do nothing anymore because we read the data from a file.
   }
   else {
      # The strings from archive files should be OK, because the files
      # should be written in the corresponding encoding. Only strings
      # from CDDB_get must be treated.
      # But still, this gives the error:
      # Cannot decode string with wide characters at
      # /usr/lib/perl5/5.8.8/i586-linux-threads-multi/Encode.pm line 186.
      # So do it here to be sure to analyze manually entered data!
      #
      # Create a string with the DB data to be analyzed for true UTF-8
      # (wide) characters.
      my $char_string =  $cd{title} . $cd{artist};
      $char_string .= $_ foreach (@{$cd{track}});

      ($latinflag, $wideflag, $utf_latinflag, $utf_wideflag) =
         check_encoding($char_string);

      if($utf_latinflag >= $latinflag * 3 && $utf_wideflag == 0 && $wideflag == 0) {
         print "\nRare case: I will decode from iso 8859-1 to utf-8?\n"
            if($verbose >= 5);
         Encode::from_to($artist, 'iso-8859-1', 'UTF-8');
         Encode::from_to($album, 'iso-8859-1', 'UTF-8');
      }
      elsif($wideflag == 0 && $latinflag == 0) {
         print "No wide char found, artist is <$artist>.\n"
            if($verbose >= 5);
         print "No latin char found, artist is <$artist>.\n"
            if($verbose >= 5 && $latinflag == 0);
         $album = UTF8_encoding($album);
         $artist = UTF8_encoding($artist);
      }
      elsif($utf_wideflag == 1 && $wideflag == 0) {
         print "\nI try to force UTF8 case 1:\n"
            if($verbose >= 5);
         $album =  Encode::decode('UTF-8', $album);
         $artist =  Encode::decode('UTF-8', $artist);
      }
      elsif($utf_latinflag % 2 == 0 && $latinflag % 2 == 0 && $utf_wideflag == 0 && $wideflag == 0) {
         print "\nI try to force UTF8 case 2: source might be utf8!\n"
            if($verbose >= 5);
         # Keep it commented for the archive and online Röyksopp case.
#         $album = Encode::decode('UTF-8', $album);
#         $artist = Encode::decode('UTF-8', $artist);
      }
      elsif($utf_wideflag > 0 && $wideflag >= 0) {
         print "\nI try to force UTF8 case 3: source might be utf8!\n"
            if($verbose >= 5);
         # Keep it commented for the clean archive Enya case.
#         $album =  Encode::decode('UTF-8', $album);
#         $artist =  Encode::decode('UTF-8', $artist);
      }
      else {
         print "\nI don't know what to do. Is it cp-1252 or iso 8859-1?\n"
            if($verbose >= 5);
      }
   }


# Resetting the album and artist in the %cd-hash will screw up all the
# track titles (e.g. Bang Bang). Can you believe it? Change album and
# artist and tracknames will blow up. That's life, that's Perl.
# Again: we need a save copy of the string as it is now! What is wrong
# changing an entry of a hash? Why are all other entries of that
# hash screwed up?

   $album_utf8 = $album;
   $artist_utf8 = $artist;

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

   if($verbose >= 1) {
      print "\n", "-" x 17, "\nCDDB and tag Info", "\n", "-" x 17, "\n";
      print "Artist: $artist_utf8\n";
      print "Album: $album_utf8\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 "ASIN: $cd{asin}\n" if($cd{asin});
      print "Barcode: $cd{barcode}\n" if($cd{barcode});
      print "Language: $cd{language}\n" if($cd{language});
      print "Release date: $cd{reldate}\n" if($cd{reldate});
      print "Year: $year\n" if($year);
      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 the inserted CD...
      if($cddbid ne $cd{id} && defined $cd{id} ) {
         print "CDDB id: $cd{id}\n";
      }
      print "CD id: $cddbid\n";
      print "Discid: $cd{discid}\n" if($cd{discid});
      if(@comment && $verbose >= 2) {
         foreach (@comment) {
            print "Comment: $_\n" if($_);
         }
      }
      print "CD length: $totaltime\n";
      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");
   log_info("MB id: $cd{discid}") if($cd{discid} && $cd{discid} ne $cddbid);
   log_info("CD length: $totaltime\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 -= $minute * 60;
      printf("%s %02d:%02d %s %d %s\n",
         "There might be a hidden track", $minute, $second,
         "long,\nbecause offset of track 01 has", $frames,
         "frames\nintstead of typically 150 (equals 2 seconds).\n")
         if($verbose >= 1);
      my $riptrackname = "Hidden Track";
      $riptrackname = change_case($riptrackname);
      $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 hiddenflag == 1 or
      # hiddenflag == 0 while choosing tracknames.
      push @secondlist, "$second" if($hiddenflag == 1);
   }
   my $n = 1;
   # Print track information.
   foreach (@{$cd{track}}) {
      $_ = clean_all($_);
      $_ = change_case($_) if($uppercasefirst == 1);


      if($cd{discid}) {
         # We do nothing anymore because we read the data from a file.
      }
      else {
         if($utf_latinflag >= $latinflag * 3 && $utf_wideflag == 0 && $wideflag == 0) {
            Encode::from_to($_, 'iso-8859-1', 'UTF-8');
         }
         elsif($latinflag == 0 && $wideflag == 0) {
            $_ = UTF8_encoding($_);
         }
         elsif($utf_wideflag == 1 && $wideflag == 0) {
            $_ =  Encode::decode('UTF-8', $_);
         }
      }
      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 -= $minute * 60;
      $_ = clean_chars($_) if($chars);
      printf("%02d: [%02d:%02d.%02d] %s\n",
             $n, $minute, $second, $frame, $_)
         if($verbose >= 2);
      $_ = clean_name($_);
      $_ = change_case($_);
      $_ =~ 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";
   }
   unless($tracklist[0]) {
      die "Error: No tracks 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;
               if($outrack[0] > ($#tracklist + 1)) {
                  die "Track selection higher than number of tracks ",
                      "on CD.\n\n";
               }
            }
            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";
   }

   @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!
sub ask_subm {
   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() unless($cd{discid});
         if($revision) {
            print "\nPlease change some settings!";
            print "\nYou may confirm CDDB settings with \"enter\"!\n";
            create_deftrack(0);
         }
         else {
            print "\nPlease change some settings!";
            print "\nYou may confirm given settings with \"enter\"!\n";
            create_deftrack(0);
         }
      }
      elsif($index != 0) {
         print "You should choose 0 or 1!\n";
      }
   }
   if($index == 1) {
      pre_subm();
   }
   return;
}
########################################################################
#
# Create the directory where the sound files shall go.
# Directory created will be: /outputdir/$dirtemplate[$c] .
# We first check the wavdir and set the counter $c for the encoder
# depending arrays @sepdir, @suffix, @globopt to -1. In this way,
# directory names will not be suffixed with a counter if they shall be
# the same for wavs and encoded files (condition $soundir ne $wavdir and
# the exception handling below).
#
sub create_dirs {
   my $c = -1;

   # Get cddbid and number of tracks of CD.
   my $trackno;
   ($cddbid, $trackno) = get_cddbid();

   foreach("wav", @coder) {
      my $suffix = $suffix[$c] if(defined $suffix[$c]);
      $suffix = "wav" if($_ eq "wav");
      my $quality = $globopt[$c] if(defined $globopt[$c]);
      $quality = "" if($_ eq "wav");


      # Why this? Remember, we have worked a lot with encoding of artist
      # and album names!
      my $album = clean_all($album_utf8);
      my $artist = clean_all($artist_utf8);
      $album = clean_name($album);
      $artist = clean_name($artist);
      $album = clean_chars($album) if($chars);
      $artist = clean_chars($artist) if($chars);
      $artist = change_case($artist);
      $album = change_case($album);
      $album =~ s/ /_/g if($underscore == 1);
      $artist =~ s/ /_/g if($underscore == 1);

      # Define variable for initial letter of artist.
      my $iletter = $artist;
      $iletter =~ s/\s*(.).*/$1/;
      if($iletter =~ /\d/) {
         my @words = split(/ /, $artist);
         shift(@words);
         foreach (@words) {
            $iletter = $_;
            $iletter =~ s/\s*(.).*/$1/;
            last if($iletter =~ /\w{1}/);
         }
      }
      $iletter = "A" unless($iletter);
      $iletter = "\u$iletter" unless($lowercase == 1);

      my $dirindex = $c;
      if($suffix eq "wav") {
         $dirindex = $#dirtemplate;
      }
      elsif($c > $#dirtemplate) {
         $dirindex = $#dirtemplate;
      }

      # Check and create the full path where the files will go.
      # Check the dirtemplate and use the actual year as default if
      # $year is in the template and none is given!
      if(($dirtemplate[$dirindex] =~ /\$year/ or
          $tracktemplate =~ /\$year/) && !$year) {
         $year = sprintf("%04d", sub {$_[5]+1900}->(localtime));
      }
      # Do the same for the genre.
      if(($dirtemplate[$dirindex] =~ /\$genre/ or
          $tracktemplate =~ /\$genre/)) {
         $genre = "Other" if($genre eq "");
         chomp $genre;
      }

      my $dir;
      if(!eval("\$dir = $dirtemplate[$dirindex]")) {
         die "Directory template incorrect, caused eval to fail: $!\n";
      }

      $dir =~ s,\s-\s/,/,g; # Do this in any case, even if all chars are
      $dir =~ s,\s-\s$,,g;  # allowed.
      $dir =~ s,\s+/,/,g; # Do this in any case, even if all chars are
      $dir =~ s,\s+, ,g;  # allowed.
      $dir =~ s,\s-\s-\s*, ,g;
      # Change case again only if lowercase wanted! Else we will get
      # lower case of special dirtemplates like: $iletter/$artist: here
      # artist would be converted to lowercase, since we check for words!
      $dir = change_case($dir) if($lowercase == 1);
      $dir = clean_chars($dir) if($chars);
      $dir =~ s/ /_/g if($underscore == 1);

      $dir =~ s/\.+$// if($chars =~ /NTFS/);
      $dir =~ s/^\///;
      my $soundir = $outputdir . "/" . $dir if($outputdir !~ /\/$/);
      $soundir = $outputdir . $dir if($outputdir =~ /\/$/);
      # Check if the soundir already exists, if it does, try "soundir i"
      # with i an integer until it works, unless option resume is given.
      #
      # TODO: What if two identical named discs shall be done, but with
      # different number of tracks (different track names will be too
      # difficult to distinguish!)? Maybe we should test here the number
      # of tracks in an existing directory with same name...
      # E.g. Nouvelle Vague: Bande à part, EU version, US version,
      # LTD. Ed, initial release version... all have the same name but
      # different track names/numbers.
      #
      my $cdexistflag = 0;
      my $i = 1;
      my $nsoundir = $soundir;
      my $sfx = "";
      while(defined(opendir(TESTDIR, $nsoundir)) &&
            $rip == 1 && $resume == 0 && $soundir ne $wavdir) {
         $sfx = " " . $i if($underscore == 0);
         $sfx = "_" . $i if($underscore == 1);
         $sfx = clean_chars($sfx) if($chars);
         $nsoundir = $soundir . $sfx;
         $i++;
         $cdexistflag = 1;
      }
      return "next" if($cdexistflag == 1 && $overwrite =~ /^e|q$/);
      return "unknown" if($artist =~ /unknown.artist/i && $album =~ /unknown.album/i && $quitnodb == 1);

      $nsoundir = $soundir if($overwrite eq "y");
      # Exception handling: if the $wavdir is identical to the
      # $nsoundir apart from a suffixed counter, use the $wavdir as
      # $soundir instead of the incremented $nsoundir!
      esc_char($soundir);
      $nsoundir = $wavdir if($wavdir =~ /$soundir.\d+/);

      if($multi == 1 && $_ eq "wav") {
         my $aadir = $dir . $sfx;
         if($cdexistflag == 1) {
            $i--;
            open(SRXY,"$logfile") or
               print "Can not open \"$logfile\"!\n";
            my @srxylines = <SRXY>;
            close(SRXY);
            chomp(my $orig_album = join(' ', grep(/^album:\s/, @srxylines)));
            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;
            print SRXY "Original-$orig_album\n";
            close(SRXY);
         }
         open(SRXY,">>$logfile")
            or print "Can not append to file \"$logfile\"!\n";
         print SRXY "\n\nArtist - Album:$aadir";
         close(SRXY);
      }
      $soundir = $nsoundir;
      $soundir =~ s;/$;;g;

      # Problem: multi level directory creation should set permission to
      # each directory level. I thought the easiest way would be to
      # alter permissions using umask and then set it back. I did not
      # succeed.
      #
      # Save machines umask for reset.
      my $umask = umask();

      # Get the default permission mode.
      my $dperm = sprintf("%04o", 0777 & ~umask());

      # I've read
      # -) perldoc -f umask
      # -) Perl Cookbook
      # -) Perl by Randal L. Schwartz
      # -) internet

      # I tried this:
      #umask(umask(0750) & ~0444); # Hm, the result is not as expected.

      # Then I tired this:
      #umask($umask | 0666 ^ $dpermission) if(defined $dpermission);

      # Then I tired this:
      #umask(488); # gives d-wx-w-rwx, do you understand it? I don't.

      # Then I tired this:
      #umask(002); # gives drwxrwxr-x, takes away write permission...

      # Then I give up....http://www.dublan.net/Notes/Perl_umask.html
      #umask 384; # should give: rw------- but gives d-wxrwxrwx

      # Maybe I forgot to set everything to zero?
      #umask 0;
      #umask 384; # should give: rw------- but gives d-wxrwxrwx

      # http://docstore.mik.ua/orelly/perl/prog/ch03_179.htm
      # The umask function sets the umask for the process and returns
      # the old one. (The umask tells UNIX which permission bits to
      # disallow when creating a file.) If EXPR is omitted, the function
      # merely returns the current umask.
      # For example, to ensure that the "other" bits are turned on, and
      # the "user" bits are turned off, try something like:
      #umask((umask() & 077) | 7);
      # gives drwxr-x---, but that is not what the text says, right?
      # No chance to understand umask behaviour.

      # Maybe this helps:   http://www.perlmonks.org/?node_id=543251
      # On the command line try this:
      # perl -e 'printf "umask: %04o\n", umask;
      #          printf "mode: %04o\n", 0777;
      #          printf "masked mode: %04o\n", 0777 & ~umask'
      # or
      # perl -e 'umask 0; system("mkdir -m 0750 -p \"FOO/test/path\"")'
      # It does not work, no no no.

      # I don't use umask, no initialisation; enough is enough!
      #umask 0;

      if(!opendir(TESTDIR, $soundir)) {
         # Explicitly log soundir creation.
         log_info("new-mediadir: $soundir");

         # The so called Holzhacker-Method: create dir level by level.
         # TODO: Let me know the good way to do it, thanks.
         my $growing_dir = "";
         foreach (split(/\//, $soundir)) {
            next if($_ eq " ");
            $growing_dir .= "/$_";
            $growing_dir =~ s;//;/;g;
            if(!opendir(TESTDIR, $growing_dir)) {
               log_system("mkdir -m $dpermission -p \"$growing_dir\"")
                  or die "Can not create directory $growing_dir: $!\n";
               }
         }
         # Do it again for security reasons.
         log_system("mkdir -m $dpermission -p \"$soundir\"")
            or die "Can not create directory $soundir: $!\n";
      }
      else {
         closedir(TESTDIR);
      }

      # Reset umask
      #umask($umask) if defined $umask;

      $sepdir[$c] = $soundir unless($_ eq "wav");
      $wavdir = $soundir if($_ eq "wav");
      $c++;


      # This might not be the best place to set up the exec command,
      # but this is where most variables are available.
      if($execmd && $suffix eq "wav") {
         my $exec;
         if(!eval("\$exec = $execmd")) {
            print "execmd incorrect, caused eval to fail: $!\n";
         }
         $execmd = $exec;
      }
   }
   return("go");
}
########################################################################
#
# Create the full-path track file name from the tracktemplate variable.
#
sub get_trackname {
   my($trnum, $trname, $riptrname, $shortflag);

   ($trnum, $trname, $shortflag) = @_;
   $shortflag = 0 unless($shortflag);

   my $album = clean_all($album_utf8);
   my $artist = clean_all($artist_utf8);
   $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($trname =~ /short/ && $shortflag =~ /short/) {
      $riptrname = $trname;
   }
   elsif(defined $cd{title}) {
      # We do not need to lowercase the tracktemplate because all
      # variables are already lowercase!
      $tracktemplate =~ s/ /\\_/g if($underscore == 1);
      # We have to update tracknum and trackname because they're
      # evaluated by the tracktemplate!
      my $tracknum = sprintf("%02d", $trnum);
      my $trackname = $trname;
      if(!eval("\$riptrname = $tracktemplate")) {
         die "Track Template incorrect, caused eval to fail: $!";
      }
   }
   else {
      $trname  = change_case($trname);
      $trname =~ s/ /_/g if($underscore == 1);
      $riptrname = $trname;
   }

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

   # Cleaning.
   my $albumtag = clean_all($album_utf8);
   my $artistag = clean_all($artist_utf8);
   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);

   # Delete existing md5 files in case of resuming.
   if($md5sum == 1 && $resume == 1) {
      if($wav == 1) {
         my @paths = split(/\//, $wavdir);
         my $md5file =  $paths[$#paths] . " - wav" . ".md5";
         $md5file =~ s/ /_/g if($underscore == 1);
         unlink("$wavdir/$md5file");

      }
      for(my $c=0; $c<=$#coder; $c++) {
         my @paths = split(/\//, $sepdir[$c]);
         my $md5file =  $paths[$#paths] . " - " . $suffix[$c] . ".md5";
         $md5file =~ s/ /_/g if($underscore == 1);
         unlink("$sepdir[$c]/$md5file");
      }
   }

   # Delete machine.lock files.
   if($resume == 1) {
      opendir (DIR, "$wavdir") or print "Can't open $wavdir $!\n";
      my @lockfiles = grep(/\.lock_\d+$/, readdir(DIR));
      @lockfiles = grep(/\.lock$/, readdir(DIR)) unless($lockfiles[0]);
      closedir DIR;
      unlink("$wavdir/$_") foreach (@lockfiles);
   }

   # Define an array with intervals 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]];
            # Don't merge all track names if option book is used.
            $tracklist[$beg] = $tracklist[$beg] . $concat .
                               $tracklist[$bea[0]] unless($book == 1);
            $tracktags[$beg] = $tracktags[$beg] . " + " .
                               $tracktags[$bea[0]] unless($book == 1);
            $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 interval 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);
   }


   # Prevent failure if hald occupies drive.
   sleep 6 if($loop == 2);
   # 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,">$wavdir/error.log")
      or print "Can not append to file \"$wavdir/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 ,">$wavdir/cd.toc")
         or print "Can not append to file \"$wavdir/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:  1,0,0 : _NEC  ",
                  " then use e. g. command",
                  "\n//>cdrdao write --device 1,0,0 ",
                  "--speed 4 cd.toc< to burn the CD.",
                  "\n//Note: Not all CD (DVD) burners are able to burn",
                  " CD-text!\n//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 = change_case($riptrackname);
      $riptrackname =~ s/ /_/g if($underscore == 1);
      $riptrackname = get_trackname(0, $riptrackname);
      if(-r "$wavdir/$riptrackname.rip") {
         unlink("$wavdir/$riptrackname.rip");
         print "Found $riptrackname.rip.\n" if($verbose >= 1);
      }
      elsif(-r "$wavdir/$riptrackname.wav") {
         $checknextflag = 1;
         print "Found $riptrackname.wav.\n" if($verbose >= 1);
         md5_sum("$wavdir", "$riptrackname.wav", 0)
            if($md5sum == 1 && $wav == 1);
      }
      else{
         for(my $c=0; $c<=$#coder; $c++) {
            if(-r "$sepdir[$c]/$riptrackname.$suffix[$c]") {
               $checknextflag = 1;
               print "Found file $riptrackname.$suffix[$c].\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 = change_case($riptrackname);
      $riptrackname =~ s/ /_/g if($underscore == 1);
      unshift @seltrack, 0;
      unshift @tracklist, $riptrackname;
      $riptrackname = get_trackname(0, $tracklist[0]);
      # 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 .= " -Z" if($parano == 0);
      $ripopt .= " -q" if($verbose <= 1 && $ripopt !~ /\s-q/);
      $ripcom = "cdparanoia $ripopt -d $cddev [00:00] \\
                \"$wavdir/$riptrackname.rip\"";
      print "\nRipping \"$riptrackname\"...\n"
         if($verbose >= 1 && $rip == 1);

      unless(log_system("$ripcom")) {
         if($parano == 2) {
            $ripopt = $ripopt . " -Z" if($parano == 2);
            $ripcom = "cdparanoia $ripopt -d $cddev [00:00] \\
                      \"$wavdir/$riptrackname.rip\"";
            print "\n\nTrying again without paranoia.\n"
               if($verbose > 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);
            }
         }
         else {
            # 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 file.
      if($cdtoc == 1 && $hiddenflag == 1) {
         open(CDTOC ,">>$wavdir/cd.toc")
            or print "Can not append to file \"$wavdir/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 \"$riptrackname.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
      # chosen for this track, splitting is not allowed, while
      # extracting one chunk of sound may be desired.
      my @times = (0);
      if($ghost == 1 && $hiddenflag == 1) {
            @times = get_chunks(0, $riptrackname);
            unless($times[0] eq "blank") {
               split_chunks(0, "$riptrackname", 0, @times);
               rename_chunks(0, "$riptrackname", 0, @times);
               }
      }
      if($hiddenflag == 1) {
         rename("$wavdir/$riptrackname.rip",
                "$wavdir/$riptrackname.wav");
      }
      $ripopt = $saveripopt;
   }
   # End preparation 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, use counter $encline.
   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);
      $riptracktag = $tracktags[$_ - 1];
      $riptracktag = $tracktags[$_] if($hiddenflag == 1);
      my $riptrackno = $_;
      # 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 = $riptracktag;
         $cdtoctitle = oct_char($cdtoctitle);
         my $cdtocartis = oct_char($artistag);
         open(CDTOC, ">>$wavdir/cd.toc")
            or print "Can not append to file \"$wavdir/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 \"$riptrackname.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($riptrackname,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 (excluding the full path)
      # > 200 characters.
      if(length($riptrackname) + length($wavdir) > 200) {
         print "Warning: output trackname is longer than 200 chars,\n",
               "RipIT will use a temporary output name to for the ",
               "WAV-file.\n"
            if($verbose > 2);
         $riptrackname = get_trackname($_, $_ . "short", "short");
      }

      # Check for tracks already done if option --resume is on.
      $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 process at the end
            #     of this subroutine will not be started at all!
            # ii) why should we wait for the first missing wav, if
            #     other wavs are already here and encoding could start
            #     (continue) right away?
            if($startenc == 0 && $encode == 1) {
               $startenc = 1;
               open(ENCLOG,">$wavdir/enc.log");
               close ENCLOG;
               unless(fork) {
                  enc_cd();
               }
            }
         }

         if(-r "$wavdir/$riptrackname.rip") {
            unlink("$wavdir/$riptrackname.rip");
            print "Found $riptrackname.rip.\n" if($verbose >= 1);
         }
         elsif(-r "$wavdir/$riptrackname\_rip.wav" && $ripper == 2) {
            unlink("$wavdir/$riptrackname\_rip.wav");
            print "Found $riptrackname\_rip.wav.\n" if($verbose >= 1);
         }
         elsif(-r "$wavdir/$riptrackname.wav") {
            $checknextflag = 1;
            print "Found $riptrackname.wav.\n" if($verbose >= 1);
            if($md5sum == 1 && $wav == 1) {
               md5_sum("$wavdir", "$riptrackname.wav", 0);
            }
         }
         elsif($wav == 0) {
            for(my $c = 0; $c <= $#coder; $c++) {
               if(-r "$sepdir[$c]/$riptrackname.$suffix[$c]") {
                  $checknextflag = 1;
                  print "Found file $riptrackname.$suffix[$c].\n"
                     if($verbose >= 1);
               }
               else {
                  $checknextflag = 2;
               }
               last if($checknextflag == 2);
            }
         }
         # Cdda2wav is somehow unpleasant. It dies not quick enough with
         # ^+c. I. e. even if a track has not been ripped to the end,
         # the *.rip file will become a *.wav. So we have to check for
         # completely encoded files and assume, that for not encoded
         # files, there is no fully ripped file. OK, perhaps it would be
         # better to check for the last *.wav file and rerip only that
         # one. But on a modern machine, the encoder won't be far from
         # catching up the ripper, so deleting all *.wavs for missing
         # encoded files won't hurt, because cdda2wav is quite fast,
         # ripping those tracks again doesn't cost a lot of time.
         if($ripper == 2 && $checknextflag == 1) {
            for(my $c = 0; $c <= $#coder; $c++) {
               if(-r "$sepdir[$c]/$riptrackname.$suffix[$c]") {
                  $checknextflag = 1;
               }
               else {
                  $checknextflag = 2;
               }
               last if($checknextflag == 2);
            }
         }
      }
      # Skip that track, i.e. restart the foreach-loop of tracks if a
      # wav file or other (mp3, ogg, flac, m4a) 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 \"$riptrackname\"...\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 .= " -r 3" if($parano == 0 && $ripopt !~ /\s-r\s3/);
            $ripopt .= " -v" if($verbose >= 2 && $ripopt !~ /\s-v/);
         }
         $ripcom = "(dagrab $ripopt -d $cddev \\
                    -f \"$wavdir/$riptrackname.rip\" \\
                    $riptrackno 3>&1 1>&2 2>&3 \\
                    | tee -a \"$wavdir/error.log\") 3>&1 1>&2 2>&3 ";
         $ripcom =~ s/\$/\\\$/g;
         $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,">>$wavdir/error.log")
               or print "Can not append to file ",
                        "\"$wavdir/error.log\"!\n";
            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 .= " -Z" if($parano == 0 && $ripopt !~ /\s-Z/);
            $ripopt .= " -q" if($verbose < 2 && $ripopt !~ /\s-q/);
         }
         # Introduce the span argument into the tracknumber, adjust the
         # tracknumber suffix according to cdparanoia and recalculate
         # the track length (used in the playlist file).
         if($span) {
            my @bea = split(/-/, $span);
            my $offset = 0;
            my $chunk = 0;
            $offset = span_length($bea[0]) if($bea[0]);
            $chunk = span_length($bea[1]) if($bea[1]);
            $bea[0] = "0.0" unless($bea[0]);
            $bea[1] = " " unless($bea[1]);
            $bea[0] = "[" . $bea[0] . "]" if($bea[0] =~ /\d+/);
            $bea[1] = "[" . $bea[1] . "]" if($bea[1] =~ /\d+/);
            if($riptrackno =~ /-/) {
               my($i, $j) = split(/-/, $riptrackno);
               # Special case: if the chunk of sound is larger than the
               # (last) track, use the true track length instead of chunk
               # size.
               if($hiddenflag == 0 && $secondlist[$j - 1] < $chunk) {
                  $chunk = 0;
                  $bea[1] = " ";
               }
               elsif($hiddenflag == 1 && $secondlist[$j] < $chunk) {
                  $chunk = 0;
                  $bea[1] = " ";
               }
               if($chunk <= 0) {
                  $chunk = $secondlist[$j - 1] if($hiddenflag == 0);
                  $chunk = $secondlist[$j] if($hiddenflag == 1);
               }
               $secondlist[$_ - 1] = $secondlist[$_ - 1] - $secondlist[$j - 1] + $chunk - $offset if($hiddenflag == 0);
               $secondlist[$_] = $secondlist[$_] - $secondlist[$j] + $chunk - $offset if($hiddenflag == 1);
               $riptrackno = $i . $bea[0] . "-" . $j . $bea[1];
            }
            else {
               # Special case: if the chunk of sound is larger than the
               # (last) track, use the true track length instead of chunk
               # size.
               if($hiddenflag == 0 && $secondlist[$_ - 1] < $chunk) {
                  $chunk = 0;
                  $bea[1] = " ";
               }
               elsif($hiddenflag == 1 && $secondlist[$_] < $chunk) {
                  $chunk = 0;
                  $bea[1] = " ";
               }
               $riptrackno = $riptrackno . $bea[0] . "-" . $riptrackno . $bea[1];
               # Variable $chunk is zero if span reaches the end of the
               # track.
               if($chunk <= 0) {
                  $chunk = $secondlist[$_ - 1] if($hiddenflag == 0);
                  $chunk = $secondlist[$_] if($hiddenflag == 1);
               }
               $chunk -= $offset;
               $secondlist[$_ - 1] = $chunk if($hiddenflag == 0);
               $secondlist[$_] = $chunk if($hiddenflag == 1);
            }
         }
         if($multi == 0) {
            # Handle special paranoia mode for single failed tracks.
            my $save_ripopt = $ripopt;
            my $save_failflag = $failflag;
            if($parano == 2 && $failflag == 1) {
               $ripopt = $ripopt . " -Z" if($parano == 2);
               print "\n\nTrying again without paranoia.\n"
                  if($verbose > 1);
            }
            # Make sure $failflag is set to 0 if success.
            $failflag = 0;
            $ripcom = "cdparanoia -d $cddev $riptrackno $ripopt \\
               \"$wavdir/$riptrackname.rip\"";
            $ripcom =~ s/\$/\\\$/g;
            $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.
               if($parano == 2 && $save_failflag == 1 || $parano < 2 ) {
                  open(ERO,">>$wavdir/error.log")
                     or print "Can not append to file ",
                              "\"$wavdir/error.log\"!\n";
                  print ERO "Track $saveriptrackno on CD $artist - $album ";
                  print ERO "failed!\n";
                  close(ERO);
               }
               $failflag = $save_failflag + 1;
            }
            $ripopt = $save_ripopt;
         }
         elsif($multi == 1) {
            my $save_ripopt = $ripopt;
            my $save_failflag = $failflag;
            if($parano == 2 && $failflag == 1) {
               $ripopt .= " -Z" if($parano == 2);
               print "\n\nTrying again without paranoia.\n"
                  if($verbose > 1);
            }
            $ripcom = "cdparanoia -d $cddev $riptrackno $ripopt \\
               \"$wavdir/$riptrackname.rip\"";
            # Log the ripping output only when using paranoia!
            $ripcom .= " 2>> \"$logfile.$saveriptrackno.txt\""
               if($parano == 2 && $failflag == 1 || $parano < 2 );
            $ripcom =~ s/\$/\\\$/g;
            $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0);
            $failflag = 0;
            unless(log_system("$ripcom")) {
               if($parano == 2 && $save_failflag == 1 || $parano < 2 ) {
                  # Append error message to file srXY for rip2m to start
                  # checktrack.
                  open(SRXY,">>$logfile")
                     or print "Can not append to file \"$logfile\"!\n";
                  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,">>$wavdir/error.log")
                     or print "Can not append to file ",
                              "\"$wavdir/error.log\"!\n";
                  print ERO "Track $saveriptrackno 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.$saveriptrackno.txt")
                        or print "Can not append to file ",
                                 "\"$logfile.$saveriptrackno.txt\"!\n";
                     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,">>$outputdir/failed.log")
                        or print "Can not append to file ",
                                 "\"$outputdir/failed.log\"!\n";
                     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;
                  }
               }
               $failflag = $save_failflag + 1;
            }
            $ripopt = $save_ripopt;
         }
         # This is an awkard workaround introduced because of the
         # enhanced --paranoia option. Failures on data tracks are not
         # captured anymore. Force update of error.log for encoder.
         # Remember, because of option --span $riptrackno can be a
         # string. Use $saveriptrackno instead.
         if(! -f "$wavdir/$riptrackname.rip") {
            if($saveriptrackno == $tracksel[$#tracksel] &&
               $riptrackname =~ /data|video/i) {
               open(ERO,">>$wavdir/error.log")
                  or print "Can not append to file ",
                           "\"$wavdir/error.log\"!\n";
               print ERO "Track $saveriptrackno on CD $artist - $album ";
               print ERO "failed!\n";
               close(ERO);
               if($multi == 1) {
                  # Append error message to file srXY for rip2m to start
                  # checktrack.
                  open(SRXY,">>$logfile")
                     or print "Can not append to file \"$logfile\"!\n";
                  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);
               }
               # Missuse of variable failflag, we don't care, it's the
               # last track!
               $failflag = 3;
            }
            else {
               print "\nRip file $riptrackname.rip not found...\n"
               if($verbose > 2);
            }
         }
      }
      elsif($ripper == 2 && $rip == 1) {
         if($trackcn == 1) {
            $ripopt .= " -q" if($verbose <= 1 && $ripopt !~ /\s-q/);
         }
         $ripcom = "cdda2wav -D $cddev -H $ripopt ";
         # Introduce the span argument into the tracknumber and recalculate the track
         # length (used in the playlist file). We use $duration instead of $chunk in the cdparanoia part above.
         if($span) {
            my @bea = split(/-/, $span);
            my $offset = 0;
            my $duration = 0;
            $offset = span_length($bea[0]) if($bea[0]);
            $duration = span_length($bea[1]) if($bea[1]);
            if($riptrackno =~ /\+/) {
               my($i, $j) = split(/\+/, $riptrackno);
               if($hiddenflag == 0) {
                  if($secondlist[$j - 1] < $duration) {
                     $duration = 0;
                  }
                  else {
                     $duration = $secondlist[$_ - 1] = $secondlist[$_ - 1] - $secondlist[$j - 1] + $duration - $offset;
                  }
               }
               elsif($hiddenflag == 1) {
                  if($secondlist[$j - 1] < $duration) {
                     $duration = 0;
                  }
                  else {
                     $duration = $secondlist[$_] = $secondlist[$_] - $secondlist[$j] + $duration - $offset;
                  }
               }
            }
            else {
               if($hiddenflag == 0 && $secondlist[$_ - 1] < $duration) {
                  $duration = 0;
               }
               elsif($hiddenflag == 1 && $secondlist[$_] < $duration) {
                  $duration = 0;
               }
               else {
                  $duration -= int($offset);
                  $secondlist[$_ - 1] = $duration if($hiddenflag == 0);
                  $secondlist[$_] = $duration if($hiddenflag == 1);
               }
            }
            $duration = 0 if($duration < 0);
            $offset *= 75;
            $ripcom .= "-o $offset ";
            $ripcom .= "-d $duration " if($duration > 0);
         }
         if($multi == 0) {
            $ripcom .= "-t $riptrackno \"$wavdir/$riptrackname\_rip\"";
            $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0);
            $ripcom =~ s/\$/\\\$/g;
            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,">>$wavdir/error.log")
                  or print "Can not append to file ",
                           "\"$wavdir/error.log\"!\n";
               print ERO "Track $saveriptrackno on CD $artist - $album ";
               print ERO "failed!\n";
               close(ERO);
               $failflag++;
            }
         }
         elsif($multi == 1) {
            $ripcom = "-t $riptrackno \"$wavdir/$riptrackname\_rip\" \\
               2>> \"$logfile.$saveriptrackno.txt\"";
            $ripcom = "nice -n $nicerip " . $ripcom if($nicerip != 0);
            $ripcom =~ s/\$/\\\$/g;
            unless(log_system("$ripcom")) {
               # Append error message to file srXY for rip2m to start
               # checktrack.
               open(SRXY,">>$logfile")
                  or print "Can not append to file \"$logfile\"!\n";
               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,">>$wavdir/error.log")
                  or print "Can not append to file ",
                           "\"$wavdir/error.log\"!\n";
               print ERO "Track $saveriptrackno 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.$saveriptrackno.txt")
                     or print "Can not append to file ",
                              "\"$logfile.$saveriptrackno.txt\"!\n";
                  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,">>$outputdir/failed.log")
                     or print "Can not append to file ",
                              "\"$outputdir/failed.log\"!\n";
                  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;
               }
            }
         }
         print "\n" if($verbose > 1);
      }
      elsif($ripper == 3 && $rip == 1) {
         $ripcom = "tosha -d $cddev -f wav -t $riptrackno \\
            -o \"$wavdir/$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 - \"$wavdir/$riptrackname.wav\"";
         $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.\n";
      }

      redo if($ripper == 1 && $failflag == 1 && $parano == 2);

      # 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("$wavdir/$cddatrackname.wav",
                   "$wavdir/$riptrackname.rip");
         }
         else {
            rename("$wavdir/$riptrackname\_rip.wav",
                   "$wavdir/$riptrackname.rip");
         }
      }
      # Check for gaps and silence in tracks.
      my @times = (0);
      my $save_cdtocn = $cdtocn;
      if(-r "$wavdir/$riptrackname.rip") {
         # Remember: $saveriptrackno is the single track number, whereas
         # $riptrackno may hold an interval if option merge is used.
         if($ghost == 1 && $failflag == 0) {
            @times = get_chunks($saveriptrackno, $riptrackname);
            unless($times[0] eq "blank") {
               split_chunks($saveriptrackno, "$riptrackname",
                            $cdtocn, @times);
               $cdtocn = rename_chunks($saveriptrackno, "$riptrackname",
                                       $cdtocn, @times);
               }
         }
      }
      # A blank track has been deleted.
      next if($times[0] eq "blank");
      # Final stuff.
      rename("$wavdir/$riptrackname.rip", "$wavdir/$riptrackname.wav");
      md5_sum("$wavdir", "$riptrackname.wav", 0)
         if($md5sum == 1 && $normalize == 0 &&
            $wav == 1 && $failflag == 0);
      # Writing inf files for cdburning.
      # We use the $save_cdtocn counter as track counter instead of the
      # $riptrackno because $riptrackno might hold a span argument and
      # does not reflect the exact number of tracks created.
      # Use failflag == 3 to prevent writing inf file for failed data
      # track.
      if($inf >= 1 && $failflag < 3) {
         $trackstart = write_inf($wavdir, $riptrackname, $artistag,
           $albumtag, $riptracktag, $save_cdtocn, $cdtocn, $trackstart);
      }
      chmod oct($fpermission), "$wavdir/$riptrackname.wav"
         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));
            chomp $encstart;
            if($multi == 1) {
               open(SRXY,">>$logfile")
                  or print "Can not append to file \"$logfile\"!\n";
               print SRXY "\nEncoding started: $encstart";
               close(SRXY);
            }
            $startenc = 1;
            open(ENCLOG,">$wavdir/enc.log");
            close ENCLOG;
            unless(fork) {
               enc_cd();
            }
         }
      }
      # Print encoder messages saved in enc.log not to spoil the
      # ripper output.
      if($encode == 1 && $normalize == 0) {
         open(ENCLOG, "< $wavdir/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("$wavdir/enc.log") if(-r "$wavdir/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, ">>$wavdir/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\"!\n";
      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($escdir, $norm, $normtrackname);
   $escdir = $wavdir;
   $escdir = esc_char($escdir);

   # Generate filelist to be processed:
   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 .= "$escdir/$riptrackname.wav" . " \\\n          ";
   }
   $normtrackname =~ s/\s*$//;
   $normtrackname =~ s/\$/\\\$/g;

   # Add verbosity:
   $normopt .= "q" if($verbose == 0);
   $normopt .= "v" if($verbose >= 2 && $normopt !~ /q/);
   $normopt .= "vv" if($verbose >= 4 && $normopt !~ /q/);

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

   if(log_system("$norm")) {
      log_info("\nNormalizing complete.\n");
      print "\nNormalizing complete.\n" if($verbose >= 1);
   }
   else {
      die "\nNormalizing failed.\n";
   }
}
########################################################################
#
# 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, $suffix);
   my ($albumlametag, $artislametag, $commentlametag, $tracklametag);
   my ($ripcomplete, $trackcn, $totalencs) = (0, 0, 0);
   my $lastskip = $tracksel[0];
   my $resumenc = $resume;
   my $encodername = "";
   my @md5tracks = ();  # List of tracks to be md5-checked.

   # Cleaning.
   my $albumtag = clean_all($album_utf8);
   my $artistag = clean_all($artist_utf8);
   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 header of playlist file.
   my $playfile;
   if($playlist >= 1) {
      $playfile = "$artist" . " - " . "$album" . ".m3u";
      $playfile =~ s/ /_/g if($underscore == 1);
      open(PLST, ">$wavdir/$playfile") or
         print "Can't open $wavdir/$playfile! $!\n";
      print PLST "#EXTM3U\n";
   }

   # If using book-option define a chapter file.
   my $chapterfile;
   if($book >= 1) {
      $chapterfile = "$artist" . " - " . "$album" . ".chapters.txt";
      $chapterfile =~ s/ /_/g if($underscore == 1);
   }

   my $ghostflag = 0;
   my $ghostcn = 0;

   if($commentag =~ /^discid|cddbid$/) {
      $commentag = $cd{discid} if($commentag =~ /^discid$/);
      $commentag = $cd{id} if($commentag =~ /^cddbid$/);
      $commentlametag = $commentag;
   }
   # 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 done, this array will be
      # updated if ghost songs were found by the ripper.
      # Now: if only one track in the middle of the album has been
      # selected, problems occur if this track has ghost songs. 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-list and tag-list
      # arrays have all tracknames of the whole CD, so after track
      # number 4 will come track number 5! Therefor no track
      # "04 name of track 5" will be found and the encoder fails!
      # To prevent this: Once all (selected) tracks are done, we have to
      # set the $ghostcn to the total number of tracks of the CD to
      # access names of ghost songs added to the list by the ripper.
      $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);
         }
         if($book == 1) {
            # Search the index number of encoder faac.
            my $index = 0;
            for(my $c = 0; $c <= $#coder; $c++) {
               $index = $c if($coder[$c] == 3);
            }
            # Write the *.chapter.txt file.
            open(CHAP, ">>$sepdir[$index]/$chapterfile") or
               print "Can't open $sepdir[$index]/$chapterfile! $!\n";
            # Use timestamps, not the true track lengths. Where are the
            # specifications, please?
            my $points = chapter_length($framelist[$_ - 1] - $framelist[0]);
            my $chapname = $tracktags[$_ - 1];
            # Remember: merge writes all merged tracknames into the
            # first track of an interval.
            $chapname =~ s/\s\+\s.*$// if($_ == 1);
            print CHAP "$points $chapname\n";
            close CHAP;
         }
      }
      next if($skipflag == 1);
      $lastskip = $_;

      # 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($riptrackname,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) + length($wavdir) > 200) {
         $wavname = get_trackname($_, $_."short", "short");
      }

      # Check for tracks already done.
      my $checknextflag = 1;
      if($resumenc) {
         for(my $c=0; $c<=$#coder; $c++) {
            if(! -r "$sepdir[$c]/$riptrackname.$suffix[$c]") {
               $checknextflag = 0;
            }
            else{
               print "Found $riptrackname.$suffix[$c]:\n"
                  if($verbose >= 1);
               print "Will calculate and write md5sum for:\n"
                  if($verbose >= 4 && $md5sum == 1);
               print "$sepdir[$c], $riptrackname.$suffix[$c]\n"
                  if($verbose >= 4 && $md5sum == 1);
               md5_sum("$sepdir[$c]", "$riptrackname.$suffix[$c]", 1)
                  if($md5sum == 1);
            }
            last if($checknextflag == 0);
         }
         if($checknextflag == 1 && $playlist >= 1) {
            print PLST "#EXTINF:$secondlist[$_ - 1],$tracktag\n"
               if($hiddenflag == 0);
            print PLST "#EXTINF:$secondlist[$_],$tracktag\n"
               if($hiddenflag == 1);
            print PLST "Sepdir/$riptrackname.suffix\n"
               if($playlist == 1);
            print PLST "$riptrackname.suffix\n" if($playlist == 2);
            print PLST "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;
      my $ripsize = 0;
      while(! -r "$wavdir/$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 "$riptrackname.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("$riptrackname.wav to appear, now giving up!");
               log_info("with $artist - $album in device $cddev");
            }
            else {
               print "Encoder waited $mlength minutes for file\n";
               print "$riptrackname.wav to appear\n";
               print "with $artist - $album in device $cddev.\n";
               # If the rip file has been found, give a chance to
               # continue if the rip-file increases in size.
               if(-r "$wavdir/$wavname.rip") {
                  my $old_ripsize = $ripsize;
                  $ripsize = -s "$wavdir/$wavname.rip";
                  if($ripsize > $old_ripsize * 1.2) {
                     $tlength = $tlength * 1.5;
                  }
                  else {
                     $xtime = 0 unless($riptrackname =~ /00 Hidden Track/);
                     open(ERR, ">>$wavdir/error.log");
                     print ERR "Ripping ended: 00:00!\n";
                     close ERR;
                  }
               }
               else {
                  $xtime = 0 unless($riptrackname =~ /00 Hidden Track/);
                  open(ERR, ">>$wavdir/error.log");
                  print ERR "Ripping ended: 00:00!\n";
                  close ERR;
               }
            }
         }
         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 "$wavdir/error.log") {
             open(ERR, "$wavdir/error.log")
               or print "Encoder can't read $wavdir/error.log!\n";
            my @errlines = <ERR>;
            close ERR;
            chomp(my $errtrack = join(' ', grep(/^Track $riptrackno /, @errlines)));
            if($errtrack) {
               $xtime = $tlength + 1;
               $dataflag = 1;
               if($verbose >= 2) {
                  if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
                     open(ENCLOG, ">>$wavdir/enc.log");
                     print ENCLOG "\nDid not detect track $errtrack ",
                                  "($riptrackname.rip),\n assume ",
                                  "ripper failure!\n";
                     close ENCLOG;
                  }
                  else {
                     print "\nDid not detect track $errtrack ",
                           "($riptrackname.rip), assume ripper ",
                           "failure!\n";
                  }
               }
               if($verbose >= 2 && $sshflag == 0) {
                  if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
                     open(ENCLOG, ">>$wavdir/enc.log");
                     print ENCLOG "\nRipIT will finish the job! ",
                                  "Check the error.log!\n";
                     close ENCLOG;
                  }
                  else {
                     print "RipIT will finish the job! ",
                           "Check the error.log!\n";
                  }
               }
            }
            chomp(my $rip_ended = join(' ', grep(/^Ripping\sended:\s\d\d:\d\d/, @errlines)));
            if($rip_ended and $xtime == 0 and $multi == 1) {
               print "Ripper reported having ripped all wavs.\n";
               print "There is a problem with $riptrackname.wav.\n";
               print "with $artist - $album in device $cddev.\n";
               open(SRTF,">>$logfile.$riptrackno.txt")
                  or print "Can not append to file ",
                           "\"$logfile.$riptrackno.txt\"!\n";
               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,">>$outputdir/failed.log")
                  or print "Can not append to file ",
                           "\"$outputdir/failed.log\"!\n";
               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;
            }
         }
      }
      # This is an other hack to update the track-arrays modified 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 "$wavdir/ghost.log") {
         open(GHOST, "<$wavdir/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("$wavdir/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);

      # It seems that we need to rename long filenames in a subshell,
      # because the rename function does not work if the full path is
      # even longer. NOTE: There is a problem with UTF8, when special
      # characters are true wide characters... Too many of them, and
      # it will fail again. Maybe one should check the length with the
      # unpack function.
      if(length($riptrackname) + length($wavdir) > 200) {
         $riptrackname = substr($riptrackname, 0, 200);
         $riptrackname =~ s/\s*$//;
#         rename("\"$wavdir/$wavname.wav\"","\"$wavdir/$riptrackname.wav\"");
         log_system("cd \"$wavdir\" && mv \"$wavname.wav\" \"$riptrackname.wav\"");
      }

      my $delwav = 0;
      my $starts = sprintf("%3d", sub {$_[1]*60+$_[0]}->(localtime));

      if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
         open(ENCLOG, ">>$wavdir/enc.log");
         print ENCLOG "\nEncoding \"$riptrackname\"...\n"
            if($verbose >= 3);
         close ENCLOG;
      }
      else {
         print "\nEncoding \"$riptrackname\"...\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) {
            $encodername = "Lame";
            $lameopt = $globopt[$c];
            $enc = "lame $lameopt -S --tt \"$tracklametag\" \\
             --ta \"$artislametag\" --tl \"$albumlametag\" \\
             --ty \"$year\" --tg \"$genre\" --tn $riptrackno \\
             --tc \"$commentlametag\" --add-id3v2 \\
             \"$wavdir/$riptrackname.wav\" \\
             \"$sepdir[$c]/$riptrackname.$suffix[$c]_enc\"";
            if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$wavdir/enc.log");
               print ENCLOG "\nLame $lameopt encoding track $trackcn" .
                     " of " . ($#tracksel + 1) . "\n" if($verbose >= 3);
               close ENCLOG;
            }
            else {
               print "Lame $lameopt encoding track $trackcn" .
                     " of " . ($#tracksel + 1) . "\n" if($verbose >= 3);
            }
            log_info("new-mediafile: $sepdir[$c]/${riptrackname}.mp3");
         }
         elsif($coder[$c] == 1) {
            $encodername = "Oggenc";
            $oggencopt = $globopt[$c];
            $enc = "oggenc $oggencopt -Q -t \"$tracktag\" \\
               -a \"$artistag\" -l \"$albumtag\" \\
               -d \"$year\" -G \"$genre\" \\
               -N $riptrackno -c \"DESCRIPTION=$commentag\" \\
               -o \"$sepdir[$c]/$riptrackname.$suffix[$c]_enc\" \\
               \"$wavdir/$riptrackname.wav\"";
            if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$wavdir/enc.log");
               print ENCLOG "\nOggenc $oggencopt encoding track" .
                     " $trackcn of " . ($#tracksel + 1) . "\n"
                     if($verbose >= 3);
               close ENCLOG;
            }
            else {
               print "Oggenc $oggencopt encoding track $trackcn of " .
                  ($#tracksel + 1) . "\n" if($verbose >= 3);
            }
            log_info("new-mediafile: $sepdir[$c]/${riptrackname}.ogg");
         }
         elsif($coder[$c] == 2) {
            $encodername = "Flac";
            $flacopt = $globopt[$c];
            my $save_flacopt = $flacopt;
            $flacopt = $flacopt . " -f" if($resume);
            $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 \"$sepdir[$c]/$riptrackname.$suffix[$c]_enc\" \\
             \"$wavdir/$riptrackname.wav\"";
            if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$wavdir/enc.log");
               print ENCLOG "\nFlac $flacopt encoding track $trackcn" .
                     " of " . ($#tracksel + 1) . "\n" if($verbose >= 3);
               close ENCLOG;
            }
            else {
               print "Flac $flacopt encoding track $trackcn of " .
                     ($#tracksel + 1) . "\n" if($verbose >= 3);
            }
            log_info("new-mediafile: $sepdir[$c]/${riptrackname}.flac");
            my $flacopt = $save_flacopt if($resume);
         }
         elsif($coder[$c] == 3) {
            $encodername = "Faac";
            $faacopt = $globopt[$c];
            $enc = "faac $faacopt -w --title \"$tracktag\" \\
             --artist \"$artistag\" --album \"$albumtag\" \\
             --year \"$year\" --genre \"$genre\" --track $riptrackno \\
             --comment \"$commentag\" \\
             -o \"$sepdir[$c]/$riptrackname.$suffix[$c]_enc\" \\
             \"$wavdir/$riptrackname.wav\" > /dev/null 2>&1";
            if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$wavdir/enc.log");
               print ENCLOG "\nFaac $faacopt encoding track $trackcn" .
                     " of " . ($#tracksel + 1) . "\n" if($verbose >= 3);
               close ENCLOG;
            }
            else {
               print "Faac $faacopt encoding track $trackcn of " .
                     ($#tracksel + 1) . "\n" if($verbose >= 3);
            }
            log_info("new-mediafile: $sepdir[$c]/${riptrackname}.m4a");
         }
         elsif($coder[$c] == 4) {
            $encodername = "mp4als";
            $mp4alsopt = $globopt[$c];
            $enc = "mp4als $mp4alsopt -w --title \"$tracktag\" \\
             --artist \"$artistag\" --album \"$albumtag\" \\
             --year \"$year\" --genre \"$genre\" --track $riptrackno \\
             --comment \"$commentag\" \\
             \"$wavdir/$riptrackname.wav\" \\
             \"$sepdir[$c]/$riptrackname.$suffix[$c]_enc\" > /dev/null 2>&1";
            if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$wavdir/enc.log");
               print ENCLOG "\nMp4als $mp4alsopt encoding track $trackcn" .
                     " of " . ($#tracksel + 1) . "\n" if($verbose >= 3);
               close ENCLOG;
            }
            else {
               print "Mp4als $mp4alsopt encoding track $trackcn of " .
                     ($#tracksel + 1) . "\n" if($verbose >= 3);
            }
            log_info("new-mediafile: $sepdir[$c]/${riptrackname}.m4a");
         }
         elsif($coder[$c] == 5) {
            $encodername = "Musepack";
            $museopt = $globopt[$c];
            $enc = "$musenc --silent $museopt --title \"$tracktag\" \\
             --artist \"$artistag\" --album \"$albumtag\" \\
             --year \"$year\" --genre \"$genre\" --track $riptrackno \\
             --comment \"$commentag\" \\
             \"$wavdir/$riptrackname.wav\" \\
             \"$sepdir[$c]/$riptrackname\_enc.$suffix[$c]\"";
            if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$wavdir/enc.log");
               print ENCLOG "\nMppenc $museopt encoding track $trackcn" .
                     " of " . ($#tracksel + 1) . "\n" if($verbose >= 3);
               close ENCLOG;
            }
            else {
               print "Mppenc $museopt encoding track $trackcn of " .
                     ($#tracksel + 1) . "\n" if($verbose >= 3);
            }
            log_info("new-mediafile: $sepdir[$c]/${riptrackname}.mpc");
         }
         # 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 "$wavdir/error.log") {
               open(ERR, "$wavdir/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);
            }
         }

         $enc =~ s/\$/\\\$/g;
         # Finally, do the job of encoding.
         if($sshflag == 1) {
            enc_ssh($delwav,$enc,$riptrackname,$sepdir[$c],$suffix[$c]);
            if($md5sum == 1) {
               push(@md5tracks,
                    "$sepdir[$c];#;$riptrackname.$suffix[$c]");
               my @waitracks;
               foreach my $md5tr (@md5tracks) {
                  my ($sepdir, $donetrack) = split(/;#;/, $md5tr);
                  if(-r "$sepdir/$donetrack") {
                     md5_sum("$sepdir", "$donetrack", 0);
                  }
                  else {
                     push(@waitracks, "$sepdir;#;$donetrack");
                  }
               }
               @md5tracks = @waitracks;
            }
         }
         else {
            if(log_system("$enc")) {
               if($ripcomplete == 0) {
                  if(-r "$wavdir/error.log") {
                     open(ERR, "$wavdir/error.log")
                        or print "Can open file error.log!\n";
                     my @errlines = <ERR>;
                     close ERR;
                     my @ripcomplete = grep(/^$ripmsg/, @errlines);
                     $ripcomplete = 1 if(@ripcomplete);
                  }
               }
               if($coder[$c] == 5) {
                  rename("$sepdir[$c]/$riptrackname\_enc.$suffix[$c]",
                         "$sepdir[$c]/$riptrackname.$suffix[$c]");
               }
               else {
                  rename("$sepdir[$c]/$riptrackname.$suffix[$c]\_enc",
                         "$sepdir[$c]/$riptrackname.$suffix[$c]");
               }
               chmod oct($fpermission),
                  "$sepdir[$c]/$riptrackname.$suffix[$c]"
                  if($fpermission);
               md5_sum("$sepdir[$c]",
                       "$riptrackname.$suffix[$c]", $ripcomplete)
                  if($md5sum == 1);
               if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
                  open(ENCLOG, ">>$wavdir/enc.log");
                  print ENCLOG "Encoding of " .
                               "\"$riptrackname.$suffix[$c]\" " .
                               "complete.\n" if($verbose >= 1);
                  close ENCLOG;
               }
               else {
                  print "Encoding of \"$riptrackname.$suffix[$c]\" " .
                        "complete.\n" if($verbose >= 1);
               }
            }
            else {
               print "Encoder $encodername failed on $tracklist[$_ - 1]\n",
                     "of disc in device $cddev.\n",
                     "Error 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\"!\n";
                  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, ">>$outputdir/failed.log")
                     or print "Can not append to file ",
                              "\"$outputdir/failed.log\"!\n";
                  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("$wavdir/$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.
      # TODO:
      # An other buggy behaviour: if the last encoder of a list fails,
      # failflag will prevent writing playlist files, although encoding
      # was successful for all other encoders (but the last one).
      # Would it be better to write the playlist file in any case?
      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 split into chunks and
         # the counter increased, continue to insert as regular file.
         if($ghostflag == 2) {
            print PLST "GS$_:#EXTINF:$secondlist[$ghostcn - 1],",
                        "$tracktag\n"
               if($hiddenflag == 0);
            print PLST "GS$_:#EXTINF:$secondlist[$ghostcn],$tracktag\n"
               if($hiddenflag == 1);
            print PLST "GS$_:Sepdir/$riptrackname.suffix\n"
               if($playlist == 1);
            print PLST "GS$_:$riptrackname.suffix\n" if($playlist == 2);
         }
         else {
            if($ghost == 1 && -r "$wavdir/ghost.log") {
               open(GHOST, "<$wavdir/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 PLST "#EXTINF:$secondlist[$_ - 1],$tracktag\n"
               if($hiddenflag == 0);
            print PLST "#EXTINF:$secondlist[$_],$tracktag\n"
               if($hiddenflag == 1);
            print PLST "Sepdir/$riptrackname.suffix\n"
               if($playlist == 1);
            print PLST "$riptrackname.suffix\n" if($playlist == 2);
            print PLST "Add Ghost Song $_ Here.\n"
               if($ghost == 1 || $ghostflag == 1);
         }
      }
   }
   # Tell the mother process the encoding time.
   open(ERR, ">>$wavdir/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(PLST);
   exit unless($normalize == 1);
}
########################################################################
#
# Finish the M3U file used by players such as Amarok, Noatun, X11Amp...
#
sub create_m3u {
   my $playfile;
   my @mp3s = ();

   my $album = clean_all($album_utf8);
   my $artist = clean_all($artist_utf8);
   $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);

   # Prevent warnings in some rare cases if no tracks have been ripped.
   return unless(-r "$wavdir/$playfile");

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

   unlink("$wavdir/$playfile");

   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;
   for(my $c = 0; $c <= $#coder; $c++) {
      my @mp3s = @playlist;
      $_ =~ s/\.suffix$/.$suffix[$c]/i foreach (@mp3s);
      $_ =~ s/^Sepdir/$sepdir[$c]/ foreach (@mp3s);
      # Extension of playlist-file only needed when more than one
      # encoder selected. Using separate dirs, this would not be
      # necessary, but who says we use them? We keep the extension.
      if($#coder != 0) {
         $nplayfile = $playfile;
         $nplayfile = change_case($nplayfile);
         $nplayfile =~ s/\.m3u$/ - $suffix[$c].m3u/
            if($underscore == 0);
         $nplayfile =~ s/\.m3u$/_-_$suffix[$c].m3u/
            if($underscore == 1);
         open(PLST, ">$sepdir[$c]/$nplayfile") or
            print "Can't open $sepdir[$c]/$nplayfile! $!\n";
      }
      else {
         $nplayfile = $playfile;
         open(PLST, ">$sepdir[$c]/$nplayfile") or
            print "Can't open $sepdir[$c]/$nplayfile! $!\n";
      }
      print PLST "$_\n" foreach(@mp3s);
      close(PLST);
      chmod oct($fpermission), "$sepdir[$c]/$nplayfile"
         if($fpermission);
   }
   # Recreate the wav-playlist if wavs aren't deleted.
   if($wav == 1) {
      my @mp3s = @playlist;
      $_ =~ s/\.suffix$/\.wav/i foreach (@mp3s);
      $_ =~ s/^Sepdir/$wavdir/ foreach (@mp3s);
      $nplayfile = $playfile;
      $nplayfile = change_case($nplayfile);
      $nplayfile =~ s/\.m3u$/ - wav\.m3u/
         if($underscore == 0);
      $nplayfile =~ s/\.m3u$/_-_wav\.m3u/
         if($underscore == 1);
      open(PLST, ">$wavdir/$nplayfile") or
         print "Can't open $wavdir/$nplayfile! $!\n";
      print PLST "$_\n" foreach(@mp3s);
      close(PLST);
      chmod oct($fpermission), "$wavdir/$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($album_utf8) if(defined $cd{title});
   $artist = clean_all($artist_utf8) if(defined $cd{artist});

   # Preselect answer if no interaction requested.
   $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 = $artist_utf8;
      }
      # If CDDB entry CHANGED, submission OK.
      elsif(defined $cd{artist} && $artist ne "") {
         $cddbsubmission = 1;
         $cd{artist} = $artist;
         $artist_utf8 = $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 = $album_utf8;
      }
      # If CDDB entry CHANGED, submission OK.
      elsif(defined $cd{title} && $album ne "") {
         $cddbsubmission = 1;
         $cd{title} = $album;
         $album_utf8 = $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 = change_case($track);
         $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";
   }
   return;
}
########################################################################
#
# 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($album_utf8);
   my $artist = clean_all($artist_utf8);

   my $revision = get_rev() unless($cd{discid});
   if($revision) {
      # TODO: if submission fails, set revision back.
      $revision++;
      print "Revision is set to $revision.\n" if($verbose > 4);
   }
   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);
      $cddbsubmission = 1;
      }
   }
   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 && !$cd{discid} && $submission != 0) {
      print "Shall Ripit check for available categories?",
               " [y/n] (y) ";
      $ans = <STDIN>;
      chomp $ans;
      if($ans eq "") {
         $ans = "y";
      }
      if($ans =~ /^y/) {
         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 in 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 !~ m/^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});
      }
   }
   elsif($cd{discid}) {
      $categ = "musicbrainz";
   }
   # If one changes category 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 print "Can not write to cddb.toc $!\n";
   print TOC "# xmcd CD database generated by RipIT\n#\n",
             "# Track frame offsets:\n";
   $i = 0;
   foreach (@framelist) {
      print TOC "# $_\n" if($i < $#framelist);
      $i++;
   }
   print TOC "#\n# Disc length: $totals seconds\n#\n";
   if(!$cd{discid} && $archive == 1) {
      my $source = "http://www.freedb.org/freedb/" . $categ . "/" . $cddbid;
      print "Will try to get <$source>.\n";
      # Funny, thought this problem was solved some weeks ago, but no,
      # Perl is not funny, and grep is really ...!
      my $templines = LWP::Simple::get($source);
      my @templines = split(/\n/, $templines);
      chomp($revision = join('', grep(s/^\s*#\sRevision:\s(\d+)/$1/, @templines)));
      $revision++ if($revision =~ /^\d+/);
      $revision = 0 unless($revision =~ /^\d+/);
      print "\nRevision number set to $revision.\n" if($verbose >= 4);
   }
   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) {
         chomp($_);
         s/\n//g;
         next if($_ eq "");
         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/\n//g;
      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 0755 -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\": $!\n";
      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 we came from MB do not submit.
   elsif($cd{discid}) {
      $cddbsubmission = 0;
   }

   if($cddbsubmission == 1) {
      my $ans = "x";
      while($ans !~ /^y$|^n$/) {
         print "Do you really want to submit your data to freeDB.org?",
               " [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? $!\n";

      # 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 2>&1 | 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);
         chomp($genreno = "not ID3v2 compliant!\n");
      }
      return ($genre,$genreno);
   }

   # If Lame is not installed, don't loop for ever.
   if($lameflag == -1) {
      chomp($genreno = "Unknown.\n");
      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 2>&1 | 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+$/) {
      chomp($genre = `lame --genre-list 2>&1 | grep -i \' $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
      # possibilities, e.g. genre is Pop, then we get a bunch of
      # "pop-like" genres!
      # There will be a line break, if multiple possibilities found.
      my $ogenre = $genre;
      chomp($genre = `lame --genre-list 2>&1 | grep -i \'$genre\'`);
      # Second we want THE original genre, if it precisely exists.
      chomp(my $testgenre = `lame --genre-list 2>&1 | grep -i \'\^... $ogenre\$\'`);
      $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;
         }
         chomp($genre = $list[$i-1]);
      }
      # 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. Need to be tested from time to time, which ones are up.
#
#  http://at.freedb.org:80/~cddb/cddb.cgi working
#  http://au.freedb.org:80/~cddb/cddb.cgi not working
#  http://ca.freedb.org:80/~cddb/cddb.cgi working
#  http://ca2.freedb.org:80/~cddb/cddb.cgi working
#  http://de.freedb.org:80/~cddb/cddb.cgi working
#  http://es.freedb.org:80/~cddb/cddb.cgi working
#  http://fi.freedb.org:80/~cddb/cddb.cgi working
#  http://freedb.freedb.org:80/~cddb/cddb.cgi not working
#  http://ru.freedb.org:80/~cddb/cddb.cgi working
#  http://uk.freedb.org:80/~cddb/cddb.cgi working
#  http://us.freedb.org:80/~cddb/cddb.cgi not working
#
#
sub check_host {
#   while($mirror !~ m/^freedb$|^at$|^au$|^ca$|^es$|^fi$|
#                     |^fr$|^jp$|^jp2$|^ru$|^uk$|^uk2$|^us$/) {
   while($mirror !~ m/^freedb$|^at$|^au$|^bg$|^ca$|^es$|^fi$|
                     |^lu$|^no$|^uk$|^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;
   }
}
########################################################################
#
# Answer 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 lame, oggenc, flac, faac.
#
sub check_quality {
   @pquality = defined unless(@pquality); # prevent warnings.
   # Remember, if we come from reading the config file, the array
   # consists of a comma separated string in the first entry only!
   #
   # What should be used to prevent warnings? Do I need the defined
   # statement or not?
   if($pquality[0]) {
      @quality = split(/,/, join(',', @pquality));
   }
   elsif("@quality" eq "5 3 5 100 0 5") {
      return;
   }
   # If no coder-array has been passed, we do not know to which encoder
   # each quality-entry belongs. NOTE, we've not yet read the config.
   # So we need to read the config file to check if there is a unusual
   # order of encoders. In this way, this subroutine will ask the
   # correct questions and not mess up the encoders if qualities are
   # wrong, supposing the operator is aware about the unusual order!
   my $openflag = 0;
   if(!@pcoder && -r "$homedir/.ripit/config") {
      open(CONF, "$homedir/.ripit/config");
      $openflag = 1;
   }
   elsif(!@pcoder && -r "/etc/ripit/config") {
      open(CONF, "/etc/ripit/config");
      $openflag = 1;
   }
   if($openflag == 1) {
      my @conflines = <CONF>;
      close CONF;
      @pcoder = grep(s/^coder=//, @conflines) unless(@pcoder);
      chomp @pcoder;
      if($pcoder[0] =~ /^\d/) {
         @coder = split(/,/, join(',',@pcoder));
      }
   }

   # Actually check only those qualities needed, i.e. for chosen
   # encoders.
   # NOTE again: the $qualame etc. variables hold the string needed for
   # the config, it might be a comma separated string. When passing
   # commands, we should not use them, but things like "$quality[$c]"
   # instead!
   my $corrflag = 0;
   $qualame = "";
   $qualoggenc = "";
   $quaflac = "";
   $quafaac = "";
   $quamp4als = "";
   $quamuse = "";
   for(my $c=0; $c<=$#coder; $c++) {
      if($coder[$c] == 0 && !defined($quality[$c])) {
         $quality[$c] = 5; # prevent warnings.
      }
      elsif($coder[$c] == 0 && $quality[$c] ne "off") {
         $quality[$c] = 5 unless($quality[$c] =~ /\d/);
         while($quality[$c] > 9) {
            print "\nThe quality $quality[$c] is not valid for Lame!",
                  "\nPlease enter a different quality (0 = best),",
                  " [0-9]: ";
            $quality[$c] = <STDIN>;
            chomp $quality[$c];
         }
         $qualame .= ";" . $quality[$c];
      }
      elsif($coder[$c] == 0 && $quality[$c] eq "off") {
         $qualame .= ";" . $quality[$c];
      }
      # Done with lame, do the other encoders.
      if($coder[$c] == 1 && !defined($quality[$c])) {
         $quality[$c] = 3; # prevent warnings.
      }
      elsif($coder[$c] == 1 && $quality[$c] ne "off") {
         $quality[$c] = 3 unless($quality[$c] =~ /\d/);
         while($quality[$c] > 10 || $quality[$c] == 0) {
            print "\nThe quality $quality[$c] is not valid for Oggenc!",
                  "\nPlease enter a different quality (10 = best),",
                  " [1-10]: ";
            $quality[$c] = <STDIN>;
            chomp $quality[$c];
         }
         $qualoggenc .= "," . $quality[$c];
      }
      elsif($coder[$c] == 1 && $quality[$c] eq "off") {
         $qualoggenc .= "," . $quality[$c];
      }
      if($coder[$c] == 2 && !defined($quality[$c])) {
         $quality[$c] = 5; # prevent warnings.
      }
      elsif($coder[$c] == 2 && $quality[$c] ne "off") {
         $quality[$c] = 5 unless($quality[$c] =~ /\d/);
         while($quality[$c] > 8) {
            print "\nThe compression level $quality[$c] is not valid ",
                  "for Flac!",
                  "\nPlease enter a different compression level ",
                  "(0 = lowest), [0-8]: ";
            $quality[$c] = <STDIN>;
            chomp $quality[$c];
         }
         $quaflac = $quaflac . "," . $quality[$c];
      }
      elsif($coder[$c] == 2 && $quality[$c] eq "off") {
         $quaflac .= "," . $quality[$c];
      }
      if($coder[$c] == 3 && !defined($quality[$c])) {
         $quality[$c] = 100; # prevent warnings.
      }
      elsif($coder[$c] == 3 && $quality[$c] ne "off") {
         $quality[$c] = 100 unless($quality[$c] =~ /\d/);
         while($quality[$c] > 500 || $quality[$c] < 10) {
            print "\nThe quality $quality[$c] is not valid for Faac!",
                  "\nPlease enter a different quality (500 = max), ",
                  "[10-500]: ";
            $quality[$c] = <STDIN>;
            chomp $quality[$c];
         }
         $quafaac .= "," . $quality[$c];
      }
      elsif($coder[$c] == 3 && $quality[$c] eq "off") {
         $quafaac .= "," . $quality[$c];
      }
      if($coder[$c] == 4 && !defined($quality[$c])) {
         $quality[$c] = 0; # prevent warnings.
      }
      elsif($coder[$c] == 4 && $quality[$c] ne "off") {
         $quality[$c] = 0 unless($quality[$c] =~ /\d/);
         # Any info about mp4als "qualities", i. e. compression levels?
#         while($quality[$c] > 500 || $quality[$c] < 10) {
#            print "\nThe quality $quality[$c] is not valid for mp4als!",
#                  "\nPlease enter a different quality (500 = max), ",
#                  "[10-500]: ";
#            $quality[$c] = <STDIN>;
#            chomp $quality[$c];
#         }
         $quamp4als .= "," . $quality[$c];
      }
      elsif($coder[$c] == 4 && $quality[$c] eq "off") {
         $quamp4als .= "," . $quality[$c];
      }
      if($coder[$c] == 5 && !defined($quality[$c])) {
         $quality[$c] = 5; # prevent warnings.
      }
      elsif($coder[$c] == 5 && $quality[$c] ne "off") {
         $quality[$c] = 5 unless($quality[$c] =~ /\d/);
         while($quality[$c] > 10 || $quality[$c] < 0) {
            print "\nThe quality $quality[$c] is not valid for $musenc!",
                  "\nPlease enter a different quality (10 = max), ",
                  "[0-10]: ";
            $quality[$c] = <STDIN>;
            chomp $quality[$c];
         }
         $quamuse .= "," . $quality[$c];
      }
      elsif($coder[$c] == 5 && $quality[$c] eq "off") {
         $quamuse .= "," . $quality[$c];
      }
   }
   $qualame =~ s/^,//;
   $qualoggenc =~ s/^,//;
   $quaflac =~ s/^,//;
   $quafaac =~ s/^,//;
   $quamuse =~ s/^,//;
   # Small problem if only option --savenew is used, with no other
   # option at all. Then, qualame has default value (because Lame is
   # default encoder), but all other qualities are empty!
   $qualoggenc = 3 unless($qualoggenc);
   $quaflac = 5 unless($quaflac);
   $quafaac = 100 unless($quafaac);
   $quamp4als = 0 unless($quamp4als);
   $quamuse = 5 unless($quamuse);
   # NOTE: corrections have been done on quality array, not pquality.
   # If pquality was passed, we need to apply corrections and save it
   # the same way as if it had been passed on command line.
   if($pquality[0]) {
      my $pquality = join(',', @quality);
      @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]) unless($line[0] =~ /by-id/);
   }
   # 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;
   }
   elsif(open(CD, "/dev/sr0")) {
      $cddev = "/dev/sr0";
      close CD;
   }
   elsif(open(CD, "/dev/sr1")) {
      $cddev = "/dev/sr1";
      close CD;
   }
   else {
      foreach (@devlist) {
         if(open(CD, "$_")) {
            $cddev = $_;
            chomp $cddev;
            close CD;
         }
         else {
            $cddev = "";
         }
      }
   }
   # On a notebook, the tray can't 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";
   }
   return;
}
########################################################################
#
# Check bitrate if bitrate is not zero.
#
sub check_bitrate {
   while($bitrate !~ m/^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 and clean the coder array.
#
sub check_coder {

   # Reset $lameflag set by past invocation of check_coder() except if
   # lame is not installed ($lameflag == -1).
   $lameflag = 0 if($lameflag > 0);

   # Create encoder array if passed or read from config file.
   # Remember, if we come from reading the config file, the array
   # consists of a comma separated string in the first entry only!
   if(@pcoder) {
      @coder = split(/,/, join(',', @pcoder));
   }
   else {
      # This can happen because this subroutine is called before config
      # file is read! So @pcoder can be empty and @coder will be filled
      # with default value for Lame. Do we need this?
      @coder = split /,/, join(',', @coder);
   }

   # Check if there is an entry >= 4.
   for(my $c = 0; $c <= $#coder; $c++) {
      if($coder[$c] >= 6) {
         print "Encoder number $coder[$c] does not yet exist, ",
               "please enter\n0 for Lame, 1 for Oggenc, 2 for Flac";
         die "3 for Faac, 4 for mp4als, 5 for Musepack!\n\n";
         # TODO: splice that entry out, don't die!
      }
      $lameflag = 1 if($coder[$c] == 0);
      $suffix[$c] = "mp3" if($coder[$c] == 0);
      $suffix[$c] = "ogg" if($coder[$c] == 1);
      $suffix[$c] = "flac" if($coder[$c] == 2);
      $suffix[$c] = "m4a" if($coder[$c] == 3);
      $suffix[$c] = "m4b" if($coder[$c] == 3 && $book == 1);
      $suffix[$c] = "als" if($coder[$c] == 4);
      $suffix[$c] = "mpc" if($coder[$c] == 5);
   }
   # Use comma separated string to write the encoder array to the
   # config file!
   $wcoder = join(',', @coder);
}
########################################################################
#
# Over or re-write the config file (depends on option savenew or save).
#
sub save_config {
   log_system("mkdir -m 0755 -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 the README provided with ripit
# or type ripit --help .


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

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

cddevice=$cddev

# scsidevice: Device name for special devices if the non ripping
# commands should be executed on a different device node. This might
# be useful for some old SCSI devices. If not set the cddevice will
# be used.
# Example: /dev/sr18
# Default: not set

scsidevice=$scsi_cddev

# output: Path for audio files. If not set, \$HOME will be used.
# Default: not set

output=$outputdir

# directory permissions: Permissions for directories.
# Default: 0755

dpermission=$dpermission

# file permissions: Permissions for sound and log files.
# If not set, uses the default system settings.
# Default: not set

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

# span: Rip only part of a single track or the merged track-interval.
# Possible values: any in the format hh:mm:ss.ff-hh:mm:ss.ff
# Example: rip first 30s of each track: 0-30
# Default: not set

span=$span

# paranoia: Turn \"paranoia\" on or off for dagrab and cdparanoia.
# Possible values: 0 - no paranoia, 1 - use paranoia
#                  2 - switch paranoia off if ripping fails on one
#                      track and retry this track without paranoia
# Default: 1 - use paranoia

paranoia=$parano

# ghost: Analyze the wavs for possible gaps, split the wav into
# chunks of sound and delete blank tracks.
# 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 and zero; precision in
# tenths of seconds. Be aware of low numbers.
# Default: 2.0

prepend=$prepend

# extend: Enlarge the the chunk of sound by a number of
# seconds at the end (if possible).
# Possible values: any positive number and zero; precision in
# tenths of seconds. Be aware of low numbers.
# Default: 2.0

extend=$extend

# resume: Resume a previously started session.
# Possible values: 0 - off, 1 - on
# Default: off

resume=$resume

# overwrite: Default behaviour of Ripit is not to overwrite existing
# directories, a suffix will be added if directory name exists.
# Use option overwrite to prevent this and either overwrite a previous
# rip or force Ripit to quit or even eject the disc. If ejection is
# chosen, the disc will be ejected even if option eject has not been
# switched on.
# Possible values: n - off, y - on,
#                  q - quit, e - quit and force ejection
# Default: off

overwrite=$overwrite


#####
#
# 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)
# 4 - mp4als (als)
# 5 - Musepack (mpc)
# Multiple encoders can be selected by giving a comma-separated list
# Example: coder=0,0,1,2 encodes CD twice to mp3, ogg and flac files
# Default: Lame

coder=$wcoder

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

# qualame: Sets audio quality for lame encoder in cbr (lame-option -q)
# and vbr (lame-option -V) mode, comma separated list.
# Possible values: 0...9, off
# 0: higest quality
# 9: lowest quality
# Can be set to \"off\" if all options are passed to --lameopt.
# Example: qualame=off,off
# Note: default value is the same for cbr and vbr,
# although vbr-default should be 4.
# Default: 5

qualame=$qualame

# lameopt: Additional options for lame encoder, comma separated list.
# Example: lameopt=-b 128,--preset extreme
# Default: not set

lameopt=$lameopt

# vbrmode: Enable variable bitrate for lame encoder.
# Possible 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, comma separated list.
# Default: not set

oggencopt=$oggencopt

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

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

quaflac=$quaflac

# flacopt: Additional options for flac encoder, comma separated list.
# Default: not set

flacopt=$flacopt

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

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

quafaac=$quafaac

# faacopt: Additional options for faac encoder, comma separated list.
# Default: not set

faacopt=$faacopt

###
#
# mp4als (als) encoder options
#

# quamp4als: Would set compression level for mp4als, but mp4als does not
# accept any options (?).
# Default: 0

quamp4als=$quamp4als

# mp4alsopt: Additional options for mp4als encoder, none are accepted,
# comma separated list.
# Default: not set

mp4alsopt=$mp4alsopt

###
#
# Musepack (mpc) encoder options
#

# musenc: The encoder name on the command line
# Possible values: any
# Example: musenc=mppenc for older versions
# Default: mpcenc

musenc=$musenc

# quamuse: Sets audio quality for Musepack encoder
# Possible values: 0...10, off
# 0: lowest quality
# 10: highest quality
# Can be set to \"off\"
# Default: 5

quamuse=$quamuse

# museopt: Additional options for Musepack encoder,
# comma separated list.
# Default: not set

museopt=$museopt


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

# dirtemplate: Template for directory structure
# The template can be created using any legal
# character, including slashes (/) for multi-level
# directory-trees, and the following variables:
# \$album
# \$artist
# \$iletter
# \$genre
# \$quality
# \$suffix
# \$trackname
# \$tracknum
# \$year
# \$trackno
#
# The variable \$iletter is the initial letter of
# the artist variable, the \$quality is the quality
# according to the encoding format defined by \$suffix.
# The variable \$quality reflects the encoder options,
# not the arguments of option --quality which may be set
# to off. The variable \$trackno is the total number of tracks
# of the release.
#
# dirtemplate is an array, for each encoder a different
# dirtemplate may be defined (i. e. for each encoder state
# a line starting with dirtemplate=...).
#
# Example:
# dirtemplate=\"\$suffix/hard_path/\$iletter/\$artist/\$year - \$album\"
#
# The double quotes (\") are mandatory!
# Default: \"\$artist - \$album\"
\n";
   print CONF "dirtemplate=$_\n" foreach(@dirtemplate);
   print CONF "
# 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

# uppercasefirst: Convert filenames and tags to uppercase first,
# not recommended. To be used on the command line only if CDDB entry
# is in uppercase.
# Possible values: 0 - off, 1 - on
# Default: off

uppercasefirst=$uppercasefirst

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

underscore=$underscore

# chars: Exclude special characters in file names and path.
# Note: following characters will always be purged:
#  ; > < \" and \\015 .
# Side note: if calling this option on the command line without
# argument, following characters will be purged:  |\\:*?\$  plus
# blanks and periods at beginning and end of file names and directories.
# This is identical to the word NTFS passed as argument to the command
# line or stated here in the config file. The word HFS will purge colons
# only plus blanks and periods at beginning of file names and
# directories.
#
# No need to escape the special characters here in the config file.
# Possible values: HFS, NTFS, 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 (with full path)

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, mpc) or a
# description (ogg, flac) tag. To write the cddbid used for freedb
# or the MusicBrainz discid into the comment, use the word \"cddbid\"
# or \"discid\".
# Possible values: discid, cddbid or 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
#

# mb: Access MusicBrainz DB via WebService::MusicBrainz module instead
# of the http protocol (see below).
# Possible values: 0 - off, 1 - on
# Default: off

mb=$mb

# CDDBHOST: Specifies the CDDB server
# Possible values: freedb.org, freedb2.org or musicbrainz.org
# Note: Full name of the server used is \$mirror.\$CDDBHOST, except for
# freedb2.org (no mirror) and musicbrainz.org has freedb as default
# mirror.
# 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

# transfer: Set transfer mode for cddb queries
# Possible values: cddb, http
# Note: CDDB servers freedb2.org and musicbrainz.org may need transfer
# mode 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 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 extremely 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 or \"{cddev}\" to design the CD
# device.
# Note: Don't use options -t / close or eject,
#       RipIT knows when to eject or load the tray
# Default: {cddev}

ejectopt=$ejectopt

# quitnodb: Give up CD if no CDDB entry found.
# Useful if option --loop or --nointeraction are on.
# Default behaviour is to let operator enter data or to use default
# artist, album and track names.
# Possible values: 0 - off, 1 - on
# Default: off

quitnodb=$quitnodb

# execmd: Execute a command when done with ripping. Quote the command
# if needed.
# Possible values: none - off, string - on
# Example: execmd=\"get_cover -a \\\"\$artist\\\" -r \\\"\$album\\\"\"
# Default: off

execmd=$execmd

# book: Create an audiobook, i. e. merge all tracks into one single
# file, option --ghost will be switched off and file suffix will be
# m4b. Make sure to use encoder faac, ripit will not check that.
# A chapter file will be written for chapter marks.
# Possible values: 0 - off, 1 - on
# Default: off

book=$book

# loop: Continue with a new CD when the previous one is done.
# Option --eject will be forced. To start ripping process immediately
# after ejection of previous disc, use experimental argument 2. Ripit
# will restart as child process, one might see the prompt and it will
# be necessary to manually terminate the process! Do not use!
# Possible values: 0 - off, 1 - on, 2 - immediate restart, experimental
# 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

# threads: 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

threads=$wthreads

# 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

# normcmd: Command to be used to normalize.
# Possible values: string
# Example: normalize-audio (when using Debian)
# Default: normalize

normcmd=$normcmd

# 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: -b
# The -v option will be added by default according to RipITs verbosity

normopt=$normopt

# cdtoc: Create a toc 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

# inf: Create inf files to burn the wavs with
# cd-text using wodim or cdrecord (in dao mode).
# Possible values: 0 - off, 1 - on
# Default: off

inf=$inf
\n";
   close CONF;
}
########################################################################
#
# Read the config file, take the parameters only if NOT yet defined!
#
sub read_config {
   my $ripdir = $homedir."/.ripit/config";
   $ripdir = "/etc/ripit/config" unless(-r "$ripdir");
   if(-r "$ripdir") {
      open(CONF, "$ripdir") or
      print "Can not read config file in $ripdir: $!\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";
         # TODO:
         # In older configs the option var was either set to zero or
         # one. In the case one wanted to replace characters, ignore
         # for simplicity! User should update! This line and the elsif
         # part below will go away with version 3.8.0.
         grep(s/^chars=[01]\s*$/chars=/, @conflines);
         sleep(3);
      }
      elsif($version ne $confver) {
         grep(s/^chars=[01]\s*$/chars=/, @conflines);
      }
      chomp($archive = join(' ', grep(s/^archive=//, @conflines)))
         unless defined $parchive;
      chomp($bitrate = join(' ', grep(s/^bitrate=//, @conflines)))
         unless($pbitrate);
      chomp($book = join(' ', grep(s/^book=//, @conflines)))
         unless($pbook);
      chomp($maxrate = join(' ', grep(s/^maxrate=//, @conflines)))
         unless($pmaxrate);
      chomp($cddev = join(' ', grep(s/^cddevice=//, @conflines)))
         unless($pcddev);
      chomp($scsi_cddev = join(' ', grep(s/^scsidevice=//, @conflines)))
         unless($pscsi_cddev);
      chomp($cdtoc = join('', grep(s/^cdtoc=//, @conflines)))
         unless($pcdtoc);
      chomp($chars = join('', grep(s/^chars=//, @conflines)))
         if($chars eq "XX");
      chomp($commentag = join('', grep(s/^comment=//, @conflines)))
         unless($pcommentag);
      chomp($CDDB_HOST = join('', grep(s/^CDDBHOST=//, @conflines)))
         unless($PCDDB_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;
      @dirtemplate = grep(s/^dirtemplate=//, @conflines)
         unless($pdirtemplate[0]);
      chomp $_ foreach(@dirtemplate);
      chomp($dpermission = join('', grep(s/^dpermission=//, @conflines)))
         unless($pdpermission);
      chomp($eject = join('', grep(s/^eject=//, @conflines)))
         unless defined $peject;
      chomp($ejectcmd = join('', grep(s/^ejectcmd=//, @conflines)))
         unless defined $pejectcmd;
      chomp($ejectopt = join('', grep(s/^ejectopt=//, @conflines)))
         unless defined $pejectopt;
      chomp($encode = join('', grep(s/^encode=//, @conflines)))
         unless defined $pencode;
      chomp($extend = join('', grep(s/^extend=//, @conflines)))
         unless defined $pextend;
      chomp($execmd = join('', grep(s/^execmd=//, @conflines)))
         unless defined $pexecmd;
      chomp($fpermission = join('', grep(s/^fpermission=//, @conflines)))
         unless($pfpermission);
      chomp($ghost = join('', grep(s/^ghost=//, @conflines)))
         unless defined $pghost;
      chomp($halt = join('', grep(s/^halt=//, @conflines)))
         unless($phalt);
      chomp($inf = join('', grep(s/^inf=//, @conflines)))
         unless($pinf);
      chomp($infolog = join('', grep(s/^infolog=//, @conflines)))
         unless($pinfolog);
      chomp($interaction = join('', grep(s/^interaction=//, @conflines)))
         unless defined $pinteraction;
      chomp($lcd = join('', grep(s/^lcd=//, @conflines)))
         unless defined $plcd;
      chomp($lcdhost = join('', grep(s/^lcdhost=//, @conflines)))
         unless($plcdhost);
      chomp($lcdport = join('', grep(s/^lcdport=//, @conflines)))
         unless($plcdport);
      chomp($local = join('', grep(s/^local=//, @conflines)))
         unless defined $plocal;
      chomp($loop = join('', grep(s/^loop=//, @conflines)))
         unless defined $ploop;
      chomp($lowercase = join('', grep(s/^lowercase=//, @conflines)))
         unless defined $plowercase;
      chomp($uppercasefirst = join('', grep(s/^uppercasefirst=//, @conflines)))
         unless defined $puppercasefirst;
      chomp($mailad = join('', grep(s/^mailad=//, @conflines)))
         unless($pmailad);
      chomp($mb = join('', grep(s/^mb=//, @conflines)))
         unless defined $pmb;
      chomp($md5sum = join('', grep(s/^md5sum=//, @conflines)))
         unless($pmd5sum);
      chomp($mirror = join('', grep(s/^mirror=//, @conflines)))
         unless($pmirror);
      chomp($musenc = join('', grep(s/^musenc=//, @conflines)))
         unless($pmusenc);
      chomp($normalize = join('', grep(s/^normalize=//, @conflines)))
         unless defined $pnormalize;
      chomp($normcmd = join('', grep(s/^normcmd=//, @conflines)))
         unless($pnormcmd);
      chomp($normopt = join('', grep(s/^normopt=//, @conflines)))
         unless($pnormopt);
      chomp($nice = join('', grep(s/^nice=//, @conflines)))
         unless defined $pnice;
      chomp($nicerip = join('', grep(s/^nicerip=//, @conflines)))
         unless defined $pnicerip;
      chomp($outputdir = join('', grep(s/^output=//, @conflines)))
         unless($poutputdir);
      chomp($overwrite = join('', grep(s/^overwrite=//, @conflines)))
         unless($poverwrite);
      chomp($parano = join('', grep(s/^paranoia=//, @conflines)))
         unless defined $pparano;
      chomp($playlist = join('', grep(s/^playlist=//, @conflines)))
         unless defined $pplaylist;
      chomp($prepend = join('', grep(s/^prepend=//, @conflines)))
         unless defined $pprepend;
      chomp($preset = join('', grep(s/^preset=//, @conflines)))
         unless($ppreset);
      # NOTE: we have to fill the w_RITE_preset variable!
      $wpreset = $preset unless($ppreset);
      chomp $preset;
      chomp $wpreset;
      chomp($proto = join('', grep(s/^proto=//, @conflines)))
         unless($pproto);
      chomp($proxy = join('', grep(s/^proxy=//, @conflines)))
         unless($pproxy);
      my @quafaac = grep(s/^quafaac=//, @conflines) unless($pquality[0]);
      chomp($quafaac = $quafaac[0]) unless($pquality[0]);
      my @quaflac = grep(s/^quaflac=//, @conflines) unless($pquality[0]);
      chomp($quaflac = $quaflac[0]) unless($pquality[0]);
      my @qualame = grep(s/^qualame=//, @conflines) unless($pquality[0]);
      chomp($qualame = $qualame[0]) unless($pquality[0]);
      my @qualoggenc = grep(s/^qualoggenc=//, @conflines)
         unless($pquality[0]);
      chomp($qualoggenc = $qualoggenc[0]) unless($pquality[0]);
      my @quamp4als = grep(s/^quamp4als=//, @conflines)
         unless($pquality[0]);
      chomp($quamp4als = $quamp4als[0]) unless($pquality[0]);
      my @quamuse = grep(s/^quamuse=//, @conflines)
         unless($pquality[0]);
      chomp($quamuse = $quamuse[0]) unless($pquality[0]);
      # I don't really like this. I don't like the variables qualame etc
      # too and wanted to get rid of them. Not possible anymore. We need
      # them because they hold a comma separated string necessary to
      # write to the config file...
      unless($pquality[0]) {
         @qualame = split(/,/, $qualame);
         @qualoggenc = split(/,/, $qualoggenc);
         @quaflac = split(/,/, $quaflac);
         @quafaac = split(/,/, $quafaac);
         @quamp4als = split(/,/, $quamp4als);
         @quamuse = split(/,/, $quamuse);
         @coder = split(/,/, join(',',@pcoder));
         for(my $c=0; $c<=$#coder; $c++) {
            if($coder[$c] == 0) {
               $quality[$c] = $qualame[0];
               shift(@qualame);
            }
            if($coder[$c] == 1) {
               $quality[$c] = $qualoggenc[0];
               shift(@qualoggenc);
            }
            if($coder[$c] == 2) {
               $quality[$c] = $quaflac[0];
               shift(@quaflac);
            }
            if($coder[$c] == 3) {
               $quality[$c] = $quafaac[0];
               shift(@quafaac);
            }
            if($coder[$c] == 4) {
               $quality[$c] = $quamp4als[0];
               shift(@quamp4als);
            }
            if($coder[$c] == 5) {
               $quality[$c] = $quamuse[0];
               shift(@quamuse);
            }
         }
      }
      chomp($faacopt = join('', grep(s/^faacopt=//, @conflines)))
         unless($pfaacopt);
      chomp($flacopt = join('', grep(s/^flacopt=//, @conflines)))
         unless($pflacopt);
      chomp($lameopt = join('', grep(s/^lameopt=//, @conflines)))
         unless($plameopt);
      chomp($mp4alsopt = join('', grep(s/^mp4alsopt=//, @conflines)))
         unless($pmp4alsopt);
      chomp($museopt = join('', grep(s/^museopt=//, @conflines)))
         unless($pmuseopt);
      chomp($oggencopt = join('', grep(s/^oggencopt=//, @conflines)))
         unless($poggencopt);
      chomp($quitnodb = join('', grep(s/^quitnodb=//, @conflines)))
         unless defined $pquitnodb;
      chomp($ripper = join('', grep(s/^ripper=//, @conflines)))
         unless defined $pripper;
      chomp($resume = join('', grep(s/^resume=//, @conflines)))
         unless defined $presume;
      chomp($ripopt = join('', grep(s/^ripopt=//, @conflines)))
         unless defined $pripopt;
      my @clist = grep(s/^threads=//, @conflines) unless($pthreads[0]);
      chomp @clist;
      # NOTE: all threads numbers are in array entry $clist[0]!
      @threads = 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));
      chomp($scp = join('', grep(s/^scp=//, @conflines)))
         unless defined $pscp;
      chomp($span = join('', grep(s/^span=//, @conflines)))
         unless defined $pspan;
      chomp($submission = join('', grep(s/^submission=//, @conflines)))
         unless defined $psubmission;
      chomp($transfer = join('', grep(s/^transfer=//, @conflines)))
         unless($ptransfer);
      chomp($tracktemplate = join('', grep(s/^tracktemplate=//, @conflines)))
         unless($ptracktemplate);
      chomp($underscore = join('', grep(s/^underscore=//, @conflines)))
         unless defined $punderscore;
      chomp($utftag = join('', grep(s/^utftag=//, @conflines)))
         unless defined $putftag;
      chomp($vbrmode = join('', grep(s/^vbrmode=//, @conflines)))
         unless($pvbrmode);
      chomp($year = join('', grep(s/^year=//, @conflines)))
         unless($pyear);
      chomp($wav = join('', grep(s/^wav=//, @conflines)))
         unless defined $pwav;
   }
   else {
      print "\nNo config file found! Use option --save to create one.\n"
         if($verbose >= 2);
   }
}
########################################################################
#
# Encode to utf-8 with UTF8 flag.
#
sub UTF8_encoding {
   my $string = shift;
   # We are still at point zero: is it Latin-1 or UTF-8? Let's decode
   # without fear, it will work because no wide chars are in!
   my @c_points = unpack("C0U*", "$string");
   my $d_string = decode_utf8($string, Encode::FB_QUIET);
   Encode::from_to($d_string, 'utf8', 'UTF-8');
   my @d_points = unpack("C0U*", "$d_string");
   # This is one possible test, compare the number of bytes. If
   # a wide character is in, then there are less bytes after
   # Encode::from_to. E.g. the string DÅµ will become Dŵr, but
   # Ärger will be �ger and by chance has the same number of
   # bytes -- and unicode points look like: 65533 114 103 101 114.
   print "@c_points\n==\n@d_points.\n" if($verbose >= 5);
   # In album - artist part we have == !
#   $string = $d_string unless(@c_points == @d_points);
   # In track part we have > ! Why?
   $string = $d_string unless(@c_points > @d_points);
   return($string);
}
########################################################################
#
# Change encoding of tags back to iso-8859-1. Again: this is only needed
# when using lame to create mp3s. Tagging works for all other
# encoders and encodings.
#
# Test CDs where option --noutf should work:
# Bang Bang:  Je t'aime...     10: Sacré cœur
# Distain!:   [Li:quíd]:        3: Summer 84
# Enya:       The Celts:       10: Triad: St. Patrick Cú Chulainn Oisin
# Enya:       The Celts:       14: Dan y Dŵr
# Röyksopp:   Junior:           5: Röyksopp Forever
# Žofka:      Bad Girls:        1: Woho
#
sub back_encoding {
   my $string = shift;
   my $utf_string = $string;
   if(utf8::is_utf8($string)) {
      print "The \$string is already in utf8, do nothing!\n"
      if($verbose >= 5);
   }
   else {
      $utf_string = Encode::decode('UTF-8', $utf_string, Encode::FB_QUIET);
   }
   my @utf_points = unpack("U0U*", "$utf_string"); # Perl 5.10
   print "\nutf_points:\n@utf_points\n" if($verbose >= 5);
   my $latinflag = 0;
   my $wideflag = 0;
   foreach (@utf_points) {
      $wideflag = 1 if($_ > 255);
      $latinflag++ if($_ > 128 && $_ < 256);
   }

   # It works with Röyksopp archive and freeCDDB entry.
   my @char_points = unpack("U0U*", "$string");


   @char_points = @utf_points if($wideflag == 1);

   return $string if($string eq "");
   my $decoded = "";
   foreach (@char_points) {
      if($_ > 255) {
         print "\"Wide\" char detected: $_.\n" if($verbose >= 5);
         use Unicode::UCD 'charinfo';
         my $charinfo = charinfo(sprintf("0x%X", $_));
         my $letter = $charinfo->{name};
         print "The charinfo is <$letter>.\n" if($verbose >= 5);
         my $smallflag = 0;
         $smallflag = 1 if($letter =~ /SMALL\sLETTER/);
         $smallflag = 1 if($letter =~ /SMALL\sLIGATURE/);
         $letter =~ s/^.*LETTER\s(\w+)\s.*/$1/;
         $letter =~ s/^.*LIGATURE\s(\w+)(\.|\s)*.*/$1/;
         $letter = "\L$letter" if($smallflag == 1);
         # Rather do nothing than print rubbish (string with words):
         $letter = $_ if($letter =~ /\s/);
         print "New letter will be: $letter.\n" if($verbose >= 5);
         $decoded .= $letter;
      }
      else {
         $decoded .= chr($_);
      }
   }

   if($cd{discid}) {
      # Special condition for MB data. Please do not ask why.
      if($wideflag == 0) {
         Encode::from_to($decoded, 'utf-8', 'iso-8859-15');
      }
   }
   elsif($wideflag == 0) {
      Encode::from_to($decoded, 'UTF8', 'ISO-8859-15');
   }
   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 !~ m/^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(@pthreads) {
      @threads = split(/,/, join(',', @pthreads));
   }
   $wthreads = join(',', @threads);
   if(@sshlist || $threads[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'} = $threads[0] if($local == 1);
      my $threadscn = 1;
      foreach (@sshlist) {
         $threads[$threadscn] = 1 unless($threads[$threadscn]);
         $sshlist{$_} = $threads[$threadscn];
         $threadscn++;
      }
   }
   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 $sepdir = $_[3];
   my $suffix = $_[4];
   my $old_wavdir = $wavdir;
   my $old_sepdir = $sepdir;
   my $old_ripnam = $ripnam;
   my $esc_name;
   my $esc_dir;
   my $threadscn;

   $sshflag = 2;
   while ($sshflag == 2) {
      # Start on the local machine first.
      $threadscn = 1;
      for($threadscn = 1; $threadscn <= $threads[0]; $threadscn++) {
         if(! -r "$wavdir/local.lock_$threadscn") {
            if($local == 1) {
               $sshflag = 1;
               $machine = "local";
               push @codwav, "$ripnam";
            }
         }
         last if($sshflag == 1);
      }
      last if($sshflag == 1);
      $threadscn = 1;
      foreach $_ (keys %sshlist) {
         $machine = $_; # Why this?
         for($threadscn = 1; $threadscn <= $sshlist{$_}; $threadscn++) {
            if(! -r "$wavdir/$machine.lock_$threadscn") {
               $sshflag = 1;
            }
            # Prepare array @codwav with all tracknames in, which are
            # still in progress, i. e. either being ripped or encoded.
            else {
               open(LOCK, "$wavdir/$machine.lock_$threadscn");
               my @locklines = <LOCK>;
               close LOCK;
               if($locklines[0]) {
                  chomp(my $locklines = $locklines[0]);
                  # 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;
   }

   if(-r "$wavdir/enc.log" && $verbose >= 3) {
      open(ENCLOG, ">>$wavdir/enc.log");
      print ENCLOG "...on machine $machine.\n"
         if($#threads > 1 || $machine !~ /^local$/);
      print ENCLOG "Executing scp command to $machine.\n"
         if($scp && $machine !~ /^local$/);
      close ENCLOG;
   }
   elsif($verbose >= 3) {
      print "...on machine $machine.\n"
         if($#threads > 1 || $machine !~ /^local$/);
      print ENCLOG "Executing scp command to $machine.\n"
         if($scp && $machine !~ /^local$/);
   }
   open(LOCKF, ">$wavdir/$machine.lock_$threadscn");
   print LOCKF "$sepdir/$ripnam.$suffix\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 = "ssh " . $machine . " '" . $enccom . "'";
      if($scp) {
         # *Create* the directory:
         # Quote the double quotes with a backslash when using ssh!
         $sepdir = esc_char($sepdir);
         $wavdir = esc_char($wavdir);
         log_info("new-outputdir: $sepdir on $machine created.");
         log_system("ssh $machine mkdir -p \\\"$sepdir\\\"");
         log_info("new-outputdir: $wavdir on $machine created.");
         log_system("ssh $machine mkdir -p \\\"$wavdir\\\"");
         # *Copy* the File:
         # Don't overwrite destination file, it will confuse running
         # encoders! Do it the hard way! First get all lock-file-names
         # of that machine. There will be at least one, created above!
         opendir(LOCK, "$old_wavdir") or
            print "Can not read in $old_wavdir: $!\n";
         my @boxes = grep {/^$machine/i} readdir(LOCK);
         close LOCK;
         my $wavflag = 0;
         # Open each lock-file, read the content, increase counter if
         # the same wavname is found. Again: it will be found at least
         # once.
         foreach(@boxes) {
            open(LOCKF, "$old_wavdir/$_") or
               print "Can't open $old_wavdir/$_: $!\n";
            my @content = <LOCKF>;
            close(LOCKF);
            $wavflag++ if("@content" =~ /$ripnam/);
         }
         $ripnam = esc_char($ripnam);
         log_system("scp $wavdir/$ripnam.wav \\
           $machine:\"$wavdir/$ripnam.wav\" > /dev/null 2>&1")
           if($wavflag <= 1);
      }
   }
   else {
      # On the local machine escape at least the dollar sign.
      $ripnam =~ s/\$/\\\$/g;
      $sepdir =~ s/\$/\\\$/g;
   }
   $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 verbosity 4
   # or higher and look what's going on.
   $enccom = $enccom . " 2> /dev/null" if($verbose <= 3);
   if($machine !~ /^local$/ && $scp) {
      $enccom = $enccom . " && \\
               scp $machine:\"$sepdir/$ripnam.$suffix\_enc\" \\
               $sepdir/$ripnam.$suffix > /dev/null 2>&1 && \\
               ssh $machine rm \"$sepdir/$ripnam.$suffix\_enc\" ";
   }
   if($suffix eq "mpc") {
      $enccom = $enccom . " && \\
                mv \"$sepdir/$ripnam\_enc.$suffix\" \\
                \"$sepdir/$ripnam.$suffix\""
         if($machine eq "local" || ($machine !~ /^local$/ && !$scp));
   }
   else {
      $enccom = $enccom . " && \\
                mv \"$sepdir/$ripnam.$suffix\_enc\" \\
                \"$sepdir/$ripnam.$suffix\""
         if($machine eq "local" || ($machine !~ /^local$/ && !$scp));
   }
   $enccom = $enccom . " && \\
             chmod $fpermission \"$sepdir/$ripnam.$suffix\""
              if($fpermission);
   $enccom = $enccom . " && \\
             rm \"$old_wavdir/$machine.lock_$threadscn\" &";

   # A huge hack only not to interfere with the ripper output.
   if($verbose >= 4) {
      my $ripmsg = "The audio CD ripper reports: all done!";
      my $ripcomplete = 0;
      if(-r "$wavdir/error.log") {
         open(ERR, "$wavdir/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);
         if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
            open(ENCLOG, ">>$wavdir/enc.log");
            print ENCLOG "\n\nWill execute command on machine $machine",
                         " and try to encode \n$ripnam.$suffix\_enc.\n";
            close ENCLOG;
         }
         else {
            print "\nWill execute command on machine $machine and try",
                  " to encode \n$ripnam.$suffix\_enc.\n";
         }
      }
      else {
         print "\nWill execute command on machine $machine and try",
               " to encode \n$ripnam.$suffix\_enc.\n";
      }
   }
   log_system("$enccom");
   sleep 2; # Don't mess up with possible error-msgs from remote hosts.

   $wavdir = $old_wavdir;
   $sepdir = $old_sepdir;
   $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, "$ripnam";
      my $delflag = 0;
      while ($delflag == 0) {
         my $delname = $delname[0];
         my @delwav = grep(/$delname/, @codwav);
         if(!$delwav[0] && $#delname > 1) {
            unlink("$wavdir/$delname.wav");
            log_info("File $wavdir/$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, "$wavdir");
      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, "$wavdir");
      my @wavs = readdir(DIR);
      closedir(DIR);
      @wavs = grep { /\.wav$/ } @wavs;
      foreach (@wavs) {
         unlink("$wavdir/$_");
         log_info("File $wavdir/$_ deleted.");
      }
   }
   if($scp) {
      foreach my $machine (keys %sshlist) {
         next if($machine =~ /local/);
         foreach my $deldir (@sepdir, $wavdir) {
            my $dd = $deldir;
            $dd = esc_char($dd);
            log_system("ssh $machine rm \"$dd/*.wav\" 2> /dev/null");
            log_system("ssh $machine rmdir -p \"$dd\" 2> /dev/null");
         }
      }
   }
}
########################################################################
#
# 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, $asin, $discid, $barcode,
       $language, $reldate);
   my $logfile = $_[0];
   open(LOG, "<$logfile") || print "Can't open $logfile\n";
   my @cddblines = <LOG>;
   close(LOG);
   %cd = ();
   # Note that long lines may be split into several lines
   # all starting with the same keyword, e.g. DTITLE.
   if($_[1] eq "musicbrainz" or $multi == 1) {
      chomp($artist = join('', grep(s/^artist:\s//i, @cddblines)));
      chomp($album = join('', grep(s/^album:\s//i, @cddblines)));
      chomp($categ = join('', grep(s/^category:\s//i, @cddblines)));
      chomp($genre = join('', grep(s/^genre:\s//i, @cddblines)));
      chomp($year = join('', grep(s/^year:\s//i, @cddblines)));
      chomp($cddbid = join('', grep(s/^cddbid:\s//i, @cddblines)));
      chomp($discid = join('', grep(s/^discid:\s//i, @cddblines)));
      chomp($asin = join('', grep(s/^asin:\s//i, @cddblines)));
      chomp($barcode = join('', grep(s/^barcode:\s//i, @cddblines)));
      chomp($language = join('', grep(s/^language:\s//i, @cddblines)));
      chomp($reldate = join('', grep(s/^reldate:\s//i, @cddblines)));
      chomp($trackno = join('', grep(s/^trackno:\s//i, @cddblines)));
      $trackno = $_[2] unless($trackno);
   }
   else {
      $cd{raw} = \@cddblines;
      chomp($artist = join(' / ', grep(s/^DTITLE=//g, @cddblines)));
      $artist =~ s/[\015]//g;
      $artist =~ s/\n\s\/\s//g;
      # 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];
      unless($genre) {
         chomp($genre = join('', grep(s/^DGENRE=//, @cddblines)));
         $genre =~ s/[\015]//g;
      }
      unless($year) {
         chomp($year = join('', grep(s/^DYEAR=//, @cddblines)));
         $year =~ s/[\015]//g;
      }
      $trackno = $_[2];
   }
   $cd{artist} = $artist;
   $cd{title} = $album;
   $cd{cat} = $categ;
   $cd{genre} = $genre;
   $cd{id} = $cddbid;
   $cd{discid} = $discid;
   $cd{asin} = $asin;
   $cd{year} = $year;
   $cd{barcode} = $barcode;
   $cd{language} = $language;
   $cd{reldate} = $reldate;

   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($_[1] eq "musicbrainz" or $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 "$wavdir/error.log") {
      open(ERR, "$wavdir/error.log")
        or print "Fatal: $wavdir/error.log disappeared!\n";
      my @errlines = <ERR>;
      close ERR;
      my @md5tracks = grep(s/^md5: //, @errlines) if($md5sum == 1);
      if(@md5tracks) {
         foreach (@md5tracks) {
            my ($sepdir, $trackname) = split(/;#;/, $_);
            md5_sum("$sepdir", "$trackname", 1);
         }
      }
      # Change file permissions for md5 files.
      if($fpermission && $md5sum == 1){
         foreach(@sepdir, $wavdir) {
            opendir(MD5, "$_") or print "Can not read in $_: $!\n";
            my @md5files = grep {/\.md5$/i} readdir(MD5);
            close MD5;
            # Security check: if encoder not installed, but directory
            # created, then no md5sum-file will be found and the
            # directory instead of the file gets the permissions.
            next unless($md5files[0]);
            if($_ eq $wavdir) {
               chmod oct($fpermission), "$_/$md5files[0]" if($wav == 1);
            }
            else {
               chmod oct($fpermission), "$_/$md5files[0]";
            }
         }
      }
      chmod oct($fpermission), "$wavdir/cd.toc" if($fpermission);
      my @ulink = grep(/^Track /, @errlines);
      if(!@ulink && $multi == 0) {
         unlink("$wavdir/error.log");
      }
      elsif($fpermission) {
         chmod oct($fpermission), "$wavdir/error.log";
      }
      if($ghost == 1&& -r "$wavdir/ghost.log") {
         unlink("$wavdir/ghost.log");
      }
      if($wav == 0 && $wavdir ne $homedir) {
         # I don't like the -p option.
         log_system("rmdir -p \"$wavdir\" 2> /dev/null");
      }
   }
}
########################################################################
#
# 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;
   $_[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 $wavdir/error.log.
   open(ERR, "$wavdir/error.log")
      or print "Can't calculate time, $wavdir/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);
   chomp(my $blanktrks = join(', ', grep(s/^Blankflag = //, @errlines)));
   chomp(my $ghostrks = join(', ', grep(s/^Ghostflag = //, @errlines)));
   chomp(my $splitrks = join(', ', grep(s/^Splitflag = //, @errlines)));
   $blanktrks =~ s/\n//g;
   $ghostrks =~ s/\n//g;
   $splitrks =~ s/\n//g;
   $blanktrks =~ s/,\s(\d+)$/ and $1/g;
   $ghostrks =~ s/,\s(\d+)$/ and $1/g;
   $splitrks =~ s/,\s(\d+)$/ and $1/g;

   @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[0] = 0 unless(@enctime);
      $enctime = int($enctime[0]/60);
   }
   else {
      $enctime = 0;
   }
   return ($riptime,$enctime,$encend,$blanktrks,$ghostrks,$splitrks);
}
########################################################################
#
# Thanks to mjb: log info to file.
#
sub log_info {
   if(!defined($infolog)) { return; }
   elsif($infolog eq "") { return; }
   open(SYSLOG, ">>$infolog") or
   print "Can't open info log file <$infolog>.\n";
   print SYSLOG "@_\n";
   close(SYSLOG);
}
########################################################################
#
# Thanks to mjb and Stefan Wartens improvements:
# log_system used throughout in place of system() calls.
#
sub log_system {
   my $P_command = shift;
   if($verbose > 3) {
      # A huge hack only not to interfer with the ripper output.
      if($P_command =~ /faac|flac|lame|machine|oggenc/) {
         my $ripmsg = "The audio CD ripper reports: all done!";
         my $ripcomplete = 0;
         if(-r "$wavdir/error.log") {
            open(ERR, "$wavdir/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);
            if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
               open(ENCLOG, ">>$wavdir/enc.log");
               print ENCLOG "\n$P_command\n\n";
               close ENCLOG;
            }
            else {
               print "system: $P_command\n\n";
            }
         }
      }
      else {
         print "system: $P_command\n\n";
      }
   }

   log_info("system: $P_command");

   # Start a watch process to check progress of ripped tracks.
   if($parano == 2 && $P_command =~ /^cdparano/
                   && $P_command !~ /-Z/
                   && $P_command !~ /-V/) {
      my $pid = 0;
      # This is probably dangerous, very dangerous because of zombies...
      $SIG{CHLD} = 'IGNORE';
      unless($pid = fork) {
         exec($P_command);
         exit;
      }
      # ... but we check and wait for $pid to finish in subroutine.
      my $result = check_ripper($P_command, $pid);
      waitpid($pid, 0);
      $SIG{CHLD} = 'DEFAULT';
      return $result;
   }
   else {
      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!
#
# Thanks to pancho horrillo:
# http://perldoc.perl.org/perluniintro.html#Displaying-Unicode-As-Text
#
sub oct_char {
   $_[0] = join '',
               map { $_ > 191
                     ? sprintf '\%o', $_
                     : chr $_
               } unpack 'U*', $_[0];
}
########################################################################
#
# Check if there is a CD in the CD device. If a CD is present, start
# process. If not, wait until operator inserts a CD, i.e. come back!
# Problem: when used with option loop, the CD already done should not
# be reread again. In this case, don't close the tray automatically.
#
sub cd_present {
   sysopen(CD, $scsi_cddev, O_RDONLY | O_NONBLOCK) or return;
   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;
   $wavdir           = "";
   @sepdir           = ();
}
########################################################################
#
# Get the revision number of the CDDB entry.
#
sub get_rev {
   my @revision = grep(/^\#\sRevision:\s/, @{$cd{raw}});
   my $revision = join('', grep(s/^\#\sRevision:\s//, @revision));
   chomp $revision if($revision);
   return $revision;
}
########################################################################
#
# Change case to lowercase and uppercase first if wanted.
#
sub change_case {
   if($lowercase == 1 or $uppercasefirst == 1) {
      $_[0] = lc($_[0]);
      $_[0] =~ tr/[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ]/[àáâãäåæçèéêëìíîï]/;
      $_[0] =~ tr/[ÐÑÒÓÔÕÖØÙÚÛÜÝÞ]/[ðñòóôõöøùúûüýþ]/;
   }
   if($uppercasefirst == 1) {
      my @words = split(/ /, $_[0]);
      foreach (@words) {
         $_ = "\u$_";
         $_ =~ tr/^[àáâãäåæçèéêëìíîï]/[ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ]/;
         $_ =~ tr/^[ðñòóôõöøùúûüýþ]/[ÐÑÒÓÔÕÖØÙÚÛÜÝÞ]/;
      }
      $_[0] = join(' ', @words);
   }
   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
# and paths. Remember the default chars to be erased are:   |\:*?$  plus
# blanks and periods at begin and end of file names and directories.
# But the ending periods problem is not done here, because
# it should only affect directories, not files which have a suffix
# anyway! See subroutine create_dirs!
#
sub clean_chars {
   # Delete beginning blanks in directory names.
   $_[0] =~ s,/\s+,/,g if($chars =~ /NTFS/);
   # Delete beginning blanks in file names.
   $_[0] =~ s/^\s+//g if($chars =~ /NTFS/);
   # Delete beginning periods in directory names.
   $_[0] =~ s,/\.+,/,g if($chars =~ /HFS|NTFS/);
   # Delete beginning perods in file names.
   $_[0] =~ s/^\.+//g if($chars =~ /HFS|NTFS/);
   my $purged_chars = $chars;
   $purged_chars = ":" if($chars =~ /HFS/);
   $purged_chars = "[|\\\\:*?\$]" if($chars =~ /NTFS/);
   $_[0] =~ s/$purged_chars//g;
   $_[0] =~ s/\s+/ /g;
   $_[0] =~ s/\s$//;
   return $_[0];
}
########################################################################
#
# Put all chars in brackets and escape some.
#
sub check_chars {
   $chars =~ s/\\/\\\\/;
   $chars =~ s/-/\\-/;
   $chars =~ s/]/\\]/;
   $chars =~ s/\s/\\s/;
   $chars = "[" . $chars . "]" unless($chars =~ /HFS|NTFS|off/);
}
########################################################################
#
# 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.
 -I, --span number-number
              Give a span or interval to rip only a part of the track.
              The cdparanoia notation is used in the format hh:mm:ss.ff
              without brackets. The hyphen is mandatory.
 --merge ordered list of comma separated intervals
              Place a hyphen or a + between first and last tracknumber
              to be merged, default: not set.
 -o, --outputdir directory
              Where the sound should go. If not set, \$HOME will be used.
              Default: not set.
 --dpermission number
              Define directory permissions, default: 0755.
 --fpermission number
              Define permissions of sound and log files, default: not
              set, i.e. depending on the system settings.
 -d, --device cddevice
              Path of audio CD device, default: /dev/cdrom.
 --scsidevice cddevice
              Devicename for a different device node of cddevice
              where non ripping commands shall be executed.
 -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 [number]
              When using dagrab, the number of retries will be set to 3,
              with cdparanoia this option is equal to the -Z option of
              cdparanoia. Usefull for faster ripping but not recommended.
              Use no argument or 1 to swith paranoia off or 2 if failed
              tracks should be done again without paranoia (only one
              retry). Default: off (i.e. paranoia on).
 -G, --ghost  Analyze wav and split into possible chunks of sound or try
              to trim lead-in/out. This may override option merge!
              Delete blank tracks if only silence ("zero bytes") are
              found. Experimental! Default: off.
 --extend seconds
              Enlarge splitted chunk by number of seconds if possible,
              or track may be trimmed if value is small (e.g. 0.2), use
              with caution! default: 2.0.
 --prepend seconds
              Enlarge splitted chunk by number of seconds if possible,
              or track may be trimmed if value is small (e.g. 0.2), use
              with caution! Default: 2.0.
 -c, --coder encoder
              0 Lame, 1 Oggenc, 2 Flac,  3 Faac, 4 mp4als, 5 Musepack,
              a comma separated list or use -c for each encoder.
              The same encoder may be stated more than once. Adapt
              --dirtemplate in this case, see below. Default: 0.
 --musenc name
              Pass the command line name of Musepack encoder, e. g.
              mppenc. Default: mpcenc.
 --faacopt Faac-options
              Pass other options to the encoder,  quote them with double
              quotes if needed; comma separated list if same enocder
              is used more than once. Default: not set.
 --flacopt Flac-options
              Same as above.
 --lameopt Lame-options
              Same as above.
 --museopt Musepack-options
              Same as above.
 --mp4alsopt mp4als-options
              None are requierd (?).
 --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 option
              --vbrmode new --preset fast will be used. Default: off.
 -W, --chars [list]
              Exclude special characters and  (ending!)  periods in file
              names and path. The argument is optional. Following
              characters will be erased, if no argument is 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). To write the cddbid used for freedb
              or the MusicBrainz discid into the comment, use the word
              \"cddbid\" or \"discid\". 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. More than one --dirtemplate may be stated, or
              use variables \$quality and \$suffix. See manpage for more
              info. Default: '\"\$artist - \$album\"'
 -T, --tracktemplate '\"foo \$parameters\"'
              See above. Only one tracktemplate can be stated. 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.
 --mb         Use musicbrainz instead of freedb, default: off.
 -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 with full paths in filenames.
              For filenames without paths use --playlist 2. To prevent
              playlist creation, use: --playlist 0. 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.
 --uppercasefirst
              Uppercase first characters of each word in filenames and
              tags, 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.
              Default: on.
 --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 wav-files to a given dB-value (default:
              -12dB). Default: off.
 --normcmd    Command to use for normalizing, default: normalize.
 -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: -b. Option v will be set according to verbosity.
 --cdtoc n
              n=1: Create a toc file to burn the wavs with cd-text using
              cdrdao or cdrecord (in dao mode), default: 0 - off.
 --inf n
              n=1: Creat inf files for each track to burn the wavs with
              cd-text using wodim or cdrecord (in dao mode),
              default: 0 - 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
              messages (2), normal (3), verbose (4) or extremely 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.
 -A, --book number
              Create an audiobook, i. e. merge all tracks into one sinlge
              file, option --ghost will be switched off and file suffix
              will be m4b. Make sure to use encoder faac. A chapter file
              will be written for chapter marks. Default: off
 --loop number
              Continue to ripp and encode as soon as the previous CD has
              finished. This option forces ejection of the CD. Set
              number to 2 for immediate restart of ripping process,
              experimental. Default off.
 --quitnodb value
              Give up CD if no CDDB entry found.
              Possible values: 0 - off, 1 - on, default: off
 --resume     Resume a previously started session. Default: not set.
 - O, --overwrite argument
              Overwrite existing rip (y), quit if directory exists (q)
              or force ejection of disc if directory exists (e). Default
              off (n), do not overwrite existing directories, use a
              directory name with a suffix instead.
 --md5sum     Create a MD5-sum file for each type of sound files.
              Default: not set.
 --threads number
              Comma separated list of numbers giving maximum of allowed
              encoders to run at the same time, default: 1.
 --execmd command
              State a command to be executed when ripit finshed. Make
              sure to escape the command if needed. Default: not set.


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
       18 August 2009

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

Usage:
ripit [--device|d cd-device] [--scsidevice path] [--outputdir|o path]
      [--dirtemplate '\"\$parameters\"'] [--chars|W [list]]
      [--tracktemplate '\"\$parameters\"']
      [--dpermission number] [--fpermission number]
      [--overwrite argument] [--resume|R]
      [--rip] [--ripper|r cdripper] [--ripopt ripper-options]
      [--nicerip number] [--disable-paranoia|Z] [--wav|w]
      [--ghost|G] [--extend seconds] [--prepend seconds]
      [--quitnodb value] [--encode] [--coder|c encoders] [--musenc cmd]
      [--faacopt options] [--flacopt options] [--oggencopt options]
      [--lameopt options] [--mp4alsopt options] [--museopt options]
      [--quality qualities-list] [--bitrate|b rate]
      [--maxrate|B rate] [--vbrmode|v old or new] [--preset|S mode]
      [--comment id3-comment] [--genre|g genre-tag] [--year|y year-tag]
      [--utftag|U] [--lowercase|l] [--underscore|u] [--uppercasefirst]
      [--proxy|P path] [--mb]
      [--cddbserver|C server] [--mirror|m mirror] [--protocol|L level]
      [--transfer|t cddb or http] [--submission|s] [--mail|M address]
      [--eject|e] [--ejectcmd command] [--ejectopt options for command]
      [--lcd] [--lcdhost host] [--lcdport port]
      [--config] [--save] [--savenew]
      [--sshlist remote hosts] [--local] [--scp] [--threads numbers]
      [--archive|a] [--playlist|p number] [--infolog path] [--md5sum]
      [--cdtoc number] [--inf number] [--loop number] [--verbose|x number]
      [--normalize|N] [--normcmd] [--normopt|z options]
      [--interaction|i] [--nice|n adjustment] [--halt]
      [--help|h] [--version|V] [--execmd|X cmd]
      [--book|A number] [--merge list] [--span|I span] [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 skipped.
# In the same time, the intervals have to be tested if valid.
#
sub skip_tracks {
   my @merge = split(/,/, $pmerge);
   foreach (@merge) {
      # Split each interval into a BeginEndArray.
      my @bea = split(/-|\+/, $_);
      my $i = $bea[0] + 1;
      # Missing separator in command line argument?
      if($#bea > 1) {
         print "\nStrange interval in argument of option merge ($_)!",
               "\nIs there a comma missing?\n\n";
         exit;
      }
      # Operator forgot to give last track or wanted the whole CD to be
      # merged.
      $pmerge .= $#tracklist + 1 unless($bea[1]);
      $bea[1] = $#tracklist + 1 unless($bea[1]);
      # Track number larger than number of tracks on CD?
      if($#tracklist > 0) {
         if($bea[0] > $#tracklist + 1 || $bea[1] > $#tracklist + 1) {
            print "\nWrong 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);
}

########################################################################
#
# Read the header of the wav file yet still called $trn.rip.
#
sub get_wavhead {
   my $trn = shift;
   my $prn = shift;
   open(IN, "< $wavdir/$trn") or print "Can't open $trn: $!\n";
   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}));
   close(IN);

   # Unpack the wav 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 && $prn == 1) {
      print "\nThe wav header has following entries:\n";
      print "$_ \t -> $H->{$_} \n" foreach (keys %$H);
      print "\n";
   }
   return($wavheader, $H);
}

########################################################################
#
# 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 extremely 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 useful 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 split) this
# snippet works. (See below for explanation!)
#
#
# Testreport (CDs with correctly split ghost songs):
#
# OK: 2raumwohnung: in wirklich: 11
# OK: A Camp: Colonia: 12
# OK: Archive: Londonium: 13
# OK: Archive: Take My Head: 10
# OK: Aromabar: 1!: 15 (2 ghost songs!)
# OK: Autour de Lucie: L'échappée belle: 11
# OK: Camille: Sac des filles: 11
# OK: Camille: Le fil: 15 (Ghost song without zero-gap... not splitted!)
# OK: Cibelle: Cibelle: 11
# OK: Dining Rooms: Experiments In Ambient Soul: 13
# OK: Distain!: [li:quíd]: 11
# OK: Falco: Out of the Dark: 9
# OK: Helena: Née dans la nature: 11
# OK: Jay-Jay Johanson: Antenna: 10
# OK: Laika: Sound Of The Satellites: 12
# OK: Lamb: Debut: 10
# OK: Lamb: Fear Of Fours: 00 Hidden Track
# OK: Lamb: What Sound: 10
# OK: Little Boots: Hands: 12
# OK: Lunik: Preparing To Leave: 11
# OK: Lunik: Weather: 11
# OK: Mãozinha: Aerosferas: 11
# OK: Massive Attack: 100th Window: 09
# OK: Moloko: Do You Like My Tight Sweater?: 02
# OK: Olive: Trickle: 12
# OK: Rightous Men: Disconnected: 11
# OK: Samia Farah: Samia Farah: 12
# OK: Saint Etienne: Heart Failed [In The Back Of A Taxi] (CD1): 03
# OK: Stereo Total: Musique Automatique 15
# OK: Yoshinori Sunahara: Pan Am: 09
#
# Deleted blank tracks:
#
# OK: 22 Pistepirkko: Ralley of Love: 0 (hidden track - copy protection)
# OK: NPG: New Power Soul: all blank tracks
# OK: Dave Matthews Band: Under the Table and Dreaming: all blank tracks
#
#
sub get_chunks {
   my ($tcn, $trn) = @_;
   my @times = ();
   $trn = $trn . ".rip";
   my ($wavheader, $H) = get_wavhead("$trn", 0);

# 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 raised 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 bailing out immediately. During the track, a
# weight will help to prevent the same. If the silence lasts more than
# 4 seconds, the detected startsound and duration values will be pushed
# into the @times array. In version 3.7.0 additionally a $trimcn is
# introduced, to enable RipIT to trim tracks at beginning and end. This
# can now be done, if the --extend and --prepend options are set to 0,
# not recommended. If the lead-in/out and gaps are really zero, the
# $trimcn will correct the values pushed into @times which correspond to
# the time points where volume is below the thresh value, but not yet
# zero. With these values a --prepend or --extend of 0 would cut off a
# few fractions of seconds. This may still happen, if the lead-in/out
# and/or gap is not really zero. How should RipIT know about silence?
# If lead-in/out and gaps are zero, $trimcn will slightly enlarge the
# chunks of sound and trimming should not cut away sound, hopefully.
# As far as I understand, the unpack function returns the number of bits
# set -- in a bit vector.  Using this value, I stress that we deal with
# bits and not bytes for the variables $thresh and $maxthresh. Therefore
# $maxthresh is multiplied by 8!

   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 $leadinflag = 0;
   my $startsnd = 0;
   my $soundflag = 0;
   my $deltathre = $H->{byte_per_sec} * $chunksize;
   my $totalthre = 0;
   my $trimcn = 0;
   my $weight = 1;
   my $maxthresh = $deltathre * 8 * 0.7;

   open(IN, "< $wavdir/$trn") or print "Can't open $trn: $!\n";
   binmode(IN);
   seek(IN, 44, 0);
   while(read(IN, $bindata, $chunkbyte)) {
      $chunkcn++;
      my $thresh = unpack('%32b*', $bindata);
      $totalthre += $thresh / 1000 if($thresh < $maxthresh * 1.1 );
      $weight++
         if($thresh > 0.8 * $deltathre && $thresh < 1.1 * $deltathre);
      $deltathre = ($deltathre * $weight + $thresh) / (1 + $weight)
         if($thresh > 0.8 * $deltathre && $thresh < $maxthresh &&
            $chunkcn =~ /[05]$/);
      # According to the $thresh value, decide whether it is sound or
      # not.
      # The if-condition itself is a little more tricky. We have to
      # force this condition at beginning, even if there is no silence!
      # Why this? If there is a lead-in with immediate sound but very
      # short interruptions, the switch of $soundflag = 1 will be the
      # reason that the startsnd will increase, although it shouldn't,
      # it should stay at 0.0, but will become 0.1 or similar in this
      # case! In this way, if the interuptions are short (< 4s) nothing
      # will happen, and the fact that $startsnd will not set back to
      # zero until a true gap will be found, $startsnd will not be
      # recalculated in the else-part.
      if($thresh < 0.8 * $deltathre || $bytecn == 0) {
         $silencecn += $chunkbyte;
         # If thesh is zero, use an other counter to calculate more
         # precise values.
         $trimcn += $chunkbyte if($thresh == 0);
         $leadinflag = 1 if($thresh == 0 && $bytecn == 0);
         # If the gap is 4 seconds long, save values in array @times, or
         # to detect lead-ins shorter than 4s, set the $soundflag to 1.
         if($silencecn == $H->{byte_per_sec} * 4 ||
            $bytecn < $H->{byte_per_sec} * 4) {
            $chunklen = ($bytecn - $silencecn) / $H->{byte_per_sec};
            # Otherwise:
            $chunklen = ($bytecn - $trimcn) / $H->{byte_per_sec}
               if($trimcn < $silencecn && $trimcn > 0);
            $chunklen -= $startsnd;
            # The chunk of sound must be longer than 4.0 seconds!
            if($chunklen < 4) {
               $chunklen = 0;
            }
            else {
               push(@times, "$startsnd $chunklen");
               # Prevent re-entering a duplicate last entry outside of
               # the loop.
               $startsnd = 0;
            }
            # Chunk of sound has been detected. Doing this here and not
            # just above where $starsnd is set to zero, will enable
            # detection of short lead-ins!
            $soundflag = 1;
            # From now on we are in silence!
            # Set $trimcn to $silencecn to detect another difference
            # at the end of the gap, if the gap consists of zeros.
            $trimcn = $silencecn if($bytecn > $H->{byte_per_sec} * 4);
         }
         # We will stay in this condition, until...
      }
      else {
         # ... sound is detected (again)!
         # If we get here the first time, save the $startsound time.
         if($soundflag == 1 && $startsnd == 0) {
            if($trimcn < $silencecn &&
               $trimcn > (0.8 * $silencecn)) {
               $startsnd = ($bytecn - $silencecn + $trimcn) /
                            $H->{byte_per_sec};
            }
            elsif($startsnd == 0) {
               $startsnd = $bytecn / $H->{byte_per_sec};
            }
            $soundflag = 0;
         }
         $trimcn = 0;
         $silencecn = 0;
      }
      $bytecn += $chunkbyte;
   }
   # Calculations for the last (only) chunk of sound.
   $chunklen = ($bytecn - $silencecn) / $H->{byte_per_sec};
   # Otherwise (slightly different condition than above):
   $chunklen = ($bytecn - $trimcn) / $H->{byte_per_sec}
      if($trimcn < $silencecn);
   $chunklen -= $startsnd;
   push(@times, "$startsnd $chunklen") unless($startsnd == 0);
   push(@times, "$startsnd $chunklen") unless(@times);
   $times[0] =~ s/^0.1/0/ if($startsnd == 0.1 && $leadinflag == 0);

   my $tracklen = int(($framelist[$tcn] - $framelist[$tcn - 1]) / 7.5);
   $tracklen = int($framelist[$tcn] / 7.5) if($tcn == 0);
   $tracklen /= 10;

   # I don't like it, but it might be OK to delete very short tracks
   # if their content is blank.
   if(-s "$wavdir/$trn" < 200000 && $totalthre >= 200) {
      $chunkcn = 0;
      $totalthre = 0;
      open(IN, "< $wavdir/$trn") or print "Can't open $trn: $!\n";
      binmode(IN);
      seek(IN, 44, 0);
      while(read(IN, $bindata, 2)) {
         $chunkcn++;
         my $thresh = unpack('%32b*', $bindata);
         $thresh = 0 if($thresh >= 14);
         $totalthre += $thresh;
      }
      $totalthre = $totalthre * 4 / $chunkcn;
   }

   if($totalthre < 200) {
      unlink("$wavdir/$trn") or print "Can't delete $trn: $!\n";
      if($verbose >= 1) {
         print "\n\nRipIT found blank track $trn\n",
               "and decided to delete it.\n\n";
      }
      open(ERO,">>$wavdir/error.log")
         or print "Can not append to file $wavdir/error.log\"!\n";
      print ERO "Blankflag = $tcn\nTrack $tcn on CD failed!\n";
      close(ERO);
      log_info("blank track deleted: $wavdir/$trn");
      $times[0] = "blank";
      return(@times);
   }

   if($verbose >= 2) {
      print "\nRipIT found following chunks for track\n",
            "$trn (${tracklen}s long):\nstart duration (in seconds)\n";
      log_info("\nRipIT found following chunks for track:");
      log_info("$trn (${tracklen}s long):\nstart duration (in seconds)");
      foreach(@times) {
         my @interval = split(/ /, $_);
         printf("%5.1f %9.1f\n", $interval[0], $interval[1]);
         log_info("@interval");
      }
   }
   return(@times);
}
########################################################################
#
# Split the wav into chunks of sound and rename all of them to
# "Ghost Song $counter.wav".
#
sub split_chunks {

   my ($tcn, $trn, $cdtocn, @times) = @_;
   my $bindata;
   my ($wavheader, $H) = get_wavhead("$trn.rip", 1);
   my $chunksize = 0.1; # Chunk size in seconds.
   my $chunkbyte = $H->{byte_per_sec} * $chunksize;
   my $chunkcn = 0;
   # Save the tracklength of the original track to be compared with the
   # chunks of sound.
   my $tracklen = int($H->{data_size} / $H->{byte_per_sec} * 10);
   $tracklen /= 10;

   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;
      }
      else{
         $interval[1] += $interval[0];
         $interval[0] = 0;
      }
      # Extend the interval, this might result in a too long interval.
      $interval[1] += $extend;
      # Don't allow too long end-times, this can happen with the above
      # extend command.
      if($interval[0] + $interval[1] > $tracklen) {
         $interval[1] = $tracklen - $interval[0];
      }
      # Don't split if interval is larger than tracklength from cdtoc.
      # Use a threshold of $extend + $prepend. Reasonable?
      if(($tracklen - $extend - $prepend) <= $interval[1] ||
          $interval[1] < 3) {
         print "Track $tcn not splitted.\n\n" if($verbose >= 1);
         log_info("Track $tcn not splitted.");
         return;
      }

      # Use array @secondlist to save new track lengths to allow the
      # ripper (!) process to write correct playlist files. The array
      # will be printed to ghost.log for encoder process in the "next"
      # subroutine called rename_chunks, see below.
      if($chunkcn == 0) {
         $secondlist[$tcn - 1] = int($interval[1]);
         $secondlist[$tcn] = int($interval[1]) if($hiddenflag == 1);
      }
      else {
         push(@secondlist, int($interval[1]));
      }

      # Print info message about what is going on (only once):
      if($verbose >= 2 && $chunkcn == 0) {
         print "Splitting \"$trn\" into " . ($#times + 1) . " chunk";
         print ".\n" if($#times == 0);
         print "s.\n" unless($#times == 0);
      }
      if($chunkcn == 0) {
         log_info("Splitting \"$trn\" into " . ($#times + 1) . " chunk.")
            if($#times == 0);
         log_info("Splitting \"$trn\" into " . ($#times + 1) . " chunks.")
            unless($#times == 0);
      }
      if($verbose >= 4) {
         print "\n\nUsing these values for chunk $chunkcn:\n";
         printf("%5.1f %5.1f\n", $interval[0], $interval[1]);
      }
      log_info("\nUsing these values for chunk $chunkcn:");
      log_info("@interval");

      # Prepare the filename for output.
      my $outr = "Ghost Song $chunkcn";
      $outr = get_trackname($tcn, $outr) . ".rip";
      open(OUT, "> $wavdir/$outr");
      binmode(OUT);

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

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

      # This is nuts, don't know why this happens, but the chunk sizes
      # in the RIFF header are sometimes one byte smaller leading to an
      # unpaired number. This causes flac to fail on splitted tracks!
      # So let's do it the ugly way: add 1 byte and rewrite the header.
      # What goes wrong in the above substr command or elsewhere?
      # If someone finds out, please let me know!
      my $loopcn = 0;
      # Initialization:
      ($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);

      while($loopcn < 10 and $H->{data_size} ne $interval[1]) {
         if($verbose >= 5) {
            print "\nFatal error, unpair chunk sizes detected\n",
                  "in new header of ghost track part $chunkcn:\n",
                  "\$H->{data_size} is $H->{data_size} ",
                  "instead of chunk length = $interval[1]!\n",
                  "The new wav header has following entries:\n";
            print "$_ \t -> $H->{$_} \n" foreach(keys %$H);
            print "\n";
         }
         log_info("\nFatal error, unpair chunk sizes detected\n",
               "in new header of ghost track part $chunkcn:\n",
               "\$H->{data_size} is $H->{data_size} ",
               "instead of chunk length = $interval[1]!\n",
               "The new wav header has following entries:");
         log_info("$_ \t -> $H->{$_}") foreach(keys %$H);
         log_info("\n");

         $H->{data_size} = 2 * $interval[1] - $H->{data_size};
#         $H->{data_size} = $interval[1] + 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});

         ($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);

         $loopcn++;
      }

      if($loopcn >= 9 && $verbose >= 3) {
         print "\nMajor problem writing the wav header.";
         log_info("\nMajor problem writing the wav header.");
         if($wcoder =~ /2/) {
            print "\nWon't split this track because Flac will fail.";
            log_info("\nWon't split this track because Flac will fail.");
            # Reset the @times array.
            @interval = (0, $tracklen);
            @times = ("0 $tracklen");
            if($chunkcn == 0) {
               $secondlist[$tcn - 1] = $interval[1];
               $secondlist[$tcn] = $interval[1] if($hiddenflag == 1);
            }
            else {
               pop(@secondlist);
            }
         }
         else {
            print "\nWill try to continue anyway.\n";
            log_info("\nWill try to continue anyway.\n");
         }
      }
      return if($loopcn >= 9 && $wcoder =~ /2/);

      syswrite(OUT, $wavheader, 44);
      log_info("The length of data is $interval[1].");
      log_info("The final wav header has following entries:");
      log_info("$_ \t -> $H->{$_}") foreach(keys %$H);
      log_info("\n");
      if($verbose >= 5) {
         print "The length of data is $interval[1].\nThe final wav",
               "header of chunk $chunkcn has following entries:\n";
         print "$_ \t -> $H->{$_} \n" foreach (keys %$H);
         print "\n";
      }

      # Seek from beginning of file to start of sound of chunk.
      open(IN, "< $wavdir/$trn.rip") or
      print "Can't open $trn.rip: $!\n";
      binmode(IN);
      print "I seek to: ${interval[0]}B, starting from 0B.\n"
         if($verbose >= 4);
      log_info("I seek to: ${interval[0]}B, starting from 0B.");
      seek(IN, $interval[0], 0) or
         print "\nCould not seek in file IN: $!\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.
      my $start_snd = $interval[0];
      $interval[1] = $interval[1] + $interval[0];
      while(read(IN, $bindata, $chunkbyte) &&
            $interval[0] < $interval[1] - 1) {
         $interval[0] += $chunkbyte;
         # Before we write the data, check it, because it happens that
         # seek does not seek to a pair number, starting to read an
         # unpair (right-channel) byte. In this case, the wav will sound
         # like pure noise, and adding or deleting a single byte right
         # after the header will heal the wav.
         # The amount of data in the read $bindata seems OK, only the
         # position is wrong.
         my $pos = tell(IN);
         if($pos !~ /[02468]$/) {
            print "After chunkbyte = <$chunkbyte> reached pos <$pos>.\n"
               if($verbose >= 5);
            log_info("After chunkbyte = <$chunkbyte> reached pos <$pos>.\n");
            # Move one byte!
            read(IN, my $dummybyte, 1);
            $pos = tell(IN);
            print "After 1 byte read reached pos <$pos>.\n"
               if($verbose >= 5);
         }
         print OUT $bindata;
      }
      print "This chunk should be ", $interval[0] - $start_snd,
            "B large.\n" if($verbose >= 5);
      log_info("This chunk should be ", $interval[0] - $start_snd,
               "B large.");
      log_info("Remember, steps in the size of $chunkbyte B are used.");
      close(OUT);
      $chunkcn++;
   }
   close(IN);
   open(ERO,">>$wavdir/error.log")
      or print "Can not append to file ",
               "\"$wavdir/error.log\"!\n";
   if($#times == 0) {
      print "Track $tcn successfully trimmed.\n\n" if($verbose >= 1);
      log_info("Track $tcn successfully trimmed.\n\n");
      print ERO "Splitflag = $tcn\n";
   }
   else {
      print "Track $tcn successfully splitted.\n\n" if($verbose >= 1);
      log_info("Track $tcn successfully splitted.\n\n");
      print ERO "Ghostflag = $tcn\n";
   }
   close(ERO);
}
########################################################################
#
# Rename the chunks called "XY Ghost Song $chunkcn" to the appropriate
# file name according to the track-template.
#
sub rename_chunks {

   # The ripper uses 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. Note that splitted songs are of course
   # already ripped, so we do not need to notify the ripper about ghost
   # songs.

   # 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? If there are only
   # two chunks, the second will get the suffix Ghost Song without a
   # counter. If there are more than two chunks, a counter will be
   # added.

   # 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!)

   my ($tcn, $trn, $cdtocn, @times) = @_;
   my $chunkcn = 0;
   my $ghostflag = 0;
   my $outr = "Ghost Song $chunkcn";
   $outr = get_trackname($tcn, $outr) . ".rip";
   # The first track must be renamed to the *.rip file because the
   # ripper will rename it to wav!
   rename("$wavdir/$outr", "$wavdir/$trn.rip");
   shift(@times);
   # If only one chunk has been trimmed, we are done, array @times is
   # empty now.
   # If there are two or more chunks, proceed and hack all necessary
   # arrays needed for the encoder. They will be written int the
   # ghost.log file. Note that with only one ghost song no counter is
   # needed in the filename. The suffix can now be wav instead of rip.
   # TODO: check if final trackname already exists.
   # 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);
   $chunkcn++;
   foreach (@times) {
      $trn = $tracklist[$tcn - 1];
      $trn = $tracklist[$tcn] if($hiddenflag == 1);
      my $artistag = clean_all($artist_utf8);
      # Remember: $outr is the output track name of the splitted wav.
      $outr = "Ghost Song $chunkcn";
      # The name for the tags will be with originating track name as
      # prefix.
      my $trt = $trn . " - Ghost Song" if($#times == 0);
      $trt = $trn . " - Ghost Song $chunkcn" if($#times > 0);
      # The actual track name will be slightly different.
      $trn = $trt;
      $trn = clean_name($trn);
      $trn = change_case($trn);
      $trn =~ s/ /_/g if($underscore == 1);
      push(@seltrack, $gcn);
      push(@tracklist, $trn);
      push(@tracktags, "$trt");
      $outr = get_trackname($tcn, $outr) . ".rip";
      $trn = get_trackname($gcn, $trn);
      rename("$wavdir/$outr", "$wavdir/$trn.wav");
      md5_sum("$wavdir", "$trn.wav", 0) if($md5sum == 1 && $wav == 1);
      if($cdtoc == 1) {
         $cdtocn++;
         my $cdtocartis = oct_char($artistag);
         my $cdtoctitle = $trt;
         $cdtoctitle = clean_name($cdtoctitle);
         $cdtoctitle = oct_char($cdtoctitle);
         open(CDTOC, ">>$wavdir/cd.toc")
            or print "Can not append to file \"$wavdir/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 \"$trn.wav\" 0\n";
         close(CDTOC);
      }

      $gcn++ if($ghostflag == 1);
      $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, ">$wavdir/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 dependencies ",
            "\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 dependencies",
            "\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;
   }
   $@ = ();
   eval { require Unicode::UCD } if($utftag == 0);
   if($@) {
      print "\nPlease install Unicode::UCD and dependencies",
            "\nfrom your closest CPAN mirror before trying again with",
            "\noption --noutftag. Install by hand or e.g. type as root:",
            "\nperl -MCPAN -e 'install Unicode::UCD'\n\n";
      exit 0;
   }

   eval { require WebService::MusicBrainz::Release } if($mb == 1);
   if($@) {
      print "\nPlease install WebService::MusicBrainz and dependencies",
            "\nfrom your closest CPAN mirror before trying again with",
            "\noption --mb. Install by hand because using (as root:",
            "\nperl -MCPAN -e 'install WebService::MusicBrainz'",
            "\nmigth fail.\n\n";
      exit 0;
   }

   eval { require  MusicBrainz::DiscID } if($mb == 1);
   if($@) {
      print "\nPlease install MusicBrainz::DiscID and dependencies",
            "\nfrom your closest CPAN mirror; e.g. type as root:",
            "\nperl -MCPAN -e 'install MusicBrainz::DiscID'\n\n";
#      exit 0;
   }

   if($multi == 1) {
      eval "use Color::Output";
      if($@) {print "\nColor::Output not installed!\n"};
      eval "Color::Output::Init";
   }

   print "\n\n" if($verbose >= 1);
}
########################################################################
#
# Check if lame is installed.
#
sub check_enc {
   my ($enc, $suf) = @_;
   unless(log_system("$enc --version > /dev/null 2>&1")) {
      $enc = "\u$enc";
      if(!@pcoder && "@coder" =~ /0/ || "@pcoder" =~ /0/) {
         print "\n$enc not found (needed to encode $suf)!",
               "\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 if($enc =~ /Lame/);
               $coders =~ s/3/1/g if($enc =~ /Faac/);
            }
            else {
               $coders =~ s/0//g if($enc =~ /Lame/);
               $coders =~ s/3//g if($enc =~ /Faac/);
            }
            if($pcoders !~ /1/) {
               $pcoders =~ s/0/1/g if($enc =~ /Lame/);
               $pcoders =~ s/3/1/g if($enc =~ /Faac/);;
            }
            else {
               $pcoders =~ s/0//g if($enc =~ /Lame/);
               $pcoders =~ s/3//g if($enc =~ /Faac/);
            }
            $lameflag = -1;
            @coder = split(/ /, $coders);
            @pcoder = split(/ /, $pcoders);
         }
         else {
            print "\n",
                  "Install $enc or choose another encoder with option",
                  "\n",
                  "-c 1 for oggenc, -c 2 for flac 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 $sepdir = shift;
   my $filename = shift;
   my $ripcomplete = shift;
   my $suffix = $filename;
   $suffix =~ s/^.*\.//;
   chomp($filename);
   chomp($suffix);

   # What name should the md5 file get?
   my @paths = split(/\//, $sepdir);
   my $md5file =  $paths[$#paths] . " - " . $suffix . ".md5";
   $md5file =~ s/ /_/g if($underscore == 1);

   return unless(-r "$sepdir/$filename");

   open(SND,"<$sepdir/$filename") or
      print "Can not open $sepdir/$filename: $!\n";
   binmode SND;
   if($verbose >= 4) {
      if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
         open(ENCLOG, ">>$wavdir/enc.log");
         print ENCLOG "\n\nCalculating MD5-sum for $filename...";
         close ENCLOG;
      }
      else {
         print "\nCalculating MD5-sum for $filename...";
      }
   }
   my $md5 = Digest::MD5->new->addfile(*SND)->hexdigest;
   close SND;
   if($verbose >= 4) {
      if(-r "$wavdir/enc.log" && $ripcomplete == 0) {
         open(ENCLOG, ">>$wavdir/enc.log");
         print ENCLOG "\nThe MD5-sum for $filename is: $md5.\n\n";
         close ENCLOG;
      }
      else {
         print "\nThe MD5-sum for $filename is: $md5.\n";
      }
   }
   open(MD5SUM,">>$sepdir/$md5file")
      or print "Can not append to file \"$sepdir/$md5file\"!\n";
   print MD5SUM "$md5 *$filename\n";
   close MD5SUM;
}
########################################################################
#
# Sort the options and fill the globopt array according to the encoder.
# Remember, the list of options for one encoder stated several times is
# separated by commas. The *opt arrays below will have only one
# entry, if the corresponding encoder has been stated only once. If one
# needs to find globopt in the code, search for "$globopt[" and not for
# @globopt.
#
sub check_options {
   my @flacopt = split(/,/, $flacopt);
   my @lameopt = split(/,/, $lameopt);
   my @oggencopt = split(/,/, $oggencopt);
   my @faacopt = split(/,/, $faacopt);
   my @mp4alsopt = split(/,/, $mp4alsopt);
   my @museopt = split(/,/, $museopt);
   $faacopt[0] = " " unless($faacopt[0]);
   $flacopt[0] = " " unless($flacopt[0]);
   $lameopt[0] = " " unless($lameopt[0]);
   $mp4alsopt[0] = " " unless($mp4alsopt[0]);
   $museopt[0] = " " unless($museopt[0]);
   $oggencopt[0] = " " unless($oggencopt[0]);
   for(my $c=0; $c<=$#coder; $c++) {
      if($coder[$c] == 0) {
         if($preset) {
            $lameopt[0] .= " --preset $preset";
         }
         else {
            $lameopt[0] .= " --vbr-$vbrmode" if($vbrmode);
            $lameopt[0] .= " -b $bitrate" if($bitrate ne "off");
            $lameopt[0] .= " -B $maxrate" if($maxrate != 0);
            $lameopt[0] .= " -V $quality[$c]"
               if($qualame ne "off" && $vbrmode);
            $lameopt[0] .= " -q $quality[$c]"
               if($quality[$c] ne "off" && !$vbrmode);
         }
         # Nice output of Lame-encoder messages.
         if($quality[$c] eq "off" && $lameopt[0] =~ /\s*-q\s\d\s*/) {
            $quality[$c] = $lameopt[0];
            $quality[$c] =~ s/^.*-q\s(\d).*$/$1/;
         }
         $lameopt[0] =~ s/^\s*//;
         push(@globopt, $lameopt[0]);
         shift(@lameopt);
      }
      elsif($coder[$c] == 1) {
         $oggencopt[0] .= " -q $quality[$c]" if($quality[$c] ne "off");
         $oggencopt[0] .= " -M $maxrate" if($maxrate != 0);
         $oggencopt[0] =~ s/^\s*//;
         push(@globopt, $oggencopt[0]);
         shift(@oggencopt);
      }
      elsif($coder[$c] == 2) {
         $flacopt[0] .= " -$quality[$c]" if($quality[$c] ne "off");
         $flacopt[0] =~ s/^\s*//;
         push(@globopt, $flacopt[0]);
         shift(@flacopt);
      }
      elsif($coder[$c] == 3) {
         $faacopt[0] .= " -q $quality[$c]" if($quality[$c] ne "off");
         $faacopt[0] =~ s/^\s*//;
         push(@globopt, $faacopt[0]);
         shift(@faacopt);
      }
      elsif($coder[$c] == 4) {
         $mp4alsopt[0] .= " -q $quality[$c]" if($quality[$c] ne "off");
         $mp4alsopt[0] =~ s/^\s*//;
         push(@globopt, $mp4alsopt[0]);
         shift(@mp4alsopt);
      }
      elsif($coder[$c] == 5) {
         $museopt[0] .= " --quality $quality[$c]" if($quality[$c] ne "off");
         $museopt[0] =~ s/^\s*//;
         push(@globopt, $museopt[0]);
         shift(@museopt);
      }
   }
}
########################################################################
#
# Check ripper (cdparanoia) and calculate a timeout according to track
# length.
#
sub check_ripper {
   my $P_command = shift;
   my $pid = shift;
   my @commands = split(/ /, $P_command);
   my $riptrackno = $commands[3];
   # Remember, $riptrackno might hold an span (interval) format.
   $riptrackno =~ s/\[.*$//;
   $riptrackno =~ s/-.*$//;
   # The $P_command is slightly different in case of hidden tracks.
   # Prevent warning when $riptrackno holds the device path instead of
   # the hidden track number.
   $riptrackno = 0 if($hiddenflag == 1 && $riptrackno !~ /^\d+$/);
   my $tlength = $secondlist[$riptrackno - 1];
   $tlength = $secondlist[$riptrackno] if($hiddenflag == 1);
   $tlength = int(exp(- $tlength / 2000) * ($tlength + 20));
   my $cn = 0;
   while(kill 0, $pid) {
      if($cn > $tlength) {
         unless(kill 9, $pid) {
            warn "\nProcess $pid already finished!\n";
         }
         return 0;
      }
      sleep 3;
      $cn += 3;
   }
   return 1;
}
########################################################################
#
# Check distribution.
#
sub check_distro {
   $distro = "debian" if(-f "/etc/debian_version");
}
########################################################################
#
# Get discid and number of tracks of inserted disc.
#
sub get_cddbid {
   CDDB_get->import( qw( get_cddb get_discids ) );
   my $cd = get_discids($scsi_cddev);
   my ($id, $tracks, $toc) = ($cd->[0], $cd->[1], $cd->[2]);
   $cddbid = sprintf("%08x", $id);
   my $totaltime = sprintf("%02d:%02d",$toc->[$tracks]->{min},$toc->[$tracks]->{sec});
   return($cddbid, $tracks, $totaltime);
}
########################################################################
#
# Analyze string build from CDDB data for latin and wide chars.
#
sub check_encoding {
   my $char_string = shift;
   my $utf_string = $char_string;
   my $latinflag = 0;
   my $wideflag = 0;
   my $utf_latinflag = 0;
   my $utf_wideflag = 0;

   if($cd{discid}) {
      # We do nothing for the moment.
      # $char_string = decode("iso-8859-15", $char_string);
   }
   else {
      $utf_string = Encode::decode('UTF-8', $utf_string, Encode::FB_QUIET);
   }

   my @char_points = ();
   my @utf_points = ();

   # Prevent warning:
   # Malformed UTF-8 character (unexpected non-continuation byte 0x74,
   # immediately after start byte 0xe1) in unpack.
   if(!utf8::is_utf8($char_string)) {
      @char_points = unpack("C0U*", "$char_string");
   }
   # @utf_points = unpack("C0U*", "$datb"); # Perl 5.8
   @utf_points = unpack("U0U*", "$utf_string"); # Perl 5.10


   foreach (@char_points) {
#      print "$_ " if($verbose >= 5);
      $latinflag++ if($_ > 128 && $_ < 256);
      $wideflag++ if($_ > 255);
   }

   foreach (@utf_points) {
#      print "$_ " if($verbose >= 5);
      $utf_latinflag++ if($_ > 128 && $_ < 256);
      $utf_wideflag++ if($_ > 255);
   }

   return($latinflag, $wideflag, $utf_latinflag, $utf_wideflag);
}
########################################################################
#
# Transform length of span in seconds. Argument has hh:mm:ss.ff format.
#
sub span_length {
   my $time = shift;
   my @time = split(/:/, $time);
   my $factor = 60;
   $time = pop(@time);
   # Cut off frames (sectors).
   my $frames = 0;
   ($time, $frames) = split(/\./, $time) if($time =~ /\./);
   # Round the value of frames.
   $time++ if($frames > 37);
   while ($time[0]) {
      $time += pop(@time) * $factor;
      $factor += 60;
   }
   return($time);
}
########################################################################
#
# Transform length of span from seconds to hh:mm:ss.ff format.
# Thanks to perlmonks.
#
sub chapter_length {
    my $f = shift;

    my $s = int($f / 75);

    return sprintf("%s%02d", "00:00:", $s) if($s < 60);

    my $m = $s / 60;
    $s = $s % 60;
    return sprintf("%s%02d:%02d", "00:", $m, $s) if($m < 60);

    my $h = $m /  60;
    $m %= 60;
    return sprintf("%02d:%02d:%02d", $h, $m, $s) if($h < 24);

    my $d = $h / 24;
    $h %= 24;
    return sprintf("%d:%02d:%02d:%02d", $d, $h, $m, $s);
}
########################################################################
#
# Finish process.
#
sub finish_process {

   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, $blanktrks, $ghostrks, $splitrks)
      = cal_times();
   del_erlog();

   if(-r "$wavdir/error.log" && $blanktrks eq "") {
      if($verbose >= 1) {
         print "\nCD may NOT be complete! Check the error.log \n",
               "in $wavdir!\n";
      }
      elsif($verbose >= 3) {
         print "\nRipping needed $riptime min and encoding needed ",
               "$enctime min.\n\n";
      }
   }
   else {
      if($verbose >= 1) {
         if($ghost == 1) {
            if($blanktrks) {
               print "\nCD may NOT be complete! Check the error.log \n",
                    "in $wavdir!\n";
               print "Blank track deleted: $blanktrks!\n"
                   if($blanktrks !~ /and/);
               print "Blank tracks deleted: $blanktrks!\n"
                   if($blanktrks =~ /and/);
            }
            else {
               print "\nAll complete!\n";
            }
            if($ghostrks) {
               print "Ghost song found in track $ghostrks!\n"
                   if($ghostrks !~ /and/);
               print "Ghost songs found in tracks $ghostrks!\n"
                   if($ghostrks =~ /and/);
            }
            else {
               print "No ghost songs found!\n";
            }
            if($splitrks) {
               print "Track $splitrks trimmed!\n"
                  if($splitrks !~ /and/);
               print "Tracks $splitrks trimmed!\n"
                  if($splitrks =~ /and/);
            }
            else {
              print "No tracks trimmed!\n" unless($splitrks);
            }
         }
         else {
            print "\nAll complete!\n";
         }
         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) or print "close: $!";
   }

   if($multi == 1) {
      open(SRXY,">>$logfile")
         or print "Can not append to file \"$logfile\"!\n";
      print SRXY "\nEncoding   ended: $encend";
      print SRXY "\nRipping  needed: $riptime min.";
      print SRXY "\nEncoding needed: $enctime min.";
      print SRXY "\nGhost song(s) found in tracks $ghostrks!\n"
         if($ghostrks && $ghost == 1);
      print SRXY "\nTrack(s) $splitrks trimmed!\n"
         if($splitrks && $ghost == 1);
      print SRXY "\nTrack(s) $blanktrks deleted!\n"
         if($blanktrks && $ghost == 1);
      close(SRXY);
      my $cddevno = $cddev;
      $cddevno =~ s/\/dev\///;
      open(SUCC,">>$outputdir/done.log")
         or print "Can not append to file \"$outputdir/succes.log\"!\n";
      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$/);
      cprint("\x037Encoding done in $cddev with:\x030");
      cprint("\x037\n$cd{artist} - $cd{title}.\x030");
      cprint("\x033\nGhost song(s) found in tracks $ghostrks!\x030")
         if($ghostrks =~ /1/ && $ghost == 1);
      cprint("\x033\nTrack(s) $splitrks trimmed!\x030")
         if($splitrks =~ /1/ && $ghost == 1);
      cprint("\x033\nTrack(s) $blanktrks deleted!\x030")
         if($blanktrks =~ /1/ && $ghost == 1);
   }

   if($execmd) {
      $execmd =~ s/\$/\\\$/g;
      print "Will execute command \"$execmd\".\n" if($verbose >= 3);
      log_system("$execmd");
   }

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

   log_info("*" x 72, "\n");
   print "\n";
   print "Please insert a new CD!\n\n" if($loop == 2);
   return;
}
########################################################################
#
# Write inf files for each track.
#
sub write_inf {
   my $wavdir = shift;
   my $riptrackname = shift;
   my $artistag = shift;
   my $albumtag = shift;
   my $tracktag = shift;
   my $riptrackno = shift;
   my $nofghosts = shift;
   my $trackstart = shift;

   $nofghosts = $nofghosts - $riptrackno + 1;
   my $ripstart = sprintf("%02d:%02d:%02d",
                          sub {$_[2], $_[1], $_[0]}->(localtime));
   my $date = sprintf("%04d-%02d-%02d",
      sub {$_[5]+1900, $_[4]+1, $_[3]}->(localtime));

   while ($nofghosts > 0) {
      open(INF,">$wavdir/$riptrackname.inf");
      print INF "# Wave-info file created by ripit $version on ",
                "$date at $ripstart.\n# To burn the wav files use e.g.",
                " command:\n# wodim dev=/dev/scd0 -v -eject -pad -dao ",
                "-useinfo -text *.wav\n#\n";
      print INF "CDINDEX_DISCID=\t'$cd{discid}'\n" if($cd{discid});
      print INF "CDDB_DISCID=\t$cddbid\n#\n";
      print INF "Albumperformer=\t'$artistag'\n";
      print INF "Performer=\t'$artistag'\n";
      print INF "Albumtitle=\t'$albumtag'\n";
      print INF "Tracktitle=\t'$tracktag'\n";
      print INF "Tracknumber=\t$riptrackno\n";
      print INF "Trackstart=\t$trackstart\n";
      my $length = -s "$wavdir/$riptrackname.wav";
      $length = int(($length - 44) / 2352);
      print INF "# track length in sectors (1/75 seconds each), rest samples\n";
      print INF "Tracklength=\t'",  $length, ", 0'\n";
      $trackstart += $length;
      print INF "Pre-emphasis=\tno\n";
      print INF "Channels=\t2\n";
      print INF "Endianess=\tlittle\n";
      print INF "# index list\n";
      print INF "Index=\t0\n";
      print INF "Index0=\t-1\n";
      close INF;
      $nofghosts--;
      if($nofghosts > 0) {
         my $gcn = $seltrack[$#seltrack - $nofghosts];
         my $trn = $tracklist[$gcn];
         $tracktag = $tracktags[$gcn];
         $riptrackname = get_trackname($gcn + 1, $trn);
         $riptrackno++;
      }
   }
   return($trackstart);
}
