File Coverage

blib/lib/Image/IPTCInfo.pm
Criterion Covered Total %
statement 243 467 52.0
branch 86 182 47.2
condition 22 60 36.6
subroutine 21 40 52.5
pod 0 36 0.0
total 372 785 47.3


line stmt bran cond sub pod time code
1             # IPTCInfo: extractor for IPTC metadata embedded in images
2             # Copyright (C) 2000-2004 Josh Carter
3             # All rights reserved.
4             #
5             # This program is free software; you can redistribute it and/or modify
6             # it under the same terms as Perl itself.
7              
8             package Image::IPTCInfo;
9 1     1   1995 use IO::File;
  1         11883  
  1         146  
10              
11 1     1   8 use vars qw($VERSION);
  1         2  
  1         71  
12             $VERSION = '1.95';
13              
14             #
15             # Global vars
16             #
17 1         6175 use vars ('%datasets', # master list of dataset id's
18             '%datanames', # reverse mapping (for saving)
19             '%listdatasets', # master list of repeating dataset id's
20             '%listdatanames', # reverse
21             '$MAX_FILE_OFFSET', # maximum offset for blind scan
22 1     1   5 );
  1         15  
23              
24             $MAX_FILE_OFFSET = 8192; # default blind scan depth
25              
26             # Debug off for production use
27             my $debugMode = 0;
28             my $error;
29            
30             #####################################
31             # These names match the codes defined in ITPC's IIM record 2.
32             # This hash is for non-repeating data items; repeating ones
33             # are in %listdatasets below.
34             %datasets = (
35             # 0 => 'record version', # skip -- binary data
36             5 => 'object name',
37             7 => 'edit status',
38             8 => 'editorial update',
39             10 => 'urgency',
40             12 => 'subject reference',
41             15 => 'category',
42             # 20 => 'supplemental category', # in listdatasets (see below)
43             22 => 'fixture identifier',
44             # 25 => 'keywords', # in listdatasets
45             26 => 'content location code',
46             27 => 'content location name',
47             30 => 'release date',
48             35 => 'release time',
49             37 => 'expiration date',
50             38 => 'expiration time',
51             40 => 'special instructions',
52             42 => 'action advised',
53             45 => 'reference service',
54             47 => 'reference date',
55             50 => 'reference number',
56             55 => 'date created',
57             60 => 'time created',
58             62 => 'digital creation date',
59             63 => 'digital creation time',
60             65 => 'originating program',
61             70 => 'program version',
62             75 => 'object cycle',
63             80 => 'by-line',
64             85 => 'by-line title',
65             90 => 'city',
66             92 => 'sub-location',
67             95 => 'province/state',
68             100 => 'country/primary location code',
69             101 => 'country/primary location name',
70             103 => 'original transmission reference',
71             105 => 'headline',
72             110 => 'credit',
73             115 => 'source',
74             116 => 'copyright notice',
75             # 118 => 'contact', # in listdatasets
76             120 => 'caption/abstract',
77             121 => 'local caption',
78             122 => 'writer/editor',
79             # 125 => 'rasterized caption', # unsupported (binary data)
80             130 => 'image type',
81             131 => 'image orientation',
82             135 => 'language identifier',
83             200 => 'custom1', # These are NOT STANDARD, but are used by
84             201 => 'custom2', # Fotostation. Use at your own risk. They're
85             202 => 'custom3', # here in case you need to store some special
86             203 => 'custom4', # stuff, but note that other programs won't
87             204 => 'custom5', # recognize them and may blow them away if
88             205 => 'custom6', # you open and re-save the file. (Except with
89             206 => 'custom7', # Fotostation, of course.)
90             207 => 'custom8',
91             208 => 'custom9',
92             209 => 'custom10',
93             210 => 'custom11',
94             211 => 'custom12',
95             212 => 'custom13',
96             213 => 'custom14',
97             214 => 'custom15',
98             215 => 'custom16',
99             216 => 'custom17',
100             217 => 'custom18',
101             218 => 'custom19',
102             219 => 'custom20',
103             );
104              
105             # this will get filled in if we save data back to file
106             %datanames = ();
107              
108             %listdatasets = (
109             20 => 'supplemental category',
110             25 => 'keywords',
111             118 => 'contact',
112             );
113              
114             # this will get filled in if we save data back to file
115             %listdatanames = ();
116            
117             #######################################################################
118             # New, Save, Destroy, Error
119             #######################################################################
120              
121             #
122             # new
123             #
124             # $info = new IPTCInfo('image filename goes here')
125             #
126             # Returns IPTCInfo object filled with metadata from the given image
127             # file. File on disk will be closed, and changes made to the IPTCInfo
128             # object will *not* be flushed back to disk.
129             #
130             sub new
131             {
132 5     5 0 335 my ($pkg, $file, $force) = @_;
133              
134 5         9 my $input_is_handle = eval {$file->isa('IO::Handle')};
  5         45  
135 5 50 66     25 if ($input_is_handle and not $file->isa('IO::Seekable')) {
136 0         0 $error = "Handle must be seekable."; Log($error);
  0         0  
137 0         0 return undef;
138             }
139              
140             #
141             # Open file and snarf data from it.
142             #
143 5 100       31 my $handle = $input_is_handle ? $file : IO::File->new($file);
144 5 50       314 unless($handle)
145             {
146 0         0 $error = "Can't open file: $!"; Log($error);
  0         0  
147 0         0 return undef;
148             }
149              
150 5         10 binmode($handle);
151              
152 5         14 my $datafound = ScanToFirstIMMTag($handle);
153 5 50 33     15 unless ($datafound || defined($force))
154             {
155 0         0 $error = "No IPTC data found."; Log($error);
  0         0  
156             # don't close unless we opened it
157 0 0       0 $handle->close() unless $input_is_handle;
158 0         0 return undef;
159             }
160              
161 5         25 my $self = bless
162             {
163             '_data' => {}, # empty hashes; wil be
164             '_listdata' => {}, # filled in CollectIIMInfo
165             '_handle' => $handle,
166             }, $pkg;
167              
168 5 100       20 $self->{_filename} = $file unless $input_is_handle;
169              
170             # Do the real snarfing here
171 5 50       19 $self->CollectIIMInfo() if $datafound;
172            
173 5 100       23 $handle->close() unless $input_is_handle;
174            
175 5         89 return $self;
176             }
177              
178             #
179             # create
180             #
181             # Like new, but forces an object to always be returned. This allows
182             # you to start adding stuff to files that don't have IPTC info and then
183             # save it.
184             #
185             sub create
186             {
187 0     0 0 0 my ($pkg, $filename) = @_;
188              
189 0         0 return new($pkg, $filename, 'force');
190             }
191              
192             #
193             # Save
194             #
195             # Saves JPEG with IPTC data back to the same file it came from.
196             #
197             sub Save
198             {
199 1     1 0 5 my ($self, $options) = @_;
200              
201 1         6 return $self->SaveAs($self->{'_filename'}, $options);
202             }
203              
204             #
205             # Save
206             #
207             # Saves JPEG with IPTC data to a given file name.
208             #
209             sub SaveAs
210             {
211 2     2 0 8 my ($self, $newfile, $options) = @_;
212              
213             #
214             # Open file and snarf data from it.
215             #
216 2 50       14 my $handle = $self->{_filename} ? IO::File->new($self->{_filename}) : $self->{_handle};
217 2 50       147 unless($handle)
218             {
219 0         0 $error = "Can't open file: $!"; Log($error);
  0         0  
220 0         0 return undef;
221             }
222              
223 2         7 $handle->seek(0, 0);
224 2         18 binmode($handle);
225              
226 2 50       5 unless (FileIsJPEG($handle))
227             {
228 0         0 $error = "Source file is not a JPEG; I can only save JPEGs. Sorry.";
229 0         0 Log($error);
230 0         0 return undef;
231             }
232              
233 2         7 my $ret = JPEGCollectFileParts($handle, $options);
234              
235 2 50       6 if ($ret == 0)
236             {
237 0         0 Log("collectfileparts failed");
238 0         0 return undef;
239             }
240              
241 2 50       8 if ($self->{_filename}) {
242 2         7 $handle->close();
243 2 50       39 unless ($handle = IO::File->new($newfile, ">")) {
244 0         0 $error = "Can't open output file: $!"; Log($error);
  0         0  
245 0         0 return undef;
246             }
247 2         335 binmode($handle);
248             } else {
249 0 0       0 unless ($handle->truncate(0)) {
250 0         0 $error = "Can't truncate, handle might be read-only"; Log($error);
  0         0  
251 0         0 return undef;
252             }
253             }
254              
255 2         57 my ($start, $end, $adobe) = @$ret;
256              
257 2 50 33     9 if (defined($options) && defined($options->{'discardAdobeParts'}))
258             {
259 0         0 undef $adobe;
260             }
261              
262              
263 2         14 $handle->print($start);
264 2         32 $handle->print($self->PhotoshopIIMBlock($adobe, $self->PackedIIMData()));
265 2         16 $handle->print($end);
266              
267 2 50       22 $handle->close() if $self->{_filename};
268            
269 2         140 return 1;
270             }
271              
272             #
273             # DESTROY
274             #
275             # Called when object is destroyed. No action necessary in this case.
276             #
277             sub DESTROY
278 0     0   0 {
279             # no action necessary
280             }
281              
282             #
283             # Error
284             #
285             # Returns description of the last error.
286             #
287             sub Error
288             {
289 0     0 0 0 return $error;
290             }
291              
292             #######################################################################
293             # Attributes for clients
294             #######################################################################
295              
296             #
297             # Attribute/SetAttribute
298             #
299             # Returns/Changes value of a given data item.
300             #
301             sub Attribute
302             {
303 4     4 0 30 my ($self, $attribute) = @_;
304              
305 4         13 return $self->{_data}->{$attribute};
306             }
307              
308             sub SetAttribute
309             {
310 2     2 0 50 my ($self, $attribute, $newval) = @_;
311              
312 2         7 $self->{_data}->{$attribute} = $newval;
313             }
314              
315             sub ClearAttributes
316             {
317 0     0 0 0 my $self = shift;
318              
319 0         0 $self->{_data} = {};
320             }
321              
322             sub ClearAllData
323             {
324 0     0 0 0 my $self = shift;
325              
326 0         0 $self->{_data} = {};
327 0         0 $self->{_listdata} = {};
328             }
329              
330             #
331             # Keywords/Clear/Add
332             #
333             # Returns reference to a list of keywords/clears the keywords
334             # list/adds a keyword.
335             #
336             sub Keywords
337             {
338 0     0 0 0 my $self = shift;
339 0         0 return $self->{_listdata}->{'keywords'};
340             }
341              
342             sub ClearKeywords
343             {
344 0     0 0 0 my $self = shift;
345 0         0 $self->{_listdata}->{'keywords'} = undef;
346             }
347              
348             sub AddKeyword
349             {
350 0     0 0 0 my ($self, $add) = @_;
351            
352 0         0 $self->AddListData('keywords', $add);
353             }
354              
355             #
356             # SupplementalCategories/Clear/Add
357             #
358             # Returns reference to a list of supplemental categories.
359             #
360             sub SupplementalCategories
361             {
362 0     0 0 0 my $self = shift;
363 0         0 return $self->{_listdata}->{'supplemental category'};
364             }
365              
366             sub ClearSupplementalCategories
367             {
368 0     0 0 0 my $self = shift;
369 0         0 $self->{_listdata}->{'supplemental category'} = undef;
370             }
371              
372             sub AddSupplementalCategories
373             {
374 0     0 0 0 my ($self, $add) = @_;
375            
376 0         0 $self->AddListData('supplemental category', $add);
377             }
378              
379             #
380             # Contacts/Clear/Add
381             #
382             # Returns reference to a list of contactss/clears the contacts
383             # list/adds a contact.
384             #
385             sub Contacts
386             {
387 0     0 0 0 my $self = shift;
388 0         0 return $self->{_listdata}->{'contact'};
389             }
390              
391             sub ClearContacts
392             {
393 0     0 0 0 my $self = shift;
394 0         0 $self->{_listdata}->{'contact'} = undef;
395             }
396              
397             sub AddContact
398             {
399 0     0 0 0 my ($self, $add) = @_;
400            
401 0         0 $self->AddListData('contact', $add);
402             }
403              
404             sub AddListData
405             {
406 0     0 0 0 my ($self, $list, $add) = @_;
407              
408             # did user pass in a list ref?
409 0 0       0 if (ref($add) eq 'ARRAY')
410             {
411             # yes, add list contents
412 0         0 push(@{$self->{_listdata}->{$list}}, @$add);
  0         0  
413             }
414             else
415             {
416             # no, just a literal item
417 0         0 push(@{$self->{_listdata}->{$list}}, $add);
  0         0  
418             }
419             }
420              
421             #######################################################################
422             # XML, SQL export
423             #######################################################################
424              
425             #
426             # ExportXML
427             #
428             # $xml = $info->ExportXML('entity-name', \%extra-data,
429             # 'optional output file name');
430             #
431             # Exports XML containing all image metadata. Attribute names are
432             # translated into XML tags, making adjustments to spaces and slashes
433             # for compatibility. (Spaces become underbars, slashes become dashes.)
434             # Caller provides an entity name; all data will be contained within
435             # this entity. Caller optionally provides a reference to a hash of
436             # extra data. This will be output into the XML, too. Keys must be
437             # valid XML tag names. Optionally provide a filename, and the XML
438             # will be dumped into there.
439             #
440             sub ExportXML
441             {
442 0     0 0 0 my ($self, $basetag, $extraRef, $filename) = @_;
443 0         0 my $out;
444            
445 0 0       0 $basetag = 'photo' unless length($basetag);
446            
447 0         0 $out .= "<$basetag>\n";
448              
449             # dump extra info first, if any
450 0         0 foreach my $key (keys %$extraRef)
451             {
452 0         0 $out .= "\t<$key>" . $extraRef->{$key} . "\n";
453             }
454            
455             # dump our stuff
456 0         0 foreach my $key (keys %{$self->{_data}})
  0         0  
457             {
458 0         0 my $cleankey = $key;
459 0         0 $cleankey =~ s/ /_/g;
460 0         0 $cleankey =~ s/\//-/g;
461            
462 0         0 $out .= "\t<$cleankey>" . $self->{_data}->{$key} . "\n";
463             }
464              
465 0 0       0 if (defined ($self->Keywords()))
466             {
467             # print keywords
468 0         0 $out .= "\t\n";
469            
470 0         0 foreach my $keyword (@{$self->Keywords()})
  0         0  
471             {
472 0         0 $out .= "\t\t$keyword\n";
473             }
474            
475 0         0 $out .= "\t\n";
476             }
477              
478 0 0       0 if (defined ($self->SupplementalCategories()))
479             {
480             # print supplemental categories
481 0         0 $out .= "\t\n";
482            
483 0         0 foreach my $category (@{$self->SupplementalCategories()})
  0         0  
484             {
485 0         0 $out .= "\t\t$category\n";
486             }
487            
488 0         0 $out .= "\t\n";
489             }
490              
491 0 0       0 if (defined ($self->Contacts()))
492             {
493             # print contacts
494 0         0 $out .= "\t\n";
495            
496 0         0 foreach my $contact (@{$self->Contacts()})
  0         0  
497             {
498 0         0 $out .= "\t\t$contact\n";
499             }
500            
501 0         0 $out .= "\t\n";
502             }
503              
504             # close base tag
505 0         0 $out .= "\n";
506              
507             # export to file if caller asked for it.
508 0 0       0 if (length($filename))
509             {
510 0         0 open(XMLOUT, ">$filename");
511 0         0 print XMLOUT $out;
512 0         0 close(XMLOUT);
513             }
514            
515 0         0 return $out;
516             }
517              
518             #
519             # ExportSQL
520             #
521             # my %mappings = (
522             # 'IPTC dataset name here' => 'your table column name here',
523             # 'caption/abstract' => 'caption',
524             # 'city' => 'city',
525             # 'province/state' => 'state); # etc etc etc.
526             #
527             # $statement = $info->ExportSQL('mytable', \%mappings, \%extra-data);
528             #
529             # Returns a SQL statement to insert into your given table name
530             # a set of values from the image. Caller passes in a reference to
531             # a hash which maps IPTC dataset names into column names for the
532             # database table. Optionally pass in a ref to a hash of extra data
533             # which will also be included in the insert statement. Keys in that
534             # hash must be valid column names.
535             #
536             sub ExportSQL
537             {
538 0     0 0 0 my ($self, $tablename, $mappingsRef, $extraRef) = @_;
539 0         0 my ($statement, $columns, $values);
540            
541 0 0 0     0 return undef if (($tablename eq undef) || ($mappingsRef eq undef));
542              
543             # start with extra data, if any
544 0         0 foreach my $column (keys %$extraRef)
545             {
546 0         0 my $value = $extraRef->{$column};
547 0         0 $value =~ s/'/''/g; # escape single quotes
548            
549 0         0 $columns .= $column . ", ";
550 0         0 $values .= "\'$value\', ";
551             }
552            
553             # process our data
554 0         0 foreach my $attribute (keys %$mappingsRef)
555             {
556 0         0 my $value = $self->Attribute($attribute);
557 0         0 $value =~ s/'/''/g; # escape single quotes
558            
559 0         0 $columns .= $mappingsRef->{$attribute} . ", ";
560 0         0 $values .= "\'$value\', ";
561             }
562            
563             # must trim the trailing ", " from both
564 0         0 $columns =~ s/, $//;
565 0         0 $values =~ s/, $//;
566              
567 0         0 $statement = "INSERT INTO $tablename ($columns) VALUES ($values)";
568            
569 0         0 return $statement;
570             }
571              
572             #######################################################################
573             # File parsing functions (private)
574             #######################################################################
575              
576             #
577             # ScanToFirstIMMTag
578             #
579             # Scans to first IIM Record 2 tag in the file. The will either use
580             # smart scanning for JPEGs or blind scanning for other file types.
581             #
582             sub ScanToFirstIMMTag
583             {
584 5     5 0 7 my $handle = shift @_;
585              
586 5 50       12 if (FileIsJPEG($handle))
587             {
588 5         13 Log("File is JPEG, proceeding with JPEGScan");
589 5         13 return JPEGScan($handle);
590             }
591             else
592             {
593 0         0 Log("File not a JPEG, trying BlindScan");
594 0         0 return BlindScan($handle);
595             }
596             }
597              
598             #
599             # FileIsJPEG
600             #
601             # Checks to see if this file is a JPEG/JFIF or not. Will reset the
602             # file position back to 0 after it's done in either case.
603             #
604             sub FileIsJPEG
605             {
606 7     7 0 13 my $handle = shift @_;
607              
608             # reset to beginning just in case
609 7         32 $handle->seek(0, 0);
610              
611 7 50       63 if ($debugMode)
612             {
613 0         0 Log("Opening 16 bytes of file:\n");
614 0         0 my $dump;
615 0         0 $handle->read($dump, 16);
616 0         0 HexDump($dump);
617 0         0 $handle->seek(0, 0);
618             }
619              
620             # check start of file marker
621 7         7 my ($ff, $soi);
622 7 50       26 $handle->read($ff, 1) || goto notjpeg;
623 7         155 $handle->read($soi, 1);
624            
625 7 50 33     67 goto notjpeg unless (ord($ff) == 0xff && ord($soi) == 0xd8);
626              
627             # now check for APP0 marker. I'll assume that anything with a SOI
628             # followed by APP0 is "close enough" for our purposes. (We're not
629             # dinking with image data, so anything following the JPEG tagging
630             # system should work.)
631 7         9 my ($app0, $len, $jpeg);
632 7         21 $handle->read($ff, 1);
633 7         41 $handle->read($app0, 1);
634              
635 7 50       44 goto notjpeg unless (ord($ff) == 0xff);
636              
637             # reset to beginning of file
638 7         19 $handle->seek(0, 0);
639 7         86 return 1;
640              
641 0         0 notjpeg:
642             $handle->seek(0, 0);
643 0         0 return 0;
644             }
645              
646             #
647             # JPEGScan
648             #
649             # Assuming the file is a JPEG (see above), this will scan through the
650             # markers looking for the APP13 marker, where IPTC/IIM data should be
651             # found. While this isn't a formally defined standard, all programs
652             # have (supposedly) adopted Adobe's technique of putting the data in
653             # APP13.
654             #
655             sub JPEGScan
656             {
657 5     5 0 8 my $handle = shift @_;
658              
659             # Skip past start of file marker
660 5         6 my ($ff, $soi);
661 5 50       15 $handle->read($ff, 1) || return 0;
662 5         66 $handle->read($soi, 1);
663            
664 5 50 33     67 unless (ord($ff) == 0xff && ord($soi) == 0xd8)
665             {
666 0         0 $error = "JPEGScan: invalid start of file"; Log($error);
  0         0  
667 0         0 return 0;
668             }
669              
670             # Scan for the APP13 marker which will contain our IPTC info (I hope).
671              
672 5         12 my $marker = JPEGNextMarker($handle);
673              
674 5         15 while (ord($marker) != 0xed)
675             {
676 5 50       14 if (ord($marker) == 0)
677 0         0 { $error = "Marker scan failed"; Log($error); return 0; }
  0         0  
  0         0  
678              
679 5 50       12 if (ord($marker) == 0xd9)
680 0         0 { $error = "Marker scan hit end of image marker";
681 0         0 Log($error); return 0; }
  0         0  
682              
683 5 50       12 if (ord($marker) == 0xda)
684 0         0 { $error = "Marker scan hit start of image data";
685 0         0 Log($error); return 0; }
  0         0  
686              
687 5 50       587 if (JPEGSkipVariable($handle) == 0)
688 0         0 { $error = "JPEGSkipVariable failed";
689 0         0 Log($error); return 0; }
  0         0  
690              
691 5         9 $marker = JPEGNextMarker($handle);
692             }
693              
694             # If were's here, we must have found the right marker. Now
695             # BlindScan through the data.
696 5         9 return BlindScan($handle, JPEGGetVariableLength($handle));
697             }
698              
699             #
700             # JPEGNextMarker
701             #
702             # Scans to the start of the next valid-looking marker. Return value is
703             # the marker id.
704             #
705             sub JPEGNextMarker
706             {
707 14     14 0 20 my $handle = shift @_;
708              
709 14         16 my $byte;
710              
711             # Find 0xff byte. We should already be on it.
712 14 50       43 $handle->read($byte, 1) || return 0;
713 14         127 while (ord($byte) != 0xff)
714             {
715 0         0 Log("JPEGNextMarker: warning: bogus stuff in JPEG file");
716 0 0       0 $handle->read($byte, 1) || return 0;
717             }
718              
719             # Now skip any extra 0xffs, which are valid padding.
720             do
721 14         17 {
722 14 50       34 $handle->read($byte, 1) || return 0;
723             } while (ord($byte) == 0xff);
724              
725             # $byte should now contain the marker id.
726 14         139 Log("JPEGNextMarker: at marker " . unpack("H*", $byte));
727 14         39 return $byte;
728             }
729              
730             #
731             # JPEGGetVariableLength
732             #
733             # Gets length of current variable-length section. File position at
734             # start must be on the marker itself, e.g. immediately after call to
735             # JPEGNextMarker. File position is updated to just past the length
736             # field.
737             #
738             sub JPEGGetVariableLength
739             {
740 14     14 0 86 my $handle = shift @_;
741              
742             # Get the marker parameter length count
743 14         12 my $length;
744 14 50       35 $handle->read($length, 2) || return 0;
745            
746 14         101 ($length) = unpack("n", $length);
747              
748 14         48 Log("JPEG variable length: $length");
749              
750             # Length includes itself, so must be at least 2
751 14 50       30 if ($length < 2)
752             {
753 0         0 Log("JPEGGetVariableLength: erroneous JPEG marker length");
754 0         0 return 0;
755             }
756 14         17 $length -= 2;
757              
758 14         24 return $length;
759             }
760              
761             #
762             # JPEGSkipVariable
763             #
764             # Skips variable-length section of JPEG block. Should always be called
765             # between calls to JPEGNextMarker to ensure JPEGNextMarker is at the
766             # start of data it can properly parse.
767             #
768             sub JPEGSkipVariable
769             {
770 9     9 0 14 my $handle = shift;
771 9         10 my $rSave = shift;
772              
773 9         15 my $length = JPEGGetVariableLength($handle);
774 9 50       18 return if ($length == 0);
775              
776             # Skip remaining bytes
777 9         10 my $temp;
778 9 100 66     45 if (defined($rSave) || $debugMode)
779             {
780 4 50       12 unless ($handle->read($temp, $length))
781             {
782 0         0 Log("JPEGSkipVariable: read failed while skipping var data");
783 0         0 return 0;
784             }
785              
786             # prints out a heck of a lot of stuff
787             # HexDump($temp);
788             }
789             else
790             {
791             # Just seek
792 5 50       16 unless($handle->seek($length, 1))
793             {
794 0         0 Log("JPEGSkipVariable: read failed while skipping var data");
795 0         0 return 0;
796             }
797             }
798              
799 9 100       122 $$rSave = $temp if defined($rSave);
800              
801 9         29 return 1;
802             }
803              
804             #
805             # BlindScan
806             #
807             # Scans blindly to first IIM Record 2 tag in the file. This method may
808             # or may not work on any arbitrary file type, but it doesn't hurt to
809             # check. We expect to see this tag within the first 8k of data. (This
810             # limit may need to be changed or eliminated depending on how other
811             # programs choose to store IIM.)
812             #
813             sub BlindScan
814             {
815 5     5 0 7 my $handle = shift;
816 5   33     12 my $maxoff = shift() || $MAX_FILE_OFFSET;
817            
818 5         14 Log("BlindScan: starting scan, max length $maxoff");
819            
820             # start digging
821 5         7 my $offset = 0;
822 5         12 while ($offset <= $maxoff)
823             {
824 135         114 my $temp;
825            
826 135 50       301 unless ($handle->read($temp, 1))
827             {
828 0         0 Log("BlindScan: hit EOF while scanning");
829 0         0 return 0;
830             }
831              
832             # look for tag identifier 0x1c
833 135 100       824 if (ord($temp) == 0x1c)
834             {
835             # if we found that, look for record 2, dataset 0
836             # (record version number)
837 5         6 my ($record, $dataset);
838 5         10 $handle->read($record, 1);
839 5         29 $handle->read($dataset, 1);
840            
841 5 50       26 if (ord($record) == 2)
842             {
843             # found it. seek to start of this tag and return.
844 5         15 Log("BlindScan: found IIM start at offset $offset");
845 5         16 $handle->seek(-3, 1); # seek rel to current position
846 5         76 return $offset;
847             }
848             else
849             {
850             # didn't find it. back up 2 to make up for
851             # those reads above.
852 0         0 $handle->seek(-2, 1); # seek rel to current position
853             }
854             }
855            
856             # no tag, keep scanning
857 130         274 $offset++;
858             }
859            
860 0         0 return 0;
861             }
862              
863             #
864             # CollectIIMInfo
865             #
866             # Assuming file is seeked to start of IIM data (using above), this
867             # reads all the data into our object's hashes
868             #
869             sub CollectIIMInfo
870             {
871 5     5 0 7 my $self = shift;
872            
873 5         9 my $handle = $self->{_handle};
874            
875             # NOTE: file should already be at the start of the first
876             # IPTC code: record 2, dataset 0.
877            
878 5         6 while (1)
879             {
880 90         85 my $header;
881 90 50       215 return unless $handle->read($header, 5);
882            
883 90         628 ($tag, $record, $dataset, $length) = unpack("CCCn", $header);
884              
885             # bail if we're past end of IIM record 2 data
886 90 100 66     358 return unless ($tag == 0x1c) && ($record == 2);
887            
888             # print "tag : " . $tag . "\n";
889             # print "record : " . $record . "\n";
890             # print "dataset : " . $dataset . "\n";
891             # print "length : " . $length . "\n";
892            
893 85         79 my $value;
894 85         243 $handle->read($value, $length);
895            
896             # try to extract first into _listdata (keywords, categories)
897             # and, if unsuccessful, into _data. Tags which are not in the
898             # current IIM spec (version 4) are currently discarded.
899 85 100       579 if (exists $listdatasets{$dataset})
    100          
900             {
901 20         31 my $dataname = $listdatasets{$dataset};
902 20         21 my $listref = $listdata{$dataname};
903            
904 20         20 push(@{$self->{_listdata}->{$dataname}}, $value);
  20         79  
905             }
906             elsif (exists $datasets{$dataset})
907             {
908 60         90 my $dataname = $datasets{$dataset};
909            
910 60         161 $self->{_data}->{$dataname} = $value;
911             }
912             # else discard
913             }
914             }
915              
916             #######################################################################
917             # File Saving
918             #######################################################################
919              
920             #
921             # JPEGCollectFileParts
922             #
923             # Collects all pieces of the file except for the IPTC info that we'll
924             # replace when saving. Returns the stuff before the info, stuff after,
925             # and the contents of the Adobe Resource Block that the IPTC data goes
926             # in. Returns undef if a file parsing error occured.
927             #
928             sub JPEGCollectFileParts
929             {
930 2     2 0 3 my $handle = shift;
931 2         3 my ($options) = @_;
932 2         3 my ($start, $end, $adobeParts);
933 2         4 my $discardAppParts = 0;
934              
935 2 50 33     17 if (defined($options) && defined($options->{'discardAppParts'}))
936 0         0 { $discardAppParts = 1; }
937              
938             # Start at beginning of file
939 2         7 $handle->seek(0, 0);
940              
941             # Skip past start of file marker
942 2         14 my ($ff, $soi);
943 2 50       7 $handle->read($ff, 1) || return 0;
944 2         28 $handle->read($soi, 1);
945            
946 2 50 33     21 unless (ord($ff) == 0xff && ord($soi) == 0xd8)
947             {
948 0         0 $error = "JPEGScan: invalid start of file"; Log($error);
  0         0  
949 0         0 return 0;
950             }
951              
952             #
953             # Begin building start of file
954             #
955 2         4 $start .= pack("CC", 0xff, 0xd8);
956              
957             # Get first marker in file. This will be APP0 for JFIF or APP1 for
958             # EXIF.
959 2         5 my $marker = JPEGNextMarker($handle);
960              
961 2         2 my $app0data;
962 2 50       10 if (JPEGSkipVariable($handle, \$app0data) == 0)
963 0         0 { $error = "JPEGSkipVariable failed";
964 0         0 Log($error); return 0; }
  0         0  
965              
966 2 50 33     10 if (ord($marker) == 0xe0 || !$discardAppParts)
967             {
968             # Always include APP0 marker at start if it's present.
969 2         8 $start .= pack("CC", 0xff, ord($marker));
970             # Remember that the length must include itself (2 bytes)
971 2         5 $start .= pack("n", length($app0data) + 2);
972 2         3 $start .= $app0data;
973             }
974             else
975             {
976             # Manually insert APP0 if we're trashing application parts, since
977             # all JFIF format images should start with the version block.
978 0         0 $start .= pack("CC", 0xff, 0xe0);
979 0         0 $start .= pack("n", 16); # length (including these 2 bytes)
980 0         0 $start .= "JFIF"; # format
981 0         0 $start .= pack("CC", 1, 2); # call it version 1.2 (current JFIF)
982 0         0 $start .= pack(C8, 0); # zero everything else
983             }
984              
985             #
986             # Now scan through all markers in file until we hit image data or
987             # IPTC stuff.
988             #
989 2         5 $marker = JPEGNextMarker($handle);
990              
991 2         3 while (1)
992             {
993 2 50       7 if (ord($marker) == 0)
994 0         0 { $error = "Marker scan failed"; Log($error); return 0; }
  0         0  
  0         0  
995              
996             # Check for end of image
997 2 50       7 if (ord($marker) == 0xd9)
998             {
999 0         0 Log("JPEGCollectFileParts: saw end of image marker");
1000 0         0 $end .= pack("CC", 0xff, ord($marker));
1001 0         0 goto doneScanning;
1002             }
1003              
1004             # Check for start of compressed data
1005 2 50       6 if (ord($marker) == 0xda)
1006             {
1007 0         0 Log("JPEGCollectFileParts: saw start of compressed data");
1008 0         0 $end .= pack("CC", 0xff, ord($marker));
1009 0         0 goto doneScanning;
1010             }
1011              
1012 2         3 my $partdata;
1013 2 50       5 if (JPEGSkipVariable($handle, \$partdata) == 0)
1014 0         0 { $error = "JPEGSkipVariable failed";
1015 0         0 Log($error); return 0; }
  0         0  
1016              
1017             # Take all parts aside from APP13, which we'll replace
1018             # ourselves.
1019 2 50 33     25 if ($discardAppParts && ord($marker) >= 0xe0 && ord($marker) <= 0xef)
    50 33        
1020             {
1021             # Skip all application markers, including Adobe parts
1022 0         0 undef $adobeParts;
1023             }
1024             elsif (ord($marker) == 0xed)
1025             {
1026             # Collect the adobe stuff from part 13
1027 2         8 $adobeParts = CollectAdobeParts($partdata);
1028 2         38 goto doneScanning;
1029             }
1030             else
1031             {
1032             # Append all other parts to start section
1033 0         0 $start .= pack("CC", 0xff, ord($marker));
1034 0         0 $start .= pack("n", length($partdata) + 2);
1035 0         0 $start .= $partdata;
1036             }
1037              
1038 0         0 $marker = JPEGNextMarker($handle);
1039             }
1040              
1041             doneScanning:
1042              
1043             #
1044             # Append rest of file to $end
1045             #
1046 2         4 my $buffer;
1047              
1048 2         21 while ($handle->read($buffer, 16384))
1049             {
1050 2         79 $end .= $buffer;
1051             }
1052              
1053 2         30 return [$start, $end, $adobeParts];
1054             }
1055              
1056             #
1057             # CollectAdobeParts
1058             #
1059             # Part APP13 contains yet another markup format, one defined by Adobe.
1060             # See "File Formats Specification" in the Photoshop SDK (avail from
1061             # www.adobe.com). We must take everything but the IPTC data so that
1062             # way we can write the file back without losing everything else
1063             # Photoshop stuffed into the APP13 block.
1064             #
1065             sub CollectAdobeParts
1066             {
1067 2     2 0 5 my ($data) = @_;
1068 2         3 my $length = length($data);
1069 2         3 my $offset = 0;
1070 2         4 my $out = '';
1071              
1072             # Skip preamble
1073 2         8 $offset = length('Photoshop 3.0 ');
1074              
1075             # Process everything
1076 2         7 while ($offset < $length)
1077             {
1078             # Get OSType and ID
1079 24         63 my ($ostype, $id1, $id2) = unpack("NCC", substr($data, $offset, 6));
1080 24 50       64 last unless (($offset += 6) < $length); # $offset += 6;
1081              
1082             # printf("CollectAdobeParts: ID %2.2x %2.2x\n", $id1, $id2);
1083            
1084             # Get pascal string
1085 24         43 my ($stringlen) = unpack("C", substr($data, $offset, 1));
1086 24 50       59 last unless (++$offset < $length); # $offset += 1;
1087              
1088             # printf("CollectAdobeParts: str len %d\n", $stringlen);
1089            
1090 24         39 my $string = substr($data, $offset, $stringlen);
1091 24         26 $offset += $stringlen;
1092             # round up if odd
1093 24 50       48 $offset++ if ($stringlen % 2 != 0);
1094             # there should be a null if string len is 0
1095 24 50       47 $offset++ if ($stringlen == 0);
1096 24 50       47 last unless ($offset < $length);
1097              
1098             # Get variable-size data
1099 24         45 my ($size) = unpack("N", substr($data, $offset, 4));
1100 24 50       48 last unless (($offset += 4) < $length); # $offset += 4;
1101              
1102             # printf("CollectAdobeParts: size %d\n", $size);
1103              
1104 24         41 my $var = substr($data, $offset, $size);
1105 24         25 $offset += $size;
1106 24 100       48 $offset++ if ($size % 2 != 0); # round up if odd
1107              
1108             # skip IIM data (0x0404), but write everything else out
1109 24 100 100     84 unless ($id1 == 4 && $id2 == 4)
1110             {
1111 22         43 $out .= pack("NCC", $ostype, $id1, $id2);
1112 22         33 $out .= pack("C", $stringlen);
1113 22         21 $out .= $string;
1114 22 50 33     52 $out .= pack("C", 0) if ($stringlen == 0 || $stringlen % 2 != 0);
1115 22         31 $out .= pack("N", $size);
1116 22         29 $out .= $var;
1117 22 100 66     94 $out .= pack("C", 0) if ($size % 2 != 0 && length($out) % 2 != 0);
1118             }
1119             }
1120              
1121 2         6 return $out;
1122             }
1123              
1124             #
1125             # PackedIIMData
1126             #
1127             # Assembles and returns our _data and _listdata into IIM format for
1128             # embedding into an image.
1129             #
1130             sub PackedIIMData
1131             {
1132 2     2 0 4 my $self = shift;
1133 2         3 my $out;
1134              
1135             # First, we need to build a mapping of datanames to dataset
1136             # numbers if we haven't already.
1137 2 100       8 unless (scalar(keys %datanames))
1138             {
1139 1         12 foreach my $dataset (keys %datasets)
1140             {
1141 63         93 my $dataname = $datasets{$dataset};
1142 63         171 $datanames{$dataname} = $dataset;
1143             }
1144             }
1145              
1146             # Ditto for the lists
1147 2 100       10 unless (scalar(keys %listdatanames))
1148             {
1149 1         3 foreach my $dataset (keys %listdatasets)
1150             {
1151 3         5 my $dataname = $listdatasets{$dataset};
1152 3         8 $listdatanames{$dataname} = $dataset;
1153             }
1154             }
1155              
1156             # Print record version
1157             # tag - record - dataset - len (short) - 2 (short)
1158 2         5 $out .= pack("CCCnn", 0x1c, 2, 0, 2, 2);
1159              
1160             # Iterate over data sets
1161 2         3 foreach my $key (keys %{$self->{_data}})
  2         19  
1162             {
1163 24         39 my $dataset = $datanames{$key};
1164 24         42 my $value = $self->{_data}->{$key};
1165              
1166 24 50       63 if ($dataset == 0)
1167 0         0 { Log("PackedIIMData: illegal dataname $key"); next; }
  0         0  
1168              
1169 24 50       41 next unless $value;
1170              
1171 24         29 my ($tag, $record) = (0x1c, 0x02);
1172              
1173 24         47 $out .= pack("CCCn", $tag, $record, $dataset, length($value));
1174 24         40 $out .= $value;
1175             }
1176              
1177             # Do the same for list data sets
1178 2         7 foreach my $key (keys %{$self->{_listdata}})
  2         7  
1179             {
1180 4         7 my $dataset = $listdatanames{$key};
1181              
1182 4 50       11 if ($dataset == 0)
1183 0         0 { Log("PackedIIMData: illegal dataname $key"); next; }
  0         0  
1184              
1185 4         5 foreach my $value (@{$self->{_listdata}->{$key}})
  4         10  
1186             {
1187 8 50       14 next unless $value;
1188            
1189 8         11 my ($tag, $record) = (0x1c, 0x02);
1190              
1191 8         16 $out .= pack("CCCn", $tag, $record, $dataset, length($value));
1192 8         19 $out .= $value;
1193             }
1194             }
1195              
1196 2         10 return $out;
1197             }
1198              
1199             #
1200             # PhotoshopIIMBlock
1201             #
1202             # Assembles the blob of Photoshop "resource data" that includes our
1203             # fresh IIM data (from PackedIIMData) and the other Adobe parts we
1204             # found in the file, if there were any.
1205             #
1206             sub PhotoshopIIMBlock
1207             {
1208 2     2 0 4 my ($self, $otherparts, $data) = @_;
1209 2         4 my $resourceBlock;
1210             my $out;
1211              
1212 2         3 $resourceBlock .= "Photoshop 3.0";
1213 2         3 $resourceBlock .= pack("C", 0);
1214             # Photoshop identifier
1215 2         3 $resourceBlock .= "8BIM";
1216             # 0x0404 is IIM data, 00 is required empty string
1217 2         4 $resourceBlock .= pack("CCCC", 0x04, 0x04, 0, 0);
1218             # length of data as 32-bit, network-byte order
1219 2         5 $resourceBlock .= pack("N", length($data));
1220             # Now tack data on there
1221 2         4 $resourceBlock .= $data;
1222             # Pad with a blank if not even size
1223 2 50       8 $resourceBlock .= pack("C", 0) if (length($data) % 2 != 0);
1224             # Finally tack on other data
1225 2 50       16 $resourceBlock .= $otherparts if defined($otherparts);
1226              
1227 2         4 $out .= pack("CC", 0xff, 0xed); # JPEG start of block, APP13
1228 2         4 $out .= pack("n", length($resourceBlock) + 2); # length
1229 2         5 $out .= $resourceBlock;
1230              
1231 2         9 return $out;
1232             }
1233              
1234             #######################################################################
1235             # Helpers, docs
1236             #######################################################################
1237              
1238             #
1239             # Log: just prints a message to STDERR if $debugMode is on.
1240             #
1241             sub Log
1242             {
1243 43 50   43 0 99 if ($debugMode)
1244 0           { my $message = shift; print STDERR "**IPTC** $message\n"; }
  0            
1245             }
1246              
1247             #
1248             # HexDump
1249             #
1250             # Very helpful when debugging.
1251             #
1252             sub HexDump
1253             {
1254 0     0 0   my $dump = shift;
1255 0           my $len = length($dump);
1256 0           my $offset = 0;
1257 0           my ($dcol1, $dcol2);
1258              
1259 0           while ($offset < $len)
1260             {
1261 0           my $temp = substr($dump, $offset++, 1);
1262              
1263 0           my $hex = unpack("H*", $temp);
1264 0           $dcol1 .= " " . $hex;
1265 0 0 0       if (ord($temp) >= 0x21 && ord($temp) <= 0x7e)
1266 0           { $dcol2 .= " $temp"; }
1267             else
1268 0           { $dcol2 .= " ."; }
1269              
1270 0 0         if ($offset % 16 == 0)
1271             {
1272 0           print STDERR $dcol1 . " | " . $dcol2 . "\n";
1273 0           undef $dcol1; undef $dcol2;
  0            
1274             }
1275             }
1276              
1277 0 0 0       if (defined($dcol1) || defined($dcol2))
1278             {
1279 0           print STDERR $dcol1 . " | " . $dcol2 . "\n";
1280 0           undef $dcol1; undef $dcol2;
  0            
1281             }
1282             }
1283              
1284             #
1285             # JPEGDebugScan
1286             #
1287             # Also very helpful when debugging.
1288             #
1289             sub JPEGDebugScan
1290             {
1291 0     0 0   my $filename = shift;
1292 0           my $handle = IO::File->new($filename);
1293 0 0         $handle or die "Can't open $filename: $!";
1294              
1295             # Skip past start of file marker
1296 0           my ($ff, $soi);
1297 0 0         $handle->read($ff, 1) || return 0;
1298 0           $handle->read($soi, 1);
1299            
1300 0 0 0       unless (ord($ff) == 0xff && ord($soi) == 0xd8)
1301             {
1302 0           Log("JPEGScan: invalid start of file");
1303 0           goto done;
1304             }
1305              
1306             # scan to 0xDA (start of scan), dumping the markers we see between
1307             # here and there.
1308 0           my $marker = JPEGNextMarker($handle);
1309              
1310 0           while (ord($marker) != 0xda)
1311             {
1312 0 0         if (ord($marker) == 0)
1313 0           { Log("Marker scan failed"); goto done; }
  0            
1314              
1315 0 0         if (ord($marker) == 0xd9)
  0            
1316 0           {Log("Marker scan hit end of image marker"); goto done; }
1317              
1318 0 0         if (JPEGSkipVariable($handle) == 0)
1319 0           { Log("JPEGSkipVariable failed"); return 0; }
  0            
1320              
1321 0           $marker = JPEGNextMarker($handle);
1322             }
1323              
1324             done:
1325 0           $handle->close();
1326             }
1327              
1328             # sucessful package load
1329             1;
1330              
1331             __END__