File Coverage

blib/lib/Image/ExifTool/XMP.pm
Criterion Covered Total %
statement 720 979 73.5
branch 495 762 64.9
condition 259 467 55.4
subroutine 24 28 85.7
pod 0 20 0.0
total 1498 2256 66.4


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