#!/usr/bin/perl
#
#  Perl Audio Converter
#
#  Copyright (C) 2005-2013 Philip Lyons (vorzox@gmail.com)
#
#    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 Getopt::Long;
use File::Basename;
use File::Find;
use File::Spec::Functions qw(rel2abs);
use Parallel::ForkManager;

use feature qw(switch say);

no if $] >= 5.018, warnings => "experimental::smartmatch";

# Tagging modules
use MP3::Tag;
use Audio::FLAC::Header;
use Audio::Scan;

# 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 & icon information
my $name      = "Perl Audio Converter";
my $version   = "5.0.1";
my $icon_path = "/etc/pacpl/pacpl.png";

# If these options are toggled,  messages/errors will be displayed 
# via kdialog or zenity/notify-send (depending on environment)
my ($gui, $kde, $gnome);

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

# default configuration.
my %config = (

               IMPORTM      => 0,
               JOBS         => 4,

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

               BITRATE      => 128,
               FREQ         => 44100,
               CHANNELS     => 2,
               EFFECT       => '',
               FCOMP        => 2,
               PCOMP        => 3,
               ACOMP        => 3000,
               OGGQUAL      => 3,
               SPXQUAL      => 8,
               AACQUAL      => 100,
               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/dvd',
               NSCHEME      => '%ar - %ti',

               ZEN_DIR      => 1,
               ZEN_OPTS     => 1,               
               KDE_DIR      => 1,
               KDE_OPTS     => 1,

             );

# location of configuration file
my $conf_path = "/etc/pacpl";
my $po_dir    = "/usr/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;
        say "$lang{debug} $lang{loaded_config} $conf_file" if $debug == 1;
    }
}

say "$lang{no_config}" 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;
            say "$lang{debug} $k = $v" if $debug == 1 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);
       @codecs = grep(s/^\s*|\s*$//, @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) = @_;
    say "$name - $version\n";
    say "$lang{error}: $lang{$key} $msg" . "\n";
    system("kdialog --title \"$name\" --error \"$lang{error}: $lang{$key} $msg\" &") if $gui and $kde;
    system("zenity  --title \"$name\" --error \"$lang{error}: $lang{$key} $msg\" &") if $gui and $gnome;
    die "$lang{seek_help}\n";
}

