#!/usr/bin/perl
#
#  Perl Audio Converter
#
#  Copyright (C) 2005-2009 Philip Lyons
#
#    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 3 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, see <http://www.gnu.org/licenses/>.

use strict;
use warnings;
use Switch;
use Getopt::Long;
use File::Basename;
use File::Find;
use File::Spec::Functions qw(rel2abs);

# Tagging modules
use Ogg::Vorbis::Header;
use MP3::Tag;
use Audio::FLAC::Header;
use MP4::Info;
use Audio::WMA;
use Audio::Musepack;
use Audio::APETags;

# CDDB module
use CDDB_get qw(get_cddb);

# non-encoder/decoder related options
my 
(
   $to,
   $recursive,
   $preserve,
   $rip,
   $help,
   $longhelp,
   $verbose,
   $verinfo,
   $dryrun,
   $delete,
   $keep,
   $overwrite,
   $formats,
   $topdir,
   $only,
   $first_run,

   @file,
   @dir,

   %run,
   %lang,
);

my $silent = "> /dev/null 2>&1";

# tagging options
my
(
   $title,
   $track,
   $artist,
   $album,
   $comment,
   $year,
   $genre,
   $taginfo,
);

# name/version information
my $name = "Perl Audio Converter";
my $version = "4.0.4";

# If this option is set, messages/errors will be displayed via kdialog
my $gui = '';

# Debugging (for internal use only)
my $debug = 0;

# default configuration.
my %config = (

               IMPORTM      => 0,

               DEFOPTS      => 1,
               EOPTS        => '',
               DOPTS        => '',
               NOPTS        => '',

               BITRATE      => 128,
               FREQ         => 44100,
               CHANNELS     => 2,
               EFFECT       => '',
               FCOMP        => 2,
               PCOMP        => 3,
               ACOMP        => 3000,
               OGGQUAL      => 3,
               SPXQUAL      => 8,
               AACQUAL      => 3,
               MPCQUAL      => 'radio',
               OFMODE       => 'normal',
               OFOPT        => 'fast',
               BRATIO       => 2,
               BQUANL       => 1.0,
               BPSIZE       => 128,

               USE_CDDB     => 1,
               CDDB_HOST    => 'freedb.freedb.org',
               CDDB_PORT    => 8880,
               CDDB_MODE    => 'cddb',
               CDDB_INPUT   => 1,
               DEVICE       => '/dev/cdrom',
               NSCHEME      => '%ar - %ti',

               KDE_DIR      => 1,
               KDE_OPTS     => 1,

             );

# location of configuration file
my $conf_path = "/etc/pacpl";
my $po_dir    = "/usr/local/share/pacpl/locale";
my $mod_dir   = "$conf_path/modules";

my $conf_file;

# load the appropriate locale file
load_lang();

# try to load configuration file in this order...
my @conf_locations = ( 
                       "$ENV{HOME}/.pacplrc",   # Local
                       "$conf_path/pacpl.conf", # Global
                       "$ENV{PWD}/pacpl.conf",  # Current Directory
                     );

# try to load conf file from one of the above locations
foreach my $i (@conf_locations) {
    if (not $conf_file and -e $i) {
        $conf_file = $i;
        print "$lang{loaded_config} $conf_file\n" if $debug;
    }
}

print "$lang{no_config}\n" if not $conf_file;

