#!/usr/bin/perl -w

#############################################################################
#                                                                           #
#   IMPORTANT NOTE                                                          #
#                                                                           #
#   !!! THE AUTHOR IS ==NOT== RESPONSIBLE FOR ANY USE OF THIS PROGRAM !!!   #
#                                                                           #
#   GPL LICENSE                                                             #
#                                                                           #
#   This program is free software; you can redistribute it and/or modify    #
#   it under the terms of the GNU General Public License as published by    #
#   the Free Software Foundation; either version 2 of the License, or       #
#   (at your option) any later version.                                     #
#                                                                           #
#   This program is distributed in the hope that it will be useful,         #
#   but WITHOUT ANY WARRANTY; without even the implied warranty of          #
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           #
#   GNU General Public License for more details.                            #
#                                                                           #
#   You should have received a copy of the GNU General Public License       #
#   along with this program; if not, write to the Free Software             #
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston,                   #
#   MA  02111-1307 USA                                                      #
#                                                                           #
#############################################################################

#
#   $Id: mp3roaster.pl,v 1.29 2004/05/18 18:27:12 eim Exp $
#
#   ON-THE-FLY-VIEW TODO LIST
#
#       . Move the @todo_array over to the RC file.
#       . Complete WAV file support (WAV file time?).
#       . Define and complete verbose/debug options mode.
#       . Complete all the little TODO signed comments in the code.
#       . Fix the clear line code (x75, see perldoc perldop).
#
#   Check also the file Docs/TODO for further TODO informations.
#   


#
#   CPAN LIBS
# 
use Getopt::Long;           # For the input options
use File::Basename;         # Get the basename of files
use File::Compare;          # Compare files
use File::Path;             # Get file path
use File::Copy;             # Copy files or filehandles
use File::MMagic;           # Guess file type
use Term::ReadKey;          # Module for simple terminal control
use MPEG::MP3Info;          # Allows to get MP3 infos
use Ogg::Vorbis::Header;    # Perl extension for Ogg Vorbis streams
use Audio::FLAC;            # Perl extension for FLAC files


#
#   LIBS CONFIGURATIONS
#
# Getopt::Long configuration
Getopt::Long::Configure("no_auto_abbrev", "no_ignorecase");


#
#   IMPLICATIONS
#
# Turn strict on (vars, refs, subs), this implicates strict declaration
# of all variables this way no dynamic variables will be allocated.
use strict;


#
#   USE GLOBAL VARS
#
# Jip, you see right: no global vars here, it's good code.


#
#   DECLARE GLOBAL VARS
#
my $version = "0.2.0";
my $appname = "mp3roaster";
my $realname = "MP3Roaster";
my $description = "A Perl hack for burning audio CDs out of MP3/OGG/FLACs";

# Declare configuration options
my $config_file_dir = "mp3roaster";
my $config_file_name = "mp3roasterrc";

# Declare Getopt::Long vars
my %opthash;


#
#   GET OPTIONS
#
GetOptions (

    \%opthash,
    
    'opt_cdr_dev|dev|D=s',
    'opt_cdr_speed|speed|s=i',
    'opt_cdr_dummy|dummy|d',
    'opt_cdr_dao|dao|a',
    'opt_temp_dir|temp|t=s',
    'opt_mp3_decoder|mp3dec|m=s',
    'opt_check_files|check|c',
    'opt_wav_normalize|normalize|n',

    'verbose|v',
    'help|h',
    'version|V'
);


#
#   FUNCTIONS
#

#
# PRINT_ERROR
#
# This function composes and returns an error message.
#
sub print_error {

    my ($in_sub, $in_r_message) = @_;

    if (defined $in_sub) {

        return " \! ERROR in sub $in_sub\n   $in_r_message\n\n";

    } else {

        return " \! ERROR: $in_r_message\n\n";
    }
}

#
# WHICH_CONFIG_FILE
#
# This function checks for a configuration file.
# If this file exists and is not world writable
# the function returns it's location path as string.
#
# If the configuration file is world writable we
# print out an error message and die.
# 
# If no configuration is available we return undef.
# 
sub which_config_file {

    my ($in_file_dir, $in_file_name) = @_;      # directory and file
    my $sub = "WHICH_CONFIG_FILE";              # subroutine name

    my $location;
    my @location_array = (

        "$ENV{'HOME'}/.$in_file_name",                  # HOME -> .FILE
        "$ENV{'HOME'}/.$in_file_dir/$in_file_name",     # HOME -> /.DIR -> FILE
        "/etc/$in_file_name",                           # /ETC -> FILE
        "/etc/$in_file_dir/$in_file_name"               # /ETC -> /DIR  -> FILE
    );

    foreach $location (@location_array) {

        # if file available
        if (-r $location) {

            # if config file is world writable
                if ((stat($location))[2] & 02) {
        
                        # print error and die
                        die print_error ($sub, "$location should not be world-writable");
            }

            return $location;
        }
    }
    
    return undef;
}