# print notices/warnings
sub pnotice {

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

    say "$name - $version\n" if $banner == 1;
    print "$lang{$key} $msg";
    say ''    if $nm == 1;
    say "\n"  if $nm == 2;
      
    $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 $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;

my $jobs = $config{JOBS};

# 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,
	'j|jobs=n'    => \$jobs,

	'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,
	'kde'         => \$kde,
	'gnome'       => \$gnome,

	'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,
	'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;
my $total_converted = 0;
my $total_failed = 0;

# Initialize the fork manager with a count set through pacpl.conf or via the command line
my $pm = Parallel::ForkManager->new($jobs);

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

     '3g2' => {
        
                DEFAULT_ENCODER => 'ffmpeg',
                DEFAULT_DECODER => 'ffmpeg',
                
                ENCODER => { 
                             ffmpeg => {
                                         NAME => 'ffmpeg',
                                         ESTR => sub {
                                                       $opts = "-ar 8000 -ac 1" if $defopts == 1;
                                                       $opts = '' if $defopts == 0;
                                                       "-y -i %i $eopts $opts %o"
                                                     },
                                         
                                         PROMPT => { NONE => 0, },
                                       },
                                       
                             avconv => {
                                         NAME => 'avconv',
                                         ESTR => sub {
                                                       $opts = "-ar 8000 -ac 1" if $defopts == 1;
                                                       $opts = '' if $defopts == 0;
                                                       "-y -i %i $eopts $opts %o"
                                                     },
                                         
                                         PROMPT => { NONE => 0, },
                                       },
                                       
                           },
                           
                DECODER => {
                             ffmpeg => {
                                         NAME => 'ffmpeg',
                                         DSTR => sub { "-y -i %o $dopts %o" },                                         
                                       },
                                       
                             avconv => {
                                         NAME => 'avconv',
                                         DSTR => sub { "-y -i %o %dopts %o" },
                                       },
                           },
                           
                   TAGS => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
                   
               },
       
      '3gp' => {
        
                DEFAULT_ENCODER => 'ffmpeg',
                DEFAULT_DECODER => 'ffmpeg',
                
                ENCODER => { 
                             ffmpeg => {
                                         NAME => 'ffmpeg',
                                         ESTR => sub {
                                                       $opts = "-ar 8000 -ac 1" if $defopts == 1;
                                                       $opts = '' if $defopts == 0;
                                                       "-y -i %i $eopts $opts %o"
                                                     },
                                         
                                         PROMPT => { NONE => 0, },
                                       },
                                       
                             avconv => {
                                         NAME => 'avconv',
                                         ESTR => sub {
                                                       $opts = "-ar 8000 -ac 1" if $defopts == 1;
                                                       $opts = '' if $defopts == 0;
                                                       "-y -i %i $eopts $opts %o"
                                                     },
                                         
                                         PROMPT => { NONE => 0, },
                                       },
                                       
                           },
                           
                DECODER => {
                             ffmpeg => {
                                         NAME => 'ffmpeg',
                                         DSTR => sub { "-y -i %o $dopts %o" },
                                       },
                                       
                             avconv => {
                                         NAME => 'avconv',
                                         DSTR => sub { "-y -i %o %dopts %o" },
                                       },
                           },
                           
                   TAGS => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
                   
               },
			   
       aac  => {

                DEFAULT_ENCODER => "faac",
                DEFAULT_DECODER => "faad",
                
                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;
                                                      "-y -i %i $eopts $opts %o"
                                                    },
                                        
                                        PROMPT => {
                                                    BITRATE  => 1,
                                                    FREQ     => 1,
                                                    CHANNELS => 1,
                                                  },
                                      },
                                      
                            avconv => {
                                        NAME => 'avconv',
                                        ESTR => sub {
                                                      $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                      $opts = '' if $defopts == 1,
                                                    },
                                                    
                                        PROMPT => {
                                                    BITRATE  => 1,
                                                    FREQ     => 1,
                                                    CHANNELS => 1,
                                                  },
                                      },
                           },
                           
                DECODER => {
                              faad => {
                                        NAME => "faad",
                                        DSTR => sub { "$dopts -o %i %o" },
                                      },
                              
                            ffmpeg => {
                                        NAME => "ffmpeg",
                                        DSTR => sub { "$dopts -y -i %i %o" },
                                      },
                                      
                            avconv => {
                                        NAME => 'avconv',
                                        DSTR => sub { "$dopts -y -i %i %o" },
                                      },

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

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

        ac3 => {

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

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

       adts => {
   
                DEFAULT_ENCODER => 'ffmpeg',
                DEFAULT_DECODER => 'ffmpeg',
                
                ENCODER => {
                             ffmpeg => {
                                         NAME => 'ffmpeg',
                                         ESTR => sub {
                                                       $opts = "-ab $bitrate.k -ar $freq -ac $channels -codec:a libfaac" if $defopts == 1;
                                                       $opts = '' if $defopts == 0;
                                                       "-y -i %i $opts $eopts -strict -2 %o"
                                                     },
                                        PROMPT => {
                                                    BITRATE   => 1,
                                                    FREQ      => 1,
                                                    CHANNELS  => 1,
                                                  },
                                        },
                              
                              avconv => {
                                          NAME => 'avconv',
                                          ESTR => sub {
                                                        $opts = "-ab $bitrate.k -ar $freq -ac $channels -codec:a libfaac" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "-y -i %i $opts $eopts %o"
                                                      },
                                          
                                          PROMPT => {
                                                      BITRATE  => 1,
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },                                                    
                                        },
                           },
                                        
                DECODER => {
                             ffmpeg => {
                                         NAME => 'ffmpeg',
                                         DSTR => "$dopts -y -i %i %o",
                                       },
                             
                             avconv => {
                                         NAME => 'avconv',
                                         DSTR => "$dopts -y -i %i %o",
                                       },
                            },
  
                    TAGS => {
                              READ   => 0,
                              WRITE  => 0,
                              MODULE => undef,
                            },
               },

       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" },
                                     },
                                     
                           ffmpeg => {
                                       NAME => 'ffmpeg',
                                       DSTR => sub { "-y -i %i %dopts %o" },
                                     },
                                     
                           avconv => {
                                       NAME => 'avconv',
                                       DSTR => sub { "-y -i %i $dopts %o" },
                                     },
                           },

                TAGS    => {
                             READ   => 1,
                             WRITE  => 0,
                             MODULE => "Audio::Scan",
                           },
               },
               
        amr => {
        
                DEFAULT_ENCODER => "ffmpeg",
                DEFAULT_DECODER => "ffmpeg",
                
                ENCODER => { 
                             ffmpeg => {
                                         NAME => "ffmpeg",
                                         ESTR => sub {
                                                       $opts = "-ar 8000 -ac 1" if $defopts == 1;
                                                       $opts = '' if $defopts == 0;
                                                       "-y -i %i $eopts $opts %o"
                                                     },
                                         
                                         PROMPT => { NONE => 0, },
                                       },
                                       
                             avconv => {
                                         NAME => "avconv",
                                         ESTR => sub {
                                                       $opts = "-ar 8000 -ac 1" if $defopts == 1;
                                                       $opts = '' if $defopts == 0;
                                                       "-y -i %i $eopts $opts %o"
                                                     },
                                         
                                         PROMPT => { NONE => 0, },
                                       },
                                       
                           },
                           
                DECODER => {
                             ffmpeg => {
                                         NAME => 'ffmpeg',
                                         DSTR => sub { "-y -i %i $dopts %o" },
                                       },
                                       
                             avconv => {
                                         NAME => 'ffmpeg',
                                         DSTR => sub { "-y -i %i %dopts %o" },
                                       },
                           },
                           
                   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,
                           },
               },

        dts => {
	   
                DEFAULT_ENCODER => 'ffmpeg',
                DEFAULT_DECODER => 'ffmpeg',
                
                ENCODER => {
                             ffmpeg => {
                                         NAME => 'ffmpeg',
                                         ESTR => sub {
                                                       $opts = '' if $defopts == 1;
                                                       $opts = '' if $defopts == 0;
                                                       "-y -i %i $opts $eopts -strict -2 -codec:a libfaac %o"
                                                     },
       
                                           PROMPT => { NONE => 0, },
                                       },
                                       
                             avconv => {
                                         NAME => 'avconv',
                                         ESTR => sub {
                                                       $opts = '' if $defopts == 1 or $defopts == 0;
                                                       "-y -i %i $opts $eopts -codec:a libfaac %o",
                                                     },
                                           PROMPT => { NONE => 0, },
                                       },
                                       
                             dcaenc => {
                                         NAME => 'dcaenc',
                                         ESTR => sub {
                                                       $opts = "$bitrate" if $defopts == 0;
                                                       $opts = '' if $defopts == 1;
                                                       "%i %o $opts"
                                                     },
                                                     
                                         PROMPT => { NONE => 0, },
                                       },
                           },
                           
                DECODER => {
                             ffmpeg => {
                                         NAME => 'ffmpeg',
                                         DSTR => "$dopts -y -i %i %o",
                                       },
                                       
                             avconv => {
                                         NAME => 'avconv',
                                         DSTR => "$dopts -y -i %i %o",
                                       },
                           },

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

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

                             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;
                                                      "-y -i %i $eopts $opts %o" 
                                                    },
                                        
                                      PROMPT => {
                                                    BITRATE  => 1,
                                                    FREQ     => 1,
                                                    CHANNELS => 1,
                                                 },
                                      },
                                      
                            avconv => {
                                        NAME => 'avconv',
                                        ESTR => sub {
                                                      $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                      $opts = '' if $defopts == 1;
                                                      "-y -i %i $eopts $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" },
                                      },
                                    
                            avconv => {
                                        NAME => 'avconv',
                                        DSTR => sub { "$dopts -y -i %i %o" },
                                      },

                           mplayer => {
                                        NAME => "mplayer",
                                        DSTR => sub { "-vc null -vo null -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 null -vo null -ao pcm:file=%o %i" },
                                      },
                           },

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

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

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

                DEFAULT_ENCODER => "ffmpeg",
                DEFAULT_DECODER => "ffmpeg",
                
                ENCODER => {
                              ffmpeg => {
                                          NAME => 'ffmpeg',
                                          ESTR => sub {
                                                        $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "-y -i %i $eopts $opts %o"
                                                      },
                                          
                                          PROMPT => {
                                                      BITRATE  => 1,
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },
                                        
                              avconv => {
                                          NAME => 'avconv',
                                          ESTR => sub {
                                                        $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "-y -i %i $eopts $opts %o"
                                                      },
                                          PROMPT => {
                                                      BITRATE  => 1,
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },
                                        
                             toolame => {
                                          NAME => 'toolame',
                                          ESTR => sub {
                                                        $opts = "-b $bitrate" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "$eopts $opts %i %o"
                                                      },
                                                      
                                          PROMPT => {
                                                      BITRATE => 1,
                                                    },
                                        },
                          
                             twolame => {
                                          NAME => 'twolame',
                                          ESTR => sub {
                                                        $opts = "-b $bitrate -s $freq" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "$eopts $opts %i %o"
                                                      },
                                          
                                          PROMPT => {
                                                      BITRATE => 1,
                                                      FREQ    => 1,
                                                    },
                                        },
                                        
                                 sox => {
                                          NAME => 'sox',
                                          ESTR => sub {
                                                        $opts = "-c $channels -r $bitrate" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "%i $eopts $opts %o"
                                                      },

                                          PROMPT => {
                                                      BITRATE  => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },
                           },
                           
                DECODER => {
                              
                              ffmpeg => {
                                          NAME => 'ffmpeg',
                                          DSTR => "$dopts -y -i %i %o",
                                        },
                                        
                              avconv => {
                                          NAME => 'avconv',
                                          DSTR => "%dopts -y -i %i %o",
                                        },
                                        
                             mplayer => {
                                          NAME => 'mplayer',
                                          DSTR => "-vc null -vo null -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 },
                                      },

                            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,
                                                  },
                                      },
                                      
                            avconv => {
                                        NAME => 'avconv',
                                        ESTR => sub {
                                                      $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                      $opts = '' if $defopts == 0;
                                                      "-y -i %i $eopts $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" },
                                      },
                                      
                            avconv => {
                                        NAME => 'avconv',
                                        DSTR => sub { "$dopts -y -i %i %O" },
                                      },

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

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

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

      mp3hd => {
                
                DEFAULT_ENCODER => "mp3hdEncoder",
                DEFAULT_DECODER => "mp3hdDecoder",
                
                ENCODER => {
                              mp3hdEncoder => {
                                                NAME => "mp3hdEncoder",
                                                ESTR => sub {
                                                              $opts = "-br $bitrate" if $defopts == 1;
                                                              $opts = '' if $defopts == 0;
                                                              "$eopts $opts -if %i -of %o"
                                                            },
                                                PROMPT => { BITRATE => 1 },
                                              },
                           },

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

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

       mp4  => {

                DEFAULT_ENCODER => "faac",
                DEFAULT_DECODER => "faad",
                
                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;
                                                    "-y -i %i $eopts $opts %o"
                                                  },
                                                  
                                      PRIMPT => {
                                                  BITRATE  => 1,
                                                  FREQ     => 1,
                                                  CHANNELS => 1,
                                                },
                                    },
                                    
                          avconv => {
                                      NAME => 'avconv',
                                      ESTR => sub {
                                                    $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                    $opts = '' if $defopts == 0;
                                                    "-y -i %i $eopts $opts %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" },
                                    },
                                    
                          avconv => {
                                      NAME => 'avconv',
                                      DSTR => sub { "$dopts -y -i %i %o" },
                                    },

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

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

       m4a  => {

                DEFAULT_ENCODER => "faac",
                DEFAULT_DECODER => "faad",
                
                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 %o"
                                                  },
                                              
                                     PROMPT => {
                                                BITRATE  => 1,
                                                FREQ     => 1,
                                                CHANNELS => 1,
                                               },
                                     },
                                     
                           avconv => {
                                       NAME => 'avconv',
                                       ESTR => sub {
                                                     $opts = "-ab $bitrate.k -ar $freq -ac $channels" if $defopts == 1;
                                                     $opts = '' if $defopts == 0;
                                                     "-y -i %i $eopts $opts %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" },
                                     },
                                     
                           avconv => {
                                       NAME => 'avconv',
                                       DSTR => sub { "$dopts -y -i %i %o" },
                                     },

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

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

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

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

                          mplayer => {
                                       NAME => "mplayer",
                                       DSTR => sub { "$dopts -vc null -vo null -ao pcm:file=%o %i" },
                                     },
                                     
                           ffmpeg => {
                                       NAME => 'ffmpeg',
                                       DSTR => sub { "$dopts -y -i %i %o" },
                                     },
                                     
                           avconv => {
                                       NAME => 'avconv',
                                       DSTR => sub { "$dopts -y -i %i %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" },
                                      },
                                      
                            ffmpeg => {
                                        NAME => 'ffmpeg',
                                        DSTR => sub { "$dopts -y -i %i %o" },
                                      },
                                      
                            avconv => {
                                        NAME => 'avconv',
                                        DSTR => sub { "$dopts -y -i %i %o" },
                                      },

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

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

                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" },
                                      },
                                      
                            ffmpeg => {
                                        NAME => 'ffmpeg',
                                        DSTR => sub { "$dopts -y -i %i %o" },
                                      },
                            
                            avconv => {
                                        NAME => 'avconv',
                                        DSTR => sub { "$dopts -y -i %i %o" },
                                      },

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

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

       off => {
                DEFAULT_ENCODER => 'off',
                DEFAULT_DECODER => 'off',
                
                ENCODER => {
                             off => {
                                      NAME => 'off',
                                      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 => {
                             off => {
                                      NAME => 'off',
                                      DSTR => sub { "$dopts --overwrite --decode %i --output %o" },
                                    },
                           },

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

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

			   
        oga => {
                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"
                                                      },
                                                                        PROMPT => {
                                                      FREQ    => 1,
                                                      OGGQUAL => 1,
                                                    },
                                        },

                              ffmpeg => {
                                          NAME => "ffmpeg",
                                          ESTR => sub { 
                                                        $opts = "-ab $bitrate.k -ar $freq -ac $channels -codec:a libvorbis" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "-y -i %i $eopts $opts %o" 
                                                      },
                                          
                                          PROMPT => {
                                                      BITRATE  => 1,
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },
                              
                              avconv => {
                                          NAME => 'avconv',
                                          ESTR => sub {
                                                        $opts = "-ab $bitrate.k -ar $freq -ac $channels -codec:a libvorbis" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "-y -i %i $eopts $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" },
                                        },
                                        
                              avconv => {
                                          NAME => 'avconv',
                                          DSTR => sub { "$dopts -y -i %i %o" },
                                        },

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

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

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "vorbiscomment",
                           },
               }, 				
							  
        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 -codec:a libvorbis" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "-y -i %i $eopts $opts %o" 
                                                      },
                                          
                                          PROMPT => {
                                                      BITRATE  => 1,
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },
                              
                              avconv => {
                                          NAME => 'avconv',
                                          ESTR => sub {
                                                        $opts = "-ab $bitrate.k -ar $freq -ac $channels -codec:a libvorbis" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "-y -i %i $eopts $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" },
                                        },
                                        
                              avconv => {
                                          NAME => 'avconv',
                                          DSTR => sub { "$dopts -y -i %i %o" },
                                        },

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

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

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "vorbiscomment",
                           },
               },
 				
       opus => {
                
                DEFAULT_ENCODER => "opusenc",
                DEFAULT_DECODER => "opusdec",
                
                ENCODER => {
                             opusenc => {
                                          NAME => 'opusenc',
                                          ESTR => sub {
                                                        $opts = "--bitrate $bitrate" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "$eopts $opts %i %o"
                                                      },
                                         PROMTP => {
                                                     BITRATE => 1,
                                                   },
                                        },
                           },

                DECODER => {
                              opusdec => {
                                           NAME => 'opusdec',
                                           DSTR => sub { "--force-wav %i %o" },
                                           PROMPT => { NONE => 0 },
                                         },
                           },
   
                   TAGS => {
                             READ   => 0,
                             WRITE  => 0,
                             MODULE => undef,
                           },
               },

	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,
                           },
               },			   
			   
	 rm => {
        
                 DEFAULT_ENCODER => 'ffmpeg',
                 DEFAULT_DECODER => 'ffmpeg',
                 
                 ENCODER => {
                              ffmpeg => {
                                          NAME => 'ffmpeg',
                                          ESTR => sub { 
                                                        $opts = "-ar $freq -ac $channels" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "-y -i %i $eopts $opts %o" 
                                                      },
                                          
                                          PROMPT => {
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },
                                        
                              avconv => {
                                          NAME => 'avconv',
                                          ESTR => sub {
                                                        $opts = "-ar $freq -ac $channels" if $defopts == 1;
                                                        $opts = '' if $defopts == 0;
                                                        "-y -i %i $eopts $opts %o"
                                                      },
                                          
                                          PROMPT => {
                                                      FREQ     => 1,
                                                      CHANNELS => 1,
                                                    },
                                        },
                           },
                           
                DECODER => {
                             mplayer => {
                                          NAME => 'mplayer',
                                          DSTR => sub { "-vc null -vo null -ao pcm:file=%o %i" },
                                        },
                                        
                              ffmpeg => {
                                          NAME => 'ffmpeg',
                                          DSTR => sub { "$dopts -y -i %i %o" },
                                        },
                                        
                              avconv => {
                                          NAME => 'avconv',
                                          DSTR => sub { "$dopts -y -i %i %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,
                           },
               },

       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 },
                                          },
                                          
                                ffmpeg => {
                                            NAME => 'ffmpeg',
                                            ESTR => sub {
                                                          $opts = "--bitrate $bitrate.k" if $defopts == 1;
                                                          $opts = '' if $defopts == 0;
                                                          "-y -i %i $eopts $opts %o"
                                                        },
                                                        
                                            PROMPT => {
                                                        BITRATE => 1,
                                                      },
                                          },
                                          
                                avconv => {
                                            NAME => 'avconv',
                                            ESTR => sub {
                                                          $opts = "--bitrate $bitrate.k" if $defopts == 1;
                                                          $opts = '' if $defopts == 0;
                                                          "-y -i $eopts $opts %o"
                                                        },
                                            PROMPT => {
                                                        BITRATE => 1,
                                                      },
                                          },
                           },
                           
                DECODER => {
                              speexdec => {
                                            NAME => "speexdec",
                                            DSTR => sub { "$dopts %i %o" },
                                          },
                                          
                                ffmpeg => {
                                            NAME => 'ffmpeg',
                                            DSTR => sub { "$dopts -y -i %i %o" },
                                          },
                                          
                                avconv => {
                                            NAME => 'avconv',
                                            DSTR => sub { "$dopts -y -i %i %o" },
                                          },
                           },
                
                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => undef,
                           },
               },			   			   

       tta  => {

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

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

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

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

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

       wv   => {

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

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

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

                TAGS    => {
                             READ   => 1,
                             WRITE  => 1,
                             MODULE => "Audio::Scan",
                           },
               },
);

