File Coverage

blib/lib/Image/MetaData/JPEG/access/app1_exif.pl
Criterion Covered Total %
statement 220 220 100.0
branch 121 144 84.0
condition 18 24 75.0
subroutine 23 23 100.0
pod 6 14 42.8
total 388 425 91.2


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             package Image::MetaData::JPEG;
7 14     14   58 use Image::MetaData::JPEG::data::Tables qw(:Endianness :TagsAPP1_Exif);
  14         17  
  14         2843  
8 14     14   71 use Image::MetaData::JPEG::Segment;
  14         22  
  14         248  
9 14     14   49 no integer;
  14         14  
  14         61  
10 14     14   249 use strict;
  14         16  
  14         310  
11 14     14   50 use warnings;
  14         15  
  14         6091  
12              
13             ###########################################################
14             # This method finds the $index-th Exif APP1 segment in #
15             # the file, and returns its reference. If $index is #
16             # undefined, it defaults to zero (i.e., first segment). #
17             # If no such segment exists, it returns undef. If $index #
18             # is (-1), the routine returns the number of available #
19             # Exif APP1 segments (which is >= 0). #
20             ###########################################################
21             sub retrieve_app1_Exif_segment {
22 64     64 1 3670 my ($this, $index) = @_;
23             # prepare the segment reference to be returned
24 64         91 my $chosen_segment = undef;
25             # $index defaults to zero if undefined
26 64 100       184 $index = 0 unless defined $index;
27             # get the references of all APP1 segments
28 64         233 my @references = $this->get_segments('APP1$');
29             # filter out those without Exif information
30 64         110 @references = grep { $_->is_app1_Exif() } @references;
  64         168  
31             # if $index is -1, return the size of @references
32 64 100       166 return scalar @references if $index == -1;
33             # return the $index-th such segment, or undef if absent
34 55 100       185 return exists $references[$index] ? $references[$index] : undef;
35             }
36              
37             ###########################################################
38             # This method forces an Exif APP1 segment to be present #
39             # in the file, and returns its reference. The algorithm #
40             # is the following: 1) if at least one segment with these #
41             # properties is already present, the first one is retur- #
42             # ned; 2) if [1] fails, an APP1 segment is added and #
43             # initialised with an Exif structure. #
44             ###########################################################
45             sub provide_app1_Exif_segment {
46 137     137 1 243 my ($this) = @_;
47             # get the references of all APP1 segments
48 137         585 my @app1_refs = $this->get_segments('APP1$');
49             # filter out those without Exif information
50 137         251 my @Exif_refs = grep { $_->is_app1_Exif() } @app1_refs;
  128         362  
51             # if @Exif_refs is not empty, return the first segment
52 137 100       470 return $Exif_refs[0] if @Exif_refs;
53             # if we are still here, an Exif APP1 segment must be created
54             # and initialised (contrary to the IPTC case, an existing APP1
55             # segment, presumably XPM, cannot be "adapted"). We write here
56             # a minimal Exif segment with no data at all (in big endian).
57 9         61 my $minimal_exif = $APP1_EXIF_TAG . $BIG_ENDIAN
58             . pack "nNnN", $APP1_TIFF_SIG, 8, 0, 0;
59 9         48 my $Exif = new Image::MetaData::JPEG::Segment('APP1', \ $minimal_exif);
60             # choose a position for the new segment (the improved version
61             # of find_new_app_segment_position can now be safely used).
62 9         44 my $position = $this->find_new_app_segment_position('APP1');
63             # actually insert the segment
64 9         43 $this->insert_segments($Exif, $position);
65             # return a reference to the new segment
66 9         23 return $Exif;
67             }
68              
69             ###########################################################
70             # This method eliminates the $index-th Exif APP1 segment #
71             # from the JPEG file segment list. If $index is (-1) or #
72             # undef, all Exif APP1 segments are affected at once. #
73             ###########################################################
74             sub remove_app1_Exif_info {
75 8     8 1 22148 my ($this, $index) = @_;
76             # the default value for $index is -1
77 8 100       38 $index = -1 unless defined $index;
78             # this is the list of segments to be purged (initially empty)
79 8         20 my %deleteme = ();
80             # call the selection routine and save the segment reference
81 8         30 my $segment = $this->retrieve_app1_Exif_segment($index);
82             # if $segment is really a non-null segment reference, mark it
83             # for deletion; otherwise, it is the number of segments to be
84             # deleted (this happens if $index is -1). In this case, the
85             # whole procedure is repeated for every index.
86 8 50       28 $segment->{name} = "deleteme" if ref $segment;
87 8 50       26 if ($index == -1) { $this->retrieve_app1_Exif_segment($_)
88 8         34 ->{name} = "deleteme" for 0..($segment-1); }
89             # remove marked segments from the file
90 8         38 $this->drop_segments('deleteme');
91             }
92              
93             ###########################################################
94             # This method is an interface to the method with the same #
95             # name in the Segment class. First, the first Exif APP1 #
96             # segment is retrieved (if there is no such segment, the #
97             # undefined value is returned). Then the get_Exif_data is #
98             # called on this segment passing the arguments through. #
99             # For further details, see Segment::get_Exif_data() and #
100             # JPEG::retrieve_app1_Exif_segment(). #
101             ###########################################################
102             sub get_Exif_data {
103 33     33 1 17036 my $this = shift;
104             # get the first Exif APP1 segment in the current JPEG
105             # file (if no such segment exists, this returns undef).
106 33         108 my $segment = $this->retrieve_app1_Exif_segment();
107             # return undef if not suitable segment exists
108 33 50       89 return undef unless defined $segment;
109             # pass the arguments through to the Segment method
110 33         92 return $segment->get_Exif_data(@_);
111             }
112              
113             ###########################################################
114             # This method is an interface to the method with the same #
115             # name in the Segment class. First, the first Exif APP1 #
116             # segment is retrieved (if there is no such segment, one #
117             # is created and initialised). Then the set_Exif_data is #
118             # called on this segment passing the arguments through. #
119             # For further details, see Segment::set_Exif_data() and #
120             # JPEG::provide_app1_Exif_segment(). #
121             ###########################################################
122             sub set_Exif_data {
123 134     134 1 54424 my $this = shift;
124             # get the first Exif APP1 segment in the current JPEG file
125             # (if there is no such segment, initialise one; therefore,
126             # this call cannot fail [mhh ...]).
127 134         483 my $segment = $this->provide_app1_Exif_segment();
128             # pass the arguments through to the Segment method
129 134         409 return $segment->set_Exif_data(@_);
130             }
131              
132             ###########################################################
133             # An Interoperability subIFD is supposed to be used for, #
134             # well, inter-operability, so it should be made as stan- #
135             # dard as possible. This method takes care to chose a set #
136             # of "correct" values for you: the Index is set to "R98" #
137             # (because we are interested in IFD0), Version to 1.0, #
138             # FileFormat to Exif v.2.2, and the picture dimensions #
139             # are taken from get_dimensions(). #
140             ###########################################################
141             sub forge_interoperability_IFD {
142 2     2 1 615 my $this = shift;
143             # get the real picture dimensions
144 2         9 my ($x_dim, $y_dim) = $this->get_dimensions();
145             # prepare a table of records for the Interop. IFD
146 2         13 my $std_values = {
147             'InteroperabilityIndex' => "R98",
148             'InteroperabilityVersion' => "0100",
149             'RelatedImageFileFormat', => "Exif JPEG Ver. 2.2",
150             'RelatedImageWidth' => $x_dim,
151             'RelatedImageLength' => $y_dim, };
152             # call the setter method for Exif data appropriately
153 2         6 return $this->set_Exif_data($std_values, 'INTEROP_DATA', 'REPLACE');
154             }
155              
156             ###########################################################
157             # The following routines best fit as Segment methods. #
158             ###########################################################
159             package Image::MetaData::JPEG::Segment;
160 14     14   68 use Image::MetaData::JPEG::data::Tables qw(:Lookups);
  14         22  
  14         27943  
