File Coverage

blib/lib/Image/ExifTool/BigTIFF.pm
Criterion Covered Total %
statement 76 152 50.0
branch 23 86 26.7
condition 5 32 15.6
subroutine 6 6 100.0
pod 0 2 0.0
total 110 278 39.5


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: BigTIFF.pm
3             #
4             # Description: Read Big TIFF meta information
5             #
6             # Revisions: 07/03/2007 - P. Harvey Created
7             #
8             # References: 1) http://www.awaresystems.be/imaging/tiff/bigtiff.html
9             #------------------------------------------------------------------------------
10              
11             package Image::ExifTool::BigTIFF;
12              
13 1     1   4686 use strict;
  1         2  
  1         33  
14 1     1   5 use vars qw($VERSION);
  1         2  
  1         40  
15 1     1   6 use Image::ExifTool qw(:DataAccess :Utils);
  1         2  
  1         224  
16 1     1   1522 use Image::ExifTool::Exif;
  1         5  
  1         1873  
17              
18             $VERSION = '1.07';
19              
20             my $maxOffset = 0x7fffffff; # currently supported maximum data offset/size
21              
22             #------------------------------------------------------------------------------
23             # Process Big IFD directory
24             # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
25             # Returns: 1 on success, otherwise returns 0 and sets a Warning
26             sub ProcessBigIFD($$$)
27             {
28 1     1 0 4 my ($et, $dirInfo, $tagTablePtr) = @_;
29 1         3 my $raf = $$dirInfo{RAF};
30 1         3 my $verbose = $$et{OPTIONS}{Verbose};
31 1         3 my $htmlDump = $$et{HTML_DUMP};
32 1         3 my $dirName = $$dirInfo{DirName};
33 1         2 my $dirStart = $$dirInfo{DirStart};
34 1         4 my ($offName, $nextOffName);
35              
36 1 50       3 if ($htmlDump) {
37 0         0 $verbose = -1; # mix htmlDump into verbose so we can test for both at once
38 0         0 $offName = $$dirInfo{OffsetName};
39             }
40              
41             # loop through IFD chain
42 1         3 for (;;) {
43 1 50 33     4 if ($dirStart > $maxOffset and not $et->Options('LargeFileSupport')) {
44 0         0 $et->Warn('Huge offsets not supported (LargeFileSupport not set)');
45 0         0 last;
46             }
47 1 50       7 unless ($raf->Seek($dirStart, 0)) {
48 0         0 $et->Warn("Bad $dirName offset");
49 0         0 return 0;
50             }
51 1         3 my ($dirBuff, $index);
52 1 50       6 unless ($raf->Read($dirBuff, 8) == 8) {
53 0         0 $et->Warn("Truncated $dirName count");
54 0         0 return 0;
55             }
56 1         13 my $numEntries = Image::ExifTool::Get64u(\$dirBuff, 0);
57 1 50       16 $verbose > 0 and $et->VerboseDir($dirName, $numEntries);
58 1         4 my $bsize = $numEntries * 20;
59 1 50       6 if ($bsize > $maxOffset) {
60 0         0 $et->Warn('Huge directory counts not yet supported');
61 0         0 last;
62             }
63 1         7 my $bufPos = $raf->Tell();
64 1 50       4 unless ($raf->Read($dirBuff, $bsize) == $bsize) {
65 0         0 $et->Warn("Truncated $dirName directory");
66 0         0 return 0;
67             }
68 1         3 my $nextIFD;
69 1 50       4 $raf->Read($nextIFD, 8) == 8 or undef $nextIFD; # try to read next IFD pointer
70 1 50       4 if ($htmlDump) {
71 0         0 $et->HDump($bufPos-8, 8, "$dirName entries", "Entry count: $numEntries", undef, $offName);
72 0 0       0 if (defined $nextIFD) {
73 0         0 my $off = Image::ExifTool::Get64u(\$nextIFD, 0);
74 0         0 my $tip = sprintf("Offset: 0x%.8x", $off);
75 0         0 my $id = $offName;
76 0 0       0 ($nextOffName, $id) = Image::ExifTool::Exif::NextOffsetName($et, $id) if $off;
77 0         0 $et->HDump($bufPos + 20 * $numEntries, 8, "Next IFD", $tip, 0, $id);
78             }
79             }
80             # loop through all entries in this BigTIFF IFD
81 1         4 for ($index=0; $index<$numEntries; ++$index) {
82 8         16 my $entry = 20 * $index;
83 8         21 my $tagID = Get16u(\$dirBuff, $entry);
84 8         22 my $format = Get16u(\$dirBuff, $entry+2);
85 8         25 my $count = Image::ExifTool::Get64u(\$dirBuff, $entry+4);
86 8         24 my $formatSize = $Image::ExifTool::Exif::formatSize[$format];
87 8 50       18 unless (defined $formatSize) {
88 0         0 $et->HDump($bufPos+$entry,20,"[invalid IFD entry]",
89             "Bad format value: $format", 1, $offName);
90             # warn unless the IFD was just padded with zeros
91 0         0 $et->Warn(sprintf("Unknown format ($format) for $dirName tag 0x%x",$tagID));
92 0         0 return 0; # assume corrupted IFD
93             }
94 8         17 my $formatStr = $Image::ExifTool::Exif::formatName[$format];
95 8         118 my $size = $count * $formatSize;
96 8         104 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID);
97 8 50 33     21 next unless defined $tagInfo or $verbose;
98 8         13 my $valuePtr = $entry + 12;
99 8         16 my ($valBuff, $valBase, $rational, $subOffName);
100 8 50       17 if ($size > 8) {
101 0 0       0 if ($size > $maxOffset) {
102 0         0 $et->Warn("Can't handle $dirName entry $index (huge size)");
103 0         0 next;
104             }
105 0         0 $valuePtr = Image::ExifTool::Get64u(\$dirBuff, $valuePtr);
106 0 0 0     0 if ($valuePtr > $maxOffset and not $et->Options('LargeFileSupport')) {
107 0         0 $et->Warn("Can't handle $dirName entry $index (LargeFileSupport not set)");
108 0         0 next;
109             }
110 0 0 0     0 unless ($raf->Seek($valuePtr, 0) and $raf->Read($valBuff, $size) == $size) {
111 0         0 $et->Warn("Error reading $dirName entry $index");
112 0         0 next;
113             }
114 0         0 $valBase = 0;
115             } else {
116 8         28 $valBuff = substr($dirBuff, $valuePtr, $size);
117 8         12 $valBase = $bufPos;
118             }
119 8 50 33     33 if (defined $tagInfo and not $tagInfo) {
120             # GetTagInfo() required the value for a Condition
121 0         0 $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID, \$valBuff);
122             }
123 8         23 my $val = ReadValue(\$valBuff, 0, $formatStr, $count, $size, \$rational);
124 8 50       18 if ($htmlDump) {
125 0         0 my $tval = $val;
126             # show numerator/denominator separately for rational numbers
127 0 0       0 $tval .= " ($rational)" if defined $rational;
128 0         0 my ($tagName, $colName);
129 0 0 0     0 if ($tagID == 0x927c and $dirName eq 'ExifIFD') {
    0          
130 0         0 $tagName = 'MakerNotes';
131             } elsif ($tagInfo) {
132 0         0 $tagName = $$tagInfo{Name};
133             } else {
134 0         0 $tagName = sprintf("Tag 0x%.4x",$tagID);
135             }
136 0         0 my $dname = sprintf("$dirName-%.2d", $index);
137             # build our tool tip
138 0         0 my $tip = sprintf("Tag ID: 0x%.4x\n", $tagID) .
139             "Format: $formatStr\[$count]\nSize: $size bytes\n";
140 0 0       0 if ($size > 8) {
141 0         0 $tip .= sprintf("Value offset: 0x%.8x\n", $valuePtr);
142 0         0 $colName = "$tagName";
143             } else {
144 0         0 $colName = $tagName;
145             }
146 0 0       0 $tval = substr($tval,0,28) . '[...]' if length($tval) > 32;
147 0 0 0     0 if ($formatStr =~ /^(string|undef|binary)/) {
    0          
148             # translate non-printable characters
149 0         0 $tval =~ tr/\x00-\x1f\x7f-\xff/./;
150             } elsif ($tagInfo and Image::ExifTool::IsInt($tval)) {
151 0 0       0 if ($$tagInfo{IsOffset}) {
    0          
152 0         0 $tval = sprintf('0x%.4x', $tval);
153             } elsif ($$tagInfo{PrintHex}) {
154 0         0 $tval = sprintf('0x%x', $tval);
155             }
156             }
157 0         0 $tip .= "Value: $tval";
158 0         0 my ($id, $sid);
159 0 0 0     0 if ($tagInfo and $$tagInfo{SubIFD}) {
160 0         0 ($subOffName, $id, $sid) = Image::ExifTool::Exif::NextOffsetName($et, $offName);
161             } else {
162 0         0 $id = $offName;
163             }
164 0         0 $et->HDump($entry+$bufPos, 20, "$dname $colName", $tip, 1, $id);
165 0 0       0 if ($size > 8) {
166             # add value data block
167 0 0 0     0 my $flg = ($tagInfo and $$tagInfo{SubDirectory} and $$tagInfo{MakerNotes}) ? 4 : 0;
168 0         0 $et->HDump($valuePtr,$size,"$tagName value",'SAME', $flg, $sid);
169             }
170             }
171 8 50 33     33 if ($tagInfo and $$tagInfo{SubIFD}) {
172             # process all SubIFD's as BigTIFF
173 0 0       0 $verbose > 0 and $et->VerboseInfo($tagID, $tagInfo,
174             Table => $tagTablePtr,
175             Index => $index,
176             Value => $val,
177             DataPt => \$valBuff,
178             DataPos => $valBase + $valuePtr,
179             Start => 0,
180             Size => $size,
181             Format => $formatStr,
182             Count => $count,
183             );
184 0         0 my @offsets = split ' ', $val;
185 0         0 my $i;
186 0         0 for ($i=0; $i
187 0         0 my $subdirName = $$tagInfo{Name};
188 0 0       0 $subdirName .= $i if $i;
189 0         0 my %subdirInfo = (
190             RAF => $raf,
191             DataPos => 0,
192             DirStart => $offsets[$i],
193             DirName => $subdirName,
194             Parent => $dirName,
195             OffsetName => $subOffName,
196             );
197 0         0 $et->ProcessDirectory(\%subdirInfo, $tagTablePtr, \&ProcessBigIFD);
198             }
199             } else {
200 8         34 my $tagKey = $et->HandleTag($tagTablePtr, $tagID, $val,
201             Index => $index,
202             DataPt => \$valBuff,
203             DataPos => $valBase + $valuePtr,
204             Start => 0,
205             Size => $size,
206             Format => $formatStr,
207             TagInfo => $tagInfo,
208             RAF => $raf,
209             );
210 8 50       26 $tagKey and $et->SetGroup($tagKey, $dirName);
211             }
212             }
213 1 50       18 last unless $dirName =~ /^(IFD|SubIFD)(\d*)$/;
214 1   50     10 $dirName = $1 . (($2 || 0) + 1);
215 1 50       4 defined $nextIFD or $et->Warn("Bad $dirName pointer"), return 0;
216 1         5 $dirStart = Image::ExifTool::Get64u(\$nextIFD, 0);
217 1 50       10 $dirStart or last;
218 0         0 $offName = $nextOffName;
219             }
220 1         3 return 1;
221             }
222              
223             #------------------------------------------------------------------------------
224             # Extract meta information from a BigTIFF image
225             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
226             # Returns: 1 on success, 0 if this wasn't a valid BigTIFF image
227             sub ProcessBTF($$)
228             {
229 1     1 0 11 my ($et, $dirInfo) = @_;
230 1         3 my $raf = $$dirInfo{RAF};
231 1         2 my $buff;
232              
233 1 50       20 return 0 unless $raf->Read($buff, 16) == 16;
234 1 50       15 return 0 unless $buff =~ /^(MM\0\x2b\0\x08\0\0|II\x2b\0\x08\0\0\0)/;
235 1 50       5 if ($$dirInfo{OutFile}) {
236 0         0 $et->Error('ExifTool does not support writing of BigTIFF images');
237 0         0 return 1;
238             }
239 1         5 $et->SetFileType('BTF'); # set the FileType tag
240 1         8 SetByteOrder(substr($buff, 0, 2));
241 1         9 my $offset = Image::ExifTool::Get64u(\$buff, 8);
242 1 50       113 if ($$et{HTML_DUMP}) {
243 0 0       0 my $o = (GetByteOrder() eq 'II') ? 'Little' : 'Big';
244 0         0 $et->HDump(0, 8, "BigTIFF header", "Byte order: $o endian", 0);
245 0         0 $et->HDump(8, 8, "IFD0 pointer", sprintf("Offset: 0x%.8x",$offset), 0);
246             }
247 1         9 my %dirInfo = (
248             RAF => $raf,
249             DataPos => 0,
250             DirStart => $offset,
251             DirName => 'IFD0',
252             Parent => 'BigTIFF',
253             );
254 1         8 my $tagTablePtr = GetTagTable('Image::ExifTool::Exif::Main');
255 1         8 $et->ProcessDirectory(\%dirInfo, $tagTablePtr, \&ProcessBigIFD);
256 1         12 return 1;
257             }
258              
259             1; # end
260              
261             __END__