#
# READ_CONFIG_FILE_AND_OPTIONS
# 
# This function reads the configuration file
# and returns a HASH with all configurations.
# 
sub read_config_file_and_options {

    my ($in_config_file_location) = @_;         # config file path
    my $sub = "READ_CONFIG_FILE_AND_OPTIONS";   # subroutine name
    my $oldRS;                                  # file holder
    my $rc;                                     # file holder
    
    my $reference;
    my %confhash;
    my %refhash;

    # This variables are found in the configuration file and
    # need to be declared here in order to follow `use strict`
    my $config_cdr_dev;
    my $config_cdr_speed;
    my $config_cdr_dummy;
    my $config_cdr_dao;
    my $config_temp_dir;
    my $config_mp3_decoder;
    my $config_check_files;
    my $config_wav_normalize;

    # This options MUST be defined in the configuration file
    # or MUST be passed to the program as a command line option
    %refhash = (
        
        "config_cdr_dev"        => \$config_cdr_dev,
        "config_cdr_speed"      => \$config_cdr_speed,
        "config_cdr_dummy"      => \$config_cdr_dummy,
        "config_cdr_dao"        => \$config_cdr_dao,
        "config_temp_dir"       => \$config_temp_dir,
        "config_mp3_decoder"    => \$config_mp3_decoder,
        "config_check_files"    => \$config_check_files,
        "config_wav_normalize"  => \$config_wav_normalize
    );

    open('RC', $in_config_file_location);   # open file
    
        $oldRS = $/;
        undef $/;
        $rc = <RC>;
        
    close('RC');    # close file

    unless(defined eval $rc) {      # read data and check syntax

        die print_error($sub, "Found a syntax error in $in_config_file_location:\n $@");
    }

    $/ = $oldRS;
    
    #
    # Procedure...
    #
    #   1. Check if option was given
    #   2. Check if config var was given
    #   3. Error
    # 

    # cycle all config variable names
    foreach $reference (keys %refhash) {

        # $$foo holds the values from the config file
        my $foo = $refhash{$reference};
        # print "FOO: $$foo\n";
    
        # create option variable names
        my $reference2 = $reference;
        $reference2 =~ s/config/opt/i;

        # if option variable for `variable name` available
        if (defined $opthash{$reference2}) {

            $confhash{$reference} = $opthash{$reference2};

            if ($opthash{verbose}) {
                print " OPTS: $reference2 => $opthash{$reference2}\n";
            }

        # if config variable for `variable name` available
        } elsif (defined $$foo) {

            #
            # Return error if only blank spaces where defined for an option
            # in the configuration file, e.g.: $config_cdr_dev = " ", this is
            # different from $config_cdr_dev = "" which will be stooped later
            #
            # TODO  Add a regexp function which checks for one or more spaces
            #   or remove this option an let the functions check the values 
            #
            if ($$foo eq " ") {

                die print_error($sub, "Just a blank value is set for option \"\$$reference\" in the\n   configuration file, you need to fix your configuration file now\n   or provide a correct value via the respective command line option.");
        
            } else {

                $confhash{$reference} = $$foo;
            
                if ($opthash{verbose}) {
                    print " CONF: $reference => $confhash{$reference}\n";
                }   
            }
    
        # if variable was not defined anywhere
        } else {
            $reference =~ s/config_//i;
            die print_error($sub, "Option \"$reference\" was neither defined in the configuration file\n   and neither as an option, please define this value somewhere.");
        }
    };

    return %confhash;
}

#
# FETCH_FILE_ARRAY
#
# This function checks if the file specified via
# ARGV exists and is a valid MP3 or OGG file via
# the check_file_type function.
#
# If the file is valid it will be copied to the
# files_array which will be returned by this function.
#
# This function can only return a good value in all bad
# cases it calls the print_error function and dies.
#
sub fetch_file_array {

    my $sub = "FETCH_FILE_ARRAY";   # subroutine name
    my @file_array;                 # the file array
    my $i = 0;                      # a simple counter

    foreach my $file (@ARGV) {

        if (-r $file) {
            
            if (defined check_file_type($file)) {

                $file_array[$i] = $file;
                $i++;
            
            } else {
                die print_error ($sub, "The file \"$file\" is not a valid MP3, OGG or FLAC file.");
            }

        } else {
                die print_error ($sub, "The file \"$file\" does not exist.");
        }
    }

    return @file_array;
}

