3
# Copyright (C) 1999--2002 Chris Vaill
4
# This file is part of normalize.
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2, or (at your option)
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
#######################################################################
22
# These variables may be customized for local setup
23
#######################################################################
25
# %m becomes name of mp3 or ogg file
26
# %w becomes name of temporary WAV file
27
# %b becomes bitrate of re-encoded file, as specified by the -b option
28
# Example: $OGGENCODE="oggenc -Q -b %b -o %m %w"
30
$MP3DECODE = "@MP3DECODE@";
31
$MP3ENCODE = "@MP3ENCODE@";
32
$OGGDECODE = "@OGGDECODE@";
33
$OGGENCODE = "@OGGENCODE@";
34
$FLACDECODE = "@FLACDECODE@";
35
$FLACENCODE = "@FLACENCODE@";
37
# The %w etc. substitutions should *not* be used in the following, as
38
# this script knows about their options already.
39
$VORBISCOMMENT = "@VORBISCOMMENT@";
40
$METAFLAC = "@METAFLAC@";
42
# change this if normalize is not on your path
43
$NORMALIZE = "normalize";
46
#######################################################################
47
# No user serviceable parts below
48
#######################################################################
54
Usage: $progname [OPTION]... [FILE]...
55
Normalize volume of mp3, ogg, or flac files by decoding, running
56
normalize, and re-encoding. This requires as much extra disk space
57
as the largest file, decoded. Note that for batch and mix mode, all
58
files must be decoded, so there must be enough disk space for the
59
decoded copies of all specified files.
64
-T THR |_ These arguments are passed as arguments to normalize.
65
-b | Run "normalize --help" for more info.
70
--bitrate BR Set bitrate of re-encoded file [default 128]
71
--tmpdir TMP Put temporary WAV files in temp directory TMP
72
--notags Do not copy ID3 or ogg tags to the output file
73
--force-encode Re-encode even if file is already normalized
74
--backup Keep backups of original files, suffixed with '~'
76
Force output format (this disables copying of comment tags):
78
--ogg Convert files to ogg, regardless of original format
79
--mp3 Convert files to mp3, regardless of original format
80
--flac Convert files to flac, regardless of original format
82
The following four options may be used to set the encoder and
83
decoder commands for mp3 and ogg vorbis. \%m is expanded to the
84
name of the mp3 or vorbis file, \%w expands to the name of the
85
temporary WAV file, and \%b expands to the bitrate, as specified by
86
the --bitrate option. The default values are shown in brackets
89
--mp3encode=X mp3 encoder [$MP3ENCODE]
90
--mp3decode=X mp3 decoder [$MP3DECODE]
91
--oggencode=X ogg vorbis encoder [$OGGENCODE]
92
--oggdecode=X ogg vorbis decoder [$OGGDECODE]
94
-h Display this help and exit.
95
-V Display version information and exit.
97
Report bugs to <cvaill\@cs.columbia.edu>.
102
# same effect as a backtick, but shell metacharacters are not expanded
103
sub backtick_noshell {
106
defined(my $pid = open(BABY, "-|")) || die "Can't fork: $!, stopped";
108
local $SIG{INT} = 'IGNORE';
114
exec(@args) || die "Can't exec $args[0], stopped";
122
my ($retval, $vorbis_tag, $id3v1_tag, $id3v2_tag, $id3v2_sz);
124
if ($fname =~ /\.ogg$/i) {
125
$vorbis_tag = backtick_noshell($VORBISCOMMENT, $fname);
126
defined($vorbis_tag) || die "Can't run vorbiscomment: $!, stopped";
127
$retval = [ 'ogg', $vorbis_tag ];
129
} elsif ($fname =~ /\.mp3$/i) {
130
open(IN, $fname) || die "Can't read $fname: $!, stopped";
131
# read ID3v2 tag, if it's there
132
# FIXME: doesn't work for ID3v2.4.0 appended tags
133
read(IN, $id3v2_tag, 3);
134
if ($id3v2_tag eq "ID3") {
135
read(IN, $id3v2_tag, 7, 3);
137
my ($x1, $x2, $x3, $x4) = unpack("x6 C C C C", $id3v2_tag);
145
read(IN, $id3v2_tag, $tagsz, 10);
146
$id3v2_sz = $tagsz + 10;
151
# read ID3v1 tag, if it's there
153
read(IN, $id3v1_tag, 3);
154
if ($id3v1_tag eq "TAG") {
155
read(IN, $id3v1_tag, 125, 3);
161
$retval = [ 'id3', $id3v1_tag, $id3v2_tag, $id3v2_sz ];
163
$retval = [ 'none' ];
171
my ($fname, $tag) = @_;
173
if ($fname =~ /\.ogg$/i) {
174
if ($tag->[0] eq 'ogg' && $tag->[1]) {
175
my @args = ($VORBISCOMMENT, "-a", $fname);
176
defined(my $pid = open(BABY, "|-"))
177
|| die "Can't fork: $!, stopped";
179
local $SIG{INT} = 'IGNORE';
180
print BABY $tag->[1];
182
$? == 0 || die "Error running vorbiscomment, stopped";
184
exec(@args) || die "Can't run vorbiscomment: $!, stopped";
189
} elsif ($fname =~ /\.mp3$/i) {
190
if ($tag->[0] eq 'id3' && $tag->[1]) {
191
my $id3v1_tag = $tag->[1];
192
open(OUT, ">>".$fname)
193
|| die "Can't append tag to $fname: $!, stopped";
194
syswrite(OUT, $id3v1_tag, 128);
197
if ($tag->[0] eq 'id3' && $tag->[2]) {
199
my $id3v2_tag = $tag->[2];
200
my $id3v2_sz = $tag->[3];
203
$tmpfile = $tmpdir.$progname."-".$n.".tag";
204
if (sysopen(OUT, $tmpfile, O_WRONLY|O_CREAT|O_EXCL)) {
207
$! == EEXIST || die "Can't write $tmpfile: $!, stopped";
210
syswrite(OUT, $id3v2_tag, $id3v2_sz);
211
open(IN, $fname) || die "Can't read $fname: $!, stopped";
212
while ($ret = sysread(IN, $buf, 4096)) {
213
syswrite(OUT, $buf, $ret);
218
rename($tmpfile, $fname)
219
|| die "Can't rename temp file, leaving in $tmpfile, stopped";
229
@_ = split(/:/, $ENV{PATH});
231
($_ .= "/") unless (/\/$/);
232
$fullpath = $_.$prog;
244
$path = find_prog("madplay");
245
if ($path) { $path .= " -q -o %w %m"; }
247
$path = find_prog("mpg123");
248
if ($path) { $path .= " -q -w %w %m"; }
250
if ($path) { $MP3DECODE = $path; }
255
$path = find_prog("lame");
257
$path = find_prog("notlame");
259
if ($path) { $path .= " --quiet -h -b %b %w %m"; }
261
$path = find_prog("bladeenc");
262
if ($path) { $path .= " -quiet %w %m"; }
264
if ($path) { $MP3ENCODE = $path; }
269
$path = find_prog("oggdec");
270
if ($path) { $path .= " -Q -o %w %m"; }
272
$path = find_prog("ogg123");
273
if ($path) { $path .= " -q -d wav -f %w %m"; }
275
if ($path) { $OGGDECODE = $path; }
280
$path = find_prog("oggenc");
282
$path .= " -Q -b %b -o %m %w";
287
sub find_vorbiscomment {
289
$path = find_prog("vorbiscomment");
290
if ($path) { $VORBISCOMMENT = $path; }
293
sub find_flacdecode {
295
$path = find_prog("flac");
297
$path .= " -s -d -o %w %m";
302
sub find_flacencode {
304
$path = find_prog("flac");
306
$path .= " -s -o %m %w";
313
$path = find_prog("metaflac");
314
if ($path) { $METAFLAC = $path; }
318
($progname = $0) =~ s/.*\///;
319
$version = "@VERSION@";
322
# default option values
323
@normalize_args = ($NORMALIZE, "--frontend", "-T", "0.25");
335
# we track verbosity separately for this script
339
# for any helper programs that haven't been specified statically at
340
# the top of this file, try to set them dynamically
341
find_mp3decode unless ($MP3DECODE);
342
find_mp3encode unless ($MP3ENCODE);
343
find_oggdecode unless ($OGGDECODE);
344
find_oggencode unless ($OGGENCODE);
345
find_vorbiscomment unless ($VORBISCOMMENT);
346
find_flacdecode unless ($FLACDECODE);
347
find_flacencode unless ($FLACENCODE);
351
# step through arguments
355
if ($ARGV[0] =~ /^-/ && !$nomoreoptions) {
358
if ($_ eq "-a" || $_ eq "--amplitude") {
359
if ($#ARGV < 1) { print "$progname: option $_ requires an argument\n"; exit 1; }
360
push @normalize_args, "-a", $ARGV[1];
361
shift; shift; next ARG_LOOP;
362
} elsif ($_ eq "--bitrate") {
363
if ($#ARGV < 1) { print "$progname: option $_ requires an argument\n"; exit 1; }
365
shift; shift; next ARG_LOOP;
366
} elsif ($_ eq "-g" || $_ eq "--gain") {
367
if ($#ARGV < 1) { print "$progname: option $_ requires an argument\n"; exit 1; }
368
push @normalize_args, "-g", $ARGV[1];
369
shift; shift; next ARG_LOOP;
370
} elsif ($_ eq "-n" || $_ eq "--no-adjust") {
371
push @normalize_args, "-n";
373
shift; next ARG_LOOP;
374
} elsif ($_ eq "-T" || $_ eq "--adjust-threshold") {
375
if ($#ARGV < 1) { print "$progname: option $_ requires an argument\n"; exit 1; }
376
push @normalize_args, "-T", $ARGV[1];
377
shift; shift; next ARG_LOOP;
378
} elsif ($_ eq "--fractions") {
379
push @normalize_args, "--fractions";
380
shift; next ARG_LOOP;
381
} elsif ($_ eq "--tmp" || $_ eq "--tmpdir") {
382
if ($#ARGV < 1) { print "$progname: option $_ requires an argument\n"; exit 1; }
384
unless (-d $tmpdir) { print "$progname: $tmpdir: no such directory\n"; exit 1; }
385
if ($tmpdir !~ /\/$/) {
386
$tmpdir = $tmpdir."/";
388
shift; shift; next ARG_LOOP;
389
} elsif ($_ eq "-v" || $_ eq "--verbose") {
390
push @normalize_args, "-v";
392
shift; next ARG_LOOP;
393
} elsif ($_ eq "-b" || $_ eq "--batch") {
394
push @normalize_args, "-b";
396
shift; next ARG_LOOP;
397
} elsif ($_ eq "-m" || $_ eq "--mix") {
398
push @normalize_args, "-m";
400
shift; next ARG_LOOP;
401
} elsif ($_ eq "-q" || $_ eq "--quiet") {
402
push @normalize_args, "-q";
404
shift; next ARG_LOOP;
405
} elsif ($_ eq "--ogg") {
409
shift; next ARG_LOOP;
410
} elsif ($_ eq "--mp3") {
414
shift; next ARG_LOOP;
415
} elsif ($_ eq "--flac") {
419
shift; next ARG_LOOP;
420
} elsif ($_ eq "--force-encode") {
422
shift; next ARG_LOOP;
423
} elsif ($_ eq "--backup") {
425
shift; next ARG_LOOP;
426
} elsif ($_ eq "--notags" || $_ eq "--noid3") {
428
shift; next ARG_LOOP;
429
} elsif ($_ eq "--mp3encode") {
430
if ($#ARGV < 1) { print "$progname: option $_ requires an argument\n"; exit 1; }
431
$MP3ENCODE = $ARGV[1];
432
shift; shift; next ARG_LOOP;
433
} elsif ($_ eq "--mp3decode") {
434
if ($#ARGV < 1) { print "$progname: option $_ requires an argument\n"; exit 1; }
435
$MP3DECODE = $ARGV[1];
436
shift; shift; next ARG_LOOP;
437
} elsif ($_ eq "--oggencode") {
438
if ($#ARGV < 1) { print "$progname: option $_ requires an argument\n"; exit 1; }
439
$MP3ENCODE = $ARGV[1];
440
shift; shift; next ARG_LOOP;
441
} elsif ($_ eq "--oggdecode") {
442
if ($#ARGV < 1) { print "$progname: option $_ requires an argument\n"; exit 1; }
443
$OGGDECODE = $ARGV[1];
444
shift; shift; next ARG_LOOP;
445
} elsif ($_ eq "-h" || $_ eq "--help") {
448
} elsif ($_ eq "-V" || $_ eq "--version") {
449
print "$progname (normalize) $version\n";
451
} elsif ($_ eq "--") {
453
shift; next ARG_LOOP;
455
print "Unrecognized option \"",$ARGV[0],"\"\n";
461
push(@infnames, shift);
465
print STDERR "Error: no files specified\n";
466
print STDERR "Usage: $progname [OPTION]... [FILE]...\n";
467
print STDERR "Try `$progname --help' for more information\n";
472
if ($batch_mode || $mix_mode) {
479
for($i = 0; $i <= $#infnames; $i++) {
480
$input_file = $infnames[$i];
483
if ($input_file =~ /\.mp3$/i) {
484
$decoder = $MP3DECODE;
485
} elsif ($input_file =~ /\.ogg$/i) {
486
$decoder = $OGGDECODE;
487
} elsif ($input_file =~ /\.flac$/i) {
488
$decoder = $FLACDECODE;
490
print STDERR "$progname: $input_file has unrecognized extension\n";
491
print STDERR "$progname: Recognized extensions are mp3, ogg, and flac\n";
495
print STDERR "$progname: $input_file: no decoder available\n";
496
splice(@infnames, $i, 1);
501
# construct temporary file name
502
# NOTE: There is a race condition here, similar to the C
503
# tmpnam() function. We are ignoring it.
504
($filebase = $input_file) =~ s/^.*\///;
505
$filebase = $tmpdir.$filebase;
508
$tmp_file = $filebase.".".$n.".wav";
510
} while (-e $tmp_file);
511
push(@tmpfnames, $tmp_file);
512
# construct output file name
513
($filebase = $input_file) =~ s/\..*?$//;
515
$output_file = $filebase.".mp3";
516
} elsif ($all_to_ogg) {
517
$output_file = $filebase.".ogg";
519
$output_file = $input_file;
521
push(@outfnames, $output_file);
523
# construct decode command
524
@decode_args = split(/\s+/, $decoder);
527
s/^\%m$/$input_file/;
532
$do_copy_tags && ($tagref = read_tags($input_file));
533
push(@tags, $tagref);
536
$verbose > 0 && print STDERR "Decoding $input_file...\n";
538
open(OLDOUT, ">&STDOUT");
539
open(STDOUT, ">/dev/null") || die "Can't redirect stdout, stopped";
541
$ret = system(@decode_args);
544
open(STDOUT, ">&OLDOUT");
546
$ret == 0 || die "Error decoding, stopped";
551
# normalize all files
553
$verbose > 0 && print STDERR "Running normalize...\n";
554
@args = (@normalize_args, @tmpfnames);
555
$adjust_needed = $force_encode;
556
defined($pid = open(NORM, "-|")) || die "Can't fork: $!, stopped";
558
local $SIG{INT} = 'IGNORE';
559
$dummy = 0; # suppress warnings about single use
561
if (/^ADJUST_NEEDED /) {
562
($dummy, $adjust_needed_here) = split;
563
$adjust_needed = $adjust_needed || $adjust_needed_here;
564
} elsif (/^LEVEL /) {
565
unless ($do_adjust) {
566
# with -n specified, the line following a LEVEL line
567
# is the "level peak gain" line, so print it out
574
$? == 0 || die "Error during normalize, stopped";
576
exec(@args) || die "Can't run normalize: $!, stopped";
581
# re-encode all files
584
for($i = 0; $i <= $#infnames; $i++) {
585
$input_file = $infnames[$i];
586
$output_file = $outfnames[$i];
587
$tmp_file = $tmpfnames[$i];
590
# construct encode command
592
if ($output_file =~ /\.mp3$/i) {
593
$encoder = $MP3ENCODE;
594
} elsif ($output_file =~ /\.ogg$/i) {
595
$encoder = $OGGENCODE;
596
} elsif ($output_file =~ /\.flac$/i) {
597
$encoder = $FLACENCODE;
599
print STDERR "$progname: $output_file has unrecognized extension\n";
600
print STDERR "$progname: Recognized extensions are mp3, ogg, and flac\n";
604
print STDERR "$progname: $output_file: no decoder available\n";
605
print STDERR "$progname: leaving output in $tmp_file\n";
609
@encode_args = split(/\s+/, $encoder);
612
s/^\%m$/$output_file/;
616
if ($adjust_needed || $input_file ne $output_file) {
618
rename($input_file, $input_file."~");
623
$verbose > 0 && print STDERR "Re-encoding $input_file...\n";
625
open(OLDOUT, ">&STDOUT");
626
open(STDOUT, ">/dev/null")
627
|| die "Can't redirect stdout, stopped";
629
$ret = system(@encode_args);
632
open(STDOUT, ">&OLDOUT");
634
$ret == 0 || die "Error encoding, stopped";
636
# restore tags, if necessary
637
$do_copy_tags && write_tags($output_file, $tagref);
639
$verbose > 0 && print "$input_file is already normalized, not re-encoding...\n";
643
unlink $tmp_file || print STDERR "Can't remove $tmp_file: $!\n";
652
# not mix or batch mode
654
for $input_file (@infnames) {
656
$decoder = $encoder = undef;
657
if ($input_file =~ /\.mp3$/i) {
658
$decoder = $MP3DECODE; $encoder = $MP3ENCODE;
659
} elsif ($input_file =~ /\.ogg$/i) {
660
$decoder = $OGGDECODE; $encoder = $OGGENCODE;
661
} elsif ($input_file =~ /\.flac$/i) {
662
$decoder = $FLACDECODE; $encoder = $FLACENCODE;
664
print STDERR "$progname: $input_file has unrecognized extension\n";
665
print STDERR "$progname: Recognized extensions are mp3, ogg, and flac\n";
669
# construct temporary file name
670
# NOTE: There is a race condition here, similar to the C
671
# tmpnam() function. We are ignoring it.
672
($filebase = $input_file) =~ s{^.*/}{};
673
$filebase = $tmpdir.$filebase;
676
$tmp_file = $filebase.".".$n.".wav";
678
} while (-e $tmp_file);
679
# construct output file name
680
#($filebase = $input_file) =~ s{(.*/)?(.*)\..*}{$2};
681
($filebase = $input_file) =~ s{\..*?$}{};
683
$output_file = $filebase.".mp3";
684
$encoder = $MP3ENCODE;
685
} elsif ($all_to_ogg) {
686
$output_file = $filebase.".ogg";
687
$encoder = $OGGENCODE;
688
} elsif ($all_to_flac) {
689
$output_file = $filebase.".flac";
690
$encoder = $FLACENCODE;
692
$output_file = $input_file;
696
print STDERR "$progname: $input_file: no decoder available\n";
700
print STDERR "$progname: $output_file: no encoder available\n";
704
# construct encode and decode commands
705
@decode_args = split(/\s+/, $decoder);
708
s/^\%m$/$input_file/;
711
@encode_args = split(/\s+/, $encoder);
714
s/^\%m$/$output_file/;
719
$do_copy_tags && ($tagref = read_tags($input_file));
725
$verbose > 0 && print STDERR "Decoding $input_file...\n";
727
open(OLDOUT, ">&STDOUT");
728
open(STDOUT, ">/dev/null") || die "Can't redirect stdout, stopped";
730
$ret = system(@decode_args);
733
open(STDOUT, ">&OLDOUT");
735
$ret == 0 || die "Error decoding, stopped";
741
$verbose > 0 && print STDERR "Running normalize...\n";
742
@args = (@normalize_args, $tmp_file);
743
$adjust_needed = $force_encode;
744
defined($pid = open(NORM, "-|")) || die "Can't fork: $!, stopped";
746
local $SIG{INT} = 'IGNORE';
747
$dummy = 0; # suppress warnings about single use
749
if (/^ADJUST_NEEDED /) {
750
($dummy, $adjust_needed_here) = split;
751
$adjust_needed = $adjust_needed || $adjust_needed_here;
752
} elsif (/^LEVEL /) {
753
unless ($do_adjust) {
754
# with -n specified, the line following a LEVEL line
755
# is the "level peak gain" line, so print it out
762
$? == 0 || die "Error during normalize, stopped";
764
exec(@args) || die "Can't run normalize: $!, stopped";
769
# run encoder, if necessary
772
if ($adjust_needed || $input_file ne $output_file) {
774
rename($input_file, $input_file."~");
779
$verbose > 0 && print STDERR "Re-encoding $input_file...\n";
781
open(OLDOUT, ">&STDOUT");
782
open(STDOUT, ">/dev/null")
783
|| die "Can't redirect stdout, stopped";
785
$ret = system(@encode_args);
788
open(STDOUT, ">&OLDOUT");
790
$ret == 0 || die "Error encoding, stopped";
792
# restore tags, if necessary
793
$do_copy_tags && write_tags($output_file, $tagref);
795
$verbose > 0 && print "$input_file is already normalized, not re-encoding...\n";
800
unlink $tmp_file || print STDERR "Can't remove $tmp_file: $!\n";