161              
162             ###########################################################
163             # A private hash for get_Exif_data and set_Exif_data. #
164             # Each '@' indicates the beginning of a new subdirectory. #
165             ###########################################################
166             my %WHAT2IFD = ('ROOT_DATA' => '',
167             'IFD0_DATA' => '@IFD0',
168             'SUBIFD_DATA' => '@IFD0@SubIFD',
169             'GPS_DATA' => '@IFD0@GPS',
170             'INTEROP_DATA' => '@IFD0@SubIFD@Interop',
171             'MAKERNOTE_DATA' => '@IFD0@SubIFD@MakerNoteData',
172             'IFD1_DATA' => '@IFD1' );
173              
174             ###########################################################
175             # This method inspects a segments, and returns "undef" if #
176             # it is not an APP1 segment or if its structure is not #
177             # Exif like. Otherwise, it returns "ok". #
178             ###########################################################
179             sub is_app1_Exif {
180 543     543 0 600 my ($this) = @_;
181             # return undef if this segment is not APP1
182 543 50       1295 return undef unless $this->{name} eq 'APP1';
183             # return undef if there is no 'Identifier' in this segment
184             # or if it does not match with an Exif-like segment
185 543         1333 my $identifier = $this->search_record_value('Identifier');
186 543 50 33     2228 return undef unless defined $identifier && $identifier eq $APP1_EXIF_TAG;
187             # return ok
188 543         1293 return "ok";
189             }
190              
191             ###########################################################
192             # This method accepts two arguments ($what and $type) and #
193             # returns the content of the Exif APP1 segment packed in #
194             # various forms. All Exif records are natively identified #
195             # by numeric tags (keys), which can be "translated" into #
196             # a human-readable form by using the Exif standard docs; #
197             # only a few fields in the Exif APP1 preamble (they are #
198             # not Exif records) are always identified by this module #
199             # by means of textual tags. The $type argument selects #
200             # the output format for the record keys (tags): #
201             # - NUMERIC: record tags are native numeric keys #
202             # - TEXTUAL: record tags are human-readable (default) #
203             # Of course, record values are never translated. If a #
204             # numeric Exif tag is not known, a custom textual key is #
205             # created with "Unknown_tag_" followed by the numerical #
206             # value (this solves problems with non-standard tags). #
207             # ------------------------------------------------------- #
208             # Error conditions (invalid $what's and $type's) manifest #
209             # themselves through an undefined return value. So, undef #
210             # should not be used for other cases: use empty hashes or #
211             # a reference to an empty string for the thumbnail. #
212             # ------------------------------------------------------- #
213             # The subset of Exif tags returned by this method is #
214             # determined by the value of $what. If $what is set equal #
215             # to '*_DATA', this method returns a reference to a flat #
216             # hash, corresponding to one or more IFD (sub)dirs: #
217             # - ROOT_DATA APP1(TIFF header records and similar) #
218             # - IFD0_DATA APP1@IFD0 (primary image TIFF tags) #
219             # - SUBIFD_DATA APP1@IFD0@SubIFD (Exif private tags) #
220             # - GPS_DATA APP1@IFD0@GPS (GPS data in IFD0) #
221             # - INTEROP_DATA APP1@IFD0@SubIFD@Interop(erability) #
222             # - IFD1_DATA APP1@IFD1 (thumbnail TIFF tags) #
223             # - IMAGE_DATA a merge of IFD0_DATA and SUBIFD_DATA #
224             # - THUMB_DATA an alias for IFD1_DATA #
225             # Setting $what equal to 'ALL' returns a data dump very #
226             # close to the Exif APP1 segment structure; the returned #
227             # value is a reference to a hash of hashes: each element #
228             # of the root-level hash is a pair ($name, $hashref), #
229             # where $hashref points to a second-level hash containing #
230             # a copy of all Exif records present in the $name IFD #
231             # (sub)directory. The root-level hash includes a special #
232             # root directory (named 'APP1') containing some non Exif #
233             # parameters. Last, setting $what to 'THUMBNAIL' returns #
234             # a reference to a copy of the actual Exif thumbnail #
235             # image (not returned by 'THUMB_DATA'), if present, or a #
236             # reference to an empty string, if not present. #
237             # ------------------------------------------------------- #
238             # Note that the Exif record values' format is not checked #
239             # to be valid according to the Exif standard. This is, in #
240             # some sense, consistent with the fact that also "unknown"#
241             # tags are included in the output. #
242             ###########################################################
243             sub get_Exif_data {
244 154     154 0 30726 my ($this, $what, $type) = @_;
245             # refuse to work unless you are an Exif APP1 segment
246 154 50       284 return undef unless $this->is_app1_Exif();
247             # set the default section and type, if undefined;
248 154 100       306 $what = 'ALL' unless defined $what;
249 154 100       272 $type = 'TEXTUAL' unless defined $type;
250             # reject unknown types (return undef, which means 'error')
251 154 100       535 return undef unless $type =~ /^NUMERIC$|^TEXTUAL$/;
252             # a reference to the hash to be returned, initially empty
253 152         182 my $pairs = {};
254             # ========= SPECIAL CASES ====================================
255             # IMAGE_DATA means IFD0_DATA and SUBIFD_DATA (merged)
256 152 100       305 if ($what eq 'IMAGE_DATA') {
257 10         24 for ('IFD0_DATA', 'SUBIFD_DATA') {
258 20         54 my $h = $this->get_Exif_data($_, $type);
259 20         174 @$pairs{keys %$h} = values %$h; } return $pairs; }
  10         33  
260             # ALL means a hash of hashes with all subdirs (even if emtpy)
261 142 100       260 if ($what eq 'ALL') {
262 8         54 $$pairs{$_} = $this->get_Exif_data($_, $type) for keys %WHAT2IFD;
263 8         31 return $pairs; }
264             # $what equal to 'THUMBNAIL' is special: it returns a copy of the
265             # thumbnail data area (this can be a self-contained JPEG picture
266             # or an uncompressed picture needing more parameters from IFD1).
267             # If no thumbnail is there, return a reference to an empty string
268 134 100       255 if ($what eq 'THUMBNAIL') {
269 8         25 my $thumbnail = $this->search_record_value('ThumbnailData');
270 8 100       46 return $thumbnail ? \ $thumbnail : \ (my $ns = ''); }
271             # IFD1_DATA is an alias for THUMB_DATA
272 126 100       232 $what = 'IFD1_DATA' if $what eq 'THUMB_DATA';
273             # ============================================================
274             # %WHAT2IFD keys must correspond to the legal $what's. It is now
275             # time to reject unknown sections ('THUMBNAIL' already dealt with).
276             # As usual, this error condition corresponds to returning undef.
277 126 100       271 return undef unless exists $WHAT2IFD{$what};
278             # $WHAT2IFD{$what} contains a '@' separated list of dir names;
279             # use it to retrieve a reference to the appropriate record list
280 125         170 my $path = $WHAT2IFD{$what};
281             # follow the path blindly, get undef on problems
282 125         290 my $dirref = $this->search_record_value($path);
283             # give $path a second try, assuming the last part of the path
284             # is just the beginning of a tag (this is needed for MakerNote).
285             # This might modify $path and set $dirref to non-undefined.
286 125 100       259 unless (defined $dirref) {
287 27         176 $path =~ s/(.*@|)([^@]*)/$1/;
288 27         74 my $partial_dirref = $this->search_record_value($path);
289 422         969 $path .= $_->{key}, $dirref = $_->get_value(), last
290 27         61 for (grep{$_->{key}=~/^$2/} @$partial_dirref);}
291             # if $dirref is undefined, the corresponding subdirectory was not
292             # present, and we are going to return a reference to an empty hash
293 125 100       244 return $pairs unless $dirref;
294             # map the record list reference to a full hash containing the subdir-
295             # ectory records as (tag => values) pairs. Do not include $REFERENCE's
296             # (private). Make COPIES of the array references found in $_->{values}
297             # (the caller could use them to corrupt the internal structures).
298 1546         1220 %$pairs = map { $_->{key} => [ @{$_->{values}} ] }
  1546         3853  
  1660         1912  
299 112         169 grep { $_->{type} != $REFERENCE } @$dirref;
300             # up to now, all record keys (tags) are numeric (exception made for
301             # some MakerNote keys and all keys in the "root" directory, for which
302             # there is no numeric counterpart). If $type is 'TEXTUAL', they must
303             # be translated (test explicitely that they are numeric).
304 112 100       349 if ($type eq "TEXTUAL") {
305             # get the right numeric-to-textual conversion table with $path
306 90         271 my $table = JPEG_lookup($this->{name}, $path);
307             # run the translation (create a name also for unknown tags)
308 90 100       338 %$pairs = map { (($_!~/^\d+$/)?$_:(exists $$table{$_}) ? $$table{$_} :
  1203 100       3926  
309             "Unknown_tag_$_") => $$pairs{$_} } keys %$pairs; }
310             # return the reference to the hash containing all data
311 112         499 return $pairs;
312             }
313              
314             ###########################################################
315             # This method is the entry point for setting Exif data in #
316             # the current APP1 segment. The mandatory arguments are: #
317             # $data (hash reference, with new records to be written), #
318             # $what (a scalar, selecting the concerned portion of the #
319             # Exif APP1 segment) and $action (a scalar specifying the #
320             # requested action). Valid values are: #
321             # $action --> ADD | REPLACE #
322             # $what --> IFD0_DATA, IFD1_DATA, INTEROP_DATA, #
323             # GPS_DATA, SUBIFD_DATA (see get_Exif_data) #
324             # THUMB_DATA (an alias for IFD1_DATA) #
325             # IMAGE_DATA (IFD0_DATA or SUBIFD_DATA) #
326             # ROOT_DATA (only 'Endianness' can be set) #
327             # .- THUMBNAIL (including automatic fields) #
328             # \____.--> $data is a scalar reference here ... #
329             # The behaviour of $action is similar to that for IPTC #
330             # data. Note that Exif records are non-repeatable in #
331             # nature, so there is no need for an 'UPDATE' action in #
332             # addition to 'ADD' (they would both overwrite an old re- #
333             # cord with the same tag as a new record); $action equal #
334             # to 'REPLACE', on the other hand, clears the appropriate #
335             # record list(s) before the insertions. Records are #
336             # rewritten in increasing (numerical) tag order. #
337             # The elements of $data which can be converted to valid #
338             # records are inserted in the appropriate (sub)IFD, the #
339             # others are returned. The return value is always a hash #
340             # reference; in general it contains rejected records. If #
341             # an error occurs in a very early stage of the setter, #
342             # this reference contains a single entry with key='ERROR' #
343             # and value set to some meaningful error message. So, a #
344             # reference to an empty hash means that everything was OK.#
345             # ------------------------------------------------------- #
346             # $what equal to 'THUMBNAIL' is meant to replace the IFD1 #
347             # thumbnail. $data should be a reference to a scalar or #
348             # to a JPEG object containing the new thumbnail ; if it #
349             # points to an emtpy string, the thumbnail is erased. #
350             # Corresponding fields follow the thumbnail (all this is #
351             # dealt with by a private method). $data undefined DOES #
352             # NOT erase the thumbnail, it is an error (too dangerous).#
353             # ------------------------------------------------------- #
354             # When $what is 'IMAGE_DATA', try to insert first into #
355             # SubIFD, then, into IFD0. This favours SubIFD standard #
356             # tags in front of IFD company-related non-standard tags. #
357             # For security reasons however, these non-standard tags #
358             # should be labelled as invalid: this would prevent them #
359             # from being set but not from being recognised if present.#
360             # ------------------------------------------------------- #
361             # Remeber that, even for $action eq REPLACE, we cannot #
362             # delete all the records. We must preserve $REFERENCE #
363             # records, otherwise the corresponding directories would #
364             # be forgotten; we don't want that, for instance, SubIFD #
365             # is deleted when the records of IFD0 are REPLACED. #
366             # ------------------------------------------------------- #
367             # The fourth argument ($dontupdate) is to be considered #
368             # strictly private. It is used by set_Exif_data itself #
369             # when called with $action eq 'IMAGE_DATA', so that the #
370             # update() routine can be called only once (not twice). #
371             # ------------------------------------------------------- #
372             # First, some basic argument checking is performed: the #
373             # segment must be of the appropriate type, $data must be #
374             # a hash reference, $action and $what must be valid. #
375             # Then, the appropriate record (sub)directory is created #
376             # (this can trigger the creation of other directories), #
377             # if it is not present. Then records are screened and #
378             # set. Mandatory data are added, if not present, at the #
379             # end of the process (see Tables.pm for this). Note that #
380             # there are some record intercorrelations still neglected.#
381             ###########################################################
382             sub set_Exif_data {
383 197     197 0 21698 my ($this, $data, $what, $action, $dontupdate) = @_;
384             # refuse to work unless you are an Exif APP1 segment
385 197 50       431 return {'ERROR'=>'Not an Exif APP1 segment'} unless $this->is_app1_Exif();
386             # set the default action, if undefined
387 197 100       614 $action = 'REPLACE' unless defined $action;
388             # refuse to work for unkwnon actions
389 197 100       949 return {'ERROR'=>"Unknown action $action"} unless $action =~ /ADD|REPLACE/;
390             # return immediately if $data is undefined
391 194 100       438 return {'ERROR'=>'Undefined data reference'} unless defined $data;
392             # ========= SPECIAL CASES ====================================
393             # IMAGE_DATA: first, try to insert all tags into SubIFD, then, try
394             # to insert rejected data into IFD0, last, return doubly rejected data.
395 193 100       491 if ($what eq 'IMAGE_DATA') {
396 16         51 my $rejected = $this->set_Exif_data($data, 'SUBIFD_DATA', $action, 1);
397 16         79 return $this->set_Exif_data($rejected, 'IFD0_DATA', $action); }
398             # THUMBNAIL requires a very specific treatment
399 177 100       389 return $this->set_Exif_thumbnail($data) if $what eq 'THUMBNAIL';
400             # 'THUMB_DATA' is an alias to 'IFD1_DATA'
401 172 50       420 $what = 'IFD1_DATA' if $what eq 'THUMB_DATA';
402             # ============================================================
403             # $data must be a hash reference (from this point on)
404 172 50       414 return {'ERROR'=>'$data not a hash reference'} unless ref $data eq 'HASH';
405             # return with an error if $what is not a valid key in %WHAT2IFD
406 172 100       546 return {'ERROR'=>"Unknown section $what"} unless exists $WHAT2IFD{$what};
407             # translate $what into a path specification
408 168         384 my $path = 'APP1' . $WHAT2IFD{$what};
409             # the mandatory records list must be present (debug point)
410 168 50       579 return {'ERROR'=>'no $mandatory records'} unless exists
411             $IFD_SUBDIRS{$path}{'__mandatory'};
412             # get the mandatory record list
413 168         301 my $mandatory = $IFD_SUBDIRS{$path}{'__mandatory'};
414             # all arguments look healty, go to stage two; get the record list
415             # of the appropriate (sub)directory; this call creates the supporting
416             # directory tree if necessary, taking care of gory details.
417 168         479 my $record_list = $this->build_IFD_directory_tree($path);
418             # analyse the passed records for correctness (syntactical rules);
419             # the following function divides them into two obvious categories
420 168         501 my ($rejected, $accepted) = $this->screen_records($data, $path);
421             # For $action equal to 'ADD', we read the old records and insert
422             # them in the $accepted hash, unless they are already present.
423             # If $action is 'REPLACE' we preserve only the subdirectories
424 168 100       486 my $save = $action eq 'REPLACE' ? 'p' : '.';
425 168         377 my $old_records = [ grep {$_->get_category() =~ $save} @$record_list ];
  2846         4384  
426 168         472 $this->complement_records($old_records, $accepted);
427             # retrieve the section about mandatory values for this $path and transform
428             # them into Records (there is also a syntactical analysis, but all records
429             # should be accepted here, so I take the return value in scalar context).
430             # ('B' is currently necessary for stupid root-level mandatory records)
431 168         388 my ($notempty, $values) = $this->screen_records($mandatory, $path, 'B');
432 168 50       436 $this->die('Mandatory values rejected') if %$notempty;
433             # merge in mandatory records, if they are not already present
434 168         384 $this->complement_records($values, $accepted);
435             # take all records from $accepted and set them into the record
436             # list (their order must anambiguous, so perform a clever sorting).
437 168         485 @$record_list = ordered_record_list($accepted, $path);
438             # commit changes to the data area unless explicitely forbidden
439 168 100       1124 $this->update() unless $dontupdate;
440             # that's it, return the reference to the rejected data hash
441 168         2562 return $rejected;
442             }
443              
444             ###########################################################
445             # This private method is called by set_Exif_data when the #
446             # $what argument is set to 'THUMBNAIL'. $data must be a #
447             # reference to a JPEG object or a reference to a scalar #
448             # value containing a valid JPEG stream (an undefined ref. #
449             # is considered an error!). First, we erase all thumbnail #
450             # related records from IFD1 then we reinsert those which #
451             # are appropriate. Last, the update method is called #
452             # (this also fixes some fields). #
453             # ------------------------------------------------------- #
454             # ($$data is ''): nothing else to do, thumbnail erased. #
455             # ($$data is a JPEG stream or a JPEG object): thumbnail #
456             # data are saved in the root level directory, and a few #
457             # records are added to IFD1: 'JPEGInterchangeFormat', #
458             # 'JPEGInterchangeFormatLength', and 'Compression' set #
459             # to six (this indicates a JPEG thumbnail). #
460             ###########################################################
461             sub set_Exif_thumbnail {
462 5     5 0 7 my ($this, $dataref) = @_;
463             # this variable holds the thumbnail format
464 5         5 my $type = undef;
465             # $dataref must be a valid reference: I don't want the user to be
466             # able to erase the thumbnail by passing an erroneously undef ref.
467 5 50       23 return { 'ERROR' => 'argument is not a reference' } unless ref $dataref;
468             # if $dataref points to an Image::MetaData::JPEG object, replace it
469             # with a reference to its bare content and set $type to 'JPEG'.
470 5 100       16 if ('Image::MetaData::JPEG' eq ref $dataref) {
471 2         5 my $r = ""; $dataref->save(\ $r); $dataref = \ $r; $type = 'JPEG'; }
  2         9  
  2         4  
  2         3  
472             # $dataref must now be a scalar reference; everything else is an error
473 5 50       20 return { 'ERROR' => 'not a good reference' } if ref $dataref ne 'SCALAR';
474             # try to recognise the content of $$dataref. If it is defined but empty,
475             # we just need to erase the thumbnail. If it is accepted by the JPEG
476             # ctor or $type is already 'JPEG', we consider it a regular JPEG stream.
477 5 100       13 $type = 'NONE' if length $$dataref == 0;
478 5 100 100     21 $type = 'JPEG' if ! $type && Image::MetaData::JPEG->new($dataref, '');
479             # If $type is not yet set, generate an error (TIFF not yet supported ...)
480 5 100       41 return { 'Error' => 'unsupported thumbnail format' } unless $type;
481             # the following lists contain all records to be erased before inserting
482             # the new thumbnail. They are inserted in a hash for faster lookup
483 4         13 my %thumb_records = map { $_ => 1 }
  40         65  
484             ('Compression', 'JPEGInterchangeFormat', 'JPEGInterchangeFormatLength',
485             'StripOffsets','ImageWidth','ImageLength','BitsPerSample',
486             'SamplesPerPixel', 'RowsPerStrip', 'StripByteCounts');
487             # get the appropriate record lists (IFD1) (build it if not present)
488 4         16 my $ifd1_list = $this->build_IFD_directory_tree('APP1@IFD1');
489             # delete all tags mentioned in %forbidden. This is a fresh start before
490             # inserting a new thumbnail (and the whole story if $type is 'NONE')
491 18         39 @$ifd1_list = grep
492 4         10 {! exists $thumb_records{JPEG_lookup('APP1@IFD1', $_->{key})}} @$ifd1_list;
493             # delete existing thumbnail data and replace it if necessary; this
494             # "record" is in the root directory, and a regular expression check
495             # is really impossible. So, we adopt a low-level approach here ...
496 4         9 my $root_list = $this->{records};
497 4         7 @$root_list = grep { $_->{key} ne 'ThumbnailData' } @$root_list;
  23         36  
498             # insert the thumbnail, if necessary (this must be the last record)
499 4 50       26 push @$root_list, new Image::MetaData::JPEG::Record
500             ('ThumbnailData', $UNDEF, $dataref, length $$dataref) if $dataref;
501             # if $type is 'JPEG', we need to insert some records in IFD1 ...
502 4 100       13 if ($type eq 'JPEG') {
503             # we have two non-offset records: the thumbnail type and its length
504 3         11 my $records = { 'Compression' => 6, # 6 means JPEG-compressed
505             'JPEGInterchangeFormatLength' => length $$dataref };
506             # analyse the passed records for correctness (semi-paranoia)
507 3         12 my ($rej, $accepted) = $this->screen_records($records,'APP1@IFD1','T');
508             # $rej must be an empty hash, or we have a problem
509 3 50       8 return { 'Error' => 'Records rejected internally! [JPEG]' } if %$rej;
510             # add all other old (non-thumbnail-related) records
511 3         9 $this->complement_records($ifd1_list, $accepted);
512             # add the 'JPEGInterchangeFormat' record (an offset). This is really
513             # dummy, it is here to trigger the correct behaviour in update(), but
514             # I really should modify update() to make it calculate the field on
515             # its own (since it already calcuates its value anyway).
516 3         8 my $JIF = JPEG_lookup('APP1@IFD1', 'JPEGInterchangeFormat');
517 3         26 $$accepted{$JIF} = new
518             Image::MetaData::JPEG::Record($JIF, $LONG, \ ("\000" x 4), 1);
519             # take all records from $accepted and set them into the record
520             # list (their order must anambiguous, so perform a clever sorting).
521 3         10 @$ifd1_list = ordered_record_list($accepted, 'APP1@IFD1'); }
522             # remember to commit these changes to the data area
523 4         17 $this->update();
524             # return success (a reference to an empty hash)
525 4         38 return {};
526             }
527              
528             ###########################################################
529             # This helper function returns an ordered list of records.#
530             # Records are sorted according to the numerical value of #
531             # their key; if the key is not numeric, but its transla- #
532             # tion matches Idx-n, n is used. If even this fails, a #
533             # stringwise comparison is performed ($REFERENCE records).#
534             ###########################################################
535             sub ordered_record_list {
536 171     171 0 258 my ($data, $path) = @_;
537             # a regular expression for an integer positive number
538 171         571 my $num = qr/^\d+$/o;
539             # tag to number translation; if the tag is not numeric and translates
540             # to Idx-n, return n. If even this fails, return the textual tag itself
541             # (the last case should be restricted to subdirectory entries).
542 21410 100   21410   70560 my $tag_index = sub { return $_[0] if $_[0] =~ /$num/;
543 365         787 my $n = JPEG_lookup($path, $_[0]);
544 171 100       846 $n =~ s/^Idx-(\d+)$/$1/; $n =~ /$num/ ? $n : $_[0] };
  365         569  
  365         1540  
545             # numeric comparison when possible, stringwise comparison otherwise
546 171 100   10705   522 my $comp = sub { (grep {!/$num/} @_) ? $_[0] cmp $_[1] : $_[0] <=> $_[1] };
  10705         9686  
  21410         60949  
547             # the actual sorting function for the sort operator
548 171     10705   474 my $or = sub { &$comp(&$tag_index($a), &$tag_index($b)) };
  10705         10867  
549             # take all records from $data and perform a sorting
550 171         1048 map {$$data{$_}} sort {&$or} keys %$data;
  3127         5627  
  10705         9937  
551             }
552              
553             ###########################################################
554             # This method, obviously, creates a (sub)directory tree #
555             # in an IFD-like segment (i.e. APP1/APP3). The argument #
556             # is a string describing the tree, like 'APP1@IFD0@GPS'. #
557             # This method takes care of the "extra" field of the #
558             # newly created directories if mandatory or useful. The #
559             # return value is the record list of the deepest subdir. #
560             ###########################################################
561             sub build_IFD_directory_tree {
562 172     172 0 259 my ($this, $dirnames) = @_;
563             # split the passed string into tokens on '@'
564 172         679 my ($first, @dirnames) = split '@', $dirnames;
565             # the first token must correspond to the segment name
566 172 50       471 $this->die("Incorrect segment ($first)") unless $first eq $this->{name};
567             # build the whole directory tree, as requested
568 172         566 $this->provide_subdirectory(@dirnames);
569             # prepare two "running" variables
570 172         278 my $dirref = $this->{records};
571 172         261 my $path = $first;
572             # travel through the token list and fix the tree
573 172         432 for my $name (@dirnames) {
574             # get the $REFERENCE record for the subdir $name
575 291         686 my $record = $this->search_record($name, $dirref);
576             # if there is information in %IFD_SUBDIR ...
577 291 50       703 if (exists $IFD_SUBDIRS{$path}) {
578             # get the reverse (offset tag => subdir name) mapping
579 291         330 my %revmapping = reverse %{$IFD_SUBDIRS{$path}};
  291         1471  
580             # if $name is present in %revmapping, set the "extra" field
581             # of $record. This used to be necessary during the dump stage;
582             # now, it could be avoided by using %IFD_SUBDIRS, but displaying
583             # this kind of information is nonetheless usefull.
584 291 100       1155 $record->{extra} = JPEG_lookup($path, $revmapping{$name})
585             if exists $revmapping{$name}; }
586             # update the running variables
587 291         644 $dirref = $record->get_value();
588 291         678 $path = join '@', $path, $name; }
589             # return the final value of $dirref
590 172         340 return $dirref;
591             }
592              
593             ###########################################################
594             # This private method takes a reference to a Record list #
595             # or hash and a reference to a Record hash, and inserts #
596             # all records from the first container into the hash, #
597             # unless its key is already present. #
598             ###########################################################
599             sub complement_records {
600 339     339 0 407 my ($this, $record_container, $record_hash) = @_;
601             # be sure that the first argument is not a scalar
602 339 50       654 $this->die('first arg. not a reference') unless ref $record_container;
603             # get a record list from the record container
604 339 100       862 my $record_list = (ref $record_container eq 'HASH') ?
605             [ values %$record_container ] : $record_container;
606             # records from a list
607 339         534 for (@$record_list) {
608 3150 100       6908 $$record_hash{$_->{key}} = $_
609             unless exists $$record_hash{$_->{key}}; }
610             }
611              
612             ###########################################################
613             # This method takes a hash reference [$data] and an IFD #
614             # path specification [$path] (like 'APP1@IFD0@GPS'). It #
615             # then tries to convert the elements of $data into valid #
616             # records according to the specific syntactical rules of #
617             # the corresponding IFD. It returns a list of two hash #
618             # references: the first list contains the key-recordref #
619             # pairs for successful conversions, the other one the #
620             # key-value(ref) pairs for unsuccessful ones. #
621             #---------------------------------------------------------#
622             # Records' tags can be give textually or numerically. #
623             # First, the tags are checked for validity and converted #
624             # to numeric form (records with undefined values are #
625             # immediately rejected). Then, the specifications for #
626             # each tag are read from a helper table and values are #
627             # matched against a regular expression (or a surrogate, #
628             # see %special_screen_rules). Then a Record object is #
629             # forged and evaluated to see if it is valid and it #
630             # corresponds to the user will. #
631             #---------------------------------------------------------#
632             # New feature: if the record value is a code reference #
633             # instead of an array reference, the corresponding code #
634             # is executed (passing the segment reference through) and #
635             # the result is stored. This is necessary for mandatory #
636             # records which need to know the current segment. #
637             #---------------------------------------------------------#
638             # New feature. The syntax hash can have a fifth field, #
639             # acting as a filter. Unless it matches the optional #
640             # $fregex argument, the record is rejected. This allows #
641             # us to exclude some tags from general usage. If $fregex #
642             # is undefined, all tags with a filter are rejected. #
643             ###########################################################
644             sub screen_records {
645 339     339 0 461 my ($this, $data, $path, $fregex) = @_;
646             # prepare two hashes for rejected and accepted records
647 339         481 my $rejected = {}; my $accepted = {};
  339         567  
648             # die immediately if $data or $path are not defined
649 339 50 33     1367 $this->die('Undefined arguments') unless defined $data && defined $path;
650             # get a reference to the hash with all record properties
651 339 50       1200 $this->die('Supporting hash not found') unless exists $IFD_SUBDIRS{$path};
652 339         649 my $syntax = $IFD_SUBDIRS{$path}{'__syntax'};
653 339 50       652 $this->die('Syntax specification not found') unless $syntax;
654             # loop over entries in $data and decide whether to accept them or not
655 339         1146 while (my ($key, $value) = each %$data) {
656             # do a key lookup and save the result
657 1786         3933 my $key_lookup = JPEG_lookup($path, $key);
658             # use the looked-up key if it is numeric
659 1786 100 100     13948 $key = $key_lookup if defined $key_lookup && $key_lookup =~ /^\d+$/;
660             # I have never been optimist ...
661 1786         2900 $$rejected{$key} = $value;
662             # reject unknown keys
663 1786 100       3239 next unless defined $key_lookup;
664             # of course, check that $value is defined
665 1600 100       2319 next unless defined $value;
666             # if value is a code reference, execute it, passing $this
667 1599 50       2778 $value = &$value($this) if ref $value eq 'CODE';
668             # if value is a scalar, transform it into a single-valued array
669 1599 100       3167 $value = [ $value ] unless ref $value;
670             # $value must now be an array reference
671 1599 50       2630 next unless ref $value eq 'ARRAY';
672             # get all mandatory properties of this record
673 1599         1354 my ($name, $type, $count, $rule, $filter) = @{$$syntax{$key}};
  1599         3660  
674             # a "rule" matching 'calculated' means that this record
675             # cannot be supplied by the user (so, we reject it)
676 1599 100       2804 next if $rule =~ /calculated/;
677             # very special mechanism to inhibit some tags
678 1572 100 66     3375 next if defined $filter && ((!defined $fregex)||($filter!~/$fregex/));
      100        
679             # if $type is $ASCII and $$value[0] is not null terminated,
680             # we are going to add the null character for the lazy user
681 1521 100 66     4420 $$value[0].="\000" if $type==$ASCII && @$value && $$value[0]!~/\000$/;
      100        
682             # if $rule points to an anonymous subroutine (i.e., a special rule,
683             # execute the corresponding code and reject if it fails (i.e. dies);
684             # otherwise, $rule must be interpreted as a regular expression (if
685             # the record is multi-valued, $rule must match all the elements).
686 1521 100       2336 if (ref $rule eq 'CODE') { eval { &$rule(@$value) }; next if $@; }
  109 100       142  
  109         352  
  109         258  
687 1412 100       1796 else { next unless scalar @$value == grep {$_ =~ /^$rule$/s} @$value; }
  12180         47536  
688             # let us see if the values can actually be saved
689             # in a record ($record remains undef on failure).
690 1439 100       4614 next unless my $record =
691             Image::MetaData::JPEG::Record->check_consistency
692             ($key, $type, $count, $value);
693             # well, it seems that the record is OK, so my pessimism
694             # was not justified. Let us change the record status
695 1430         2946 delete $$rejected{$key};
696 1430         6376 $$accepted{$key} = $record;
697             }
698             # return references to accepted and rejected data
699 339         746 return ($rejected, $accepted);
700             }
701              
702             # successful package load
703             1;