#
# FETCH_ATIP_TIME
# 
# This function fetches the ATIP of the CD/RW Disc.
# We use the cdrecord application to get this infos.
#
# We return the ATIP time in seconds.
#
sub fetch_atip_time {

    my ($config_cdr_dev) = @_;
    my $sub = "FETCH_ATIP_TIME";
    my $atip_sec;
    my $atip_min;
    my $atip_time;

    if (!$config_cdr_dev) {
    
        my $r_message = "You must specify the CDR/W drive id in the configuration file or with\n   the option --drive x.y.z. Execute: % cdrecord \-scanbus for further details.";
        die print_error($sub, $r_message);
    }

    # open file and use "cdrecord -atip" to fetch ATIP info
    open(CDINFO,"cdrecord -atip dev=".$config_cdr_dev." 2>&1 |");

        while (<CDINFO>) {
        
            # fetch until timeout
            next unless (/out:.+\((\d+):(\d+)/);
            
            # get the time vars
            $atip_min = $1;
            $atip_sec = $2;
        }
        
    close CDINFO;

    # die if no CDR in Drive
    unless ($atip_sec && $atip_min) {

        my $r_message = "No CD-R/W in the CDR/W writer.";
        die print_error ($sub, $r_message);
    }

    #
    # Print out the ATIP time
    # TODO : This should be printed out in verbose mode.
    # 
    # printf " * %d:%.2d minutes available on CDR/W.\n",$atip_min,$atip_sec;

    # compose ATIP time in secs
    $atip_time = ($atip_min * 60) + $atip_sec;

    return $atip_time;
}

#
# CHECK_FILE_TYPE
# 
# This function returns the extension of a file
# or undef if the file is not valid.
#
sub check_file_type {
    
    my ($file) = @_;
    my $sub = "CHECK_FILE_TYPE";

    my $ext;

    my $mm = new File::MMagic;
    my $fh = new FileHandle "< $file";
    my $res = $mm->checktype_filehandle($fh);

    #
    # Secrets of File::MMagic, this works only on Debian...
    #
    # if ($res eq "application/octet-stream"), we need to add
    # if ($res eq "audio/mpeg"), for Red Hat and maybe also some other distro.
    #
    if ($res eq "application/octet-stream" || "audio/x-wav" || "audio/mpeg") {

        $ext = (split(/\./,$file))[-1];
    
        if ($ext eq "wav"||"mp3"||"ogg"||"flac") {

            return $ext;
        }
    }

    return undef;
}

#
# CHECK_FILE
#
# This function checks the filename of each $file
# in the @file_array using a specified syntax command,
# this command is passed to the functions as parameter.
#
sub check_file {

    my ($todo, @file_array) = @_;
    my $sub = "CHECK_FILE";         # subroutine name

    # Browse through the MP3 Array
    foreach my $file (@file_array) {

        # Make copy of actual file
        my $file_tmp = $file;

        # unofficial Perl case
        for ($todo) {

            # Check what is todo
            if (/up2lowercase/) {
        
                # Lowercase in $file_tmp
                $file_tmp =~ tr/A-Z/a-z/;
        
            } elsif (/rmspaces/) {
        
                # Remove spaces in $file_tmp
                $file_tmp =~ tr/" "/"_"/;

            } elsif (/rmbrackets/) {
        
                        # Remove brackets in $file_tmp
                        $file_tmp =~ tr/"("/"_"/;
                $file_tmp =~ tr/")"/"_"/;

            } elsif (/rmquotes/) {
        
                        # Remove quotes in $file_tmp
                        $file_tmp =~ tr/"'"/"_"/;
                        $file_tmp =~ tr/"""/"_"/;

            } elsif (/rmquestionmarks/) {
        
                        # Remove quotes in $file_tmp
                        $file_tmp =~ tr/"?"/"_"/;
            }
        }

        # If original MP3 name is different from lowercased copy
        if ($file_tmp ne $file) {

            # Check if there's already a file with the lowercased name
            if (-e $file_tmp) {
        
                # If there's already a file which
                # is the original, the same, rename it.
                if (compare ("$file", "$file_tmp") == 0) {
            
                    # Rename the file
                    rename ("$file" , "$file_tmp");

                    # Update the @file_array
                    $file = $file_tmp;

                # The File is not the same, big problem !
                # die with error message.
                } else {

                    #
                    # TODO
                    # Solve this extreme error case.
                    #
                    
                    print "\n";
                    print "Can't Rename $file to $todo the file\n";
                    print "there is already a $file_tmp named file\n";
                    print "which is not equal\n";
                    print "\n";

                    exit 1;
                }
    
            # If there's no file with the lowercased name
            } else {
            
                # Rename the file
                rename ("$file" , "$file_tmp");

                # Update the @file_array
                            $file = $file_tmp;
            }
        }
    }

    print "$todo ";
    return @file_array;
}

#
# FETCH_FILE_TIME
# 
# This is a generic function which calculates the
# total time of all MP3/OGG/FLAC files in the @file_array.
# 
# The function calculates also the total fudge factor time
# which is the sum of all the seconds between two audio tracks,
# I've set it's value to 4 seconds.
#
# We have three dependences for this function:
#
#   DEPENDENCY MODULE       DEBIAN PACKAGE
#   -------------------------------------------------
#   MPEG::MP3Info           libmp3-info-perl
#   Ogg::Vorbis::Header     libogg-vorbis-header-perl
#   Audio::FLAC             libaudio-flac-perl (note)
#
#   libaudio-flac-perl note:
#
#       This package is currently not available on Debian GNU/Linux
#       unstable, eim has packaged it and will upload it to the
#       Mentors Debian Net server.
#
# That's all.
#
sub fetch_file_time {

    my (@file_array) = @_;
    my $sub = "FETCH_FILE_TIME";    # subroutine name

    my $mp3info;                    # get_mp3info() return
    my $ogg;                        # holds the Ogg::Vorbis::Header object
    my $oggtime;                    # holds the ogg's length in seconds
    my $flac;                       # holds the Audio::FLAC object
    my $flactime;                   # holds the flac's length in seconds

    my $tot_secs;                   # sum of all file plus fudge seconds
    
    my $tot_min;                    # calculated file length in minutes
    my $tot_sec;                    # calculated file length in seconds
    
    my $fudge = "4";                # fudge time value (preference)
    my $tot_fudge_secs;             # sum of all fudge seconds
    
    my $tot_fudge_min;              # calculated fudge length in minutes
    my $tot_fudge_sec;              # calculated fudge length in seconds

    # Cycle the file array
    foreach my $file (@file_array) {
        
        # if we treat a WAV file
        if (check_file_type($file) eq "wav") {

            #
            # TODO
            #
            # Add some way to check how long, in seconds,
            # a WAV file is -- I have, actually, no fsck'n
            # idea how to do this, suggestions are welcome.
            # maybe libaudio-wav-perl can be useful.
            #
            print "\n\tFIXME: Time of WAV file $file will not be counted.\n\tWe need a way to fetch the play length time of WAV files.\n\n";

        # if we treat a MP3 file
        } elsif (check_file_type($file) eq "mp3") {

            # Fetch informations about the MP3 file via libmp3-info-perl.
            $mp3info = get_mp3info($file);
        
            # Sum total seconds plus fudge factor
            $tot_secs += ($mp3info->{MM}*60) + $mp3info->{SS} + $fudge;
            
        # if we treat a OGG file
        } elsif (check_file_type($file) eq "ogg") {

            #
            #   We use Ogg::Vorbis::Header here
            #   Example: http://search.cpan.org/src/FOOF/libvorbis-perl-0.02/test.pl
            #
            my $ogg = Ogg::Vorbis::Header->load($file);
            $oggtime = $ogg->info("length");
            
            $tot_secs += $oggtime + $fudge;

        # if we treat a FLAC file
        } elsif (check_file_type($file) eq "flac") {

            #
            #   We use Audio::FLAC here
            #
            my $flac = Audio::FLAC->new($file);
            
            $flactime = $flac->info("TOTALSAMPLES") / $flac->info("SAMPLERATE");

            $tot_secs += $flactime + $fudge;
        }

        # Calculating the total fudge factor time
        $tot_fudge_secs += ($fudge);
    }

    # Calculating $tot_min and $tot_sec
    $tot_min = int $tot_secs/60;
    $tot_sec = $tot_secs % 60;

    # Calculating $tot_fudge_min and $tot_fuge_sec
    $tot_fudge_min = int $tot_fudge_secs/60;
    $tot_fudge_sec = $tot_fudge_secs % 60;

    # Compose the MP3 time in secs
    # $file_time = $tot_secs;

    # TODO
    # This should become a verbose option
    # Print out the file time
    # printf " * %d:%.2d \(+ %d:%.2d fudge\) minutes of audio.\n", $tot_min, $tot_sec, $tot_fudge_min, $tot_fudge_sec;

    return $tot_secs;
}

#
# COMPARE_FILE_ATIP
#
# This function compares the total time of the MP3/OGG/FLAC files
# with the ATIP time and warns you the songs won't fit on CDR/W.
#
sub compare_file_atip {

    my ($in_atip_time, $in_file_time) = @_;
    my $key;
    my @fifo;

    my $in_atip_min = int $in_atip_time/60;
    my $in_file_min = int $in_file_time/60;

    # if file time is shorter than ATIP time
    if ($in_file_time <= $in_atip_time) {
    
        print " * The audio files fit on CD\/RW\ (CD: $in_atip_min min / File: $in_file_min min\)\n";
    
    } else {
    
        print " * WARNING: The files won\'t fit on CD\/RW \(CD: $in_atip_min min / File: $in_file_min min\)\n";
    }

    print "   Do you want to continue? ([Any Key/n]) ";

    open(TTY,"</dev/tty");
    ReadMode "raw";
    $key = ReadKey 0, *TTY;
    ReadMode "normal";

    print "\n";
    (print "   Well then, bye bye human -- See ya.\n") && (exit 0) if ($key eq ('n'||'N'));
}

#
# CHECK_TEMP
# 
# This function checks if the temp directory exists,
# else it will be created with appropriate permissions.
#
# If the temp directory already exists we will return
# 1 else we create it but return 0 in order to alert
# the code segment which recalls this function.
#
# If $config_temp_dir is a file we can't create the
# temp directory so we print an error and die.
#
sub check_temp {

    my ($config_temp_dir) = @_;
    my $sub = "CHECK_TEMP";

    if (-x "$config_temp_dir") {    # if it's a directory and exists: OK

        return 1;

    } else {

        if (-f "$config_temp_dir") {    # if it's a file: fatal error, die
        
            die print_error($sub, "$config_temp_dir is a file, please remove it first, then launch $appname again.");

        } else {    # if it's not available: create it

            mkpath($config_temp_dir, 0, 0755);
        }

        return 0;
    }
}

#
# CHECK_FOR_WAV_FILE
#
# With this function we check if a wav file exists for a
# given MP3/OGG or whatever file we are going to decode.
#
# If true ask user what todo: Use the present decoded WAV
# file or re decode the compressed file (The last option is
# safe cause WAV file could eventually not be complete)
#
# Return 0 if user wants to decode again, else 1.
#
sub check_for_wav_file {

    my ($i, $file, $wavpath) = @_;
    my $key;

    if (-f $wavpath) {

        print "\r                                                                                ";
        print "\r   File $file already decoded, decode again? ([Any Key/n]) ";

        open(TTY,"</dev/tty");
        ReadMode "raw";
        $key = ReadKey 0, *TTY;
        ReadMode "normal";

        if ($key eq ('n'||'N')) {
        
            return 1;
        
                } else {
        
            print "\r                                                                                ";
            printf "\r   file %d: $file", $i+1;
        
            # Remove the file in order to be re decoded
            unlink($wavpath);
            return 0;
        }
    }
}

#
# FILE_DECODE
#
# This function decodes all the MP3/OGG/FLAC files to wav files,
# we also create the wav array and return it if all decoding
# processes were executed clean, else we print out an error
# message and die.
#
sub file_decode {

    my ($config_wav_normalize, $config_temp_dir, $config_mp3_decoder, @file_array) = @_;
    my $sub = "FILE_DECODE";
    my $wavpath;
    my $i = 0;
    my @wav_array;
    my $cmd;
    my $rc;


    foreach my $file (@file_array) {

        $wavpath = (basename $file);
        
        #
        # TODO
        # clear the line with 80 chars, this could be done much better (x75, see perldoc perldop)
        #
        print "\r                                                                                ";
        printf "\r   file %d: $wavpath", $i+1;

        if (check_file_type($file) eq "wav") {
           
            if ($config_wav_normalize) {

                #
                # In this case we need to copy the WAV file from it's
                # current location to the temp dir in order to be able
                # to run normalize on it.
                #
                # TODO  Add some control routine to check if copy was successful.
                #
                printf "\r   file %d: $wavpath", $i+1;
                copy ($wavpath, $config_temp_dir."/".$wavpath);
                $wav_array[$i] = $config_temp_dir."/".$wavpath;
                $i++;
            
            } else {
                
                #
                # Leave WAV file where it is and just update the WAV array.
                #
                $wav_array[$i] = $wavpath;
                $i++;
            }
        
        } elsif (check_file_type($file) eq "mp3") {
        
            $wavpath =~ s/mp3/wav/i;
            $wavpath = $config_temp_dir."/".$wavpath;
            
            if (!check_for_wav_file($i, $file, $wavpath)) {
    
                # unofficial Perl case
                for ($config_mp3_decoder) {

                    # same syntax
                    if (/mpg321/ || /mpg123/) {
                    
                        $cmd = "$config_mp3_decoder --rate 44100 --stereo -w \"$wavpath\" \"$file\" 2>&-";

                        #
                        # TODO  Finish to implement toolame decoder support.
                        #       toolame is available on Debian GNU/Linux and is free.
                        #       lame is not free instead so toolame is the case (perhaps).
                        #
                        # } elsif (/toolame/) {
                        #
                        #   $cmd = "toolame --decode $file $wavpath";
                        #

                    } else {

                        die print_error($sub, "Configure a valid mp3 decoder.")
                    }
                }
            
                $rc = system($cmd);

                if (!$rc) {

                    $wav_array[$i] = $wavpath;
                    $i++;

                } else {

                    die print_error($sub, "$file was not correctly decoded,\n   $config_mp3_decoder error code $rc.");
                }

            } else {

                $wav_array[$i] = $wavpath;
                $i++;
            }
        
        } elsif (check_file_type($file) eq "ogg") {

            $wavpath =~ s/ogg/wav/i;
            $wavpath = $config_temp_dir."/".$wavpath;
    
            if (!check_for_wav_file($i, $file, $wavpath)) {
    
                #
                #   system and `` return to the perl code, exec not
                #   system returns the exit code `` not.
                #
                #   ogg123 exits with error code 256 if the destination
                #   file already exists, mpg321 does not.
                #
                #   Here we can also use oggdec which is available, as ogg123,
                #   in the Debian vorbis-tools package. Which one is better?
                #
                #       % oggdec foo.wav will decode to foo.wav
                #
                $cmd = "ogg123 -d wav -f \"$wavpath\" \"$file\" 2>&-";
                $rc = system($cmd);
            
                if (!$rc) {

                    $wav_array[$i] = $wavpath;
                    $i++;

                } else {
    
                    die print_error($sub, "$file was not correctly decoded to $wavpath\n   ogg123 error code $rc, try to remove $wavpath if exists.");
                }

            } else {

                $wav_array[$i] = $wavpath;
                $i++;
            }

        } elsif (check_file_type($file) eq "flac") {

            $wavpath =~ s/flac/wav/i;
            $wavpath = $config_temp_dir."/".$wavpath;
    
            if (!check_for_wav_file($i, $file, $wavpath)) {
    
                #
                #   system and `` return to the perl code, exec not
                #   system returns the exit code `` not.
                #
                $cmd = "flac -d  -s \"$file\" -o \"$wavpath\" 2>&-";
                $rc = system($cmd);
            
                if (!$rc) {

                    $wav_array[$i] = $wavpath;
                    $i++;

                } else {
    
                    die print_error($sub, "$file was not correctly decoded to $wavpath\n   flac error code $rc, try to remove $wavpath if exists.");
                }

            } else {
            
                $wav_array[$i] = $wavpath;
                $i++;
            }
        }
    }
    
    return @wav_array;
}

#
# WAV_NORMALIZE
# 
# This function recalls normalize with the -m (mix)
# option in order to standardize the max volume level
# for all wav files in the temp directory, if execution
# is successfully we'll go back to main without any return
# value else we die with an error message.
#
sub wav_normalize {

    my ($config_temp_dir) = @_;
    my $sub = "WAV_NORMALIZE";
    my $cmd;
    my $rc;

    $cmd = "normalize -m $config_temp_dir/*.wav 2>&-";
    $rc = system($cmd);
            
    if ($rc) {

        die print_error($sub, "the normalizing process was not successful,\n   normalize error code $rc.");
    }
}

#
# BURN
# 
# This function burns all wav files to a CD/RW disc
# using the cdrecord application, if cdrecord returns
# an error code we print out an error message and die,
# else we go back to main without any return value.
# 
sub burn {

    my ($config_cdr_dev, $config_cdr_speed, $config_cdr_dummy, $config_cdr_dao, @wav_array) = @_;
    my $sub = "BURN";
    my $burn_wav_list;
    my $cdrecord_options;
    my $cmd;
    my $rc;

    foreach my $wav (@wav_array) {

        $burn_wav_list = $burn_wav_list." \"$wav\"";
    }

    $cdrecord_options = "dev=$config_cdr_dev speed=$config_cdr_speed gracetime=2 -eject -pad -audio -silent";

    if ($config_cdr_dummy) {
    
        $cdrecord_options = $cdrecord_options." -dummy";
    }

    if ($config_cdr_dao) {
    
        $cdrecord_options = $cdrecord_options." -dao";
    }

    # Let's Burn Baby, Burn !
    $cmd = "cdrecord $cdrecord_options $burn_wav_list >/dev/null 2>&1";
    $rc = system($cmd);

    if ($rc) {

        die print_error($sub, "the burn process was not successful,\n   cdrecord error code $rc.");
    }
}

#
# FLUSH_TEMP
# 
# This Function removes all wav files of the WAV array
# from the temporary directory and returns the summarized
# number of all removed files. The temporary directory
# itself won't be remove because user could have set it
# to something like / which is anyway quite lame.
#
sub flush_temp {

    my (@wav_array) = @_;
    my $i = 0;

    foreach my $wav (@wav_array) {

        unlink($wav);
        $i++;
    }

    return $i;
}


#
#   MAIN
#
my $config_file_location;
my %confhash;
my @file_array;
my @wav_array;
my $todo;

#
# TODO
#
# This should be definable in the RC file
#
my @todo_array = (

    "up2lowercase",
    "rmspaces",
    "rmbrackets",
    "rmquotes",
    "rmquestionmarks"
);

if (@ARGV) {

    print "\n";
    print "===========================================================================\n";
    print "$realname - $version - $description\n";
    print "===========================================================================\n\n";

    #
    # 1: check for configuration file
    #
    if (defined ($config_file_location = which_config_file($config_file_dir, $config_file_name))) {
        
        #
        # 2: read configuration file and put all vars in a hash
        #
        print " * Options [ ";
        %confhash = read_config_file_and_options($config_file_location);
        
        # print "dev ($confhash{config_cdr_dev}) ";
        print "$confhash{config_cdr_speed}x ";
        if ($confhash{config_cdr_dummy}) { print "dummy "; }
        if ($confhash{config_cdr_dao}) { print "dao "; }
        if ($confhash{config_check_files}) { print "filecheck "; }
        if ($confhash{config_wav_normalize}) { print "normalize "; }
        
        print "]\n";

        #
        # 3: create the file array
        #
        @file_array = fetch_file_array;
        
        print " * Processing ";
        
        if (($#file_array) > 0) {
            
            printf "%d files.\n", $#file_array+1;
        
        } else {
            
            print "1 file.\n";
        }

        #
        # 4: check files (optional)
        #
        if ($confhash{config_check_files}) {
            
            print " * Checking file [ ";
            
            foreach $todo (@todo_array) {
                
                @file_array = check_file($todo, @file_array);
            }
            
            print "]\n";
        }

        #       
        # 5: check if files fit on CD/RW
        #
        compare_file_atip(fetch_atip_time($confhash{config_cdr_dev}), fetch_file_time(@file_array));
        
        #
        # 6: check the temp directory
        #
        if (check_temp($confhash{config_temp_dir})) {
            
            print " * Temporary directory available.\n";
        
        } else {
            
            print " * Temporary directory created.\n";
        }

        #   
        # 7: decode MP3/OGG/FLAC files and get the wave array
        #
        print " * Decoding MP3/OGG/FLAC files to wav...\n";

        @wav_array = file_decode($confhash{config_wav_normalize}, $confhash{config_temp_dir}, $confhash{config_mp3_decoder}, @file_array);
        
        #
        # TODO
        # clear the line with 80 chars, this could be done much better (x75, see perldoc perldop)
        #
        print "\r                                                                                ";
        
        if (($#wav_array) > 0) {
            
            printf "\r   done, %d files successfully decoded.\n", $#wav_array+1;

        } else {

            print  "\r   done, 1 file successfully decoded.\n";
        }

        #
        # 8: normalize all the WAV files
        #
        if ($confhash{config_wav_normalize}) {
            
            print " * Normalizing wav files, please wait... ";
            
            wav_normalize($confhash{config_temp_dir});

            print "done.\n";
        }

        #
        # 9: now let's BURN
        #
        # TODO  Debug this printf..
        #
        # printf " * Burning %d files, please wait... ", ($#wav_array+1);
        # 
        print " * Burning files, please wait... ";
        burn($confhash{config_cdr_dev}, $confhash{config_cdr_speed}, $confhash{config_cdr_dummy}, $confhash{config_cdr_dao}, @wav_array);
        print "done.\n";

        #
        # 10: flush all wav files in temporary directory
        #
        my $f = flush_temp(@wav_array);
        if ($f > 1) {

            printf " * Removed %d wav files.\n\n", $f;

        } else {
            
            print " * Removed 1 wav file.\n\n";
        }

        exit 0;

    } else {
        
        die print_error(undef, "MP3Roaster configuration file is not available.");
    }


# if version option
} elsif ($opthash{version}) {

print <<EOT;
$appname $version

Written by Ivo Marino <eim\@users.sourceforge.net>
with lots of help from Lorenzo Prince <lorenzo1\@users.sourceforge.net>.

Copyright (C) 2002-2004 Ivo Marino <eim\@users.sourceforge.net>
This is free software; see the source for copying conditions. There is
NO warranty; the author is NOT RESPONSIBLE for any use of this program.
EOT

exit 0;


# if no args or help option
} elsif ($#ARGV < 1 || $opthash{help}) {

    print <<EOT;
$description.

Usage: mp3roaster [OPTION]... \"MP3/OGG/FLAC files\"

Options:
  -D, --dev             CDR device to use
  -s, --speed           Burn speed
  -d, --dummy           Burn with laser off
  -a, --dao             Burn in disk-at-once (DAO) mode
  -t, --temp            Temporary directory
  -m, --mp3dec          MP3 decoder to use
  -c, --check           Check file before burning
  -n, --normalize       Normalize WAV files before burning

  -v, --verbose         Enable verbose output
  -h, --help            Show this help screen
  -V, --version         Show version and infos

Examples:
  \% mp3roaster \"foo.mp3 bar.ogg blech.flac\"

Report bugs to <eim\@users.sourceforge.net>
EOT
    exit 0;
}

__END__

=head1 NAME

mp3roaster - A Perl hack for burning audio CDs out of MP3/OGG/FLACs

=head1 SYNOPSIS

B<mp3roaster> [OPTION]... C<MP3/OGG/FLAC files>

=head1 DESCRIPTION

B<mp3roaster>
A Perl hack for burning audio CDs out of MP3, OGG VORBIS and FLAC files.
The main highlights of this application are an easy to use command line syntax
and automatic volume leveling support for best audio CD quality.

=head1 ENVIRONMENT

Debian GNU/Linux. Red Hat Linux.

=head1 OPTIONS

All options have been imported, now we should add specific descriptions
for each option.

=over 4

=item B<-d, --dev>

CDR device to use

=item B<-s, --speed>

Burn speed

=item B<-d, --dummy>

Burn with laser off

=item B<-a, --dao>

Burn in disk-at-once (DAO) mode

=item B<-t, --temp>

Temporary directory

=item B<-m, --mp3dec>

MP3 decoder to use

=item B<-c, --check>

Check file before burning

=item B<-n, --normalize>

Normalize WAV files before burning

=item B<-v, --verbose>

Enable verbose output

=item B<-h, --help>

Show the help screen

=item B<-V, --version>

Show version and infos

=back

=head1 RETURN VALUE

B<mp3roaster> returns 0 on success, 1 on error.

=head1 DIAGNOSTICS

This has to be written yet.

=head1 EXAMPLES

This has to be written yet.
Import examples from Docs/README

=head1 FILES

This has to be written yet.
Import files from Docs/README

=head1 CAVEHEATS

This has to be written yet.

=head1 BUGS

This has to be written yet.

=head1 NOTES

This has to be written yet.

=head1 SEE ALSO

This has to be written yet.

=head1 AUTHOR

=over 4

=item Ivo Marino <eim@users.sourceforge.net>

=item Lorenzo Prince <lorenzo1@users.sourceforge.net>

=back

=head1 HISTORY

This has to be written yet.

=cut

# vim: ts=8:sw=4:sts=4:et
