#!/usr/bin/perl -w
use strict;
use Getopt::Std 'getopts';
# The "size*" fields may be followed by a byte to get to an even alignment;
# it is not included into size! (Not applicable to these formats)
my $wav_header = <<EOH; # Used for MP3???
a4 # header: 'RIFF'
V # size: Size of what follows
a4 # type: 'WAVE'
a4 # type1: 'fmt ' subchunk
V # size1: Size of the rest of subchunk (usually 0x1e, sometime 0x20?)
v # format: 1 for pcm, 0x55 for mp3
v # channels: 2 stereo 1 mono
V # frequency
V # bytes_per_sec
v # bytes_per_sample
v # bits_per_sample_channel
V # Unknown1
V # Unknown2
V # Unknown3
v # Unknown4
# v # Unknown4a (when size1 is 0x20)
# Optional section:
# a4 # type12: 'fact' subchunk
# V # size12: Size of the rest of subchunk (4)
# V # Unknown5
a4 # type2: 'data' subchunk
V # sizedata: Size of the rest of subchunk
EOH
my @wav_fields = ($wav_header =~ /^\s*\w+\s*#\s*(\w+)/mg);
my $header_size = length pack $wav_header, (0) x 20; # No Optional part
sub MY_INF () {1e200}
my %opt;
sub wav_eat_header ($) {
my $fh = shift;
my $in;
my $read = sysread $fh, $in, $header_size or die "can't read the header";
my $prefix = '';
return {prefix => $prefix, buf => $in} unless $read == $header_size;
if ($opt{I} and $in =~ /^ID3(..)(.)([\x00-\x7f]{4})/s) {
my $f = 0 + ord $2; # Make into integer
my $s = 0;
for my $c (split //, $3) {
$s <<= 7;
$s |= ord $c;
}
$s += (($f & 0x10) ? 20 : 10);
$read = sysread $fh, $in, $s, $header_size or die "can't read the header";
return {buf => $in} unless $read == $s;
$prefix = substr $in, 0, $s;
$in = substr $in, $s;
}
my %vals;
@vals{@wav_fields} = unpack $wav_header, $in or return {buf => $in};
return {prefix => $prefix, buf => $in} unless $vals{header} eq 'RIFF';
if ($vals{size1} == 0x20) {
# Format above expects 0x1e...
my $in2;
$read = sysread $fh, $in2, 2 or die "can't read rest of the header";
$in .= $in2;
return {prefix => $prefix, buf => $in} unless $read == 2;
my %vals1;
@vals1{@wav_fields} = unpack $wav_header, substr $in, 2 or return {buf => $in};
@vals{'type2', 'sizedata'} = @vals1{'type2', 'sizedata'};
}
if ($vals{type2} eq 'fact') {
my $h2_size = length pack "V a4 V", (0) x 20; # No Optional part
my $in2;
$read = sysread $fh, $in2, $h2_size or die "can't read rest of the header";
$in .= $in2;
return {prefix => $prefix, buf => $in} unless $read == $h2_size;
@vals{qw[type12 size12]} = @vals{qw[type2 sizedata]};
@vals{qw[Unknown5 type2 sizedata]} = unpack "V a4 V", $in2;
}
die <<EOD
Unexpected RIFF format:
type='$vals{type}' (WAVE)
type1='$vals{type1}' (fmt )
size1=$vals{size1} (0x1e)
format=$vals{format} (0x55)
type12=$vals{type12} (fact)
size12=$vals{size12} (4)
type2=$vals{type2} (data)
EOD
unless $vals{type} eq 'WAVE' and $vals{type1} eq 'fmt '
and ($vals{size1} == 0x1e or $vals{size1} == 0x20)
and $vals{format} == 0x55
and (not exists $vals{type12}
or $vals{type12} eq 'fact' and $vals{size12} eq 4)
and $vals{type2} eq 'data';
$vals{buf} = $in;
$vals{prefix} = $prefix;
return \%vals;
}
# Typical usage:
# a) rename all .mp3 to .wav: pfind . "s/\.mp3$/.wav/i"
# b) run: eat_wav_mp3_header.pl -FdI -R .
# or, to list MP3 files with RIFF header
# eat_wav_mp3_header.pl -IDM -R .
getopts('FGRdsIDM', \%opt); # Force, Glob, recurse, delete, silent, ID3v2-header-OK, Dry-run, input-is MP3
if ($opt{G}) {
require File::Glob; # "usual" glob() fails on spaces...
@ARGV = map File::Glob::bsd_glob($_), @ARGV;
}
sub process_file ($) {
my $f = shift;
print "$f\n" unless $opt{s} or $opt{D};
open IN, "< $f" or die;
binmode IN;
my $rc = wav_eat_header \*IN;
if ($opt{D}) { # Report only
print "$f\n" if defined $rc->{type2};
return;
}
(my $o = $f) =~ s/\.wav$/.mp3/i or die "`$f' is not with extension .wav";
unless (defined $rc->{type2}) {
close IN or die;
die "File `$f': no valid RIFF header" unless $opt{F};
rename $f, $o or die "rename `$f' => `$o': $!";
return;
}
open OUT, ">$o" or die;
binmode OUT;
syswrite OUT, $rc->{prefix} if length $rc->{prefix};
my ($in, $c);
while ($c = sysread IN, $in, 1<<20) {
syswrite OUT, $in or die;
}
close IN or die "close for read: $!";
close OUT or die "close for write: $!";
unlink $f or die "unlink: $!" if $opt{d};
}
if ($opt{R}) {
require File::Find;
File::Find::find({wanted => sub {return unless -f and ($opt{M} ? /\.mp3$/i : /\.wav$/i); process_file $_},
no_chdir => 1}, @ARGV);
} else {
for my $f (@ARGV) {
process_file $f;
}
}