# supported ffmpeg/avconv formats (decode only)
my @ffmpeg_codecs = ( "mmf", "ra", "wma" );
my @ffmpeg_forks  = ( "avconv", "ffmpeg" );

foreach my $fork (@ffmpeg_forks) {

  foreach my $codec (@ffmpeg_codecs) {
  
      $run{$codec}{DEFAULT_ENCODER}      = $fork;
      $run{$codec}{DEFAULT_DECODER}      = $fork;
      $run{$codec}{ENCODER}{$fork}{NAME} = $fork;
      $run{$codec}{DECODER}{$fork}{NAME} = $fork;
      $run{$codec}{DECODER}{$fork}{DSTR} = sub {"-y %i $dopts %o"};
      $run{$codec}{ENCODER}{$fork}{ESTR} = sub {"-y -i %i $eopts -ab $bitrate.k -ar $freq -ac $channels %o"};
      $run{$codec}{TAGS}{READ}           = 0;
      $run{$codec}{TAGS}{WRITE}          = 0;
      $run{$codec}{TAGS}{MODULE}         = undef;
  }

}

# support reading of WMA tags via Audio::Scan
$run{wma}{TAGS}{READ}   = 1;
$run{wma}{TAGS}{MODULE} = "Audio::Scan";
   
# 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",
                     "ogv",   "webm",
                     
                     # decode only audio formats
                     "vqf",   "tak",
                   );

