1#------------------------------------------------------------------------------
2# File:         AIFF.pm
3#
4# Description:  Read AIFF meta information
5#
6# Revisions:    01/06/2006 - P. Harvey Created
7#               09/22/2008 - PH Added DjVu support
8#
9# References:   1) http://developer.apple.com/documentation/QuickTime/INMAC/SOUND/imsoundmgr.30.htm#pgfId=3190
10#               2) http://astronomy.swin.edu.au/~pbourke/dataformats/aiff/
11#               3) http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/
12#------------------------------------------------------------------------------
13
14package Image::ExifTool::AIFF;
15
16use strict;
17use vars qw($VERSION);
18use Image::ExifTool qw(:DataAccess :Utils);
19use Image::ExifTool::ID3;
20
21$VERSION = '1.11';
22
23# information for time/date-based tags (time zero is Jan 1, 1904)
24my %timeInfo = (
25    Groups => { 2 => 'Time' },
26    ValueConv => 'ConvertUnixTime($val - ((66 * 365 + 17) * 24 * 3600))',
27    PrintConv => '$self->ConvertDateTime($val)',
28);
29
30# AIFF info
31%Image::ExifTool::AIFF::Main = (
32    GROUPS => { 2 => 'Audio' },
33    NOTES => q{
34        Tags extracted from Audio Interchange File Format (AIFF) files.  See
35        L<http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/AIFF/AIFF.html> for
36        the AIFF specification.
37    },
38#    FORM => 'Format',
39    FVER => {
40        Name => 'FormatVersion',
41        SubDirectory => { TagTable => 'Image::ExifTool::AIFF::FormatVers' },
42    },
43    COMM => {
44        Name => 'Common',
45        SubDirectory => { TagTable => 'Image::ExifTool::AIFF::Common' },
46    },
47    COMT => {
48        Name => 'Comment',
49        SubDirectory => { TagTable => 'Image::ExifTool::AIFF::Comment' },
50    },
51    NAME => {
52        Name => 'Name',
53        ValueConv => '$self->Decode($val, "MacRoman")',
54    },
55    AUTH => {
56        Name => 'Author',
57        Groups => { 2 => 'Author' },
58        ValueConv => '$self->Decode($val, "MacRoman")',
59    },
60   '(c) ' => {
61        Name => 'Copyright',
62        Groups => { 2 => 'Author' },
63        ValueConv => '$self->Decode($val, "MacRoman")',
64    },
65    ANNO => {
66        Name => 'Annotation',
67        ValueConv => '$self->Decode($val, "MacRoman")',
68    },
69   'ID3 ' => {
70        Name => 'ID3',
71        SubDirectory => {
72            TagTable => 'Image::ExifTool::ID3::Main',
73            ProcessProc => \&Image::ExifTool::ID3::ProcessID3,
74        },
75    },
76    APPL => 'ApplicationData', # (first 4 bytes are the application signature)
77#    SSND => 'SoundData',
78#    MARK => 'Marker',
79#    INST => 'Instrument',
80#    MIDI => 'MidiData',
81#    AESD => 'AudioRecording',
82);
83
84%Image::ExifTool::AIFF::Common = (
85    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
86    GROUPS => { 2 => 'Audio' },
87    FORMAT => 'int16u',
88    0 => 'NumChannels',
89    1 => { Name => 'NumSampleFrames', Format => 'int32u' },
90    3 => 'SampleSize',
91    4 => { Name => 'SampleRate', Format => 'extended' }, #3
92    9 => {
93        Name => 'CompressionType',
94        Format => 'string[4]',
95        PrintConv => {
96            NONE => 'None',
97            ACE2 => 'ACE 2-to-1',
98            ACE8 => 'ACE 8-to-3',
99            MAC3 => 'MAC 3-to-1',
100            MAC6 => 'MAC 6-to-1',
101            sowt => 'Little-endian, no compression',
102            alaw => 'a-law',
103            ALAW => 'A-law',
104            ulaw => 'mu-law',
105            ULAW => 'Mu-law',
106           'GSM '=> 'GSM',
107            G722 => 'G722',
108            G726 => 'G726',
109            G728 => 'G728',
110        },
111    },
112    11 => { #PH
113        Name => 'CompressorName',
114        Format => 'pstring',
115        ValueConv => '$self->Decode($val, "MacRoman")',
116    },
117);
118
119%Image::ExifTool::AIFF::FormatVers = (
120    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
121    FORMAT => 'int32u',
122    0 => { Name => 'FormatVersionTime', %timeInfo },
123);
124
125%Image::ExifTool::AIFF::Comment = (
126    PROCESS_PROC => \&Image::ExifTool::AIFF::ProcessComment,
127    GROUPS => { 2 => 'Audio' },
128    0 => { Name => 'CommentTime', %timeInfo },
129    1 => 'MarkerID',
130    2 => {
131        Name => 'Comment',
132        ValueConv => '$self->Decode($val, "MacRoman")',
133    },
134);
135
136%Image::ExifTool::AIFF::Composite = (
137    Duration => {
138        Require => {
139            0 => 'AIFF:SampleRate',
140            1 => 'AIFF:NumSampleFrames',
141        },
142        RawConv => '($val[0] and $val[1]) ? $val[1] / $val[0] : undef',
143        PrintConv => 'ConvertDuration($val)',
144    },
145);
146
147# add our composite tags
148Image::ExifTool::AddCompositeTags('Image::ExifTool::AIFF');
149
150
151#------------------------------------------------------------------------------
152# Process AIFF Comment chunk
153# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) tag table ref
154# Returns: 1 on success
155sub ProcessComment($$$)
156{
157    my ($et, $dirInfo, $tagTablePtr) = @_;
158    my $dataPt = $$dirInfo{DataPt};
159    my $dirLen = $$dirInfo{DirLen};
160    my $verbose = $et->Options('Verbose');
161    return 0 unless $dirLen > 2;
162    my $numComments = unpack('n',$$dataPt);
163    my $pos = 2;
164    my $i;
165    $verbose and $et->VerboseDir('Comment', $numComments);
166    for ($i=0; $i<$numComments; ++$i) {
167        last if $pos + 8 > $dirLen;
168        my ($time, $markerID, $size) = unpack("x${pos}Nnn", $$dataPt);
169        $et->HandleTag($tagTablePtr, 0, $time);
170        $et->HandleTag($tagTablePtr, 1, $markerID) if $markerID;
171        $pos += 8;
172        last if $pos + $size > $dirLen;
173        my $val = substr($$dataPt, $pos, $size);
174        $et->HandleTag($tagTablePtr, 2, $val);
175        ++$size if $size & 0x01;    # account for padding byte if necessary
176        $pos += $size;
177    }
178}
179
180#------------------------------------------------------------------------------
181# Extract information from a AIFF file
182# Inputs: 0) ExifTool object reference, 1) DirInfo reference
183# Returns: 1 on success, 0 if this wasn't a valid AIFF file
184sub ProcessAIFF($$)
185{
186    my ($et, $dirInfo) = @_;
187    my $raf = $$dirInfo{RAF};
188    my ($buff, $err, $tagTablePtr, $page, $type, $n);
189
190    # verify this is a valid AIFF file
191    return 0 unless $raf->Read($buff, 12) == 12;
192    my $fast3 = $$et{OPTIONS}{FastScan} && $$et{OPTIONS}{FastScan} == 3;
193    my $pos = 12;
194    # check for DjVu image
195    if ($buff =~ /^AT&TFORM/) {
196        # http://www.djvu.org/
197        # http://djvu.sourceforge.net/specs/djvu3changes.txt
198        my $buf2;
199        return 0 unless $raf->Read($buf2, 4) == 4 and $buf2 =~ /^(DJVU|DJVM)/;
200        $pos += 4;
201        $buff = substr($buff, 4) . $buf2;
202        $et->SetFileType('DJVU');
203        return 1 if $fast3;
204        $tagTablePtr = GetTagTable('Image::ExifTool::DjVu::Main');
205        # modify FileType to indicate a multi-page document
206        $$et{VALUE}{FileType} .= " (multi-page)" if $buf2 eq 'DJVM';
207        $type = 'DjVu';
208    } else {
209        return 0 unless $buff =~ /^FORM....(AIF(F|C))/s;
210        $et->SetFileType($1);
211        return 1 if $fast3;
212        $tagTablePtr = GetTagTable('Image::ExifTool::AIFF::Main');
213        $type = 'AIFF';
214    }
215    SetByteOrder('MM');
216    my $verbose = $et->Options('Verbose');
217#
218# Read through the IFF chunks
219#
220    for ($n=0;;++$n) {
221        $raf->Read($buff, 8) == 8 or last;
222        $pos += 8;
223        my ($tag, $len) = unpack('a4N', $buff);
224        my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
225        $et->VPrint(0, "AIFF '${tag}' chunk ($len bytes of data): ", $raf->Tell(),"\n");
226        # AIFF chunks are padded to an even number of bytes
227        my $len2 = $len + ($len & 0x01);
228        if ($len2 > 100000000) {
229            if ($len2 >= 0x80000000 and not $et->Options('LargeFileSupport')) {
230                $et->Warn('End of processing at large chunk (LargeFileSupport not enabled)');
231                last;
232            }
233            if ($tagInfo) {
234                $et->Warn("Skipping large $$tagInfo{Name} chunk (> 100 MB)");
235                undef $tagInfo;
236            }
237        }
238        if ($tagInfo) {
239            if ($$tagInfo{TypeOnly}) {
240                $len = $len2 = 4;
241                $page = ($page || 0) + 1;
242                $et->VPrint(0, $$et{INDENT} . "Page $page:\n");
243            }
244            $raf->Read($buff, $len2) >= $len or $err=1, last;
245            unless ($$tagInfo{SubDirectory} or $$tagInfo{Binary}) {
246                $buff =~ s/\0+$//;  # remove trailing nulls
247            }
248            $et->HandleTag($tagTablePtr, $tag, $buff,
249                DataPt => \$buff,
250                DataPos => $pos,
251                Start => 0,
252                Size => $len,
253            );
254        } elsif (not $len) {
255            next if ++$n < 100;
256            $et->Warn('Aborting scan.  Too many empty chunks');
257            last;
258        } elsif ($verbose > 2 and $len2 < 1024000) {
259            $raf->Read($buff, $len2) == $len2 or $err = 1, last;
260            $et->VerboseDump(\$buff);
261        } else {
262            $raf->Seek($len2, 1) or $err=1, last;
263        }
264        $pos += $len2;
265        $n = 0;
266    }
267    $err and $et->Warn("Error reading $type file (corrupted?)");
268    return 1;
269}
270
2711;  # end
272
273__END__
274
275=head1 NAME
276
277Image::ExifTool::AIFF - Read AIFF meta information
278
279=head1 SYNOPSIS
280
281This module is used by Image::ExifTool
282
283=head1 DESCRIPTION
284
285This module contains routines required by Image::ExifTool to extract
286information from AIFF (Audio Interchange File Format) audio files.
287
288=head1 AUTHOR
289
290Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
291
292This library is free software; you can redistribute it and/or modify it
293under the same terms as Perl itself.
294
295=head1 REFERENCES
296
297=over 4
298
299=item L<http://developer.apple.com/documentation/QuickTime/INMAC/SOUND/imsoundmgr.30.htm#pgfId=3190>
300
301=item L<http://astronomy.swin.edu.au/~pbourke/dataformats/aiff/>
302
303=item L<http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/>
304
305=back
306
307=head1 SEE ALSO
308
309L<Image::ExifTool::TagNames/AIFF Tags>,
310L<Image::ExifTool(3pm)|Image::ExifTool>
311
312=cut
313