#!/usr/bin/perl -w ################################################################################ # # clip-proc.pl -- Run some standard post-production processing on an audio clip # written by Christopher Taylor # # this script requires: # ecasound [http://www.wakkanet.fi/~kaiv/ecasound/] # gogo [http://homepage1.nifty.com/herumi/gogo_e.html] # # please report bugs to Christopher Taylor # ################################################################################ ## ## NOTE: If the duplicate function where modified to check the infile before it ## copies, then mp3's would be fully supported as input files. Currently, ## mp3's are only supported as input files if the evaluation step gets ## executed. The duplicate function was created to speed up situations ## where the evaluation code could be skipped. If this is useful to ## someone, then tell me and I might add that functionality. If someone ## decides to write a similar script in C using libecasound, send me a ## copy of the source :) ## use strict; use Getopt::Long; use File::Copy; use POSIX; ################################################################################ ## Global Variables # the name of this program my $program_name = $0; $program_name =~ s#.*/##g; # usage test to display when invoked my $usage_text =<<__EOT__; Usage: $program_name [OPTIONS] INFILE [OUTFILE] Run some standard post-production processing on an audio clip. Copyright (C) 2000, theDial, All Rights Reserved. -t, --threshold=PERCENT minimum headroom below which, normalization won't be done -a, --amp_percent=PERCENT specify amplification for normalization step, overrides eval step -p, --peak_level=PERCENT specifies how hard the peak limiter is pushed. the default of 69% is good -l, --release_time=SECONDS used in compression step -f, --fastrate=REAL the compression ratio for the fast compressor. value of 1.0 is infinity to one, while the default 0.50 is 2:1. another really good value is special cased in the code: 0.25 is somewhat less than 2:1, and sounds super smooth -r, --rate=REAL the compression ratio for the entire compressor chain. the default is 1.0, and holds the volume very constant without many nasty side effects. however the dynamics in music are severly restricted, and a value of 0.5 might keep the music more intact -b, --bitrate=INT rate of playback for encoding -c, --codec=STRING name of codec (currently only 'mp3' is supported) --noeval skip the evaluation step --noencode skip the encoding step --nodelete do not delete the input file (input file deleted by default) --nonormalize skip the normalization step --nocompress skep the compression step --notime do not display total processing time --silent do not display any output --help display this message and exit --version display version information and exit Report bugs to __EOT__ # defaults to user-specifiable options my %OPTIONS = ( 'threshold' => 102, 'amp_percent' => 0, # must be set to '0' 'peak_level' => 0, 'release_time' => 0, 'fastrate' => '0', 'rate' => '0.5', 'bitrate' => 128, 'codec' => 'mp3', 'noencode' => 0, # must be set to '0' 'nodelete' => 0, # must be set to '0' 'nonormalize' => 0, # must be set to '0' 'nocompress' => 0, # must be set to '0' 'noeval' => 0, # must be set to '0' 'infile' => '', 'outfile' => '', 'notime' => 0, # must be set to '0' 'silent' => 0, # must be set to '0' 'version' => 0, # must be set to '0' 'help' => 0, # must be set to '0' ); # codecs my %CODECS = ( 'mp3' => 1, ); # system my $ecasound_path = "/usr/local/bin/ecasound"; my $mp3_encoder_path = "/usr/local/bin/gogo"; # version my $VERSION = '1.01'; ################################################################################ ## Main routine { # monitor elapsed time my $start_time = time (); # process and validate command line options process_options (); # do eval step? if (! $OPTIONS{'noeval'}) { $OPTIONS{'amp_percent'} = evaluate (); # the ecasound '-ea' option will round the amplify-% value to two decimal # points. This will cause clipping of the waveform if the amplify-% is at # the maximum and the '-ea' option rounds up # round _down_ to precesion of two decimal places $OPTIONS{'amp_percent'} = floor (100*$OPTIONS{'amp_percent'}) / 100; # check to see if amp_percent is above threshold if ($OPTIONS{'amp_percent'} < $OPTIONS{'threshold'}) { $OPTIONS{'nonormalize'} = 1; } } # create duplicate file? if ($OPTIONS{'noeval'} && ($OPTIONS{'infile'} ne $OPTIONS{'outfile'})) { duplicate (); } # do normalize step? if (! $OPTIONS{'nonormalize'}) { normalize () || die "Error during normalization step"; } # do compress step? if (! $OPTIONS{'nocompress'}) { compress () || die "Error during compression step"; } # do encoding step? if (! $OPTIONS{'noencode'}) { encode () || die "Error during encoding step"; } # cleanup cleanup (); # show time information show_elapsed ($start_time); # done } ################################################################################ ## Subroutines sub process_options { # get options GetOptions (\%OPTIONS, "threshold=s", "amp_percent=s", "peak_level=s", "fastrate=s", "rate=s", "bitrate=s", "codec=s", "silent+", "help+", "version+", "noeval+", "noencode+", "nodelete+", "nonormalize+", "nocompress+", "notime+", "release_time=s", "l=s", "r=s"); # insert values options with more than one name into appropriate slot if (defined ($OPTIONS{'l'})) { $OPTIONS{'release_time'} = $OPTIONS{'l'}; } if (defined ($OPTIONS{'r'})) { $OPTIONS{'rate'} = $OPTIONS{'r'}; } # Handel help and version requests if ($OPTIONS{'help'}) { print "$usage_text"; exit 0; } if ($OPTIONS{'version'}) { print "$program_name $VERSION\n"; exit 0; } # there must be exactly one or two arguments remaining if (! (($#ARGV == 0) || ($#ARGV == 1)) ) { print "$usage_text"; exit 0; } $OPTIONS{'infile'} = $ARGV[0]; $OPTIONS{'outfile'} = $ARGV[1] || $ARGV[0]; # override appropriate passed arguments and defaults # if normalize diabled, don't evaluate clip if ($OPTIONS{'nonormalize'}) { $OPTIONS{'noeval'} = 1; } # if amp_percent set by user, don't evaluate clip if ($OPTIONS{'amp_percent'}) { $OPTIONS{'noeval'} = 1; } # if amp_percent not set by user and evaluate disabled, don't normalize elsif ($OPTIONS{'noeval'}) { $OPTIONS{'nonormalize'} = 1; } # if the encode step is enabled, but the specified codec is unknown, die if (!$OPTIONS{'noencode'} && !$CODECS{$OPTIONS{'codec'}}) { die "Codec unknown"; } # if encode step disabled and infile equals outfile, enable nodelete if ($OPTIONS{'noencode'} && ($OPTIONS{'infile'} eq $OPTIONS{'outfile'})) { $OPTIONS{'nodelete'} = 1; } # remove extension from outfile and replace with .wav $OPTIONS{'outfile'} =~ s/(.*)\..+$/$1.wav/; } # Evaluate amplification possbile of audio file before clipping sub evaluate { # print progress info if (!$OPTIONS{'silent'}) { print "Evaluate: '" . $OPTIONS{'infile'} . "' --> '" . $OPTIONS{'outfile'} . "'\n"; } # call ecasound with the -ev (level test) option my $output = `$ecasound_path -i $OPTIONS{'infile'} -o $OPTIONS{'outfile'} -ev`; # extract amplification level $output =~ /signal can be amplified by (.+)%/; return $1; } # Create copy of file for processing sub duplicate { # print progress info if (!$OPTIONS{'silent'}) { print "Duplicate: '" . $OPTIONS{'infile'} . "' --> '" . $OPTIONS{'outfile'} . "'\n"; } copy ($OPTIONS{'infile'}, $OPTIONS{'outfile'}) || die "Could not copy file"; } # Normalize an audio file sub normalize { my $return_val = 1; # print progress info if (!$OPTIONS{'silent'}) { print "Normalize: Normalizing '" . $OPTIONS{'outfile'} . "' with...\n"; print " amplify-%: ". $OPTIONS{'amp_percent'} . "\n"; } # call ecasound with the -ea (normalization) option if (system ("$ecasound_path -i " . $OPTIONS{'outfile'} . " -o " . $OPTIONS{'outfile'} . " -ea:". $OPTIONS{'amp_percent'} . " > /dev/null 2>&1") != 0) { $return_val = 0; } return $return_val; } # Compress an audio file sub compress { my $return_val = 1; # print progress info if (!$OPTIONS{'silent'}) { print "Compress: Compressing '" . $OPTIONS{'outfile'} . "' with...\n"; print " peak-level: " . $OPTIONS{'peak_level'} . "\n"; print " release time: " . $OPTIONS{'release_time'} . "\n"; print " fastrate: " . $OPTIONS{'fastrate'} . "\n"; print " rate: " . $OPTIONS{'rate'} . "\n"; } # call ecasound with the -eca (compression) option if (system ("$ecasound_path -i " . $OPTIONS{'outfile'} . " -o " . $OPTIONS{'outfile'} . " -eca:" . $OPTIONS{'peak_level'} . "," . $OPTIONS{'release_time'} . "," . $OPTIONS{'fastrate'} . "," . $OPTIONS{'rate'} . " > /dev/null 2>&1") != 0) { $return_val = 0; } return $return_val; } # Encode a wav file into mp3 format sub encode { my $return_val = 1; my $encodedfile = $OPTIONS{'outfile'}; # replace wav extension with codec extension $encodedfile =~ s/\.wav$/.$OPTIONS{'codec'}/; # print progress info if (!$OPTIONS{'silent'}) { print "Encode: Encoding '" . $OPTIONS{'outfile'} . "' --> '$encodedfile' with...\n"; print " codec: " . $OPTIONS{'codec'} . "\n"; print " bitrate: " . $OPTIONS{'bitrate'} . "\n"; } # call lame compatible mp3 encoder (preferably gogo) if (system ("$mp3_encoder_path -b " . $OPTIONS{'bitrate'} . " " . $OPTIONS{'outfile'} . " $encodedfile > /dev/null 2>&1") != 0) { $return_val = 0; } return $return_val; } # print difference between current time and given time sub show_elapsed { my $given_time = shift; if (! ($OPTIONS{'notime'} || $OPTIONS{'silent'})) { my $elapsed_time = time () - $given_time; print "elapsed time: $elapsed_time\n"; } } # perform cleanup operations sub cleanup { # print progress info if (!$OPTIONS{'silent'}) { print "Cleanup:\n"; } # delete infile if delete enabled if (!$OPTIONS{'nodelete'}) { unlink ($OPTIONS{'infile'}); } # delete outfile wav if it was encoded and not same as infile if (!$OPTIONS{'noencode'} && ($OPTIONS{'infile'} ne $OPTIONS{'outfile'})) { unlink ($OPTIONS{'outfile'}); } }