source: gs2-extensions/parallel-building/trunk/src/perllib/cpan/Image/ExifTool/BuildTagLookup.pm@ 24626

Last change on this file since 24626 was 24626, checked in by jmt12, 13 years ago

An (almost) complete copy of the perllib directory from a (circa SEP2011) head checkout from Greenstone 2 trunk - in order to try and make merging in this extension a little easier later on (as there have been some major changes to buildcol.pl commited in the main trunk but not in the x64 branch)

File size: 94.4 KB
Line 
1#------------------------------------------------------------------------------
2# File: BuildTagLookup.pm
3#
4# Description: Utility to build tag lookup tables in Image::ExifTool::TagLookup.pm
5#
6# Revisions: 12/31/2004 - P. Harvey Created
7# 02/15/2005 - PH Added ability to generate TagNames documentation
8#
9# Notes: Documentation for the tag tables may either be placed in the
10# %docs hash below or in a NOTES entry in the table itself, and
11# individual tags may have their own Notes entry.
12#------------------------------------------------------------------------------
13
14package Image::ExifTool::BuildTagLookup;
15
16use strict;
17require Exporter;
18
19BEGIN {
20 # prevent ExifTool from loading the user config file
21 $Image::ExifTool::configFile = '';
22 $Image::ExifTool::debug = 1; # enabled debug messages
23}
24
25use vars qw($VERSION @ISA);
26use Image::ExifTool qw(:Utils :Vars);
27use Image::ExifTool::Exif;
28use Image::ExifTool::Shortcuts;
29use Image::ExifTool::HTML qw(EscapeHTML);
30use Image::ExifTool::IPTC;
31use Image::ExifTool::XMP;
32use Image::ExifTool::Canon;
33use Image::ExifTool::Nikon;
34
35$VERSION = '2.28';
36@ISA = qw(Exporter);
37
38sub NumbersFirst;
39
40my $numbersFirst = 1; # set to -1 to sort numbers last
41
42# list of all tables in plug-in modules
43my @pluginTables = ('Image::ExifTool::MWG::Composite');
44
45# colors for html pages
46my $noteFont = "<span class=n>";
47my $noteFontSmall = "<span class='n s'>";
48
49my $docType = q{<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
50 "http://www.w3.org/TR/html4/loose.dtd">
51};
52
53my $homePage = 'http://owl.phy.queensu.ca/~phil/exiftool';
54
55# list of all recognized Format strings
56# (not a complete list, but this is all we use so far)
57# (also, formats like "var_X[Y]" are allowed for any valid X)
58my %formatOK = (
59 %Image::ExifTool::Exif::formatNumber,
60 0 => 1,
61 1 => 1,
62 real => 1,
63 integer => 1,
64 date => 1,
65 boolean => 1,
66 rational => 1,
67 'lang-alt' => 1,
68 fixed16u => 1,
69 fixed16s => 1,
70 fixed32u => 1,
71 fixed32s => 1,
72 extended => 1,
73 resize => 1,
74 digits => 1,
75 int16uRev => 1,
76 rational32u => 1,
77 rational32s => 1,
78 var_string => 1,
79 var_int16u => 1,
80 var_pstr32 => 1,
81 # Matroska
82 signed => 1,
83 unsigned => 1,
84 utf8 => 1,
85);
86
87my $caseInsensitive; # flag to ignore case when sorting tag names
88
89# Descriptions for the TagNames documentation
90# (descriptions may also be defined in tag table NOTES)
91# Note: POD headers in these descriptions start with '~' instead of '=' to keep
92# from confusing POD parsers which apparently parse inside quoted strings.
93my %docs = (
94 PodHeader => q{
95~head1 NAME
96
97Image::ExifTool::TagNames - ExifTool tag name documentation
98
99~head1 DESCRIPTION
100
101This document contains a complete list of ExifTool tag names, organized into
102tables based on information type. Tag names are used to reference specific
103meta information extracted from or written to a file.
104
105~head1 TAG TABLES
106},
107 ExifTool => q{
108The tables listed below give the names of all tags recognized by ExifTool.
109},
110 ExifTool2 => q{
111B<Tag ID>, B<Index> or B<Sequence> is given in the first column of each
112table. A B<Tag ID> is the computer-readable equivalent of a tag name, and
113is the identifier that is actually stored in the file. An B<Index> refers
114to the location of a value when found at a fixed position within a data
115block, and B<Sequence> gives the order of values for a serial data stream.
116
117A B<Tag Name> is the handle by which the information is accessed in
118ExifTool. In some instances, more than one name may correspond to a single
119tag ID. In these cases, the actual name used depends on the context in
120which the information is found. Case is not significant for tag names. A
121question mark (C<?>) after a tag name indicates that the information is
122either not understood, not verified, or not very useful -- these tags are
123not extracted by ExifTool unless the Unknown (-u) option is enabled. Be
124aware that some tag names are different than the descriptions printed out by
125default when extracting information with exiftool. To see the tag names
126instead of the descriptions, use C<exiftool -s>.
127
128The B<Writable> column indicates whether the tag is writable by ExifTool.
129Anything but an C<N> in this column means the tag is writable. A C<Y>
130indicates writable information that is either unformatted or written using
131the existing format. Other expressions give details about the information
132format, and vary depending on the general type of information. The format
133name may be followed by a number in square brackets to indicate the number
134of values written, or the number of characters in a fixed-length string
135(including a null terminator which is added if required).
136
137A plus sign (C<+>) after an entry in the B<Writable> column indicates a
138"list" tag which supports multiple values and allows individual values to be
139added and deleted. A slash (C</>) indicates an "avoided" tag that is not
140created when writing if another same-named tag may be created instead. To
141write these tags, the group should be specified. A tilde (C<~>) indicates a
142tag this is writable only when the print conversion is disabled (by setting
143PrintConv to 0, using the -n option, or suffixing the tag name with a C<#>
144character). An exclamation point (C<!>) indicates a tag that is considered
145unsafe to write under normal circumstances. These "unsafe" tags are not set
146when calling SetNewValuesFromFile() or copied with the exiftool
147-tagsFromFile option unless specified explicitly, and care should be taken
148when editing them manually since they may affect the way an image is
149rendered. An asterisk (C<*>) indicates a "protected" tag which is not
150writable directly, but is written automatically by ExifTool (often when a
151corresponding Composite or Extra tag is written). A colon (C<:>) indicates
152a mandatory tag which may be added automatically when writing.
153
154The HTML version of these tables also lists possible B<Values> for
155discrete-valued tags, as well as B<Notes> for some tags. The B<Values> are
156listed as the computer-readable and human-readable values on the left and
157right hand side of an equals sign (C<=>) respectively. The human-readable
158values are used by default when reading and writing, but the
159computer-readable values may be accessed by disabling the value conversion
160with the -n option on the command line, by setting the ValueConv option to 0
161in the API, or or on a per-tag basis by appending a number symbol (C<#>) to
162the tag name.
163
164B<Note>: If you are familiar with common meta-information tag names, you may
165find that some ExifTool tag names are different than expected. The usual
166reason for this is to make the tag names more consistent across different
167types of meta information. To determine a tag name, either consult this
168documentation or run C<exiftool -s> on a file containing the information in
169question.
170},
171 EXIF => q{
172EXIF stands for "Exchangeable Image File Format". This type of information
173is formatted according to the TIFF specification, and may be found in JPG,
174TIFF, PNG, PGF, MIFF, HDP, PSP and XCF images, as well as many TIFF-based
175RAW images, and even some AVI and MOV videos.
176
177The EXIF meta information is organized into different Image File Directories
178(IFD's) within an image. The names of these IFD's correspond to the
179ExifTool family 1 group names. When writing EXIF information, the default
180B<Group> listed below is used unless another group is specified.
181
182The table below lists all EXIF tags. Also listed are TIFF, DNG, HDP and
183other tags which are not part of the EXIF specification, but may co-exist
184with EXIF tags in some images. Tags which are part of the EXIF 2.3
185specification have an underlined B<Tag Name> in the HTML version of this
186documentation. See
187L<http://www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-008-2010_E.pdf> for the
188official EXIF 2.3 specification.
189},
190 GPS => q{
191These GPS tags are part of the EXIF standard, and are stored in a separate
192IFD within the EXIF information.
193
194ExifTool is very flexible about the input format when writing lat/long
195coordinates, and will accept from 1 to 3 floating point numbers (for decimal
196degrees, degrees and minutes, or degrees, minutes and seconds) separated by
197just about anything, and will format them properly according to the EXIF
198specification.
199
200Some GPS tags have values which are fixed-length strings. For these, the
201indicated string lengths include a null terminator which is added
202automatically by ExifTool. Remember that the descriptive values are used
203when writing (ie. 'Above Sea Level', not '0') unless the print conversion is
204disabled (with '-n' on the command line or the PrintConv option in the API,
205or by suffixing the tag name with a C<#> character).
206
207When adding GPS information to an image, it is important to set all of the
208following tags: GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef,
209GPSAltitude and GPSAltitudeRef. ExifTool will write the required
210GPSVersionID tag automatically if new a GPS IFD is added to an image.
211},
212 XMP => q{
213XMP stands for "Extensible Metadata Platform", an XML/RDF-based metadata
214format which is being pushed by Adobe. Information in this format can be
215embedded in many different image file types including JPG, JP2, TIFF, GIF,
216EPS, PDF, PSD, IND, PNG, DJVU, SVG, PGF, MIFF, XCF, CRW, DNG and a variety
217of proprietary TIFF-based RAW images, as well as MOV, AVI, ASF, WMV, FLV,
218SWF and MP4 videos, and WMA and audio formats supporting ID3v2 information.
219
220The XMP B<Tag ID>'s aren't listed because in most cases they are identical
221to the B<Tag Name> (aside from differences in case). Tags with different
222ID's are mentioned in the B<Notes> column of the HTML version of this
223document.
224
225All XMP information is stored as character strings. The B<Writable> column
226specifies the information format: C<string> is an unformatted string,
227C<integer> is a string of digits (possibly beginning with a '+' or '-'),
228C<real> is a floating point number, C<rational> is entered as a floating
229point number but stored as two C<integer> strings separated by a '/'
230character, C<date> is a date/time string entered in the format "YYYY:mm:dd
231HH:MM:SS[.ss][+/-HH:MM]", C<boolean> is either "True" or "False",
232C<lang-alt> indicates that the tag supports alternate languages (see below),
233and C<struct> is an XMP structure. When reading, structures are extracted
234only if the Struct (-struct) option is used. Otherwise the corresponding
235"flattened" tags, indicated by an underline (C<_>) after the B<Writable>
236type, are extracted. When copying information, the Struct option is in
237effect by default. When writing, the Struct option has no effect, and both
238structured and flattened tags may be written. See
239L<http://owl.phy.queensu.ca/~phil/exiftool/struct.html> for more details.
240
241Individual languages for C<lang-alt> tags are accessed by suffixing the tag
242name with a '-', followed by an RFC 3066 language code (ie. "XMP:Title-fr",
243or "Rights-en-US"). (See L<http://www.ietf.org/rfc/rfc3066.txt> for the RFC
2443066 specification.) A C<lang-alt> tag with no language code accesses the
245"x-default" language, but causes other languages for this tag to be deleted
246when writing. The "x-default" language code may be specified when writing
247to preserve other existing languages (ie. "XMP-dc:Description-x-default").
248When reading, "x-default" is not specified.
249
250The XMP tags are organized according to schema B<Namespace> in the following
251tables. Note that a few of the longer namespace prefixes given below have
252been shortened for convenience (since the family 1 group names are derived
253from these by adding a leading "XMP-"). In cases where a tag name exists in
254more than one namespace, less common namespaces are avoided when writing.
255However, any namespace may be written by specifying a family 1 group name
256for the tag, ie) XMP-exif:Contrast or XMP-crs:Contrast. When deciding on
257which tags to add to an image, using standard schemas such as
258L<dc|/XMP dc Tags>, L<xmp|/XMP xmp Tags> or L<iptc|/XMP iptcCore Tags> is
259recommended if possible.
260
261For structures, the heading of the first column is B<Field Name>. Field
262names are very similar to tag names, except they are used to identify fields
263inside structures instead of stand-alone tags. See
264L<the Field Name section of the Structured Information documentation|http://owl.phy.queensu.ca/~phil/exiftool/struct.html#Fields> for more
265details.
266
267ExifTool will extract XMP information even if it is not listed in these
268tables. For example, the C<pdfx> namespace doesn't have a predefined set of
269tag names because it is used to store application-defined PDF information,
270but this information is extracted by ExifTool.
271
272See L<http://www.adobe.com/devnet/xmp/> for the official XMP specification.
273},
274 IPTC => q{
275The tags listed below are part of the International Press Telecommunications
276Council (IPTC) and the Newspaper Association of America (NAA) Information
277Interchange Model (IIM). This is an older meta information format, slowly
278being phased out in favor of XMP. (In fact, the newer IPTCCore
279specification actually uses XMP format!) IPTC information may be embedded
280in JPG, TIFF, PNG, MIFF, PS, PDF, PSD, XCF and DNG images.
281
282IPTC information is separated into different records, each of which has its
283own set of tags. See
284L<http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf> for the
285official specification.
286
287This specification dictates a length for ASCII (C<string> or C<digits>) and
288binary (C<undef>) values. These lengths are given in square brackets after
289the B<Writable> format name. For tags where a range of lengths is allowed,
290the minimum and maximum lengths are separated by a comma within the
291brackets. IPTC strings are not null terminated. When writing, ExifTool
292issues a minor warning and truncates the value if it is longer than allowed
293by the IPTC specification. Minor errors may be ignored with the
294IgnoreMinorErrors (-m) option, allowing longer values to be written, but
295beware that values like this may cause problems for some other IPTC readers.
296
297Separate IPTC date and time tags may be written with a combined date/time
298value and ExifTool automagically takes the appropriate part of the date/time
299string depending on whether a date or time tag is being written. This is
300very useful when copying date/time values to IPTC from other metadata
301formats.
302
303IPTC time values include a timezone offset. If written with a value which
304doesn't include a timezone then the current local timezone offset is used
305(unless written with a combined date/time, in which case the local timezone
306offset at the specified date/time is used, which may be different due to
307changes in daylight savings time).
308},
309 Photoshop => q{
310Photoshop tags are found in PSD and PSB files, as well as inside embedded
311Photoshop information in many other file types (JPEG, TIFF, PDF, PNG to name
312a few).
313
314Many Photoshop tags are marked as Unknown (indicated by a question mark
315after the tag name) because the information they provide is not very useful
316under normal circumstances I<[and because Adobe denied my application for
317their file format documentation -- apparently open source software is too
318big a concept for them]>. These unknown tags are not extracted unless the
319Unknown (-u) option is used.
320},
321 PrintIM => q{
322The format of the PrintIM information is known, however no PrintIM tags have
323been decoded. Use the Unknown (-u) option to extract PrintIM information.
324},
325 GeoTiff => q{
326ExifTool extracts the following tags from GeoTIFF images. See
327L<http://www.remotesensing.org/geotiff/spec/geotiffhome.html> for the
328complete GeoTIFF specification.
329},
330 JFIF => q{
331The following information is extracted from the JPEG JFIF header. See
332L<http://www.jpeg.org/public/jfif.pdf> for the JFIF 1.02 specification.
333},
334 Kodak => q{
335Many Kodak models don't store the maker notes in standard IFD format, and
336these formats vary with different models. Some information has been
337decoded, but much of the Kodak information remains unknown.
338},
339 'Kodak SpecialEffects' => q{
340The Kodak SpecialEffects and Borders tags are found in sub-IFD's within the
341Kodak JPEG APP3 "Meta" segment.
342},
343 Minolta => q{
344These tags are used by Minolta, Konica/Minolta as well as some Sony cameras.
345Minolta doesn't make things easy for decoders because the meaning of some
346tags and the location where some information is stored is different for
347different camera models. (Take MinoltaQuality for example, which may be
348located in 5 different places.)
349},
350 Olympus => q{
351Tags 0x0000 through 0x0103 are used by some older Olympus cameras, and are
352the same as Konica/Minolta tags. The Olympus tags are also used for Epson
353and Agfa cameras.
354},
355 Panasonic => q{
356These tags are used in Panasonic/Leica cameras.
357},
358 Pentax => q{
359These tags are used in Pentax/Asahi cameras.
360},
361 Sigma => q{
362These tags are used in Sigma/Foveon cameras.
363},
364 Sony => q{
365The maker notes in images from most recent Sony camera models contain a
366wealth of information, but for some models very little has been decoded.
367Use the ExifTool Unknown (-u) or Verbose (-v) options to see information
368about the unknown tags. Also see the Minolta tags which are used by some
369Sony models.
370},
371 CanonRaw => q{
372These tags apply to CRW-format Canon RAW files and information in the APP0
373"CIFF" segment of JPEG images. When writing CanonRaw/CIFF information, the
374length of the information is preserved (and the new information is truncated
375or padded as required) unless B<Writable> is C<resize>. Currently, only
376JpgFromRaw and ThumbnailImage are allowed to change size.
377
378CRW images also support the addition of a CanonVRD trailer, which in turn
379supports XMP. This trailer is created automatically if necessary when
380ExifTool is used to write XMP to a CRW image.
381},
382 Unknown => q{
383The following tags are decoded in unsupported maker notes. Use the Unknown
384(-u) option to display other unknown tags.
385},
386 PDF => q{
387The tags listed in the PDF tables below are those which are used by ExifTool
388to extract meta information, but they are only a small fraction of the total
389number of available PDF tags. See
390L<http://www.adobe.com/devnet/pdf/pdf_reference.html> for the official PDF
391specification.
392
393ExifTool supports reading and writing PDF documents up to version 1.7
394extension level 3, including support for RC4, AES-128 and AES-256
395encryption. A Password option is provided to allow processing of
396password-protected PDF files.
397
398When writing PDF files, ExifTool uses an incremental update. This has the
399advantages of being fast and reversible. The original PDF can be easily
400recovered by deleting the C<PDF-update> pseudo-group (with
401C<-PDF-update:all=> on the command line). But there are two main
402disadvantages to this technique:
403
4041) A linearized PDF file is no longer linearized after the update, so it
405must be subsequently re-linearized if this is required.
406
4072) All metadata edits are reversible. While this would normally be
408considered an advantage, it is a potential security problem because old
409information is never actually deleted from the file.
410},
411 DNG => q{
412The main DNG tags are found in the EXIF table. The tables below define only
413information found within structures of these main DNG tag values. See
414L<http://www.adobe.com/products/dng/> for the official DNG specification.
415},
416 MPEG => q{
417The MPEG format doesn't specify any file-level meta information. In lieu of
418this, information is extracted from the first audio and video frame headers
419in the file.
420},
421 Real => q{
422ExifTool recognizes three basic types of Real audio/video files: 1)
423RealMedia (RM, RV and RMVB), 2) RealAudio (RA), and 3) Real Metafile (RAM
424and RPM).
425},
426 Extra => q{
427The extra tags represent extra information extracted or generated by
428ExifTool that is not directly associated with another tag group. The three
429writable "pseudo" tags (FileName, Directory and FileModifyDate) may be
430written without the need to rewrite the file since their values are not
431contained within the file data. These "pseudo" tags belong to the family 1
432"System" group.
433},
434 Composite => q{
435The values of the composite tags are B<Derived From> the values of other
436tags. These are convenience tags which are calculated after all other
437information is extracted.
438},
439 Shortcuts => q{
440Shortcut tags are convenience tags that represent one or more other tag
441names. They are used like regular tags to read and write the information
442for a specified set of tags.
443
444The shortcut tags below have been pre-defined, but user-defined shortcuts
445may be added via the %Image::ExifTool::UserDefined::Shortcuts lookup in the
446~/.ExifTool_config file. See the Image::ExifTool::Shortcuts documentation
447for more details.
448},
449 PodTrailer => q{
450~head1 NOTES
451
452This document generated automatically by
453L<Image::ExifTool::BuildTagLookup|Image::ExifTool::BuildTagLookup>.
454
455~head1 AUTHOR
456
457Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)
458
459This library is free software; you can redistribute it and/or modify it
460under the same terms as Perl itself.
461
462~head1 SEE ALSO
463
464L<Image::ExifTool(3pm)|Image::ExifTool>
465
466~cut
467},
468);
469
470# notes for Shortcuts tags
471my %shortcutNotes = (
472 MakerNotes => q{
473 useful when copying tags between files to either copy the maker notes as a
474 block or prevent it from being copied
475 },
476 CommonIFD0 => q{
477 common metadata tags found in IFD0 of TIFF-format images. Used to simpify
478 deletion of all metadata from these images. See FAQ number 7 for details
479 },
480 Unsafe => q{
481 "unsafe" tags in JPEG images which are normally not copied. Defined here
482 as a shortcut to use when rebuilding JPEG EXIF from scratch
483 },
484);
485
486
487
488# EXIF table tag ID's which are part of the EXIF 2.3 specification
489# (used only to add underlines in HTML version of EXIF Tag Table)
490my %exifSpec = (
491 0x100 => 1, 0x212 => 1, 0x9204 => 1, 0xa217 => 1,
492 0x101 => 1, 0x213 => 1, 0x9205 => 1, 0xa300 => 1,
493 0x102 => 1, 0x214 => 1, 0x9206 => 1, 0xa301 => 1,
494 0x103 => 1, 0x8298 => 1, 0x9207 => 1, 0xa302 => 1,
495 0x106 => 1, 0x829a => 1, 0x9208 => 1, 0xa401 => 1,
496 0x10e => 1, 0x829d => 1, 0x9209 => 1, 0xa402 => 1,
497 0x10f => 1, 0x8769 => 1, 0x920a => 1, 0xa403 => 1,
498 0x110 => 1, 0x8822 => 1, 0x9214 => 1, 0xa404 => 1,
499 0x111 => 1, 0x8824 => 1, 0x927c => 1, 0xa405 => 1,
500 0x112 => 1, 0x8825 => 1, 0x9286 => 1, 0xa406 => 1,
501 0x115 => 1, 0x8827 => 1, 0x9290 => 1, 0xa407 => 1,
502 0x116 => 1, 0x8828 => 1, 0x9291 => 1, 0xa408 => 1,
503 0x117 => 1, 0x8830 => 1, 0x9292 => 1, 0xa409 => 1,
504 0x11a => 1, 0x8831 => 1, 0xa000 => 1, 0xa40a => 1,
505 0x11b => 1, 0x8832 => 1, 0xa001 => 1, 0xa40b => 1,
506 0x11c => 1, 0x8833 => 1, 0xa002 => 1, 0xa40c => 1,
507 0x128 => 1, 0x8834 => 1, 0xa003 => 1, 0xa420 => 1,
508 0x12d => 1, 0x8835 => 1, 0xa004 => 1, 0xa430 => 1,
509 0x131 => 1, 0x9000 => 1, 0xa005 => 1, 0xa431 => 1,
510 0x132 => 1, 0x9003 => 1, 0xa20b => 1, 0xa432 => 1,
511 0x13b => 1, 0x9004 => 1, 0xa20c => 1, 0xa433 => 1,
512 0x13e => 1, 0x9101 => 1, 0xa20e => 1, 0xa434 => 1,
513 0x13f => 1, 0x9102 => 1, 0xa20f => 1, 0xa435 => 1,
514 0x201 => 1, 0x9201 => 1, 0xa210 => 1,
515 0x202 => 1, 0x9202 => 1, 0xa214 => 1,
516 0x211 => 1, 0x9203 => 1, 0xa215 => 1,
517);
518# same thing for RIFF INFO tags found in the EXIF spec
519my %riffSpec = (
520 IARL => 1, ICRD => 1, IGNR => 1, IPLT => 1, ISRC => 1,
521 IART => 1, ICRP => 1, IKEY => 1, IPRD => 1, ISRF => 1,
522 ICMS => 1, IDIM => 1, ILGT => 1, ISBJ => 1, ITCH => 1,
523 ICMT => 1, IDPI => 1, IMED => 1, ISFT => 1,
524 ICOP => 1, IENG => 1, INAM => 1, ISHP => 1,
525);
526
527#------------------------------------------------------------------------------
528# New - create new BuildTagLookup object
529# Inputs: 0) reference to BuildTagLookup object or BuildTagLookup class name
530sub new
531{
532 local $_;
533 my $that = shift;
534 my $class = ref($that) || $that || 'Image::ExifTool::BuildTagLookup';
535 my $self = bless {}, $class;
536 my (%subdirs, %isShortcut, %allStructs);
537 my %count = (
538 'unique tag names' => 0,
539 'total tags' => 0,
540 );
541#
542# loop through all tables, accumulating TagLookup and TagName information
543#
544 my (%tagNameInfo, %id, %longID, %longName, %shortName, %tableNum,
545 %tagLookup, %tagExists, %tableWritable, %sepTable, %structs,
546 %compositeModules, %isPlugin, %flattened, %structLookup);
547 $self->{TAG_NAME_INFO} = \%tagNameInfo;
548 $self->{ID_LOOKUP} = \%id;
549 $self->{LONG_ID} = \%longID;
550 $self->{LONG_NAME} = \%longName;
551 $self->{SHORT_NAME} = \%shortName;
552 $self->{TABLE_NUM} = \%tableNum;
553 $self->{TAG_LOOKUP} = \%tagLookup;
554 $self->{TAG_EXISTS} = \%tagExists;
555 $self->{FLATTENED} = \%flattened;
556 $self->{TABLE_WRITABLE} = \%tableWritable;
557 $self->{SEPARATE_TABLE} = \%sepTable;
558 $self->{STRUCTURES} = \%structs;
559 $self->{STRUCT_LOOKUP} = \%structLookup; # lookup for Struct hash ref based on Struct name
560 $self->{COMPOSITE_MODULES} = \%compositeModules;
561 $self->{COUNT} = \%count;
562
563 Image::ExifTool::LoadAllTables();
564 my @tableNames = sort keys %allTables;
565 # add Shortcuts after other tables
566 push @tableNames, 'Image::ExifTool::Shortcuts::Main';
567 # add plug-in modules last
568 $Image::ExifTool::documentOnly = 1; # (don't really load them)
569 foreach (@pluginTables) {
570 push @tableNames, $_;
571 $isPlugin{$_} = 1;
572 }
573
574 my $tableNum = 0;
575 my $exifTool = new Image::ExifTool;
576 my ($tableName, $tag);
577 # create lookup for short table names
578 foreach $tableName (@tableNames) {
579 my $short = $tableName;
580 $short =~ s/^Image::ExifTool:://;
581 $short =~ s/::Main$//;
582 $short =~ s/::/ /;
583 $short =~ s/^(.+)Tags$/\u$1/ unless $short eq 'Nikon AVITags';
584 $short =~ s/^Exif\b/EXIF/;
585 $shortName{$tableName} = $short; # remember short name
586 $tableNum{$tableName} = $tableNum++;
587 }
588 # validate DICOM UID values
589 foreach (values %Image::ExifTool::DICOM::uid) {
590 next unless /[\0-\x1f\x7f-\xff]/;
591 warn "Warning: Special characters in DICOM UID value ($_)\n";
592 }
593 # make lookup table to check for shortcut tags
594 foreach $tag (keys %Image::ExifTool::Shortcuts::Main) {
595 my $entry = $Image::ExifTool::Shortcuts::Main{$tag};
596 # ignore if shortcut tag name includes itself
597 next if ref $entry eq 'ARRAY' and grep /^$tag$/, @$entry;
598 $isShortcut{lc($tag)} = 1;
599 }
600 foreach $tableName (@tableNames) {
601 # create short table name
602 my $short = $shortName{$tableName};
603 my $info = $tagNameInfo{$tableName} = [ ];
604 my $isPlugin = $isPlugin{$tableName};
605 my ($table, $shortcut, %isOffset, %datamember, %hasSubdir);
606 if ($short eq 'Shortcuts') {
607 # can't use GetTagTable() for Shortcuts (not a normal table)
608 $table = \%Image::ExifTool::Shortcuts::Main;
609 $shortcut = 1;
610 } elsif ($isPlugin) {
611 $table = GetTagTable($tableName);
612 # don't add to allTables list because this messes our table order
613 delete $allTables{$tableName};
614 pop @tableOrder;
615 } else {
616 $table = GetTagTable($tableName);
617 }
618 my $tableNum = $tableNum{$tableName};
619 my $writeProc = $$table{WRITE_PROC};
620 my $vars = $$table{VARS} || { };
621 $longID{$tableName} = 0;
622 $longName{$tableName} = 0;
623 # save all tag names
624 my ($tagID, $binaryTable, $noID, $isIPTC, $isXMP);
625 $isIPTC = 1 if $writeProc and $writeProc eq \&Image::ExifTool::IPTC::WriteIPTC;
626 # generate flattened tag names for structure fields if this is an XMP table
627 if ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP') {
628 $isXMP = 1;
629 foreach $tagID (TagTableKeys($table)) {
630 my $tagInfo = $$table{$tagID};
631 next unless ref $tagInfo eq 'HASH' and $$tagInfo{Struct};
632 Image::ExifTool::XMP::AddFlattenedTags($table, $tagID);
633 }
634 }
635 $noID = 1 if $isXMP or $short =~ /^(Shortcuts|ASF.*)$/ or $$vars{NO_ID};
636 my $processBinaryData = ($$table{PROCESS_PROC} and (
637 $$table{PROCESS_PROC} eq \&Image::ExifTool::ProcessBinaryData or
638 $$table{PROCESS_PROC} eq \&Image::ExifTool::Nikon::ProcessNikonEncrypted));
639 if ($$vars{ID_LABEL} or $processBinaryData) {
640 $binaryTable = 1;
641 $id{$tableName} = $$vars{ID_LABEL} || 'Index';
642 } elsif ($isIPTC and $$table{PROCESS_PROC}) { #only the main IPTC table has a PROCESS_PROC
643 $id{$tableName} = 'Record';
644 } elsif (not $noID) {
645 $id{$tableName} = 'Tag ID';
646 }
647 $caseInsensitive = $isXMP;
648 $numbersFirst = -1 if $$table{VARS} and $$table{VARS}{ALPHA_FIRST};
649 my @keys = sort NumbersFirst TagTableKeys($table);
650 $numbersFirst = 1;
651 my $defFormat = $table->{FORMAT};
652 # use default format for binary data tables
653 $defFormat = 'int8u' if not $defFormat and $binaryTable;
654
655TagID: foreach $tagID (@keys) {
656 my ($tagInfo, @tagNames, $subdir, $format, @values);
657 my (@infoArray, @require, @writeGroup, @writable);
658 if ($shortcut) {
659 # must build a dummy tagInfo list since Shortcuts is not a normal table
660 $tagInfo = {
661 Name => $tagID,
662 Notes => $shortcutNotes{$tagID},
663 Writable => 1,
664 Require => { },
665 };
666 my $i;
667 for ($i=0; $i<@{$$table{$tagID}}; ++$i) {
668 $tagInfo->{Require}->{$i} = $table->{$tagID}->[$i];
669 }
670 @infoArray = ( $tagInfo );
671 } else {
672 @infoArray = GetTagInfoList($table,$tagID);
673 }
674 $format = $defFormat;
675 foreach $tagInfo (@infoArray) {
676 my $name = $$tagInfo{Name};
677 # validate Name
678 warn "Warning: Invalid tag name $short '$name'\n" if $name !~ /^[-\w]+$/;
679 # accumulate information for consistency check of BinaryData tables
680 if ($processBinaryData and $$table{WRITABLE}) {
681 $isOffset{$tagID} = $name if $$tagInfo{IsOffset};
682 $hasSubdir{$tagID} = $name if $$tagInfo{SubDirectory};
683 # require DATAMEMBER for writable var-format tags, Hook and DataMember tags
684 if ($$tagInfo{Format} and $$tagInfo{Format} =~ /^var_/) {
685 $datamember{$tagID} = $name;
686 unless (defined $$tagInfo{Writable} and not $$tagInfo{Writable}) {
687 warn "Warning: Var-format tag is writable - $short $name\n"
688 }
689 } elsif ($$tagInfo{Hook} or ($$tagInfo{RawConv} and
690 $$tagInfo{RawConv} =~ /\$self(->)?\{\w+\}\s*=(?!~)/))
691 {
692 $datamember{$tagID} = $name;
693 }
694 }
695 if ($$tagInfo{Hidden}) {
696 warn "Warning: Hidden tag in list - $short $name\n" if @infoArray > 1;
697 next TagID;
698 }
699 my $writable;
700 if (defined $$tagInfo{Writable}) {
701 $writable = $$tagInfo{Writable};
702 # validate Writable
703 unless ($formatOK{$writable} or ($writable =~ /(.*)\[/ and $formatOK{$1})) {
704 warn "Warning: Unknown Writable ($writable) for $short $name\n",
705 }
706 } elsif (not $$tagInfo{SubDirectory}) {
707 $writable = $$table{WRITABLE};
708 }
709 # validate some characteristics of obvious date/time tags
710 if ($$tagInfo{PrintConv} and $$tagInfo{PrintConv} eq '$self->ConvertDateTime($val)') {
711 my @g = $exifTool->GetGroup($tagInfo);
712 warn "$short $name should be in 'Time' group!\n" unless $g[2] eq 'Time';
713 if ($writable and not $$tagInfo{Shift} and $g[0] ne 'Composite' and
714 $short ne 'PostScript')
715 {
716 warn "$short $name is not shiftable!\n";
717 }
718 } elsif ($name =~ /DateTime(?!Stamp)/ and (not $$tagInfo{Groups}{2} or
719 $$tagInfo{Groups}{2} ne 'Time') and $short ne 'DICOM') {
720 warn "$short $name should be in 'Time' group!\n";
721 }
722 # validate Description (can't contain special characters)
723 if ($$tagInfo{Description} and
724 $$tagInfo{Description} ne EscapeHTML($$tagInfo{Description}))
725 {
726 # this is a problem because the Escape option currently only
727 # escapes descriptions if the default Lang option isn't default
728 warn "$name description contains special characters!\n";
729 }
730 # validate SubIFD flag
731 my $subdir = $$tagInfo{SubDirectory};
732 my $struct = $$tagInfo{Struct};
733 my $strTable;
734 if (ref $struct) {
735 $strTable = $struct;
736 $struct = $$strTable{STRUCT_NAME};
737 if ($struct) {
738 my $oldTable = $structLookup{$struct};
739 if ($oldTable and $oldTable ne $strTable) {
740 warn "Duplicate XMP structure with name $struct\n";
741 } else {
742 $structLookup{$struct} = $strTable;
743 }
744 } else {
745 warn "Missing STRUCT_NAME for structure in $$tagInfo{Name}\n";
746 undef $strTable;
747 }
748 } elsif ($struct) {
749 $strTable = $structLookup{$struct};
750 unless ($strTable) {
751 warn "Missing XMP $struct structure!\n";
752 undef $struct;
753 }
754 }
755 my $isSub = ($subdir and $$subdir{Start} and $$subdir{Start} eq '$val');
756 if ($$tagInfo{SubIFD}) {
757 warn "Warning: Wrong SubDirectory Start for SubIFD tag - $short $name\n" unless $isSub;
758 } else {
759 warn "Warning: SubIFD flag not set for $short $name\n" if $isSub;
760 }
761 if ($$tagInfo{Notes}) {
762 my $note = $$tagInfo{Notes};
763 # remove leading/trailing blank lines
764 $note =~ s/(^\s+|\s+$)//g;
765 # remove leading/trailing spaces on each line
766 $note =~ s/(^[ \t]+|[ \t]+$)//mg;
767 push @values, "($note)";
768 } elsif ($isXMP and lc $tagID ne lc $name) {
769 # add note about different XMP Tag ID
770 if ($$tagInfo{RootTagInfo}) {
771 push @values, "($tagID)";
772 } else {
773 push @values,"(called $tagID by the spec)";
774 }
775 }
776 my $writeGroup;
777 $writeGroup = $$tagInfo{WriteGroup};
778 unless ($writeGroup) {
779 $writeGroup = $$table{WRITE_GROUP} if $writable;
780 $writeGroup = '-' unless $writeGroup;
781 }
782 if (defined $$tagInfo{Format}) {
783 $format = $$tagInfo{Format};
784 # validate Format
785 unless ($formatOK{$format} or $short eq 'PICT' or
786 ($format =~ /^(var_)?(.*)\[/ and $formatOK{$2}))
787 {
788 warn "Warning: Unknown Format ($format) for $short $name\n";
789 }
790 }
791 if ($subdir) {
792 my $subTable = $$subdir{TagTable} || $tableName;
793 push @values, $shortName{$subTable}
794 } elsif ($struct) {
795 push @values, $struct;
796 $structs{$struct} = 1;
797 }
798 my $type;
799 foreach $type ('Require','Desire') {
800 my $require = $$tagInfo{$type};
801 if (ref $require) {
802 foreach (sort { $a <=> $b } keys %$require) {
803 push @require, $$require{$_};
804 }
805 } elsif ($require) {
806 push @require, $require;
807 }
808 }
809 my $printConv = $$tagInfo{PrintConv};
810 if ($$tagInfo{Mask}) {
811 my $val = $$tagInfo{Mask};
812 push @values, sprintf('[Mask 0x%.2x]',$val);
813 $$tagInfo{PrintHex} = 1 unless defined $$tagInfo{PrintHex};
814 # verify that all values are within the mask
815 if (ref $printConv eq 'HASH') {
816 # convert mask if necessary
817 if ($$tagInfo{ValueConv}) {
818 my $v = eval $$tagInfo{ValueConv};
819 $val = $v if defined $v;
820 }
821 foreach (keys %$printConv) {
822 next if $_ !~ /^\d+$/ or ($_ & $val) == $_;
823 my $hex = sprintf '0x%.2x', $_;
824 warn "$short $name PrintConv value $hex is not in Mask!\n";
825 }
826 }
827 }
828 if (ref($printConv) =~ /^(HASH|ARRAY)$/) {
829 my (@printConvList, @indexList, $index);
830 if (ref $printConv eq 'ARRAY') {
831 for ($index=0; $index<@$printConv; ++$index) {
832 next if ref $$printConv[$index] ne 'HASH';
833 next unless %{$$printConv[$index]};
834 push @printConvList, $$printConv[$index];
835 push @indexList, $index;
836 # collapse values with identical PrintConv's
837 if (@printConvList >= 2 and $printConvList[-1] eq $printConvList[-2]) {
838 if (ref $indexList[-2]) {
839 push @{$indexList[-2]}, $indexList[-1];
840 } else {
841 $indexList[-2] = [ $indexList[-2], $indexList[-1] ];
842 }
843 pop @printConvList;
844 pop @indexList;
845 }
846 }
847 $printConv = shift @printConvList;
848 $index = shift @indexList;
849 }
850 while (defined $printConv) {
851 if (defined $index) {
852 # (print indices of original values if reorganized)
853 my $s = '';
854 my $idx = $$tagInfo{Relist} ? $tagInfo->{Relist}->[$index] : $index;
855 if (ref $idx) {
856 $s = 's' if @$idx > 1;
857 # collapse consecutive number ranges
858 my ($i, @i, $rngStart);
859 for ($i=0; $i<@$idx; ++$i) {
860 if ($i < @$idx - 1 and $$idx[$i+1] == $$idx[$i] + 1) {
861 $rngStart = $i unless defined $rngStart;
862 next;
863 }
864 push @i, defined($rngStart) ? "$rngStart-$i" : $i;
865 }
866 ($idx = join ', ', @i) =~ s/(.*),/$1 and/;
867 } elsif (not $$tagInfo{Relist}) {
868 while (@printConvList and $printConv eq $printConvList[0]) {
869 shift @printConvList;
870 $index = shift @indexList;
871 }
872 if ($idx != $index) {
873 $idx = "$idx-$index";
874 $s = 's';
875 }
876 }
877 push @values, "[Value$s $idx]";
878 }
879 if ($$tagInfo{SeparateTable}) {
880 $subdir = 1;
881 my $s = $$tagInfo{SeparateTable};
882 $s = $name if $s eq '1';
883 # add module name if not specified
884 $s =~ / / or ($short =~ /^(\w+)/ and $s = "$1 $s");
885 push @values, $s;
886 $sepTable{$s} = $printConv;
887 # add PrintHex flag to PrintConv so we can check it later
888 $$printConv{PrintHex} = 1 if $$tagInfo{PrintHex};
889 $$printConv{PrintString} = 1 if $$tagInfo{PrintString};
890 } else {
891 $caseInsensitive = 0;
892 my @pk = sort NumbersFirst keys %$printConv;
893 my $n = scalar @values;
894 my ($bits, $cols, $i);
895 foreach (@pk) {
896 next if $_ eq '';
897 $_ eq 'BITMASK' and $bits = $$printConv{$_}, next;
898 $_ eq 'OTHER' and next;
899 my $index;
900 if (($$tagInfo{PrintHex} or $$printConv{BITMASK}) and /^\d+$/) {
901 $index = sprintf('0x%x',$_);
902 } elsif (/^[+-]?(?=\d|\.\d)\d*(\.\d*)?$/ and not $$tagInfo{PrintString}) {
903 $index = $_;
904 } else {
905 $index = $_;
906 # translate unprintable values
907 if ($index =~ s/([\x00-\x1f\x80-\xff])/sprintf("\\x%.2x",ord $1)/eg) {
908 $index = qq{"$index"};
909 } else {
910 $index = qq{'$index'};
911 }
912 }
913 push @values, "$index = " . $$printConv{$_};
914 # validate all PrintConv values
915 if ($$printConv{$_} =~ /[\0-\x1f\x7f-\xff]/) {
916 warn "Warning: Special characters in $short $name PrintConv ($$printConv{$_})\n";
917 }
918 }
919 if ($bits) {
920 my @pk = sort NumbersFirst keys %$bits;
921 foreach (@pk) {
922 push @values, "Bit $_ = " . $$bits{$_};
923 }
924 }
925 # organize values into columns if specified
926 if (defined($cols = $$tagInfo{PrintConvColumns})) {
927 my @new = splice @values, $n;
928 my $v = '[!HTML]<table class=cols><tr>';
929 my $rows = int((scalar(@new) + $cols - 1) / $cols);
930 for ($n=0; $n<@new; $n+=$rows) {
931 $v .= "\n <td>";
932 for ($i=0; $i<$rows and $n+$i<@new; ++$i) {
933 $v .= "\n <br>" if $i;
934 $v .= EscapeHTML($new[$n+$i]);
935 }
936 $v .= '</td><td>&nbsp;&nbsp;</td>';
937 }
938 push @values, $v . "</tr></table>\n";
939 }
940 }
941 last unless @printConvList;
942 $printConv = shift @printConvList;
943 $index = shift @indexList;
944 }
945 } elsif ($printConv and $printConv =~ /DecodeBits\(\$val,\s*(\{.*\})\s*\)/s) {
946 $$self{Model} = ''; # needed for Nikon ShootingMode
947 my $bits = eval $1;
948 delete $$self{Model};
949 if ($@) {
950 warn $@;
951 } else {
952 my @pk = sort NumbersFirst keys %$bits;
953 foreach (@pk) {
954 push @values, "Bit $_ = " . $$bits{$_};
955 }
956 }
957 }
958 if ($subdir and not $$tagInfo{SeparateTable}) {
959 # subdirectories are only writable if specified explicitly
960 my $tw = $$tagInfo{Writable};
961 $writable = '-' . ($tw ? $writable : '');
962 $writable .= '!' if $tw and ($$tagInfo{Protected} || 0) & 0x01;
963 } else {
964 # not writable if we can't do the inverse conversions
965 my $noPrintConvInv;
966 if ($writable) {
967 foreach ('PrintConv','ValueConv') {
968 next unless $$tagInfo{$_};
969 next if $$tagInfo{$_ . 'Inv'};
970 next if ref($$tagInfo{$_}) =~ /^(HASH|ARRAY)$/;
971 next if $$tagInfo{WriteAlso};
972 if ($_ eq 'ValueConv') {
973 undef $writable;
974 } else {
975 $noPrintConvInv = 1;
976 }
977 }
978 }
979 if (not $writable) {
980 $writable = 'N';
981 } else {
982 $writable eq '1' and $writable = $format ? $format : 'Y';
983 my $count = $$tagInfo{Count} || 1;
984 # adjust count to Writable size if different than Format
985 if ($writable and $format and $writable ne $format and
986 $Image::ExifTool::Exif::formatNumber{$writable} and
987 $Image::ExifTool::Exif::formatNumber{$format})
988 {
989 my $n1 = $Image::ExifTool::Exif::formatNumber{$format};
990 my $n2 = $Image::ExifTool::Exif::formatNumber{$writable};
991 $count *= $Image::ExifTool::Exif::formatSize[$n1] /
992 $Image::ExifTool::Exif::formatSize[$n2];
993 }
994 if ($count != 1) {
995 $count = 'n' if $count < 0;
996 $writable .= "[$count]";
997 }
998 $writable .= '~' if $noPrintConvInv;
999 # add a '*' if this tag is protected or a '!' for unsafe tags
1000 if ($$tagInfo{Protected}) {
1001 $writable .= '*' if $$tagInfo{Protected} & 0x02;
1002 $writable .= '!' if $$tagInfo{Protected} & 0x01;
1003 }
1004 $writable .= '/' if $$tagInfo{Avoid};
1005 }
1006 $writable = "=struct" if $struct;
1007 $writable .= '_' if defined $$tagInfo{Flat};
1008 $writable .= '+' if $$tagInfo{List};
1009 $writable .= ':' if $$tagInfo{Mandatory};
1010 # separate tables link like subdirectories (flagged with leading '-')
1011 $writable = "-$writable" if $subdir;
1012 }
1013 # don't duplicate a tag name unless an entry is different
1014 my $lcName = lc($name);
1015 # check for conflicts with shortcut names
1016 if ($isShortcut{$lcName} and $short ne 'Shortcuts' and
1017 ($$tagInfo{Writable} or not $$tagInfo{SubDirectory}))
1018 {
1019 warn "WARNING: $short $name is a shortcut tag!\n";
1020 }
1021 $name .= '?' if $$tagInfo{Unknown};
1022 unless (@tagNames and $tagNames[-1] eq $name and
1023 $writeGroup[-1] eq $writeGroup and $writable[-1] eq $writable)
1024 {
1025 push @tagNames, $name;
1026 push @writeGroup, $writeGroup;
1027 push @writable, $writable;
1028 }
1029#
1030# add this tag to the tag lookup unless PROCESS_PROC is 0 or shortcut or plug-in tag
1031#
1032 next if $shortcut or $isPlugin;
1033 next if defined $$table{PROCESS_PROC} and not $$table{PROCESS_PROC};
1034 # count our tags
1035 if ($$tagInfo{SubDirectory}) {
1036 $subdirs{$lcName} or $subdirs{$lcName} = 0;
1037 ++$subdirs{$lcName};
1038 } else {
1039 ++$count{'total tags'};
1040 unless ($tagExists{$lcName} and (not $subdirs{$lcName} or $subdirs{$lcName} == $tagExists{$lcName})) {
1041 ++$count{'unique tag names'};
1042 }
1043 }
1044 $tagExists{$lcName} or $tagExists{$lcName} = 0;
1045 ++$tagExists{$lcName};
1046 # only add writable tags to lookup table (for speed)
1047 my $wflag = $$tagInfo{Writable};
1048 next unless $writeProc and ($wflag or ($$table{WRITABLE} and
1049 not defined $wflag and not $$tagInfo{SubDirectory}));
1050 $tagLookup{$lcName} or $tagLookup{$lcName} = { };
1051 # add to lookup for flattened tags if necessary
1052 if ($$tagInfo{RootTagInfo}) {
1053 $flattened{$lcName} or $flattened{$lcName} = { };
1054 $flattened{$lcName}{$tableNum} = $$tagInfo{RootTagInfo}{TagID};
1055 }
1056 # remember number for this table
1057 my $tagIDs = $tagLookup{$lcName}->{$tableNum};
1058 # must allow for duplicate tags with the same name in a single table!
1059 if ($tagIDs) {
1060 if (ref $tagIDs eq 'HASH') {
1061 $$tagIDs{$tagID} = 1;
1062 next;
1063 } elsif ($tagID eq $tagIDs) {
1064 next;
1065 } else {
1066 $tagIDs = { $tagIDs => 1, $tagID => 1 };
1067 }
1068 } else {
1069 $tagIDs = $tagID;
1070 }
1071 $tableWritable{$tableName} = 1;
1072 $tagLookup{$lcName}->{$tableNum} = $tagIDs;
1073 if ($short eq 'Composite' and $$tagInfo{Module}) {
1074 $compositeModules{$lcName} = $$tagInfo{Module};
1075 }
1076 }
1077#
1078# save TagName information
1079#
1080 my $tagIDstr;
1081 if ($tagID =~ /^\d+(\.\d+)?$/) {
1082 if ($1 or $binaryTable or $isIPTC or ($short =~ /^CanonCustom/ and $tagID < 256)) {
1083 if ($tagID < 0x10000) {
1084 $tagIDstr = $tagID;
1085 } else {
1086 $tagIDstr = sprintf('0x%.8x',$tagID);
1087 }
1088 } else {
1089 $tagIDstr = sprintf('0x%.4x',$tagID);
1090 }
1091 } elsif ($short eq 'DICOM') {
1092 ($tagIDstr = $tagID) =~ s/_/,/;
1093 } else {
1094 # convert non-printable characters to hex escape sequences
1095 if ($tagID =~ s/([\x00-\x1f\x7f-\xff])/'\x'.unpack('H*',$1)/eg) {
1096 $tagID =~ s/\\x00/\\0/g;
1097 next if $tagID eq 'jP\x1a\x1a'; # ignore abnormal JP2 signature tag
1098 $tagIDstr = qq{"$tagID"};
1099 } else {
1100 $tagIDstr = "'$tagID'";
1101 }
1102 }
1103 my $len = length $tagIDstr;
1104 $longID{$tableName} = $len if $longID{$tableName} < $len;
1105 foreach (@tagNames) {
1106 $len = length $_;
1107 $longName{$tableName} = $len if $longName{$tableName} < $len;
1108 }
1109 push @$info, [ $tagIDstr, \@tagNames, \@writable, \@values, \@require, \@writeGroup ];
1110 }
1111 # do consistency check of writable BinaryData tables
1112 if ($processBinaryData and $$table{WRITABLE}) {
1113 my %lookup = (
1114 IS_OFFSET => \%isOffset,
1115 IS_SUBDIR => \%hasSubdir,
1116 DATAMEMBER => \%datamember,
1117 );
1118 my ($var, $tagID);
1119 foreach $var (sort keys %lookup) {
1120 my $hash = $lookup{$var};
1121 if ($$table{$var}) {
1122 foreach $tagID (@{$$table{$var}}) {
1123 $$hash{$tagID} and delete($$hash{$tagID}), next;
1124 warn "Warning: Extra $var for $short tag $tagID\n";
1125 }
1126 }
1127 foreach $tagID (sort keys %$hash) {
1128 warn "Warning: Missing $var for $short $$hash{$tagID}\n";
1129 }
1130 }
1131 }
1132 }
1133 # save information about structures
1134 my $strName;
1135 foreach $strName (keys %structs) {
1136 my $struct = $structLookup{$strName};
1137 my $info = $tagNameInfo{"XMP $strName Struct"} = [ ];
1138 my $tag;
1139 foreach $tag (sort keys %$struct) {
1140 my $tagInfo = $$struct{$tag};
1141 next unless ref $tagInfo eq 'HASH';
1142 my $writable = $$tagInfo{Writable};
1143 my @vals;
1144 unless ($writable) {
1145 $writable = $$tagInfo{Struct};
1146 ref $writable and $writable = $$writable{STRUCT_NAME};
1147 if ($writable) {
1148 push @vals, $writable;
1149 $structs{$writable} = 1;
1150 $writable = "=$writable";
1151 } else {
1152 $writable = 'string';
1153 }
1154 }
1155 $writable .= '+' if $$tagInfo{List};
1156 push @$info, [
1157 $tag,
1158 [ $$tagInfo{Name} || ucfirst($tag) ],
1159 [ $writable ],
1160 \@vals,
1161 [], []
1162 ];
1163 }
1164 }
1165 return $self;
1166}
1167
1168#------------------------------------------------------------------------------
1169# Rewrite this file to build the lookup tables
1170# Inputs: 0) BuildTagLookup object reference
1171# 1) output tag lookup module name (ie. 'lib/Image/ExifTool/TagLookup.pm')
1172# Returns: true on success
1173sub WriteTagLookup($$)
1174{
1175 local ($_, *INFILE, *OUTFILE);
1176 my ($self, $file) = @_;
1177 my $tagLookup = $self->{TAG_LOOKUP};
1178 my $tagExists = $self->{TAG_EXISTS};
1179 my $flattened = $self->{FLATTENED};
1180 my $tableWritable = $self->{TABLE_WRITABLE};
1181#
1182# open/create necessary files and transfer file headers
1183#
1184 my $tmpFile = "${file}_tmp";
1185 open(INFILE, $file) or warn("Can't open $file\n"), return 0;
1186 unless (open(OUTFILE, ">$tmpFile")) {
1187 warn "Can't create temporary file $tmpFile\n";
1188 close(INFILE);
1189 return 0;
1190 }
1191 my $success;
1192 while (<INFILE>) {
1193 print OUTFILE $_ or last;
1194 if (/^#\+{4} Begin/) {
1195 $success = 1;
1196 last;
1197 }
1198 }
1199 print OUTFILE "\n# list of tables containing writable tags\n";
1200 print OUTFILE "my \@tableList = (\n";
1201
1202#
1203# write table list
1204#
1205 my @tableNames = sort keys %allTables;
1206 my $tableName;
1207 my %wrNum; # translate from allTables index to writable tables index
1208 my $count = 0;
1209 my $num = 0;
1210 foreach $tableName (@tableNames) {
1211 if ($$tableWritable{$tableName}) {
1212 print OUTFILE "\t'$tableName',\n";
1213 $wrNum{$count} = $num++;
1214 }
1215 $count++;
1216 }
1217#
1218# write the tag lookup table
1219#
1220 my $tag;
1221 # verify that certain critical tag names aren't duplicated
1222 foreach $tag (qw{filename directory}) {
1223 next unless $$tagLookup{$tag};
1224 my $n = scalar keys %{$$tagLookup{$tag}};
1225 warn "Warning: $n writable '$tag' tags!\n" if $n > 1;
1226 }
1227 print OUTFILE ");\n\n# lookup for all writable tags\nmy \%tagLookup = (\n";
1228 foreach $tag (sort keys %$tagLookup) {
1229 print OUTFILE "\t'$tag' => { ";
1230 my @tableNums = sort { $a <=> $b } keys %{$$tagLookup{$tag}};
1231 my (@entries, $tableNum);
1232 foreach $tableNum (@tableNums) {
1233 my $tagID = $$tagLookup{$tag}{$tableNum};
1234 my $rootID = $$flattened{$tag}{$tableNum};
1235 my $entry;
1236 if (ref $tagID eq 'HASH' or $rootID) {
1237 $tagID = { $tagID => 1 } unless ref $tagID eq 'HASH';
1238 my @tagIDs = sort keys %$tagID;
1239 foreach (@tagIDs) {
1240 if (/^\d+$/) {
1241 $_ = sprintf('0x%x',$_);
1242 } else {
1243 my $quot = "'";
1244 # escape non-printable characters in tag ID if necessary
1245 $quot = '"' if s/[\x00-\x1f,\x7f-\xff]/sprintf('\\x%.2x',ord($&))/ge;
1246 $_ = $quot . $_ . $quot;
1247 }
1248 }
1249 # reference to root structure ID must come first in lookup
1250 # (so we can generate the flattened tags just before we need them)
1251 unshift @tagIDs, "\\'$rootID'" if $rootID;
1252 $entry = '[' . join(',', @tagIDs) . ']';
1253 } elsif ($tagID =~ /^\d+$/) {
1254 $entry = sprintf('0x%x',$tagID);
1255 } else {
1256 $entry = "'$tagID'";
1257 }
1258 my $wrNum = $wrNum{$tableNum};
1259 push @entries, "$wrNum => $entry";
1260 }
1261 print OUTFILE join(', ', @entries);
1262 print OUTFILE " },\n";
1263 }
1264#
1265# write tag exists lookup
1266#
1267 print OUTFILE ");\n\n# lookup for non-writable tags to check if the name exists\n";
1268 print OUTFILE "my \%tagExists = (\n";
1269 foreach $tag (sort keys %$tagExists) {
1270 next if $$tagLookup{$tag};
1271 print OUTFILE "\t'$tag' => 1,\n";
1272 }
1273#
1274# write module lookup for writable composite tags
1275#
1276 my $compositeModules = $self->{COMPOSITE_MODULES};
1277 print OUTFILE ");\n\n# module names for writable Composite tags\n";
1278 print OUTFILE "my \%compositeModules = (\n";
1279 foreach (sort keys %$compositeModules) {
1280 print OUTFILE "\t'$_' => '$$compositeModules{$_}',\n";
1281 }
1282 print OUTFILE ");\n\n";
1283#
1284# finish writing TagLookup.pm and clean up
1285#
1286 if ($success) {
1287 $success = 0;
1288 while (<INFILE>) {
1289 $success or /^#\+{4} End/ or next;
1290 print OUTFILE $_;
1291 $success = 1;
1292 }
1293 }
1294 close(INFILE);
1295 close(OUTFILE) or $success = 0;
1296#
1297# return success code
1298#
1299 if ($success) {
1300 local (*ORG, *TMP);
1301 # only rename the file if something changed
1302 open ORG, $file or return 0;
1303 open TMP, $tmpFile or return 0;
1304 my ($buff, $buf2, $changed);
1305 for (;;) {
1306 my $n1 = read ORG, $buff, 65536;
1307 my $n2 = read TMP, $buf2, 65536;
1308 $n1 eq $n2 or $changed = 1, last;
1309 last unless $n1;
1310 $buff eq $buf2 or $changed = 1, last;
1311 }
1312 close ORG;
1313 close TMP;
1314 if ($changed) {
1315 rename($tmpFile, $file) or warn("Error renaming $tmpFile\n"), $success = 0;
1316 } else {
1317 unlink($tmpFile);
1318 }
1319 } else {
1320 unlink($tmpFile);
1321 warn "Error rewriting file\n";
1322 }
1323 return $success;
1324}
1325
1326#------------------------------------------------------------------------------
1327# sort numbers first numerically, then strings alphabetically (case insensitive)
1328sub NumbersFirst
1329{
1330 my $rtnVal;
1331 my $bNum = ($b =~ /^-?[0-9]+(\.\d*)?$/);
1332 if ($a =~ /^-?[0-9]+(\.\d*)?$/) {
1333 $rtnVal = ($bNum ? $a <=> $b : -$numbersFirst);
1334 } elsif ($bNum) {
1335 $rtnVal = $numbersFirst;
1336 } else {
1337 my ($a2, $b2) = ($a, $b);
1338 # expand numbers to 3 digits (with restrictions to avoid messing up ascii-hex tags)
1339 $a2 =~ s/(\d+)/sprintf("%.3d",$1)/eg if $a2 =~ /^(APP)?[.0-9 ]*$/ and length($a2)<16;
1340 $b2 =~ s/(\d+)/sprintf("%.3d",$1)/eg if $b2 =~ /^(APP)?[.0-9 ]*$/ and length($b2)<16;
1341 $caseInsensitive and $rtnVal = (lc($a2) cmp lc($b2));
1342 $rtnVal or $rtnVal = ($a2 cmp $b2);
1343 }
1344 return $rtnVal;
1345}
1346
1347#------------------------------------------------------------------------------
1348# Convert pod documentation to pod
1349# (funny, I know, but the pod headings must be hidden to prevent confusing
1350# the pod parser)
1351# Inputs: 0-N) documentation strings
1352sub Doc2Pod($;@)
1353{
1354 my $doc = shift;
1355 local $_;
1356 $doc .= shift while @_;
1357 $doc =~ s/\n~/\n=/g;
1358 $doc =~ s/L<[^>]+?\|(http[^>]+)>/L<$1>/g; # POD doesn't support text for http links
1359 return $doc;
1360}
1361
1362#------------------------------------------------------------------------------
1363# Convert pod documentation to html
1364# Inputs: 0) string
1365sub Doc2Html($)
1366{
1367 my $doc = EscapeHTML(shift);
1368 $doc =~ s/\n\n/<\/p>\n\n<p>/g;
1369 $doc =~ s/B&lt;(.*?)&gt;/<b>$1<\/b>/sg;
1370 $doc =~ s/C&lt;(.*?)&gt;/<code>$1<\/code>/sg;
1371 $doc =~ s/I&lt;(.*?)&gt;/<i>$1<\/i>/sg;
1372 $doc =~ s{L&lt;([^&]+?)\|\Q$homePage\E/TagNames/(.*?)&gt;}{<a href="$2">$1<\/a>}sg;
1373 $doc =~ s{L&lt;([^&]+?)\|\Q$homePage\E/(.*?)&gt;}{<a href="../$2">$1<\/a>}sg;
1374 $doc =~ s{L&lt;\Q$homePage\E/TagNames/(.*?)&gt;}{<a href="$1">$1<\/a>}sg;
1375 $doc =~ s{L&lt;\Q$homePage\E/(.*?)&gt;}{<a href="../$1">$1<\/a>}sg;
1376 $doc =~ s{L&lt;([^&]+?)\|/\w+ ([^/&|]+) Tags&gt;}{<a href="#$2">$1</a>}sg;
1377 $doc =~ s/L&lt;([^&]+?)\|(.+?)&gt;/<a href="$2">$1<\/a>/sg;
1378 $doc =~ s/L&lt;(.*?)&gt;/<a href="$1">$1<\/a>/sg;
1379 return $doc;
1380}
1381
1382#------------------------------------------------------------------------------
1383# Get the order that we want to print the tables in the documentation
1384# Returns: tables in the order we want
1385sub GetTableOrder()
1386{
1387 my %gotTable;
1388 my @tableNames = @tableOrder;
1389 my (@orderedTables, %mainTables, @outOfOrder);
1390 my $lastTable = '';
1391
1392 while (@tableNames) {
1393 my $tableName = shift @tableNames;
1394 next if $gotTable{$tableName};
1395 if ($tableName =~ /^Image::ExifTool::(\w+)::Main/) {
1396 $mainTables{$1} = 1;
1397 } elsif ($lastTable and not $tableName =~ /^${lastTable}::/) {
1398 push @outOfOrder, $tableName;
1399 }
1400 ($lastTable) = ($tableName =~ /^(Image::ExifTool::\w+)/);
1401 push @orderedTables, $tableName;
1402 $gotTable{$tableName} = 1;
1403 my $table = GetTagTable($tableName);
1404 # recursively scan through tables in subdirectories
1405 my @moreTables;
1406 $caseInsensitive = ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP');
1407 $numbersFirst = -1 if $$table{VARS} and $$table{VARS}{ALPHA_FIRST};
1408 my @keys = sort NumbersFirst TagTableKeys($table);
1409 $numbersFirst = 1;
1410 foreach (@keys) {
1411 my @infoArray = GetTagInfoList($table,$_);
1412 my $tagInfo;
1413 foreach $tagInfo (@infoArray) {
1414 my $subdir = $$tagInfo{SubDirectory} or next;
1415 $tableName = $$subdir{TagTable} or next;
1416 next if $gotTable{$tableName}; # next if table already loaded
1417 push @moreTables, $tableName; # must scan this one too
1418 }
1419 }
1420 unshift @tableNames, @moreTables;
1421 }
1422 # clean up the order for tables which are out of order
1423 # (groups all Canon and Kodak tables together)
1424 my %fixOrder;
1425 foreach (@outOfOrder) {
1426 next unless /^Image::ExifTool::(\w+)/;
1427 # only re-order tables which have a corresponding main table
1428 next unless $mainTables{$1};
1429 $fixOrder{$1} = []; # fix the order of these tables
1430 }
1431 my (@sortedTables, %fixPos, $pos);
1432 foreach (@orderedTables) {
1433 if (/^Image::ExifTool::(\w+)/ and $fixOrder{$1}) {
1434 my $fix = $fixOrder{$1};
1435 unless (@$fix) {
1436 $pos = @sortedTables;
1437 $fixPos{$pos} or $fixPos{$pos} = [];
1438 push @{$fixPos{$pos}}, $1;
1439 }
1440 push @{$fix}, $_;
1441 } else {
1442 push @sortedTables, $_;
1443 }
1444 }
1445 # insert back in better order
1446 foreach $pos (sort { $b <=> $a } keys %fixPos) { # (reverse sort)
1447 my $fix = $fixPos{$pos};
1448 foreach (@$fix) {
1449 splice(@sortedTables, $pos, 0, @{$fixOrder{$_}});
1450 }
1451 }
1452 # tweak the table order
1453 my %tweakOrder = (
1454 JPEG => '-', # JPEG comes first
1455 IPTC => 'Exif', # put IPTC after EXIF,
1456 GPS => 'XMP', # etc...
1457 GeoTiff => 'GPS',
1458 CanonVRD=> 'CanonCustom',
1459 Kodak => 'JVC',
1460 'Kodak::IFD' => 'Kodak::Unknown',
1461 'Kodak::TextualInfo' => 'Kodak::IFD',
1462 'Kodak::Processing' => 'Kodak::TextualInfo',
1463 Leaf => 'Kodak',
1464 Minolta => 'Leaf',
1465 SonyIDC => 'Sony',
1466 Unknown => 'SonyIDC',
1467 DNG => 'Unknown',
1468 PrintIM => 'ICC_Profile',
1469 ID3 => 'PostScript',
1470 MinoltaRaw => 'KyoceraRaw',
1471 KyoceraRaw => 'CanonRaw',
1472 SigmaRaw => 'PanasonicRaw',
1473 Olympus => 'NikonCapture',
1474 PhotoMechanic => 'FotoStation',
1475 Microsoft => 'PhotoMechanic',
1476 'Microsoft::MP'=> 'Microsoft::MP1',
1477 GIMP => 'Microsoft',
1478 'Nikon::CameraSettingsD300' => 'Nikon::ShotInfoD300b',
1479 'Pentax::LensData' => 'Pentax::LensInfo2',
1480 'Sony::SRF2' => 'Sony::SRF',
1481 'Samsung::INFO' => 'Samsung::Type2', # (necessary because Samsung doesn't have a main table)
1482 'Samsung::MP4' => 'Samsung::INFO', # (necessary because Samsung doesn't have a main table)
1483 );
1484 my @tweak = sort keys %tweakOrder;
1485 while (@tweak) {
1486 my $table = shift @tweak;
1487 my $first = $tweakOrder{$table};
1488 if ($tweakOrder{$first}) {
1489 push @tweak, $table; # must defer this till later
1490 next;
1491 }
1492 delete $tweakOrder{$table}; # because the table won't move again
1493 my @moving = grep /^Image::ExifTool::$table\b/, @sortedTables;
1494 my @notMoving = grep !/^Image::ExifTool::$table\b/, @sortedTables;
1495 my @after;
1496 while (@notMoving) {
1497 last if $notMoving[-1] =~ /^Image::ExifTool::$first\b/;
1498 unshift @after, pop @notMoving;
1499 }
1500 @sortedTables = (@notMoving, @moving, @after);
1501 }
1502 return @sortedTables
1503}
1504
1505#------------------------------------------------------------------------------
1506# Open HTMLFILE and print header and description
1507# Inputs: 0) Filename, 1) optional category
1508# Returns: True on success
1509my %createdFiles;
1510sub OpenHtmlFile($;$$)
1511{
1512 my ($htmldir, $category, $sepTable) = @_;
1513 my ($htmlFile, $head, $title, $url, $class);
1514 my $top = '';
1515
1516 if ($category) {
1517 my @names = split ' ', $category;
1518 $class = shift @names;
1519 $htmlFile = "$htmldir/TagNames/$class.html";
1520 $head = $category;
1521 if ($head =~ /^XMP .+ Struct$/) {
1522 pop @names;
1523 } else {
1524 $head .= ($sepTable ? ' Values' : ' Tags');
1525 }
1526 ($title = $head) =~ s/ .* / /;
1527 @names and $url = join '_', @names;
1528 } else {
1529 $htmlFile = "$htmldir/TagNames/index.html";
1530 $category = $class = 'ExifTool';
1531 $head = $title = 'ExifTool Tag Names';
1532 }
1533 if ($createdFiles{$htmlFile}) {
1534 open(HTMLFILE, ">>${htmlFile}_tmp") or return 0;
1535 } else {
1536 open(HTMLFILE, ">${htmlFile}_tmp") or return 0;
1537 print HTMLFILE "$docType<html>\n<head>\n<title>$title</title>\n";
1538 print HTMLFILE "<link rel=stylesheet type='text/css' href='style.css' title='Style'>\n";
1539 print HTMLFILE "</head>\n<body>\n";
1540 if ($category ne $class and $docs{$class}) {
1541 print HTMLFILE "<h2 class=top>$class Tags</h2>\n" or return 0;
1542 print HTMLFILE '<p>',Doc2Html($docs{$class}),"</p>\n" or return 0;
1543 } else {
1544 $top = " class=top";
1545 }
1546 }
1547 $head = "<a name='$url'>$head</a>" if $url;
1548 print HTMLFILE "<h2$top>$head</h2>\n" or return 0;
1549 print HTMLFILE '<p>',Doc2Html($docs{$category}),"</p>\n" if $docs{$category};
1550 $createdFiles{$htmlFile} = 1;
1551 return 1;
1552}
1553
1554#------------------------------------------------------------------------------
1555# Close all html files and write trailers
1556# Returns: true on success
1557# Inputs: 0) BuildTagLookup object reference
1558sub CloseHtmlFiles($)
1559{
1560 my $self = shift;
1561 my $preserveDate = $$self{PRESERVE_DATE};
1562 my $success = 1;
1563 # get the date
1564 my ($sec,$min,$hr,$day,$mon,$yr) = localtime;
1565 my @month = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
1566 $yr += 1900;
1567 my $date = "$month[$mon] $day, $yr";
1568 my $htmlFile;
1569 my $countNewFiles = 0;
1570 my $countSameFiles = 0;
1571 foreach $htmlFile (keys %createdFiles) {
1572 my $tmpFile = $htmlFile . '_tmp';
1573 my $fileDate = $date;
1574 if ($preserveDate) {
1575 my @lines = `grep '<i>Last revised' $htmlFile`;
1576 $fileDate = $1 if @lines and $lines[-1] =~ m{<i>Last revised (.*)</i>};
1577 }
1578 open(HTMLFILE, ">>$tmpFile") or $success = 0, next;
1579 # write the trailers
1580 print HTMLFILE "<hr>\n";
1581 print HTMLFILE "(This document generated automatically by Image::ExifTool::BuildTagLookup)\n";
1582 print HTMLFILE "<br><i>Last revised $fileDate</i>\n";
1583 print HTMLFILE "<p class=lf><a href=";
1584 if ($htmlFile =~ /index\.html$/) {
1585 print HTMLFILE "'../index.html'>&lt;-- Back to ExifTool home page</a></p>\n";
1586 } else {
1587 print HTMLFILE "'index.html'>&lt;-- ExifTool Tag Names</a></p>\n"
1588 }
1589 print HTMLFILE "</body>\n</html>\n" or $success = 0;
1590 close HTMLFILE or $success = 0;
1591 # check for differences and only use new file if it was changed
1592 # (so the date only gets updated if changes were really made)
1593 my $useNewFile;
1594 if ($success) {
1595 open (TEMPFILE, $tmpFile) or $success = 0, last;
1596 if (open (HTMLFILE, $htmlFile)) {
1597 while (<HTMLFILE>) {
1598 my $newLine = <TEMPFILE>;
1599 if (defined $newLine) {
1600 next if /^<br><i>Last revised/;
1601 next if $_ eq $newLine;
1602 }
1603 # files are different -- use the new file
1604 $useNewFile = 1;
1605 last;
1606 }
1607 $useNewFile = 1 if <TEMPFILE>;
1608 close HTMLFILE;
1609 } else {
1610 $useNewFile = 1;
1611 }
1612 close TEMPFILE;
1613 if ($useNewFile) {
1614 ++$countNewFiles;
1615 rename $tmpFile, $htmlFile or warn("Error renaming temporary file\n"), $success = 0;
1616 } else {
1617 ++$countSameFiles;
1618 unlink $tmpFile; # erase new file and use existing file
1619 }
1620 }
1621 last unless $success;
1622 }
1623 # save number of files processed so we can check the results later
1624 $self->{COUNT}->{'HTML files changed'} = $countNewFiles;
1625 $self->{COUNT}->{'HTML files unchanged'} = $countSameFiles;
1626 return $success;
1627}
1628
1629#------------------------------------------------------------------------------
1630# Write the TagName HTML and POD documentation
1631# Inputs: 0) BuildTagLookup object reference
1632# 1) output pod file (ie. 'lib/Image/ExifTool/TagNames.pod')
1633# 2) output html directory (ie. 'html')
1634# Returns: true on success
1635# Notes: My apologies for the patchwork code, but this is only used to generate the docs.
1636sub WriteTagNames($$)
1637{
1638 my ($self, $podFile, $htmldir) = @_;
1639 my ($tableName, $short, $url, @sepTables, @structs);
1640 my $tagNameInfo = $self->{TAG_NAME_INFO} or return 0;
1641 my $idLabel = $self->{ID_LOOKUP};
1642 my $shortName = $self->{SHORT_NAME};
1643 my $sepTable = $self->{SEPARATE_TABLE};
1644 my $structs = $self->{STRUCTURES};
1645 my $structLookup = $self->{STRUCT_LOOKUP};
1646 my $success = 1;
1647 my $columns = 6; # number of columns in html index
1648 my $percent = int(100 / $columns);
1649
1650 # open the file and write the header
1651 open(PODFILE, ">$podFile") or return 0;
1652 print PODFILE Doc2Pod($docs{PodHeader}, $docs{ExifTool}, $docs{ExifTool2});
1653 mkdir "$htmldir/TagNames";
1654 OpenHtmlFile($htmldir) or return 0;
1655 print HTMLFILE "<blockquote>\n";
1656 print HTMLFILE "<table width='100%' class=frame><tr><td>\n";
1657 print HTMLFILE "<table width='100%' class=inner cellspacing=1><tr class=h>\n";
1658 print HTMLFILE "<th colspan=$columns><span class=l>Tag Table Index</span></th></tr>\n";
1659 print HTMLFILE "<tr class=b><td width='$percent%'>\n";
1660 # write the index
1661 my @tableNames = GetTableOrder();
1662 # add shortcuts last
1663 push @tableNames, 'Image::ExifTool::Shortcuts::Main';
1664 push @tableNames, @pluginTables;
1665 # get list of headings and add any missing ones
1666 my $heading = 'xxx';
1667 my (@tableIndexNames, @headings);
1668 foreach $tableName (@tableNames) {
1669 $short = $$shortName{$tableName};
1670 my @names = split ' ', $short;
1671 my $class = shift @names;
1672 if (@names) {
1673 # add heading for tables without a Main
1674 unless ($heading eq $class) {
1675 $heading = $class;
1676 push @tableIndexNames, $heading;
1677 push @headings, $heading;
1678 }
1679 } else {
1680 $heading = $short;
1681 push @headings, $heading;
1682 }
1683 push @tableIndexNames, $tableName;
1684 }
1685 @tableNames = @tableIndexNames;
1686 # print html index of headings only
1687 my $count = 0;
1688 my $lines = int((scalar(@headings) + $columns - 1) / $columns);
1689 foreach $tableName (@headings) {
1690 if ($count) {
1691 if ($count % $lines) {
1692 print HTMLFILE "<br>\n";
1693 } else {
1694 print HTMLFILE "</td><td width='$percent%'>\n";
1695 }
1696 }
1697 $short = $$shortName{$tableName};
1698 $short = $tableName unless $short;
1699 $url = "$short.html";
1700 print HTMLFILE "<a href='$url'>$short</a>";
1701 ++$count;
1702 }
1703 print HTMLFILE "\n</td></tr></table></td></tr></table></blockquote>\n";
1704 print HTMLFILE '<p>',Doc2Html($docs{ExifTool2}),"</p>\n";
1705 # write all the tag tables
1706 while (@tableNames or @sepTables or @structs) {
1707 while (@sepTables) {
1708 $tableName = shift @sepTables;
1709 my $printConv = $$sepTable{$tableName};
1710 next unless ref $printConv eq 'HASH';
1711 $$sepTable{$tableName} = 1;
1712 my $notes = $$printConv{Notes};
1713 if ($notes) {
1714 # remove unnecessary whitespace
1715 $notes =~ s/(^\s+|\s+$)//g;
1716 $notes =~ s/(^[ \t]+|[ \t]+$)//mg;
1717 }
1718 my $head = $tableName;
1719 $head =~ s/.* //;
1720 close HTMLFILE;
1721 if (OpenHtmlFile($htmldir, $tableName, 1)) {
1722 print HTMLFILE '<p>', Doc2Html($notes), "</p>\n" if $notes;
1723 print HTMLFILE "<blockquote>\n";
1724 print HTMLFILE "<table class=frame><tr><td>\n";
1725 print HTMLFILE "<table class='inner sep' cellspacing=1>\n";
1726 my $align = ' class=r';
1727 my $wid = 0;
1728 my @keys;
1729 foreach (sort NumbersFirst keys %$printConv) {
1730 next if /^(Notes|PrintHex|PrintString|OTHER)$/;
1731 $align = '' if $align and /[^\d]/;
1732 my $w = length($_) + length($$printConv{$_});
1733 $wid = $w if $wid < $w;
1734 push @keys, $_;
1735 }
1736 $wid = length($tableName)+7 if $wid < length($tableName)+7;
1737 # print in multiple columns if there is room
1738 my $cols = int(80 / ($wid + 4));
1739 $cols = 1 if $cols < 1 or $cols > @keys or @keys < 4;
1740 my $rows = int((scalar(@keys) + $cols - 1) / $cols);
1741 my ($r, $c);
1742 print HTMLFILE '<tr class=h>';
1743 for ($c=0; $c<$cols; ++$c) {
1744 print HTMLFILE "<th>Value</th><th>$head</th>";
1745 }
1746 print HTMLFILE "</tr>\n";
1747 for ($r=0; $r<$rows; ++$r) {
1748 print HTMLFILE '<tr>';
1749 for ($c=0; $c<$cols; ++$c) {
1750 my $key = $keys[$r + $c*$rows];
1751 my ($index, $prt);
1752 if (defined $key) {
1753 $index = $key;
1754 $prt = '= ' . EscapeHTML($$printConv{$key});
1755 if ($$printConv{PrintHex}) {
1756 $index = sprintf('0x%x',$index);
1757 } elsif ($$printConv{PrintString} or
1758 $index !~ /^[+-]?(?=\d|\.\d)\d*(\.\d*)?$/)
1759 {
1760 $index = "'" . EscapeHTML($index) . "'";
1761 }
1762 } else {
1763 $index = $prt = '&nbsp;';
1764 }
1765 my ($ic, $pc);
1766 if ($c & 0x01) {
1767 $pc = ' class=b';
1768 $ic = $align ? " class='r b'" : $pc;
1769 } else {
1770 $ic = $align;
1771 $pc = '';
1772 }
1773 print HTMLFILE "<td$ic>$index</td><td$pc>$prt</td>\n";
1774 }
1775 print HTMLFILE '</tr>';
1776 }
1777 print HTMLFILE "</table></td></tr></table></blockquote>\n\n";
1778 }
1779 }
1780 last unless @tableNames or @structs;
1781 my $isStruct;
1782 if (@structs) {
1783 $tableName = shift @structs;
1784 next if $$structs{$tableName} == 2; # only list each structure once
1785 $$structs{$tableName} = 2;
1786 $isStruct = $$structLookup{$tableName};
1787 $isStruct or warn("Missing structure $tableName\n"), next;
1788 $short = $tableName = "XMP $tableName Struct";
1789 my $maxLen = 0;
1790 $maxLen < length and $maxLen = length foreach keys %$isStruct;
1791 $$self{LONG_ID}{$tableName} = $maxLen;
1792 } else {
1793 $tableName = shift @tableNames;
1794 $short = $$shortName{$tableName};
1795 unless ($short) {
1796 # this is just an index heading
1797 print PODFILE "\n=head2 $tableName Tags\n";
1798 print PODFILE Doc2Pod($docs{$tableName}) if $docs{$tableName};
1799 next;
1800 }
1801 }
1802 my $isExif = $tableName eq 'Image::ExifTool::Exif::Main' ? 1 : undef;
1803 my $isRiff = $tableName eq 'Image::ExifTool::RIFF::Info' ? 1 : undef;
1804 my $info = $$tagNameInfo{$tableName};
1805 my $id = $$idLabel{$tableName};
1806 my ($hid, $showGrp);
1807 # widths of the different columns in the POD documentation
1808 my ($wID,$wTag,$wReq,$wGrp) = (8,36,24,10);
1809 my ($composite, $derived, $notes, $prefix);
1810 if ($short eq 'Shortcuts') {
1811 $derived = '<th>Refers To</th>';
1812 $composite = 2;
1813 } elsif ($isStruct) {
1814 $derived = '';
1815 $notes = $$isStruct{NOTES};
1816 } else {
1817 my $table = GetTagTable($tableName);
1818 $notes = $$table{NOTES};
1819 if ($$table{GROUPS}{0} eq 'Composite') {
1820 $composite = 1;
1821 $derived = '<th>Derived From</th>';
1822 } else {
1823 $composite = 0;
1824 $derived = '';
1825 }
1826 }
1827 my $podIdLen = $self->{LONG_ID}->{$tableName};
1828 if ($notes) {
1829 # remove unnecessary whitespace
1830 $notes =~ s/(^\s+|\s+$)//g;
1831 $notes =~ s/(^[ \t]+|[ \t]+$)//mg;
1832 if ($notes =~ /leading '(.*?_)' which/) {
1833 $prefix = $1;
1834 $podIdLen -= length $prefix;
1835 }
1836 }
1837 if ($podIdLen <= $wID) {
1838 $podIdLen = $wID;
1839 } elsif ($short eq 'DICOM') {
1840 $podIdLen = 10;
1841 } else {
1842 # align tag names in secondary columns if possible
1843 my $col = ($podIdLen <= 10) ? 12 : 20;
1844 $podIdLen = $col if $podIdLen < $col;
1845 }
1846 if ($id) {
1847 ($hid = "<th>$id</th>") =~ s/ /&nbsp;/g;
1848 $wTag -= $podIdLen - $wID;
1849 $wID = $podIdLen;
1850 my $longTag = $self->{LONG_NAME}->{$tableName};
1851 if ($wTag < $longTag) {
1852 warn "Notice: Long tags in $tableName table\n";
1853 if ($wID - $longTag + $wTag >= 6) { # don't let ID column get too narrow
1854 $wID -= $longTag - $wTag;
1855 $wTag = $longTag;
1856 }
1857 }
1858 } elsif ($composite) {
1859 $wTag += $wID - $wReq;
1860 $hid = '';
1861 } else {
1862 $wTag += 9;
1863 $hid = '';
1864 }
1865 if ($short eq 'EXIF') {
1866 $derived = '<th>Group</th>';
1867 $showGrp = 1;
1868 $wTag -= $wGrp + 1;
1869 }
1870 my $head = ($short =~ / /) ? 'head3' : 'head2';
1871 my $str = $isStruct ? '' : ' Tags';
1872 print PODFILE "\n=$head $short$str\n";
1873 print PODFILE Doc2Pod($docs{$short}) if $docs{$short};
1874 print PODFILE "\n", Doc2Pod($notes), "\n" if $notes;
1875 my $line = "\n";
1876 if ($id) {
1877 # shift over 'Index' heading by one character for a bit more balance
1878 $id = " $id" if $id eq 'Index';
1879 $line .= sprintf " %-${wID}s", $id;
1880 } else {
1881 $line .= ' ';
1882 }
1883 my $tagNameHeading = ($short eq 'XMP') ? 'Namespace' : ($isStruct?'Field':'Tag').' Name';
1884 $line .= sprintf " %-${wTag}s", $tagNameHeading;
1885 $line .= sprintf " %-${wReq}s", $composite == 2 ? 'Refers To' : 'Derived From' if $composite;
1886 $line .= sprintf " %-${wGrp}s", 'Group' if $showGrp;
1887 $line .= ' Writable';
1888 print PODFILE $line;
1889 $line =~ s/^(\s*\w.{6}\w) /$1\t/; # change space to tab after long ID label (ie. "Sequence")
1890 $line =~ s/\S/-/g;
1891 $line =~ s/- -/---/g;
1892 $line =~ tr/\t/ /; # change tab back to space
1893 print PODFILE $line,"\n";
1894 close HTMLFILE;
1895 OpenHtmlFile($htmldir, $short) or $success = 0;
1896 print HTMLFILE '<p>',Doc2Html($notes), "</p>\n" if $notes;
1897 print HTMLFILE "<blockquote>\n";
1898 print HTMLFILE "<table class=frame><tr><td>\n";
1899 print HTMLFILE "<table class=inner cellspacing=1>\n";
1900 print HTMLFILE "<tr class=h>$hid<th>$tagNameHeading</th>\n";
1901 print HTMLFILE "<th>Writable</th>$derived<th>Values / ${noteFont}Notes</span></th></tr>\n";
1902 my $rowClass = 1;
1903 my $infoCount = 0;
1904 my $infoList;
1905 foreach $infoList (@$info) {
1906 ++$infoCount;
1907 my ($tagIDstr, $tagNames, $writable, $values, $require, $writeGroup) = @$infoList;
1908 my ($align, $idStr, $w, $tip);
1909 my $wTag2 = $wTag;
1910 if (not $id) {
1911 $idStr = ' ';
1912 } elsif ($tagIDstr =~ /^\d+(\.\d+)?$/) {
1913 $w = $wID - 3;
1914 $idStr = sprintf " %${w}g ", $tagIDstr;
1915 $align = " class=r";
1916 } else {
1917 $tagIDstr =~ s/^'$prefix/'/ if $prefix;
1918 $w = $wID;
1919 my $over = length($tagIDstr) - $w;
1920 if ($over > 0) {
1921 # shift over tag name if there is room
1922 if ($over <= $wTag - length($$tagNames[0])) {
1923 $wTag2 -= $over;
1924 $w += $over;
1925 } else {
1926 # put tag name on next line if ID is too long
1927 $idStr = " $tagIDstr\n " . (' ' x $w);
1928 warn "Notice: Split $$tagNames[0] line\n";
1929 }
1930 }
1931 $idStr = sprintf " %-${w}s ", $tagIDstr unless defined $idStr;
1932 $align = '';
1933 }
1934 my @reqs;
1935 my @tags = @$tagNames;
1936 my @wGrp = @$writeGroup;
1937 my @vals = @$writable;
1938 my $wrStr = shift @vals;
1939 my $subdir;
1940 my @masks = grep /^\[Mask 0x[\da-f]+\]/, @$values;
1941 my $tag = shift @tags;
1942 # if this is a subdirectory or structure, print subdir name (from values) instead of writable
1943 if ($wrStr =~ /^[-=]/) {
1944 $subdir = 1;
1945 if (@masks) {
1946 # combine any mask into the format string
1947 $wrStr .= " & $1" if $masks[0] =~ /(0x[\da-f]+)/;
1948 shift @masks;
1949 @vals = grep !/^\[Mask 0x[\da-f]+\]/, @$values;
1950 } else {
1951 @vals = @$values;
1952 }
1953 # remove Notes if subdir has Notes as well
1954 shift @vals if $vals[0] =~ /^\(/ and @vals >= @$writable;
1955 foreach (@vals) { /^\(/ and $_ = '-' }
1956 my $i; # fill in any missing entries from non-directory tags
1957 for ($i=0; $i<@$writable; ++$i) {
1958 $vals[$i] = $$writable[$i] unless defined $vals[$i];
1959 if (@masks) {
1960 $vals[$i] .= " & $1" if $masks[0] =~ /(0x[\da-f]+)/;
1961 shift @masks;
1962 }
1963 }
1964 if ($$sepTable{$vals[0]}) {
1965 $wrStr =~ s/^[-=]//;
1966 $wrStr = 'N' unless $wrStr;
1967 } elsif ($$structs{$vals[0]}) {
1968 my $flags = $wrStr =~ /([+_]+)$/ ? $1 : '';
1969 $wrStr = "$vals[0] Struct$flags";
1970 } else {
1971 $wrStr = $vals[0];
1972 }
1973 shift @vals;
1974 } elsif ($wrStr and $wrStr ne 'N' and @masks) {
1975 # fill in missing entries if masks are different
1976 my $mask = shift @masks;
1977 while (@masks > @vals) {
1978 last if $masks[@vals] eq $mask;
1979 push @vals, $wrStr;
1980 push @tags, $tag if @tags < @vals;
1981 }
1982 # add Mask to Writable column in POD doc
1983 $wrStr .= " & $1" if $mask =~ /(0x[\da-f]+)/;
1984 }
1985 printf PODFILE "%s%-${wTag2}s", $idStr, $tag;
1986 warn "Warning: Pushed $tag\n" if $id and length($tag) > $wTag2;
1987 printf PODFILE " %-${wGrp}s", shift(@wGrp) || '-' if $showGrp;
1988 if ($composite) {
1989 @reqs = @$require;
1990 $w = $wReq; # Keep writable column in line
1991 length($tag) > $wTag2 and $w -= length($tag) - $wTag2;
1992 printf PODFILE " %-${w}s", shift(@reqs) || '';
1993 }
1994 printf PODFILE " $wrStr\n";
1995 my $numTags = scalar @$tagNames;
1996 my $n = 0;
1997 while (@tags or @reqs or @vals) {
1998 my $more = (@tags or @reqs);
1999 $line = ' ';
2000 $line .= ' 'x($wID+1) if $id;
2001 $line .= sprintf("%-${wTag2}s", shift(@tags) || '');
2002 $line .= sprintf(" %-${wReq}s", shift(@reqs) || '') if $composite;
2003 $line .= sprintf(" %-${wGrp}s", shift(@wGrp) || '-') if $showGrp;
2004 ++$n;
2005 if (@vals) {
2006 my $val = shift @vals;
2007 # use writable if this is a note
2008 my $wrStr = $$writable[$n];
2009 if ($subdir and ($val =~ /^\(/ or $val =~ /=/ or ($wrStr and $wrStr !~ /^[-=]/))) {
2010 $val = $wrStr;
2011 if (defined $val) {
2012 $val =~ s/^[-=]//;
2013 } else {
2014 # done with tag if nothing else to print
2015 last unless $more;
2016 }
2017 }
2018 if (defined $val) {
2019 $line .= " $val";
2020 if (@masks) {
2021 $line .= " & $1" if $masks[0] =~ /(0x[\da-f]+)/;
2022 shift @masks;
2023 }
2024 }
2025 }
2026 $line =~ s/\s+$//; # trim trailing white space
2027 print PODFILE "$line\n";
2028 }
2029 my @htmlTags;
2030 foreach (@$tagNames) {
2031 push @htmlTags, EscapeHTML($_);
2032 }
2033 if (($isExif and $exifSpec{hex $tagIDstr}) or
2034 ($isRiff and $tagIDstr=~/(\w+)/ and $riffSpec{$1}))
2035 {
2036 # underline "unknown" makernote tags only
2037 my $n = $tagIDstr eq '0x927c' ? -1 : 0;
2038 $htmlTags[$n] = "<u>$htmlTags[$n]</u>";
2039 }
2040 $rowClass = $rowClass ? '' : " class=b";
2041 my $isSubdir;
2042 if ($$writable[0] =~ /^[-=]/) {
2043 $isSubdir = 1;
2044 s/^[-=](.+)/$1/ foreach @$writable;
2045 }
2046 # add tooltip for hex conversion of Tag ID
2047 if ($tagIDstr =~ /^0x[0-9a-f]+$/i) {
2048 $tip = sprintf(" title='$tagIDstr = %u'",hex $tagIDstr);
2049 } elsif ($tagIDstr =~ /^(\d+)(\.\d*)?$/) {
2050 $tip = sprintf(" title='%u = 0x%x'", $1, $1);
2051 } else {
2052 $tip = '';
2053 # use copyright symbol in QuickTime UserData tags
2054 $tagIDstr =~ s/^"\\xa9/"&copy;/;
2055 }
2056 # add tooltip for special writable attributes
2057 my $wtip = '';
2058 my %wattr = (
2059 '_' => 'Flattened',
2060 '+' => 'List',
2061 '/' => 'Avoided',
2062 '~' => 'Writable only with -n',
2063 '!' => 'Unsafe',
2064 '*' => 'Protected',
2065 ':' => 'Mandatory',
2066 );
2067 my ($wstr, %hasAttr, @hasAttr);
2068 foreach $wstr (@$writable) {
2069 next unless $wstr =~ m{([+/~!*:_]+)$};
2070 my @a = split //, $1;
2071 foreach (@a) {
2072 next if $hasAttr{$_};
2073 push @hasAttr, $_;
2074 $hasAttr{$_} = 1;
2075 }
2076 }
2077 if (@hasAttr) {
2078 $wtip = " title='";
2079 my $n = 0;
2080 foreach (@hasAttr) {
2081 $wtip .= "\n" if $n;
2082 $wtip .= " $_ = $wattr{$_}";
2083 ++$n;
2084 }
2085 $wtip .= "'";
2086 }
2087 # print this row in the tag table
2088 print HTMLFILE "<tr$rowClass>\n";
2089 print HTMLFILE "<td$align$tip>$tagIDstr</td>\n" if $id;
2090 print HTMLFILE "<td>", join("\n <br>",@htmlTags), "</td>\n";
2091 print HTMLFILE "<td class=c$wtip>",join('<br>',@$writable),"</td>\n";
2092 print HTMLFILE '<td class=n>',join("\n <br>",@$require),"</td>\n" if $composite;
2093 print HTMLFILE "<td class=c>",join('<br>',@$writeGroup),"</td>\n" if $showGrp;
2094 print HTMLFILE "<td>";
2095 if (@$values) {
2096 if ($isSubdir) {
2097 my ($smallNote, @values);
2098 foreach (@$values) {
2099 if (/^\(/) {
2100 # set the note font
2101 $smallNote = 1 if $numTags < 2;
2102 push @values, ($smallNote ? $noteFontSmall : $noteFont) . "$_</span>";
2103 next;
2104 }
2105 # make text in square brackets small
2106 /^\[/ and push(@values, "<span class=s>$_</span>"), next;
2107 /=/ and push(@values, $_), next;
2108 my @names = split;
2109 my $suffix = ' Tags';
2110 if ($$sepTable{$_}) {
2111 push @sepTables, $_;
2112 $suffix = ' Values';
2113 }
2114 # currently all structures are in XMP documentation
2115 if ($$structs{$_} and $short =~ /^XMP/) {
2116 unshift @names, 'XMP';
2117 push @structs, $_; # list this later
2118 $suffix = ' Struct';
2119 }
2120 $url = (shift @names) . '.html';
2121 @names and $url .= '#' . join '_', @names;
2122 push @values, "--&gt; <a href='$url'>$_$suffix</a>";
2123 }
2124 # put small note last
2125 $smallNote and push @values, shift @values;
2126 print HTMLFILE join("\n <br>",@values);
2127 } else {
2128 my ($close, $br) = ('', '');
2129 foreach (@$values) {
2130 if (s/^\[!HTML\]//) {
2131 print HTMLFILE $close if $close;
2132 print HTMLFILE $_;
2133 $close = $br = '';
2134 } else {
2135 if (/^\(/) {
2136 # notes can use POD syntax
2137 $_ = $noteFont . Doc2Html($_) . "</span>";
2138 } else {
2139 $_ = EscapeHTML($_);
2140 }
2141 $close or $_ = "<span class=s>$_", $close = '</span>';
2142 print HTMLFILE $br, $_;
2143 $br = "\n <br>";
2144 }
2145 }
2146 print HTMLFILE $close if $close;
2147 }
2148 } else {
2149 print HTMLFILE '&nbsp;';
2150 }
2151 print HTMLFILE "</td></tr>\n";
2152 }
2153 unless ($infoCount) {
2154 printf PODFILE " [no tags known]\n";
2155 my $cols = 3;
2156 ++$cols if $hid;
2157 ++$cols if $derived;
2158 print HTMLFILE "<tr><td colspan=$cols class=c><i>[no tags known]</i></td></tr>\n";
2159 }
2160 print HTMLFILE "</table></td></tr></table></blockquote>\n\n";
2161 }
2162 close(HTMLFILE) or $success = 0;
2163 CloseHtmlFiles($self) or $success = 0;
2164 print PODFILE Doc2Pod($docs{PodTrailer}) or $success = 0;
2165 close(PODFILE) or $success = 0;
2166 return $success;
2167}
2168
21691; # end
2170
2171
2172__END__
2173
2174=head1 NAME
2175
2176Image::ExifTool::BuildTagLookup - Build ExifTool tag lookup tables
2177
2178=head1 DESCRIPTION
2179
2180This module is used to generate the tag lookup tables in
2181Image::ExifTool::TagLookup.pm and tag name documentation in
2182Image::ExifTool::TagNames.pod, as well as HTML tag name documentation. It
2183is used before each new ExifTool release to update the lookup tables and
2184documentation, but it is not used otherwise.
2185
2186=head1 SYNOPSIS
2187
2188 use Image::ExifTool::BuildTagLookup;
2189
2190 $builder = new Image::ExifTool::BuildTagLookup;
2191
2192 $ok = $builder->WriteTagLookup('lib/Image/ExifTool/TagLookup.pm');
2193
2194 $ok = $builder->WriteTagNames('lib/Image/ExifTool/TagNames.pod','html');
2195
2196=head1 MEMBER VARIABLES
2197
2198=over 4
2199
2200=item PRESERVE_DATE
2201
2202Flag to preserve "Last revised" date in HTML files. Set before calling
2203WriteTagNames().
2204
2205=item COUNT
2206
2207Reference to hash containing counting statistics. Keys are the
2208descriptions, and values are the numerical counts. Valid after
2209BuildTagLookup object is created, but additional statistics are added by
2210WriteTagNames().
2211
2212=back
2213
2214=head1 AUTHOR
2215
2216Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)
2217
2218This library is free software; you can redistribute it and/or modify it
2219under the same terms as Perl itself.
2220
2221=head1 SEE ALSO
2222
2223L<Image::ExifTool(3pm)|Image::ExifTool>,
2224L<Image::ExifTool::TagLookup(3pm)|Image::ExifTool::TagLookup>,
2225L<Image::ExifTool::TagNames(3pm)|Image::ExifTool::TagNames>
2226
2227=cut
Note: See TracBrowser for help on using the repository browser.