File Coverage

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