foreach (@movie_codecs) {
   $run{$_}{DEFAULT_ENCODER}        = undef;
   $run{$_}{DEFAULT_DECODER}        = "ffmpeg";
   $run{$_}{DECODER}{mplayer}{NAME} = "mplayer";
   $run{$_}{DECODER}{ffmpeg}{NAME}  = "ffmpeg";
   $run{$_}{DECODER}{mplayer}{DSTR} = sub { "$dopts -vc null -vo null -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",
                "rf64",
              );

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", "8svx", "amb",
                   "dat",  "dvms", "f32", "f64",  "fssd",
                   "gsrt", "hcom", "txw", "vms",  "al",
                   "sou",  "ima",  "prc", "cvu",
                   "maud", 
                 );

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 $first_message    = 1;
my %files;
my $number = 0;

# process all files and directories
sub proc_input {

   $to =~ tr/A-Z/a-z/;
   
   perror("bad_input","") unless(exists($run{$to}));   

   proc_preserve_dir($dir[0]) if @dir and $preserve; # process recursive directory preservation (sends to proc_files   
   proc_dirs() if @dir and not $preserve;            # process directories including recursively (sends to proc_files)
   proc_files() if @file;                            # process all files
   fork_files();                                  # start the conversion process.
}
   
# process all files (all roads lead to rome)
sub proc_files {
   
   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 (-e "$file.$to" and not $overwrite) {
                   pnotice("overwrite","$file.$to",1);
                   $total_failed++;
                   next;
               }
                                                                                                   
               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);
                   
                   $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 {
                            $files{"FILE$number"}{FILE} = "$dir/$file";
                            $files{"FILE$number"}{OUTF} = "$outdir/$outfile";
                            $files{"FILE$number"}{EXT}  = "$ext";
                            $files{"FILE$number"}{NAME} = "$file";
                            $files{"FILE$number"}{OUTD} = "$outfile";

                            $number++;                  
                   }
                 
               }                       
       }
   }
   undef(@file);
}

