File Coverage

blib/lib/Image/MetaData/JPEG/dumpers/app1_exif.pl
Criterion Covered Total %
statement 119 119 100.0
branch 54 62 87.1
condition 14 18 77.7
subroutine 8 8 100.0
pod 0 4 0.0
total 195 211 92.4


line stmt bran cond sub pod time code
1             ###########################################################
2             # A Perl package for showing/modifying JPEG (meta)data. #
3             # Copyright (C) 2004,2005,2006 Stefano Bettelli #
4             # See the COPYING and LICENSE files for license terms. #
5             ###########################################################
6 15     15   61 use Image::MetaData::JPEG::data::Tables qw(:TagsAPP1_Exif);
  15         22  
  15         2254  
7 15     15   76 no integer;
  15         999  
  15         81  
8 15     15   362 use strict;
  15         17  
  15         332  
9 15     15   48 use warnings;
  15         16  
  15         18077  
10              
11             ###########################################################
12             # This method dumps an Exif APP1 segment. Basically, it #
13             # dumps the identifier, the two IFDs and the thumbnail. #
14             ###########################################################
15             sub dump_app1_exif {
16 161     161 0 294 my ($this) = @_;
17             # dump the identifier (not part of the TIFF header)
18 161         394 my $identifier = $this->search_record('Identifier')->get();
19 161         549 $this->set_data($identifier);
20             # dump the TIFF header; note that the offset returned by
21             # dump_TIFF_header is the current position in the newly written
22             # data area AFTER the identifier (i.e., the base is the base
23             # of the TIFF header), so it does not start from zero but from the
24             # value of $ifd0_link. Be aware that its meaning is slightly
25             # different from $offset in the parser.
26 161         459 my ($header, $offset, $endianness) = $this->dump_TIFF_header();
27 161         492 $this->set_data($header);
28             # locally set the current endianness to what we have found.
29 161         357 local $this->{endianness} = $endianness;
30             # dump all the records of the 0th IFD, and update $offset to
31             # point after the end of the current data area (with respect
32             # to the TIFF header base). This must be done even if the IFD
33             # itself is empty (in order to find the next one).
34 161 100       399 my $ifd1_link = defined $this->search_record('IFD1') ? 0 : 1;
35 161         529 $offset += $this->set_data($this->dump_ifd('IFD0', $offset, $ifd1_link));
36             # same thing with the 1st IFD. We don't have to worry if this
37             # IFD is not there, because dump_ifd tests for this case.
38 161         461 $offset += $this->set_data($this->dump_ifd('IFD1', $offset, 1));
39             # if there is thumbnail data in the main directory of this
40             # segment, it is time to dump it. Use the reference, because
41             # this can be quite large (some tens of kilobytes ....)
42 161 100       481 if (my $th_record = $this->search_record('ThumbnailData')) {
43 82         290 (undef, undef, undef, my $tdataref) = $th_record->get();
44 82         248 $this->set_data($tdataref); }
45             }
46              
47             ###########################################################
48             # This method reconstructs a TIFF header and returns a #
49             # list with all the relevant values. Nothing is written #
50             # to the data area. Records are searched for in the #
51             # directory specified by the second argument. #
52             ###########################################################
53             sub dump_TIFF_header {
54 162     162 0 222 my ($this, $dirref) = @_;
55             # retrieve the endianness, and signature. It is not worth
56             # setting the temporary segment endianness here, do it later.
57 162         422 my $endianness=$this->search_record('Endianness',$dirref)->get();
58 162         455 my $signature =$this->search_record('Signature',$dirref)->get($endianness);
59             # create a string containing the TIFF header (we always
60             # choose the offset of the 0th IFD must to be 8 here).
61 162         230 my $ifd0_len = 8;
62 162 100       625 my $ifd0_link = pack $endianness eq $BIG_ENDIAN ? "N" : "V", $ifd0_len;
63 162         369 my $header = $endianness . $signature . $ifd0_link;
64             # return all relevant values in a list
65 162         495 return ($header, $ifd0_len, $endianness);
66             }
67              
68             ###########################################################
69             # This is the core of the Exif APP1 dumping method. It #
70             # takes care to dump a whole IFD, including a special #
71             # treatement for thumbnails and makernotes. No action is #
72             # taken unless there is already a directory for this IFD #
73             # in the structured data area of the segment. #
74             # ------------------------------------------------------- #
75             # Special treatement for tags holding an IFD offset (this #
76             # includes makernotes); these tags are regenerated on the #
77             # fly (since they are no more stored) and their value is #
78             # recalculated and written to the raw data area. #
79             # ------------------------------------------------------- #
80             # New argument ($next), which specifies how the next_link #
81             # pointer is to be treated: '0' --> the pointer is dumped #
82             # with a non zero value; '1' --> the pointer is dumped #
83             # with value set to zero; '2' -->: the pointer is ignored #
84             ###########################################################
85             sub dump_ifd {
86 630     630 0 987 my ($this, $dirnames, $offset, $next) = @_;
87             # set the next link flag to zero if it is undefined
88 630 50       1333 $next = 0 unless defined $next;
89             # retrieve the appropriate record list (specified by a '@' separated
90             # list of dir names in $dirnames to be interpreted in sequence). If
91             # this fails, return immediately with a reference to an empty string
92 630         1467 my $dirref = $this->search_record_value($dirnames);
93 630 100       1348 return \ (my $ns = '') unless $dirref;
94             # $short and $long are two useful format strings correctly taking
95             # into account the IFD endianness. $format is a format string for
96             # packing an Interoperability array
97 575 100       1278 my $short = $this->{endianness} eq $BIG_ENDIAN ? 'n' : 'v';
98 575 100       1059 my $long = $this->{endianness} eq $BIG_ENDIAN ? 'N' : 'V';
99 575         1593 my $format = $short. $short . $long;
100             # retrieve the record list for this IFD, then eliminate the REFERENCE
101             # records (added by the parser routine, they were not in the JPEG file).
102 575         774 my @records = grep { $_->{type} != $REFERENCE } @$dirref;
  7246         10577  
103             # for each reference record with a non-undef extra field, regenerate
104             # the corresponding offset record (which can be retraced from the
105             # "extra" field) and insert it into the @records list with a dummy
106             # value (0). We can safely use $LONG as record type (new-style offsets).
107 392         1355 push @records, map {
108 7246 100       12330 my $nt = JPEG_lookup($this->{name}, $dirnames, $_->{extra});
109 392         3362 new Image::MetaData::JPEG::Record($nt, $LONG, \ pack($long, 0)) }
110 575         852 grep { $_->{type} == $REFERENCE && $_->{extra} } @$dirref;
111             # sort the accumulated records with respect to their tags (numeric).
112             # This is not, strictly speaking mandatory, but the file looks more
113             # polished after this (am I introducing any gratuitous incompatibility?)
114 575         1721 @records = sort { $a->{key} <=> $b->{key} } @records;
  10965         10459  
115             # the IFD data area is to be initialised with two bytes specifying
116             # the number of Interoperability arrays.
117 575         1410 my $ifd_content = pack $short, scalar @records;
118             # Data areas too large for the Interop array will be saved in $extra;
119             # $remote should point to its beginning (from TIFF header base), so we
120             # must skip 12 bytes for each Interop. array, 2 bytes for the initial
121             # count (and 4 bytes for the next IFD link, unless $next is two).
122 575         1294 my ($remote, $extra) = ($offset + 2 + 12*@records, '');
123 575 50       1140 $remote += 4 unless $next == 2;
124             # managing the thumbnail is not trivial. We want to be sure that
125             # its declared size corresponds to the reality and correct if
126             # this is not the case (is this a stupid idea?)
127 575 100 100     1582 if ($dirnames eq 'IFD1' &&
128             (my $th_record = $this->search_record('ThumbnailData'))) {
129 82         248 (undef, undef, undef, my $tdataref) = $th_record->get();
130 82         248 for ($THTIFF_LENGTH, $THJPEG_LENGTH) {
131 164         417 my $th_len = $this->search_record($_, $dirref);
132 164 100       594 $th_len->set_value(length $$tdataref) if $th_len; } }
133             # the following tags can be found only in IFD1 in APP1, and concern
134             # the thumbnail location. They must be dealt with in a special way.
135 575         1709 my %th_tags = ($THTIFF_OFFSET => undef, $THJPEG_OFFSET => undef);
136             # determine weather this IFD can have subidrectories or not; if so,
137             # get a special mapping table from %IFD_SUBDIRS (avoid autovivification)
138 575         1254 my $path = join '@', $this->{name}, $dirnames;
139 575 100       1574 my $mapping = exists $IFD_SUBDIRS{$path} ? $IFD_SUBDIRS{$path} : undef;
140             # loop on all selected records and dump them
141 575         805 for my $record (@records) {
142             # extract all necessary information about this
143             # Interoperability array, with the correct endianness.
144 7243         14722 my ($tag, $type, $count, $dataref) = $record->get($this->{endianness});
145             # calculate the length of the array data, and correct $count
146             # for string-like records (it had been set to 1 during the
147             # parsing, it must be the data length in this case).
148 7243         7759 my $length = length $$dataref;
149 7243 100       12198 $count = $length if $record->get_category() eq 'S';
150             # the last four bytes in an interoperability array are either
151             # data or an address; prepare a variable for holding this value
152 7243         6349 my $record_end = '';
153             # if this IFD1 record specifies the thumbnail location, it needs
154             # a special treatment, since we cannot yet know where the thumbnail
155             # will be located. Write a bogus offset now and overwrite it later.
156 7243 100 100     34975 if ($dirnames eq 'IFD1' && exists $th_tags{$tag}) {
    100 100        
    100          
157 72         131 $th_tags{$tag} = 8 + length $ifd_content;
158 72         95 $record_end = "\000\000\000\000"; }
159             # if this Interop array is known to correspond to a subdirectory
160             # (use %$mapping for this), the subdirectory content is calculated
161             # on the fly, and stored in this IFD's remote data area. Its offset
162             # instead is saved at the end of the Interoperability array.
163             elsif ($mapping && exists $$mapping{$tag}) {
164 392         1325 my $is_makernote = ($tag =~ $MAKERNOTE_TAG);
165 392         818 my $extended_dirnames = $dirnames.'@'.$$mapping{$tag};
166             # MakerNotes require a special treatment, including rewriting
167             # type and count (one LONG is really many UNDEF bytes); other
168             # subIFD's are written by a recursive dump_ifd (next link is 0).
169 392 100       1413 my $subifd = $is_makernote ?
170             $this->dump_makernote($extended_dirnames, $remote) :
171             $this->dump_ifd($extended_dirnames, $remote, 1);
172 392 100       879 $type = $UNDEF, $count = length($$subifd) if $is_makernote;
173 392         722 $record_end = pack $long, $remote;
174 392         581 $extra .= $$subifd; $remote += length $$subifd; }
  392         662  
175             # if the data length is not larger than four bytes, we are ok.
176             # $$dataref is simply appended (with padding up to 4 bytes,
177             # AFTER $$dataref, independently of the IFD endianness).
178 3485         5219 elsif ($length <= 4) { $record_end = $$dataref . "\000"x(4-$length); }
179             # if $$dataref is too big, it must be packed in the $extra
180             # section, and its pointer appended here. Remember to update
181             # $remote for the next record of this type.
182 3294         4312 else { $record_end = pack $long, $remote;
183 3294         2804 $remote += $length; $extra .= $$dataref; }
  3294         3400  
184             # the interoperability array starts with tag, type and count,
185             # followed by $record_end (4 bytes): dump into the ifd data area
186 7243         16392 $ifd_content .= (pack $format, $tag, $type, $count) . $record_end; }
187             # after the Interop. arrays there can be a link to the next IFD
188             # (this takes 4 bytes). $next = 0 --> write the next IFD offset,
189             # = 1 --> write zero, 2 --> do not write these four bytes.
190 575 100       1096 $ifd_content .= pack $long, $remote if $next == 0;
191 575 100       1241 $ifd_content .= pack $long, 0 if $next == 1;
192             # then, we save the remote data area
193 575         2034 $ifd_content .= $extra;
194             # if the thumbnail offset tags were found during the scan, we
195             # need to overwrite their values with a meaningful offset now.
196 575         1318 for (keys %th_tags) {
197 1150 100       2354 next unless my $overwrite = $th_tags{$_};
198 72         244 my $tag_record = $this->search_record($_, $dirref);
199 72         187 $tag_record->set_value($remote);
200 72         218 my $new_offset = $tag_record->get($this->{endianness});
201 72         262 substr($ifd_content, $overwrite, length $new_offset) = $new_offset; }
202             # return a reference to the scalar which holds the binary dump
203             # of this IFD (to be saved in the caller routine, I think).
204 575         3067 return \$ifd_content;
205             }
206              
207             ###########################################################
208             # This routine dumps all kinds of makernotes. Have a look #
209             # at parse_makernote() for further details. #
210             ###########################################################
211             sub dump_makernote {
212 87     87 0 154 my ($this, $dirnames, $offset) = @_;
213             # look for a MakerNote subdirectory beginning with $dirnames: the
214             # actual name has the format appended, e.g., MakerNoteData_Canon.
215 87         757 $dirnames =~ s/(.*@|)([^@]*)/$1/;
216 87         266 my $dirref = $this->search_record_value($dirnames);
217 2201         4861 $dirnames .= $_->{key}, $dirref = $_->get_value(), last
218 87         184 for (grep{$_->{key}=~/^$2/} @$dirref);
219             # Also look for the subdir with special information.
220 87         336 my $spcref = $this->search_record_value($dirnames.'@special');
221             # entering here without the dir and its subdir being present is an error
222 87 50 33     463 $this->die('MakerNote subdirs not found') unless $dirref && $spcref;
223             # read all MakerNote special values (added by the parser routine)
224 435         849 my ($data, $signature, $endianness, $format, $error) =
225 87         198 map { $this->search_record_value($_, $spcref) }
226             ('ORIGINAL', 'SIGNATURE', 'ENDIANNESS', 'FORMAT', 'ERROR');
227             # die and debug if the format record is not present
228 87 50       256 $this->die('FORMAT not found') unless $format;
229             # if the format is unknown or there was an error at parse time, it
230             # is wiser to return the original, unparsed content of the MakerNote
231 87 100 100     478 if ($format =~ /unknown/ || defined $error) {
232 2 50       6 $this->die('ORIGINAL data not found') unless $data; return \$data; };
  2         5  
233             # also extract the property table for this MakerNote format
234 85         256 my $hash = $$HASH_MAKERNOTES{$format};
235             # now, die if the signature or endianness is still undefined
236 85 50 33     398 $this->die('Properties not found')unless defined $signature && $endianness;
237             # in general, the MakerNote's next-IFD link is zero, but some
238             # MakerNotes do not even have these four bytes: prepare the flag
239 85 50       259 my $next_flag = exists $$hash{nonext} ? 2 : 1;
240             # in general, MakerNote's offsets are computed from the APP1 segment
241             # TIFF base; however, some formats compute offsets from the beginning
242             # of the MakerNote itself: setup the offset base as required.
243 85 100       263 $offset = length($signature) + (exists $$hash{mkntstart} ? 0 : $offset);
244             # initialise the data area with the detected signature
245 85         131 $data = $signature;
246             # some MakerNotes have a TIFF header on their own, freeing them
247             # from the relocation problem; values from this header overwrite
248             # the previously assigned values; records are saved in $mknt_dir.
249 85 100       274 if (exists $$hash{mkntTIFF}) {
250 1         3 my ($TIFF_header, $TIFF_offset, $TIFF_endianness)
251             = $this->dump_TIFF_header($spcref);
252 1 50       4 $this->die('Endianness mismatch') if $endianness ne $TIFF_endianness;
253 1         2 $data .= $TIFF_header; $offset = $TIFF_offset; }
  1         1  
254             # Unstructured case: the content of the MakerNote is simply
255             # a sequence of bytes, which must be encoded using $$hash{tags}
256 85 100       240 if (exists $$hash{nonIFD}) {
257 3608         4488 $data .= $this->search_record($$_[0], $dirref)->get($endianness)
258 82         121 for map {$$hash{tags}{$_}} sort {$a <=> $b} keys %{$$hash{tags}}; }
  15335         11384  
  82         1120  
259             # Structured case: the content of the MakerNote can be dumped
260             # with dump_ifd (change locally the endianness value).
261 3         7 else { local $this->{endianness} = $endianness;
262 3         5 $data .= ${$this->dump_ifd($dirnames, $offset, $next_flag)} };
  3         12  
263             # return the MakerNote as a binary object
264 85         959 return \$data;
265             }
266              
267             # successful load
268             1;