File Coverage

blib/lib/Image/ExifTool/XMP.pm
Criterion Covered Total %
statement 698 960 72.7
branch 482 742 64.9
condition 256 485 52.7
subroutine 25 29 86.2
pod 0 21 0.0
total 1461 2237 65.3


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: XMP.pm
3             #
4             # Description: Read XMP meta information
5             #
6             # Revisions: 11/25/2003 - P. Harvey Created
7             # 10/28/2004 - P. Harvey Major overhaul to conform with XMP spec
8             # 02/27/2005 - P. Harvey Also read UTF-16 and UTF-32 XMP
9             # 08/30/2005 - P. Harvey Split tag tables into separate namespaces
10             # 10/24/2005 - P. Harvey Added ability to parse .XMP files
11             # 08/25/2006 - P. Harvey Added ability to handle blank nodes
12             # 08/22/2007 - P. Harvey Added ability to handle alternate language tags
13             # 09/26/2008 - P. Harvey Added Iptc4xmpExt tags (version 1.0 rev 2)
14             #
15             # References: 1) http://www.adobe.com/products/xmp/pdfs/xmpspec.pdf
16             # 2) http://www.w3.org/TR/rdf-syntax-grammar/ (20040210)
17             # 3) http://www.portfoliofaq.com/pfaq/v7mappings.htm
18             # 4) http://www.iptc.org/IPTC4XMP/
19             # 5) http://creativecommons.org/technology/xmp
20             # --> changed to http://wiki.creativecommons.org/Companion_File_metadata_specification (2007/12/21)
21             # 6) http://www.optimasc.com/products/fileid/xmp-extensions.pdf
22             # 7) Lou Salkind private communication
23             # 8) http://partners.adobe.com/public/developer/en/xmp/sdk/XMPspecification.pdf
24             # 9) http://www.w3.org/TR/SVG11/
25             # 10) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf (Oct 2008)
26             # 11) http://www.extensis.com/en/support/kb_article.jsp?articleNumber=6102211
27             # 12) http://www.cipa.jp/std/documents/e/DC-010-2012_E.pdf
28             # 13) http://www.cipa.jp/std/documents/e/DC-010-2017_E.pdf (changed to
29             # http://www.cipa.jp/std/documents/e/DC-X010-2017.pdf)
30             #
31             # Notes: - Property qualifiers are handled as if they were separate
32             # properties (with no associated namespace).
33             #
34             # - Currently, there is no special treatment of the following
35             # properties which could potentially affect the extracted
36             # information: xml:base, rdf:parseType (note that parseType
37             # Literal isn't allowed by the XMP spec).
38             #
39             # - The family 2 group names will be set to 'Unknown' for any XMP
40             # tags not found in the XMP or Exif tag tables.
41             #------------------------------------------------------------------------------
42              
43             package Image::ExifTool::XMP;
44              
45 60     60   4549 use strict;
  60         115  
  60         2173  
46 60         6399 use vars qw($VERSION $AUTOLOAD @ISA @EXPORT_OK %stdXlatNS %nsURI %latConv %longConv
47 60     60   287 %dateTimeInfo %xmpTableDefaults %specialStruct %sDimensions %sArea %sColorant);
  60         109  
48 60     60   341 use Image::ExifTool qw(:Utils);
  60         114  
  60         6786  
49 60     60   6720 use Image::ExifTool::Exif;
  60         182  
  60         2137  
50 60     60   18718 use Image::ExifTool::GPS;
  60         167  
  60         475626  