# process directorys and push all to @file arrary
sub proc_dirs {

   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_files();
    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_files();
                 return 0;

             } 
      }

    }   
}

# preserve directory structuer (revised 10-7-2013)
my $out_dir_original = rel2abs($outdir) if $outdir and -d $outdir;

sub proc_preserve_dir {

$topdir = rel2abs($dir[0]);

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

undef(@file); # clear the array before we begin.

find ( sub {
             my $val = $File::Find::name;
                $val =~ s/(\$)/\\\$/g;                
                             
             if ($val ne $topdir) {

                $val =~ s/$topdir\///;

                # create mirrored directories in output directory
                if (not -f fileparse("$topdir/$val") and not -d "$out_dir_original/$val") {
                      say "creating directory $out_dir_original/$val" if not -f "$topdir/$val" and $verbose;
                      mkdir("$out_dir_original/$val", 0755) if not -f "$topdir/$val";
                }                

                # this will list all files in their original directories
                if (-f "$topdir/$val") {
                     say "$lang{debug} pushing to \@file: $topdir/$val" if $debug == 1;
                     push(@file, "$topdir/$val");
                }
               
                # set $out_dir to the 
                $out_dir = "$out_dir_original/" . dirname($val) if -f "$topdir/$val";             
                
                # send file, and output dir to proc_files();
                proc_files();
             }                
                                                                
           }, $topdir
     );   
}                                                                                                                                                                   

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

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

       pnotice("debug","\n$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";
           say "";
           $banner = 0;
           pnotice("decode_failed","$inf.$infmt: try with --verbose",1);
           return 1;
       }
       
       # 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 $tag_opts = format_tags($to); # tag options for mp4/m4a/m4b/mpc/mpp/bonk/spx/wv
    
    my $encode_string = $run{$to}{ENCODER}{$encoder}{ESTR}->();
       $encode_string =~ s/%i/$tag_opts \"$outf.$$.wav\"/  if $to =~ /mpc|mpp|mp4|m4a/ and $encoder eq "faac" or $encoder eq "mpcenc";
       $encode_string =~ s/%i/\"$outf.$$.wav\" $tag_opts/  if $to =~ /mpc|mpp|mp4|m4a/ and $encoder eq "ffmpeg" or $encoder eq "avconv";
       $encode_string =~ s/%i/\"$outf.$$.wav\"/            if $to !~ /mpc|mpp|mp4|m4a/;
       $encode_string =~ s/%o/$tag_opts \"$outf.$to\"/     if $tag_opts ne " " and $to !~ /mpc|mpp|mp4|m4a/;
       $encode_string =~ s/%o/\"$outf.$to\"/               if $tag_opts eq " " or $to =~ /mpc|mpp|mp4|m4a/;
       $encode_string =~ s/(\$|\\)/\\$1/g;
       
       # read tag options from command line if present
       get_user_tags();

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

       # encode temporary WAV to desired output format.
       system("$run{$to}{ENCODER}{$encoder}{NAME} $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";
           say "";
           $banner = 0;
           pnotice("encode_failed","$outf.$to: $?",1);        
           return 1;
       }

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

$pm->run_on_finish 
( sub { 
        my $pid = shift; 
        my $exit_code = shift; 
        my $ident = shift;
                          
        if (-e "$ident.$to") {
          $total_converted++;
        }                             
        say "$lang{debug} PID: $pid EXIT_CODE: $exit_code IDENTITY: \"$ident.$to\"" if $debug == 1;
  } 
);