# open config file.
if ($conf_file and -e $conf_file) {

    open(CONF, "$conf_file") or die "$lang{opening_file} $conf_file - $!\n";

    my @conf_opts = <CONF>;
       @conf_opts = grep(!/^#|^$/, @conf_opts);

    close(CONF);

    # now read config file options and change default values accordingly.
    foreach (@conf_opts) {
        chomp;
        my ($k, $v) = split(/\s+=\s+/);
            $config{$k} = $v if exists($config{$k}) and $v;
            print "$lang{debug} $k = $v\n" if $debug and exists($config{$k});
    }
}

my $codecs_conf = "$conf_path/codecs.conf";

# set default encoders/decoders
sub load_codecs {

    open(CODECS, "$codecs_conf") or die "$lang{opening_file} $codecs_conf - $!\n";
        
    my @codecs = <CODECS>;
       @codecs = grep(!/^$|^#/, @codecs);
       
    close(CODECS);
        
       foreach (@codecs) {
        
            chomp;
            my ($k, $v) = split(/\s+=\s+/);
            my ($e, $d) = split(/,/, $v);
            
                $k =~ tr/A-Z/a-z/;
            
                $run{$k}{DEFAULT_ENCODER} = $e if (exists($run{$k}{DEFAULT_ENCODER}));
                $run{$k}{DEFAULT_DECODER} = $d if (exists($run{$k}{DEFAULT_DECODER}));
       }
}

# load po file and store in %lang hash
sub load_lang {

    my $po = "$po_dir/$ENV{LANG}.po";
       $po =~ s/\.UTF-8//i      if $po =~ /UTF-8/i; 
       $po =~ s/\.utf8//i       if $po =~ /utf8/i;  
       $po =~ s/_\w+//          if not -e $po;      
       $po = "$po_dir/en_US.po" if not -e $po;      
       
    open(LANG, "< $po") or die "error: could not open - $po\n$!\n";
    
    my @data = <LANG>;
       @data = grep(!/^#|^$/, @data);
       
    close(LANG);
    
    foreach (@data) {
         chomp;    
         s/=/__EQ__/;
         my ($k, $v) = split(/\s+__EQ__\s+/, $_);
         $lang{$k} = $v;
    }
    
}
    
my $banner = 1;

# print error messages
sub perror {
    my ($key, $msg) = @_;
    print "$name - $version\n\n";
    print "$lang{error}: $lang{$key} $msg" . "\n\n";
    system("kdialog --title \"$name\" --error \"$lang{error}: $lang{$key} $msg\" &") if $gui;
    die "$lang{seek_help}\n";
}

# print notices/warnings
sub pnotice {

    my ($key, $msg, $nm) = @_;

    print "$name - $version\n\n" if $banner == 1;
    print "$lang{$key} $msg";
    print "\n"    if $nm == 1;
    print "\n\n"  if $nm == 2;
      
    system("kdialog --title \"$name\" --passivepopup \"$lang{$key} $msg\" 10 &") if $gui;

    $banner = 0;
}

# defaults for user variables.
my $outfile;
my $outdir;
my $defopts   = $config{DEFOPTS};
my $eopts     = $config{EOPTS};
my $dopts     = $config{DOPTS};
my $nopts     = $config{NOPTS};
my $normalize = $config{NOPTS};
my $bitrate   = $config{BITRATE};
my $freq      = $config{FREQ};
my $channels  = $config{CHANNELS};
my $effect    = $config{EFFECT};
my $fcomp     = $config{FCOMP};
my $pcomp     = $config{PCOMP};
my $acomp     = $config{ACOMP};
my $oggqual   = $config{OGGQUAL};
my $spxqual   = $config{SPXQUAL};
my $aacqual   = $config{AACQUAL};
my $mpcqual   = $config{MPCQUAL};
my $ofmode    = $config{OFMODE};
my $ofopt     = $config{OFOPT};  
my $bratio    = $config{BRATIO};
my $bquanl    = $config{BQUANL};
my $bpsize    = $config{BPSIZE};

my $cdinfo;
my $nocddb;
my $noinput;
my $device  = $config{DEVICE};
my $nscheme = $config{NSCHEME};
my $my_encoder;
my $my_decoder;
my $encoder;
my $decoder;


# command line options
GetOptions
( 
	't|to=s'      => \$to,
	'r|recursive' => \$recursive,
	'p|preserve'  => \$preserve,
	'h|help'      => \$help,
	'l|longhelp'  => \$longhelp,
	'v|verbose'   => \$verbose,
	'f|formats'   => \$formats,
	'o|only=s'    => \$only,
	'k|keep'      => \$keep,

	'taginfo'     => \$taginfo,

	'version'     => \$verinfo,
	'dryrun'      => \$dryrun,
	'delete'      => \$delete,
	'overwrite'   => \$overwrite,
	'normalize'   => \$normalize,
	'encoder=s'   => \$my_encoder,
	'decoder=s'   => \$my_decoder,
	
	'title=s'     => \$title,
	'track=n'     => \$track,
	'artist=s'    => \$artist,
	'album=s'     => \$album,
	'comment=s'   => \$comment,
	'year=n'      => \$year,
	'genre=s'     => \$genre,
	
	'gui'         => \$gui,

	'eopts=s'     => \$eopts,
	'dopts=s'     => \$dopts,
	'defopts=n'   => \$defopts,
	'nopts=s'     => \$nopts,
	'outfile=s'   => \$outfile,
	'outdir=s'    => \$outdir,

	'bitrate=n'   => \$bitrate,
	'freq=n'      => \$freq,
	'channels=n'  => \$channels,
	'effect=s'    => \$effect,
	'fcomp=n'     => \$fcomp,
	'acomp=n'     => \$acomp,
	'pcomp=n'     => \$pcomp,
	'oggqual=n'   => \$oggqual,
	'spxqual=n'   => \$spxqual,
	'aacqual=n'   => \$aacqual,
	'mpcqual=s'   => \$mpcqual,
	'ofmode=s'    => \$ofmode,
	'ofopt=s'     => \$ofopt,
	'bratio=n'    => \$bratio,
	'bquanl=n'    => \$bquanl,
	'bpsize=n'    => \$bpsize,
	
        'rip=s'       => \$rip,
	'nocddb'      => \$nocddb,
	'noinput'     => \$noinput,
	'device=s'    => \$device,
	'nscheme=s'   => \$nscheme,
	'cdinfo'      => \$cdinfo,
);

$silent = '' if $verbose;
my $opts;

# conversion options.  when adding a new format, 
# wild card %i = input file and %o = output file
%run = (

        raw => {

                DEFAULT_ENCODER => "sox",
                DEFAULT_DECODER => "sox",
                
                ENCODER => {
                              sox => {
                                      NAME => "sox",
                                      ESTR => sub { 
                                                    $opts = "-r $freq -c $channels" if $defopts == 1;
                                                    $opts = '' if $defopts == 0;
                                                    "%i $opts $eopts %o $effect" 
                                                  },
                                    
                                      PROMPT => {
                                                  FREQ     => 1,
                                                  CHANNELS => 1,
                                                },
                                     },
                           },
                           
                DECODER => {
                              sox => {
                                        NAME => "sox",
                                        DSTR => sub { "-w -s -r $freq -c $channels $dopts %i %o" },
                                     },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

        ram => {

                DEFAULT_ENCODER => undef,
                DEFAULT_DECODER => "ffmpeg",
                
                DECODER => {
                              ffmpeg => {
                                          NAME => "ffmpeg",
                                          DSTR => sub { "$dopts -y -i %i %o" },
                                          PROMPT => { NONE => 0 },
                                        },
                           },
                            
                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

        ac3 => {

                DEFAULT_ENCODER => "ffmpeg",
                DEFAULT_DECODER => "mplayer",
                
                ENCODER => {
                              ffmpeg => {
                                          NAME => "ffmpeg",
                                          ESTR => sub { 
                                                        $opts = "-ab $bitrate -ar $freq -ac $channels" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "$eopts -y -i %i $opts %o"
                                                      },
                                          
                                          PROMPT => {
                                                      BITRATE  => 1,
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },
                           },
                           
                DECODER => {
                             mplayer => {
                                          NAME => "mplayer",
                                          DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                        },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },
               
         rm => {
        
                 DEFAULT_ENCODER => 'ffmpeg',
                 DEFAULT_DECODER => 'mplayer',
                 
                 ENCODER => {
                              ffmpeg => {
                                          NAME => 'ffmpeg',
                                          ESTR => sub { 
                                                        $opts = "-ar $freq -ac $channels" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "$eopts -y -i %i $opts %o" 
                                                      },
                                          
                                          PROMPT => {
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },
                           },
                           
                DECODER => {
                             mplayer => {
                                          NAME => 'mplayer',
                                          DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                        },
                           },
 
                   TAGS => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },
               
        mp3 => {

                DEFAULT_ENCODER => "lame",
                DEFAULT_DECODER => "lame",
                
                ENCODER => {
                              lame => {
                                        NAME => "lame",
                                        ESTR => sub { 
                                                      $opts = "--resample $freq -b $bitrate -h" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts $opts %i %o" 
                                                    },
                                      
                                        PROMPT => {
                                                    BITRATE => 1,
                                                    FREQ    => 1,
                                                  },
                                      },

                          bladeenc => {
                                        NAME => "bladeenc",
                                        ESTR => sub { 
                                                      $opts = "-br $bitrate" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts $opts %i %o" 
                                                    },                                                    
                                        PROMPT => { BITRATE => 1 },
                                      },

                              gogo => {
                                        NAME => "gogo",
                                        ESTR => sub { 
                                                      $opts = "-b $bitrate" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts $opts %i %o" 
                                                    },
                                        
                                        PROMPT => { BITRATE => 1 },
                                      },

                           toolame => {
                                        NAME => "toolame",
                                        ESTR => sub { 
                                                      $opts = "-b $bitrate" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts $opts %i %o" 
                                                    },
                                        
                                        PROMPT => { BITRATE => 1 },
                                      },

                            ffmpeg => {
                                        NAME => "ffmpeg",
                                        ESTR => sub { 
                                                      $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts -y -i %i $opts %o" 
                                                    },
                                        
                                        PROMPT => {
                                                    BITRATE  => 1,
                                                    FREQ     => 1,
                                                    CHANNELS => 1,
                                                  },
                                      },
 
                               sox => {
                                        NAME => "sox",
                                        ESTR => sub { 
                                                      $opts = "-r $freq -c $channels" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "%i $eopts $opts %o" 
                                                    },
                                        
                                        PROMPT => {
                                                    FREQ     => 1,
                                                    CHANNELS => 1,
                                                  },
                                      },
                           },
                            
                DECODER => {
                              lame => { 
                                        NAME => "lame",
                                        DSTR => sub { "$dopts --decode %i %o" },
                                      },
                             
                            ffmpeg => {
                                        NAME => "ffmpeg",
                                        DSTR => sub { "$dopts -y -i %i %o" },
                                      },

                           mplayer => {
                                        NAME => "mplayer",
                                        DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                      },

                               sox => {
                                        NAME => "sox",
                                        DSTR => sub { "%i $dopts %o" },
                                      },
                           },

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "MP3::Tag",
                           },
               },

        ogg => {

                DEFAULT_ENCODER => "oggenc",
                DEFAULT_DECODER => "oggdec",
                
                ENCODER => {
                              oggenc => {
                                          NAME => "oggenc",
                                          ESTR => sub { 
                                                        $opts = "--resample $freq -q $oggqual" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "$eopts $opts %i -o %o" 
                                                      },
                                        
                                          PROMPT => {
                                                      FREQ    => 1,
                                                      OGGQUAL => 1,
                                                    },
                                        },

                              ffmpeg => {
                                          NAME => "ffmpeg",
                                          ESTR => sub { 
                                                        $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "$eopts -y -i %i $opts %o" 
                                                      },
                                          
                                          PROMPT => {
                                                      BITRATE  => 1,
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },

                                 sox => {
                                          NAME => "sox",
                                          ESTR => sub { 
                                                        $opts = "-r $freq -c $channels" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "%i $eopts $opts %o" 
                                                      },
                                          
                                          PROMPT => {
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },
                           },
                           
                DECODER => {
                              oggdec => {
                                          NAME => "oggdec",
                                          DSTR => sub { "$dopts %i --output %o" },
                                        },

                              ffmpeg => {
                                          NAME => "ffmpeg",
                                          DSTR => sub { "$dopts -y -i %i %o" },
                                        },

                             mplayer => {
                                          NAME => "mplayer",
                                          DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                        },

                                 sox => {
                                          NAME => "sox",
                                          DSTR => sub { "%i $dopts %o" },
                                        },
                           },

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "Ogg::Vorbis::Header",
                           },
               },
       
       spx  => {
       
                DEFAULT_ENCODER => "speexenc",
                DEFAULT_DECODER => "speexdec",
                
                ENCODER => {
                              speexenc => {
                                            NAME => "speexenc",
                                            ESTR => sub { 
                                                          $opts = "--quality $spxqual" if $defopts == 1;
                                                          $opts = '' if $defopts == 0;
                                                          "$eopts $opts %i %o" 
                                                        },
                                            PROMPT => { SPXQUAL => 1 },
                                          },
                           },
                           
                DECODER => {
                              speexdec => {
                                            NAME => "speexdec",
                                            DSTR => sub { "$dopts %i %o" },
                                          },
                           },
                
                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => undef,
                           },
               },

       flac => {

                DEFAULT_ENCODER => "ffmpeg",
                DEFAULT_DECODER => "ffmpeg",
                
                ENCODER => {
                              flac => {
                                        NAME => "flac",
                                        ESTR => sub { 
                                                      $opts = "-$fcomp" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts -f $opts %i -o %o" 
                                                    },
                                        PROMPT => { FCOMP => 1 },
                                      },

                             flake => {
                                        NAME => "flake",
                                        ESTR => sub {
                                                      $opts = "-$fcomp" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts $opts %i -o %o"
                                                     },
                                        PROMPT => { FCOMP => 1 },
                                      },
                                    
                            ffmpeg => {
                                        NAME => "ffmpeg",
                                        ESTR => sub { 
                                                      $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts -y -i %i $opts %o" 
                                                    },
                                        
                                      PROMPT => {
                                                    BITRATE  => 1,
                                                    FREQ     => 1,
                                                    CHANNELS => 1,
                                                 },
                                      },
                           },

                DECODER => {
                              flac => {
                                        NAME => "flac",
                                        DSTR => sub { "$dopts -f -d %i -o %o" },
                                      },
                                    
                            ffmpeg => {
                                        NAME => "ffmpeg",
                                        DSTR => sub { "$dopts -y -i %i %o" },
                                      },

                           mplayer => {
                                        NAME => "mplayer",
                                        DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                      },
                           },

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "Audio::FLAC::Header",
                           },
               },

       fla  => {

                DEFAULT_ENCODER => "flac",
                DEFAULT_DECODER => "flac",
                
                ENCODER => {
                              flac => {
                                        NAME => "flac",
                                        ESTR => sub { 
                                                      $opts = "-$fcomp" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts -f $opts %i -o %o" 
                                                    },
                                        PROMPT => { FCOMP => 1 },
                                      },
                           },
                           
                DECODER => {
                              flac => {
                                        NAME => "flac",
                                        DSTR => sub { "$dopts -f -d %i -o %o" },
                                      },

                           mplayer => {
                                        NAME => "mplayer",
                                        DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                      },
                           },

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "Audio::FLAC::Header",
                           },
               },

       ape  => {

                DEFAULT_ENCODER => "mac",
                DEFAULT_DECODER => "mac",
                
                ENCODER => {
                              mac => {
                                        NAME => "mac",
                                        ESTR => sub { 
                                                      $opts = "-c$acomp" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "%i %o $opts $eopts" 
                                                    },
                                        PROMPT => { ACOMP => 1 },
                                     },
                            },
                
                DECODER => {
                              mac => {
                                        NAME => "mac",
                                        DSTR => sub { "%i %o -d $dopts" },
                                     },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

      bonk  => {

                DEFAULT_ENCODER => "bonk",
                DEFAULT_DECODER => "bonk",
                
                ENCODER => {
                              bonk => {
                                        NAME => "bonk",
                                        ESTR => sub { 
                                                      $opts = "-q $bquanl -b $bratio -s $bpsize" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "encode $eopts $opts %i -o %o" 
                                                    },
                                        
                                        PROMPT => {
                                                    BQUANL => 1,
                                                    BRATIO => 1,
                                                    BPSIZE => 1,
                                                  },
                                      },
                           },
                
                DECODER => {
                              bonk => {
                                        NAME => "bonk",
                                        DSTR => sub { "decode %i -o %o" },
                                      },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

       shn  => {

                DEFAULT_ENCODER => "shorten",
                DEFAULT_DECODER => "shorten",
                
                ENCODER => {
                              shorten => {
                                            NAME => "shorten",
                                            ESTR => sub { 
                                                          $opts = "-c $channels" if $defopts == 1;
                                                          $opts = '' if $defopts == 0;
                                                          "$eopts $opts %i %o" 
                                                        },
                                            PROMPT => { CHANNELS => 1 },
                                         },
                           },
                
                DECODER => {
                              shorten => {
                                            NAME => "shorten",
                                            DSTR => sub { "-x %i %o" },
                                         },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

       aac  => {

                DEFAULT_ENCODER => "ffmpeg",
                DEFAULT_DECODER => "ffmpeg",
                
                ENCODER => {
                              faac => {
                                        NAME => "faac",
                                        ESTR => sub { 
                                                      $opts = "-q $aacqual" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts $opts %i -o %o" 
                                                    },
                                        PROMPT => { AACQUAL => 1 },
                                      },
                                      
                            ffmpeg => {
                                        NAME => "ffmpeg",
                                        ESTR => sub { 
                                                      $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts -y -i %i $opts -strict experimental %o" 
                                                    },
                                        
                                        PROMPT => {
                                                    BITRATE  => 1,
                                                    FREQ     => 1,
                                                    CHANNELS => 1,
                                                  },
                                      },
                           },
                           
                DECODER => {
                              faad => {
                                        NAME => "faad",
                                        DSTR => sub { "$dopts -o %o %i" },
                                      },
                              
                            ffmpeg => {
                                        NAME => "ffmpeg",
                                        DSTR => sub { "$dopts -y -i %i %o" },
                                      },

                           mplayer => {
                                        NAME => "mplayer",
                                        DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                      },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

       mp4  => {

                DEFAULT_ENCODER => "ffmpeg",
                DEFAULT_DECODER => "ffmpeg",
                
                ENCODER => {
                
                            faac => {
                                      NAME => "faac",
                                      ESTR => sub { 
                                                    $opts = "-q $aacqual" if $defopts == 1;
                                                    $opts = '' if $defopts == 0;
                                                    "$eopts -w $opts %i -o %o" 
                                                  },
                                      PROMPT => { AACQUAL => 1 },
                                    },

                          ffmpeg => {
                                      NAME => "ffmpeg",
                                      ESTR => sub { 
                                                    $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                    $opts = '' if $defopts == 0;
                                                    "$eopts -y -i %i $opts -strict experimental %o" 
                                                  },
                                      
                                      PROMPT => {
                                                  BITRATE  => 1,
                                                  FREQ     => 1,
                                                  CHANNELS => 1,
                                                },
                                    },
                           },
                             
                DECODER => {

                            faad => {
                                      NAME => "faad",
                                      DSTR => sub { "$dopts -o %o %i" },
                                    },

                          ffmpeg => { 
                                      NAME => "ffmpeg",
                                      DSTR => sub { "$dopts -y -i %i %o" },
                                    },

                         mplayer => {
                                      NAME => "mplayer",
                                      DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                    },
                           },

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "MP4::Info",
                           },
               },

       m4a  => {

                DEFAULT_ENCODER => "ffmpeg",
                DEFAULT_DECODER => "ffmpeg",
                
                ENCODER => {
                  
                            faac => {
                                      NAME => "faac",
                                      ESTR => sub { 
                                                    $opts = "-q $aacqual" if $defopts == 1;
                                                    $opts = '' if $defopts == 0;
                                                    "$eopts -w $opts %i -o %o" 
                                                  },
                                      PROMPT => { AACQUAL => 1 },
                                    },

                          ffmpeg => {
                                      NAME => "ffmpeg",
                                      ESTR => sub { 
                                                    $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                    $opts = '' if $defopts == 0;
                                                    "$eopts -y -i %i $opts -strict experimental %o" 
                                                  },
                                      
                                      PROMPT => {
                                                  BITRATE  => 1,
                                                  FREQ     => 1,
                                                  CHANNELS => 1,
                                                },
                                    },
                           },
                
                DECODER => {

                             faad => {
                                       NAME => "faad",
                                       DSTR => sub { "$dopts -o %o %i" },
                                     },
                                       
                           ffmpeg => {
                                       NAME => "ffmpeg", 
                                       DSTR => sub { "$dopts -y -i %i %o" },
                                     },

                          mplayer => {
                                       NAME => "mplayer",
                                       DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                     },
                                      
                           },

                ESTR    => sub { "$eopts -w -q $aacqual %i -o %o" },

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "MP4::Info",
                           },
               },

       m4b  => {
       
                DEFAULT_ENCODER => "faac",
                DEFAULT_DECODER => "mplayer",
                
                ENCODER => {
                           
                            faac => {
                                      NAME => "faac",
                                      ESTR => sub {
                                                    $opts = "-q $aacqual" if $defopts == 1;
                                                    $opts = '' if $defopts == 0;
                                                    "$eopts -w $opts %i -o %o"
                                                  },

                                      PROMPT => { AACQUAL => 1 },
                                    },
 
                           },

                DECODER => {
                             faad => {
                                       NAME => "faad",
                                       DSTR => sub { "$dopts -o %o %i" },
                                     },

                          mplayer => {
                                       NAME => "mplayer",
                                       DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                     },
                           },

                ESTR    => sub { "$eopts -w -q $aacqual %i -o %o" },
                
                TAGS    => {
                             READ   => 0,
                             WRITE  => 1,
                             MODULE => undef,
                           },
               },
                             
       mpc  => {

                DEFAULT_ENCODER => "mpcenc",
                DEFAULT_DECODER => "mpcdec",
                
                ENCODER => {

                            mpcenc => {
                                        NAME => "mpcenc",
                                        ESTR => sub { 
                                                      $opts = "--$mpcqual" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts --overwrite $opts %i %o" 
                                                    },
                                        PROMPT => { MPCQUAL => 1 },
                                      },
                           },
                
                DECODER => {

                            mpcdec => {
                                        NAME => "mpcdec",
                                        DSTR => sub { "$dopts %i %o" },
                                      },

                           mplayer => {
                                        NAME => "mplayer",
                                        DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                      },
                           },

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "Audio::Musepack",
                           },

                PROMPT  => {
                             MPCQUAL => 1,
                           },
               },

       mpp  => {

                DEFAULT_ENCODER => "mpcenc",
                DEFAULT_DECODER => "mpcdec",
                
                ENCODER => {

                            mpcenc => {
                                        NAME => "mpcenc",
                                        ESTR => sub { 
                                                      $opts = "--$mpcqual" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts --overwrite $opts %i %o" 
                                                    },
                                        PROMPT => { MPCQUAL => 1 },
                                      },
                           },
                
                DECODER => {

                            mpcdec => {
                                        NAME => "mpcdec",
                                        DSTR => sub { "$dopts %i %o" },
                                      },

                           mplayer => {
                                        NAME => "mplayer",
                                        DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                      },
                           },

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "Audio::Musepack",
                           },
               },

       ofr  => {

                DEFAULT_ENCODER => "ofr",
                DEFAULT_DECODER => "ofr",
                
                ENCODER => {
                              ofr => {
                                        NAME => "ofr",
                                        ESTR => sub { 
                                                      $opts = "--mode $ofmode --optimize $ofopt" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts --overwrite $opts %i --output %o" 
                                                    },
                                        
                                        PROMPT => {
                                                    OFMODE => 1,
                                                    OFOPT  => 1,
                                                  },
                                     },
                           },
                           
                DECODER => {
                              ofr => {
                                        NAME => "ofr",
                                        DSTR => sub { "$dopts --overwrite --decode %i --output %o" },
                                     },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

       ofs  => {

                DEFAULT_ENCODER => "ofs",
                DEFAULT_DECODER => "ofs",
                
                ENCODER => {
                              ofs => {
                                        NAME => "ofs",
                                        ESTR => sub { 
                                                      $opts = "--mode $ofmode --optimize $ofopt" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts --overwrite $opts %i --output %o" 
                                                    },
                                    
                                        PROMPT => {
                                                    OFMODE => 1,
                                                    OFOPT  => 1,
                                                  },
                                     },
                           },
                
                DECODER => {
                              ofs => {
                                        NAME => "ofs",
                                        DSTR => sub { "$dopts --overwrite --decode %i --output %o" },
                                     },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

       pac  => {

                DEFAULT_ENCODER => "lpac",
                DEFAULT_DECODER => "lpac",
                
                ENCODER => {
                              lpac => {
                                        NAME => "lpac",
                                        ESTR => sub { 
                                                      $opts = "-$pcomp" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts -v $opts %i %o" 
                                                    },
                                      
                                        PROMPT => { PCOMP => 1 },
                                      },
                           },
                
                DECODER => {
                              lpac => {
                                        NAME => "lpac",
                                        DSTR => sub { "$dopts -x %i - > %o" },
                                      },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

       lpac => {

                DEFAULT_ENCODER => "lpac",
                DEFAULT_DECODER => "lpac",
                
                ENCODER => {
                              lpac => {
                                        NAME => "lpac",
                                        ESTR => sub { 
                                                      $opts = "-$pcomp" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "$eopts -v $opts %i %o" 
                                                    },
                                      
                                        PROMPT => { PCOMP => 1 },
                                      },
                           },
                
                DECODER => {
                              lpac => {
                                        NAME => "lpac",
                                        DSTR => sub { "$dopts -x %i - > %o" },
                                      },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

       tta  => {

                DEFAULT_ENCODER => "ttaenc",
                DEFAULT_DECODER => "ttaenc",
                
                ENCODER => {
                              ttaenc => {
                                          NAME => "ttaenc",
                                          ESTR => sub { "$eopts -e %i -o %o" },
                                        },
                           },
                
                DECODER => {
                              ttaenc => {
                                          NAME => "ttaenc",
                                          DSTR => sub { "$dopts -d %i -o %o" },
                                        },

                              ffmpeg => {
                                          NAME => "ffmpeg",
                                          DSTR => sub { "$dopts -y -i %i %o" },
                                        },

                             mplayer => {
                                          NAME => "mplayer",
                                          DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                        },
                           },
                                        
                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

       la   => {

                DEFAULT_ENCODER => "la",
                DEFAULT_DECODER => "la",
                
                ENCODER => {
                              la => {
                                      NAME => "la",
                                      ESTR => sub { "-overwrite $eopts %i %o" },
                                    },
                           },
                
                DECODER => {
                              la => {
                                      NAME => "la",
                                      ESTR => sub { "-console $dopts %i > %o" },
                                    },
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

       wv   => {

                DEFAULT_ENCODER => "wavpack",
                DEFAULT_DECODER => "ffmpeg",
                
                ENCODER => {
                              wavpack => {
                                           NAME => "wavpack",
                                           ESTR => sub { "$eopts -y %i -o %o" },
                                         },
                           },
                
                DECODER => {
                              wvunpack => {
                                            NAME => "wvunpack",
                                            DSTR => sub { "$dopts -y %i -o %o" },
                                          },

                                ffmpeg => {
                                            NAME => "ffmpeg",
                                            DSTR => sub { "$dopts -y -i %i %o" },
                                          },

                               mplayer => {
                                            NAME => "mplayer",
                                            DSTR => sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" },
                                          },
                           },

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "Audio::APETags",
                           },
               },

       wav  => {

                DEFAULT_ENCODER => "mv",
                DEFAULT_DECODER => "cp",
                
                ENCODER => {
                              mv => {
                                      NAME => "mv",
                                      ESTR => sub { "%i %o" },
                                    },
                           },
                           
                DECODER => {
                              cp => {
                                      NAME => "cp",
                                      DSTR => sub { "%i %o" },
                                    }
                           },

                TAGS    => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },
);


# supported ffmpeg formats
my @ffmpeg_codecs = ( "ra", "wma", "mp2", "mmf" );

foreach (@ffmpeg_codecs) {

   $run{$_}{DEFAULT_ENCODER}        = "ffmpeg";
   $run{$_}{DEFAULT_DECODER}        = "ffmpeg";
   $run{$_}{ENCODER}{ffmpeg}{NAME}  = "ffmpeg";
   $run{$_}{DECODER}{ffmpeg}{NAME}  = "ffmpeg";
   $run{$_}{ENCODER}{ffmpeg}{ESTR}  = sub { "$eopts -y -i %i -ab $bitrate.k -ar $freq -ac $channels %o" };
   $run{$_}{DECODER}{ffmpeg}{DSTR}  = sub { "$dopts -y -i %i %o" };
   $run{$_}{TAGS}{READ}             = 0;
   $run{$_}{TAGS}{WRITE}            = 0;
   $run{$_}{TAGS}{MODULE}           = undef;

   $run{$_}{ENCODER}{ffmpeg}{PROMPT}{BITRATE}  = 1;
   $run{$_}{ENCODER}{ffmpeg}{PROMPT}{FREQ}     = 1;
   $run{$_}{ENCODER}{ffmpeg}{PROMPT}{CHANNELS} = 1;

}

# support reading of WMA tags using the Audio::WMA module
$run{wma}{TAGS}{READ}   = 1;
$run{wma}{TAGS}{MODULE} = "Audio::WMA";
   
# supported video to audio types
my @movie_codecs = ( 
                     "rv",    "asf",   "avi",   "divx",  
                     "mkv",   "mpg",   "mpeg",  "mov",   
                     "ogm",   "qt",    "vcd",   "vob",   
                     "wmv",   "flv",   "svcd",  "m4v",   
                     "nsv",   "nuv",   "psp",   "smk",
                   );

foreach (@movie_codecs) {

   $run{$_}{DEFAULT_ENCODER}        = undef;
   $run{$_}{DEFAULT_DECODER}        = "mplayer";
   $run{$_}{DECODER}{mplayer}{NAME} = "mplayer";
   $run{$_}{DECODER}{ffmpeg}{NAME}  = "ffmpeg";
   $run{$_}{DECODER}{mplayer}{DSTR} = sub { "-vc dummy -vo null -nortc -ao pcm:file=%o %i" };
   $run{$_}{DECODER}{ffmpeg}{DSTR}  = sub { "$dopts -i %i %o"; };
   $run{$_}{TAGS}{READ}             = 0;
   $run{$_}{TAGS}{WRITE}            = 0;
   $run{$_}{TAGS}{MODULE}           = undef;
}

# supported sndfile-convert formats
my @sndfile = ( 
                "pvf",  "caf",  "sf",  "paf",   "fap", "sd2", 
                "mat4", "mat5", "mat", "ircam", "w64", "nist",
              );

foreach (@sndfile) {
     
   $run{$_}{DEFAULT_ENCODER}                  = "sndfile-convert";
   $run{$_}{DEFAULT_DECODER}                  = "sndfile-convert";
   $run{$_}{ENCODER}{"sndfile-convert"}{NAME} = "sndfile-convert";
   $run{$_}{DECODER}{"sndfile-convert"}{NAME} = "sndfile-convert";
   $run{$_}{ENCODER}{"sndfile-convert"}{ESTR} = sub { "$eopts %i %o" };
   $run{$_}{DECODER}{"sndfile-convert"}{DSTR} = sub { "$dopts %i %o" };
   $run{$_}{TAGS}{READ}                       = 0;
   $run{$_}{TAGS}{WRITE}                      = 0;
   $run{$_}{TAGS}{MODULE}                     = undef;
}
              
# supported sox formats
my @sox_codecs = ( "aiff", "aif", "au", "snd", "voc", "smp", "avr", "cdr" );

foreach (@sox_codecs) {

   $run{$_}{DEFAULT_ENCODER}    = "sox";
   $run{$_}{DEFAULT_DECODER}    = "sox";
   $run{$_}{ENCODER}{sox}{NAME} = "sox";
   $run{$_}{DECODER}{sox}{NAME} = "sox";
   $run{$_}{ENCODER}{sox}{ESTR} = sub { "%i -r $freq -c $channels $eopts %o $effect" };
   $run{$_}{DECODER}{sox}{DSTR} = sub { "%i $dopts %o" };
   $run{$_}{TAGS}{READ}         = 0;
   $run{$_}{TAGS}{WRITE}        = 0;
   $run{$_}{TAGS}{MODULE}       = undef;

   $run{$_}{ENCODER}{sox}{PROMPT}{FREQ}     = 1;
   $run{$_}{ENCODER}{sox}{PROMPT}{CHANNELS} = 1;

}

# load codecs.conf file
load_codecs() if -e $codecs_conf;

my $out_name = $outfile; 
my $out_dir  = $outdir;

my $total_converted = 0;
my $total_failed    = 0;
my $first_message   = 1;

# process all files and directories
sub proc_input {

   $to =~ tr/A-Z/a-z/;
   
   perror("bad_input","") unless(exists($run{$to}));
   
   # process all files first
   if (@file) {

       foreach my $i (sort(@file)) {
       
               my ($file, $dir, $ext) = fileparse("$i", qr/\.[^.]*/);

                   $ext    =~ s/^\.//;
                   $dir    =  rel2abs($dir);
                   $outdir =  $dir if not defined($out_dir) and not $preserve;
                   $outdir =  $out_dir if $preserve;

               my  $if  = $ext;
                   $if  =~ tr/A-Z/a-z/;
                   
                   $only =~ tr/A-Z/a-z/ if $only;
                   
              next if $only and $if ne $only;
              
                   $encoder = $my_encoder if defined($my_encoder);
                   $decoder = $my_decoder if defined($my_decoder);
                   
                   $encoder = $run{$to}{DEFAULT_ENCODER} if not $my_encoder;
                   $decoder = $run{$if}{DEFAULT_DECODER} if not $my_decoder;
                                      
               if (check_input($file,$dir,$if) == 0) {
                   
                   pnotice("unk_encoder","$file.$ext",1), next unless(exists($run{$to}{ENCODER}{$encoder}));
                   pnotice("unk_decoder","$file.$ext",1), next unless(exists($run{$if}{DECODER}{$decoder}));
              
                   get_visual_opts($to,$outdir) if $gui and not defined($first_run) and `which kdialog 2>/dev/null`;
                   
                   $first_run = 1;
                   
                   perror("multi_out","")       if (defined($out_name) and $#file gt 0 or defined($out_name) and @dir);
                   perror("no_outdir","")       if (not -e $outdir and not -d $outdir);

                   $outfile = "$file" if not $out_name;

                   if ($keep and $if eq $to) {
                       system("cp -v \"$dir/$file.$ext\" \"$outdir/$outfile.$ext\"") if $outdir ne $dir and not -e "$outdir/$outfile.$ext";
                       next if $outdir eq $dir;
                   } 
                   
                   else {
                       convert("$dir/$file","$outdir/$outfile","$ext","$file","$outfile");
                   }
                 
               }
       }
   }

   # now the directories (this includes --recursive and --preserve)
   if (@dir) {

    my @tmp_dir = @dir;
       undef(@dir);
       $topdir = $tmp_dir[0];

    if (not $recursive) {

        undef(@file);
        
        foreach my $d (@tmp_dir) {

                opendir(DIR, "$d") or perror("opening_dir","$d: $!");
             my @dir_files = readdir(DIR);
                closedir(DIR);
                
                perror("empty_dir","$d") if $#dir_files <= 1;

                foreach my $f (@dir_files) {

                     if (not -d $f and not -d "$d/$f") {
                         push(@file, "$d/$f");
                         undef($out_name);
                     }
               }
       }
       
       proc_input();
       return 0;

    } 

    elsif ($recursive) {

             if (not $preserve) {
                 
                 undef(@file);

                 foreach my $r (@tmp_dir) {

                         $r = rel2abs($r);

                         find ( sub {
                                      my $rfile = $File::Find::name;
                                         push(@file, $rfile) if not -d $rfile;
                                    }, $r
                              );
                 }
                 
                 proc_input();
                 return 0;

             } elsif ($preserve) {
                      $topdir = rel2abs($topdir);
                      find ({wanted => \&preserve_struct, no_chdir => 1, follow => 0}, $topdir);
                      return 0;
               }
      }

    }
   
    print "\n$lang{total_converted} $total_converted, $lang{failed} $total_failed\n";
}

my $out_copy = $out_dir if defined($out_dir);

# preserve directory structure (--recursive --preserve)
sub preserve_struct {

       undef($out_dir) if defined ($out_dir);

       perror("need_outdir","")        if not defined($out_copy);
       perror("no_outdir","$out_copy") if not -d $out_copy;

       $out_copy = rel2abs($out_copy);

    my $find_dir = $File::Find::name;
    my ($presdir, $odir);

    if (-d $find_dir and $find_dir ne $topdir) {

        my $out = $find_dir;
           $out =~ s/$topdir\///g;

           $presdir = "$topdir/$out";
           $odir    = "$out_copy/$out";

           $out_dir = "$odir";

        if (-d $find_dir and not -d $odir) { mkdir("$odir", 0755); }

            opendir(IN, "$presdir") or perror("opening_dir","$presdir: $!");
            
            undef(@file);

            foreach my $i (readdir(IN)) {

                 if (not -d $i and not -d "$presdir/$i") {
                     mkdir("$odir", 0755) if not -d $odir;
                     pnotice("creating_dir","$odir",2) if not -d $odir;
                     push(@file, "$presdir/$i");
                 }

            } undef($presdir); undef($odir); closedir(IN);
            
            proc_input();

        } elsif (-d $find_dir and $find_dir eq $topdir) {

                 $presdir = $topdir;
                 $odir    = $out_copy;
                 $out_dir = $odir;
                 
                 undef(@file);

                 opendir(IN, "$presdir") or perror("opening_dir","$presdir: $!");

                 foreach my $i (readdir(IN)) {

                      if (not -d $i and not -d "$presdir/$i") {
                          push(@file, "$presdir/$i");
                      }

                 } closedir(IN);
                 
                 proc_input();
          }
}

# convert input to destination format
sub convert {

    my ($inf, $outf, $infmt, $iname, $oname) = @_;

    my $if = $infmt;
       $if =~ tr/A-Z/a-z/;

       clear_tag_hash() if not $rip;

       if (-e "$outf.$to" and not $overwrite) {
           pnotice("overwrite","$outf.$to",2);
           $total_failed++;
           return 0;
       }

       # catch ^C.  will have to be pressed repeatedly to exit the process.
       $SIG{INT} = sub {
                         unlink("$outf.$$.wav");
                         kill 9, $$; 
                       };
       
       # check to see if encoder/decoder exists.  if not, see if we have one 
       # that supports the desired input/output formats.
       check_encoder();
       check_decoder($infmt);
                         
    my $decode_string = $run{$if}{DECODER}{$decoder}{DSTR}->();
       $decode_string =~ s/%i/\"$inf.$infmt\"/;
       $decode_string =~ s/%o/\"$outf.$$.wav\"/;
       $decode_string =~ s/(\$|\\)/\\$1/g;

    my $nl = 0; $nl = 2 if $verbose;
          
       pnotice("converting"," [$iname.$infmt] -> [$to] ", $nl);
       pnotice("debug","$run{$if}{DECODER}{$decoder}{NAME} $decode_string", 2) if $dryrun;

       # decode input file to temporary wav
       system("$run{$if}{DECODER}{$decoder}{NAME} $decode_string $silent") if not $dryrun;

       # remove temporary wav file and die if decode fails
       if ($? > 0) {
           unlink("$outf.$$.wav") if -e "$outf.$$.wav";
           pnotice("decode_failed","$?",2);
           $total_failed++;
           return 0;
       }
       
       # normalize wav file before encoding to output format
       perror("no_app","normalize") if $normalize and not `which normalize 2>/dev/null`;
       system("normalize $nopts \"$outf.$$.wav\"") if $normalize;

       # copy meta-data from input file to %tag_name 
       read_tags("$inf.$infmt", "$if") if $run{$if}{TAGS}{READ} == 1 and not $dryrun;

    my $encode_string = $run{$to}{ENCODER}{$encoder}{ESTR}->();
       $encode_string =~ s/%i/\"$outf.$$.wav\"/; 
       $encode_string =~ s/%o/\"$outf.$to\"/;
       $encode_string =~ s/(\$|\\)/\\$1/g;

    my $tag_opts = format_tags($to); # tag options for mp4/m4a/m4b/mpc/mpp/bonk/spx/wv
    
       # read tag options from command line if present
       get_user_tags();

       pnotice("debug","$run{$to}{ENCODER}{$encoder}{NAME} $tag_opts $encode_string", 2) if $dryrun;

       # encode temporary WAV to desired output format.
       system("$run{$to}{ENCODER}{$encoder}{NAME} $tag_opts $encode_string $silent") if not $dryrun;

       # remove partially encoded output file and temporary wav if encoding fails
       if ($? > 0) {
           unlink("$outf.$to")          if -e "$outf.$to";
           unlink("$outf.$$.wav")       if -e "$outf.$$.wav";
           pnotice("encode_failed","$?",2);
           $total_failed++;
           return 0;
       }

       # write meta-data to output file from %tag_name hash
       write_tags("$outf.$to", "$to") if $run{$to}{TAGS}{WRITE} == 1 and $tag_opts eq '' and not $dryrun;
 
       unlink("$outf.$$.wav") if not $dryrun;
       unlink("$inf.$infmt")  if $delete and not $dryrun;

       pnotice("removed_tmp","$outf.$$.wav","2") if $verbose;
       pnotice("removed_src","$inf.$infmt", "2") if $delete and $verbose;
       
       if (-e "$outf.$to") {
           pnotice("conv_comp","",1);
           $total_converted++;
       }
}

# if the default encoder does not exist, cycle through the supported
# encoders until we find one that does.
sub check_encoder {

    if (not `which $encoder 2>/dev/null`) {
        my $first_encoder = $encoder;
        foreach (keys %{$run{$to}{ENCODER}}) { $encoder = $_ and return if `which $_ 2>/dev/null`; } 
        perror("no_encode_app","$to") if $encoder eq $first_encoder;
    } 

    else { return 0; }
}

# if the default decoder does not exist, cycle through the supported
# decoders until we find one that does.
sub check_decoder {

    my $from = shift;
    
    if (not `which $decoder 2>/dev/null`) {
        my $first_decoder = $decoder;
        foreach (keys %{$run{$from}{DECODER}}) { $decoder = $_ and return if `which $_ 2>/dev/null`; }
        perror("no_decode_app","$from") if $decoder eq $first_decoder;
    }
   
    else { return 0; }
}

# make sure encoding/decoding is supported.
sub check_input {

     my ($f, $d, $s) =  @_;

     perror("no_encoder","$to") unless(defined($run{$to}{ENCODER}));

     unless(defined($run{$s}{DECODER})) {
         pnotice("no_decoder","$d/$f.$s",2) if $verbose or not $only;
         return 1;
     }

     return 0;
}

############################################################
# kdialog options for Konqueror, Dolphin, & Amarok plugins #
############################################################

my $kdialog = "kdialog --title \"$name - $version\"";

sub get_visual_opts {

    my ($to_fmt,$od) = @_;

       # escape illegal characters in output directory name
       $od =~ s/('|\(|\)|"|\\)/\\$1/g;

       $outdir    = `kdialog --getexistingdirectory $od` if $config{KDE_DIR} == 1;
       chomp($outdir);
       $out_dir = $outdir;
       exit(1) if not $outdir;
       
       if ($config{KDE_OPTS} == 1) {
           foreach (keys %{$run{$to_fmt}{ENCODER}{$encoder}{PROMPT}}) {
                   prompt_user($_);
           }
       }
}

sub prompt_user {

    my $opt = shift;

      switch ($opt) {

            case 'BITRATE'   { 
                                $bitrate  = `$kdialog --default $bitrate  --combobox \"$lang{kde_bitrate}\" 56 112 128 160 192 256 320`;
                                chomp($bitrate);
                                exit(1) if not $bitrate;
                             }

            case 'FREQ'      {
                                $freq     = `$kdialog --default $freq     --combobox \"$lang{kde_freq}\" 32000 44100 48000`;
                                chomp($freq);
                                exit(1) if not $freq;
                             }

            case 'CHANNELS'  { 
                                $channels = `$kdialog --default $channels --combobox \"$lang{kde_chans}\" 1 2`;
                                chomp($channels);
                                exit(1) if not $channels;
                             }

            case 'FCOMP'     {
                                $fcomp    = `$kdialog --default $fcomp    --combobox \"$lang{kde_fcomp}\" 0 1 2 3 4 5 6 7 8`;
                                chomp($fcomp);
                                exit(1) if not $fcomp;
                             }

            case 'PCOMP'     {
                                $pcomp    = `$kdialog --default $pcomp    --combobox \"$lang{kde_pcomp}\" 1 2 3 4 5`;
                                chomp($pcomp);
                                exit(1) if not $pcomp;
                             }

            case 'ACOMP'     {
                                $acomp    = `$kdialog --default $acomp    --combobox \"$lang{kde_acomp}\" 1000 2000 3000 4000 5000`;
                                chomp($acomp);
                                exit(1) if not $acomp;
                             }

            case 'OGGQUAL'   {
                               $oggqual  = `$kdialog --default $oggqual   --combobox \"$lang{kde_oggqual}\" 0 1 2 3 4 5 6 7 8 9 10`;
                               chomp($oggqual);
                               exit(1) if not $oggqual;
                             }
                             
            case 'SPXQUAL'   {
                               $spxqual  = `$kdialog --default $spxqual   --combobox \"$lang{kde_spxqual}\" 0 1 2 3 4 5 6 7 8 9 10`;
                               chomp($spxqual);
                               exit(1) if not $spxqual;
                             }

            case 'AACQUAL'   {
                               $aacqual  = `$kdialog --default $aacqual   --combobox \"$lang{kde_aacqual}\" 50 75 100 125 150`;
                               chomp($aacqual);
                               exit(1) if not $aacqual;
                             }

            case 'MPCQUAL'   {
                               $mpcqual  = `$kdialog --default $mpcqual   --combobox \"$lang{kde_mpcqual}\" thumb radio standard xtreme insane braindead`;
                               chomp($mpcqual);
                               exit(1) if not $mpcqual;
                             }

            case 'OFMODE'    {
                               $ofmode   = `$kdialog --default $ofmode    --combobox \"$lang{kde_ofmode}\" fast normal high extra best highnew extranew bestnew`; 
                               chomp($ofmode);
                               exit(1) if not $ofmode;
                             }

            case 'OFOPT'     {
                               $ofopt    = `$kdialog --default $ofopt     --combobox \"$lang{kde_ofopt}\" none fast normal high best`; 
                               chomp($ofopt);
                               exit(1) if not $ofopt;
                             }

            case 'BRATIO'    {
                               $bratio   = `$kdialog --default $bratio    --combobox \"$lang{kde_bratio}\" 1 2 3 4 5`;
                               chomp($bratio);
                               exit(1) if not $bratio;
                             }

            case 'BQUANL'    {
                               $bquanl   = `$kdialog --default $bquanl    --combobox \"$lang{kde_bquanl}\" 1.00 1.25 1.50 1.75 2.00`;
                               chomp($bquanl);
                               exit(1) if not $bquanl;
                             }

            case 'BPSIZE'    {
                               $bpsize   = `$kdialog --default $bpsize    --combobox \"$lang{kde_bpsize}\" 10 128`;
                               chomp($bpsize);
                               exit(1) if not $bpsize;
                             }

                        else { return 0; }
      }

}

# all copied tags will be stored here for furture use
my %tag_name = (
                 title    => undef,
                 track    => undef,
                 artist   => undef,
                 album    => undef,
                 comment  => undef,
                 year     => undef,
                 genre    => undef,
               );

# override tags read from input file if any of the tagging
# options are specified by the user during conversion.
sub get_user_tags {

    $tag_name{title}   = $title   if $title;
    $tag_name{track}   = $track   if $track;
    $tag_name{artist}  = $artist  if $artist;
    $tag_name{album}   = $album   if $album;
    $tag_name{comment} = $comment if $comment;
    $tag_name{year}    = $year    if $year;
    $tag_name{genre}   = $genre   if $genre;
    
}

# the formats MP4/M4A/M4B/MPC/MPP/WV/BONK have no tag writing module.
# tags therefore have to be written during encode via various 
# command line args.
sub format_tags {

    my $type = shift;

    switch ($type) {
        
        case /mp4|m4a|m4b/ { 
        
            if ($run{$to}{ENCODER}{$encoder}{NAME} eq "ffmpeg") {
     
                $tag_name{track} = 0 if not $tag_name{track};
                $tag_name{year}  = 0 if not $tag_name{year};
                return "-metadata title=\"$tag_name{title}\" -metadata track=\"$tag_name{track}\" -metadata author=\"$tag_name{artist}\" -metadata album=\"$tag_name{album}\" -metadata comment=\"$tag_name{comment}\" -metadata year=\"$tag_name{year}\" -metadata genre=\"$tag_name{genre}\"";

            } else {
                
              return "--title \"$tag_name{title}\" --track \"$tag_name{track}\" --artist \"$tag_name{artist}\" --album \"$tag_name{album}\" --comment \"$tag_name{comment}\" --year \"$tag_name{year}\" --genre \"$tag_name{genre}\"";

              }
        } 
                
        case /mpc|mpp/ { return "--tag \"Title=$tag_name{title}\" --tag \"Track=$tag_name{track}\" --tag \"Artist=$tag_name{artist}\" --tag \"Album=$tag_name{album}\" --tag \"Comment=$tag_name{comment}\" --tag \"Year=$tag_name{year}\" --tag \"Genre=$tag_name{genre}\""; }
        case 'wv'      { return "-w \"Title=$tag_name{title}\" -w \"Track=$tag_name{track}\" -w \"Artist=$tag_name{artist}\" -w \"Album=$tag_name{album}\" -w \"Comment=$tag_name{comment}\" -w \"Year=$tag_name{year}\" -w \"Genre=$tag_name{genre}\""; }
        case 'bonk'    { return "-t \"$tag_name{title}\" -a \"$tag_name{artist}\" -c \"$tag_name{comment}\""; }
        case 'spx'     { return "--author \"$tag_name{artist}\" --title \"$tag_name{title}\" --comment \"date=$tag_name{year}\" --comment \"track=$tag_name{track}\" --comment \"album=$tag_name{album}\" --comment \"genre=$tag_name{genre}\" --comment \"comment=$tag_name{comment}\""; }
        else           { return ''; }
    }
}

# meta-data is read based on input file extension.
# all tags which are present will be copied to 
# the %tag_name hash.
sub read_tags {

    my ($in_file, $in_format) = @_;
    my $tag_module = $run{$in_format}{TAGS}{MODULE};

    switch ($in_format) {

           case 'mp3'  {
                            my $mp3tag = $tag_module->new($in_file);
                            my @tags   = $mp3tag->autoinfo();

                               $tag_name{title}   = "$tags[0]" if $tags[0];
                               $tag_name{track}   = "$tags[1]" if $tags[1];
                               $tag_name{artist}  = "$tags[2]" if $tags[2];
                               $tag_name{album}   = "$tags[3]" if $tags[3];
                               $tag_name{comment} = "$tags[4]" if $tags[4];
                               $tag_name{year}    = "$tags[5]" if $tags[5];
                               $tag_name{genre}   = "$tags[6]" if $tags[6];
                               
                            return 0;
                       }

           case 'ogg'  {
                            my $ogg_tag = $tag_module->new($in_file);

                            my ($tit, $tra, $art, $alb, $com, $yea, $gen);

                            foreach my $i ($ogg_tag->comment_tags) {

                                 if ($i =~   /^title/i) { $tit = \$ogg_tag->comment($i); }
                                 if ($i =~   /^track/i) { $tra = \$ogg_tag->comment($i); }
                                 if ($i =~  /^artist/i) { $art = \$ogg_tag->comment($i); }
                                 if ($i =~   /^album/i) { $alb = \$ogg_tag->comment($i); }
                                 if ($i =~ /^comment/i) { $com = \$ogg_tag->comment($i); }
                                 if ($i =~    /^date/i) { $yea = \$ogg_tag->comment($i); }
                                 if ($i =~   /^genre/i) { $gen = \$ogg_tag->comment($i); }

                                 $tag_name{title}   = ${$tit} if defined(${$tit});
                                 $tag_name{track}   = ${$tra} if defined(${$tra});
                                 $tag_name{artist}  = ${$art} if defined(${$art});
                                 $tag_name{album}   = ${$alb} if defined(${$alb});
                                 $tag_name{comment} = ${$com} if defined(${$com});
                                 $tag_name{year}    = ${$yea} if defined(${$yea});
                                 $tag_name{genre}   = ${$gen} if defined(${$gen});
                            }
                            
                            return 0;
                       }
                       
        case 'spx'     {
                            my $pid  = open(SPX, "speexdec \"$in_file\" tmp1-$$.wav 2>&1 |") or perror("opening_file","$in_file");
                            my @data = <SPX>;
                               @data = grep(!/^$|^#/, @data);
                            
                            close(SPX);
                               
                            foreach (@data) {

                                next unless /=/; 
                               
                                chomp;
                               
                                my ($k, $v) = split /=/;
                                    $k =~ tr/A-Z/a-z/;
                                    $tag_name{$k} = $v if exists($tag_name{$k});
                                    $tag_name{artist} = $v if $k =~ /^author/i;
                               
                             }
                            
                             unlink("tmp-$$.wav");
                             
                             return 0;
                       }

       case /flac|fla/ {
                            my $flac_tag = $tag_module->new($in_file)->tags();

                               $tag_name{title}   = $flac_tag->{TITLE}       if $flac_tag->{TITLE};
                               $tag_name{track}   = $flac_tag->{TRACKNUMBER} if $flac_tag->{TRACKNUMBER};
                               $tag_name{artist}  = $flac_tag->{ARTIST}      if $flac_tag->{ARTIST};
                               $tag_name{album}   = $flac_tag->{ALBUM}       if $flac_tag->{ALBUM};
                               $tag_name{comment} = $flac_tag->{DESCRIPTION} if $flac_tag->{DESCRIPTION};
                               $tag_name{year}    = $flac_tag->{DATE}        if $flac_tag->{DATE};
                               $tag_name{genre}   = $flac_tag->{GENRE}       if $flac_tag->{GENRE};

                            return 0;
                       }

    case /mp4|m4a|m4b/ {    print "INFILE: $in_file\n";
                            my $mp4_tag = $tag_module->new($in_file);

                               $tag_name{title}   = $mp4_tag->{TITLE}    if $mp4_tag->{TITLE};
                               $tag_name{track}   = $mp4_tag->{TRACKNUM} if $mp4_tag->{TRACKNUM};
                               $tag_name{artist}  = $mp4_tag->{ARTIST}   if $mp4_tag->{ARTIST};
                               $tag_name{album}   = $mp4_tag->{ALBUM}    if $mp4_tag->{ALBUM};
                               $tag_name{comment} = $mp4_tag->{COMMENT}  if $mp4_tag->{COMMENT};
                               $tag_name{year}    = $mp4_tag->{YEAR}     if $mp4_tag->{YEAR};
                               $tag_name{genre}   = $mp4_tag->{GENRE}    if $mp4_tag->{GENRE};

                            return 0;
                       }

        case /mpc|mpp/ {

                            my $mpc_tag = $tag_module->new($in_file)->tags();

                               $tag_name{title}   = $mpc_tag->{TITLE}   if $mpc_tag->{TITLE};
                               $tag_name{track}   = $mpc_tag->{TRACK}   if $mpc_tag->{TRACK};
                               $tag_name{artist}  = $mpc_tag->{ARTIST}  if $mpc_tag->{ARTIST};
                               $tag_name{album}   = $mpc_tag->{ALBUM}   if $mpc_tag->{ALBUM};
                               $tag_name{comment} = $mpc_tag->{COMMENT} if $mpc_tag->{COMMENT};
                               $tag_name{year}    = $mpc_tag->{YEAR}    if $mpc_tag->{YEAR};
                               $tag_name{genre}   = $mpc_tag->{GENRE}   if $mpc_tag->{GENRE};
                               
                            return 0;
                       }

        case 'wv'      {
                            my $wv_tag = $tag_module->getTags($in_file)->{'tags'};

                               $tag_name{title}   = $wv_tag->{TITLE}   if $wv_tag->{TITLE};
                               $tag_name{track}   = $wv_tag->{TRACK}   if $wv_tag->{TRACK};
                               $tag_name{artist}  = $wv_tag->{ARTIST}  if $wv_tag->{ARTIST};
                               $tag_name{album}   = $wv_tag->{ALBUM}   if $wv_tag->{ALBUM};
                               $tag_name{comment} = $wv_tag->{COMMENT} if $wv_tag->{COMMENT};
                               $tag_name{year}    = $wv_tag->{YEAR}    if $wv_tag->{YEAR};
                               $tag_name{genre}   = $wv_tag->{GENRE}   if $wv_tag->{GENRE};
                            
                            return 0;
                       }

        case 'wma'     {
                            my $wma_tag = Audio::WMA->new($in_file)->tags();

                               $tag_name{title}   = $wma_tag->{TITLE}       if $wma_tag->{TITLE};
                               $tag_name{track}   = $wma_tag->{TRACKNUMBER} if $wma_tag->{TRACKNUMBER};
                               $tag_name{artist}  = $wma_tag->{AUTHOR}      if $wma_tag->{AUTHOR};
                               $tag_name{album}   = $wma_tag->{ALBUMTITLE}  if $wma_tag->{ALBUMTITLE};
                               $tag_name{comment} = $wma_tag->{DESCRIPTION} if $wma_tag->{DESCRIPTION};
                               $tag_name{year}    = $wma_tag->{YEAR}        if $wma_tag->{YEAR};
                               $tag_name{genre}   = $wma_tag->{GENRE}       if $wma_tag->{GENRE};
                            
                            return 0;
                       }

                  else { return 1; }
    }
}

# write meta-data stored in %tag_name to output file.
sub write_tags {

    my ($out_file, $out_format) = @_;
    my  $tag_m;

    switch ($out_format) {

           case 'mp3'  {
                            $tag_m = MP3::Tag->new("$out_file");
                            
                            # ID3v2 Tags
                            unless(exists($tag_m->{ID3v2})) { $tag_m->new_tag("ID3v2"); }

                            $tag_m->{ID3v2}->add_frame("TIT2", "$tag_name{title}")    if defined($tag_name{title});
                            $tag_m->{ID3v2}->add_frame("TRCK", "$tag_name{track}")    if defined($tag_name{track});
                            $tag_m->{ID3v2}->add_frame("TPE1", "$tag_name{artist}")   if defined($tag_name{artist});
                            $tag_m->{ID3v2}->add_frame("TALB", "$tag_name{album}")    if defined($tag_name{album});
                            $tag_m->{ID3v2}->comment("$tag_name{comment}")            if defined($tag_name{comment});
                            $tag_m->{ID3v2}->add_frame("TYER", "$tag_name{year}")     if defined($tag_name{year});
                            $tag_m->{ID3v2}->add_frame("TCON", "$tag_name{genre}")    if defined($tag_name{genre});

                            $tag_m->{ID3v2}->write_tag;
                            
                            # ID3v1 Tags
                            unless(exists($tag_m->{ID3v1})) { $tag_m->new_tag("ID3v1"); }
                            
                            $tag_m->{ID3v1}->title("$tag_name{title}")      if defined($tag_name{title});
                            $tag_m->{ID3v1}->track("$tag_name{track}")      if defined($tag_name{track});
                            $tag_m->{ID3v1}->artist("$tag_name{artist}")    if defined($tag_name{artist});
                            $tag_m->{ID3v1}->album("$tag_name{album}")      if defined($tag_name{album});
                            $tag_m->{ID3v1}->comment("$tag_name{comment}")  if defined($tag_name{comment});
                            $tag_m->{ID3v1}->year("$tag_name{year}")        if defined($tag_name{year});
                            $tag_m->{ID3v1}->genre("$tag_name{genre}")      if defined($tag_name{genre});
                            
                            $tag_m->{ID3v1}->write_tag;
                            
                            return 0;

                        }

           case 'ogg'   {
                            $tag_m = Ogg::Vorbis::Header->new("$out_file");
                            
                            # this prevents duplicate tags =)
                            $tag_m->clear_comments();
                            
                            # write new tags
                            $tag_m->add_comments("TITLE",       "$tag_name{title}")    if $tag_name{title};
                            $tag_m->add_comments("TRACKNUMBER", "$tag_name{track}")    if $tag_name{track};
                            $tag_m->add_comments("ARTIST",      "$tag_name{artist}")   if $tag_name{artist};
                            $tag_m->add_comments("ALBUM",       "$tag_name{album}")    if $tag_name{album};
                            $tag_m->add_comments("COMMENT",     "$tag_name{comment}")  if $tag_name{comment};
                            $tag_m->add_comments("DATE",        "$tag_name{year}")     if $tag_name{year};
                            $tag_m->add_comments("GENRE",       "$tag_name{genre}")    if $tag_name{genre};

                            $tag_m->write_vorbis;
                            
                            return 0;

                        }

       case /fla|flac/ {
                            $tag_m = Audio::FLAC::Header->new("$out_file");
                         my $tag_i = $tag_m->tags();

                            $tag_i->{TITLE}       = "$tag_name{title}"   if $tag_name{title};
                            $tag_i->{TRACKNUMBER} = "$tag_name{track}"   if $tag_name{track};
                            $tag_i->{ARTIST}      = "$tag_name{artist}"  if $tag_name{artist};
                            $tag_i->{ALBUM}       = "$tag_name{album}"   if $tag_name{album};
                            $tag_i->{DESCRIPTION} = "$tag_name{comment}" if $tag_name{comment};
                            $tag_i->{DATE}        = "$tag_name{year}"    if $tag_name{year};
                            $tag_i->{GENRE}       = "$tag_name{genre}"   if $tag_name{genre};

                            $tag_m->write();
                            
                          return 0;
                       }

        else	       { perror("no_write_tag","$out_file"); }
    }

}

# this is necessary when converting multiple files.
# it prevents old tags from the previous file being 
# copied over to the next file in line.
sub clear_tag_hash {
    foreach (keys(%tag_name)) {
            $tag_name{$_} = '';
    }
}

# display meta-data for selected file(s)
sub show_taginfo {

    perror("no_input","") if $#file < 0;

    foreach my $i (@file) {

     my ($file, $dir, $ext) = fileparse("$i", qr/\.[^.]*/);
     
         $ext  =~ s/^\.//;
     
     my  $from = $ext;
         $from =~ tr/A-Z/a-z/;

     if ($run{$from}{TAGS}{READ} == 1) {

         clear_tag_hash();
         read_tags("$dir/$file.$ext","$from");

         print "\n"; 

         pnotice("tag_info","$file.$ext",2);

         print " Artist: $tag_name{artist}\n";
         print "  Title: $tag_name{title}\n";
         print "  Album: $tag_name{album}\n";
         print "  Track: $tag_name{track}\n";
         print "Comment: $tag_name{comment}\n";
         print "   Year: $tag_name{year}\n";
         print "  Genre: $tag_name{genre}\n";

         print "\n";

     } else { pnotice("no_read_tag","$file.$ext",2); }
   }
}

# write tags to file supplied by the user
sub write_user_tags {

    perror("no_input","") if $#file < 0;

    print "\n$name - $version\n\n";
    
    foreach (@file) {

         my ($file, $dir, $ext) = fileparse("$_", qr/\.[^.]*/);

            $ext  =~ s/^\.//;
         my $from = $ext;
            $from =~ tr/A-Z/a-z/;

         if ($run{$from}{TAGS}{WRITE} == 1) {

             clear_tag_hash();
             read_tags("$dir/$file.$ext","$from");

             $tag_name{title}   = $title   if $title;
             $tag_name{track}   = $track   if $track;
             $tag_name{artist}  = $artist  if $artist;
             $tag_name{album}   = $album   if $album;
             $tag_name{comment} = $comment if $comment;
             $tag_name{year}    = $year    if $year;
             $tag_name{genre}   = $genre   if $genre;

             write_tags("$dir/$file.$ext","$from");
             print "$lang{write_tag_i} $file.$ext\n";         

         } else { perror("no_write_tag","$file.$ext"); }
    }
    print "\n";
}

# print out list of supported encode & decode formats
sub show_formats {

print "\n$name - $version\n\n";
print "EXT       E   D   ENCODER         DECODER         TAG-READ   TAG-WRITE\n";
print "----------------------------------------------------------------------\n";

my $enc_count = 0;
my $dec_count = 0;

   foreach (sort(keys(%run))) {

        my $e = "N"; $e = "Y" if defined($run{$_}{DEFAULT_ENCODER});
        my $d = "N"; $d = "Y" if defined($run{$_}{DEFAULT_DECODER});
        my $r = "N"; $r = "Y" if $run{$_}{TAGS}{READ}  == 1;
        my $w = "N"; $w = "Y" if $run{$_}{TAGS}{WRITE} == 1;

        my $enc = ''; $enc = $run{$_}{DEFAULT_ENCODER} if defined($run{$_}{DEFAULT_ENCODER});
        my $dec = ''; $dec = $run{$_}{DEFAULT_DECODER} if defined($run{$_}{DEFAULT_DECODER});

        printf("%-9s %-3s %-3s %-15s %-18s %-10s %s\n", $_, $e, $d, $enc, $dec, $r, $w);

        ++$enc_count if $enc ne '';
        ++$dec_count if $dec ne '';

   }
   print "\n$lang{enc_fmts} $enc_count --- $lang{dec_fmts} $dec_count\n\n";
}

# show encoders for output format
sub show_encoders {
    print "\n$name - $version\n\n$lang{show_encoders}: $my_encoder\n\n";
    foreach (sort(keys %{$run{$my_encoder}{ENCODER}})) {
        print "$_";
        print " (default)" if $_ eq $run{$my_encoder}{DEFAULT_ENCODER};
        print "\n";
    }
    print "\n";
}

# show decoders for input format
sub show_decoders {
    print "\n$name - $version\n\n$lang{show_decoders}: $my_decoder\n\n";
    foreach (sort(keys %{$run{$my_decoder}{DECODER}})) {
        print "$_";
        print " (default)" if $_ eq $run{$my_decoder}{DEFAULT_DECODER};
        print "\n";
    }
    print "\n";
}

#################
# cd ripping... #
#################

# get total number of tracks from cdparanoia query
sub get_total_tracks {

    open(CDINFO, "cdparanoia -Q 2>&1 |") or die "can't fork: $!\n";
    my @cdquery = <CDINFO>;
    close(CDINFO);

    my $fmt_tracks = $cdquery[-3];
       $fmt_tracks =~ s/\s//g;

    my @ttracks = split(/\./, $fmt_tracks);

    return "$ttracks[0]";

}

my (%cd, %ripconfig, @cdtrack, $track_total);

# setup the cddb config
sub get_cddb_config {

      $config{USE_CDDB}      = 0 && return if $nocddb;

      $config{CDDB_INPUT}    = 0 if $noinput;

      $ripconfig{CDDB_HOST}  = $config{CDDB_HOST};
      $ripconfig{CDDB_PORT}  = $config{CDDB_PORT};
      $ripconfig{CDDB_MODE}  = $config{CDDB_MODE};
      $ripconfig{DEVICE}     = $device;
      $ripconfig{CDDB_INPUT} = $config{CDDB_INPUT};

      %cd = get_cddb(\%ripconfig);

      if (not $cd{title}) {
          pnotice("no_cddb","",2);
          $config{USE_CDDB} = 0;
      }
      else {
          @cdtrack     = @{$cd{track}};
          $track_total = $cd{tno};
      }

}

# check to see if a disc is present
sub query_disc {
    system("cdparanoia -q -d $device -Q 1>/dev/null 2>/dev/null");
    perror("no_disc","$device") if $? > 0;
}

# set tag info from cddb
sub tag_track {

    my $tno = shift;

       $tag_name{artist}  = $cd{artist} if $cd{artist};
       $tag_name{title}   = $cdtrack[$tno-1] if $cdtrack[$tno-1];
       $tag_name{genre}   = $cd{cat} if $cd{cat};
       $tag_name{track}   = $tno;
       $tag_name{album}   = $cd{title} if $cd{title};
       $tag_name{year}    = $cd{year} if $cd{year};
       $tag_name{comment} = '';

}

# process and convert ripped track
sub rip_cd {

    my ($on, $tno) = @_;

        $out_dir = "$ENV{HOME}";
        $to =~ tr/A-Z/a-z/;
        get_visual_opts($to,$out_dir) if $gui and not defined($first_run) and `which kdialog 2>/dev/null`;
        $first_run = 1;
        
        pnotice("ripping_track","$tno",2);    
 
        $on =~ s/\//_/g;

        print  "cdparanoia -d $device $tno $on.wav\n\n"  if $dryrun;
        system("cdparanoia -d $device $tno \"$on.wav\"") if not $dryrun;

    if ($to !~ /wav/i) {

        push(@file, "$on.wav");
        tag_track($tno);
        proc_input();
        clear_tag_hash();
        undef(@file);
        unlink("$on.wav") if not $dryrun;
        pnotice("removed_tmp","$on.wav",2) if $verbose;

    }
}

# rip selected tracks or all from disc
sub proc_cd {

 perror("no_app","cdparanoia") if not `which cdparanoia 2>/dev/null`;
 query_disc();
 get_cddb_config();

 my $total_tracks = get_total_tracks();

    if ($rip eq "all") { 

        if ($config{USE_CDDB} ne 1) {

            for(my $i=1;$i<=$total_tracks;$i++) {
                rip_cd($i, $i);
            }

        } elsif ($config{USE_CDDB} eq 1) {

              my $n = 1;

              foreach my $i (@cdtrack) {

                      my $out_string = $nscheme;
                      
                         chomp($cdtrack[$n-1]);

                         $out_string =~ s/%ar/$cd{artist}/;
                         $out_string =~ s/%ti/$cdtrack[$n-1]/;
                         $out_string =~ s/%tr/$n/;
                         $out_string =~ s/%ab/$cd{title}/;
                         $out_string =~ s/%yr/$cd{year}/;

                         rip_cd($out_string, $n);

                         ++$n;
              }
          }

    } else {

            my @tracks = split(/,/, $rip);

               foreach my $i (@tracks) {

                       next if $i > $total_tracks or $i < 1;

                       my $out_string = $nscheme;

                          $out_string =~ s/%ar/$cd{artist}/;
                          $out_string =~ s/%ti/$cdtrack[$i-1]/;
                          $out_string =~ s/%tr/$i/;
                          $out_string =~ s/%ab/$cd{title}/;
                          $out_string =~ s/%yr/$cd{year}/;

                          rip_cd($out_string, $i) if $config{USE_CDDB} == 1;
                          rip_cd($i, $i)          if $config{USE_CDDB} == 0;
               }
      }
}

# get/print cddb information from disc
sub cdinfo {

    query_disc();
    get_cddb_config();

    print "$name - $version\n\n";

    print "Artist = $cd{artist}\n";
    print "Album  = $cd{title}\n";
    print "Genre  = $cd{genre}\n";
    print "Tracks = $cd{tno}\n";
    print "Year   = $cd{year}\n\n";

    my $n = 1;

    foreach (@cdtrack) {
         print "Track $n: $_";
         print "\n";
         ++$n;
    }

    exit(0);
}

# version / license information
sub version {

print "
$name - $version

Copyright (C) 2005-2009 Philip Lyons

This is free software.  You may redistribute copies of it under the terms of
the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.
There is NO WARRANTY, to the extent permitted by law.

";

}

# help & longhelp menu
sub help_menu {

print "\n$name - $version\n";

print "
-t, --to             $lang{to}
-r, --recursive      $lang{recursive}
-p, --preserve       $lang{preserve}
-f, --formats        $lang{formats}
-o, --only           $lang{only}
-k, --keep           $lang{keep}
-h, --help           $lang{help}
-l, --longhelp       $lang{longhelp}
-v, --verbose        $lang{verbose}

";

print "$lang{user_opts}

--defopts            $lang{defopts}
--eopts              $lang{eopts}
--dopts              $lang{dopts}
--nopts              $lang{nopts}
--outfile            $lang{outfile}
--outdir             $lang{outdir}
--dryrun             $lang{dryrun}
--overwrite          $lang{overwrited}
--normalize          $lang{normalize}
--delete             $lang{delete}
--encoder            $lang{encoder}
--decoder            $lang{decoder}
--version            $lang{version}

$lang{enc_opts}

--bitrate            $lang{bitrate}
--freq               $lang{freq}
--channels           $lang{channels}
--effect             $lang{effect}
--fcomp              $lang{fcomp}
--pcomp              $lang{pcomp}
--acomp              $lang{acomp}
--oggqual            $lang{oggqual}
--spxqual            $lang{spxqual}
--aacqual            $lang{aacqual}
--mpcqual            $lang{mpcqual}
--ofmode             $lang{ofmode}
--ofopt              $lang{ofopt}
--bratio             $lang{bratio}
--bquanl             $lang{bquanl}
--bpsize             $lang{bpsize}

$lang{tag_opts}

--artist             $lang{artist}
--title              $lang{title}
--track              $lang{track}
--year               $lang{year}
--album              $lang{album}
--genre              $lang{genre}
--comment            $lang{comment}
--taginfo            $lang{taginfo}

$lang{tag_usage}

$lang{rip_opts}

--rip                $lang{rip}
--nocddb             $lang{nocddb}
--noinput            $lang{noinput}
--nscheme            $lang{nscheme}
--cdinfo             $lang{cdinfo}
--device             $lang{device}

$lang{rip_usage}

" if $longhelp;

print "$lang{usage}\n\n";

}

sub load_module {
    my ($file) = @_;
    my $return;
    unless($return = do $file) {
        perror("opening_file","$file: $@ $!") if $@ or !defined($return) or not !$return;
    }
}
                                                 
sub import_module {
    my ($format_ref) = @_;
    my $format_name=$format_ref->{NAME};
       $run{$format_name} = $format_ref;
}
               
# load all user modules
sub load_user_modules {
   my @imported_mods;
   opendir(DIR, "$mod_dir") or perror("opening_dir","$mod_dir: $!");
   foreach (readdir(DIR)) {
        load_module("$mod_dir/$_") if $_ !~ /^\./;
        push(@imported_mods, $_)   if $_ !~ /^\./;
   }
   closedir(DIR);
   print "$lang{imported} @imported_mods\n";
}

# place files/directories in their respective arrays
if (@ARGV) {
  foreach my $in (@ARGV) {
       if    (-f $in) { push(@file,$in); }
       elsif (-d $in) { push(@dir, $in); }
       else { perror("no_infile","$in"); } 
  }
}

load_user_modules() if $config{IMPORTM} == 1;

if ($to and @ARGV and not $rip) { proc_input(); } 

elsif ($to and $rip) { proc_cd(); }
elsif ($my_encoder and not $to) { show_encoders(); }
elsif ($my_decoder and not $to) { show_decoders(); }
elsif ($verinfo) { version(); }
elsif ($longhelp) { help_menu(); }
elsif ($formats) { show_formats(); }
elsif ($cdinfo) { cdinfo(); }
elsif ($taginfo and not $to) { show_taginfo(); }
elsif ($title or $track or $artist or $album or $comment or $year or $genre) { write_user_tags(); }

else { help_menu(); }

__END__

=head1 NAME

pacpl - Perl Audio Converter, a multi purpose converter/ripper/tagger

=head1 SYNOPSIS

pacpl --to <format> <options> [file(s)/directory(s)]

=head1 DESCRIPTION

Perl Audio Converter is a tool for converting multiple audio types from
one format to another. It supports AAC, AC3, AIFF, APE, AU, AVR, BONK,
CAF, CDR, FAP, FLA, FLAC, IRCAM, LA, LPAC, MAT, MAT4, MAT5, M4A, MMF,
MP2, MP3, MP4, MPC, MPP, NIST, OFR, OFS, OGG, PAC, PAF, PVF, RA, RAM,
RAW, SD2, SF, SHN, SMP, SND, SPX, TTA, VOC, W64, WAV, WMA, and WV.
It can also convert audio from the following video extensions: RM, RV,
ASF, DivX, MPG, MKV, MPEG, AVI, MOV, OGM, QT, VCD, SVCD, M4V, NSV, NUV,
PSP, SMK, VOB, FLV, and WMV. A CD ripping function with CDDB support,
batch conversion, tag preservation for most supported formats,
independent tag reading/writing, and extensions for Amarok, Dolphin,
and Konqueror are also provided.

=head1 OPTIONS

B<-t, --to> I<format>

set encode format for the input file(s) or directory(s).  you can see a
complete list of supported encode formats by using the B<--formats> option.

B<-r, --recursive>

recursively scan and convert input folder(s) to desination format.

B<-p, --preserve>

when recursively converting a directory, preserve the input folders directory 
structure in the specified output directory.  such as:

pacpl --to ogg -r -p /home/mp3s --outdir /home/oggs

in the example above, let's assume the directory structure was:

=over 21

=item /home/mp3s/Alternative

=item /home/mp3s/Alternative/New

=item /home/mp3s/Rap

=item /home/mp3s/Country

=item /home/mp3s/Techno/

=back

the output directory will now contain:

=over 4

=item /home/oggs/Alternative

=item /home/oggs/Alternative/New

=item /home/oggs/Rap

=item /home/oggs/Country

=item /home/oggs/Techno

=back

with all files in each sub-folder converted to ogg.

B<-o, --only> I<format>

only convert files matching extenstion.  this option is useful when you have
a directory or batch of files with mixed audio types and only need to
convert one specific type.

B<-k, --keep>

do not re-encode files with extensions matching the destination extension.  
instead...copy them to the output folder, or skip to the next file.

=over 4

=item B<-f, --formats>    show a list of supported encode/decode formats.

=item B<-h, --help>       show the short help menu.

=item B<-l, --longhelp>   display the complete list of options.

=item B<--version>        show version and licensing information.

=back

=head1 USER OPTIONS

B<--defopts> I<1/0>

to turn off default encoder options use --defopts 0. this option gives you 
more control when using the --eopts command.

defopts is set to 1 by default.  you can also toggle this option in
/etc/pacpl/pacpl.conf.

B<--eopts> I<options>

this option allows you to use encoder options not implemented by pacpl.
an example would be:

pacpl --to mp4 --eopts="-c 44100 -I 1,2" YourFile.mp3

B<--dopts> I<options>

this option allows you to use decoder options not implemented by pacpl.
an example would be:

pacpl --to mpc --dopts="--start 60" YourFile.ogg

B<--outfile> I<name>

set the output file name to I<name>.

B<--outdir> I<directory>

place all encoded files in I<directory>.

B<--dryrun>

show what would be done without actually converting anything.  this is a
good way to trouble shoot, or to see what would be done without actually
touching your files.

B<--overwrite>

replace the output file if it already exists.

B<--normalize>

adjusts volume levels of audio files.

B<--nopts> I<options>

this allows you to specify normalize options not implemented by pacpl

B<--delete>

remove source/input file after the conversion process has finished.

B<--encoder> I<encoder>

specify an alternate encoder for the selected output file(s)

you can see a list of supported encoders by using the following:

pacpl --encoder I<format>

B<--decoder> I<decoder>

specify an alternate decoder for the selected input file(s)

you can see a list of supported decoders by using the following:

pacpl --decoder I<format>

B<-v, --verbose>

show detailed information about the encoding process and what's actually
being done.

=head1 ENCODER OPTIONS

B<--bitrate> I<num>

set the bitrate to I<num>.  default is 128.  this option does not apply 
to all formats.

B<--freq> I<num>

set the frequency to I<num>.  default is 44100.  this option does not apply
to all formats.

B<--channels> I<num>

set the number of audio channels in the output file to I<num>.  this option
does not apply to all formats.

B<--effect> I<str>

see B<sox>(1).  this option only applies to the following:

AIFF, AU, SND, RAW, VOC, SMP, AVR, and CDR

B<--fcomp> I<num>

set flac compression level to I<num>. fastest -0, highest -8, default -2

B<--pcomp> I<num>

set lpac/pac compression level to I<num>.

=over 4

=item 1 = fast

=item 2 = simple

=item 3 = medium (default)

=item 4 = high

=item 5 = extra high,

=back

B<--acomp> I<num>

set ape compression level to I<num>.

=over 4

=item 1000 = fast

=item 2000 = normal

=item 3000 = high (default)

=item 4000 = extra high

=item 5000 = insane

=back

B<--oggqual> I<num>

set ogg quality level to I<num>. -1, very low and 10 very high, default 3

B<--spxqual> I<num>

set speex quality level to I<num>. 0-10, default 8

B<--aacqual> I<num>

set aac, mp4, m4a, or m4b quality level to I<num>. default 300

B<--mpcqual> I<str>

set mpc/mpp quality level to I<str>.

=over 4

=item thumb        low quality/internet, (typ.  58... 86 kbps)

=item radio        medium (MP3) quality, (typ. 112...152 kbps - default)

=item standard     high quality (dflt),  (typ. 142...184 kbps)

=item xtreme       extreme high quality, (typ. 168...212 kbps)

=back

B<--ofmode> I<str>

set ofr/ofs compression mode to I<str>.  normal, extra, and extranew modes
are recommended for general use.  available options are:

=over 4

=item fast

=item normal (default)

=item high

=item extra

=item best

=item highnew

=item extranew

=item bestnew

=back

B<--ofopt> I<str>

set ofr/ofs optimization level to I<str>.

specify the optimization level in the engine. In order to achieve
optimal compression at all sample types, sample rates, and audio
content, the core compression engine has the possibility to find the
optimal values for its parameters, at the cost of slightly increased
compression time only. The default recommended value is fast.
do not use normal (or even high or best) for this parameter unless
encoding time does not matter and you want to obtain the smallest
possible file for a given compression mode. The difference between
the optimize levels fast and best (which is up to three times slower
than fast) is very small, generally under 0.05%, but may be also
larger in some rare cases. Note that the none optimize level is
forced by the encoder to fast optimize level for the extra, best,
highnew, extranew, and bestnew modes.

available options are:

=over 4

=item none

=item fast (default)

=item normal

=item high

=item best

=back             

B<--bratio> I<num>

set bonk down sampling ratio.  default 2

B<--bquanl> I<num>

set bonk quantanization level.  default 1.0

B<--bpsize> I<num>

set bonk predictor size.  default 128

=head1 TAGGING OPTIONS

B<note:>

tagging outside of the encoding process can only be performed on the
following audio types:

=over 4

=item MP3

=item OGG

=item FLA

=item FLAC

=back

B<--artist> I<str>

set artist information to I<str>.

B<--title> I<str>

set title information to I<str>.

B<--track> I<num>

set track information to I<num>.

B<--year> I<num>

set year/date information to I<num>.

B<--album> I<str>

set album information to I<str>.

B<--genre> I<str>

set genre information to I<str>.

B<--comment> I<str>

set comment information to I<str>.

B<--taginfo> I<file>

show tagging information for I<file>.  multiple files can be specified
at once.

=head1 RIPPING OPTIONS

B<--rip> I<num/all>

rip selected tracks separated by comma or all from current disc.

=over 4

=item pacpl --rip all --to flac

=item pacpl --rip 1,3,9,15 --to flac 

=back

B<--nocddb>

disable cddb.  use this option if you do not want tagging based on cddb.

B<--noinput>

disable cddb interactivity.  this is enabled by default.

B<--nscheme> I<str>

set naming scheme to I<str>.  default is %ar - %ti

available options are:

=over 4

=item %ar = artist

=item %ti = song title

=item %tr = track

=item %yr = year

=item %ab = album

=back

B<eg:> --nscheme="(%tr)-%ti"

B<--device> I<str>

set device to I<str>.  default is /dev/cdrom

B<--cdinfo> 

show cddb information for current disc.

=head1 SEE ALSO

=over 4

=item B<sox>(1) B<ffmpeg>(1) B<lame>(1) B<oggenc>(1) B<oggdec>(1)

=item B<flac>(1) B<shorten>(1) B<faac>(1) B<faad>(1) B<mpcenc>(1)

=item B<mpcdec>(1) B<mplayer>(1) B<speexenc>(1) B<speexdec>(1)

=item B<sndfile-convert>(1) B<normalize>(1) B<cdparanoia>(1)

=back

=head1 BUGS

Report all bugs to Philip Lyons (viiron@gmail.com).

=head1 LICENSING

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 3 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, see <http://www.gnu.org/licenses/>.

=head1 AUTHOR

Copyright (C) 2005-2009 Philip Lyons (viiron@gmail.com)
