File Coverage

blib/lib/Image/ExifTool/XMP.pm
Criterion Covered Total %
statement 679 941 72.1
branch 466 724 64.3
condition 241 452 53.3
subroutine 24 28 85.7
pod 0 20 0.0
total 1410 2165 65.1


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