# convert all files that have been placed in the %files hash through various procs*
sub fork_files {

    say "\n$name - $version\n" if not $gui and $banner == 1;
  
    foreach (sort(keys(%files))) 
    {    
        $pm->start($files{$_}{OUTF}) and next;            
        my $msg = "$lang{converting}" . " " . basename($files{$_}{FILE}) . ".$files{$_}{EXT} -> $to";     
        say "$msg" if not $gui;        
        system("kdialog --icon $icon_path --title \"$name\" --passivepopup \"$msg\" 10 &") if $gui and $kde;
        system("notify-send \"$name\" \"$msg\" -t 35 -i $icon_path &") if $gui and $gnome;                          
        convert("$files{$_}{FILE}","$files{$_}{OUTF}","$files{$_}{EXT}","$files{$_}{NAME}","$files{$_}{OUTD}");            
        $pm->finish;
    }            

    $pm->wait_all_children;                      
    $total_failed = $number - $total_converted if $total_converted < $number;
    
    my $finished_msg = "\n$lang{total_converted} $total_converted, $lang{failed} $total_failed\n";
    
    say "$finished_msg" if not $gui;
    system("kdialog --icon $icon_path --title \"$name\" --passivepopup \"$finished_msg\" 10 &") if $gui and $kde;
    system("notify-send \"$name\" \"$finished_msg\" -t 35 -i $icon_path &") if $gui and $gnome;
    
}                   

# 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 $file = shift;
     my $dir  = shift;
     my $ext  = shift;

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

     unless(defined($run{$ext}{DECODER})) {
         pnotice("no_decoder","$ext",2) if $verbose or not $only;
         $total_failed++;
         return 1;
     }

     return 0;
}

########################################
# GUI options for KDE & Gnome Plugins  #
########################################

my $kdialog = "kdialog --title \"$name\"";
my $zenity  = "zenity  --title \"$name\" --list --radiolist --width 100 --height 300 --column '' --column ";

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 and $kde;
       $outdir    = `zenity --title \"$lang{gui_outdir}\" --file-selection --directory --filename=\`pwd\`` if $config{ZEN_DIR} == 1 and $gnome;

       chomp($outdir);
       $out_dir = $outdir;
       exit(1) if not $outdir;
       
       if ($config{KDE_OPTS} == 1 or $config{ZEN_OPTS} == 1) {
           foreach (keys %{$run{$to_fmt}{ENCODER}{$encoder}{PROMPT}}) {
                   prompt_user($_);
           }
       }
}

