File Coverage

blib/lib/Image/ExifTool/AIFF.pm
Criterion Covered Total %
statement 77 88 87.5
branch 27 54 50.0
condition 8 20 40.0
subroutine 6 6 100.0
pod 0 2 0.0
total 118 170 69.4


line stmt bran cond sub pod time code
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              
14             package Image::ExifTool::AIFF;
15              
16 2     2   4799 use strict;
  2         4  
  2         82  
17 2     2   12 use vars qw($VERSION);
  2         5  
  2         125  
18 2     2   25 use Image::ExifTool qw(:DataAccess :Utils);
  2         4  
  2         565  
19 2     2   1470 use Image::ExifTool::ID3;
  2         26  
  2         2611  
20              
21             $VERSION = '1.12';
22              
23             # information for time/date-based tags (time zero is Jan 1, 1904)
24             my %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 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
148             Image::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
155             sub ProcessComment($$$)
156             {
157 1     1 0 5 my ($et, $dirInfo, $tagTablePtr) = @_;
158 1         2 my $dataPt = $$dirInfo{DataPt};
159 1         3 my $dirLen = $$dirInfo{DirLen};
160 1         2 my $verbose = $et->Options('Verbose');
161 1 50       5 return 0 unless $dirLen > 2;
162 1         3 my $numComments = unpack('n',$$dataPt);
163 1         2 my $pos = 2;
164 1         2 my $i;
165 1 50       5 $verbose and $et->VerboseDir('Comment', $numComments);
166 1         4 for ($i=0; $i<$numComments; ++$i) {
167 1 50       3 last if $pos + 8 > $dirLen;
168 1         5 my ($time, $markerID, $size) = unpack("x${pos}Nnn", $$dataPt);
169 1         17 $et->HandleTag($tagTablePtr, 0, $time);
170 1 50       3 $et->HandleTag($tagTablePtr, 1, $markerID) if $markerID;
171 1         2 $pos += 8;
172 1 50       3 last if $pos + $size > $dirLen;
173 1         3 my $val = substr($$dataPt, $pos, $size);
174 1         4 $et->HandleTag($tagTablePtr, 2, $val);
175 1 50       3 ++$size if $size & 0x01; # account for padding byte if necessary
176 1         6 $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
184             sub ProcessAIFF($$)
185             {
186 2     2 0 7 my ($et, $dirInfo) = @_;
187 2         6 my $raf = $$dirInfo{RAF};
188 2         10 my ($buff, $err, $tagTablePtr, $page, $type, $n);
189              
190             # verify this is a valid AIFF file
191 2 50       11 return 0 unless $raf->Read($buff, 12) == 12;
192 2   33     9 my $fast3 = $$et{OPTIONS}{FastScan} && $$et{OPTIONS}{FastScan} == 3;
193 2         6 my $pos = 12;
194             # check for DjVu image
195 2 100       12 if ($buff =~ /^AT&TFORM/) {
196             # http://www.djvu.org/
197             # http://djvu.sourceforge.net/specs/djvu3changes.txt
198 1         2 my $buf2;
199 1 50 33     8 return 0 unless $raf->Read($buf2, 4) == 4 and $buf2 =~ /^(DJVU|DJVM)/;
200 1         4 $pos += 4;
201 1         5 $buff = substr($buff, 4) . $buf2;
202 1         7 $et->SetFileType('DJVU');
203 1 50       3 return 1 if $fast3;
204 1         3 $tagTablePtr = GetTagTable('Image::ExifTool::DjVu::Main');
205             # modify FileType to indicate a multi-page document
206 1 50 33     7 $$et{VALUE}{FileType} .= " (multi-page)" if $buf2 eq 'DJVM' and $$et{VALUE}{FileType};
207 1         2 $type = 'DjVu';
208             } else {
209 1 50       15 return 0 unless $buff =~ /^FORM....(AIF(F|C))/s;
210 1         9 $et->SetFileType($1);
211 1 50       4 return 1 if $fast3;
212 1         4 $tagTablePtr = GetTagTable('Image::ExifTool::AIFF::Main');
213 1         4 $type = 'AIFF';
214             }
215 2         10 SetByteOrder('MM');
216 2         10 my $verbose = $et->Options('Verbose');
217             #
218             # Read through the IFF chunks
219             #
220 2         5 for ($n=0;;++$n) {
221 16 100       56 $raf->Read($buff, 8) == 8 or last;
222 14         46 $pos += 8;
223 14         59 my ($tag, $len) = unpack('a4N', $buff);
224 14         50 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
225 14         69 $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 14         32 my $len2 = $len + ($len & 0x01);
228 14 50       29 if ($len2 > 100000000) {
229 0 0 0     0 if ($len2 >= 0x80000000 and not $et->Options('LargeFileSupport')) {
230 0         0 $et->Warn('End of processing at large chunk (LargeFileSupport not enabled)');
231 0         0 last;
232             }
233 0 0       0 if ($tagInfo) {
234 0         0 $et->Warn("Skipping large $$tagInfo{Name} chunk (> 100 MB)");
235 0         0 undef $tagInfo;
236             }
237             }
238 14 100 33     34 if ($tagInfo) {
    50          
    50          
239 10 100       26 if ($$tagInfo{TypeOnly}) {
240 2         4 $len = $len2 = 4;
241 2   100     15 $page = ($page || 0) + 1;
242 2         28 $et->VPrint(0, $$et{INDENT} . "Page $page:\n");
243             }
244 10 50       28 $raf->Read($buff, $len2) >= $len or $err=1, last;
245 10 50 66     62 unless ($$tagInfo{SubDirectory} or $$tagInfo{Binary}) {
246 3         32 $buff =~ s/\0+$//; # remove trailing nulls
247             }
248 10         58 $et->HandleTag($tagTablePtr, $tag, $buff,
249             DataPt => \$buff,
250             DataPos => $pos,
251             Start => 0,
252             Size => $len,
253             );
254             } elsif (not $len) {
255 0 0       0 next if ++$n < 100;
256 0         0 $et->Warn('Aborting scan. Too many empty chunks');
257 0         0 last;
258             } elsif ($verbose > 2 and $len2 < 1024000) {
259 0 0       0 $raf->Read($buff, $len2) == $len2 or $err = 1, last;
260 0         0 $et->VerboseDump(\$buff);
261             } else {
262 4 50       13 $raf->Seek($len2, 1) or $err=1, last;
263             }
264 14         30 $pos += $len2;
265 14         31 $n = 0;
266             }
267 2 50       12 $err and $et->Warn("Error reading $type file (corrupted?)");
268 2         11 return 1;
269             }
270              
271             1; # end
272              
273             __END__