51             require Exporter;
52              
53             $VERSION = '3.51';
54             @ISA = qw(Exporter);
55             @EXPORT_OK = qw(EscapeXML UnescapeXML);
56              
57             sub ProcessXMP($$;$);
58             sub WriteXMP($$;$);
59             sub CheckXMP($$$;$);
60             sub ParseXMPElement($$$;$$$$);
61             sub DecodeBase64($);
62             sub EncodeBase64($;$);
63             sub SaveBlankInfo($$$;$);
64             sub ProcessBlankInfo($$$;$);
65             sub ValidateXMP($;$);
66             sub ValidateProperty($$;$);
67             sub UnescapeChar($$;$);
68             sub AddFlattenedTags($;$$);
69             sub FormatXMPDate($);
70             sub ConvertRational($);
71             sub ConvertRationalList($);
72             sub WriteGSpherical($$$);
73              
74             # standard path locations for XMP in major file types
75             my %stdPath = (
76             JPEG => 'JPEG-APP1-XMP',
77             TIFF => 'TIFF-IFD0-XMP',
78             PSD => 'PSD-XMP',
79             );
80              
81             # lookup for translating to ExifTool namespaces (and family 1 group names)
82             %stdXlatNS = (
83             # shorten ugly namespace prefixes
84             'Iptc4xmpCore' => 'iptcCore',
85             'Iptc4xmpExt' => 'iptcExt',
86             'photomechanic'=> 'photomech',
87             'MicrosoftPhoto' => 'microsoft',
88             'prismusagerights' => 'pur',
89             'GettyImagesGIFT' => 'getty',
90             );
91              
92             # translate ExifTool XMP family 1 group names back to standard XMP namespace prefixes
93             my %xmpNS = (
94             'iptcCore' => 'Iptc4xmpCore',
95             'iptcExt' => 'Iptc4xmpExt',
96             'photomech'=> 'photomechanic',
97             'microsoft' => 'MicrosoftPhoto',
98             'getty' => 'GettyImagesGIFT',
99             # (prism changed their spec to now use 'pur')
100             # 'pur' => 'prismusagerights',
101             );
102              
103             # Lookup to translate standard XMP namespace prefixes into URI's. This list
104             # need not be complete, but it must contain an entry for each namespace prefix
105             # (NAMESPACE) for writable tags in the XMP tables or in structures that doesn't
106             # define a URI. Also, the namespace must be defined here for non-standard
107             # namespace prefixes to be recognized.
108             %nsURI = (
109             aux => 'http://ns.adobe.com/exif/1.0/aux/',
110             album => 'http://ns.adobe.com/album/1.0/',
111             cc => 'http://creativecommons.org/ns#', # changed 2007/12/21 - PH
112             crd => 'http://ns.adobe.com/camera-raw-defaults/1.0/',
113             crs => 'http://ns.adobe.com/camera-raw-settings/1.0/',
114             crss => 'http://ns.adobe.com/camera-raw-saved-settings/1.0/',
115             dc => 'http://purl.org/dc/elements/1.1/',
116             exif => 'http://ns.adobe.com/exif/1.0/',
117             exifEX => 'http://cipa.jp/exif/1.0/',
118             iX => 'http://ns.adobe.com/iX/1.0/',
119             pdf => 'http://ns.adobe.com/pdf/1.3/',
120             pdfx => 'http://ns.adobe.com/pdfx/1.3/',
121             photoshop => 'http://ns.adobe.com/photoshop/1.0/',
122             rdf => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
123             rdfs => 'http://www.w3.org/2000/01/rdf-schema#',
124             stDim => 'http://ns.adobe.com/xap/1.0/sType/Dimensions#',
125             stEvt => 'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#',
126             stFnt => 'http://ns.adobe.com/xap/1.0/sType/Font#',
127             stJob => 'http://ns.adobe.com/xap/1.0/sType/Job#',
128             stRef => 'http://ns.adobe.com/xap/1.0/sType/ResourceRef#',
129             stVer => 'http://ns.adobe.com/xap/1.0/sType/Version#',
130             stMfs => 'http://ns.adobe.com/xap/1.0/sType/ManifestItem#',
131             tiff => 'http://ns.adobe.com/tiff/1.0/',
132             'x' => 'adobe:ns:meta/',
133             xmpG => 'http://ns.adobe.com/xap/1.0/g/',
134             xmpGImg => 'http://ns.adobe.com/xap/1.0/g/img/',
135             xmp => 'http://ns.adobe.com/xap/1.0/',
136             xmpBJ => 'http://ns.adobe.com/xap/1.0/bj/',
137             xmpDM => 'http://ns.adobe.com/xmp/1.0/DynamicMedia/',
138             xmpMM => 'http://ns.adobe.com/xap/1.0/mm/',
139             xmpRights => 'http://ns.adobe.com/xap/1.0/rights/',
140             xmpNote => 'http://ns.adobe.com/xmp/note/',
141             xmpTPg => 'http://ns.adobe.com/xap/1.0/t/pg/',
142             xmpidq => 'http://ns.adobe.com/xmp/Identifier/qual/1.0/',
143             xmpPLUS => 'http://ns.adobe.com/xap/1.0/PLUS/',
144             dex => 'http://ns.optimasc.com/dex/1.0/',
145             mediapro => 'http://ns.iview-multimedia.com/mediapro/1.0/',
146             expressionmedia => 'http://ns.microsoft.com/expressionmedia/1.0/',
147             Iptc4xmpCore => 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/',
148             Iptc4xmpExt => 'http://iptc.org/std/Iptc4xmpExt/2008-02-29/',
149             MicrosoftPhoto => 'http://ns.microsoft.com/photo/1.0',
150             MP1 => 'http://ns.microsoft.com/photo/1.1', #PH (MP1 is fabricated)
151             MP => 'http://ns.microsoft.com/photo/1.2/',
152             MPRI => 'http://ns.microsoft.com/photo/1.2/t/RegionInfo#',
153             MPReg => 'http://ns.microsoft.com/photo/1.2/t/Region#',
154             lr => 'http://ns.adobe.com/lightroom/1.0/',
155             DICOM => 'http://ns.adobe.com/DICOM/',
156             'drone-dji'=> 'http://www.dji.com/drone-dji/1.0/',
157             svg => 'http://www.w3.org/2000/svg',
158             et => 'http://ns.exiftool.org/1.0/',
159             #
160             # namespaces defined in XMP2.pl:
161             #
162             plus => 'http://ns.useplus.org/ldf/xmp/1.0/',
163             # (prism recommendations from http://www.prismstandard.org/specifications/3.0/Image_Guide_3.0.htm)
164             prism => 'http://prismstandard.org/namespaces/basic/2.0/',
165             prl => 'http://prismstandard.org/namespaces/prl/2.1/',
166             pur => 'http://prismstandard.org/namespaces/prismusagerights/2.1/',
167             pmi => 'http://prismstandard.org/namespaces/pmi/2.2/',
168             prm => 'http://prismstandard.org/namespaces/prm/3.0/',
169             acdsee => 'http://ns.acdsee.com/iptc/1.0/',
170             digiKam => 'http://www.digikam.org/ns/1.0/',
171             swf => 'http://ns.adobe.com/swf/1.0/',
172             cell => 'http://developer.sonyericsson.com/cell/1.0/',
173             aas => 'http://ns.apple.com/adjustment-settings/1.0/',
174             'mwg-rs' => 'http://www.metadataworkinggroup.com/schemas/regions/',
175             'mwg-kw' => 'http://www.metadataworkinggroup.com/schemas/keywords/',
176             'mwg-coll' => 'http://www.metadataworkinggroup.com/schemas/collections/',
177             stArea => 'http://ns.adobe.com/xmp/sType/Area#',
178             extensis => 'http://ns.extensis.com/extensis/1.0/',
179             ics => 'http://ns.idimager.com/ics/1.0/',
180             fpv => 'http://ns.fastpictureviewer.com/fpv/1.0/',
181             creatorAtom=>'http://ns.adobe.com/creatorAtom/1.0/',
182             'apple-fi' => 'http://ns.apple.com/faceinfo/1.0/',
183             GAudio => 'http://ns.google.com/photos/1.0/audio/',
184             GImage => 'http://ns.google.com/photos/1.0/image/',
185             GPano => 'http://ns.google.com/photos/1.0/panorama/',
186             GSpherical=> 'http://ns.google.com/videos/1.0/spherical/',
187             GDepth => 'http://ns.google.com/photos/1.0/depthmap/',
188             GFocus => 'http://ns.google.com/photos/1.0/focus/',
189             GCamera => 'http://ns.google.com/photos/1.0/camera/',
190             GCreations=> 'http://ns.google.com/photos/1.0/creations/',
191             dwc => 'http://rs.tdwg.org/dwc/index.htm',
192             GettyImagesGIFT => 'http://xmp.gettyimages.com/gift/1.0/',
193             LImage => 'http://ns.leiainc.com/photos/1.0/image/',
194             Profile => 'http://ns.google.com/photos/dd/1.0/profile/',
195             sdc => 'http://ns.nikon.com/sdc/1.0/',
196             ast => 'http://ns.nikon.com/asteroid/1.0/',
197             nine => 'http://ns.nikon.com/nine/1.0/',
198             );
199              
200             # build reverse namespace lookup
201             my %uri2ns = ( 'http://ns.exiftool.ca/1.0/' => 'et' ); # (allow exiftool.ca as well as exiftool.org)
202             {
203             my $ns;
204             foreach $ns (keys %nsURI) {
205             $uri2ns{$nsURI{$ns}} = $ns;
206             }
207             }
208              
209             # conversions for GPS coordinates
210             %latConv = (
211             ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
212             ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 2, "N")',
213             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
214             PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")',
215             );
216             %longConv = (
217             ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
218             ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 2, "E")',
219             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
220             PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")',
221             );
222             %dateTimeInfo = (
223             # NOTE: Do NOT put "Groups" here because Groups hash must not be common!
224             Writable => 'date',
225             Shift => 'Time',
226             Validate => 'ValidateXMPDate($val)',
227             PrintConv => '$self->ConvertDateTime($val)',
228             PrintConvInv => '$self->InverseDateTime($val,undef,1)',
229             );
230              
231             # this conversion allows alternate language support for designated boolean tags
232             my %boolConv = (
233             PrintConv => {
234             OTHER => sub { # (inverse conversion is the same)
235             my $val = shift;
236             return 'False' if lc $val eq 'false';
237             return 'True' if lc $val eq 'true';
238             return $val;
239             },
240             True => 'True',
241             False => 'False',
242             },
243             );
244              
245             # XMP namespaces which we don't want to contribute to generated EXIF tag names
246             # (Note: namespaces with non-standard prefixes aren't currently ignored)
247             my %ignoreNamespace = ( 'x'=>1, rdf=>1, xmlns=>1, xml=>1, svg=>1, et=>1, office=>1 );
248              
249             # XMP properties to ignore (set dynamically via dirInfo IgnoreProp)
250             my %ignoreProp;
251              
252             # these are the attributes that we handle for properties that contain
253             # sub-properties. Attributes for simple properties are easy, and we
254             # just copy them over. These are harder since we don't store attributes
255             # for properties without simple values. (maybe this will change...)
256             # (special attributes are indicated by a list reference of tag information)
257             my %recognizedAttrs = (
258             'rdf:about' => [ 'Image::ExifTool::XMP::rdf', 'about', 'About' ],
259             'x:xmptk' => [ 'Image::ExifTool::XMP::x', 'xmptk', 'XMPToolkit' ],
260             'x:xaptk' => [ 'Image::ExifTool::XMP::x', 'xmptk', 'XMPToolkit' ],
261             'rdf:parseType' => 1,
262             'rdf:nodeID' => 1,
263             'et:toolkit' => 1,
264             'rdf:xmlns' => 1, # this is presumably the default namespace, which we currently ignore
265             'lastUpdate' => [ 'Image::ExifTool::XMP::XML', 'lastUpdate', 'LastUpdate' ], # found in XML from Sony ILCE-7S MP4
266             );
267              
268             # special tags in structures below
269             # NOTE: this lookup is duplicated in TagLookup.pm!!
270             %specialStruct = (
271             STRUCT_NAME => 1, # [optional] name of structure
272             NAMESPACE => 1, # [mandatory] namespace prefix used for fields of this structure
273             NOTES => 1, # [optional] notes for documentation about this structure
274             TYPE => 1, # [optional] rdf:type resource for struct (if used, the StructType flag
275             # will be set automatically for all derived flattened tags when writing)
276             GROUPS => 1, # [optional] specifies family group 2 name for the structure
277             );
278             # XMP structures (each structure is similar to a tag table so we can
279             # recurse through them in SetPropertyPath() as if they were tag tables)
280             # The main differences between structure field information and tagInfo hashes are:
281             # 1) Field information hashes do not contain Name, Groups or Table entries, and
282             # 2) The TagID entry is optional, and is used only if the key in the structure hash
283             # is different from the TagID (currently only true for alternate language fields)
284             # 3) Field information hashes support a additional "Namespace" property.
285             my %sResourceRef = (
286             STRUCT_NAME => 'ResourceRef',
287             NAMESPACE => 'stRef',
288             documentID => { },
289             instanceID => { },
290             manager => { },
291             managerVariant => { },
292             manageTo => { },
293             manageUI => { },
294             renditionClass => { },
295             renditionParams => { },
296             versionID => { },
297             # added Oct 2008
298             alternatePaths => { List => 'Seq' },
299             filePath => { },
300             fromPart => { },
301             lastModifyDate => { %dateTimeInfo, Groups => { 2 => 'Time' } },
302             maskMarkers => { PrintConv => { All => 'All', None => 'None' } },
303             partMapping => { },
304             toPart => { },
305             # added May 2010
306             originalDocumentID => { }, # (undocumented property written by Adobe InDesign)
307             # added Aug 2016 (INDD again)
308             lastURL => { },
309             linkForm => { },
310             linkCategory => { },
311             placedXResolution => { },
312             placedYResolution => { },
313             placedResolutionUnit => { },
314             );
315             my %sResourceEvent = (
316             STRUCT_NAME => 'ResourceEvent',
317             NAMESPACE => 'stEvt',
318             action => { },
319             instanceID => { },
320             parameters => { },
321             softwareAgent => { },
322             when => { %dateTimeInfo, Groups => { 2 => 'Time' } },
323             # added Oct 2008
324             changed => { },
325             );
326             my %sJobRef = (
327             STRUCT_NAME => 'JobRef',
328             NAMESPACE => 'stJob',
329             id => { },
330             name => { },
331             url => { },
332             );
333             my %sVersion = (
334             STRUCT_NAME => 'Version',
335             NAMESPACE => 'stVer',
336             comments => { },
337             event => { Struct => \%sResourceEvent },
338             modifier => { },
339             modifyDate => { %dateTimeInfo, Groups => { 2 => 'Time' } },
340             version => { },
341             );
342             my %sThumbnail = (
343             STRUCT_NAME => 'Thumbnail',
344             NAMESPACE => 'xmpGImg',
345             height => { Writable => 'integer' },
346             width => { Writable => 'integer' },
347             'format' => { },
348             image => {
349             Avoid => 1,
350             Groups => { 2 => 'Preview' },
351             ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
352             ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
353             },
354             );
355             my %sPageInfo = (
356             STRUCT_NAME => 'PageInfo',
357             NAMESPACE => 'xmpGImg',
358             PageNumber => { Writable => 'integer', Namespace => 'xmpTPg' }, # override default namespace
359             height => { Writable => 'integer' },
360             width => { Writable => 'integer' },
361             'format' => { },
362             image => {
363             Groups => { 2 => 'Preview' },
364             ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
365             ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
366             },
367             );
368             #my %sIdentifierScheme = (
369             # NAMESPACE => 'xmpidq',
370             # Scheme => { }, # qualifier for xmp:Identifier only
371             #);
372             %sDimensions = (
373             STRUCT_NAME => 'Dimensions',
374             NAMESPACE => 'stDim',
375             w => { Writable => 'real' },
376             h => { Writable => 'real' },
377             unit => { },
378             );
379             %sArea = (
380             STRUCT_NAME => 'Area',
381             NAMESPACE => 'stArea',
382             'x' => { Writable => 'real' },
383             'y' => { Writable => 'real' },
384             w => { Writable => 'real' },
385             h => { Writable => 'real' },
386             d => { Writable => 'real' },
387             unit => { },
388             );
389             %sColorant = (
390             STRUCT_NAME => 'Colorant',
391             NAMESPACE => 'xmpG',
392             swatchName => { },
393             mode => { PrintConv => { CMYK=>'CMYK', RGB=>'RGB', LAB=>'Lab' } },
394             # note: do not implement closed choice for "type" because Adobe can't
395             # get the case right: spec. says "PROCESS" but Indesign writes "Process"
396             type => { },
397             cyan => { Writable => 'real' },
398             magenta => { Writable => 'real' },
399             yellow => { Writable => 'real' },
400             black => { Writable => 'real' },
401             red => { Writable => 'integer' },
402             green => { Writable => 'integer' },
403             blue => { Writable => 'integer' },
404             gray => { Writable => 'integer' },
405             L => { Writable => 'real' },
406             A => { Writable => 'integer' },
407             B => { Writable => 'integer' },
408             # 'tint' observed in INDD sample - PH
409             tint => { Writable => 'integer', Notes => 'not part of 2010 XMP specification' },
410             );
411             my %sSwatchGroup = (
412             STRUCT_NAME => 'SwatchGroup',
413             NAMESPACE => 'xmpG',
414             groupName => { },
415             groupType => { Writable => 'integer' },
416             Colorants => {
417             FlatName => 'SwatchColorant',
418             Struct => \%sColorant,
419             List => 'Seq',
420             },
421             );
422             my %sFont = (
423             STRUCT_NAME => 'Font',
424             NAMESPACE => 'stFnt',
425             fontName => { },
426             fontFamily => { },
427             fontFace => { },
428             fontType => { },
429             versionString => { },
430             composite => { Writable => 'boolean' },
431             fontFileName=> { },
432             childFontFiles => { List => 'Seq' },
433             );
434             my %sOECF = (
435             STRUCT_NAME => 'OECF',
436             NAMESPACE => 'exif',
437             Columns => { Writable => 'integer' },
438             Rows => { Writable => 'integer' },
439             Names => { List => 'Seq' },
440             Values => { List => 'Seq', Writable => 'rational' },
441             );
442             my %sAreaModels = (
443             STRUCT_NAME => 'AreaModels',
444             NAMESPACE => 'crs',
445             ColorRangeMaskAreaSampleInfo => { FlatName => 'ColorSampleInfo' },
446             AreaComponents => { FlatName => 'Components', List => 'Seq' },
447             );
448             my %sCorrRangeMask = (
449             STRUCT_NAME => 'CorrRangeMask',
450             NAMESPACE => 'crs',
451             NOTES => 'Called CorrectionRangeMask by the spec.',
452             Version => { },
453             Type => { },
454             ColorAmount => { Writable => 'real' },
455             LumMin => { Writable => 'real' },
456             LumMax => { Writable => 'real' },
457             LumFeather => { Writable => 'real' },
458             DepthMin => { Writable => 'real' },
459             DepthMax => { Writable => 'real' },
460             DepthFeather=> { Writable => 'real' },
461             # new in LR 11.0
462             Invert => { Writable => 'boolean' },
463             SampleType => { Writable => 'integer' },
464             AreaModels => {
465             List => 'Seq',
466             Struct => \%sAreaModels,
467             },
468             LumRange => { },
469             LuminanceDepthSampleInfo => { },
470             );
471             # new LR2 crs structures (PH)
472             my %sCorrectionMask;
473             %sCorrectionMask = (
474             STRUCT_NAME => 'CorrectionMask',
475             NAMESPACE => 'crs',
476             # disable List behaviour of flattened Gradient/PaintBasedCorrections
477             # because these are nested in lists and the flattened tags can't
478             # do justice to this complex structure
479             What => { List => 0 },
480             MaskValue => { Writable => 'real', List => 0, FlatName => 'Value' },
481             Radius => { Writable => 'real', List => 0 },
482             Flow => { Writable => 'real', List => 0 },
483             CenterWeight => { Writable => 'real', List => 0 },
484             Dabs => { List => 'Seq' },
485             ZeroX => { Writable => 'real', List => 0 },
486             ZeroY => { Writable => 'real', List => 0 },
487             FullX => { Writable => 'real', List => 0 },
488             FullY => { Writable => 'real', List => 0 },
489             # new elements used in CircularGradientBasedCorrections CorrectionMasks
490             # and RetouchAreas Masks
491             Top => { Writable => 'real', List => 0 },
492             Left => { Writable => 'real', List => 0 },
493             Bottom => { Writable => 'real', List => 0 },
494             Right => { Writable => 'real', List => 0 },
495             Angle => { Writable => 'real', List => 0 },
496             Midpoint => { Writable => 'real', List => 0 },
497             Roundness => { Writable => 'real', List => 0 },
498             Feather => { Writable => 'real', List => 0 },
499             Flipped => { Writable => 'boolean', List => 0 },
500             Version => { Writable => 'integer', List => 0 },
501             SizeX => { Writable => 'real', List => 0 },
502             SizeY => { Writable => 'real', List => 0 },
503             X => { Writable => 'real', List => 0 },
504             Y => { Writable => 'real', List => 0 },
505             Alpha => { Writable => 'real', List => 0 },
506             CenterValue => { Writable => 'real', List => 0 },
507             PerimeterValue=>{ Writable => 'real', List => 0 },
508             # new in LR 11.0 MaskGroupBasedCorrections
509             MaskActive => { Writable => 'boolean', List => 0 },
510             MaskName => { List => 0 },
511             MaskBlendMode=> { Writable => 'integer', List => 0 },
512             MaskInverted => { Writable => 'boolean', List => 0 },
513             MaskSyncID => { List => 0 },
514             MaskVersion => { List => 0 },
515             MaskSubType => { List => 0 },
516             ReferencePoint => { List => 0 },
517             InputDigest => { List => 0 },
518             MaskDigest => { List => 0 },
519             WholeImageArea => { List => 0 },
520             Origin => { List => 0 },
521             Masks => { Struct => \%sCorrectionMask, NoSubStruct => 1 },
522             CorrectionRangeMask => {
523             Name => 'CorrRangeMask',
524             Notes => 'called CorrectionRangeMask by the spec',
525             FlatName => 'Range',
526             Struct => \%sCorrRangeMask,
527             },
528             );
529             my %sCorrection = (
530             STRUCT_NAME => 'Correction',
531             NAMESPACE => 'crs',
532             What => { List => 0 },
533             CorrectionAmount => { FlatName => 'Amount', Writable => 'real', List => 0 },
534             CorrectionActive => { FlatName => 'Active', Writable => 'boolean', List => 0 },
535             LocalExposure => { FlatName => 'Exposure', Writable => 'real', List => 0 },
536             LocalSaturation => { FlatName => 'Saturation', Writable => 'real', List => 0 },
537             LocalContrast => { FlatName => 'Contrast', Writable => 'real', List => 0 },
538             LocalClarity => { FlatName => 'Clarity', Writable => 'real', List => 0 },
539             LocalSharpness => { FlatName => 'Sharpness', Writable => 'real', List => 0 },
540             LocalBrightness => { FlatName => 'Brightness', Writable => 'real', List => 0 },
541             LocalToningHue => { FlatName => 'ToningHue', Writable => 'real', List => 0 },
542             LocalToningSaturation => { FlatName => 'ToningSaturation', Writable => 'real', List => 0 },
543             LocalExposure2012 => { FlatName => 'Exposure2012', Writable => 'real', List => 0 },
544             LocalContrast2012 => { FlatName => 'Contrast2012', Writable => 'real', List => 0 },
545             LocalHighlights2012 => { FlatName => 'Highlights2012', Writable => 'real', List => 0 },
546             LocalShadows2012 => { FlatName => 'Shadows2012', Writable => 'real', List => 0 },
547             LocalClarity2012 => { FlatName => 'Clarity2012', Writable => 'real', List => 0 },
548             LocalLuminanceNoise => { FlatName => 'LuminanceNoise', Writable => 'real', List => 0 },
549             LocalMoire => { FlatName => 'Moire', Writable => 'real', List => 0 },
550             LocalDefringe => { FlatName => 'Defringe', Writable => 'real', List => 0 },
551             LocalTemperature => { FlatName => 'Temperature',Writable => 'real', List => 0 },
552             LocalTint => { FlatName => 'Tint', Writable => 'real', List => 0 },
553             LocalHue => { FlatName => 'Hue', Writable => 'real', List => 0 },
554             LocalWhites2012 => { FlatName => 'Whites2012', Writable => 'real', List => 0 },
555             LocalBlacks2012 => { FlatName => 'Blacks2012', Writable => 'real', List => 0 },
556             LocalDehaze => { FlatName => 'Dehaze', Writable => 'real', List => 0 },
557             LocalTexture => { FlatName => 'Texture', Writable => 'real', List => 0 },
558             # new in LR 11.0
559             CorrectionRangeMask => {
560             Name => 'CorrRangeMask',
561             Notes => 'called CorrectionRangeMask by the spec',
562             FlatName => 'RangeMask',
563             Struct => \%sCorrRangeMask,
564             },
565             CorrectionMasks => {
566             FlatName => 'Mask',
567             Struct => \%sCorrectionMask,
568             List => 'Seq',
569             },
570             CorrectionName => { },
571             CorrectionSyncID => { },
572             );
573             my %sRetouchArea = (
574             STRUCT_NAME => 'RetouchArea',
575             NAMESPACE => 'crs',
576             SpotType => { List => 0 },
577             SourceState => { List => 0 },
578             Method => { List => 0 },
579             SourceX => { Writable => 'real', List => 0 },
580             OffsetY => { Writable => 'real', List => 0 },
581             Opacity => { Writable => 'real', List => 0 },
582             Feather => { Writable => 'real', List => 0 },
583             Seed => { Writable => 'integer', List => 0 },
584             Masks => {
585             FlatName => 'Mask',
586             Struct => \%sCorrectionMask,
587             List => 'Seq',
588             },
589             );
590             my %sMapInfo = (
591             STRUCT_NAME => 'MapInfo',
592             NAMESPACE => 'crs',
593             NOTES => q{
594             Called RangeMaskMapInfo by the specification, the same as the containing
595             structure.
596             },
597             RGBMin => { },
598             RGBMax => { },
599             LabMin => { },
600             LabMax => { },
601             LumEq => { List => 'Seq' },
602             );
603             my %sRangeMask = (
604             STRUCT_NAME => 'RangeMask',
605             NAMESPACE => 'crs',
606             NOTES => q{
607             This structure is actually called RangeMaskMapInfo, but it only contains one
608             element which is a RangeMaskMapInfo structure (Yes, really!). So these are
609             renamed to RangeMask and MapInfo respectively to avoid confusion and
610             redundancy in the tag names.
611             },
612             RangeMaskMapInfo => { FlatName => 'MapInfo', Struct => \%sMapInfo },
613             );
614              
615             # main XMP tag table (tag ID's are used for the family 1 group names)
616             %Image::ExifTool::XMP::Main = (
617             GROUPS => { 2 => 'Unknown' },
618             PROCESS_PROC => \&ProcessXMP,
619             WRITE_PROC => \&WriteXMP,
620             dc => {
621             Name => 'dc', # (otherwise generated name would be 'Dc')
622             SubDirectory => { TagTable => 'Image::ExifTool::XMP::dc' },
623             },
624             xmp => {
625             Name => 'xmp',
626             SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmp' },
627             },
628             xmpDM => {
629             Name => 'xmpDM',
630             SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpDM' },
631             },
632             xmpRights => {
633             Name => 'xmpRights',
634             SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpRights' },
635             },
636             xmpNote => {
637             Name => 'xmpNote',
638             SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpNote' },
639             },
640             xmpMM => {
641             Name => 'xmpMM',
642             SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpMM' },
643             },
644             xmpBJ => {
645             Name => 'xmpBJ',
646             SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpBJ' },
647             },
648             xmpTPg => {
649             Name => 'xmpTPg',
650             SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpTPg' },
651             },
652             pdf => {
653             Name => 'pdf',
654             SubDirectory => { TagTable => 'Image::ExifTool::XMP::pdf' },
655             },
656             pdfx => {
657             Name => 'pdfx',
658             SubDirectory => { TagTable => 'Image::ExifTool::XMP::pdfx' },
659             },
660             photoshop => {
661             Name => 'photoshop',
662             SubDirectory => { TagTable => 'Image::ExifTool::XMP::photoshop' },
663             },
664             crd => {
665             Name => 'crd',
666             SubDirectory => { TagTable => 'Image::ExifTool::XMP::crd' },
667             },
668             crs => {
669             Name => 'crs',
670             SubDirectory => { TagTable => 'Image::ExifTool::XMP::crs' },
671             },
672             # crss - it would be tedious to add the ability to write this
673             aux => {
674             Name => 'aux',
675             SubDirectory => { TagTable => 'Image::ExifTool::XMP::aux' },
676             },
677             tiff => {
678             Name => 'tiff',
679             SubDirectory => { TagTable => 'Image::ExifTool::XMP::tiff' },
680             },
681             exif => {
682             Name => 'exif',
683             SubDirectory => { TagTable => 'Image::ExifTool::XMP::exif' },
684             },
685             exifEX => {
686             Name => 'exifEX',
687             SubDirectory => { TagTable => 'Image::ExifTool::XMP::exifEX' },
688             },
689             iptcCore => {
690             Name => 'iptcCore',
691             SubDirectory => { TagTable => 'Image::ExifTool::XMP::iptcCore' },
692             },
693             iptcExt => {
694             Name => 'iptcExt',
695             SubDirectory => { TagTable => 'Image::ExifTool::XMP::iptcExt' },
696             },
697             PixelLive => {
698             SubDirectory => { TagTable => 'Image::ExifTool::XMP::PixelLive' },
699             },
700             xmpPLUS => {
701             Name => 'xmpPLUS',
702             SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpPLUS' },
703             },
704             plus => {
705             Name => 'plus',
706             SubDirectory => { TagTable => 'Image::ExifTool::PLUS::XMP' },
707             },
708             cc => {
709             Name => 'cc',
710             SubDirectory => { TagTable => 'Image::ExifTool::XMP::cc' },
711             },
712             dex => {
713             Name => 'dex',
714             SubDirectory => { TagTable => 'Image::ExifTool::XMP::dex' },
715             },
716             photomech => {
717             Name => 'photomech',
718             SubDirectory => { TagTable => 'Image::ExifTool::PhotoMechanic::XMP' },
719             },
720             mediapro => {
721             Name => 'mediapro',
722             SubDirectory => { TagTable => 'Image::ExifTool::XMP::MediaPro' },
723             },
724             expressionmedia => {
725             Name => 'expressionmedia',
726             SubDirectory => { TagTable => 'Image::ExifTool::XMP::ExpressionMedia' },
727             },
728             microsoft => {
729             Name => 'microsoft',
730             SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::XMP' },
731             },
732             MP => {
733             Name => 'MP',
734             SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::MP' },
735             },
736             MP1 => {
737             Name => 'MP1',
738             SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::MP1' },
739             },
740             lr => {
741             Name => 'lr',
742             SubDirectory => { TagTable => 'Image::ExifTool::XMP::Lightroom' },
743             },
744             DICOM => {
745             Name => 'DICOM',
746             SubDirectory => { TagTable => 'Image::ExifTool::XMP::DICOM' },
747             },
748             album => {
749             Name => 'album',
750             SubDirectory => { TagTable => 'Image::ExifTool::XMP::Album' },
751             },
752             prism => {
753             Name => 'prism',
754             SubDirectory => { TagTable => 'Image::ExifTool::XMP::prism' },
755             },
756             prl => {
757             Name => 'prl',
758             SubDirectory => { TagTable => 'Image::ExifTool::XMP::prl' },
759             },
760             pur => {
761             Name => 'pur',
762             SubDirectory => { TagTable => 'Image::ExifTool::XMP::pur' },
763             },
764             pmi => {
765             Name => 'pmi',
766             SubDirectory => { TagTable => 'Image::ExifTool::XMP::pmi' },
767             },
768             prm => {
769             Name => 'prm',
770             SubDirectory => { TagTable => 'Image::ExifTool::XMP::prm' },
771             },
772             rdf => {
773             Name => 'rdf',
774             SubDirectory => { TagTable => 'Image::ExifTool::XMP::rdf' },
775             },
776             'x' => {
777             Name => 'x',
778             SubDirectory => { TagTable => 'Image::ExifTool::XMP::x' },
779             },
780             acdsee => {
781             Name => 'acdsee',
782             SubDirectory => { TagTable => 'Image::ExifTool::XMP::acdsee' },
783             },
784             digiKam => {
785             Name => 'digiKam',
786             SubDirectory => { TagTable => 'Image::ExifTool::XMP::digiKam' },
787             },
788             swf => {
789             Name => 'swf',
790             SubDirectory => { TagTable => 'Image::ExifTool::XMP::swf' },
791             },
792             cell => {
793             Name => 'cell',
794             SubDirectory => { TagTable => 'Image::ExifTool::XMP::cell' },
795             },
796             aas => {
797             Name => 'aas',
798             SubDirectory => { TagTable => 'Image::ExifTool::XMP::aas' },
799             },
800             'mwg-rs' => {
801             Name => 'mwg-rs',
802             SubDirectory => { TagTable => 'Image::ExifTool::MWG::Regions' },
803             },
804             'mwg-kw' => {
805             Name => 'mwg-kw',
806             SubDirectory => { TagTable => 'Image::ExifTool::MWG::Keywords' },
807             },
808             'mwg-coll' => {
809             Name => 'mwg-coll',
810             SubDirectory => { TagTable => 'Image::ExifTool::MWG::Collections' },
811             },
812             extensis => {
813             Name => 'extensis',
814             SubDirectory => { TagTable => 'Image::ExifTool::XMP::extensis' },
815             },
816             ics => {
817             Name => 'ics',
818             SubDirectory => { TagTable => 'Image::ExifTool::XMP::ics' },
819             },
820             fpv => {
821             Name => 'fpv',
822             SubDirectory => { TagTable => 'Image::ExifTool::XMP::fpv' },
823             },
824             creatorAtom => {
825             Name => 'creatorAtom',
826             SubDirectory => { TagTable => 'Image::ExifTool::XMP::creatorAtom' },
827             },
828             'apple-fi' => {
829             Name => 'apple-fi',
830             SubDirectory => { TagTable => 'Image::ExifTool::XMP::apple_fi' },
831             },
832             GAudio => {
833             Name => 'GAudio',
834             SubDirectory => { TagTable => 'Image::ExifTool::XMP::GAudio' },
835             },
836             GImage => {
837             Name => 'GImage',
838             SubDirectory => { TagTable => 'Image::ExifTool::XMP::GImage' },
839             },
840             GPano => {
841             Name => 'GPano',
842             SubDirectory => { TagTable => 'Image::ExifTool::XMP::GPano' },
843             },
844             GSpherical => {
845             Name => 'GSpherical',
846             SubDirectory => { TagTable => 'Image::ExifTool::XMP::GSpherical' },
847             },
848             GDepth => {
849             Name => 'GDepth',
850             SubDirectory => { TagTable => 'Image::ExifTool::XMP::GDepth' },
851             },
852             GFocus => {
853             Name => 'GFocus',
854             SubDirectory => { TagTable => 'Image::ExifTool::XMP::GFocus' },
855             },
856             GCamera => {
857             Name => 'GCamera',
858             SubDirectory => { TagTable => 'Image::ExifTool::XMP::GCamera' },
859             },
860             GCreations => {
861             Name => 'GCreations',
862             SubDirectory => { TagTable => 'Image::ExifTool::XMP::GCreations' },
863             },
864             dwc => {
865             Name => 'dwc',
866             SubDirectory => { TagTable => 'Image::ExifTool::DarwinCore::Main' },
867             },
868             getty => {
869             Name => 'getty',
870             SubDirectory => { TagTable => 'Image::ExifTool::XMP::GettyImages' },
871             },
872             'drone-dji' => {
873             Name => 'drone-dji',
874             SubDirectory => { TagTable => 'Image::ExifTool::DJI::XMP' },
875             },
876             LImage => {
877             Name => 'LImage',
878             SubDirectory => { TagTable => 'Image::ExifTool::XMP::LImage' },
879             },
880             Device => {
881             Name => 'Device',
882             SubDirectory => { TagTable => 'Image::ExifTool::XMP::Device' },
883             },
884             sdc => {
885             Name => 'sdc',
886             SubDirectory => { TagTable => 'Image::ExifTool::Nikon::sdc' },
887             },
888             ast => {
889             Name => 'ast',
890             SubDirectory => { TagTable => 'Image::ExifTool::Nikon::ast' },
891             },
892             nine => {
893             Name => 'nine',
894             SubDirectory => { TagTable => 'Image::ExifTool::Nikon::nine' },
895             },
896             );
897              
898             # hack to allow XML containing Dublin Core metadata to be handled like XMP (eg. EPUB - see ZIP.pm)
899             %Image::ExifTool::XMP::XML = (
900             GROUPS => { 0 => 'XML', 1 => 'XML', 2 => 'Unknown' },
901             PROCESS_PROC => \&ProcessXMP,
902             dc => {
903             Name => 'dc',
904             SubDirectory => { TagTable => 'Image::ExifTool::XMP::dc' },
905             },
906             lastUpdate => {
907             Groups => { 2 => 'Time' },
908             ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)',
909             PrintConv => '$self->ConvertDateTime($val)',
910             },
911             );
912              
913             #
914             # Tag tables for all XMP namespaces:
915             #
916             # Writable - only need to define this for writable tags if not plain text
917             # (boolean, integer, rational, real, date or lang-alt)
918             # List - XMP list type (Bag, Seq or Alt, or set to 1 for elements in Struct lists --
919             # this is necessary to obtain proper list behaviour when reading/writing)
920             #
921             # (Note that family 1 group names are generated from the property namespace, not
922             # the group1 names below which exist so the groups will appear in the list.)
923             #
924             %xmpTableDefaults = (
925             WRITE_PROC => \&WriteXMP,
926             CHECK_PROC => \&CheckXMP,
927             WRITABLE => 'string',
928             LANG_INFO => \&GetLangInfo,
929             );
930              
931             # rdf attributes extracted
932             %Image::ExifTool::XMP::rdf = (
933             %xmpTableDefaults,
934             GROUPS => { 1 => 'XMP-rdf', 2 => 'Document' },
935             NAMESPACE => 'rdf',
936             NOTES => q{
937             Most RDF attributes are handled internally, but the "about" attribute is
938             treated specially to allow it to be set to a specific value if required.
939             },
940             about => { Protected => 1 },
941             );
942              
943             # x attributes extracted
944             %Image::ExifTool::XMP::x = (
945             %xmpTableDefaults,
946             GROUPS => { 1 => 'XMP-x', 2 => 'Document' },
947             NAMESPACE => 'x',
948             NOTES => qq{
949             The "x" namespace is used for the "xmpmeta" wrapper, and may contain an
950             "xmptk" attribute that is extracted as the XMPToolkit tag. When writing,
951             the XMPToolkit tag is generated automatically by ExifTool unless
952             specifically set to another value.
953             },
954             xmptk => { Name => 'XMPToolkit', Protected => 1 },
955             );
956              
957             # Dublin Core namespace properties (dc)
958             %Image::ExifTool::XMP::dc = (
959             %xmpTableDefaults,
960             GROUPS => { 1 => 'XMP-dc', 2 => 'Other' },
961             NAMESPACE => 'dc',
962             TABLE_DESC => 'XMP Dublin Core',
963             NOTES => 'Dublin Core namespace tags.',
964             contributor => { Groups => { 2 => 'Author' }, List => 'Bag' },
965             coverage => { },
966             creator => { Groups => { 2 => 'Author' }, List => 'Seq' },
967             date => { Groups => { 2 => 'Time' }, List => 'Seq', %dateTimeInfo },
968             description => { Groups => { 2 => 'Image' }, Writable => 'lang-alt' },
969             'format' => { Groups => { 2 => 'Image' } },
970             identifier => { Groups => { 2 => 'Image' } },
971             language => { List => 'Bag' },
972             publisher => { Groups => { 2 => 'Author' }, List => 'Bag' },
973             relation => { List => 'Bag' },
974             rights => { Groups => { 2 => 'Author' }, Writable => 'lang-alt' },
975             source => { Groups => { 2 => 'Author' }, Avoid => 1 },
976             subject => { Groups => { 2 => 'Image' }, List => 'Bag' },
977             title => { Groups => { 2 => 'Image' }, Writable => 'lang-alt' },
978             type => { Groups => { 2 => 'Image' }, List => 'Bag' },
979             );
980              
981             # XMP namespace properties (xmp, xap)
982             %Image::ExifTool::XMP::xmp = (
983             %xmpTableDefaults,
984             GROUPS => { 1 => 'XMP-xmp', 2 => 'Image' },
985             NAMESPACE => 'xmp',
986             NOTES => q{
987             XMP namespace tags. If the older "xap", "xapBJ", "xapMM" or "xapRights"
988             namespace prefixes are found, they are translated to the newer "xmp",
989             "xmpBJ", "xmpMM" and "xmpRights" prefixes for use in family 1 group names.
990             },
991             Advisory => { List => 'Bag', Notes => 'deprecated' },
992             BaseURL => { },
993             # (date/time tags not as reliable as EXIF)
994             CreateDate => { Groups => { 2 => 'Time' }, %dateTimeInfo, Priority => 0 },
995             CreatorTool => { },
996             Identifier => { Avoid => 1, List => 'Bag' },
997             Label => { },
998             MetadataDate=> { Groups => { 2 => 'Time' }, %dateTimeInfo },
999             ModifyDate => { Groups => { 2 => 'Time' }, %dateTimeInfo, Priority => 0 },
1000             Nickname => { },
1001             Rating => { Writable => 'real', Notes => 'a value from 0 to 5, or -1 for "rejected"' },
1002             RatingPercent=>{ Writable => 'real', Avoid => 1, Notes => 'non-standard' },
1003             Thumbnails => {
1004             FlatName => 'Thumbnail',
1005             Struct => \%sThumbnail,
1006             List => 'Alt',
1007             },
1008             # the following written by Adobe InDesign, not part of XMP spec:
1009             PageInfo => {
1010             FlatName => 'PageImage',
1011             Struct => \%sPageInfo,
1012             List => 'Seq',
1013             },
1014             PageInfoImage => { Name => 'PageImage', Flat => 1 },
1015             Title => { Avoid => 1, Notes => 'non-standard', Writable => 'lang-alt' }, #11
1016             Author => { Avoid => 1, Notes => 'non-standard', Groups => { 2 => 'Author' } }, #11
1017             Keywords => { Avoid => 1, Notes => 'non-standard' }, #11
1018             Description => { Avoid => 1, Notes => 'non-standard', Writable => 'lang-alt' }, #11
1019             Format => { Avoid => 1, Notes => 'non-standard' }, #11
1020             );
1021              
1022             # XMP Rights Management namespace properties (xmpRights, xapRights)
1023             %Image::ExifTool::XMP::xmpRights = (
1024             %xmpTableDefaults,
1025             GROUPS => { 1 => 'XMP-xmpRights', 2 => 'Author' },
1026             NAMESPACE => 'xmpRights',
1027             NOTES => 'XMP Rights Management namespace tags.',
1028             Certificate => { },
1029             Marked => { Writable => 'boolean' },
1030             Owner => { List => 'Bag' },
1031             UsageTerms => { Writable => 'lang-alt' },
1032             WebStatement => { },
1033             );
1034              
1035             # XMP Note namespace properties (xmpNote)
1036             %Image::ExifTool::XMP::xmpNote = (
1037             %xmpTableDefaults,
1038             GROUPS => { 1 => 'XMP-xmpNote' },
1039             NAMESPACE => 'xmpNote',
1040             NOTES => 'XMP Note namespace tags.',
1041             HasExtendedXMP => {
1042             Notes => q{
1043             this tag is protected so it is not writable directly. Instead, it is set
1044             automatically to the GUID of the extended XMP when writing extended XMP to a
1045             JPEG image
1046             },
1047             Protected => 2,
1048             },
1049             );
1050              
1051             # XMP xmpMM ManifestItem struct (ref PH, written by Adobe PDF library 8.0)
1052             my %sManifestItem = (
1053             STRUCT_NAME => 'ManifestItem',
1054             NAMESPACE => 'stMfs',
1055             linkForm => { },
1056             placedXResolution => { Namespace => 'xmpMM', Writable => 'real' },
1057             placedYResolution => { Namespace => 'xmpMM', Writable => 'real' },
1058             placedResolutionUnit=> { Namespace => 'xmpMM' },
1059             reference => { Struct => \%sResourceRef },
1060             );
1061              
1062             # the xmpMM Pantry
1063             my %sPantryItem = (
1064             STRUCT_NAME => 'PantryItem',
1065             NAMESPACE => undef, # stores any top-level XMP tags
1066             NOTES => q{
1067             This structure must have an InstanceID field, but may also contain any other
1068             XMP properties.
1069             },
1070             InstanceID => { Namespace => 'xmpMM', List => 0 },
1071             );
1072              
1073             # XMP Media Management namespace properties (xmpMM, xapMM)
1074             %Image::ExifTool::XMP::xmpMM = (
1075             %xmpTableDefaults,
1076             GROUPS => { 1 => 'XMP-xmpMM', 2 => 'Other' },
1077             NAMESPACE => 'xmpMM',
1078             TABLE_DESC => 'XMP Media Management',
1079             NOTES => 'XMP Media Management namespace tags.',
1080             DerivedFrom => { Struct => \%sResourceRef },
1081             DocumentID => { },
1082             History => { Struct => \%sResourceEvent, List => 'Seq' },
1083             # we treat these like list items since History is a list
1084             Ingredients => { Struct => \%sResourceRef, List => 'Bag' },
1085             InstanceID => { }, #PH (CS3)
1086             ManagedFrom => { Struct => \%sResourceRef },
1087             Manager => { Groups => { 2 => 'Author' } },
1088             ManageTo => { Groups => { 2 => 'Author' } },
1089             ManageUI => { },
1090             ManagerVariant => { },
1091             Manifest => { Struct => \%sManifestItem, List => 'Bag' },
1092             OriginalDocumentID=> { },
1093             Pantry => { Struct => \%sPantryItem, List => 'Bag' },
1094             PreservedFileName => { }, # undocumented
1095             RenditionClass => { },
1096             RenditionParams => { },
1097             VersionID => { },
1098             Versions => { Struct => \%sVersion, List => 'Seq' },
1099             LastURL => { }, # (deprecated)
1100             RenditionOf => { Struct => \%sResourceRef }, # (deprecated)
1101             SaveID => { Writable => 'integer' }, # (deprecated)
1102             subject => { List => 'Seq', Avoid => 1, Notes => 'undocumented' },
1103             );
1104              
1105             # XMP Basic Job Ticket namespace properties (xmpBJ, xapBJ)
1106             %Image::ExifTool::XMP::xmpBJ = (
1107             %xmpTableDefaults,
1108             GROUPS => { 1 => 'XMP-xmpBJ', 2 => 'Other' },
1109             NAMESPACE => 'xmpBJ',
1110             TABLE_DESC => 'XMP Basic Job Ticket',
1111             NOTES => 'XMP Basic Job Ticket namespace tags.',
1112             # Note: JobRef is a List of structures. To accomplish this, we set the XMP
1113             # List=>'Bag', but since SubDirectory is defined, this tag isn't writable
1114             # directly. Then we need to set List=>1 for the members so the Writer logic
1115             # will allow us to add list items.
1116             JobRef => { Struct => \%sJobRef, List => 'Bag' },
1117             );
1118              
1119             # XMP Paged-Text namespace properties (xmpTPg)
1120             %Image::ExifTool::XMP::xmpTPg = (
1121             %xmpTableDefaults,
1122             GROUPS => { 1 => 'XMP-xmpTPg', 2 => 'Image' },
1123             NAMESPACE => 'xmpTPg',
1124             TABLE_DESC => 'XMP Paged-Text',
1125             NOTES => 'XMP Paged-Text namespace tags.',
1126             MaxPageSize => { Struct => \%sDimensions },
1127             NPages => { Writable => 'integer' },
1128             Fonts => {
1129             FlatName => '',
1130             Struct => \%sFont,
1131             List => 'Bag',
1132             },
1133             FontsVersionString => { Name => 'FontVersion', Flat => 1 },
1134             FontsComposite => { Name => 'FontComposite', Flat => 1 },
1135             Colorants => {
1136             FlatName => 'Colorant',
1137             Struct => \%sColorant,
1138             List => 'Seq',
1139             },
1140             PlateNames => { List => 'Seq' },
1141             # the following found in an AI file:
1142             HasVisibleTransparency => { Writable => 'boolean' },
1143             HasVisibleOverprint => { Writable => 'boolean' },
1144             SwatchGroups => {
1145             Struct => \%sSwatchGroup,
1146             List => 'Seq',
1147             },
1148             SwatchGroupsColorants => { Name => 'SwatchGroupsColorants', Flat => 1 },
1149             SwatchGroupsGroupName => { Name => 'SwatchGroupName', Flat => 1 },
1150             SwatchGroupsGroupType => { Name => 'SwatchGroupType', Flat => 1 },
1151             );
1152              
1153             # PDF namespace properties (pdf)
1154             %Image::ExifTool::XMP::pdf = (
1155             %xmpTableDefaults,
1156             GROUPS => { 1 => 'XMP-pdf', 2 => 'Image' },
1157             NAMESPACE => 'pdf',
1158             TABLE_DESC => 'XMP PDF',
1159             NOTES => q{
1160             Adobe PDF namespace tags. The official XMP specification defines only
1161             Keywords, PDFVersion, Producer and Trapped. The other tags are included
1162             because they have been observed in PDF files, but some are avoided when
1163             writing due to name conflicts with other XMP namespaces.
1164             },
1165             Author => { Groups => { 2 => 'Author' } }, #PH
1166             ModDate => { Groups => { 2 => 'Time' }, %dateTimeInfo }, #PH
1167             CreationDate=> { Groups => { 2 => 'Time' }, %dateTimeInfo }, #PH
1168             Creator => { Groups => { 2 => 'Author' }, Avoid => 1 },
1169             Copyright => { Groups => { 2 => 'Author' }, Avoid => 1 }, #PH
1170             Marked => { Avoid => 1, Writable => 'boolean' }, #PH
1171             Subject => { Avoid => 1 },
1172             Title => { Avoid => 1 },
1173             Trapped => { #PH
1174             # remove leading '/' from '/True' or '/False'
1175             ValueConv => '$val=~s{^/}{}; $val',
1176             ValueConvInv => '"/$val"',
1177             PrintConv => { True => 'True', False => 'False', Unknown => 'Unknown' },
1178             },
1179             Keywords => { Priority => -1 }, # (-1 to get below Priority 0 PDF:Keywords)
1180             PDFVersion => { },
1181             Producer => { Groups => { 2 => 'Author' } },
1182             );
1183              
1184             # PDF extension namespace properties (pdfx)
1185             %Image::ExifTool::XMP::pdfx = (
1186             %xmpTableDefaults,
1187             GROUPS => { 1 => 'XMP-pdfx', 2 => 'Document' },
1188             NAMESPACE => 'pdfx',
1189             NOTES => q{
1190             PDF extension tags. This namespace is used to store application-defined PDF
1191             information, so there are no pre-defined tags. User-defined tags must be
1192             created to enable writing of XMP-pdfx information.
1193             },
1194             );
1195              
1196             # Photoshop namespace properties (photoshop)
1197             %Image::ExifTool::XMP::photoshop = (
1198             %xmpTableDefaults,
1199             GROUPS => { 1 => 'XMP-photoshop', 2 => 'Image' },
1200             NAMESPACE => 'photoshop',
1201             TABLE_DESC => 'XMP Photoshop',
1202             NOTES => 'Adobe Photoshop namespace tags.',
1203             AuthorsPosition => { Groups => { 2 => 'Author' } },
1204             CaptionWriter => { Groups => { 2 => 'Author' } },
1205             Category => { },
1206             City => { Groups => { 2 => 'Location' } },
1207             ColorMode => {
1208             Writable => 'integer', # (as of July 2010 spec, courtesy of yours truly)
1209             PrintConvColumns => 2,
1210             PrintConv => {
1211             0 => 'Bitmap',
1212             1 => 'Grayscale',
1213             2 => 'Indexed',
1214             3 => 'RGB',
1215             4 => 'CMYK',
1216             7 => 'Multichannel',
1217             8 => 'Duotone',
1218             9 => 'Lab',
1219             },
1220             },
1221             Country => { Groups => { 2 => 'Location' } },
1222             Credit => { Groups => { 2 => 'Author' } },
1223             DateCreated => { Groups => { 2 => 'Time' }, %dateTimeInfo },
1224             DocumentAncestors => {
1225             List => 'Bag',
1226             # Contrary to their own XMP specification, Adobe writes this as a simple Bag
1227             # of strings instead of structures, so comment out the structure definition...
1228             # FlatName => 'Document',
1229             # Struct => {
1230             # STRUCT_NAME => 'Ancestor',
1231             # NAMESPACE => 'photoshop',
1232             # AncestorID => { },
1233             # },
1234             },
1235             Headline => { },
1236             History => { }, #PH (CS3)
1237             ICCProfile => { Name => 'ICCProfileName' }, #PH
1238             Instructions => { },
1239             LegacyIPTCDigest=> { }, #PH
1240             SidecarForExtension => { }, #PH (CS3)
1241             Source => { Groups => { 2 => 'Author' } },
1242             State => { Groups => { 2 => 'Location' } },
1243             # the XMP spec doesn't show SupplementalCategories as a 'Bag', but
1244             # that's the way Photoshop writes it [fixed in the June 2005 XMP spec].
1245             # Also, it is incorrectly listed as "SupplementalCategory" in the
1246             # IPTC Standard Photo Metadata docs (2008rev2 and July 2009rev1) - PH
1247             SupplementalCategories => { List => 'Bag' },
1248             TextLayers => {
1249             FlatName => 'Text',
1250             List => 'Seq',
1251             Struct => {
1252             STRUCT_NAME => 'Layer',
1253             NAMESPACE => 'photoshop',
1254             LayerName => { },
1255             LayerText => { },
1256             },
1257             },
1258             TransmissionReference => { Notes => 'Now used as a job identifier' },
1259             Urgency => {
1260             Writable => 'integer',
1261             Notes => 'should be in the range 1-8 to conform with the XMP spec',
1262             PrintConv => { # (same values as IPTC:Urgency)
1263             0 => '0 (reserved)', # (not standard XMP)
1264             1 => '1 (most urgent)',
1265             2 => 2,
1266             3 => 3,
1267             4 => 4,
1268             5 => '5 (normal urgency)',
1269             6 => 6,
1270             7 => 7,
1271             8 => '8 (least urgent)',
1272             9 => '9 (user-defined priority)', # (not standard XMP)
1273             },
1274             },
1275             EmbeddedXMPDigest => { }, #PH (LR5)
1276             );
1277              
1278             # Photoshop Camera Raw namespace properties (crs) - (ref 8,PH)
1279             %Image::ExifTool::XMP::crs = (
1280             %xmpTableDefaults,
1281             GROUPS => { 1 => 'XMP-crs', 2 => 'Image' },
1282             NAMESPACE => 'crs',
1283             TABLE_DESC => 'Photoshop Camera Raw namespace',
1284             NOTES => q{
1285             Photoshop Camera Raw namespace tags. It is a shame that Adobe pollutes the
1286             metadata space with these incredibly bulky image editing parameters.
1287             },
1288             AlreadyApplied => { Writable => 'boolean' }, #PH (written by LightRoom beta 4.1)
1289             AutoBrightness => { Writable => 'boolean' },
1290             AutoContrast => { Writable => 'boolean' },
1291             AutoExposure => { Writable => 'boolean' },
1292             AutoShadows => { Writable => 'boolean' },
1293             BlueHue => { Writable => 'integer' },
1294             BlueSaturation => { Writable => 'integer' },
1295             Brightness => { Writable => 'integer' },
1296             CameraProfile => { },
1297             ChromaticAberrationB=> { Writable => 'integer' },
1298             ChromaticAberrationR=> { Writable => 'integer' },
1299             ColorNoiseReduction => { Writable => 'integer' },
1300             Contrast => { Writable => 'integer', Avoid => 1 },
1301             Converter => { }, #PH guess (found in EXIF)
1302             CropTop => { Writable => 'real' },
1303             CropLeft => { Writable => 'real' },
1304             CropBottom => { Writable => 'real' },
1305             CropRight => { Writable => 'real' },
1306             CropAngle => { Writable => 'real' },
1307             CropWidth => { Writable => 'real' },
1308             CropHeight => { Writable => 'real' },
1309             CropUnits => {
1310             Writable => 'integer',
1311             PrintConv => {
1312             0 => 'pixels',
1313             1 => 'inches',
1314             2 => 'cm',
1315             },
1316             },
1317             Exposure => { Writable => 'real' },
1318             GreenHue => { Writable => 'integer' },
1319             GreenSaturation => { Writable => 'integer' },
1320             HasCrop => { Writable => 'boolean' },
1321             HasSettings => { Writable => 'boolean' },
1322             LuminanceSmoothing => { Writable => 'integer' },
1323             MoireFilter => { PrintConv => { Off=>'Off', On=>'On' } },
1324             RawFileName => { },
1325             RedHue => { Writable => 'integer' },
1326             RedSaturation => { Writable => 'integer' },
1327             Saturation => { Writable => 'integer', Avoid => 1 },
1328             Shadows => { Writable => 'integer' },
1329             ShadowTint => { Writable => 'integer' },
1330             Sharpness => { Writable => 'integer', Avoid => 1 },
1331             Smoothness => { Writable => 'integer' },
1332             Temperature => { Writable => 'integer', Name => 'ColorTemperature' },
1333             Tint => { Writable => 'integer' },
1334             ToneCurve => { List => 'Seq' },
1335             ToneCurveName => {
1336             PrintConv => {
1337             Linear => 'Linear',
1338             'Medium Contrast' => 'Medium Contrast',
1339             'Strong Contrast' => 'Strong Contrast',
1340             Custom => 'Custom',
1341             },
1342             },
1343             Version => { },
1344             VignetteAmount => { Writable => 'integer' },
1345             VignetteMidpoint=> { Writable => 'integer' },
1346             WhiteBalance => {
1347             Avoid => 1,
1348             PrintConv => {
1349             'As Shot' => 'As Shot',
1350             Auto => 'Auto',
1351             Daylight => 'Daylight',
1352             Cloudy => 'Cloudy',
1353             Shade => 'Shade',
1354             Tungsten => 'Tungsten',
1355             Fluorescent => 'Fluorescent',
1356             Flash => 'Flash',
1357             Custom => 'Custom',
1358             },
1359             },
1360             # new tags observed in Adobe Lightroom output - PH
1361             CameraProfileDigest => { },
1362             Clarity => { Writable => 'integer' },
1363             ConvertToGrayscale => { Writable => 'boolean' },
1364             Defringe => { Writable => 'integer' },
1365             FillLight => { Writable => 'integer' },
1366             HighlightRecovery => { Writable => 'integer' },
1367             HueAdjustmentAqua => { Writable => 'integer' },
1368             HueAdjustmentBlue => { Writable => 'integer' },
1369             HueAdjustmentGreen => { Writable => 'integer' },
1370             HueAdjustmentMagenta => { Writable => 'integer' },
1371             HueAdjustmentOrange => { Writable => 'integer' },
1372             HueAdjustmentPurple => { Writable => 'integer' },
1373             HueAdjustmentRed => { Writable => 'integer' },
1374             HueAdjustmentYellow => { Writable => 'integer' },
1375             IncrementalTemperature => { Writable => 'integer' },
1376             IncrementalTint => { Writable => 'integer' },
1377             LuminanceAdjustmentAqua => { Writable => 'integer' },
1378             LuminanceAdjustmentBlue => { Writable => 'integer' },
1379             LuminanceAdjustmentGreen => { Writable => 'integer' },
1380             LuminanceAdjustmentMagenta => { Writable => 'integer' },
1381             LuminanceAdjustmentOrange => { Writable => 'integer' },
1382             LuminanceAdjustmentPurple => { Writable => 'integer' },
1383             LuminanceAdjustmentRed => { Writable => 'integer' },
1384             LuminanceAdjustmentYellow => { Writable => 'integer' },
1385             ParametricDarks => { Writable => 'integer' },
1386             ParametricHighlights => { Writable => 'integer' },
1387             ParametricHighlightSplit => { Writable => 'integer' },
1388             ParametricLights => { Writable => 'integer' },
1389             ParametricMidtoneSplit => { Writable => 'integer' },
1390             ParametricShadows => { Writable => 'integer' },
1391             ParametricShadowSplit => { Writable => 'integer' },
1392             SaturationAdjustmentAqua => { Writable => 'integer' },
1393             SaturationAdjustmentBlue => { Writable => 'integer' },
1394             SaturationAdjustmentGreen => { Writable => 'integer' },
1395             SaturationAdjustmentMagenta => { Writable => 'integer' },
1396             SaturationAdjustmentOrange => { Writable => 'integer' },
1397             SaturationAdjustmentPurple => { Writable => 'integer' },
1398             SaturationAdjustmentRed => { Writable => 'integer' },
1399             SaturationAdjustmentYellow => { Writable => 'integer' },
1400             SharpenDetail => { Writable => 'integer' },
1401             SharpenEdgeMasking => { Writable => 'integer' },
1402             SharpenRadius => { Writable => 'real' },
1403             SplitToningBalance => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
1404             SplitToningHighlightHue => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
1405             SplitToningHighlightSaturation => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
1406             SplitToningShadowHue => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
1407             SplitToningShadowSaturation => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
1408             Vibrance => { Writable => 'integer' },
1409             # new tags written by LR 1.4 (not sure in what version they first appeared)
1410             GrayMixerRed => { Writable => 'integer' },
1411             GrayMixerOrange => { Writable => 'integer' },
1412             GrayMixerYellow => { Writable => 'integer' },
1413             GrayMixerGreen => { Writable => 'integer' },
1414             GrayMixerAqua => { Writable => 'integer' },
1415             GrayMixerBlue => { Writable => 'integer' },
1416             GrayMixerPurple => { Writable => 'integer' },
1417             GrayMixerMagenta => { Writable => 'integer' },
1418             RetouchInfo => { List => 'Seq' },
1419             RedEyeInfo => { List => 'Seq' },
1420             # new tags written by LR 2.0 (ref PH)
1421             CropUnit => { # was the XMP documentation wrong with "CropUnits"??
1422             Writable => 'integer',
1423             PrintConv => {
1424             0 => 'pixels',
1425             1 => 'inches',
1426             2 => 'cm',
1427             # have seen a value of 3 here! - PH
1428             },
1429             },
1430             PostCropVignetteAmount => { Writable => 'integer' },
1431             PostCropVignetteMidpoint => { Writable => 'integer' },
1432             PostCropVignetteFeather => { Writable => 'integer' },
1433             PostCropVignetteRoundness => { Writable => 'integer' },
1434             PostCropVignetteStyle => { Writable => 'integer' },
1435             # disable List behaviour of flattened Gradient/PaintBasedCorrections
1436             # because these are nested in lists and the flattened tags can't
1437             # do justice to this complex structure
1438             GradientBasedCorrections => {
1439             FlatName => 'GradientBasedCorr',
1440             Struct => \%sCorrection,
1441             List => 'Seq',
1442             },
1443             GradientBasedCorrectionsCorrectionMasks => {
1444             Name => 'GradientBasedCorrMasks',
1445             FlatName => 'GradientBasedCorrMask',
1446             Flat => 1
1447             },
1448             GradientBasedCorrectionsCorrectionMasksDabs => {
1449             Name => 'GradientBasedCorrMaskDabs',
1450             Flat => 1, List => 0,
1451             },
1452             PaintBasedCorrections => {
1453             FlatName => 'PaintCorrection',
1454             Struct => \%sCorrection,
1455             List => 'Seq',
1456             },
1457             PaintBasedCorrectionsCorrectionMasks => {
1458             Name => 'PaintBasedCorrectionMasks',
1459             FlatName => 'PaintCorrectionMask',
1460             Flat => 1,
1461             },
1462             PaintBasedCorrectionsCorrectionMasksDabs => {
1463             Name => 'PaintCorrectionMaskDabs',
1464             Flat => 1, List => 0,
1465             },
1466             # new tags written by LR 3 (thanks Wolfgang Guelcker)
1467             ProcessVersion => { },
1468             LensProfileEnable => { Writable => 'integer' },
1469             LensProfileSetup => { },
1470             LensProfileName => { },
1471             LensProfileFilename => { },
1472             LensProfileDigest => { },
1473             LensProfileDistortionScale => { Writable => 'integer' },
1474             LensProfileChromaticAberrationScale => { Writable => 'integer' },
1475             LensProfileVignettingScale => { Writable => 'integer' },
1476             LensManualDistortionAmount => { Writable => 'integer' },
1477             PerspectiveVertical => { Writable => 'integer' },
1478             PerspectiveHorizontal => { Writable => 'integer' },
1479             PerspectiveRotate => { Writable => 'real' },
1480             PerspectiveScale => { Writable => 'integer' },
1481             CropConstrainToWarp => { Writable => 'integer' },
1482             LuminanceNoiseReductionDetail => { Writable => 'integer' },
1483             LuminanceNoiseReductionContrast => { Writable => 'integer' },
1484             ColorNoiseReductionDetail => { Writable => 'integer' },
1485             GrainAmount => { Writable => 'integer' },
1486             GrainSize => { Writable => 'integer' },
1487             GrainFrequency => { Writable => 'integer' },
1488             # new tags written by LR4
1489             AutoLateralCA => { Writable => 'integer' },
1490             Exposure2012 => { Writable => 'real' },
1491             Contrast2012 => { Writable => 'integer' },
1492             Highlights2012 => { Writable => 'integer' },
1493             Highlight2012 => { Writable => 'integer' }, # (written by Nikon software)
1494             Shadows2012 => { Writable => 'integer' },
1495             Whites2012 => { Writable => 'integer' },
1496             Blacks2012 => { Writable => 'integer' },
1497             Clarity2012 => { Writable => 'integer' },
1498             PostCropVignetteHighlightContrast => { Writable => 'integer' },
1499             ToneCurveName2012 => { },
1500             ToneCurveRed => { List => 'Seq' },
1501             ToneCurveGreen => { List => 'Seq' },
1502             ToneCurveBlue => { List => 'Seq' },
1503             ToneCurvePV2012 => { List => 'Seq' },
1504             ToneCurvePV2012Red => { List => 'Seq' },
1505             ToneCurvePV2012Green => { List => 'Seq' },
1506             ToneCurvePV2012Blue => { List => 'Seq' },
1507             DefringePurpleAmount => { Writable => 'integer' },
1508             DefringePurpleHueLo => { Writable => 'integer' },
1509             DefringePurpleHueHi => { Writable => 'integer' },
1510             DefringeGreenAmount => { Writable => 'integer' },
1511             DefringeGreenHueLo => { Writable => 'integer' },
1512             DefringeGreenHueHi => { Writable => 'integer' },
1513             # new tags written by LR5
1514             AutoWhiteVersion => { Writable => 'integer' },
1515             CircularGradientBasedCorrections => {
1516             FlatName => 'CircGradBasedCorr',
1517             Struct => \%sCorrection,
1518             List => 'Seq',
1519             },
1520             CircularGradientBasedCorrectionsCorrectionMasks => {
1521             Name => 'CircGradBasedCorrMasks',
1522             FlatName => 'CircGradBasedCorrMask',
1523             Flat => 1
1524             },
1525             CircularGradientBasedCorrectionsCorrectionMasksDabs => {
1526             Name => 'CircGradBasedCorrMaskDabs',
1527             Flat => 1, List => 0,
1528             },
1529             ColorNoiseReductionSmoothness => { Writable => 'integer' },
1530             PerspectiveAspect => { Writable => 'integer' },
1531             PerspectiveUpright => { Writable => 'integer' },
1532             RetouchAreas => {
1533             FlatName => 'RetouchArea',
1534             Struct => \%sRetouchArea,
1535             List => 'Seq',
1536             },
1537             RetouchAreasMasks => {
1538             Name => 'RetouchAreaMasks',
1539             FlatName => 'RetouchAreaMask',
1540             Flat => 1
1541             },
1542             RetouchAreasMasksDabs => {
1543             Name => 'RetouchAreaMaskDabs',
1544             Flat => 1, List => 0,
1545             },
1546             UprightVersion => { Writable => 'integer' },
1547             UprightCenterMode => { Writable => 'integer' },
1548             UprightCenterNormX => { Writable => 'real' },
1549             UprightCenterNormY => { Writable => 'real' },
1550             UprightFocalMode => { Writable => 'integer' },
1551             UprightFocalLength35mm => { Writable => 'real' },
1552             UprightPreview => { Writable => 'boolean' },
1553             UprightTransformCount => { Writable => 'integer' },
1554             UprightDependentDigest => { },
1555             UprightTransform_0 => { },
1556             UprightTransform_1 => { },
1557             UprightTransform_2 => { },
1558             UprightTransform_3 => { },
1559             UprightTransform_4 => { },
1560             UprightTransform_5 => { },
1561             # more stuff seen in lens profile file (unknown source)
1562             What => { }, # (with value "LensProfileDefaultSettings")
1563             LensProfileMatchKeyExifMake => { },
1564             LensProfileMatchKeyExifModel => { },
1565             LensProfileMatchKeyCameraModelName => { },
1566             LensProfileMatchKeyLensInfo => { },
1567             LensProfileMatchKeyLensID => { },
1568             LensProfileMatchKeyLensName => { },
1569             LensProfileMatchKeyIsRaw => { Writable => 'boolean' },
1570             LensProfileMatchKeySensorFormatFactor=>{ Writable => 'real' },
1571             # more stuff (ref forum6993)
1572             DefaultAutoTone => { Writable => 'boolean' },
1573             DefaultAutoGray => { Writable => 'boolean' },
1574             DefaultsSpecificToSerial => { Writable => 'boolean' },
1575             DefaultsSpecificToISO => { Writable => 'boolean' },
1576             DNGIgnoreSidecars => { Writable => 'boolean' },
1577             NegativeCachePath => { },
1578             NegativeCacheMaximumSize => { Writable => 'real' },
1579             NegativeCacheLargePreviewSize => { Writable => 'integer' },
1580             JPEGHandling => { },
1581             TIFFHandling => { },
1582             Dehaze => { Writable => 'real' },
1583             ToneMapStrength => { Writable => 'real' },
1584             # yet more
1585             PerspectiveX => { Writable => 'real' },
1586             PerspectiveY => { Writable => 'real' },
1587             UprightFourSegmentsCount => { Writable => 'integer' },
1588             AutoTone => { Writable => 'boolean' },
1589             Texture => { Writable => 'integer' },
1590             # more stuff (ref forum10721)
1591             OverrideLookVignette => { Writable => 'boolean' },
1592             Look => {
1593             Struct => {
1594             STRUCT_NAME => 'Look',
1595             NAMESPACE => 'crs',
1596             Name => { },
1597             Amount => { },
1598             Cluster => { },
1599             UUID => { },
1600             SupportsMonochrome => { },
1601             SupportsAmount => { },
1602             SupportsOutputReferred => { },
1603             Copyright => { },
1604             Group => { Writable => 'lang-alt' },
1605             Parameters => {
1606             Struct => {
1607             STRUCT_NAME => 'LookParms',
1608             NAMESPACE => 'crs',
1609             Version => { },
1610             ProcessVersion => { },
1611             Clarity2012 => { },
1612             ConvertToGrayscale => { },
1613             CameraProfile => { },
1614             LookTable => { },
1615             ToneCurvePV2012 => { List => 'Seq' },
1616             ToneCurvePV2012Red => { List => 'Seq' },
1617             ToneCurvePV2012Green => { List => 'Seq' },
1618             ToneCurvePV2012Blue => { List => 'Seq' },
1619             },
1620             },
1621             }
1622             },
1623             # more again (ref forum11258)
1624             GrainSeed => { },
1625             ClipboardOrientation => { Writable => 'integer' },
1626             ClipboardAspectRatio => { Writable => 'integer' },
1627             PresetType => { },
1628             Cluster => { },
1629             UUID => { Avoid => 1 },
1630             SupportsAmount => { Writable => 'boolean' },
1631             SupportsColor => { Writable => 'boolean' },
1632             SupportsMonochrome => { Writable => 'boolean' },
1633             SupportsHighDynamicRange=> { Writable => 'boolean' },
1634             SupportsNormalDynamicRange=> { Writable => 'boolean' },
1635             SupportsSceneReferred => { Writable => 'boolean' },
1636             SupportsOutputReferred => { Writable => 'boolean' },
1637             CameraModelRestriction => { },
1638             Copyright => { Avoid => 1 },
1639             ContactInfo => { },
1640             GrainSeed => { Writable => 'integer' },
1641             Name => { Writable => 'lang-alt', Avoid => 1 },
1642             ShortName => { Writable => 'lang-alt' },
1643             SortName => { Writable => 'lang-alt' },
1644             Group => { Writable => 'lang-alt', Avoid => 1 },
1645             Description => { Writable => 'lang-alt', Avoid => 1 },
1646             # new for DNG converter 13.0
1647             LookName => { NotFlat => 1 }, # (grr... conflicts with "Name" element of "Look" struct!)
1648             # new for Lightroom CC 2021 (ref forum11745)
1649             ColorGradeMidtoneHue => { Writable => 'integer' },
1650             ColorGradeMidtoneSat => { Writable => 'integer' },
1651             ColorGradeShadowLum => { Writable => 'integer' },
1652             ColorGradeMidtoneLum => { Writable => 'integer' },
1653             ColorGradeHighlightLum => { Writable => 'integer' },
1654             ColorGradeBlending => { Writable => 'integer' },
1655             ColorGradeGlobalHue => { Writable => 'integer' },
1656             ColorGradeGlobalSat => { Writable => 'integer' },
1657             ColorGradeGlobalLum => { Writable => 'integer' },
1658             # new for Adobe Camera Raw 13 (ref forum11745)
1659             LensProfileIsEmbedded => { Writable => 'boolean'},
1660             AutoToneDigest => { },
1661             AutoToneDigestNoSat => { },
1662             ToggleStyleDigest => { },
1663             ToggleStyleAmount => { Writable => 'integer' },
1664             # new for LightRoom 11.0
1665             CompatibleVersion => { },
1666             MaskGroupBasedCorrections => {
1667             FlatName => 'MaskGroupBasedCorr',
1668             Struct => \%sCorrection,
1669             List => 'Seq',
1670             },
1671             RangeMaskMapInfo => { Name => 'RangeMask', Struct => \%sRangeMask, FlatName => 'RangeMask' },
1672             );
1673              
1674             # Tiff namespace properties (tiff)
1675             %Image::ExifTool::XMP::tiff = (
1676             %xmpTableDefaults,
1677             GROUPS => { 1 => 'XMP-tiff', 2 => 'Image' },
1678             NAMESPACE => 'tiff',
1679             PRIORITY => 0, # not as reliable as actual TIFF tags
1680             TABLE_DESC => 'XMP TIFF',
1681             NOTES => q{
1682             EXIF namespace for TIFF tags. See
1683             L
1684             for the specification.
1685             },
1686             ImageWidth => { Writable => 'integer' },
1687             ImageLength => { Writable => 'integer', Name => 'ImageHeight' },
1688             BitsPerSample => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
1689             Compression => {
1690             Writable => 'integer',
1691             SeparateTable => 'EXIF Compression',
1692             PrintConv => \%Image::ExifTool::Exif::compression,
1693             },
1694             PhotometricInterpretation => {
1695             Writable => 'integer',
1696             PrintConv => \%Image::ExifTool::Exif::photometricInterpretation,
1697             },
1698             Orientation => {
1699             Writable => 'integer',
1700             PrintConv => \%Image::ExifTool::Exif::orientation,
1701             },
1702             SamplesPerPixel => { Writable => 'integer' },
1703             PlanarConfiguration => {
1704             Writable => 'integer',
1705             PrintConv => {
1706             1 => 'Chunky',
1707             2 => 'Planar',
1708             },
1709             },
1710             YCbCrSubSampling => {
1711             Writable => 'integer',
1712             List => 'Seq',
1713             # join the raw values before conversion to allow PrintConv to operate on
1714             # the combined string as it does for the corresponding EXIF tag
1715             RawJoin => 1,
1716             Notes => q{
1717             while technically this is a list-type tag, for compatibility with its EXIF
1718             counterpart it is written and read as a simple string
1719             },
1720             PrintConv => \%Image::ExifTool::JPEG::yCbCrSubSampling,
1721             },
1722             YCbCrPositioning => {
1723             Writable => 'integer',
1724             PrintConv => {
1725             1 => 'Centered',
1726             2 => 'Co-sited',
1727             },
1728             },
1729             XResolution => { Writable => 'rational' },
1730             YResolution => { Writable => 'rational' },
1731             ResolutionUnit => {
1732             Writable => 'integer',
1733             Notes => 'the value 1 is not standard EXIF',
1734             PrintConv => {
1735             1 => 'None',
1736             2 => 'inches',
1737             3 => 'cm',
1738             },
1739             },
1740             TransferFunction => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
1741             WhitePoint => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1742             PrimaryChromaticities => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1743             YCbCrCoefficients => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1744             ReferenceBlackWhite => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1745             DateTime => { # (EXIF tag named ModifyDate, but this exists in XMP-xmp)
1746             Description => 'Date/Time Modified',
1747             Groups => { 2 => 'Time' },
1748             %dateTimeInfo,
1749             },
1750             ImageDescription => { Writable => 'lang-alt' },
1751             Make => {
1752             Groups => { 2 => 'Camera' },
1753             RawConv => '$$self{Make} ? $val : $$self{Make} = $val',
1754             },
1755             Model => {
1756             Groups => { 2 => 'Camera' },
1757             Description => 'Camera Model Name',
1758             RawConv => '$$self{Model} ? $val : $$self{Model} = $val',
1759             },
1760             Software => { },
1761             Artist => { Groups => { 2 => 'Author' } },
1762             Copyright => { Groups => { 2 => 'Author' }, Writable => 'lang-alt' },
1763             NativeDigest => { Avoid => 1 }, #PH
1764             );
1765              
1766             # Exif namespace properties (exif)
1767             %Image::ExifTool::XMP::exif = (
1768             %xmpTableDefaults,
1769             GROUPS => { 1 => 'XMP-exif', 2 => 'Image' },
1770             NAMESPACE => 'exif',
1771             PRIORITY => 0, # not as reliable as actual EXIF tags
1772             NOTES => q{
1773             EXIF namespace for EXIF tags. See
1774             L
1775             for the specification.
1776             },
1777             ExifVersion => { },
1778             FlashpixVersion => { },
1779             ColorSpace => {
1780             Writable => 'integer',
1781             # (some applications incorrectly write -1 as a long integer)
1782             ValueConv => '$val == 0xffffffff ? 0xffff : $val',
1783             ValueConvInv => '$val',
1784             PrintConv => {
1785             1 => 'sRGB',
1786             2 => 'Adobe RGB',
1787             0xffff => 'Uncalibrated',
1788             },
1789             },
1790             ComponentsConfiguration => {
1791             Writable => 'integer',
1792             List => 'Seq',
1793             AutoSplit => 1,
1794             PrintConvColumns => 2,
1795             PrintConv => {
1796             0 => '-',
1797             1 => 'Y',
1798             2 => 'Cb',
1799             3 => 'Cr',
1800             4 => 'R',
1801             5 => 'G',
1802             6 => 'B',
1803             },
1804             },
1805             CompressedBitsPerPixel => { Writable => 'rational' },
1806             PixelXDimension => { Name => 'ExifImageWidth', Writable => 'integer' },
1807             PixelYDimension => { Name => 'ExifImageHeight', Writable => 'integer' },
1808             MakerNote => { },
1809             UserComment => { Writable => 'lang-alt' },
1810             RelatedSoundFile => { },
1811             DateTimeOriginal => {
1812             Description => 'Date/Time Original',
1813             Groups => { 2 => 'Time' },
1814             %dateTimeInfo,
1815             },
1816             DateTimeDigitized => { # (EXIF tag named CreateDate, but this exists in XMP-xmp)
1817             Description => 'Date/Time Digitized',
1818             Groups => { 2 => 'Time' },
1819             %dateTimeInfo,
1820             },
1821             ExposureTime => {
1822             Writable => 'rational',
1823             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
1824             PrintConvInv => '$val',
1825             },
1826             FNumber => {
1827             Writable => 'rational',
1828             PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
1829             PrintConvInv => '$val',
1830             },
1831             ExposureProgram => {
1832             Groups => { 2 => 'Camera' },
1833             Writable => 'integer',
1834             PrintConv => {
1835             0 => 'Not Defined',
1836             1 => 'Manual',
1837             2 => 'Program AE',
1838             3 => 'Aperture-priority AE',
1839             4 => 'Shutter speed priority AE',
1840             5 => 'Creative (Slow speed)',
1841             6 => 'Action (High speed)',
1842             7 => 'Portrait',
1843             8 => 'Landscape',
1844             },
1845             },
1846             SpectralSensitivity => { Groups => { 2 => 'Camera' } },
1847             ISOSpeedRatings => {
1848             Name => 'ISO',
1849             Writable => 'integer',
1850             List => 'Seq',
1851             AutoSplit => 1,
1852             },
1853             OECF => {
1854             Name => 'Opto-ElectricConvFactor',
1855             FlatName => 'OECF',
1856             Groups => { 2 => 'Camera' },
1857             Struct => \%sOECF,
1858             },
1859             ShutterSpeedValue => {
1860             Writable => 'rational',
1861             ValueConv => 'abs($val)<100 ? 1/(2**$val) : 0',
1862             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
1863             ValueConvInv => '$val>0 ? -log($val)/log(2) : 0',
1864             PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
1865             },
1866             ApertureValue => {
1867             Writable => 'rational',
1868             ValueConv => 'sqrt(2) ** $val',
1869             PrintConv => 'sprintf("%.1f",$val)',
1870             ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
1871             PrintConvInv => '$val',
1872             },
1873             BrightnessValue => { Writable => 'rational' },
1874             ExposureBiasValue => {
1875             Name => 'ExposureCompensation',
1876             Writable => 'rational',
1877             PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)',
1878             PrintConvInv => '$val',
1879             },
1880             MaxApertureValue => {
1881             Groups => { 2 => 'Camera' },
1882             Writable => 'rational',
1883             ValueConv => 'sqrt(2) ** $val',
1884             PrintConv => 'sprintf("%.1f",$val)',
1885             ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
1886             PrintConvInv => '$val',
1887             },
1888             SubjectDistance => {
1889             Groups => { 2 => 'Camera' },
1890             Writable => 'rational',
1891             PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"',
1892             PrintConvInv => '$val=~s/\s*m$//;$val',
1893             },
1894             MeteringMode => {
1895             Groups => { 2 => 'Camera' },
1896             Writable => 'integer',
1897             PrintConv => {
1898             1 => 'Average',
1899             2 => 'Center-weighted average',
1900             3 => 'Spot',
1901             4 => 'Multi-spot',
1902             5 => 'Multi-segment',
1903             6 => 'Partial',
1904             255 => 'Other',
1905             },
1906             },
1907             LightSource => {
1908             Groups => { 2 => 'Camera' },
1909             SeparateTable => 'EXIF LightSource',
1910             PrintConv => \%Image::ExifTool::Exif::lightSource,
1911             },
1912             Flash => {
1913             Groups => { 2 => 'Camera' },
1914             Struct => {
1915             STRUCT_NAME => 'Flash',
1916             NAMESPACE => 'exif',
1917             Fired => { Writable => 'boolean', %boolConv },
1918             Return => {
1919             Writable => 'integer',
1920             PrintConv => {
1921             0 => 'No return detection',
1922             2 => 'Return not detected',
1923             3 => 'Return detected',
1924             },
1925             },
1926             Mode => {
1927             Writable => 'integer',
1928             PrintConv => {
1929             0 => 'Unknown',
1930             1 => 'On',
1931             2 => 'Off',
1932             3 => 'Auto',
1933             },
1934             },
1935             Function => { Writable => 'boolean', %boolConv },
1936             RedEyeMode => { Writable => 'boolean', %boolConv },
1937             },
1938             },
1939             FocalLength=> {
1940             Groups => { 2 => 'Camera' },
1941             Writable => 'rational',
1942             PrintConv => 'sprintf("%.1f mm",$val)',
1943             PrintConvInv => '$val=~s/\s*mm$//;$val',
1944             },
1945             SubjectArea => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
1946             FlashEnergy => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
1947             SpatialFrequencyResponse => {
1948             Groups => { 2 => 'Camera' },
1949             Struct => \%sOECF,
1950             },
1951             FocalPlaneXResolution => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
1952             FocalPlaneYResolution => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
1953             FocalPlaneResolutionUnit => {
1954             Groups => { 2 => 'Camera' },
1955             Writable => 'integer',
1956             Notes => 'values 1, 4 and 5 are not standard EXIF',
1957             PrintConv => {
1958             1 => 'None', # (not standard EXIF)
1959             2 => 'inches',
1960             3 => 'cm',
1961             4 => 'mm', # (not standard EXIF)
1962             5 => 'um', # (not standard EXIF)
1963             },
1964             },
1965             SubjectLocation => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
1966             ExposureIndex => { Writable => 'rational' },
1967             SensingMethod => {
1968             Groups => { 2 => 'Camera' },
1969             Writable => 'integer',
1970             Notes => 'values 1 and 6 are not standard EXIF',
1971             PrintConv => {
1972             1 => 'Monochrome area', # (not standard EXIF)
1973             2 => 'One-chip color area',
1974             3 => 'Two-chip color area',
1975             4 => 'Three-chip color area',
1976             5 => 'Color sequential area',
1977             6 => 'Monochrome linear', # (not standard EXIF)
1978             7 => 'Trilinear',
1979             8 => 'Color sequential linear',
1980             },
1981             },
1982             FileSource => {
1983             Writable => 'integer',
1984             PrintConv => {
1985             1 => 'Film Scanner',
1986             2 => 'Reflection Print Scanner',
1987             3 => 'Digital Camera',
1988             }
1989             },
1990             SceneType => { Writable => 'integer', PrintConv => { 1 => 'Directly photographed' } },
1991             CFAPattern => {
1992             Struct => {
1993             STRUCT_NAME => 'CFAPattern',
1994             NAMESPACE => 'exif',
1995             Columns => { Writable => 'integer' },
1996             Rows => { Writable => 'integer' },
1997             Values => { Writable => 'integer', List => 'Seq' },
1998             },
1999             },
2000             CustomRendered => {
2001             Writable => 'integer',
2002             PrintConv => {
2003             0 => 'Normal',
2004             1 => 'Custom',
2005             },
2006             },
2007             ExposureMode => {
2008             Groups => { 2 => 'Camera' },
2009             Writable => 'integer',
2010             PrintConv => {
2011             0 => 'Auto',
2012             1 => 'Manual',
2013             2 => 'Auto bracket',
2014             },
2015             },
2016             WhiteBalance => {
2017             Groups => { 2 => 'Camera' },
2018             Writable => 'integer',
2019             PrintConv => {
2020             0 => 'Auto',
2021             1 => 'Manual',
2022             },
2023             },
2024             DigitalZoomRatio => { Writable => 'rational' },
2025             FocalLengthIn35mmFilm => {
2026             Name => 'FocalLengthIn35mmFormat',
2027             Writable => 'integer',
2028             Groups => { 2 => 'Camera' },
2029             PrintConv => '"$val mm"',
2030             PrintConvInv => '$val=~s/\s*mm$//;$val',
2031             },
2032             SceneCaptureType => {
2033             Groups => { 2 => 'Camera' },
2034             Writable => 'integer',
2035             PrintConv => {
2036             0 => 'Standard',
2037             1 => 'Landscape',
2038             2 => 'Portrait',
2039             3 => 'Night',
2040             },
2041             },
2042             GainControl => {
2043             Groups => { 2 => 'Camera' },
2044             Writable => 'integer',
2045             PrintConv => {
2046             0 => 'None',
2047             1 => 'Low gain up',
2048             2 => 'High gain up',
2049             3 => 'Low gain down',
2050             4 => 'High gain down',
2051             },
2052             },
2053             Contrast => {
2054             Groups => { 2 => 'Camera' },
2055             Writable => 'integer',
2056             PrintConv => {
2057             0 => 'Normal',
2058             1 => 'Low',
2059             2 => 'High',
2060             },
2061             PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
2062             },
2063             Saturation => {
2064             Groups => { 2 => 'Camera' },
2065             Writable => 'integer',
2066             PrintConv => {
2067             0 => 'Normal',
2068             1 => 'Low',
2069             2 => 'High',
2070             },
2071             PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
2072             },
2073             Sharpness => {
2074             Groups => { 2 => 'Camera' },
2075             Writable => 'integer',
2076             PrintConv => {
2077             0 => 'Normal',
2078             1 => 'Soft',
2079             2 => 'Hard',
2080             },
2081             PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
2082             },
2083             DeviceSettingDescription => {
2084             Groups => { 2 => 'Camera' },
2085             Struct => {
2086             STRUCT_NAME => 'DeviceSettings',
2087             NAMESPACE => 'exif',
2088             Columns => { Writable => 'integer' },
2089             Rows => { Writable => 'integer' },
2090             Settings => { List => 'Seq' },
2091             },
2092             },
2093             SubjectDistanceRange => {
2094             Groups => { 2 => 'Camera' },
2095             Writable => 'integer',
2096             PrintConv => {
2097             0 => 'Unknown',
2098             1 => 'Macro',
2099             2 => 'Close',
2100             3 => 'Distant',
2101             },
2102             },
2103             ImageUniqueID => { },
2104             GPSVersionID => { Groups => { 2 => 'Location' } },
2105             GPSLatitude => { Groups => { 2 => 'Location' }, %latConv },
2106             GPSLongitude => { Groups => { 2 => 'Location' }, %longConv },
2107             GPSAltitudeRef => {
2108             Groups => { 2 => 'Location' },
2109             Writable => 'integer',
2110             PrintConv => {
2111             OTHER => sub {
2112             my ($val, $inv) = @_;
2113             return undef unless $inv and $val =~ /^([-+0-9])/;
2114             return($1 eq '-' ? 1 : 0);
2115             },
2116             0 => 'Above Sea Level',
2117             1 => 'Below Sea Level',
2118             },
2119             },
2120             GPSAltitude => {
2121             Groups => { 2 => 'Location' },
2122             Writable => 'rational',
2123             # extricate unsigned decimal number from string
2124             ValueConvInv => '$val=~/((?=\d|\.\d)\d*(?:\.\d*)?)/ ? $1 : undef',
2125             PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"',
2126             PrintConvInv => '$val=~s/\s*m$//;$val',
2127             },
2128             GPSTimeStamp => {
2129             Name => 'GPSDateTime',
2130             Description => 'GPS Date/Time',
2131             Groups => { 2 => 'Time' },
2132             Notes => q{
2133             a date/time tag called GPSTimeStamp by the XMP specification. This tag is
2134             renamed here to prevent direct copy from EXIF:GPSTimeStamp which is a
2135             time-only tag. Instead, the value of this tag should be taken from
2136             Composite:GPSDateTime when copying from EXIF
2137             },
2138             %dateTimeInfo,
2139             },
2140             GPSSatellites => { Groups => { 2 => 'Location' } },
2141             GPSStatus => {
2142             Groups => { 2 => 'Location' },
2143             PrintConv => {
2144             A => 'Measurement Active',
2145             V => 'Measurement Void',
2146             },
2147             },
2148             GPSMeasureMode => {
2149             Groups => { 2 => 'Location' },
2150             Writable => 'integer',
2151             PrintConv => {
2152             2 => '2-Dimensional Measurement',
2153             3 => '3-Dimensional Measurement',
2154             },
2155             },
2156             GPSDOP => { Groups => { 2 => 'Location' }, Writable => 'rational' },
2157             GPSSpeedRef => {
2158             Groups => { 2 => 'Location' },
2159             PrintConv => {
2160             K => 'km/h',
2161             M => 'mph',
2162             N => 'knots',
2163             },
2164             },
2165             GPSSpeed => { Groups => { 2 => 'Location' }, Writable => 'rational' },
2166             GPSTrackRef => {
2167             Groups => { 2 => 'Location' },
2168             PrintConv => {
2169             M => 'Magnetic North',
2170             T => 'True North',
2171             },
2172             },
2173             GPSTrack => { Groups => { 2 => 'Location' }, Writable => 'rational' },
2174             GPSImgDirectionRef => {
2175             Groups => { 2 => 'Location' },
2176             PrintConv => {
2177             M => 'Magnetic North',
2178             T => 'True North',
2179             },
2180             },
2181             GPSImgDirection => { Groups => { 2 => 'Location' }, Writable => 'rational' },
2182             GPSMapDatum => { Groups => { 2 => 'Location' } },
2183             GPSDestLatitude => { Groups => { 2 => 'Location' }, %latConv },
2184             GPSDestLongitude=> { Groups => { 2 => 'Location' }, %longConv },
2185             GPSDestBearingRef => {
2186             Groups => { 2 => 'Location' },
2187             PrintConv => {
2188             M => 'Magnetic North',
2189             T => 'True North',
2190             },
2191             },
2192             GPSDestBearing => { Groups => { 2 => 'Location' }, Writable => 'rational' },
2193             GPSDestDistanceRef => {
2194             Groups => { 2 => 'Location' },
2195             PrintConv => {
2196             K => 'Kilometers',
2197             M => 'Miles',
2198             N => 'Nautical Miles',
2199             },
2200             },
2201             GPSDestDistance => {
2202             Groups => { 2 => 'Location' },
2203             Writable => 'rational',
2204             },
2205             GPSProcessingMethod => { Groups => { 2 => 'Location' } },
2206             GPSAreaInformation => { Groups => { 2 => 'Location' } },
2207             GPSDifferential => {
2208             Groups => { 2 => 'Location' },
2209             Writable => 'integer',
2210             PrintConv => {
2211             0 => 'No Correction',
2212             1 => 'Differential Corrected',
2213             },
2214             },
2215             GPSHPositioningError => { #12
2216             Description => 'GPS Horizontal Positioning Error',
2217             Groups => { 2 => 'Location' },
2218             Writable => 'rational',
2219             PrintConv => '"$val m"',
2220             PrintConvInv => '$val=~s/\s*m$//; $val',
2221             },
2222             NativeDigest => { }, #PH
2223             # new Exif
2224             );
2225              
2226             # Exif extended properties (exifEX, ref 12)
2227             %Image::ExifTool::XMP::exifEX = (
2228             %xmpTableDefaults,
2229             GROUPS => { 1 => 'XMP-exifEX', 2 => 'Image' },
2230             NAMESPACE => 'exifEX',
2231             PRIORITY => 0, # not as reliable as actual EXIF tags
2232             NOTES => q{
2233             EXIF tags added by the EXIF 2.32 for XMP specification (see
2234             L).
2235             },
2236             Gamma => { Writable => 'rational' },
2237             PhotographicSensitivity => { Writable => 'integer' },
2238             SensitivityType => {
2239             Writable => 'integer',
2240             PrintConv => {
2241             0 => 'Unknown',
2242             1 => 'Standard Output Sensitivity',
2243             2 => 'Recommended Exposure Index',
2244             3 => 'ISO Speed',
2245             4 => 'Standard Output Sensitivity and Recommended Exposure Index',
2246             5 => 'Standard Output Sensitivity and ISO Speed',
2247             6 => 'Recommended Exposure Index and ISO Speed',
2248             7 => 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed',
2249             },
2250             },
2251             StandardOutputSensitivity => { Writable => 'integer' },
2252             RecommendedExposureIndex => { Writable => 'integer' },
2253             ISOSpeed => { Writable => 'integer' },
2254             ISOSpeedLatitudeyyy => {
2255             Description => 'ISO Speed Latitude yyy',
2256             Writable => 'integer',
2257             },
2258             ISOSpeedLatitudezzz => {
2259             Description => 'ISO Speed Latitude zzz',
2260             Writable => 'integer',
2261             },
2262             CameraOwnerName => { Name => 'OwnerName' },
2263             BodySerialNumber => { Name => 'SerialNumber', Groups => { 2 => 'Camera' } },
2264             LensSpecification => {
2265             Name => 'LensInfo',
2266             Writable => 'rational',
2267             Groups => { 2 => 'Camera' },
2268             List => 'Seq',
2269             RawJoin => 1, # join list into a string before ValueConv
2270             ValueConv => \&ConvertRationalList,
2271             ValueConvInv => sub {
2272             my $val = shift;
2273             my @vals = split ' ', $val;
2274             return $val unless @vals == 4;
2275             foreach (@vals) {
2276             $_ eq 'inf' and $_ = '1/0', next;
2277             $_ eq 'undef' and $_ = '0/0', next;
2278             Image::ExifTool::IsFloat($_) or return $val;
2279             my @a = Image::ExifTool::Rationalize($_);
2280             $_ = join '/', @a;
2281             }
2282             return \@vals; # return list reference (List-type tag)
2283             },
2284             PrintConv => \&Image::ExifTool::Exif::PrintLensInfo,
2285             PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo,
2286             Notes => q{
2287             unfortunately the EXIF 2.3 for XMP specification defined this new tag
2288             instead of using the existing XMP-aux:LensInfo
2289             },
2290             },
2291             LensMake => { Groups => { 2 => 'Camera' } },
2292             LensModel => { Groups => { 2 => 'Camera' } },
2293             LensSerialNumber => { Groups => { 2 => 'Camera' } },
2294             InteroperabilityIndex => {
2295             Name => 'InteropIndex',
2296             Description => 'Interoperability Index',
2297             PrintConv => {
2298             R98 => 'R98 - DCF basic file (sRGB)',
2299             R03 => 'R03 - DCF option file (Adobe RGB)',
2300             THM => 'THM - DCF thumbnail file',
2301             },
2302             },
2303             # new in Exif 2.31
2304             Temperature => { Writable => 'rational', Name => 'AmbientTemperature' },
2305             Humidity => { Writable => 'rational' },
2306             Pressure => { Writable => 'rational' },
2307             WaterDepth => { Writable => 'rational' },
2308             Acceleration => { Writable => 'rational' },
2309             CameraElevationAngle=> { Writable => 'rational' },
2310             # new in Exif 2.32 (according to the spec, these should use a different namespace
2311             # URI, but the same namespace prefix... Exactly how is that supposed to work?!!
2312             # -- I'll just stick with the same URI)
2313             CompositeImage => { Writable => 'integer',
2314             PrintConv => {
2315             0 => 'Unknown',
2316             1 => 'Not a Composite Image',
2317             2 => 'General Composite Image',
2318             3 => 'Composite Image Captured While Shooting',
2319             },
2320             },
2321             CompositeImageCount => { List => 'Seq', Writable => 'integer' },
2322             CompositeImageExposureTimes => {
2323             FlatName => 'CompImage',
2324             Struct => {
2325             STRUCT_NAME => 'CompImageExp',
2326             NAMESPACE => 'exifEX',
2327             TotalExposurePeriod => { Writable => 'rational' },
2328             SumOfExposureTimesOfAll => { Writable => 'rational', FlatName => 'SumExposureAll' },
2329             SumOfExposureTimesOfUsed=> { Writable => 'rational', FlatName => 'SumExposureUsed' },
2330             MaxExposureTimesOfAll => { Writable => 'rational', FlatName => 'MaxExposureAll' },
2331             MaxExposureTimesOfUsed => { Writable => 'rational', FlatName => 'MaxExposureUsed' },
2332             MinExposureTimesOfAll => { Writable => 'rational', FlatName => 'MinExposureAll' },
2333             MinExposureTimesOfUsed => { Writable => 'rational', FlatName => 'MinExposureUsed' },
2334             NumberOfSequences => { Writable => 'integer', FlatName => 'NumSequences' },
2335             NumberOfImagesInSequences=>{ Writable => 'integer', FlatName => 'ImagesPerSequence' },
2336             Values => { List => 'Seq', Writable => 'rational' },
2337             },
2338             },
2339             );
2340              
2341             # Auxiliary namespace properties (aux) - not fully documented (ref PH)
2342             %Image::ExifTool::XMP::aux = (
2343             %xmpTableDefaults,
2344             GROUPS => { 1 => 'XMP-aux', 2 => 'Camera' },
2345             NAMESPACE => 'aux',
2346             NOTES => q{
2347             Adobe-defined auxiliary EXIF tags. This namespace existed in the XMP
2348             specification until it was dropped in 2012, presumably due to the
2349             introduction of the EXIF 2.3 for XMP specification and the exifEX namespace
2350             at this time. For this reason, tags below with equivalents in the
2351             L are avoided when writing.
2352             },
2353             Firmware => { }, #7
2354             FlashCompensation => { Writable => 'rational' }, #7
2355             ImageNumber => { }, #7
2356             LensInfo => { #7
2357             Notes => '4 rational values giving focal and aperture ranges',
2358             Avoid => 1,
2359             # convert to floating point values (or 'inf' or 'undef')
2360             ValueConv => \&ConvertRationalList,
2361             ValueConvInv => sub {
2362             my $val = shift;
2363             my @vals = split ' ', $val;
2364             return $val unless @vals == 4;
2365             foreach (@vals) {
2366             $_ eq 'inf' and $_ = '1/0', next;
2367             $_ eq 'undef' and $_ = '0/0', next;
2368             Image::ExifTool::IsFloat($_) or return $val;
2369             my @a = Image::ExifTool::Rationalize($_);
2370             $_ = join '/', @a;
2371             }
2372             return join ' ', @vals; # return string (string tag)
2373             },
2374             # convert to the form "12-20mm f/3.8-4.5" or "50mm f/1.4"
2375             PrintConv => \&Image::ExifTool::Exif::PrintLensInfo,
2376             PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo,
2377             },
2378             Lens => { },
2379             OwnerName => { Avoid => 1 }, #7
2380             SerialNumber => { Avoid => 1 },
2381             LensSerialNumber=> { Avoid => 1 },
2382             LensID => {
2383             Priority => 0,
2384             # prevent this from getting set from a LensID that has been converted
2385             ValueConvInv => q{
2386             warn "Expected one or more integer values" if $val =~ /[^-\d ]/;
2387             return $val;
2388             },
2389             },
2390             ApproximateFocusDistance => { Writable => 'rational' }, #PH (LR3)
2391             # the following new in LR6 (ref forum6497)
2392             IsMergedPanorama => { Writable => 'boolean' },
2393             IsMergedHDR => { Writable => 'boolean' },
2394             DistortionCorrectionAlreadyApplied => { Writable => 'boolean' },
2395             VignetteCorrectionAlreadyApplied => { Writable => 'boolean' },
2396             LateralChromaticAberrationCorrectionAlreadyApplied => { Writable => 'boolean' },
2397             LensDistortInfo => { }, # (LR 7.5.1, 4 signed rational values)
2398             NeutralDensityFactor => { }, # (LR 11.0 - rational value, but denominator seems significant)
2399             );
2400              
2401             # IPTC Core namespace properties (Iptc4xmpCore) (ref 4)
2402             %Image::ExifTool::XMP::iptcCore = (
2403             %xmpTableDefaults,
2404             GROUPS => { 1 => 'XMP-iptcCore', 2 => 'Author' },
2405             NAMESPACE => 'Iptc4xmpCore',
2406             TABLE_DESC => 'XMP IPTC Core',
2407             NOTES => q{
2408             IPTC Core namespace tags. The actual IPTC Core namespace prefix is
2409             "Iptc4xmpCore", which is the prefix recorded in the file, but ExifTool
2410             shortens this for the family 1 group name. (see
2411             L)
2412             },
2413             CountryCode => { Groups => { 2 => 'Location' } },
2414             CreatorContactInfo => {
2415             Struct => {
2416             STRUCT_NAME => 'ContactInfo',
2417             NAMESPACE => 'Iptc4xmpCore',
2418             CiAdrCity => { },
2419             CiAdrCtry => { },
2420             CiAdrExtadr => { },
2421             CiAdrPcode => { },
2422             CiAdrRegion => { },
2423             CiEmailWork => { },
2424             CiTelWork => { },
2425             CiUrlWork => { },
2426             },
2427             },
2428             CreatorContactInfoCiAdrCity => { Flat => 1, Name => 'CreatorCity' },
2429             CreatorContactInfoCiAdrCtry => { Flat => 1, Name => 'CreatorCountry' },
2430             CreatorContactInfoCiAdrExtadr => { Flat => 1, Name => 'CreatorAddress' },
2431             CreatorContactInfoCiAdrPcode => { Flat => 1, Name => 'CreatorPostalCode' },
2432             CreatorContactInfoCiAdrRegion => { Flat => 1, Name => 'CreatorRegion' },
2433             CreatorContactInfoCiEmailWork => { Flat => 1, Name => 'CreatorWorkEmail' },
2434             CreatorContactInfoCiTelWork => { Flat => 1, Name => 'CreatorWorkTelephone' },
2435             CreatorContactInfoCiUrlWork => { Flat => 1, Name => 'CreatorWorkURL' },
2436             IntellectualGenre => { Groups => { 2 => 'Other' } },
2437             Location => { Groups => { 2 => 'Location' } },
2438             Scene => { Groups => { 2 => 'Other' }, List => 'Bag' },
2439             SubjectCode => { Groups => { 2 => 'Other' }, List => 'Bag' },
2440             # Copyright - have seen this in a sample (Jan 2021), but I think it is non-standard
2441             # new IPTC Core 1.3 properties
2442             AltTextAccessibility => { Groups => { 2 => 'Other' }, Writable => 'lang-alt' },
2443             ExtDescrAccessibility => { Groups => { 2 => 'Other' }, Writable => 'lang-alt' },
2444             );
2445              
2446             # Adobe Lightroom namespace properties (lr) (ref PH)
2447             %Image::ExifTool::XMP::Lightroom = (
2448             %xmpTableDefaults,
2449             GROUPS => { 1 => 'XMP-lr', 2 => 'Image' },
2450             NAMESPACE => 'lr',
2451             TABLE_DESC => 'XMP Adobe Lightroom',
2452             NOTES => 'Adobe Lightroom "lr" namespace tags.',
2453             privateRTKInfo => { },
2454             hierarchicalSubject => { List => 'Bag' },
2455             weightedFlatSubject => { List => 'Bag' },
2456             );
2457              
2458             # Adobe Album namespace properties (album) (ref PH)
2459             %Image::ExifTool::XMP::Album = (
2460             %xmpTableDefaults,
2461             GROUPS => { 1 => 'XMP-album', 2 => 'Image' },
2462             NAMESPACE => 'album',
2463             TABLE_DESC => 'XMP Adobe Album',
2464             NOTES => 'Adobe Album namespace tags.',
2465             Notes => { },
2466             );
2467              
2468             # table to add tags in other namespaces
2469             %Image::ExifTool::XMP::other = (
2470             GROUPS => { 2 => 'Unknown' },
2471             LANG_INFO => \&GetLangInfo,
2472             );
2473              
2474             # Composite XMP tags
2475             %Image::ExifTool::XMP::Composite = (
2476             # get latitude/longitude reference from XMP lat/long tags
2477             # (used to set EXIF GPS position from XMP tags)
2478             GPSLatitudeRef => {
2479             Require => 'XMP-exif:GPSLatitude',
2480             Groups => { 2 => 'Location' },
2481             # Note: Do not Inihibit based on EXIF:GPSLatitudeRef (see forum10192)
2482             ValueConv => q{
2483             IsFloat($val[0]) and return $val[0] < 0 ? "S" : "N";
2484             $val[0] =~ /^.*([NS])/;
2485             return $1;
2486             },
2487             PrintConv => { N => 'North', S => 'South' },
2488             },
2489             GPSLongitudeRef => {
2490             Require => 'XMP-exif:GPSLongitude',
2491             Groups => { 2 => 'Location' },
2492             ValueConv => q{
2493             IsFloat($val[0]) and return $val[0] < 0 ? "W" : "E";
2494             $val[0] =~ /^.*([EW])/;
2495             return $1;
2496             },
2497             PrintConv => { E => 'East', W => 'West' },
2498             },
2499             GPSDestLatitudeRef => {
2500             Require => 'XMP-exif:GPSDestLatitude',
2501             Groups => { 2 => 'Location' },
2502             ValueConv => q{
2503             IsFloat($val[0]) and return $val[0] < 0 ? "S" : "N";
2504             $val[0] =~ /^.*([NS])/;
2505             return $1;
2506             },
2507             PrintConv => { N => 'North', S => 'South' },
2508             },
2509             GPSDestLongitudeRef => {
2510             Require => 'XMP-exif:GPSDestLongitude',
2511             Groups => { 2 => 'Location' },
2512             ValueConv => q{
2513             IsFloat($val[0]) and return $val[0] < 0 ? "W" : "E";
2514             $val[0] =~ /^.*([EW])/;
2515             return $1;
2516             },
2517             PrintConv => { E => 'East', W => 'West' },
2518             },
2519             LensID => {
2520             Notes => 'attempt to convert numerical XMP-aux:LensID stored by Adobe applications',
2521             Require => {
2522             0 => 'XMP-aux:LensID',
2523             1 => 'Make',
2524             },
2525             Desire => {
2526             2 => 'LensInfo',
2527             3 => 'FocalLength',
2528             4 => 'LensModel',
2529             5 => 'MaxApertureValue',
2530             },
2531             Inhibit => {
2532             6 => 'Composite:LensID', # don't override existing Composite:LensID
2533             },
2534             Groups => { 2 => 'Camera' },
2535             ValueConv => '$val',
2536             PrintConv => 'Image::ExifTool::XMP::PrintLensID($self, @val)',
2537             },
2538             Flash => {
2539             Notes => 'facilitates copying camera flash information between XMP and EXIF',
2540             Desire => {
2541             0 => 'XMP:FlashFired',
2542             1 => 'XMP:FlashReturn',
2543             2 => 'XMP:FlashMode',
2544             3 => 'XMP:FlashFunction',
2545             4 => 'XMP:FlashRedEyeMode',
2546             5 => 'XMP:Flash', # handle structured flash information too
2547             },
2548             Groups => { 2 => 'Camera' },
2549             Writable => 1,
2550             PrintHex => 1,
2551             SeparateTable => 'EXIF Flash',
2552             ValueConv => q{
2553             if (ref $val[5] eq 'HASH') {
2554             # copy structure fields into value array
2555             my $i = 0;
2556             $val[$i++] = $val[5]{$_} foreach qw(Fired Return Mode Function RedEyeMode);
2557             }
2558             return((($val[0] and lc($val[0]) eq 'true') ? 0x01 : 0) |
2559             (($val[1] || 0) << 1) |
2560             (($val[2] || 0) << 3) |
2561             (($val[3] and lc($val[3]) eq 'true') ? 0x20 : 0) |
2562             (($val[4] and lc($val[4]) eq 'true') ? 0x40 : 0));
2563             },
2564             PrintConv => \%Image::ExifTool::Exif::flash,
2565             WriteAlso => {
2566             'XMP:FlashFired' => '$val & 0x01 ? "True" : "False"',
2567             'XMP:FlashReturn' => '($val & 0x06) >> 1',
2568             'XMP:FlashMode' => '($val & 0x18) >> 3',
2569             'XMP:FlashFunction' => '$val & 0x20 ? "True" : "False"',
2570             'XMP:FlashRedEyeMode' => '$val & 0x40 ? "True" : "False"',
2571             },
2572             },
2573             );
2574              
2575             # add our composite tags
2576             Image::ExifTool::AddCompositeTags('Image::ExifTool::XMP');
2577              
2578             #------------------------------------------------------------------------------
2579             # AutoLoad our writer routines when necessary
2580             #
2581             sub AUTOLOAD
2582             {
2583 36     36   223 return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
2584             }
2585              
2586             #------------------------------------------------------------------------------
2587             # Escape necessary XML characters in UTF-8 string
2588             # Inputs: 0) string to be escaped
2589             # Returns: escaped string
2590             my %charName = ('"'=>'quot', '&'=>'amp', "'"=>'#39', '<'=>'lt', '>'=>'gt');
2591             sub EscapeXML($)
2592             {
2593 1313     1313 0 1891 my $str = shift;
2594 1313         2763 $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters
2595 1313         2631 return $str;
2596             }
2597              
2598             #------------------------------------------------------------------------------
2599             # Unescape XML character references (entities and numerical)
2600             # Inputs: 0) string to be unescaped
2601             # 1) optional hash reference to convert entity names to numbers
2602             # 2) optional character encoding
2603             # Returns: unescaped string
2604             my %charNum = ('quot'=>34, 'amp'=>38, 'apos'=>39, 'lt'=>60, 'gt'=>62);
2605             sub UnescapeXML($;$$)
2606             {
2607 4541     4541 0 8351 my ($str, $conv, $enc) = @_;
2608 4541 100       8493 $conv = \%charNum unless $conv;
2609 4541         7606 $str =~ s/&(#?\w+);/UnescapeChar($1,$conv,$enc)/sge;
  231         498  
2610 4541         8630 return $str;
2611             }
2612              
2613             #------------------------------------------------------------------------------
2614             # Escape string for XML, ensuring valid XML and UTF-8
2615             # Inputs: 0) string
2616             # Returns: escaped string
2617             sub FullEscapeXML($)
2618             {
2619 0     0 0 0 my $str = shift;
2620 0         0 $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters
2621 0         0 $str =~ s/\\/\/sg; # escape backslashes too
2622             # then use C-escape sequences for invalid characters
2623 0 0 0     0 if ($str =~ /[\0-\x1f]/ or IsUTF8(\$str) < 0) {
2624 0         0 $str =~ s/([\0-\x1f\x80-\xff])/sprintf("\\x%.2x",ord $1)/sge;
  0         0  
2625             }
2626 0         0 return $str;
2627             }
2628              
2629             #------------------------------------------------------------------------------
2630             # Unescape XML/C escaped string
2631             # Inputs: 0) string
2632             # Returns: unescaped string
2633             sub FullUnescapeXML($)
2634             {
2635 0     0 0 0 my $str = shift;
2636             # unescape C escape sequences first
2637 0         0 $str =~ s/\\x([\da-f]{2})/chr(hex($1))/sge;
  0         0  
2638 0         0 my $conv = \%charNum;
2639 0         0 $str =~ s/&(#?\w+);/UnescapeChar($1,$conv)/sge;
  0         0  
2640 0         0 return $str;
2641             }
2642              
2643             #------------------------------------------------------------------------------
2644             # Convert XML character reference to UTF-8
2645             # Inputs: 0) XML character reference stripped of the '&' and ';' (eg. 'quot', '#34', '#x22')
2646             # 1) hash reference for looking up character numbers by name
2647             # 2) optional character encoding (default 'UTF8')
2648             # Returns: UTF-8 equivalent (or original character on conversion error)
2649             sub UnescapeChar($$;$)
2650             {
2651 231     231 0 496 my ($ch, $conv, $enc) = @_;
2652 231         421 my $val = $$conv{$ch};
2653 231 100       443 unless (defined $val) {
2654 122 100       303 if ($ch =~ /^#x([0-9a-fA-F]+)$/) {
    50          
2655 112         161 $val = hex($1);
2656             } elsif ($ch =~ /^#(\d+)$/) {
2657 10         26 $val = $1;
2658             } else {
2659 0         0 return "&$ch;"; # should issue a warning here? [no]
2660             }
2661             }
2662 231 100       655 return chr($val) if $val < 0x80; # simple ASCII
2663 154 50       301 $val = $] >= 5.006001 ? pack('C0U', $val) : Image::ExifTool::PackUTF8($val);
2664 154 50 66     390 $val = Image::ExifTool::Decode(undef, $val, 'UTF8', undef, $enc) if $enc and $enc ne 'UTF8';
2665 154         459 return $val;
2666             }
2667              
2668             #------------------------------------------------------------------------------
2669             # Does a string contain valid UTF-8 characters?
2670             # Inputs: 0) string reference, 1) true to allow last character to be truncated
2671             # Returns: 0=regular ASCII, -1=invalid UTF-8, 1=valid UTF-8 with maximum 16-bit
2672             # wide characters, 2=valid UTF-8 requiring 32-bit wide characters
2673             # Notes: Changes current string position
2674             # (see http://www.fileformat.info/info/unicode/utf8.htm for help understanding this)
2675             sub IsUTF8($;$)
2676             {
2677 54     54 0 105 my ($strPt, $trunc) = @_;
2678 54         146 pos($$strPt) = 0; # start at beginning of string
2679 54 100       237 return 0 unless $$strPt =~ /([\x80-\xff])/g;
2680 13         27 my $rtnVal = 1;
2681 13         19 for (;;) {
2682 155         213 my $ch = ord($1);
2683             # minimum lead byte for 2-byte sequence is 0xc2 (overlong sequences
2684             # not allowed), 0xf8-0xfd are restricted by RFC 3629 (no 5 or 6 byte
2685             # sequences), and 0xfe and 0xff are not valid in UTF-8 strings
2686 155 100 66     348 return -1 if $ch < 0xc2 or $ch >= 0xf8;
2687             # determine number of bytes remaining in sequence
2688 153         172 my $n;
2689 153 100       219 if ($ch < 0xe0) {
    50          
2690 75         80 $n = 1;
2691             } elsif ($ch < 0xf0) {
2692 78         83 $n = 2;
2693             } else {
2694 0         0 $n = 3;
2695             # character code is greater than 0xffff if more than 2 extra bytes
2696             # were required in the UTF-8 character
2697 0         0 $rtnVal = 2;
2698             }
2699 153         168 my $pos = pos $$strPt;
2700 153 100       613 unless ($$strPt =~ /\G([\x80-\xbf]{$n})/g) {
2701 1 50 33     9 return $rtnVal if $trunc and $pos + $n > length $$strPt;
2702 1         5 return -1;
2703             }
2704             # the following is ref https://www.cl.cam.ac.uk/%7Emgk25/ucs/utf8_check.c
2705 152 100       234 if ($n == 2) {
2706 77 50 66     303 return -1 if ($ch == 0xe0 and (ord($1) & 0xe0) == 0x80) or
      33        
      33        
      66        
      33        
      33        
2707             ($ch == 0xed and (ord($1) & 0xe0) == 0xa0) or
2708             ($ch == 0xef and ord($1) == 0xbf and
2709             (ord(substr $1, 1) & 0xfe) == 0xbe);
2710             } else {
2711 75 50 33     279 return -1 if ($ch == 0xf0 and (ord($1) & 0xf0) == 0x80) or
      33        
      33        
      33        
2712             ($ch == 0xf4 and ord($1) > 0x8f) or $ch > 0xf4;
2713             }
2714 152 100       321 last unless $$strPt =~ /([\x80-\xff])/g;
2715             }
2716 10         24 return $rtnVal;
2717             }
2718              
2719             #------------------------------------------------------------------------------
2720             # Fix malformed UTF8 (by replacing bad bytes with specified character)
2721             # Inputs: 0) string reference, 1) string to replace each bad byte,
2722             # may be '' to delete bad bytes, or undef to use '?'
2723             # Returns: true if string was fixed, and updates string
2724             sub FixUTF8($;$)
2725             {
2726 1295     1295 0 2489 my ($strPt, $bad) = @_;
2727 1295         1643 my $fixed;
2728 1295         3677 pos($$strPt) = 0; # start at beginning of string
2729 1295         2210 for (;;) {
2730 1320 100       3962 last unless $$strPt =~ /([\x80-\xff])/g;
2731 25         66 my $ch = ord($1);
2732 25         43 my $pos = pos($$strPt);
2733             # (see comments in IsUTF8() above)
2734 25 50 33     91 if ($ch >= 0xc2 and $ch < 0xf8) {
2735 25 0       64 my $n = $ch < 0xe0 ? 1 : ($ch < 0xf0 ? 2 : 3);
    50          
2736 25 50       208 if ($$strPt =~ /\G([\x80-\xbf]{$n})/g) {
2737 25 50       70 next if $n == 1;
2738 0 0       0 if ($n == 2) {
2739 0 0 0     0 next unless ($ch == 0xe0 and (ord($1) & 0xe0) == 0x80) or
      0        
      0        
      0        
      0        
      0        
2740             ($ch == 0xed and (ord($1) & 0xe0) == 0xa0) or
2741             ($ch == 0xef and ord($1) == 0xbf and
2742             (ord(substr $1, 1) & 0xfe) == 0xbe);
2743             } else {
2744 0 0 0     0 next unless ($ch == 0xf0 and (ord($1) & 0xf0) == 0x80) or
      0        
      0        
      0        
2745             ($ch == 0xf4 and ord($1) > 0x8f) or $ch > 0xf4;
2746             }
2747             }
2748             }
2749             # replace bad character
2750 0 0       0 $bad = '?' unless defined $bad;
2751 0         0 substr($$strPt, $pos-1, 1) = $bad;
2752 0         0 pos($$strPt) = $pos-1 + length $bad;
2753 0         0 $fixed = 1;
2754             }
2755 1295         3906 return $fixed;
2756             }
2757              
2758             #------------------------------------------------------------------------------
2759             # Utility routine to decode a base64 string
2760             # Inputs: 0) base64 string
2761             # Returns: reference to decoded data
2762             sub DecodeBase64($)
2763             {
2764 8     8 0 43 local($^W) = 0; # unpack('u',...) gives bogus warning in 5.00[123]
2765 8         18 my $str = shift;
2766              
2767             # truncate at first unrecognized character (base 64 data
2768             # may only contain A-Z, a-z, 0-9, +, /, =, or white space)
2769 8         41 $str =~ s/[^A-Za-z0-9+\/= \t\n\r\f].*//s;
2770             # translate to uucoded and remove padding and white space
2771 8         26 $str =~ tr/A-Za-z0-9+\/= \t\n\r\f/ -_/d;
2772              
2773             # convert the data to binary in chunks
2774 8         16 my $chunkSize = 60;
2775 8         52 my $uuLen = pack('c', 32 + $chunkSize * 3 / 4); # calculate length byte
2776 8         17 my $dat = '';
2777 8         13 my ($i, $substr);
2778             # loop through the whole chunks
2779 8         16 my $len = length($str) - $chunkSize;
2780 8         28 for ($i=0; $i<=$len; $i+=$chunkSize) {
2781 39         59 $substr = substr($str, $i, $chunkSize); # get a chunk of the data
2782 39         116 $dat .= unpack('u', $uuLen . $substr); # decode it
2783             }
2784 8         14 $len += $chunkSize;
2785             # handle last partial chunk if necessary
2786 8 100       21 if ($i < $len) {
2787 7         28 $uuLen = pack('c', 32 + ($len-$i) * 3 / 4); # recalculate length
2788 7         17 $substr = substr($str, $i, $len-$i); # get the last partial chunk
2789 7         29 $dat .= unpack('u', $uuLen . $substr); # decode it
2790             }
2791 8         57 return \$dat;
2792             }
2793              
2794             #------------------------------------------------------------------------------
2795             # Generate a tag ID for this XMP tag
2796             # Inputs: 0) tag property name list ref, 1) array ref for receiving structure property list
2797             # 2) array for receiving namespace list
2798             # Returns: tagID and outtermost interesting namespace (or '' if no namespace)
2799             sub GetXMPTagID($;$$)
2800             {
2801 16059     16059 0 24183 my ($props, $structProps, $nsList) = @_;
2802 16059         18217 my ($tag, $prop, $namespace);
2803 16059         21892 foreach $prop (@$props) {
2804             # split name into namespace and property name
2805             # (Note: namespace can be '' for property qualifiers)
2806 66061 100       223069 my ($ns, $nm) = ($prop =~ /(.*?):(.*)/) ? ($1, $2) : ('', $prop);
2807 66061 100 66     157710 if ($ignoreNamespace{$ns} or $ignoreProp{$prop}) {
2808             # special case: don't ignore rdf numbered items
2809             # (not technically allowed in XMP, but used in RDF/XML)
2810 38600 50       60445 unless ($prop =~ /^rdf:(_\d+)$/) {
2811             # save list index if necessary for structures
2812 38600 100 100     63184 if ($structProps and @$structProps and $prop =~ /^rdf:li (\d+)$/) {
      100        
2813 439         584 push @{$$structProps[-1]}, $1;
  439         1092  
2814             }
2815 38600         52225 next;
2816             }
2817 0 0       0 $tag .= $1 if defined $tag;
2818             } else {
2819 27461         38309 $nm =~ s/ .*//; # remove nodeID if it exists
2820             # all uppercase is ugly, so convert it
2821 27461 100       58164 if ($nm !~ /[a-z]/) {
2822 292   66     828 my $xlat = $stdXlatNS{$ns} || $ns;
2823 292         471 my $info = $Image::ExifTool::XMP::Main{$xlat};
2824 292         344 my $table;
2825 292 50 66     757 if (ref $info eq 'HASH' and $$info{SubDirectory}) {
2826 55         268 $table = GetTagTable($$info{SubDirectory}{TagTable});
2827             }
2828 292 100 100     717 unless ($table and $$table{$nm}) {
2829 277         433 $nm = lc($nm);
2830 277         683 $nm =~ s/_([a-z])/\u$1/g;
2831             }
2832             }
2833 27461 100       39213 if (defined $tag) {
2834 11419         20123 $tag .= ucfirst($nm); # add to tag name
2835             } else {
2836 16042         19300 $tag = $nm;
2837             }
2838             # save structure information if necessary
2839 27461 100       39839 if ($structProps) {
2840 1378         2689 push @$structProps, [ $nm ];
2841 1378 100       2414 push @$nsList, $ns if $nsList;
2842             }
2843             }
2844             # save namespace of first property to contribute to tag name
2845 27461 100       48841 $namespace = $ns unless $namespace;
2846             }
2847 16059 100       24640 if (wantarray) {
2848 3781   100     11771 return ($tag, $namespace || '');
2849             } else {
2850 12278         31661 return $tag;
2851             }
2852             }
2853              
2854             #------------------------------------------------------------------------------
2855             # Register namespace for specified user-defined table
2856             # Inputs: 0) tag/structure table ref
2857             # Returns: namespace prefix
2858             sub RegisterNamespace($)
2859             {
2860 528     528 0 884 my $table = shift;
2861 528 100       2081 return $$table{NAMESPACE} unless ref $$table{NAMESPACE};
2862 28         85 my $nsRef = $$table{NAMESPACE};
2863             # recognize as either a list or hash
2864 28         52 my $ns;
2865 28 50       88 if (ref $nsRef eq 'ARRAY') {
2866 0         0 $ns = $$nsRef[0];
2867 0         0 $nsURI{$ns} = $$nsRef[1];
2868 0         0 $uri2ns{$$nsRef[1]} = $ns;
2869             } else { # must be a hash
2870 28         116 my @ns = sort keys %$nsRef; # allow multiple namespace definitions
2871 28         87 while (@ns) {
2872 28         63 $ns = pop @ns;
2873 28 50 66     144 if ($nsURI{$ns} and $nsURI{$ns} ne $$nsRef{$ns}) {
2874 0         0 warn "User-defined namespace prefix '${ns}' conflicts with existing namespace\n";
2875             }
2876 28         77 $nsURI{$ns} = $$nsRef{$ns};
2877 28         127 $uri2ns{$$nsRef{$ns}} = $ns;
2878             }
2879             }
2880 28         110 return $$table{NAMESPACE} = $ns;
2881             }
2882              
2883             #------------------------------------------------------------------------------
2884             # Generate flattened tags and add to table
2885             # Inputs: 0) tag table ref, 1) tag ID for Struct tag (if not defined, whole table is done),
2886             # 2) flag to not expand sub-structures
2887             # Returns: number of tags added (not counting those just initialized)
2888             # Notes: Must have verified that $$tagTablePtr{$tagID}{Struct} exists before calling this routine
2889             # - makes sure that the tagInfo Struct is a HASH reference
2890             sub AddFlattenedTags($;$$)
2891             {
2892 4972     4972 0 5658 local $_;
2893 4972         8115 my ($tagTablePtr, $tagID, $noSubStruct) = @_;
2894 4972         5492 my $count = 0;
2895 4972         5406 my @tagIDs;
2896              
2897 4972 50       6946 if (defined $tagID) {
2898 4972         6536 push @tagIDs, $tagID;
2899             } else {
2900 0         0 foreach $tagID (TagTableKeys($tagTablePtr)) {
2901 0         0 my $tagInfo = $$tagTablePtr{$tagID};
2902 0 0 0     0 next unless ref $tagInfo eq 'HASH' and $$tagInfo{Struct};
2903 0         0 push @tagIDs, $tagID;
2904             }
2905             }
2906              
2907             # loop through specified tags
2908 4972         6251 foreach $tagID (@tagIDs) {
2909              
2910 4972         6997 my $tagInfo = $$tagTablePtr{$tagID};
2911              
2912 4972 100       9414 $$tagInfo{Flattened} and next; # only generate flattened tags once
2913 506         830 $$tagInfo{Flattened} = 1;
2914              
2915 506         776 my $strTable = $$tagInfo{Struct};
2916 506 50       1020 unless (ref $strTable) { # (allow a structure name for backward compatibility only)
2917 0         0 my $strName = $strTable;
2918 0 0       0 $strTable = $Image::ExifTool::UserDefined::xmpStruct{$strTable} or next;
2919 0 0       0 $$strTable{STRUCT_NAME} or $$strTable{STRUCT_NAME} = "XMP $strName";
2920 0         0 $$tagInfo{Struct} = $strTable; # replace old-style name with HASH ref
2921 0         0 delete $$tagInfo{SubDirectory}; # deprecated use of SubDirectory in Struct tags
2922             }
2923              
2924             # get prefix for flattened tag names
2925 506 100       1281 my $flat = (defined $$tagInfo{FlatName} ? $$tagInfo{FlatName} : $$tagInfo{Name});
2926              
2927             # get family 2 group name for this structure tag
2928 506         737 my ($tagG2, $field);
2929 506 100       1390 $tagG2 = $$tagInfo{Groups}{2} if $$tagInfo{Groups};
2930 506 100       1169 $tagG2 or $tagG2 = $$tagTablePtr{GROUPS}{2};
2931              
2932 506         2395 foreach $field (keys %$strTable) {
2933 5925 100       10058 next if $specialStruct{$field};
2934 4854         6495 my $fieldInfo = $$strTable{$field};
2935 4854 100       7763 next if $$fieldInfo{LangCode}; # don't flatten lang-alt tags
2936 4849 100 100     8525 next if $$fieldInfo{Struct} and $noSubStruct; # don't expand sub-structures if specified
2937             # build a tag ID for the corresponding flattened tag
2938 4805         6781 my $fieldName = ucfirst($field);
2939 4805   66     10272 my $flatField = $$fieldInfo{FlatName} || $fieldName;
2940 4805         8497 my $flatID = $tagID . $fieldName;
2941 4805         7693 my $flatInfo = $$tagTablePtr{$flatID};
2942 4805 100       6373 if ($flatInfo) {
2943 314 50       611 ref $flatInfo eq 'HASH' or warn("$flatInfo is not a HASH!\n"), next; # (to be safe)
2944             # pre-defined flattened tags should have Flat flag set
2945 314 100       616 if (not defined $$flatInfo{Flat}) {
2946 4 50       16 next if $$flatInfo{NotFlat};
2947 0 0       0 warn "Missing Flat flag for $$flatInfo{Name}\n" if $Image::ExifTool::debug;
2948             }
2949 310         414 $$flatInfo{Flat} = 0;
2950             # copy all missing entries from field information
2951 310         684 foreach (keys %$fieldInfo) {
2952             # must not copy PropertyPath (but can't delete it afterwards
2953             # because the flat tag may already have this set)
2954 259 100 100     797 next if $_ eq 'PropertyPath' or defined $$flatInfo{$_};
2955             # copy the property (making a copy of the Groups hash)
2956 226 100       566 $$flatInfo{$_} = $_ eq 'Groups' ? { %{$$fieldInfo{$_}} } : $$fieldInfo{$_};
  12         71  
2957             }
2958             # (NOTE: Can NOT delete Groups because we need them if GotGroups was done)
2959             # re-generate List flag unless it is set to 0
2960 310 100       617 delete $$flatInfo{List} if $$flatInfo{List};
2961             } else {
2962             # generate new flattened tag information based on structure field
2963 4491         6355 my $flatName = $flat . $flatField;
2964 4491         14761 $flatInfo = { %$fieldInfo, Name => $flatName, Flat => 0 };
2965 4491 100       8740 $$flatInfo{FlatName} = $flatName if $$fieldInfo{FlatName};
2966             # make a copy of the Groups hash if necessary
2967 4491 100       6949 $$flatInfo{Groups} = { %{$$fieldInfo{Groups}} } if $$fieldInfo{Groups};
  165         507  
2968             # add new flattened tag to table
2969 4491         10293 AddTagToTable($tagTablePtr, $flatID, $flatInfo);
2970 4491         5544 ++$count;
2971             }
2972             # propagate List flag (unless set to 0 in pre-defined flattened tag)
2973 4801 100       8460 unless (defined $$flatInfo{List}) {
2974 2613 100 100     9400 $$flatInfo{List} = $$fieldInfo{List} || 1 if $$fieldInfo{List} or $$tagInfo{List};
      100        
2975             }
2976             # set group 2 name from the first existing family 2 group in the:
2977             # 1) structure field Groups, 2) structure table GROUPS, 3) structure tag Groups
2978 4801 100 66     13037 if ($$fieldInfo{Groups} and $$fieldInfo{Groups}{2}) {
    100 66        
2979 177         323 $$flatInfo{Groups}{2} = $$fieldInfo{Groups}{2};
2980             } elsif ($$strTable{GROUPS} and $$strTable{GROUPS}{2}) {
2981 84         142 $$flatInfo{Groups}{2} = $$strTable{GROUPS}{2};
2982             } else {
2983 4540         6718 $$flatInfo{Groups}{2} = $tagG2;
2984             }
2985             # save reference to top-level structure
2986 4801   66     11055 $$flatInfo{RootTagInfo} = $$tagInfo{RootTagInfo} || $tagInfo;
2987             # recursively generate flattened tags for sub-structures
2988 4801 100       10145 next unless $$flatInfo{Struct};
2989 214 50       458 length($flatID) > 250 and warn("Possible deep recursion for tag $flatID\n"), last;
2990             # reset flattened tag just in case we flattened hierarchy in the wrong order
2991             # because we must start from the outtermost structure to get the List flags right
2992             # (this should only happen when building tag tables)
2993 214         306 delete $$flatInfo{Flattened};
2994 214         844 $count += AddFlattenedTags($tagTablePtr, $flatID, $$flatInfo{NoSubStruct});
2995             }
2996             }
2997 4972         9916 return $count;
2998             }
2999              
3000             #------------------------------------------------------------------------------
3001             # Get localized version of tagInfo hash
3002             # Inputs: 0) tagInfo hash ref, 1) language code (eg. "x-default")
3003             # Returns: new tagInfo hash ref, or undef if invalid
3004             sub GetLangInfo($$)
3005             {
3006 107     107 0 211 my ($tagInfo, $langCode) = @_;
3007             # only allow alternate language tags in lang-alt lists
3008 107 100 66     453 return undef unless $$tagInfo{Writable} and $$tagInfo{Writable} eq 'lang-alt';
3009 99         170 $langCode =~ tr/_/-/; # RFC 3066 specifies '-' as a separator
3010 99         281 my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode);
3011 99         192 return $langInfo;
3012             }
3013              
3014             #------------------------------------------------------------------------------
3015             # Get standard case for language code
3016             # Inputs: 0) Language code
3017             # Returns: Language code in standard case
3018             sub StandardLangCase($)
3019             {
3020 53     53 0 83 my $lang = shift;
3021             # make 2nd subtag uppercase only if it is 2 letters
3022 53 100       174 return lc($1) . uc($2) . lc($3) if $lang =~ /^([a-z]{2,3}|[xi])(-[a-z]{2})\b(.*)/i;
3023 40         83 return lc($lang);
3024             }
3025              
3026             #------------------------------------------------------------------------------
3027             # Scan for XMP in a file
3028             # Inputs: 0) ExifTool object ref, 1) RAF reference
3029             # Returns: 1 if xmp was found, 0 otherwise
3030             # Notes: Currently only recognizes UTF8-encoded XMP
3031             sub ScanForXMP($$)
3032             {
3033 0     0 0 0 my ($et, $raf) = @_;
3034 0         0 my ($buff, $xmp);
3035 0         0 my $lastBuff = '';
3036              
3037 0         0 $et->VPrint(0,"Scanning for XMP\n");
3038 0         0 for (;;) {
3039 0 0 0     0 defined $buff or $raf->Read($buff, 65536) or return 0;
3040 0 0       0 unless (defined $xmp) {
3041 0         0 $lastBuff .= $buff;
3042 0 0       0 unless ($lastBuff =~ /(<\?xpacket begin=)/g) {
3043             # must keep last 15 bytes to match 16-byte "xpacket begin" string
3044 0 0       0 $lastBuff = length($buff) <= 15 ? $buff : substr($buff, -15);
3045 0         0 undef $buff;
3046 0         0 next;
3047             }
3048 0         0 $xmp = $1;
3049 0         0 $buff = substr($lastBuff, pos($lastBuff));
3050             }
3051 0         0 my $pos = length($xmp) - 18; # (18 = length("
3052 0         0 $xmp .= $buff; # add new data to our XMP
3053 0 0       0 pos($xmp) = $pos if $pos > 0; # set start for "xpacket end" scan
3054 0 0       0 if ($xmp =~ /<\?xpacket end=['"][wr]['"]\?>/g) {
3055 0         0 $buff = substr($xmp, pos($xmp)); # save data after end of XMP
3056 0         0 $xmp = substr($xmp, 0, pos($xmp)); # isolate XMP
3057             # check XMP for validity (not valid if it contains null bytes)
3058 0 0       0 $pos = rindex($xmp, "\0") + 1 or last;
3059 0         0 $lastBuff = substr($xmp, $pos); # re-parse beginning after last null byte
3060 0         0 undef $xmp;
3061             } else {
3062 0         0 undef $buff;
3063             }
3064             }
3065 0 0       0 unless ($$et{VALUE}{FileType}) {
3066 0         0 $$et{FILE_TYPE} = $$et{FILE_EXT};
3067 0         0 $et->SetFileType('', undef, '');
3068             }
3069 0         0 my %dirInfo = (
3070             DataPt => \$xmp,
3071             DirLen => length $xmp,
3072             DataLen => length $xmp,
3073             );
3074 0         0 ProcessXMP($et, \%dirInfo);
3075 0         0 return 1;
3076             }
3077              
3078             #------------------------------------------------------------------------------
3079             # Print conversion for XMP-aux:LensID
3080             # Inputs: 0) ExifTool ref, 1) LensID, 2) Make, 3) LensInfo, 4) FocalLength,
3081             # 5) LensModel, 6) MaxApertureValue
3082             # (yes, this is ugly -- blame Adobe)
3083             sub PrintLensID(@)
3084             {
3085 0     0 0 0 local $_;
3086 0         0 my ($et, $id, $make, $info, $focalLength, $lensModel, $maxAv) = @_;
3087 0         0 my ($mk, $printConv);
3088 0         0 my %alt = ( Pentax => 'Ricoh' ); # Pentax changed its name to Ricoh
3089             # missing: Olympus (no XMP:LensID written by Adobe)
3090 0         0 foreach $mk (qw(Canon Nikon Pentax Sony Sigma Samsung Leica)) {
3091 0 0 0     0 next unless $make =~ /$mk/i or ($alt{$mk} and $make =~ /$alt{$mk}/i);
      0        
3092             # get name of module containing the lens lookup (default "Make.pm")
3093 0   0     0 my $mod = { Sigma => 'SigmaRaw', Leica => 'Panasonic' }->{$mk} || $mk;
3094 0         0 require "Image/ExifTool/$mod.pm";
3095             # get the name of the lens name lookup (default "makeLensTypes")
3096             # (canonLensTypes, pentaxLensTypes, nikonLensIDs, etc)
3097             my $convName = "Image::ExifTool::${mod}::" .
3098 0   0     0 ({ Nikon => 'nikonLensIDs' }->{$mk} || lc($mk) . 'LensTypes');
3099 60     60   698 no strict 'refs';
  60         137  
  60         3142  
3100 0 0       0 %$convName or last;
3101 0         0 my $printConv = \%$convName;
3102 60     60   353 use strict 'refs';
  60         122  
  60         482641  
3103             # sf = short focal
3104             # lf = long focal
3105             # sa = max aperture at short focal
3106             # la = max aperture at long focal
3107 0         0 my ($sf, $lf, $sa, $la);
3108 0 0       0 if ($info) {
3109 0         0 my @a = split ' ', $info;
3110 0   0     0 $_ eq 'undef' and $_ = undef foreach @a;
3111 0         0 ($sf, $lf, $sa, $la) = @a;
3112             # for Sony and ambiguous LensID, $info data may be incorrect:
3113             # use only if it agrees with $focalLength and $maxAv (ref JR)
3114 0 0 0     0 if ($mk eq 'Sony' and
    0 0        
3115             (($focalLength and (($sf and $focalLength < $sf - 0.5) or
3116             ($lf and $focalLength > $lf + 0.5))) or
3117             ($maxAv and (($sa and $maxAv < $sa - 0.15) or
3118             ($la and $maxAv > $la + 0.15)))))
3119             {
3120 0         0 undef $sf;
3121 0         0 undef $lf;
3122 0         0 undef $sa;
3123 0         0 undef $la;
3124             } elsif ($maxAv) {
3125             # (using the short-focal-length max aperture in place of MaxAperture
3126             # is a bad approximation, so don't do this if MaxApertureValue exists)
3127 0         0 undef $sa;
3128             }
3129             }
3130 0 0 0     0 if ($mk eq 'Pentax' and $id =~ /^\d+$/) {
3131             # for Pentax, CS4 stores an int16u, but we use 2 x int8u
3132 0         0 $id = join(' ', unpack('C*', pack('n', $id)));
3133             }
3134             # Nikon is a special case because Adobe doesn't store the full LensID
3135             # (Apple Photos does, but we have to convert back to hex)
3136 0 0       0 if ($mk eq 'Nikon') {
3137 0         0 $id = sprintf('%X', $id);
3138 0 0       0 $id = "0$id" if length($id) & 0x01; # pad with leading 0 if necessary
3139 0 0       0 $id =~ s/(..)/$1 /g and $id =~ s/ $//; # put spaces between bytes
3140 0         0 my (%newConv, %used);
3141 0         0 my $i = 0;
3142 0         0 foreach (grep /^$id/, keys %$printConv) {
3143 0         0 my $lens = $$printConv{$_};
3144 0 0       0 next if $used{$lens}; # avoid duplicates
3145 0         0 $used{$lens} = 1;
3146 0 0       0 $newConv{$i ? "$id.$i" : $id} = $lens;
3147 0         0 ++$i;
3148             }
3149 0         0 $printConv = \%newConv;
3150             }
3151 0   0     0 my $str = $$printConv{$id} || "Unknown ($id)";
3152 0         0 return Image::ExifTool::Exif::PrintLensID($et, $str, $printConv,
3153             undef, $id, $focalLength, $sa, $maxAv, $sf, $lf, $lensModel);
3154             }
3155 0         0 return "Unknown ($id)";
3156             }
3157              
3158             #------------------------------------------------------------------------------
3159             # Convert XMP date/time to EXIF format
3160             # Inputs: 0) XMP date/time string, 1) set if we aren't sure this is a date
3161             # Returns: EXIF date/time
3162             sub ConvertXMPDate($;$)
3163             {
3164 423     423 0 847 my ($val, $unsure) = @_;
3165 423 100 66     2028 if ($val =~ /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}:\d{2})(:\d{2})?\s*(\S*)$/) {
    100          
3166 119   100     422 my $s = $5 || ''; # seconds may be missing
3167 119         681 $val = "$1:$2:$3 $4$s$6"; # convert back to EXIF time format
3168             } elsif (not $unsure and $val =~ /^(\d{4})(-\d{2}){0,2}/) {
3169 73         223 $val =~ tr/-/:/;
3170             }
3171 423         971 return $val;
3172             }
3173              
3174             #------------------------------------------------------------------------------
3175             # Convert rational string value
3176             # Inputs: 0) string (converted to number, 'inf' or 'undef' on return if rational)
3177             # Returns: true if value was converted
3178             sub ConvertRational($)
3179             {
3180 438     438 0 640 my $val = $_[0];
3181 438 100       1737 $val =~ m{^(-?\d+)/(-?\d+)$} or return undef;
3182 204 100       644 if ($2 != 0) {
    50          
3183 202         534 $_[0] = $1 / $2; # calculate quotient
3184             } elsif ($1) {
3185 0         0 $_[0] = 'inf';
3186             } else {
3187 2         3 $_[0] = 'undef';
3188             }
3189 204         493 return 1;
3190             }
3191              
3192             #------------------------------------------------------------------------------
3193             # Convert a string of floating point values to rationals
3194             # Inputs: 0) string of floating point numbers separated by spaces
3195             # Returns: string of rational numbers separated by spaces
3196             sub ConvertRationalList($)
3197             {
3198 1     1 0 2 my $val = shift;
3199 1         5 my @vals = split ' ', $val;
3200 1 50       5 return $val unless @vals == 4;
3201 1         3 foreach (@vals) {
3202 4 50       9 ConvertRational($_) or return $val;
3203             }
3204 1         12 return join ' ', @vals;
3205             }
3206              
3207             #------------------------------------------------------------------------------
3208             # We found an XMP property name/value
3209             # Inputs: 0) ExifTool object ref, 1) Pointer to tag table
3210             # 2) reference to array of XMP property names (last is current property)
3211             # 3) property value, 4) attribute hash ref (for 'xml:lang' or 'rdf:datatype')
3212             # Returns: 1 if valid tag was found
3213             sub FoundXMP($$$$;$)
3214             {
3215 3640     3640 0 4437 local $_;
3216 3640         6738 my ($et, $tagTablePtr, $props, $val, $attrs) = @_;
3217 3640         4692 my ($lang, @structProps, $rawVal, $rational);
3218 3640 100       10190 my ($tag, $ns) = GetXMPTagID($props, $$et{OPTIONS}{Struct} ? \@structProps : undef);
3219 3640 100       7055 return 0 unless $tag; # ignore things that aren't valid tags
3220              
3221             # translate namespace if necessary
3222 3623 100       7713 $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns};
3223 3623         5516 my $info = $$tagTablePtr{$ns};
3224 3623         5115 my ($table, $added, $xns, $tagID);
3225 3623 100       6256 if ($info) {
    100          
3226 3255 50       7295 $table = $$info{SubDirectory}{TagTable} or warn "Missing TagTable for $tag!\n";
3227             } elsif ($$props[0] eq 'svg:svg') {
3228 9 100       31 if (not $ns) {
    50          
3229             # disambiguate MetadataID by adding back the 'metadata' we ignored
3230 4 50 33     11 $tag = 'metadataId' if $tag eq 'id' and $$props[1] eq 'svg:metadata';
3231             # use SVG namespace in SVG files if nothing better to use
3232 4         8 $table = 'Image::ExifTool::XMP::SVG';
3233             } elsif (not grep /^rdf:/, @$props) {
3234             # only other SVG information if not inside RDF (call it XMP if in RDF)
3235 5         9 $table = 'Image::ExifTool::XMP::otherSVG';
3236             }
3237             }
3238              
3239 3623         4115 my $xmlGroups;
3240 3623         6325 my $grp0 = $$tagTablePtr{GROUPS}{0};
3241 3623 100 100     11036 if (not $ns and $grp0 ne 'XMP') {
    100 66        
3242 203         264 $tagID = $tag;
3243             } elsif ($grp0 eq 'XML' and not $table) {
3244             # this is an XML table (no namespace lookup)
3245 4         8 $tagID = "$ns:$tag";
3246             } else {
3247 3416 50       5615 $xmlGroups = 1 if $grp0 eq 'XML';
3248             # look up this tag in the appropriate table
3249 3416 100       5331 $table or $table = 'Image::ExifTool::XMP::other';
3250 3416         9022 $tagTablePtr = GetTagTable($table);
3251 3416 100       7300 if ($$tagTablePtr{NAMESPACE}) {
3252 3259         4557 $tagID = $tag;
3253             } else {
3254 157         250 $xns = $xmpNS{$ns};
3255 157 50       340 unless (defined $xns) {
3256 157         198 $xns = $ns;
3257             # validate namespace prefix
3258 157 50 33     549 unless ($ns =~ /^[A-Z_a-z\x80-\xff][-.0-9A-Z_a-z\x80-\xff]*$/ or $ns eq '') {
3259 0         0 $et->Warn("Invalid XMP namespace prefix '${ns}'");
3260             # clean up prefix for use as an ExifTool group name
3261 0         0 $ns =~ tr/-.0-9A-Z_a-z\x80-\xff//dc;
3262 0 0       0 $ns =~ /^[A-Z_a-z\x80-\xff]/ or $ns = "ns_$ns";
3263 0         0 $stdXlatNS{$xns} = $ns;
3264 0         0 $xmpNS{$ns} = $xns;
3265             }
3266             }
3267             # add XMP namespace prefix to avoid collisions in variable-namespace tables
3268 157         304 $tagID = "$xns:$tag";
3269             # add namespace to top-level structure property
3270 157 100       319 $structProps[0][0] = "$xns:" . $structProps[0][0] if @structProps;
3271             }
3272             }
3273 3623         9096 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID);
3274              
3275 3623 100       7864 $lang = $$attrs{'xml:lang'} if $attrs;
3276              
3277             # must add a new tag table entry if this tag isn't pre-defined
3278             # (or initialize from structure field if this is a pre-defined flattened tag)
3279             NoLoop:
3280 3623   100     12900 while (not $tagInfo or $$tagInfo{Flat}) {
3281 218         304 my (@tagList, @nsList);
3282 218         495 GetXMPTagID($props, \@tagList, \@nsList);
3283 218         361 my ($ta, $t, $ti, $addedFlat, $i, $j);
3284             # build tag ID strings for each level in the property path
3285 218         318 foreach $ta (@tagList) {
3286             # insert tag ID in index 1 of tagList list
3287 438 100       992 $t = $$ta[1] = $t ? $t . ucfirst($$ta[0]) : $$ta[0];
3288             # generate flattened tags for top-level structure if necessary
3289 438 100       707 next if defined $addedFlat;
3290 357 100       854 $ti = $$tagTablePtr{$t} or next;
3291 45 50 33     232 next unless ref $ti eq 'HASH' and $$ti{Struct};
3292 45         123 $addedFlat = AddFlattenedTags($tagTablePtr, $t);
3293             # all done if we generated the tag we are looking for
3294 45 100 50     186 $tagInfo = $$tagTablePtr{$tagID} and last NoLoop if $addedFlat;
3295             }
3296 196         337 my $name = ucfirst($tag);
3297              
3298             # search for the innermost containing structure
3299             # (in case tag is an unknown field in a known structure)
3300             # (only necessary if we found a structure above)
3301 196 100       376 if (defined $addedFlat) {
3302 23         31 my $t2 = '';
3303 23         57 for ($i=$#tagList-1; $i>=0; --$i) {
3304 41         62 $t = $tagList[$i][1];
3305 41         82 $t2 = $tagList[$i+1][0] . ucfirst($t2); # build relative tag id
3306 41 100       99 $ti = $$tagTablePtr{$t} or next;
3307 33 50       70 next unless ref $ti eq 'HASH';
3308 33 100       78 my $strTable = $$ti{Struct} or next;
3309 23 50       53 my $flat = (defined $$ti{FlatName} ? $$ti{FlatName} : $$ti{Name});
3310 23         34 $name = $flat . ucfirst($t2);
3311             # don't continue if structure is known but field is not
3312 23 100 66     91 last if $$strTable{NAMESPACE} or not exists $$strTable{NAMESPACE};
3313             # this is a variable-namespace structure, so we must:
3314             # 1) get tagInfo from corresponding top-level XMP tag if it exists
3315             # 2) add new entry in this tag table, but with namespace prefix on tag ID
3316 22         35 my $n = $nsList[$i+1]; # namespace of structure field
3317             # translate to standard ExifTool namespace
3318 22 100       46 $n = $stdXlatNS{$n} if $stdXlatNS{$n};
3319 22   66     62 my $xn = $xmpNS{$n} || $n; # standard XMP namespace
3320             # no need to continue with variable-namespace logic if
3321             # we are in our own namespace (right?)
3322 22 50 50     54 last if $xn eq ($$tagTablePtr{NAMESPACE} || '');
3323 22         45 $tagID = "$xn:$tag"; # add namespace to avoid collisions
3324             # change structure properties to add the standard XMP namespace
3325             # prefix for this field (needed for variable-namespace fields)
3326 22 100       40 if (@structProps) {
3327 17         49 $structProps[$i+1][0] = "$xn:" . $structProps[$i+1][0];
3328             }
3329             # copy tagInfo entries from the existing top-level XMP tag
3330 22         42 my $tg = $Image::ExifTool::XMP::Main{$n};
3331 22 50 33     80 last unless ref $tg eq 'HASH' and $$tg{SubDirectory};
3332 22 50       62 my $tbl = GetTagTable($$tg{SubDirectory}{TagTable}) or last;
3333 22         53 my $sti = $et->GetTagInfo($tbl, $t2);
3334 22 50 33     90 if (not $sti or $$sti{Flat}) {
3335             # again, we must initialize flattened tags if necessary
3336             # (but don't bother to recursively apply full logic to
3337             # allow nested variable-namespace strucures until someone
3338             # actually wants to do such a silly thing)
3339 0         0 my $t3 = '';
3340 0         0 for ($j=$i+1; $j<@tagList; ++$j) {
3341 0         0 $t3 = $tagList[$j][0] . ucfirst($t3);
3342 0 0       0 my $ti3 = $$tbl{$t3} or next;
3343 0 0 0     0 next unless ref $ti3 eq 'HASH' and $$ti3{Struct};
3344 0 0       0 last unless AddFlattenedTags($tbl, $t3);
3345 0         0 $sti = $$tbl{$t2};
3346 0         0 last;
3347             }
3348 0 0       0 last unless $sti;
3349             }
3350             # generate new tagInfo hash based on existing top-level tag
3351 22         196 $tagInfo = { %$sti, Name => $flat . $$sti{Name} };
3352             # be careful not to copy elements we shouldn't...
3353 22         46 delete $$tagInfo{Description}; # Description will be different
3354             # can't copy group hash because group 1 will be different and
3355             # we need to check this when writing tag to a specific group
3356 22         36 delete $$tagInfo{Groups};
3357 22 50       77 $$tagInfo{Groups}{2} = $$sti{Groups}{2} if $$sti{Groups};
3358 22         49 last;
3359             }
3360             }
3361             # generate a default tagInfo hash if necessary
3362 196 100       334 unless ($tagInfo) {
3363             # shorten tag name if necessary
3364 173 100       368 if ($$et{ShortenXmpTags}) {
3365 27         32 my $shorten = $$et{ShortenXmpTags};
3366 27         64 $name = &$shorten($name);
3367             }
3368 173         512 $tagInfo = { Name => $name, IsDefault => 1, Priority => 0 };
3369             }
3370             # add tag Namespace entry for tags in variable-namespace tables
3371 196 100       439 $$tagInfo{Namespace} = $xns if $xns;
3372 196 100 100     849 if ($$et{curURI}{$ns} and $$et{curURI}{$ns} =~ m{^http://ns.exiftool.(?:ca|org)/(.*?)/(.*?)/}) {
3373 78         385 my %grps = ( 0 => $1, 1 => $2 );
3374             # apply a little magic to recover original group names
3375             # from this exiftool-written RDF/XML file
3376 78 100       197 if ($grps{1} =~ /^\d/) {
3377             # URI's with only family 0 are internal tags from the source file,
3378             # so change the group name to avoid confusion with tags from this file
3379 20         45 $grps{1} = "XML-$grps{0}";
3380 20         29 $grps{0} = 'XML';
3381             }
3382 78         132 $$tagInfo{Groups} = \%grps;
3383             # flag to avoid setting group 1 later
3384 78         154 $$tagInfo{StaticGroup1} = 1;
3385             }
3386             # construct tag information for this unknown tag
3387             # -> make this a List or lang-alt tag if necessary
3388 196 100 100     845 if (@$props > 2 and $$props[-1] =~ /^rdf:li \d+$/ and
      66        
3389             $$props[-2] =~ /^rdf:(Bag|Seq|Alt)$/)
3390             {
3391 17 100 66     67 if ($lang and $1 eq 'Alt') {
3392 12         25 $$tagInfo{Writable} = 'lang-alt';
3393             } else {
3394 5         15 $$tagInfo{List} = $1;
3395             }
3396             # tried this, but maybe not a good idea for complex structures:
3397             #} elsif (grep / /, @$props) {
3398             # $$tagInfo{List} = 1;
3399             }
3400             # save property list for verbose "adding" message unless this tag already exists
3401 196 100       429 $added = \@tagList unless $$tagTablePtr{$tagID};
3402             # if this is an empty structure, we must add a Struct field
3403 196 50 66     406 if (not length $val and $$attrs{'rdf:parseType'} and $$attrs{'rdf:parseType'} eq 'Resource') {
      33        
3404 0         0 $$tagInfo{Struct} = { STRUCT_NAME => 'XMP Unknown' };
3405             }
3406 196         513 AddTagToTable($tagTablePtr, $tagID, $tagInfo);
3407 196         393 last;
3408             }
3409             # decode value if necessary (et:encoding was used before exiftool 7.71)
3410 3623 100       6589 if ($attrs) {
3411 3420   66     8565 my $enc = $$attrs{'rdf:datatype'} || $$attrs{'et:encoding'};
3412 3420 100 66     6830 if ($enc and $enc =~ /base64/) {
3413 1         5 $val = DecodeBase64($val); # (now a value ref)
3414 1 50 33     6 $val = $$val unless length $$val > 100 or $$val =~ /[\0-\x08\x0b\0x0c\x0e-\x1f]/;
3415             }
3416             }
3417 3623 100 100     7628 if (defined $lang and lc($lang) ne 'x-default') {
3418 53         129 $lang = StandardLangCase($lang);
3419 53         196 my $langInfo = GetLangInfo($tagInfo, $lang);
3420 53 50       118 $tagInfo = $langInfo if $langInfo;
3421             }
3422             # un-escape XML character entities (handling CDATA)
3423 3623         7799 pos($val) = 0;
3424 3623 100       8048 if ($val =~ //sg) {
3425 9         20 my $p = pos $val;
3426             # unescape everything up to the start of the CDATA section
3427             # (the length of "<[[CDATA[]]>" is 12 characters)
3428 9         36 my $v = UnescapeXML(substr($val, 0, $p - length($1) - 12)) . $1;
3429 9         28 while ($val =~ //sg) {
3430 0         0 my $p1 = pos $val;
3431 0         0 $v .= UnescapeXML(substr($val, $p, $p1 - length($1) - 12)) . $1;
3432 0         0 $p = $p1;
3433             }
3434 9         25 $val = $v . UnescapeXML(substr($val, $p));
3435             } else {
3436 3614         6632 $val = UnescapeXML($val);
3437             }
3438             # decode from UTF8
3439 3623         9362 $val = $et->Decode($val, 'UTF8');
3440             # convert rational and date values to a more sensible format
3441 3623         6536 my $fmt = $$tagInfo{Writable};
3442 3623   66     7036 my $new = $$tagInfo{IsDefault} && $$et{OPTIONS}{XMPAutoConv};
3443 3623 100 100     8830 if ($fmt or $new) {
3444 1628         2354 $rawVal = $val; # save raw value for verbose output
3445 1628 100 100     5645 if (($new or $fmt eq 'rational') and ConvertRational($val)) {
      100        
3446 199         296 $rational = $rawVal;
3447             } else {
3448 1429 100 100     4723 $val = ConvertXMPDate($val, $new) if $new or $fmt eq 'date';
3449             }
3450 1628 0 33     3513 if ($$et{XmpValidate} and $fmt and $fmt eq 'boolean') {
      33        
3451 0         0 $et->WarnOnce("Boolean value for XMP-$ns:$$tagInfo{Name} should be capitalized",1);
3452             }
3453             # protect against large binary data in unknown tags
3454 1628 50 66     3318 $$tagInfo{Binary} = 1 if $new and length($val) > 65536;
3455             }
3456             # store the value for this tag
3457 3623 50       8043 my $key = $et->FoundTag($tagInfo, $val) or return 0;
3458             # save original components of rational numbers (used when copying)
3459 3623 100       6901 $$et{RATIONAL}{$key} = $rational if defined $rational;
3460             # save structure/list information if necessary
3461 3623 100 100     9253 if (@structProps and (@structProps > 1 or defined $structProps[0][1]) and
      100        
      66        
3462             not $$et{NO_STRUCT})
3463             {
3464 329         876 $$et{TAG_EXTRA}{$key}{Struct} = \@structProps;
3465 329         540 $$et{IsStruct} = 1;
3466             }
3467 3623 50 100     12125 if ($xmlGroups) {
    100          
3468 0         0 $et->SetGroup($key, 'XML', 0);
3469 0         0 $et->SetGroup($key, "XML-$ns", 1);
3470             } elsif ($ns and not $$tagInfo{StaticGroup1}) {
3471             # set group1 dynamically according to the namespace
3472 3307         11697 $et->SetGroup($key, "$$tagTablePtr{GROUPS}{0}-$ns");
3473             }
3474 3623 100       8134 if ($$et{OPTIONS}{Verbose}) {
3475 1 50       4 if ($added) {
3476 0         0 my $props;
3477 0 0       0 if (@$added > 1) {
3478 0         0 $$tagInfo{Flat} = 0; # this is a flattened tag
3479 0         0 my @props = map { $$_[0] } @$added;
  0         0  
3480 0         0 $props = ' (' . join('/',@props) . ')';
3481             } else {
3482 0         0 $props = '';
3483             }
3484 0         0 my $g1 = $et->GetGroup($key, 1);
3485 0         0 $et->VPrint(0, $$et{INDENT}, "[adding $g1:$tag]$props\n");
3486             }
3487 1         5 my $tagID = join('/',@$props);
3488 1   33     6 $et->VerboseInfo($tagID, $tagInfo, Value => $rawVal || $val);
3489             }
3490             # allow read-only subdirectories (eg. embedded base64 XMP/IPTC in NKSC files)
3491 3623 50 33     7330 if ($$tagInfo{SubDirectory} and not $$et{IsWriting}) {
3492 0         0 my $subdir = $$tagInfo{SubDirectory};
3493 0 0       0 my $dataPt = ref $$et{VALUE}{$key} ? $$et{VALUE}{$key} : \$$et{VALUE}{$key};
3494             # decode if necessary (eg. Nikon XMP-ast:XMLPackets)
3495 0 0 0     0 $dataPt = DecodeBase64($$dataPt) if $$tagInfo{Encoding} and $$tagInfo{Encoding} eq 'Base64';
3496             # process subdirectory information
3497             my %dirInfo = (
3498             DirName => $$subdir{DirName} || $$tagInfo{Name},
3499             DataPt => $dataPt,
3500             DirLen => length $$dataPt,
3501             IgnoreProp => $$subdir{IgnoreProp}, # (allow XML to ignore specified properties)
3502 0   0     0 IsExtended => 1, # (hack to avoid Duplicate warning for embedded XMP)
3503             );
3504 0         0 my $oldOrder = GetByteOrder();
3505 0 0       0 SetByteOrder($$subdir{ByteOrder}) if $$subdir{ByteOrder};
3506 0         0 my $oldNS = $$et{definedNS};
3507 0         0 delete $$et{definedNS};
3508 0   0     0 my $subTablePtr = GetTagTable($$subdir{TagTable}) || $tagTablePtr;
3509 0         0 $et->ProcessDirectory(\%dirInfo, $subTablePtr, $$subdir{ProcessProc});
3510 0         0 SetByteOrder($oldOrder);
3511 0         0 $$et{definedNS} = $oldNS;
3512             }
3513 3623         9674 return 1;
3514             }
3515              
3516             #------------------------------------------------------------------------------
3517             # Recursively parse nested XMP data element
3518             # Inputs: 0) ExifTool ref, 1) tag table ref, 2) XMP data ref
3519             # 3) offset to start of XMP element, 4) offset to end of XMP element
3520             # 5) reference to array of enclosing XMP property names (undef if none)
3521             # 6) reference to blank node information hash
3522             # Returns: Number of contained XMP elements
3523             sub ParseXMPElement($$$;$$$$)
3524             {
3525 7679     7679 0 9752 local $_;
3526 7679         13974 my ($et, $tagTablePtr, $dataPt, $start, $end, $propList, $blankInfo) = @_;
3527 7679         11111 my ($count, $nItems) = (0, 0);
3528 7679         10937 my $isWriting = $$et{XMP_CAPTURE};
3529 7679         9806 my $isSVG = $$et{XMP_IS_SVG};
3530 7679         8774 my $saveNS; # save xlatNS lookup if changed for the scope of this element
3531 7679         13997 my (%definedNS, %usedNS); # namespaces defined and used in this scope
3532              
3533             # get our parse procs
3534 7679         0 my ($attrProc, $foundProc);
3535 7679 100       12186 if ($$et{XMPParseOpts}) {
3536 154         208 $attrProc = $$et{XMPParseOpts}{AttrProc};
3537 154   50     272 $foundProc = $$et{XMPParseOpts}{FoundProc} || \&FoundXMP;
3538             } else {
3539 7525         11578 $foundProc = \&FoundXMP;
3540             }
3541 7679 100       12726 $start or $start = 0;
3542 7679 50       11752 $end or $end = length $$dataPt;
3543 7679 100       12237 $propList or $propList = [ ];
3544              
3545 7679         8508 my $processBlankInfo;
3546             # create empty blank node information hash if necessary
3547 7679 100       11682 $blankInfo or $blankInfo = $processBlankInfo = { Prop => { } };
3548             # keep track of current nodeID at this nesting level
3549 7679         10422 my $oldNodeID = $$blankInfo{NodeID};
3550 7679         14560 pos($$dataPt) = $start;
3551              
3552             # lookup for translating namespace prefixes
3553 7679         12888 my $xlatNS = $$et{xlatNS};
3554              
3555 7679         8845 Element: for (;;) {
3556             # all done if there isn't enough data for another element
3557             # (the smallest possible element is 4 bytes, eg. "")
3558 15831 100       28977 last if pos($$dataPt) > $end - 4;
3559             # reset nodeID before processing each element
3560 11188         15709 my $nodeID = $$blankInfo{NodeID} = $oldNodeID;
3561             # get next element
3562 11188 100 100     55874 last if $$dataPt !~ m{<([?/]?)([-\w:.\x80-\xff]+|!--)([^>]*)>}sg or pos($$dataPt) > $end;
3563             # (the only reason we match '<[?/]' is to keep from scanning past the
3564             # "
3565 8219 100       17911 next if $1;
3566 7663         18156 my ($prop, $attrs) = ($2, $3);
3567             # skip comments
3568 7663 100       13682 if ($prop eq '!--') {
3569 158 50 33     467 next if $attrs =~ /--$/ or $$dataPt =~ /-->/sg;
3570 0         0 last;
3571             }
3572 7505         9670 my $valStart = pos($$dataPt);
3573 7505         8875 my $valEnd;
3574             # only look for closing token if this is not an empty element
3575             # (empty elements end with '/', eg. )
3576 7505 100       14209 if ($attrs !~ s/\/$//) {
3577 7384         8737 my $nesting = 1;
3578 7384         8715 for (;;) {
3579             # this match fails with perl 5.6.2 (perl bug!), but it works without
3580             # the '(.*?)', so we must do it differently...
3581             # $$dataPt =~ m/(.*?)<\/$prop>/sg or last Element;
3582             # my $val2 = $1;
3583             # find next matching closing token, or the next opening token
3584             # of a nested same-named element
3585 7706 50 33     273643 if ($$dataPt !~ m{<(/?)$prop([-\w:.\x80-\xff]*)(.*?(/?))>}sg or
3586             pos($$dataPt) > $end)
3587             {
3588 0         0 $et->Warn("XMP format error (no closing tag for $prop)");
3589 0         0 last Element;
3590             }
3591 7706 100       25347 next if $2; # ignore opening properties with different names
3592 7668 100       14532 if ($1) {
3593 7524 100       12692 next if --$nesting;
3594 7384         13398 $valEnd = pos($$dataPt) - length($prop) - length($3) - 3;
3595 7384         12721 last; # this element is complete
3596             }
3597             # this is a nested opening token (or empty element)
3598 144 100       341 ++$nesting unless $4;
3599             }
3600             } else {
3601 121         202 $valEnd = $valStart;
3602             }
3603 7505         9805 $start = pos($$dataPt); # start from here the next time around
3604              
3605             # ignore specified XMP namespaces/properties
3606 7505 0 33     16820 if ($$et{EXCL_XMP_LOOKUP} and not $isWriting and $prop =~ /^(.+):(.*)/) {
      33        
3607 0   0     0 my ($ns, $nm) = (lc($stdXlatNS{$1} || $1), lc($2));
3608 0 0 0     0 if ($$et{EXCL_XMP_LOOKUP}{"xmp-$ns:all"} or $$et{EXCL_XMP_LOOKUP}{"xmp-$ns:$nm"} or
      0        
3609             $$et{EXCL_XMP_LOOKUP}{"xmp-all:$nm"})
3610             {
3611 0         0 ++$count; # (pretend we found something so we don't store as a tag value)
3612 0         0 next;
3613             }
3614             }
3615              
3616             # extract property attributes
3617 7505         11249 my ($parseResource, %attrs, @attrs);
3618 7505         23088 while ($attrs =~ m/(\S+?)\s*=\s*(['"])(.*?)\2/sg) {
3619 4272         11013 my ($attr, $val) = ($1, $3);
3620             # handle namespace prefixes (defined by xmlns:PREFIX, or used with PREFIX:tag)
3621 4272 100       10600 if ($attr =~ /(.*?):/) {
3622 3789 100       7800 if ($1 eq 'xmlns') {
3623 1526         2942 my $ns = substr($attr, 6);
3624 1526         3729 my $stdNS = $uri2ns{$val};
3625             # keep track of namespace prefixes defined in this scope (for Validate)
3626 1526 100       5218 $$et{definedNS}{$ns} = $definedNS{$ns} = 1 unless $$et{definedNS}{$ns};
3627 1526 100       2807 unless ($stdNS) {
3628 46         82 my $try = $val;
3629             # patch for Nikon NX2 URI bug for Microsoft PhotoInfo namespace
3630 46 100       176 $try =~ s{/$}{} or $try .= '/';
3631 46         102 $stdNS = $uri2ns{$try};
3632 46 50       81 if ($stdNS) {
3633 0         0 $val = $try;
3634 0         0 $et->WarnOnce("Fixed incorrect URI for xmlns:$ns", 1);
3635             } else {
3636             # look for same namespace with different version number
3637 46         94 $try = quotemeta $val; # (note: escapes slashes too)
3638 46         225 $try =~ s{\\/\d+\\\.\d+(\\/|$)}{\\/\\d+\\\.\\d+$1};
3639 46         2694 my ($good) = grep /^$try$/, keys %uri2ns;
3640 46 50       297 if ($good) {
3641 0         0 $stdNS = $uri2ns{$good};
3642 0         0 $et->VPrint(0, $$et{INDENT}, "[different $stdNS version: $val]\n");
3643             }
3644             }
3645             }
3646             # tame wild namespace prefixes (patches Microsoft stupidity)
3647 1526         2100 my $newNS;
3648 1526 100       2487 if ($stdNS) {
    50          
3649             # use standard namespace prefix if pre-defined
3650 1480 100       3582 if ($stdNS ne $ns) {
    100          
3651 202         332 $newNS = $stdNS;
3652             } elsif ($$xlatNS{$ns}) {
3653             # this prefix is re-defined to the standard prefix in this scope
3654 2         5 $newNS = '';
3655             }
3656             } elsif ($$et{curNS}{$val}) {
3657             # use a consistent prefix over the entire XMP for a given namespace URI
3658 0 0       0 $newNS = $$et{curNS}{$val} if $$et{curNS}{$val} ne $ns;
3659             } else {
3660 46         81 my $curURI = $$et{curURI};
3661 46         75 my $curNS = $$et{curNS};
3662 46         62 my $usedNS = $ns;
3663             # use unique prefixes for all namespaces across the entire XMP
3664 46 100 66     212 if ($$curURI{$ns} or $nsURI{$ns}) {
3665             # generate a temporary namespace prefix to resolve any conflict
3666 2         7 my $i = 0;
3667 2         10 ++$i while $$curURI{"tmp$i"};
3668 2         7 $newNS = $usedNS = "tmp$i";
3669             }
3670             # keep track of the namespace prefixes and URI's used in this XMP
3671 46         118 $$curNS{$val} = $usedNS;
3672 46         90 $$curURI{$usedNS} = $val;
3673             }
3674 1526 100       3114 if (defined $newNS) {
3675             # save translation used in containing scope if necessary
3676             # create new namespace translation for the scope of this element
3677 206 100       693 $saveNS or $saveNS = $xlatNS, $xlatNS = $$et{xlatNS} = { %$xlatNS };
3678 206 100       417 if (length $newNS) {
3679             # use the new namespace prefix
3680 204         389 $$xlatNS{$ns} = $newNS;
3681 204         378 $attr = 'xmlns:' . $newNS;
3682             # must go through previous attributes and change prefixes if necessary
3683 204         430 foreach (@attrs) {
3684 332 50 66     1441 next unless /(.*?):/ and $1 eq $ns and $1 ne $newNS;
      33        
3685 0         0 my $newAttr = $newNS . substr($_, length($ns));
3686 0         0 $attrs{$newAttr} = $attrs{$_};
3687 0         0 delete $attrs{$_};
3688 0         0 $_ = $newAttr;
3689             }
3690             } else {
3691 2         7 delete $$xlatNS{$ns};
3692             }
3693             }
3694             } else {
3695 2263 100       4673 $attr = $$xlatNS{$1} . substr($attr, length($1)) if $$xlatNS{$1};
3696 2263         4082 $usedNS{$1} = 1;
3697             }
3698             }
3699 4272         6952 push @attrs, $attr; # preserve order
3700 4272         17607 $attrs{$attr} = $val;
3701             }
3702 7505 100       19946 if ($prop =~ /(.*?):/) {
3703 7160         13812 $usedNS{$1} = 1;
3704             # tame wild namespace prefixes (patch for Microsoft stupidity)
3705 7160 100       13741 $prop = $$xlatNS{$1} . substr($prop, length($1)) if $$xlatNS{$1};
3706             }
3707              
3708 7505 100       18668 if ($prop eq 'rdf:li') {
    100          
    50          
3709             # impose a reasonable maximum on the number of items in a list
3710 1411 50       2818 if ($nItems == 1000) {
3711 0         0 my ($tg,$ns) = GetXMPTagID($propList);
3712 0 0       0 if ($isWriting) {
    0          
3713 0         0 $et->WarnOnce("Excessive number of items for $ns:$tg. Processing may be slow", 1);
3714             } elsif (not $$et{OPTIONS}{IgnoreMinorErrors}) {
3715 0         0 $et->WarnOnce("Extracted only 1000 $ns:$tg items. Ignore minor errors to extract all", 2);
3716 0         0 last;
3717             }
3718             }
3719             # add index to list items so we can keep them in order
3720             # (this also enables us to keep structure elements grouped properly
3721             # for lists of structures, like JobRef)
3722             # Note: the list index is prefixed by the number of digits so sorting
3723             # alphabetically gives the correct order while still allowing a flexible
3724             # number of digits -- this scheme allows up to 9 digits in the index,
3725             # with index numbers ranging from 0 to 999999999. The sequence is:
3726             # 10,11,12-19,210,211-299,3100,3101-3999,41000...9999999999.
3727 1411         3347 $prop .= ' ' . length($nItems) . $nItems;
3728             # reset LIST_TAGS at the start of the outtermost list
3729             # (avoids accumulating incorrectly-written elements in a correctly-written list)
3730 1411 100 100     6352 if (not $nItems and not grep /^rdf:li /, @$propList) {
3731 780         2313 $$et{LIST_TAGS} = { };
3732             }
3733 1411         2060 ++$nItems;
3734             } elsif ($prop eq 'rdf:Description') {
3735             # remove unnecessary rdf:Description elements since parseType='Resource'
3736             # is more efficient (also necessary to make property path consistent)
3737 751 100       2890 if (grep /^rdf:Description$/, @$propList) {
3738 4         9 $parseResource = 1;
3739             # set parseType so we know this is a structure
3740 4         11 $attrs{'rdf:parseType'} = 'Resource';
3741             }
3742             } elsif ($prop eq 'xmp:xmpmeta') {
3743             # patch MicrosoftPhoto unconformity
3744 0         0 $prop = 'x:xmpmeta';
3745 0 0       0 $et->Warn('Wrong namespace for xmpmeta') if $$et{XmpValidate};
3746             }
3747              
3748             # hook for special parsing of attributes
3749 7505         8989 my $val;
3750 7505 100       12356 if ($attrProc) {
3751 58         110 $val = substr($$dataPt, $valStart, $valEnd - $valStart);
3752 58 100       147 if (&$attrProc(\@attrs, \%attrs, \$prop, \$val)) {
3753             # the value was changed, so reset $valStart/$valEnd to use $val instead
3754 53         72 $valStart = $valEnd;
3755             }
3756             }
3757              
3758             # add nodeID to property path (with leading ' #') if it exists
3759 7505 100       13154 if (defined $attrs{'rdf:nodeID'}) {
3760 16         36 $nodeID = $$blankInfo{NodeID} = $attrs{'rdf:nodeID'};
3761 16         30 delete $attrs{'rdf:nodeID'};
3762 16         35 $prop .= ' #' . $nodeID;
3763 16         23 undef $parseResource; # can't ignore if this is a node
3764             }
3765              
3766             # push this property name onto our hierarchy list
3767 7505 50       16399 push @$propList, $prop unless $parseResource;
3768              
3769 7505 100       15298 if ($isSVG) {
    100          
3770             # ignore everything but top level SVG tags and metadata unless Unknown set
3771 16 50 33     64 unless ($$et{OPTIONS}{Unknown} > 1 or $$et{OPTIONS}{Verbose}) {
3772 16 50 66     95 if (@$propList > 1 and $$propList[1] !~ /\b(metadata|desc|title)$/) {
3773 0         0 pop @$propList;
3774 0         0 next;
3775             }
3776             }
3777 16 100 100     53 if ($prop eq 'svg' or $prop eq 'metadata') {
3778             # add svg namespace prefix if missing to ignore these entries in the tag name
3779 2         9 $$propList[-1] = "svg:$prop";
3780             }
3781             } elsif ($$et{XmpIgnoreProps}) { # ignore specified properties for tag name
3782 13         15 foreach (@{$$et{XmpIgnoreProps}}) {
  13         27  
3783 38 100       52 last unless @$propList;
3784 36 100       60 pop @$propList if $_ eq $$propList[0];
3785             }
3786             }
3787              
3788             # handle properties inside element attributes (RDF shorthand format):
3789             # (attributes take the form a:b='c' or a:b="c")
3790 7505         10024 my ($shortName, $shorthand, $ignored);
3791 7505         12422 foreach $shortName (@attrs) {
3792 4166 100       7742 next unless defined $attrs{$shortName};
3793 4150         5295 my $propName = $shortName;
3794 4150         5192 my ($ns, $name);
3795 4150 100       11822 if ($propName =~ /(.*?):(.*)/) {
    100          
3796 3773         6371 $ns = $1; # specified namespace
3797 3773         5320 $name = $2;
3798             } elsif ($prop =~ /(\S*?):/) {
3799 250         505 $ns = $1; # assume same namespace as parent
3800 250         410 $name = $propName;
3801 250         514 $propName = "$ns:$name"; # generate full property name
3802             } else {
3803             # a property qualifier is the only property name that may not
3804             # have a namespace, and a qualifier shouldn't have attributes,
3805             # but what the heck, let's allow this anyway
3806 127         164 $ns = '';
3807 127         147 $name = $propName;
3808             }
3809 4150 100       7186 if ($propName eq 'rdf:about') {
3810 737 100       2138 if (not $$et{XmpAbout}) {
    50          
3811 452         1209 $$et{XmpAbout} = $attrs{$shortName};
3812             } elsif ($$et{XmpAbout} ne $attrs{$shortName}) {
3813 0 0       0 if ($isWriting) {
    0          
3814 0         0 my $str = "Different 'rdf:about' attributes not handled";
3815 0 0       0 unless ($$et{WARNED_ONCE}{$str}) {
3816 0         0 $et->Error($str, 1);
3817 0         0 $$et{WARNED_ONCE}{$str} = 1;
3818             }
3819             } elsif ($$et{XmpValidate}) {
3820 0         0 $et->WarnOnce("Different 'rdf:about' attributes");
3821             }
3822             }
3823             }
3824 4150 100       6787 if ($isWriting) {
3825             # keep track of our namespaces when writing
3826 1227 100       2581 if ($ns eq 'xmlns') {
    100          
3827 341         665 my $stdNS = $uri2ns{$attrs{$shortName}};
3828 341 100 100     1327 unless ($stdNS and ($stdNS eq 'x' or $stdNS eq 'iX')) {
      100        
3829 276         411 my $nsUsed = $$et{XMP_NS};
3830 276 100       3764 $$nsUsed{$name} = $attrs{$shortName} unless defined $$nsUsed{$name};
3831             }
3832 341         683 delete $attrs{$shortName}; # (handled by namespace logic)
3833 341         571 next;
3834             } elsif ($recognizedAttrs{$propName}) {
3835 203         373 next;
3836             }
3837             }
3838 3606         5545 my $shortVal = $attrs{$shortName};
3839 3606 100 66     8172 if ($ignoreNamespace{$ns} or $ignoreProp{$prop}) {
3840 2804         3648 $ignored = $propName;
3841             # handle special attributes (extract as tags only once if not empty)
3842 2804 100 100     7064 if (ref $recognizedAttrs{$propName} and $shortVal) {
3843 466         974 my ($tbl, $id, $name) = @{$recognizedAttrs{$propName}};
  466         1474  
3844 466         1291 my $tval = UnescapeXML($shortVal);
3845 466 100 66     2114 unless (defined $$et{VALUE}{$name} and $$et{VALUE}{$name} eq $tval) {
3846 235         806 $et->HandleTag(GetTagTable($tbl), $id, $tval);
3847             }
3848             }
3849 2804         4980 next;
3850             }
3851 802         1333 delete $attrs{$shortName}; # don't re-use this attribute
3852 802         1271 push @$propList, $propName;
3853             # save this shorthand XMP property
3854 802 100       1368 if (defined $nodeID) {
    100          
3855 4         18 SaveBlankInfo($blankInfo, $propList, $shortVal);
3856             } elsif ($isWriting) {
3857 512         933 CaptureXMP($et, $propList, $shortVal);
3858             } else {
3859 286 50       496 ValidateProperty($et, $propList) if $$et{XmpValidate};
3860 286         549 &$foundProc($et, $tagTablePtr, $propList, $shortVal);
3861             }
3862 802         1154 pop @$propList;
3863 802         1259 $shorthand = 1;
3864             }
3865 7505 100       11444 if ($isWriting) {
3866 1222 100 66     2625 if (ParseXMPElement($et, $tagTablePtr, $dataPt, $valStart, $valEnd,
    100          
3867             $propList, $blankInfo))
3868             {
3869             # (no value since we found more properties within this one)
3870             # set an error on any ignored attributes here, because they will be lost
3871 579 50       979 $$et{XMP_ERROR} = "Can't handle XMP attribute '${ignored}'" if $ignored;
3872             } elsif (not $shorthand or $valEnd != $valStart) {
3873 625         1356 $val = substr($$dataPt, $valStart, $valEnd - $valStart);
3874             # remove comments and whitespace from rdf:Description only
3875 625 100       1055 if ($prop eq 'rdf:Description') {
3876 8         46 $val =~ s///g; $val =~ s/^\s+//; $val =~ s/\s+$//;
  8         27  
  8         15  
3877             }
3878 625 100       1190 if (defined $nodeID) {
3879 13         39 SaveBlankInfo($blankInfo, $propList, $val, \%attrs);
3880             } else {
3881 612         1798 CaptureXMP($et, $propList, $val, \%attrs);
3882             }
3883             }
3884             } else {
3885             # look for additional elements contained within this one
3886 6283 100 100     19336 if ($valStart == $valEnd or
3887             !ParseXMPElement($et, $tagTablePtr, $dataPt, $valStart, $valEnd,
3888             $propList, $blankInfo))
3889             {
3890 3614         4590 my $wasEmpty;
3891 3614 100       6228 unless (defined $val) {
3892 3561         7156 $val = substr($$dataPt, $valStart, $valEnd - $valStart);
3893             # remove comments and whitespace from rdf:Description only
3894 3561 100 66     6910 if ($prop eq 'rdf:Description' and $val) {
3895 16         96 $val =~ s///g; $val =~ s/^\s+//; $val =~ s/\s+$//;
  16         51  
  16         31  
3896             }
3897             # if element value is empty, take value from RDF 'value' or 'resource' attribute
3898             # (preferentially) or 'about' attribute (if no 'value' or 'resource')
3899 3561 100 100     6514 if ($val eq '' and ($attrs =~ /\brdf:(?:value|resource)=(['"])(.*?)\1/ or
      100        
3900             $attrs =~ /\brdf:about=(['"])(.*?)\1/))
3901             {
3902 17         46 $val = $2;
3903 17         25 $wasEmpty = 1;
3904             }
3905             }
3906             # there are no contained elements, so this must be a simple property value
3907             # (unless we already extracted shorthand values from this element)
3908 3614 100 100     7498 if (length $val or not $shorthand) {
3909 3601         5580 my $lastProp = $$propList[-1];
3910 3601 50       6043 $lastProp = '' unless defined $lastProp;
3911 3601 100 66     12067 if (defined $nodeID) {
    100 0        
    50 33        
3912 13         36 SaveBlankInfo($blankInfo, $propList, $val);
3913             } elsif ($lastProp eq 'rdf:type' and $wasEmpty) {
3914             # do not extract empty structure types (for now)
3915             } elsif ($lastProp =~ /^et:(desc|prt|val)$/ and ($count or $1 eq 'desc')) {
3916             # ignore et:desc, and et:val if preceded by et:prt
3917 0         0 --$count;
3918             } else {
3919 3580 50       6644 ValidateProperty($et, $propList, \%attrs) if $$et{XmpValidate};
3920 3580         7615 &$foundProc($et, $tagTablePtr, $propList, $val, \%attrs);
3921             }
3922             }
3923             }
3924             }
3925 7505 50       13666 pop @$propList unless $parseResource;
3926 7505         9264 ++$count;
3927              
3928             # validate namespace prefixes used at this level if necessary
3929 7505 50       13287 if ($$et{XmpValidate}) {
3930 0         0 foreach (sort keys %usedNS) {
3931 0 0 0     0 next if $$et{definedNS}{$_} or $_ eq 'xml';
3932 0 0       0 if (defined $$et{definedNS}{$_}) {
3933 0         0 $et->Warn("XMP namespace $_ is used out of scope");
3934             } else {
3935 0         0 $et->Warn("Undefined XMP namespace: $_");
3936             }
3937 0         0 $$et{definedNS}{$_} = -1; # (don't warn again for this namespace)
3938             }
3939             # reset namespaces that went out of scope
3940 0         0 $$et{definedNS}{$_} = 0 foreach keys %definedNS;
3941 0         0 undef %usedNS;
3942 0         0 undef %definedNS;
3943             }
3944              
3945 7505 100       11698 last if $start >= $end;
3946 7438         12708 pos($$dataPt) = $start;
3947 7438         24793 $$dataPt =~ /\G\s+/gc; # skip white space after closing token
3948             }
3949             #
3950             # process resources referenced by blank nodeID's
3951             #
3952 7679 100 100     13716 if ($processBlankInfo and %{$$blankInfo{Prop}}) {
  276         1021  
3953 4         17 ProcessBlankInfo($et, $tagTablePtr, $blankInfo, $isWriting);
3954 4         13 %$blankInfo = (); # free some memory
3955             }
3956             # restore namespace lookup from the containing scope
3957 7679 100       11208 $$et{xlatNS} = $saveNS if $saveNS;
3958              
3959 7679         23553 return $count; # return the number of elements found at this level
3960             }
3961              
3962             #------------------------------------------------------------------------------
3963             # Process XMP data
3964             # Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) Pointer to tag table
3965             # Returns: 1 on success
3966             # Notes: The following flavours of XMP files are currently recognized:
3967             # - standard XMP with xpacket, x:xmpmeta and rdf:RDF elements
3968             # - XMP that is missing the xpacket and/or x:xmpmeta elements
3969             # - mutant Microsoft XMP with xmp:xmpmeta element
3970             # - XML files beginning with "
3971             # - SVG files that begin with "
3972             # - XMP and XML files beginning with a UTF-8 byte order mark
3973             # - UTF-8, UTF-16 and UTF-32 encoded XMP
3974             # - erroneously double-UTF8 encoded XMP
3975             # - otherwise valid files with leading XML comment
3976             sub ProcessXMP($$;$)
3977             {
3978 299     299 0 832 my ($et, $dirInfo, $tagTablePtr) = @_;
3979 299         699 my $dataPt = $$dirInfo{DataPt};
3980 299         917 my ($dirStart, $dirLen, $dataLen, $double);
3981 299         0 my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG);
3982 299         505 my $rtnVal = 0;
3983 299         470 my $bom = 0;
3984 299         978 my $path = $et->MetadataPath();
3985              
3986             # namespaces and prefixes currently in effect while parsing the file,
3987             # and lookup to translate brain-dead-Microsoft-Photo-software prefixes
3988 299         900 $$et{curURI} = { };
3989 299         814 $$et{curNS} = { };
3990 299         762 $$et{xlatNS} = { };
3991 299         710 $$et{definedNS} = { };
3992 299         604 delete $$et{XmpAbout};
3993 299         537 delete $$et{XmpValidate}; # don't validate by default
3994 299         509 delete $$et{XmpValidateLangAlt};
3995              
3996             # ignore non-standard XMP while in strict MWG compatibility mode
3997 299 100 66     2241 if (($Image::ExifTool::MWG::strict or $$et{OPTIONS}{Validate}) and
      66        
      100        
      100        
      100        
3998             not ($$et{XMP_CAPTURE} or $$et{DOC_NUM}) and
3999             (($$dirInfo{DirName} || '') eq 'XMP' or $$et{FILE_TYPE} eq 'XMP'))
4000             {
4001 7 50       26 $$et{XmpValidate} = { } if $$et{OPTIONS}{Validate};
4002 7   66     61 my $nonStd = ($stdPath{$$et{FILE_TYPE}} and $path ne $stdPath{$$et{FILE_TYPE}});
4003 7 0 33     27 if ($nonStd and $Image::ExifTool::MWG::strict) {
4004 0         0 $et->Warn("Ignored non-standard XMP at $path");
4005 0         0 return 1;
4006             }
4007 7 50       32 if ($nonStd) {
    50          
4008 0         0 $et->Warn("Non-standard XMP at $path", 1);
4009             } elsif (not $$dirInfo{IsExtended}) {
4010 7 50       29 $et->Warn("Duplicate XMP at $path") if $$et{DIR_COUNT}{XMP};
4011 7   50     88 $$et{DIR_COUNT}{XMP} = ($$et{DIR_COUNT}{XMP} || 0) + 1; # count standard XMP
4012             }
4013             }
4014 299 100       833 if ($dataPt) {
4015 216   100     786 $dirStart = $$dirInfo{DirStart} || 0;
4016 216   66     747 $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
4017 216   66     662 $dataLen = $$dirInfo{DataLen} || length($$dataPt);
4018             # check leading BOM (may indicate double-encoded UTF)
4019 216         739 pos($$dataPt) = $dirStart;
4020 216 50       1621 $double = $1 if $$dataPt =~ /\G((\0\0)?\xfe\xff|\xff\xfe(\0\0)?|\xef\xbb\xbf)\0*<\0*\?\0*x\0*p\0*a\0*c\0*k\0*e\0*t/g;
4021             } else {
4022 83         196 my ($type, $mime, $buf2, $buf3);
4023             # read information from XMP file
4024 83 50       260 my $raf = $$dirInfo{RAF} or return 0;
4025 83 100       271 $raf->Read($buff, 256) or return 0;
4026 67         294 ($buf2 = $buff) =~ tr/\0//d; # cheap conversion to UTF-8
4027             # remove leading comments if they exist (eg. ImageIngester)
4028 67         343 while ($buf2 =~ /^\s*\s+//s) {
4031             # continue with parsing if we have more than 128 bytes remaining
4032 0 0       0 next if length $buf2 > 128;
4033             } else {
4034             # don't read more than 10k when looking for the end of comment
4035 0 0       0 return 0 if length($buf2) > 10000;
4036             }
4037 0 0       0 $raf->Read($buf3, 256) or last; # read more data if available
4038 0         0 $buff .= $buf3;
4039 0         0 $buf3 =~ tr/\0//d;
4040 0         0 $buf2 .= $buf3;
4041             }
4042             # check to see if this is XMP format
4043             # (CS2 writes .XMP files without the "xpacket begin")
4044 67 100       412 if ($buf2 =~ /^\s*(<\?xpacket begin=|
4045 53         117 $hasXMP = 1;
4046             } else {
4047             # also recognize XML files and .XMP files with BOM and without x:xmpmeta
4048 14 50       138 if ($buf2 =~ /^(\xfe\xff)(<\?xml|
    50          
    100          
    50          
4049 0         0 $fmt = 'n'; # UTF-16 or 32 MM with BOM
4050             } elsif ($buf2 =~ /^(\xff\xfe)(<\?xml|
4051 0         0 $fmt = 'v'; # UTF-16 or 32 II with BOM
4052             } elsif ($buf2 =~ /^(\xef\xbb\xbf)?(<\?xml|
4053 7         18 $fmt = 0; # UTF-8 with BOM or unknown encoding without BOM
4054             } elsif ($buf2 =~ /^(\xfe\xff|\xff\xfe|\xef\xbb\xbf)(<\?xpacket begin=)/g) {
4055 0         0 $double = $1; # double-encoded UTF
4056             } else {
4057 7         29 return 0; # not recognized XMP or XML
4058             }
4059 7 50       30 $bom = 1 if $1;
4060 7 50       26 if ($2 eq '
    0          
    0          
4061 7 100 33     79 if (defined $fmt and not $fmt and $buf2 =~ /^[^\n\r]*[\n\r]+<\?aid /s) {
    50 66        
4062 1         4 undef $$et{XmpValidate}; # don't validate INX
4063 1 50       4 if ($$et{XMP_CAPTURE}) {
4064 0         0 $et->Error("ExifTool does not yet support writing of INX files");
4065 0         0 return 0;
4066             }
4067 1         3 $type = 'INX';
4068             } elsif ($buf2 =~ /
4069 0         0 $hasXMP = 1;
4070             } else {
4071 6         17 undef $$et{XmpValidate}; # don't validate XML
4072             # identify SVG images and PLIST files by DOCTYPE if available
4073 6 100       46 if ($buf2 =~ /
    100          
    50          
    0          
4074 2 50       7 if ($1 eq 'svg') {
    50          
    0          
    0          
4075 0         0 $isSVG = 1;
4076             } elsif ($1 eq 'plist') {
4077 2         4 $type = 'PLIST';
4078             } elsif ($1 eq 'REDXIF') {
4079 0         0 $type = 'RMD';
4080 0         0 $mime = 'application/xml';
4081             } elsif ($1 ne 'fcpxml') { # Final Cut Pro XML
4082 0         0 return 0;
4083             }
4084             } elsif ($buf2 =~ /]/) {
4085 1         3 $isSVG = 1;
4086             } elsif ($buf2 =~ /
4087 3         8 $isRDF = 1;
4088             } elsif ($buf2 =~ /]/) {
4089 0         0 $type = 'PLIST';
4090             }
4091             }
4092 7         14 $isXML = 1;
4093             } elsif ($2 eq '
4094 0         0 $isRDF = 1; # recognize XMP without x:xmpmeta element
4095             } elsif ($2 eq '
4096 0         0 $isSVG = $isXML = 1;
4097             }
4098 7 50 66     33 if ($isSVG and $$et{XMP_CAPTURE}) {
4099 0         0 $et->Error("ExifTool does not yet support writing of SVG images");
4100 0         0 return 0;
4101             }
4102 7 50       46 if ($buff =~ /^\0\0/) {
    50          
    50          
4103 0         0 $fmt = 'N'; # UTF-32 MM with or without BOM
4104             } elsif ($buff =~ /^..\0\0/s) {
4105 0         0 $fmt = 'V'; # UTF-32 II with or without BOM
4106             } elsif (not $fmt) {
4107 7 50       30 if ($buff =~ /^\0/) {
    50          
4108 0         0 $fmt = 'n'; # UTF-16 MM without BOM
4109             } elsif ($buff =~ /^.\0/s) {
4110 0         0 $fmt = 'v'; # UTF-16 II without BOM
4111             }
4112             }
4113             }
4114 60         110 my $size;
4115 60 100       142 if ($type) {
4116 3 100       10 if ($type eq 'PLIST') {
4117 2         4 my $ext = $$et{FILE_EXT};
4118 2 50 33     7 $type = $ext if $ext and $ext eq 'MODD';
4119 2         6 $tagTablePtr = GetTagTable('Image::ExifTool::PLIST::Main');
4120 2         5 $$dirInfo{XMPParseOpts}{FoundProc} = \&Image::ExifTool::PLIST::FoundTag;
4121             }
4122             } else {
4123 57 100 66     307 if ($isSVG) {
    50 66        
4124 1         3 $type = 'SVG';
4125             } elsif ($isXML and not $hasXMP and not $isRDF) {
4126 0         0 $type = 'XML';
4127 0         0 my $ext = $$et{FILE_EXT};
4128 0 0 0     0 $type = $ext if $ext and $ext eq 'COS'; # recognize COS by extension
4129             }
4130             }
4131 60         263 $et->SetFileType($type, $mime);
4132              
4133 60         220 my $fast = $et->Options('FastScan');
4134 60 50 33     234 return 1 if $fast and $fast == 3;
4135              
4136 60 100 100     228 if ($type and $type eq 'INX') {
4137             # brute force search for first XMP packet in INX file
4138             # start: '
4139             # end: ']]>' (22 bytes)
4140 1 50       5 $raf->Seek(0, 0) or return 0;
4141 1 50       5 $raf->Read($buff, 65536) or return 1;
4142 1         4 for (;;) {
4143 1 50       7 last if $buff =~ /
4144 0 0       0 $raf->Read($buf2, 65536) or return 1;
4145 0         0 $buff = substr($buff, -24) . $buf2;
4146             }
4147 1         6 $buff = substr($buff, pos($buff) - 15); # (discard '
4148 1         3 for (;;) {
4149 1 50       9 last if $buff =~ /<\?xpacket end="[rw]"\?>\]\]>/g;
4150 0         0 my $n = length $buff;
4151 0 0       0 $raf->Read($buf2, 65536) or $et->Warn('Missing xpacket end'), return 1;
4152 0         0 $buff .= $buf2;
4153 0         0 pos($buff) = $n - 22; # don't miss end pattern if it was split
4154             }
4155 1         2 $size = pos($buff) - 3; # (discard ']]>' and after)
4156 1         3 $buff = substr($buff, 0, $size);
4157             } else {
4158             # read the entire file
4159 59 50       169 $raf->Seek(0, 2) or return 0;
4160 59 50       236 $size = $raf->Tell() or return 0;
4161 59 50       157 $raf->Seek(0, 0) or return 0;
4162 59 50       215 $raf->Read($buff, $size) == $size or return 0;
4163             }
4164 60         177 $dataPt = \$buff;
4165 60         102 $dirStart = 0;
4166 60         483 $dirLen = $dataLen = $size;
4167             }
4168              
4169             # decode the first layer of double-encoded UTF text (if necessary)
4170 276 50       749 if ($double) {
4171 0         0 my ($buf2, $fmt);
4172 0         0 $buff = substr($$dataPt, $dirStart + length $double); # remove leading BOM
4173 0         0 Image::ExifTool::SetWarning(undef); # clear old warning
4174 0         0 local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning;
4175             # assume that character data has been re-encoded in UTF, so re-pack
4176             # as characters and look for warnings indicating a false assumption
4177 0 0       0 if ($double eq "\xef\xbb\xbf") {
4178 0         0 require Image::ExifTool::Charset;
4179 0         0 my $uni = Image::ExifTool::Charset::Decompose(undef,$buff,'UTF8');
4180 0         0 $buf2 = pack('C*', @$uni);
4181             } else {
4182 0 0       0 if (length($double) == 2) {
4183 0 0       0 $fmt = ($double eq "\xfe\xff") ? 'n' : 'v';
4184             } else {
4185 0 0       0 $fmt = ($double eq "\0\0\xfe\xff") ? 'N' : 'V';
4186             }
4187 0         0 $buf2 = pack('C*', unpack("$fmt*",$buff));
4188             }
4189 0 0       0 if (Image::ExifTool::GetWarning()) {
4190 0         0 $et->Warn('Superfluous BOM at start of XMP');
4191 0         0 $dataPt = \$buff; # use XMP with the BOM removed
4192             } else {
4193 0         0 $et->Warn('XMP is double UTF-encoded');
4194 0         0 $dataPt = \$buf2; # use the decoded XMP
4195             }
4196 0         0 $dirStart = 0;
4197 0         0 $dirLen = $dataLen = length $$dataPt;
4198             }
4199              
4200             # extract XMP/XML as a block if specified
4201 276 100       886 my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : 'XMP';
4202 276         912 my $blockExtract = $et->Options('BlockExtract');
4203 276 100 66     3010 if (($$et{REQ_TAG_LOOKUP}{lc $blockName} or ($$et{TAGS_FROM_FILE} and
      100        
      100        
4204             not $$et{EXCL_TAG_LOOKUP}{lc $blockName}) or $blockExtract) and
4205             (($$et{FileType} eq 'XMP' and $blockName eq 'XMP') or
4206             ($$dirInfo{DirName} and $$dirInfo{DirName} eq $blockName)))
4207             {
4208 40   100     298 $et->FoundTag($$dirInfo{BlockInfo} || 'XMP', substr($$dataPt, $dirStart, $dirLen));
4209 40 50 33     236 return 1 if $blockExtract and $blockExtract > 1;
4210             }
4211              
4212 276 100       895 $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
4213 276 100 66     887 if ($et->Options('Verbose') and not $$et{XMP_CAPTURE}) {
4214 1 50       5 my $dirType = $isSVG ? 'SVG' : $$tagTablePtr{GROUPS}{1};
4215 1         4 $et->VerboseDir($dirType, 0, $dirLen);
4216             }
4217             #
4218             # convert UTF-16 or UTF-32 encoded XMP to UTF-8 if necessary
4219             #
4220 276         631 my $begin = '
4221 276         524 my $dirEnd = $dirStart + $dirLen;
4222 276         725 pos($$dataPt) = $dirStart;
4223 276         628 delete $$et{XMP_IS_XML};
4224 276         475 delete $$et{XMP_IS_SVG};
4225 276 100 66     3402 if ($isXML or $isRDF) {
    100 66        
    100 66        
4226 7         20 $$et{XMP_IS_XML} = $isXML;
4227 7         17 $$et{XMP_IS_SVG} = $isSVG;
4228 7         19 $$et{XMP_NO_XPACKET} = 1 + $bom;
4229             } elsif ($$dataPt =~ /\G\Q$begin\E/gc) {
4230 202         499 delete $$et{XMP_NO_XPACKET};
4231             } elsif ($$dataPt =~ /
4232             pos($$dataPt) > $dirStart and pos($$dataPt) < $dirEnd)
4233             {
4234 1         4 $$et{XMP_NO_XPACKET} = 1 + $bom;
4235             } else {
4236 66         159 delete $$et{XMP_NO_XPACKET};
4237             # check for UTF-16 encoding (insert one \0 between characters)
4238 66         526 $begin = join "\0", split //, $begin;
4239             # must reset pos because it was killed by previous unsuccessful //g match
4240 66         222 pos($$dataPt) = $dirStart;
4241 66 100       713 if ($$dataPt =~ /\G(\0)?\Q$begin\E\0./sg) {
4242             # validate byte ordering by checking for U+FEFF character
4243 34 50       196 if ($1) {
4244             # should be big-endian since we had a leading \0
4245 34 50       174 $fmt = 'n' if $$dataPt =~ /\G\xfe\xff/g;
4246             } else {
4247 0 0       0 $fmt = 'v' if $$dataPt =~ /\G\0\xff\xfe/g;
4248             }
4249             } else {
4250             # check for UTF-32 encoding (with three \0's between characters)
4251 32         208 $begin =~ s/\0/\0\0\0/g;
4252 32         81 pos($$dataPt) = $dirStart;
4253 32 50       427 if ($$dataPt !~ /\G(\0\0\0)?\Q$begin\E\0\0\0./sg) {
    0          
4254 32         78 $fmt = 0; # set format to zero as indication we didn't find encoded XMP
4255             } elsif ($1) {
4256             # should be big-endian
4257 0 0       0 $fmt = 'N' if $$dataPt =~ /\G\0\0\xfe\xff/g;
4258             } else {
4259 0 0       0 $fmt = 'V' if $$dataPt =~ /\G\0\0\0\xff\xfe\0\0/g;
4260             }
4261             }
4262 66 50       219 defined $fmt or $et->Warn('XMP character encoding error');
4263             }
4264             # warn if standard XMP is missing xpacket wrapper
4265 276 0 66     853 if ($$et{XMP_NO_XPACKET} and $$et{OPTIONS}{Validate} and
      33        
      33        
      0        
      0        
4266             $stdPath{$$et{FILE_TYPE}} and $path eq $stdPath{$$et{FILE_TYPE}} and
4267             not $$dirInfo{IsExtended} and not $$et{DOC_NUM})
4268             {
4269 0         0 $et->Warn('XMP is missing xpacket wrapper', 1);
4270             }
4271 276 100       806 if ($fmt) {
4272             # trim if necessary to avoid converting non-UTF data
4273 34 100 66     150 if ($dirStart or $dirEnd != length($$dataPt)) {
4274 33         264 $buff = substr($$dataPt, $dirStart, $dirLen);
4275 33         92 $dataPt = \$buff;
4276             }
4277             # convert into UTF-8
4278 34 50       129 if ($] >= 5.006001) {
4279 34         6486 $buff = pack('C0U*', unpack("$fmt*",$$dataPt));
4280             } else {
4281 0         0 $buff = Image::ExifTool::PackUTF8(unpack("$fmt*",$$dataPt));
4282             }
4283 34         1516 $dataPt = \$buff;
4284 34         69 $dirStart = 0;
4285 34         73 $dirLen = length $$dataPt;
4286 34         68 $dirEnd = $dirStart + $dirLen;
4287             }
4288             # avoid scanning for XMP later in case ScanForXMP is set
4289 276 100       1358 $$et{FoundXMP} = 1 if $tagTablePtr eq \%Image::ExifTool::XMP::Main;
4290              
4291             # set XMP parsing options
4292 276         788 $$et{XMPParseOpts} = $$dirInfo{XMPParseOpts};
4293              
4294             # ignore any specified properties (XML hack)
4295 276 50       851 if ($$dirInfo{IgnoreProp}) {
4296 0         0 %ignoreProp = %{$$dirInfo{IgnoreProp}};
  0         0  
4297             } else {
4298 276         600 undef %ignoreProp;
4299             }
4300              
4301             # need to preserve list indices to be able to handle multi-dimensional lists
4302 276         454 my $keepFlat;
4303 276 100       898 if ($$et{OPTIONS}{Struct}) {
4304 36 100       145 if ($$et{OPTIONS}{Struct} eq '2') {
4305 21         42 $keepFlat = 1; # preserve flattened tags
4306             # setting NO_LIST to 0 combines list items in a TAG_EXTRA "NoList" element
4307             # to allow them to be re-listed later if necessary. A "NoListDel" element
4308             # is also created for tags that wouldn't have existed.
4309 21         73 $$et{NO_LIST} = 0;
4310             } else {
4311 15         57 $$et{NO_LIST} = 1;
4312             }
4313             }
4314              
4315             # don't generate structures if this isn't real XMP
4316 276 100 66     1428 $$et{NO_STRUCT} = 1 if $$dirInfo{BlockInfo} or $$dirInfo{NoStruct};
4317              
4318             # parse the XMP
4319 276 50 0     1021 if (ParseXMPElement($et, $tagTablePtr, $dataPt, $dirStart, $dirEnd)) {
    0          
4320 276         562 $rtnVal = 1;
4321             } elsif ($$dirInfo{DirName} and $$dirInfo{DirName} eq 'XMP') {
4322             # if DirName was 'XMP' we expect well-formed XMP, so set Warning since it wasn't
4323             # (but allow empty XMP as written by some PhaseOne cameras)
4324 0         0 my $xmp = substr($$dataPt, $dirStart, $dirLen);
4325 0 0       0 if ($xmp =~ /^ *\0*$/) {
4326 0         0 $et->Warn('Invalid XMP');
4327             } else {
4328 0         0 $et->Warn('Empty XMP',1);
4329 0         0 $rtnVal = 1;
4330             }
4331             }
4332 276         602 delete $$et{NO_STRUCT};
4333              
4334             # return DataPt if successful in case we want it for writing
4335 276 100 66     1495 $$dirInfo{DataPt} = $dataPt if $rtnVal and $$dirInfo{RAF};
4336              
4337             # restore structures if necessary
4338 276 100       874 if ($$et{IsStruct}) {
4339 28         3401 require 'Image/ExifTool/XMPStruct.pl';
4340 28         169 RestoreStruct($et, $keepFlat);
4341 28         65 delete $$et{IsStruct};
4342             }
4343             # reset NO_LIST flag (must do this _after_ RestoreStruct() above)
4344 276         529 delete $$et{NO_LIST};
4345 276         575 delete $$et{XMPParseOpts};
4346 276         547 delete $$et{curURI};
4347 276         590 delete $$et{curNS};
4348 276         617 delete $$et{xlatNS};
4349 276         682 delete $$et{definedNS};
4350              
4351 276         1205 return $rtnVal;
4352             }
4353              
4354              
4355             1; #end
4356              
4357             __END__