sub prompt_user {

    my $opt = shift;

      given ($opt) {

            when (/^BITRATE/)   { 
                                  $bitrate  = `$kdialog --default $bitrate --combobox \"$lang{gui_bitrate}\" 56 112 128 160 192 256 320` if $kde;
                                  $bitrate  = `$zenity \"$lang{gui_bitrate}\" FALSE 56 FALSE 112 FALSE 128 FALSE 160 TRUE 192 FALSE 256 FALSE 320` if $gnome;
                                  chomp($bitrate);
                                  exit(1) if not $bitrate;
                                }

            when (/^FREQ/)      {
                                  $freq = `$kdialog --default $freq --combobox \"$lang{gui_freq}\" 32000 44100 48000` if $kde;
                                  $freq = `$zenity \"$lang{gui_freq}\" FALSE 32000 TRUE 44100 FALSE 48000` if $gnome;
                                  chomp($freq);
                                  exit(1) if not $freq;
                                }

            when (/^CHANNELS/)  { 
                                  $channels = `$kdialog --default $channels --combobox \"$lang{gui_chans}\" 1 2` if $kde;
                                  $channels = `$zenity \"$lang{gui_chans}\" FALSE 1 TRUE 2` if $gnome;
                                  chomp($channels);
                                  exit(1) if not $channels;
                                }

            when (/^FCOMP/)    	{
                                  $fcomp = `$kdialog --default $fcomp --combobox \"$lang{gui_fcomp}\" 0 1 2 3 4 5 6 7 8` if $kde;
                                  $fcomp = `$zenity \"$lang{gui_fcomp}\" FALSE 0 FALSE 1 TRUE 2 FALSE 3 FALSE 4 FALSE 5 FALSE 6 FALSE 7 FALSE 8` if $gnome;
                                  chomp($fcomp);
                                  exit(1) if not $fcomp;
                                }

            when (/^ACOMP/)   	{
                                  $acomp = `$kdialog --default $acomp --combobox \"$lang{gui_acomp}\" 1000 2000 3000 4000 5000` if $kde;
                                  $acomp = `$zenity \"$lang{gui_acomp}\" FALSE 1000 FALSE 2000 TRUE 3000 FALSE 4000 FALSE 5000` if $gnome;
                                  chomp($acomp);
                                  exit(1) if not $acomp;
                                }

            when (/^OGGQUAL/)   {
                                  $oggqual = `$kdialog --default $oggqual --combobox \"$lang{gui_oggqual}\" 0 1 2 3 4 5 6 7 8 9 10` if $kde;
                                  $oggqual = `$zenity \"$lang{gui_oggqual}\" FALSE 0 FALSE 1 FALSE 2 TRUE 3 FALSE 4 FALSE 5 FALSE 6 FALSE 7 FALSE 8 FALSE 9 FALSE 10` if $gnome;
                                  chomp($oggqual);
                                  exit(1) if not $oggqual;
                                }
                             
            when (/^SPXQUAL/)   {
                                  $spxqual = `$kdialog --default $spxqual --combobox \"$lang{gui_spxqual}\" 0 1 2 3 4 5 6 7 8 9 10` if $kde;
                                  $spxqual = `$zenity \"$lang{gui_spxqual}\" FALSE 0 FALSE 1 FALSE 2 FALSE 3 FALSE 4 FALSE 5 FALSE 6 FALSE 7 TRUE 8 FALSE 9 FALSE 10` if $gnome;
                                  chomp($spxqual);
                                  exit(1) if not $spxqual;
                                }

            when (/^AACQUAL/)   {
                                  $aacqual = `$kdialog --default $aacqual --combobox \"$lang{gui_aacqual}\" 100 200 300 400 500` if $kde;
                                  $aacqual = `$zenity \"$lang{gui_aacqual}\" TRUE 100 FALSE 200 FALSE 300 FALSE 400 FALSE 500` if $gnome;
                                  chomp($aacqual);
                                  exit(1) if not $aacqual;
                                }

            when (/^MPCQUAL/)   {
                                  $mpcqual = `$kdialog --default $mpcqual --combobox \"$lang{gui_mpcqual}\" thumb radio standard xtreme insane braindead` if $kde;
                                  $mpcqual = `$zenity \"$lang{gui_mpcqual}\" FALSE thumb TRUE radio FALSE standard FALSE xtreme FALSE insane FALSE braindead` if $gnome;
                                  chomp($mpcqual);
                                  exit(1) if not $mpcqual;
                                }

            when (/^OFMODE/ )   {
                                   $ofmode = `$kdialog --default $ofmode --combobox \"$lang{gui_ofmode}\" fast normal high extra best highnew extranew bestnew` if $kde;
                                   $ofmode = `$zenity \"$lang{gui_ofmode}\" FALSE fast TRUE normal FALSE high FALSE extra FALSE best FALSE highnew FALSE extranew FALSE bestnew` if $gnome;
                                   chomp($ofmode);
                                   exit(1) if not $ofmode;
                                }

            when (/^OFOPT/)     {
                                   $ofopt = `$kdialog --default $ofopt --combobox \"$lang{gui_ofopt}\" none fast normal high best` if $kde;
                                   $ofopt = `$zenity \"$lang{gui_ofopt}\" FALSE none TRUE fast FALSE normal FALSE high FALSE best` if $gnome;
                                   chomp($ofopt);
                                   exit(1) if not $ofopt;
                                }

            when (/^BRATIO/)    {
                                   $bratio = `$kdialog --default $bratio --combobox \"$lang{gui_bratio}\" 1 2 3 4 5` if $kde;
                                   $bratio = `$zenity \"$lang{gui_bratio}\" FALSE 1 TRUE 2 FALSE 3 FALSE 4 FALSE 5` if $gnome;
                                   chomp($bratio);
                                   exit(1) if not $bratio;
                                }

            when (/^BQUANL/)    {
                                   $bquanl = `$kdialog --default $bquanl --combobox \"$lang{gui_bquanl}\" 1.00 1.25 1.50 1.75 2.00` if $kde;
                                   $bquanl = `$zenity \"$lang{gui_bquanl}\" TRUE 1.00 FALSE 1.25 FALSE 1.50 FALSE 1.75 FALSE 2.00` if $gnome;
                                   chomp($bquanl);
                                   exit(1) if not $bquanl;
                                }

            when (/^BPSIZE/)    {
                                   $bpsize = `$kdialog --default $bpsize --combobox \"$lang{gui_bpsize}\" 10 128` if $kde;
                                   $bpsize = `$zenity \"$lang{gui_bpsize}\" FALSE 10 TRUE 128` if $gnome;
                                   chomp($bpsize);
                                   exit(1) if not $bpsize;
                                }

            default { 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;

    given ($type) {
        
        when (/^mp4$|^m4a$|^m4b$/) { 
        
            if ($run{$to}{ENCODER}{$encoder}{NAME} eq "ffmpeg" or $run{$to}{ENCODER}{$encoder}{NAME} eq "avconv") {
     
                $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 ARTIST=\"$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}\"";

              }
        } 
                
        when (/^mpc$|^mpp$/) { return "--artist \"$tag_name{artist}\" --title \"$tag_name{title}\" --album \"$tag_name{album}\" --track \"$tag_name{track}\" --genre \"$tag_name{genre}\" --year \"$tag_name{year}\" --comment \"$tag_name{comment}\""; }
        when (/^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}\""; }
        when (/^bonk$/)  { return "-t \"$tag_name{title}\" -a \"$tag_name{artist}\" -c \"$tag_name{comment}\""; }
        when (/^spx$/)   { return "--author \"$tag_name{artist}\" --title \"$tag_name{title}\" --comment \"year=$tag_name{year}\" --comment \"tracknumber=$tag_name{track}\" --comment \"album=$tag_name{album}\" --comment \"genre=$tag_name{genre}\" --comment \"comment=$tag_name{comment}\""; }

        default          { 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   = shift;
    my $in_format = shift;

    my $tag_module = $run{$in_format}{TAGS}{MODULE};

    given ($in_format) {

        when (/^mp3$|^mp3hd$/) {
                                  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;
                                }

           when (/^ogg$|^oga$/) {
                                   my $ogg_tag = Audio::Scan->scan_tags($in_file)->{tags};

                                      $tag_name{title}   = $ogg_tag->{TITLE}       if $ogg_tag->{TITLE};
                                      $tag_name{track}   = $ogg_tag->{TRACKNUMBER} if $ogg_tag->{TRACKNUMBER};
                                      $tag_name{artist}  = $ogg_tag->{ARTIST}      if $ogg_tag->{ARTIST};
                                      $tag_name{album}   = $ogg_tag->{ALBUM}       if $ogg_tag->{ALBUM};
                                      $tag_name{comment} = $ogg_tag->{COMMENT}     if $ogg_tag->{COMMENT};
                                      $tag_name{year}    = $ogg_tag->{YEAR}        if $ogg_tag->{YEAR};
                                      $tag_name{genre}   = $ogg_tag->{GENRE}       if $ogg_tag->{GENRE};                                    
                             
                                      return 0;
                                }
                       
                 when (/^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;
                                           $tag_name{track}  = $v if $k =~ /^tracknumber/i;                               
                                   }
                            
                                   unlink("tmp-$$.wav");
                                   return 0;
                                }

          when (/^flac$|^fla$/) {
                                       my $flac_tag = Audio::Scan->scan_tags($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->{COMMENT}     if $flac_tag->{COMMENT};
                                          $tag_name{year}    = $flac_tag->{DATE}        if $flac_tag->{DATE};       
                                          $tag_name{genre}   = $flac_tag->{GENRE}       if $flac_tag->{GENRE};

                                        return 0;
                                }

     when (/^mp4$|^m4a$|^m4b$/) {    
                                        my $mp4_tag = $tag_module->scan_tags($in_file)->{tags};

                                           $tag_name{title}   = $mp4_tag->{NAM}    if $mp4_tag->{NAM};
                                           $tag_name{track}   = $mp4_tag->{TRKN}   if $mp4_tag->{TRKN}; 
                                           $tag_name{artist}  = $mp4_tag->{ART}    if $mp4_tag->{ART};
                                           $tag_name{album}   = $mp4_tag->{ALB}    if $mp4_tag->{ALB};
                                           $tag_name{comment} = $mp4_tag->{CMT}    if $mp4_tag->{CMT};
                                           $tag_name{year}    = $mp4_tag->{DAY}    if $mp4_tag->{DAY};
                                           $tag_name{genre}   = $mp4_tag->{GNRE}   if $mp4_tag->{GNRE};
 
                                        return 0;
                                }

when (/^ape$|^mpc$|^mpp$|^wv$/)	{
                                        my $audio_tag = $tag_module->scan_tags($in_file)->{tags};

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

                 when (/^wma$/) {
                                        my $wma_tag = $tag_module->scan_tags($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;
                                }
                         
        default { return 1; }
    }
}

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

    my $out_file   = shift;
    my $out_format = shift;

    my $tag_m;

    given ($out_format) {

       when (/^mp3$|^mp3hd$/) {
                                  $tag_m = MP3::Tag->config(write_v24 => 'YES');
                                  $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;

                              }

         when (/^ogg$|^oga$/) {
                                  $tag_m = '';
                                  
                                  $tag_m = "$tag_m -t \"TITLE=$tag_name{title}\""       if $tag_name{title};
                                  $tag_m = "$tag_m -t \"TRACKNUMBER=$tag_name{track}\"" if $tag_name{track};
                                  $tag_m = "$tag_m -t \"ARTIST=$tag_name{artist}\""     if $tag_name{artist};
                                  $tag_m = "$tag_m -t \"ALBUM=$tag_name{album}\""       if $tag_name{album};
                                  $tag_m = "$tag_m -t \"COMMENT=$tag_name{comment}\""   if $tag_name{comment};
                                  $tag_m = "$tag_m -t \"YEAR=$tag_name{year}\""         if $tag_name{year};
                                  $tag_m = "$tag_m -t \"GENRE=$tag_name{genre}\""       if $tag_name{genre};
                            
                                  system("vorbiscomment -w $tag_m \"$out_file\"") if $tag_m ne ''; 
                            
                                  return 0;
                              }

       when (/^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->{COMMENT}     = "$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;
                              }

        default { 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");

         say '';

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

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

         say '';

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

    say '';
    say "$name - $version\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");
             say "$lang{write_tag_i} $file.$ext";

         } else { perror("no_write_tag","$file.$ext"); }
    }
    say '';
}

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

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

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

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

# show encoders for output format
sub show_encoders {
    say "\n$name - $version\n\n$lang{show_encoders}: $my_encoder\n";
    foreach (sort(keys %{$run{$my_encoder}{ENCODER}})) {        
        print "$_ -> ";
        print "installed" if `which $_ 2>/dev/null`;
        print "not found" if not `which $_ 2>/dev/null`;
        print " (default)" if $_ eq $run{$my_encoder}{DEFAULT_ENCODER};
        say '';
    }
    say '';
}

# show decoders for input format
sub show_decoders {
    say "\n$name - $version\n\n$lang{show_decoders}: $my_decoder\n";
    foreach (sort(keys %{$run{$my_decoder}{DECODER}})) {
        print "$_ -> ";
        print "installed" if `which $_ 2>/dev/null`;       
        print " (default)" if $_ eq $run{$my_decoder}{DEFAULT_DECODER};
        say '';
    }
    say '';
}

#################
# 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};
      
      print $ripconfig{CDDB_HOST};

      %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 > /dev/null 2>&1");
    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) = @_;

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

        say "\ncdparanoia -d $device $tno $on.wav\n"  if $dryrun;
        system("cdparanoia -d $device $tno \"$on.wav\" >/dev/null 2>&1")  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();

    say "$name - $version\n";

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

    my $n = 1;

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

    exit(0);
}

# version / license information
sub version {

say "
$name - $version

Copyright (C) 2005-2013 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 {

say "\n$name - $version";

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

say "$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}
--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;

say "\n$lang{usage}\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);
   say "$lang{imported} @imported_mods";
}

# place files/directories in their respective arrays
if (@ARGV) {
  foreach (@ARGV) {
       if    (-f $_) { push(@file,$_); print "$lang{debug} adding $_ to \@file\n" if $debug == 1; }
       elsif (-d $_) { push(@dir, $_); print "$lang{debug} adding $_ to \@dir\n"  if $debug == 1; }
       else { perror("no_infile","$_"); } 
  }
}

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

A Linux CLI tool for converting multiple audio types from one format to another.

It supports the following audio formats:

========================================

3G2, 3GP, 8SVX, AAC, AC3, ADTS, AIFF, AL, AMB, AMR, APE, AU, 
AVR, BONK, CAF, CDR, CVU, DAT, DTS, DVMS, F32, F64, FAP, FLA, 
FLAC, FSSD, GSRT, HCOM, IMA, IRCAM, LA, MAT, MAUD, MAT4, MAT5, 
M4A, MP2, MP3, MP4, MPC, MPP, NIST, OFF, OFR, OFS, OPUS, OGA, 
OGG, PAF, PRC, PVF, RA, RAM, RAW, RF64, SD2, SF, SHN, SMP, SND, 
SOU, SPX, SRN, TAK, TTA, TXW, VOC, VMS, VQF, W64, WAV, WMA, 
and WV.

It can also extract audio from the following video extensions:

==============================================================

RM, RV, ASF, DivX, MPG, MKV, MPEG, AVI, MOV, OGM, OGV, QT, VCD, 
SVCD, M4V, NSV, NUV, PSP, SMK, VOB, FLV, WEBM and WMV.

Parallel Processing, a CD ripping function with CDDB support, batch conversion, 
tag preservation for most supported formats, independent tag reading & writing, 
service menus for KDE Dolphin/Konqueror, Gnome Nautilus script, and action 
scripts for Nemo/Thunar 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.

B<-j, --jobs>

number of simultanious jobs to run at once.  the default limit
is set to 4 in pacpl.conf.  You can modify this value on the fly 
using --jobs=<num> or set it perminately in the configuration 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

=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 (use --bitrate <num> when using ffmpeg/avconv as encoder)

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 off/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 off/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<device>

set device to I<device>.  default is /dev/dvd

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)

=item B<opusenc>(1) B<opusdec>(1) B<wavpack>(1) B<ffmpeg>(1)

=item B<mplayer>(1) B<avconv>(1)

=back

=head1 BUGS

Report all bugs to Philip Lyons (vorzox@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-2013 Philip Lyons (vorzox@gmail.com)
