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   5277 use strict;
  65         157  
  65         3917  
46 65         10581 use vars qw($VERSION $AUTOLOAD @ISA @EXPORT_OK %stdXlatNS %nsURI %latConv %longConv
47 65     65   372 %dateTimeInfo %xmpTableDefaults %specialStruct %sDimensions %sArea %sColorant);
  65         157  
48 65     65   461 use Image::ExifTool qw(:Utils);
  65         360  
  65         11624  
49 65     65   12269 use Image::ExifTool::Exif;
  65         332  
  65         3860  
50 65     65   40185 use Image::ExifTool::GPS;
  65         245  
  65         801099  
51             require Exporter;
52              
53             $VERSION = '3.77';
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             },
1381             },
1382             },
1383             },
1384             );
1385              
1386             # Photoshop Camera Raw namespace properties (crs) - (ref 8,PH)
1387             %Image::ExifTool::XMP::crs = (
1388             %xmpTableDefaults,
1389             GROUPS => { 1 => 'XMP-crs', 2 => 'Image' },
1390             NAMESPACE => 'crs',
1391             TABLE_DESC => 'Photoshop Camera Raw namespace',
1392             NOTES => q{
1393             Photoshop Camera Raw namespace tags. It is a shame that Adobe pollutes the
1394             metadata space with these incredibly bulky image editing parameters.
1395             },
1396             AlreadyApplied => { Writable => 'boolean' }, #PH (written by LightRoom beta 4.1)
1397             AutoBrightness => { Writable => 'boolean' },
1398             AutoContrast => { Writable => 'boolean' },
1399             AutoExposure => { Writable => 'boolean' },
1400             AutoShadows => { Writable => 'boolean' },
1401             BlueHue => { Writable => 'integer' },
1402             BlueSaturation => { Writable => 'integer' },
1403             Brightness => { Writable => 'integer' },
1404             CameraProfile => { },
1405             ChromaticAberrationB=> { Writable => 'integer' },
1406             ChromaticAberrationR=> { Writable => 'integer' },
1407             ColorNoiseReduction => { Writable => 'integer' },
1408             Contrast => { Writable => 'integer', Avoid => 1 },
1409             Converter => { }, #PH guess (found in EXIF)
1410             CropTop => { Writable => 'real' },
1411             CropLeft => { Writable => 'real' },
1412             CropBottom => { Writable => 'real' },
1413             CropRight => { Writable => 'real' },
1414             CropAngle => { Writable => 'real' },
1415             CropWidth => { Writable => 'real' },
1416             CropHeight => { Writable => 'real' },
1417             CropUnits => {
1418             Writable => 'integer',
1419             PrintConv => {
1420             0 => 'pixels',
1421             1 => 'inches',
1422             2 => 'cm',
1423             },
1424             },
1425             Exposure => { Writable => 'real' },
1426             GreenHue => { Writable => 'integer' },
1427             GreenSaturation => { Writable => 'integer' },
1428             HasCrop => { Writable => 'boolean' },
1429             HasSettings => { Writable => 'boolean' },
1430             LuminanceSmoothing => { Writable => 'integer' },
1431             MoireFilter => { PrintConv => { Off=>'Off', On=>'On' } },
1432             RawFileName => { },
1433             RedHue => { Writable => 'integer' },
1434             RedSaturation => { Writable => 'integer' },
1435             Saturation => { Writable => 'integer', Avoid => 1 },
1436             Shadows => { Writable => 'integer' },
1437             ShadowTint => { Writable => 'integer' },
1438             Sharpness => { Writable => 'integer', Avoid => 1 },
1439             Smoothness => { Writable => 'integer' },
1440             Temperature => { Writable => 'integer', Name => 'ColorTemperature' },
1441             Tint => { Writable => 'integer' },
1442             ToneCurve => { List => 'Seq' },
1443             ToneCurveName => {
1444             PrintConv => {
1445             Linear => 'Linear',
1446             'Medium Contrast' => 'Medium Contrast',
1447             'Strong Contrast' => 'Strong Contrast',
1448             Custom => 'Custom',
1449             },
1450             },
1451             Version => { },
1452             VignetteAmount => { Writable => 'integer' },
1453             VignetteMidpoint=> { Writable => 'integer' },
1454             WhiteBalance => {
1455             Avoid => 1,
1456             PrintConv => {
1457             'As Shot' => 'As Shot',
1458             Auto => 'Auto',
1459             Daylight => 'Daylight',
1460             Cloudy => 'Cloudy',
1461             Shade => 'Shade',
1462             Tungsten => 'Tungsten',
1463             Fluorescent => 'Fluorescent',
1464             Flash => 'Flash',
1465             Custom => 'Custom',
1466             },
1467             },
1468             # new tags observed in Adobe Lightroom output - PH
1469             CameraProfileDigest => { },
1470             Clarity => { Writable => 'integer' },
1471             ConvertToGrayscale => { Writable => 'boolean' },
1472             Defringe => { Writable => 'integer' },
1473             FillLight => { Writable => 'integer' },
1474             HighlightRecovery => { Writable => 'integer' },
1475             HueAdjustmentAqua => { Writable => 'integer' },
1476             HueAdjustmentBlue => { Writable => 'integer' },
1477             HueAdjustmentGreen => { Writable => 'integer' },
1478             HueAdjustmentMagenta => { Writable => 'integer' },
1479             HueAdjustmentOrange => { Writable => 'integer' },
1480             HueAdjustmentPurple => { Writable => 'integer' },
1481             HueAdjustmentRed => { Writable => 'integer' },
1482             HueAdjustmentYellow => { Writable => 'integer' },
1483             IncrementalTemperature => { Writable => 'integer' },
1484             IncrementalTint => { Writable => 'integer' },
1485             LuminanceAdjustmentAqua => { Writable => 'integer' },
1486             LuminanceAdjustmentBlue => { Writable => 'integer' },
1487             LuminanceAdjustmentGreen => { Writable => 'integer' },
1488             LuminanceAdjustmentMagenta => { Writable => 'integer' },
1489             LuminanceAdjustmentOrange => { Writable => 'integer' },
1490             LuminanceAdjustmentPurple => { Writable => 'integer' },
1491             LuminanceAdjustmentRed => { Writable => 'integer' },
1492             LuminanceAdjustmentYellow => { Writable => 'integer' },
1493             ParametricDarks => { Writable => 'integer' },
1494             ParametricHighlights => { Writable => 'integer' },
1495             ParametricHighlightSplit => { Writable => 'integer' },
1496             ParametricLights => { Writable => 'integer' },
1497             ParametricMidtoneSplit => { Writable => 'integer' },
1498             ParametricShadows => { Writable => 'integer' },
1499             ParametricShadowSplit => { Writable => 'integer' },
1500             SaturationAdjustmentAqua => { Writable => 'integer' },
1501             SaturationAdjustmentBlue => { Writable => 'integer' },
1502             SaturationAdjustmentGreen => { Writable => 'integer' },
1503             SaturationAdjustmentMagenta => { Writable => 'integer' },
1504             SaturationAdjustmentOrange => { Writable => 'integer' },
1505             SaturationAdjustmentPurple => { Writable => 'integer' },
1506             SaturationAdjustmentRed => { Writable => 'integer' },
1507             SaturationAdjustmentYellow => { Writable => 'integer' },
1508             SharpenDetail => { Writable => 'integer' },
1509             SharpenEdgeMasking => { Writable => 'integer' },
1510             SharpenRadius => { Writable => 'real' },
1511             SplitToningBalance => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
1512             SplitToningHighlightHue => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
1513             SplitToningHighlightSaturation => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
1514             SplitToningShadowHue => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
1515             SplitToningShadowSaturation => { Writable => 'integer', Notes => 'also used for newer ColorGrade settings' },
1516             Vibrance => { Writable => 'integer' },
1517             # new tags written by LR 1.4 (not sure in what version they first appeared)
1518             GrayMixerRed => { Writable => 'integer' },
1519             GrayMixerOrange => { Writable => 'integer' },
1520             GrayMixerYellow => { Writable => 'integer' },
1521             GrayMixerGreen => { Writable => 'integer' },
1522             GrayMixerAqua => { Writable => 'integer' },
1523             GrayMixerBlue => { Writable => 'integer' },
1524             GrayMixerPurple => { Writable => 'integer' },
1525             GrayMixerMagenta => { Writable => 'integer' },
1526             RetouchInfo => { List => 'Seq' },
1527             RedEyeInfo => { List => 'Seq' },
1528             # new tags written by LR 2.0 (ref PH)
1529             CropUnit => { # was the XMP documentation wrong with "CropUnits"??
1530             Writable => 'integer',
1531             PrintConv => {
1532             0 => 'pixels',
1533             1 => 'inches',
1534             2 => 'cm',
1535             # have seen a value of 3 here! - PH
1536             },
1537             },
1538             PostCropVignetteAmount => { Writable => 'integer' },
1539             PostCropVignetteMidpoint => { Writable => 'integer' },
1540             PostCropVignetteFeather => { Writable => 'integer' },
1541             PostCropVignetteRoundness => { Writable => 'integer' },
1542             PostCropVignetteStyle => {
1543             Writable => 'integer',
1544             PrintConv => { #forum14011
1545             1 => 'Highlight Priority',
1546             2 => 'Color Priority',
1547             3 => 'Paint Overlay',
1548             },
1549             },
1550             # disable List behaviour of flattened Gradient/PaintBasedCorrections
1551             # because these are nested in lists and the flattened tags can't
1552             # do justice to this complex structure
1553             GradientBasedCorrections => {
1554             FlatName => 'GradientBasedCorr',
1555             Struct => \%sCorrection,
1556             List => 'Seq',
1557             },
1558             GradientBasedCorrectionsCorrectionMasks => {
1559             Name => 'GradientBasedCorrMasks',
1560             FlatName => 'GradientBasedCorrMask',
1561             Flat => 1
1562             },
1563             GradientBasedCorrectionsCorrectionMasksDabs => {
1564             Name => 'GradientBasedCorrMaskDabs',
1565             Flat => 1, List => 0,
1566             },
1567             PaintBasedCorrections => {
1568             FlatName => 'PaintCorrection',
1569             Struct => \%sCorrection,
1570             List => 'Seq',
1571             },
1572             PaintBasedCorrectionsCorrectionMasks => {
1573             Name => 'PaintBasedCorrectionMasks',
1574             FlatName => 'PaintCorrectionMask',
1575             Flat => 1,
1576             },
1577             PaintBasedCorrectionsCorrectionMasksDabs => {
1578             Name => 'PaintCorrectionMaskDabs',
1579             Flat => 1, List => 0,
1580             },
1581             # new tags written by LR 3 (thanks Wolfgang Guelcker)
1582             ProcessVersion => { },
1583             LensProfileEnable => { Writable => 'integer' },
1584             LensProfileSetup => { },
1585             LensProfileName => { },
1586             LensProfileFilename => { },
1587             LensProfileDigest => { },
1588             LensProfileDistortionScale => { Writable => 'integer' },
1589             LensProfileChromaticAberrationScale => { Writable => 'integer' },
1590             LensProfileVignettingScale => { Writable => 'integer' },
1591             LensManualDistortionAmount => { Writable => 'integer' },
1592             PerspectiveVertical => { Writable => 'integer' },
1593             PerspectiveHorizontal => { Writable => 'integer' },
1594             PerspectiveRotate => { Writable => 'real' },
1595             PerspectiveScale => { Writable => 'integer' },
1596             CropConstrainToWarp => { Writable => 'integer' },
1597             LuminanceNoiseReductionDetail => { Writable => 'integer' },
1598             LuminanceNoiseReductionContrast => { Writable => 'integer' },
1599             ColorNoiseReductionDetail => { Writable => 'integer' },
1600             GrainAmount => { Writable => 'integer' },
1601             GrainSize => { Writable => 'integer' },
1602             GrainFrequency => { Writable => 'integer' },
1603             # new tags written by LR4
1604             AutoLateralCA => { Writable => 'integer' },
1605             Exposure2012 => { Writable => 'real' },
1606             Contrast2012 => { Writable => 'integer' },
1607             Highlights2012 => { Writable => 'integer' },
1608             Highlight2012 => { Writable => 'integer' }, # (written by Nikon software)
1609             Shadows2012 => { Writable => 'integer' },
1610             Whites2012 => { Writable => 'integer' },
1611             Blacks2012 => { Writable => 'integer' },
1612             Clarity2012 => { Writable => 'integer' },
1613             PostCropVignetteHighlightContrast => { Writable => 'integer' },
1614             ToneCurveName2012 => { },
1615             ToneCurveRed => { List => 'Seq' },
1616             ToneCurveGreen => { List => 'Seq' },
1617             ToneCurveBlue => { List => 'Seq' },
1618             ToneCurvePV2012 => { List => 'Seq' },
1619             ToneCurvePV2012Red => { List => 'Seq' },
1620             ToneCurvePV2012Green => { List => 'Seq' },
1621             ToneCurvePV2012Blue => { List => 'Seq' },
1622             DefringePurpleAmount => { Writable => 'integer' },
1623             DefringePurpleHueLo => { Writable => 'integer' },
1624             DefringePurpleHueHi => { Writable => 'integer' },
1625             DefringeGreenAmount => { Writable => 'integer' },
1626             DefringeGreenHueLo => { Writable => 'integer' },
1627             DefringeGreenHueHi => { Writable => 'integer' },
1628             # new tags written by LR5
1629             AutoWhiteVersion => { Writable => 'integer' },
1630             CircularGradientBasedCorrections => {
1631             FlatName => 'CircGradBasedCorr',
1632             Struct => \%sCorrection,
1633             List => 'Seq',
1634             },
1635             CircularGradientBasedCorrectionsCorrectionMasks => {
1636             Name => 'CircGradBasedCorrMasks',
1637             FlatName => 'CircGradBasedCorrMask',
1638             Flat => 1
1639             },
1640             CircularGradientBasedCorrectionsCorrectionMasksDabs => {
1641             Name => 'CircGradBasedCorrMaskDabs',
1642             Flat => 1, List => 0,
1643             },
1644             ColorNoiseReductionSmoothness => { Writable => 'integer' },
1645             PerspectiveAspect => { Writable => 'integer' },
1646             PerspectiveUpright => {
1647             Writable => 'integer',
1648             PrintConv => { #forum14012
1649             0 => 'Off', # Disable Upright
1650             1 => 'Auto', # Apply balanced perspective corrections
1651             2 => 'Full', # Apply level, horizontal, and vertical perspective corrections
1652             3 => 'Level', # Apply only level correction
1653             4 => 'Vertical',# Apply level and vertical perspective corrections
1654             5 => 'Guided', # Draw two or more guides to customize perspective corrections
1655             },
1656             },
1657             RetouchAreas => {
1658             FlatName => 'RetouchArea',
1659             Struct => \%sRetouchArea,
1660             List => 'Seq',
1661             },
1662             RetouchAreasMasks => {
1663             Name => 'RetouchAreaMasks',
1664             FlatName => 'RetouchAreaMask',
1665             Flat => 1
1666             },
1667             RetouchAreasMasksDabs => {
1668             Name => 'RetouchAreaMaskDabs',
1669             Flat => 1, List => 0,
1670             },
1671             UprightVersion => { Writable => 'integer' },
1672             UprightCenterMode => { Writable => 'integer' },
1673             UprightCenterNormX => { Writable => 'real' },
1674             UprightCenterNormY => { Writable => 'real' },
1675             UprightFocalMode => { Writable => 'integer' },
1676             UprightFocalLength35mm => { Writable => 'real' },
1677             UprightPreview => { Writable => 'boolean' },
1678             UprightTransformCount => { Writable => 'integer' },
1679             UprightDependentDigest => { },
1680             UprightGuidedDependentDigest => { },
1681             UprightTransform_0 => { },
1682             UprightTransform_1 => { },
1683             UprightTransform_2 => { },
1684             UprightTransform_3 => { },
1685             UprightTransform_4 => { },
1686             UprightTransform_5 => { },
1687             UprightFourSegments_0 => { },
1688             UprightFourSegments_1 => { },
1689             UprightFourSegments_2 => { },
1690             UprightFourSegments_3 => { },
1691             # more stuff seen in lens profile file (unknown source)
1692             What => { }, # (with value "LensProfileDefaultSettings")
1693             LensProfileMatchKeyExifMake => { },
1694             LensProfileMatchKeyExifModel => { },
1695             LensProfileMatchKeyCameraModelName => { },
1696             LensProfileMatchKeyLensInfo => { },
1697             LensProfileMatchKeyLensID => { },
1698             LensProfileMatchKeyLensName => { },
1699             LensProfileMatchKeyIsRaw => { Writable => 'boolean' },
1700             LensProfileMatchKeySensorFormatFactor=>{ Writable => 'real' },
1701             # more stuff (ref forum6993)
1702             DefaultAutoTone => { Writable => 'boolean' },
1703             DefaultAutoGray => { Writable => 'boolean' },
1704             DefaultsSpecificToSerial => { Writable => 'boolean' },
1705             DefaultsSpecificToISO => { Writable => 'boolean' },
1706             DNGIgnoreSidecars => { Writable => 'boolean' },
1707             NegativeCachePath => { },
1708             NegativeCacheMaximumSize => { Writable => 'real' },
1709             NegativeCacheLargePreviewSize => { Writable => 'integer' },
1710             JPEGHandling => { },
1711             TIFFHandling => { },
1712             Dehaze => { Writable => 'real' },
1713             ToneMapStrength => { Writable => 'real' },
1714             # yet more
1715             PerspectiveX => { Writable => 'real' },
1716             PerspectiveY => { Writable => 'real' },
1717             UprightFourSegmentsCount => { Writable => 'integer' },
1718             AutoTone => { Writable => 'boolean' },
1719             Texture => { Writable => 'integer' },
1720             # more stuff (ref forum10721)
1721             OverrideLookVignette => { Writable => 'boolean' },
1722             Look => {
1723             Struct => {
1724             STRUCT_NAME => 'Look',
1725             NAMESPACE => 'crs',
1726             Name => { },
1727             Amount => { },
1728             Cluster => { },
1729             UUID => { },
1730             SupportsMonochrome => { },
1731             SupportsAmount => { },
1732             SupportsOutputReferred => { },
1733             Copyright => { },
1734             Group => { Writable => 'lang-alt' },
1735             Parameters => {
1736             Struct => {
1737             STRUCT_NAME => 'LookParms',
1738             NAMESPACE => 'crs',
1739             Version => { },
1740             ProcessVersion => { },
1741             Clarity2012 => { },
1742             ConvertToGrayscale => { },
1743             CameraProfile => { },
1744             LookTable => { },
1745             ToneCurvePV2012 => { List => 'Seq' },
1746             ToneCurvePV2012Red => { List => 'Seq' },
1747             ToneCurvePV2012Green => { List => 'Seq' },
1748             ToneCurvePV2012Blue => { List => 'Seq' },
1749             Highlights2012 => { },
1750             Shadows2012 => { },
1751             },
1752             },
1753             }
1754             },
1755             # more again (ref forum11258)
1756             GrainSeed => { },
1757             ClipboardOrientation => { Writable => 'integer' },
1758             ClipboardAspectRatio => { Writable => 'integer' },
1759             PresetType => { },
1760             Cluster => { },
1761             UUID => { Avoid => 1 },
1762             SupportsAmount => { Writable => 'boolean' },
1763             SupportsColor => { Writable => 'boolean' },
1764             SupportsMonochrome => { Writable => 'boolean' },
1765             SupportsHighDynamicRange=> { Writable => 'boolean' },
1766             SupportsNormalDynamicRange=> { Writable => 'boolean' },
1767             SupportsSceneReferred => { Writable => 'boolean' },
1768             SupportsOutputReferred => { Writable => 'boolean' },
1769             CameraModelRestriction => { },
1770             Copyright => { Avoid => 1 },
1771             ContactInfo => { },
1772             GrainSeed => { Writable => 'integer' },
1773             Name => { Writable => 'lang-alt', Avoid => 1 },
1774             ShortName => { Writable => 'lang-alt' },
1775             SortName => { Writable => 'lang-alt' },
1776             Group => { Writable => 'lang-alt', Avoid => 1 },
1777             Description => { Writable => 'lang-alt', Avoid => 1 },
1778             # new for DNG converter 13.0
1779             LookName => { NotFlat => 1 }, # (grr... conflicts with "Name" element of "Look" struct!)
1780             # new for Lightroom CC 2021 (ref forum11745)
1781             ColorGradeMidtoneHue => { Writable => 'integer' },
1782             ColorGradeMidtoneSat => { Writable => 'integer' },
1783             ColorGradeShadowLum => { Writable => 'integer' },
1784             ColorGradeMidtoneLum => { Writable => 'integer' },
1785             ColorGradeHighlightLum => { Writable => 'integer' },
1786             ColorGradeBlending => { Writable => 'integer' },
1787             ColorGradeGlobalHue => { Writable => 'integer' },
1788             ColorGradeGlobalSat => { Writable => 'integer' },
1789             ColorGradeGlobalLum => { Writable => 'integer' },
1790             # new for Adobe Camera Raw 13 (ref forum11745)
1791             LensProfileIsEmbedded => { Writable => 'boolean'},
1792             AutoToneDigest => { },
1793             AutoToneDigestNoSat => { },
1794             ToggleStyleDigest => { },
1795             ToggleStyleAmount => { Writable => 'integer' },
1796             # new for LightRoom 11.0
1797             CompatibleVersion => { },
1798             MaskGroupBasedCorrections => {
1799             FlatName => 'MaskGroupBasedCorr',
1800             Struct => \%sCorrection,
1801             List => 'Seq',
1802             },
1803             RangeMaskMapInfo => { Name => 'RangeMask', Struct => \%sRangeMask, FlatName => 'RangeMask' },
1804             # new for ACR 15.1 (not sure if these are integer or real, so just guess)
1805             HDREditMode => { Writable => 'integer' },
1806             SDRBrightness => { Writable => 'real' },
1807             SDRContrast => { Writable => 'real' },
1808             SDRHighlights => { Writable => 'real' },
1809             SDRShadows => { Writable => 'real' },
1810             SDRWhites => { Writable => 'real' },
1811             SDRBlend => { Writable => 'real' },
1812             # new for ACR 16 (ref forum15305)
1813             LensBlur => {
1814             Struct => {
1815             STRUCT_NAME => 'LensBlur',
1816             NAMESPACE => 'crs',
1817             # (Note: all the following 'real' values could be limited to 'integer')
1818             Active => { Writable => 'boolean' },
1819             BlurAmount => { FlatName => 'Amount', Writable => 'real' },
1820             BokehAspect => { Writable => 'real' },
1821             BokehRotation => { Writable => 'real' },
1822             BokehShape => { Writable => 'real' },
1823             BokehShapeDetail => { Writable => 'real' },
1824             CatEyeAmount => { Writable => 'real' },
1825             CatEyeScale => { Writable => 'real' },
1826             FocalRange => { }, # (eg. "-48 32 64 144")
1827             FocalRangeSource => { Writable => 'real' },
1828             HighlightsBoost => { Writable => 'real' },
1829             HighlightsThreshold => { Writable => 'real' },
1830             SampledArea => { }, # (eg. "0.500000 0.500000 0.500000 0.500000")
1831             SampledRange => { }, # (eg. "0 0")
1832             SphericalAberration => { Writable => 'real' },
1833             SubjectRange => { }, # (eg. "0 57");
1834             Version => { },
1835             },
1836             },
1837             DepthMapInfo => {
1838             Struct => {
1839             STRUCT_NAME => 'DepthMapInfo',
1840             NAMESPACE => 'crs',
1841             BaseHighlightGuideInputDigest => { },
1842             BaseHighlightGuideTable => { },
1843             BaseHighlightGuideVersion => { },
1844             BaseLayeredDepthInputDigest => { },
1845             BaseLayeredDepthTable => { },
1846             BaseLayeredDepthVersion => { },
1847             BaseRawDepthInputDigest => { },
1848             BaseRawDepthTable => { },
1849             BaseRawDepthVersion => { },
1850             DepthSource => { },
1851             },
1852             },
1853             DepthBasedCorrections => {
1854             List => 'Seq',
1855             FlatName => 'DepthBasedCorr',
1856             Struct => {
1857             STRUCT_NAME => 'DepthBasedCorr',
1858             NAMESPACE => 'crs',
1859             CorrectionActive => { Writable => 'boolean' },
1860             CorrectionAmount => { Writable => 'real' },
1861             CorrectionMasks => { FlatName => 'Mask', List => 'Seq', Struct => \%sCorrectionMask },
1862             CorrectionSyncID => { },
1863             LocalCorrectedDepth => { Writable => 'real' },
1864             LocalCurveRefineSaturation => { Writable => 'real' },
1865             What => { },
1866             },
1867             },
1868             # more new stuff
1869             PointColors => { List => 'Seq' },
1870             );
1871              
1872             # Tiff namespace properties (tiff)
1873             %Image::ExifTool::XMP::tiff = (
1874             %xmpTableDefaults,
1875             GROUPS => { 1 => 'XMP-tiff', 2 => 'Image' },
1876             NAMESPACE => 'tiff',
1877             PRIORITY => 0, # not as reliable as actual TIFF tags
1878             TABLE_DESC => 'XMP TIFF',
1879             NOTES => q{
1880             EXIF namespace for TIFF tags. See
1881             L
1882             for the specification.
1883             },
1884             ImageWidth => { Writable => 'integer' },
1885             ImageLength => { Writable => 'integer', Name => 'ImageHeight' },
1886             BitsPerSample => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
1887             Compression => {
1888             Writable => 'integer',
1889             SeparateTable => 'EXIF Compression',
1890             PrintConv => \%Image::ExifTool::Exif::compression,
1891             },
1892             PhotometricInterpretation => {
1893             Writable => 'integer',
1894             PrintConv => \%Image::ExifTool::Exif::photometricInterpretation,
1895             },
1896             Orientation => {
1897             Writable => 'integer',
1898             PrintConv => \%Image::ExifTool::Exif::orientation,
1899             },
1900             SamplesPerPixel => { Writable => 'integer' },
1901             PlanarConfiguration => {
1902             Writable => 'integer',
1903             PrintConv => {
1904             1 => 'Chunky',
1905             2 => 'Planar',
1906             },
1907             },
1908             YCbCrSubSampling => {
1909             Writable => 'integer',
1910             List => 'Seq',
1911             # join the raw values before conversion to allow PrintConv to operate on
1912             # the combined string as it does for the corresponding EXIF tag
1913             RawJoin => 1,
1914             Notes => q{
1915             while technically this is a list-type tag, for compatibility with its EXIF
1916             counterpart it is written and read as a simple string
1917             },
1918             PrintConv => \%Image::ExifTool::JPEG::yCbCrSubSampling,
1919             },
1920             YCbCrPositioning => {
1921             Writable => 'integer',
1922             PrintConv => {
1923             1 => 'Centered',
1924             2 => 'Co-sited',
1925             },
1926             },
1927             XResolution => { Writable => 'rational' },
1928             YResolution => { Writable => 'rational' },
1929             ResolutionUnit => {
1930             Writable => 'integer',
1931             Notes => 'the value 1 is not standard EXIF',
1932             PrintConv => {
1933             1 => 'None',
1934             2 => 'inches',
1935             3 => 'cm',
1936             },
1937             },
1938             TransferFunction => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
1939             WhitePoint => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1940             PrimaryChromaticities => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1941             YCbCrCoefficients => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1942             ReferenceBlackWhite => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1943             DateTime => { # (EXIF tag named ModifyDate, but this exists in XMP-xmp)
1944             Description => 'Date/Time Modified',
1945             Groups => { 2 => 'Time' },
1946             %dateTimeInfo,
1947             },
1948             ImageDescription => { Writable => 'lang-alt' },
1949             Make => {
1950             Groups => { 2 => 'Camera' },
1951             RawConv => '$$self{Make} ? $val : $$self{Make} = $val',
1952             },
1953             Model => {
1954             Groups => { 2 => 'Camera' },
1955             Description => 'Camera Model Name',
1956             RawConv => '$$self{Model} ? $val : $$self{Model} = $val',
1957             },
1958             Software => { },
1959             Artist => { Groups => { 2 => 'Author' } },
1960             Copyright => { Groups => { 2 => 'Author' }, Writable => 'lang-alt' },
1961             NativeDigest => { Avoid => 1 }, #PH
1962             );
1963              
1964             # Exif namespace properties (exif)
1965             %Image::ExifTool::XMP::exif = (
1966             %xmpTableDefaults,
1967             GROUPS => { 1 => 'XMP-exif', 2 => 'Image' },
1968             NAMESPACE => 'exif',
1969             PRIORITY => 0, # not as reliable as actual EXIF tags
1970             NOTES => q{
1971             EXIF namespace for EXIF tags. See
1972             L
1973             for the specification.
1974             },
1975             ExifVersion => { },
1976             FlashpixVersion => { },
1977             ColorSpace => {
1978             Writable => 'integer',
1979             # (some applications incorrectly write -1 as a long integer)
1980             ValueConv => '$val == 0xffffffff ? 0xffff : $val',
1981             ValueConvInv => '$val',
1982             PrintConv => {
1983             1 => 'sRGB',
1984             2 => 'Adobe RGB',
1985             0xffff => 'Uncalibrated',
1986             },
1987             },
1988             ComponentsConfiguration => {
1989             Writable => 'integer',
1990             List => 'Seq',
1991             AutoSplit => 1,
1992             PrintConvColumns => 2,
1993             PrintConv => {
1994             0 => '-',
1995             1 => 'Y',
1996             2 => 'Cb',
1997             3 => 'Cr',
1998             4 => 'R',
1999             5 => 'G',
2000             6 => 'B',
2001             },
2002             },
2003             CompressedBitsPerPixel => { Writable => 'rational' },
2004             PixelXDimension => { Name => 'ExifImageWidth', Writable => 'integer' },
2005             PixelYDimension => { Name => 'ExifImageHeight', Writable => 'integer' },
2006             MakerNote => { },
2007             UserComment => { Writable => 'lang-alt' },
2008             RelatedSoundFile => { },
2009             DateTimeOriginal => {
2010             Description => 'Date/Time Original',
2011             Groups => { 2 => 'Time' },
2012             %dateTimeInfo,
2013             },
2014             DateTimeDigitized => { # (EXIF tag named CreateDate, but this exists in XMP-xmp)
2015             Description => 'Date/Time Digitized',
2016             Groups => { 2 => 'Time' },
2017             %dateTimeInfo,
2018             },
2019             ExposureTime => {
2020             Writable => 'rational',
2021             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
2022             PrintConvInv => '$val',
2023             },
2024             FNumber => {
2025             Writable => 'rational',
2026             PrintConv => 'Image::ExifTool::Exif::PrintFNumber($val)',
2027             PrintConvInv => '$val',
2028             },
2029             ExposureProgram => {
2030             Groups => { 2 => 'Camera' },
2031             Writable => 'integer',
2032             PrintConv => {
2033             0 => 'Not Defined',
2034             1 => 'Manual',
2035             2 => 'Program AE',
2036             3 => 'Aperture-priority AE',
2037             4 => 'Shutter speed priority AE',
2038             5 => 'Creative (Slow speed)',
2039             6 => 'Action (High speed)',
2040             7 => 'Portrait',
2041             8 => 'Landscape',
2042             },
2043             },
2044             SpectralSensitivity => { Groups => { 2 => 'Camera' } },
2045             ISOSpeedRatings => {
2046             Name => 'ISO',
2047             Writable => 'integer',
2048             List => 'Seq',
2049             AutoSplit => 1,
2050             Notes => 'deprecated',
2051             },
2052             OECF => {
2053             Name => 'Opto-ElectricConvFactor',
2054             FlatName => 'OECF',
2055             Groups => { 2 => 'Camera' },
2056             Struct => \%sOECF,
2057             },
2058             ShutterSpeedValue => {
2059             Writable => 'rational',
2060             ValueConv => 'abs($val)<100 ? 1/(2**$val) : 0',
2061             PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
2062             ValueConvInv => '$val>0 ? -log($val)/log(2) : 0',
2063             PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
2064             },
2065             ApertureValue => {
2066             Writable => 'rational',
2067             ValueConv => 'sqrt(2) ** $val',
2068             PrintConv => 'sprintf("%.1f",$val)',
2069             ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
2070             PrintConvInv => '$val',
2071             },
2072             BrightnessValue => { Writable => 'rational' },
2073             ExposureBiasValue => {
2074             Name => 'ExposureCompensation',
2075             Writable => 'rational',
2076             PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)',
2077             PrintConvInv => '$val',
2078             },
2079             MaxApertureValue => {
2080             Groups => { 2 => 'Camera' },
2081             Writable => 'rational',
2082             ValueConv => 'sqrt(2) ** $val',
2083             PrintConv => 'sprintf("%.1f",$val)',
2084             ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
2085             PrintConvInv => '$val',
2086             },
2087             SubjectDistance => {
2088             Groups => { 2 => 'Camera' },
2089             Writable => 'rational',
2090             PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"',
2091             PrintConvInv => '$val=~s/\s*m$//;$val',
2092             },
2093             MeteringMode => {
2094             Groups => { 2 => 'Camera' },
2095             Writable => 'integer',
2096             PrintConv => {
2097             1 => 'Average',
2098             2 => 'Center-weighted average',
2099             3 => 'Spot',
2100             4 => 'Multi-spot',
2101             5 => 'Multi-segment',
2102             6 => 'Partial',
2103             255 => 'Other',
2104             },
2105             },
2106             LightSource => {
2107             Groups => { 2 => 'Camera' },
2108             SeparateTable => 'EXIF LightSource',
2109             PrintConv => \%Image::ExifTool::Exif::lightSource,
2110             },
2111             Flash => {
2112             Groups => { 2 => 'Camera' },
2113             Struct => {
2114             STRUCT_NAME => 'Flash',
2115             NAMESPACE => 'exif',
2116             Fired => { Writable => 'boolean', %boolConv },
2117             Return => {
2118             Writable => 'integer',
2119             PrintConv => {
2120             0 => 'No return detection',
2121             2 => 'Return not detected',
2122             3 => 'Return detected',
2123             },
2124             },
2125             Mode => {
2126             Writable => 'integer',
2127             PrintConv => {
2128             0 => 'Unknown',
2129             1 => 'On',
2130             2 => 'Off',
2131             3 => 'Auto',
2132             },
2133             },
2134             Function => { Writable => 'boolean', %boolConv },
2135             RedEyeMode => { Writable => 'boolean', %boolConv },
2136             },
2137             },
2138             FocalLength=> {
2139             Groups => { 2 => 'Camera' },
2140             Writable => 'rational',
2141             PrintConv => 'sprintf("%.1f mm",$val)',
2142             PrintConvInv => '$val=~s/\s*mm$//;$val',
2143             },
2144             SubjectArea => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
2145             FlashEnergy => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
2146             SpatialFrequencyResponse => {
2147             Groups => { 2 => 'Camera' },
2148             Struct => \%sOECF,
2149             },
2150             FocalPlaneXResolution => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
2151             FocalPlaneYResolution => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
2152             FocalPlaneResolutionUnit => {
2153             Groups => { 2 => 'Camera' },
2154             Writable => 'integer',
2155             Notes => 'values 1, 4 and 5 are not standard EXIF',
2156             PrintConv => {
2157             1 => 'None', # (not standard EXIF)
2158             2 => 'inches',
2159             3 => 'cm',
2160             4 => 'mm', # (not standard EXIF)
2161             5 => 'um', # (not standard EXIF)
2162             },
2163             },
2164             SubjectLocation => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
2165             ExposureIndex => { Writable => 'rational' },
2166             SensingMethod => {
2167             Groups => { 2 => 'Camera' },
2168             Writable => 'integer',
2169             Notes => 'values 1 and 6 are not standard EXIF',
2170             PrintConv => {
2171             1 => 'Monochrome area', # (not standard EXIF)
2172             2 => 'One-chip color area',
2173             3 => 'Two-chip color area',
2174             4 => 'Three-chip color area',
2175             5 => 'Color sequential area',
2176             6 => 'Monochrome linear', # (not standard EXIF)
2177             7 => 'Trilinear',
2178             8 => 'Color sequential linear',
2179             },
2180             },
2181             FileSource => {
2182             Writable => 'integer',
2183             PrintConv => {
2184             1 => 'Film Scanner',
2185             2 => 'Reflection Print Scanner',
2186             3 => 'Digital Camera',
2187             }
2188             },
2189             SceneType => { Writable => 'integer', PrintConv => { 1 => 'Directly photographed' } },
2190             CFAPattern => {
2191             Struct => {
2192             STRUCT_NAME => 'CFAPattern',
2193             NAMESPACE => 'exif',
2194             Columns => { Writable => 'integer' },
2195             Rows => { Writable => 'integer' },
2196             Values => { Writable => 'integer', List => 'Seq' },
2197             },
2198             },
2199             CustomRendered => {
2200             Writable => 'integer',
2201             PrintConv => {
2202             0 => 'Normal',
2203             1 => 'Custom',
2204             },
2205             },
2206             ExposureMode => {
2207             Groups => { 2 => 'Camera' },
2208             Writable => 'integer',
2209             PrintConv => {
2210             0 => 'Auto',
2211             1 => 'Manual',
2212             2 => 'Auto bracket',
2213             },
2214             },
2215             WhiteBalance => {
2216             Groups => { 2 => 'Camera' },
2217             Writable => 'integer',
2218             PrintConv => {
2219             0 => 'Auto',
2220             1 => 'Manual',
2221             },
2222             },
2223             DigitalZoomRatio => { Writable => 'rational' },
2224             FocalLengthIn35mmFilm => {
2225             Name => 'FocalLengthIn35mmFormat',
2226             Writable => 'integer',
2227             Groups => { 2 => 'Camera' },
2228             PrintConv => '"$val mm"',
2229             PrintConvInv => '$val=~s/\s*mm$//;$val',
2230             },
2231             SceneCaptureType => {
2232             Groups => { 2 => 'Camera' },
2233             Writable => 'integer',
2234             PrintConv => {
2235             0 => 'Standard',
2236             1 => 'Landscape',
2237             2 => 'Portrait',
2238             3 => 'Night',
2239             },
2240             },
2241             GainControl => {
2242             Groups => { 2 => 'Camera' },
2243             Writable => 'integer',
2244             PrintConv => {
2245             0 => 'None',
2246             1 => 'Low gain up',
2247             2 => 'High gain up',
2248             3 => 'Low gain down',
2249             4 => 'High gain down',
2250             },
2251             },
2252             Contrast => {
2253             Groups => { 2 => 'Camera' },
2254             Writable => 'integer',
2255             PrintConv => {
2256             0 => 'Normal',
2257             1 => 'Low',
2258             2 => 'High',
2259             },
2260             PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
2261             },
2262             Saturation => {
2263             Groups => { 2 => 'Camera' },
2264             Writable => 'integer',
2265             PrintConv => {
2266             0 => 'Normal',
2267             1 => 'Low',
2268             2 => 'High',
2269             },
2270             PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
2271             },
2272             Sharpness => {
2273             Groups => { 2 => 'Camera' },
2274             Writable => 'integer',
2275             PrintConv => {
2276             0 => 'Normal',
2277             1 => 'Soft',
2278             2 => 'Hard',
2279             },
2280             PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
2281             },
2282             DeviceSettingDescription => {
2283             Groups => { 2 => 'Camera' },
2284             Struct => {
2285             STRUCT_NAME => 'DeviceSettings',
2286             NAMESPACE => 'exif',
2287             Columns => { Writable => 'integer' },
2288             Rows => { Writable => 'integer' },
2289             Settings => { List => 'Seq' },
2290             },
2291             },
2292             SubjectDistanceRange => {
2293             Groups => { 2 => 'Camera' },
2294             Writable => 'integer',
2295             PrintConv => {
2296             0 => 'Unknown',
2297             1 => 'Macro',
2298             2 => 'Close',
2299             3 => 'Distant',
2300             },
2301             },
2302             ImageUniqueID => { Avoid => 1, Notes => 'moved to exifEX namespace in 2024 spec' },
2303             GPSVersionID => { Groups => { 2 => 'Location' } },
2304             GPSLatitude => { Groups => { 2 => 'Location' }, %latConv },
2305             GPSLongitude => { Groups => { 2 => 'Location' }, %longConv },
2306             GPSAltitudeRef => {
2307             Groups => { 2 => 'Location' },
2308             Writable => 'integer',
2309             PrintConv => {
2310             OTHER => sub {
2311             my ($val, $inv) = @_;
2312             return undef unless $inv and $val =~ /^([-+0-9])/;
2313             return($1 eq '-' ? 1 : 0);
2314             },
2315             0 => 'Above Sea Level',
2316             1 => 'Below Sea Level',
2317             },
2318             },
2319             GPSAltitude => {
2320             Groups => { 2 => 'Location' },
2321             Writable => 'rational',
2322             # extricate unsigned decimal number from string
2323             ValueConvInv => '$val=~/((?=\d|\.\d)\d*(?:\.\d*)?)/ ? $1 : undef',
2324             PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"',
2325             PrintConvInv => '$val=~s/\s*m$//;$val',
2326             },
2327             GPSTimeStamp => {
2328             Name => 'GPSDateTime',
2329             Description => 'GPS Date/Time',
2330             Groups => { 2 => 'Time' },
2331             Notes => q{
2332             a date/time tag called GPSTimeStamp by the XMP specification. This tag is
2333             renamed here to prevent direct copy from EXIF:GPSTimeStamp which is a
2334             time-only tag. Instead, the value of this tag should be taken from
2335             Composite:GPSDateTime when copying from EXIF
2336             },
2337             %dateTimeInfo,
2338             },
2339             GPSSatellites => { Groups => { 2 => 'Location' } },
2340             GPSStatus => {
2341             Groups => { 2 => 'Location' },
2342             PrintConv => {
2343             A => 'Measurement Active',
2344             V => 'Measurement Void',
2345             },
2346             },
2347             GPSMeasureMode => {
2348             Groups => { 2 => 'Location' },
2349             Writable => 'integer',
2350             PrintConv => {
2351             2 => '2-Dimensional Measurement',
2352             3 => '3-Dimensional Measurement',
2353             },
2354             },
2355             GPSDOP => { Groups => { 2 => 'Location' }, Writable => 'rational' },
2356             GPSSpeedRef => {
2357             Groups => { 2 => 'Location' },
2358             PrintConv => {
2359             K => 'km/h',
2360             M => 'mph',
2361             N => 'knots',
2362             },
2363             },
2364             GPSSpeed => { Groups => { 2 => 'Location' }, Writable => 'rational' },
2365             GPSTrackRef => {
2366             Groups => { 2 => 'Location' },
2367             PrintConv => {
2368             M => 'Magnetic North',
2369             T => 'True North',
2370             },
2371             },
2372             GPSTrack => { Groups => { 2 => 'Location' }, Writable => 'rational' },
2373             GPSImgDirectionRef => {
2374             Groups => { 2 => 'Location' },
2375             PrintConv => {
2376             M => 'Magnetic North',
2377             T => 'True North',
2378             },
2379             },
2380             GPSImgDirection => { Groups => { 2 => 'Location' }, Writable => 'rational' },
2381             GPSMapDatum => { Groups => { 2 => 'Location' } },
2382             GPSDestLatitude => { Groups => { 2 => 'Location' }, %latConv },
2383             GPSDestLongitude=> { Groups => { 2 => 'Location' }, %longConv },
2384             GPSDestBearingRef => {
2385             Groups => { 2 => 'Location' },
2386             PrintConv => {
2387             M => 'Magnetic North',
2388             T => 'True North',
2389             },
2390             },
2391             GPSDestBearing => { Groups => { 2 => 'Location' }, Writable => 'rational' },
2392             GPSDestDistanceRef => {
2393             Groups => { 2 => 'Location' },
2394             PrintConv => {
2395             K => 'Kilometers',
2396             M => 'Miles',
2397             N => 'Nautical Miles',
2398             },
2399             },
2400             GPSDestDistance => {
2401             Groups => { 2 => 'Location' },
2402             Writable => 'rational',
2403             },
2404             GPSProcessingMethod => { Groups => { 2 => 'Location' } },
2405             GPSAreaInformation => { Groups => { 2 => 'Location' } },
2406             GPSDifferential => {
2407             Groups => { 2 => 'Location' },
2408             Writable => 'integer',
2409             PrintConv => {
2410             0 => 'No Correction',
2411             1 => 'Differential Corrected',
2412             },
2413             },
2414             GPSHPositioningError => { #12
2415             Description => 'GPS Horizontal Positioning Error',
2416             Groups => { 2 => 'Location' },
2417             Writable => 'rational',
2418             PrintConv => '"$val m"',
2419             PrintConvInv => '$val=~s/\s*m$//; $val',
2420             },
2421             NativeDigest => { }, #PH
2422             # the following written incorrectly by ACR 15.1
2423             # SubSecTime (should not be written according to Exif4XMP 2.32 specification)
2424             # SubSecTimeOriginal (should not be written according to Exif4XMP 2.32 specification)
2425             # SubSecTimeDigitized (should not be written according to Exif4XMP 2.32 specification)
2426             # SerialNumber (should be BodySerialNumber)
2427             # Lens (should be XMP-aux)
2428             # LensInfo (should be XMP-aux)
2429             );
2430              
2431             # Exif extended properties (exifEX, ref 12)
2432             %Image::ExifTool::XMP::exifEX = (
2433             %xmpTableDefaults,
2434             GROUPS => { 1 => 'XMP-exifEX', 2 => 'Image' },
2435             NAMESPACE => 'exifEX',
2436             PRIORITY => 0, # not as reliable as actual EXIF tags
2437             NOTES => q{
2438             EXIF tags added by the EXIF 2.32 for XMP specification (see
2439             L).
2440             },
2441             Gamma => { Writable => 'rational' },
2442             PhotographicSensitivity => { Writable => 'integer' },
2443             SensitivityType => {
2444             Writable => 'integer',
2445             PrintConv => {
2446             0 => 'Unknown',
2447             1 => 'Standard Output Sensitivity',
2448             2 => 'Recommended Exposure Index',
2449             3 => 'ISO Speed',
2450             4 => 'Standard Output Sensitivity and Recommended Exposure Index',
2451             5 => 'Standard Output Sensitivity and ISO Speed',
2452             6 => 'Recommended Exposure Index and ISO Speed',
2453             7 => 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed',
2454             },
2455             },
2456             StandardOutputSensitivity => { Writable => 'integer' },
2457             RecommendedExposureIndex => { Writable => 'integer' },
2458             ISOSpeed => { Writable => 'integer' },
2459             ISOSpeedLatitudeyyy => {
2460             Description => 'ISO Speed Latitude yyy',
2461             Writable => 'integer',
2462             },
2463             ISOSpeedLatitudezzz => {
2464             Description => 'ISO Speed Latitude zzz',
2465             Writable => 'integer',
2466             },
2467             CameraOwnerName => { Name => 'OwnerName' },
2468             BodySerialNumber => { Name => 'SerialNumber', Groups => { 2 => 'Camera' } },
2469             LensSpecification => {
2470             Name => 'LensInfo',
2471             Writable => 'rational',
2472             Groups => { 2 => 'Camera' },
2473             List => 'Seq',
2474             RawJoin => 1, # join list into a string before ValueConv
2475             ValueConv => \&ConvertRationalList,
2476             ValueConvInv => sub {
2477             my $val = shift;
2478             my @vals = split ' ', $val;
2479             return $val unless @vals == 4;
2480             foreach (@vals) {
2481             $_ eq 'inf' and $_ = '1/0', next;
2482             $_ eq 'undef' and $_ = '0/0', next;
2483             Image::ExifTool::IsFloat($_) or return $val;
2484             my @a = Image::ExifTool::Rationalize($_);
2485             $_ = join '/', @a;
2486             }
2487             return \@vals; # return list reference (List-type tag)
2488             },
2489             PrintConv => \&Image::ExifTool::Exif::PrintLensInfo,
2490             PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo,
2491             Notes => q{
2492             unfortunately the EXIF 2.3 for XMP specification defined this new tag
2493             instead of using the existing XMP-aux:LensInfo
2494             },
2495             },
2496             LensMake => { Groups => { 2 => 'Camera' } },
2497             LensModel => { Groups => { 2 => 'Camera' } },
2498             LensSerialNumber => { Groups => { 2 => 'Camera' } },
2499             InteroperabilityIndex => {
2500             Name => 'InteropIndex',
2501             Description => 'Interoperability Index',
2502             PrintConv => {
2503             R98 => 'R98 - DCF basic file (sRGB)',
2504             R03 => 'R03 - DCF option file (Adobe RGB)',
2505             THM => 'THM - DCF thumbnail file',
2506             },
2507             },
2508             # new in Exif 2.31
2509             Temperature => { Writable => 'rational', Name => 'AmbientTemperature' },
2510             Humidity => { Writable => 'rational' },
2511             Pressure => { Writable => 'rational' },
2512             WaterDepth => { Writable => 'rational' },
2513             Acceleration => { Writable => 'rational' },
2514             CameraElevationAngle=> { Writable => 'rational' },
2515             # new in Exif 2.32 (according to the spec, these should use a different namespace
2516             # URI, but the same namespace prefix... Exactly how is that supposed to work?!!
2517             # -- I'll just stick with the same URI)
2518             CompositeImage => { Writable => 'integer',
2519             PrintConv => {
2520             0 => 'Unknown',
2521             1 => 'Not a Composite Image',
2522             2 => 'General Composite Image',
2523             3 => 'Composite Image Captured While Shooting',
2524             },
2525             },
2526             CompositeImageCount => { List => 'Seq', Writable => 'integer' },
2527             CompositeImageExposureTimes => {
2528             FlatName => 'CompImage',
2529             Struct => {
2530             STRUCT_NAME => 'CompImageExp',
2531             NAMESPACE => 'exifEX',
2532             TotalExposurePeriod => { Writable => 'rational' },
2533             SumOfExposureTimesOfAll => { Writable => 'rational', FlatName => 'SumExposureAll' },
2534             SumOfExposureTimesOfUsed=> { Writable => 'rational', FlatName => 'SumExposureUsed' },
2535             MaxExposureTimesOfAll => { Writable => 'rational', FlatName => 'MaxExposureAll' },
2536             MaxExposureTimesOfUsed => { Writable => 'rational', FlatName => 'MaxExposureUsed' },
2537             MinExposureTimesOfAll => { Writable => 'rational', FlatName => 'MinExposureAll' },
2538             MinExposureTimesOfUsed => { Writable => 'rational', FlatName => 'MinExposureUsed' },
2539             NumberOfSequences => { Writable => 'integer', FlatName => 'NumSequences' },
2540             NumberOfImagesInSequences=>{ Writable => 'integer', FlatName => 'ImagesPerSequence' },
2541             Values => { List => 'Seq', Writable => 'rational' },
2542             },
2543             },
2544             # new in Exif 3.0
2545             ImageUniqueID => { },
2546             ImageTitle => { },
2547             ImageEditor => { },
2548             Photographer => { Groups => { 2 => 'Author' } },
2549             CameraFirmware => { Groups => { 2 => 'Camera' } },
2550             RAWDevelopingSoftware => { },
2551             ImageEditingSoftware => { },
2552             MetadataEditingSoftware => { },
2553             );
2554              
2555             # Auxiliary namespace properties (aux) - not fully documented (ref PH)
2556             %Image::ExifTool::XMP::aux = (
2557             %xmpTableDefaults,
2558             GROUPS => { 1 => 'XMP-aux', 2 => 'Camera' },
2559             NAMESPACE => 'aux',
2560             NOTES => q{
2561             Adobe-defined auxiliary EXIF tags. This namespace existed in the XMP
2562             specification until it was dropped in 2012, presumably due to the
2563             introduction of the EXIF 2.3 for XMP specification and the exifEX namespace
2564             at this time. For this reason, tags below with equivalents in the
2565             L are avoided when writing.
2566             },
2567             Firmware => { }, #7
2568             FlashCompensation => { Writable => 'rational' }, #7
2569             ImageNumber => { }, #7
2570             LensInfo => { #7
2571             Notes => '4 rational values giving focal and aperture ranges',
2572             Avoid => 1,
2573             # convert to floating point values (or 'inf' or 'undef')
2574             ValueConv => \&ConvertRationalList,
2575             ValueConvInv => sub {
2576             my $val = shift;
2577             my @vals = split ' ', $val;
2578             return $val unless @vals == 4;
2579             foreach (@vals) {
2580             $_ eq 'inf' and $_ = '1/0', next;
2581             $_ eq 'undef' and $_ = '0/0', next;
2582             Image::ExifTool::IsFloat($_) or return $val;
2583             my @a = Image::ExifTool::Rationalize($_);
2584             $_ = join '/', @a;
2585             }
2586             return join ' ', @vals; # return string (string tag)
2587             },
2588             # convert to the form "12-20mm f/3.8-4.5" or "50mm f/1.4"
2589             PrintConv => \&Image::ExifTool::Exif::PrintLensInfo,
2590             PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo,
2591             },
2592             Lens => { },
2593             OwnerName => { Avoid => 1 }, #7
2594             SerialNumber => { Avoid => 1 },
2595             LensSerialNumber=> { Avoid => 1 },
2596             LensID => {
2597             Priority => 0,
2598             # prevent this from getting set from a LensID that has been converted
2599             ValueConvInv => q{
2600             warn "Expected one or more integer values" if $val =~ /[^-\d ]/;
2601             return $val;
2602             },
2603             },
2604             ApproximateFocusDistance => {
2605             Writable => 'rational',
2606             PrintConv => {
2607             4294967295 => 'infinity',
2608             OTHER => sub {
2609             my ($val, $inv) = @_;
2610             return $val eq 'infinity' ? 4294967295 : $val if $inv;
2611             return $val eq 4294967295 ? 'infinity' : $val;
2612             },
2613             },
2614             }, #PH (LR3)
2615             # the following new in LR6 (ref forum6497)
2616             IsMergedPanorama => { Writable => 'boolean' },
2617             IsMergedHDR => { Writable => 'boolean' },
2618             DistortionCorrectionAlreadyApplied => { Writable => 'boolean' },
2619             VignetteCorrectionAlreadyApplied => { Writable => 'boolean' },
2620             LateralChromaticAberrationCorrectionAlreadyApplied => { Writable => 'boolean' },
2621             LensDistortInfo => { }, # (LR 7.5.1, 4 signed rational values)
2622             NeutralDensityFactor => { }, # (LR 11.0 - rational value, but denominator seems significant)
2623             # the following are ref forum13747
2624             EnhanceDetailsAlreadyApplied => { Writable => 'boolean' },
2625             EnhanceDetailsVersion => { }, # integer?
2626             EnhanceSuperResolutionAlreadyApplied => { Writable => 'boolean' },
2627             EnhanceSuperResolutionVersion => { }, # integer?
2628             EnhanceSuperResolutionScale => { Writable => 'rational' },
2629             EnhanceDenoiseAlreadyApplied => { Writable => 'boolean' }, #forum14760
2630             EnhanceDenoiseVersion => { }, #forum14760 integer?
2631             EnhanceDenoiseLumaAmount => { }, #forum14760 integer?
2632             # FujiRatingAlreadyApplied - boolean written by LR classic 13.2 (forum15815)
2633             );
2634              
2635             # IPTC Core namespace properties (Iptc4xmpCore) (ref 4)
2636             %Image::ExifTool::XMP::iptcCore = (
2637             %xmpTableDefaults,
2638             GROUPS => { 1 => 'XMP-iptcCore', 2 => 'Author' },
2639             NAMESPACE => 'Iptc4xmpCore',
2640             TABLE_DESC => 'XMP IPTC Core',
2641             NOTES => q{
2642             IPTC Core namespace tags. The actual IPTC Core namespace prefix is
2643             "Iptc4xmpCore", which is the prefix recorded in the file, but ExifTool
2644             shortens this for the family 1 group name. (see
2645             L)
2646             },
2647             CountryCode => { Groups => { 2 => 'Location' } },
2648             CreatorContactInfo => {
2649             Struct => {
2650             STRUCT_NAME => 'ContactInfo',
2651             NAMESPACE => 'Iptc4xmpCore',
2652             CiAdrCity => { },
2653             CiAdrCtry => { },
2654             CiAdrExtadr => { },
2655             CiAdrPcode => { },
2656             CiAdrRegion => { },
2657             CiEmailWork => { },
2658             CiTelWork => { },
2659             CiUrlWork => { },
2660             },
2661             },
2662             CreatorContactInfoCiAdrCity => { Flat => 1, Name => 'CreatorCity' },
2663             CreatorContactInfoCiAdrCtry => { Flat => 1, Name => 'CreatorCountry' },
2664             CreatorContactInfoCiAdrExtadr => { Flat => 1, Name => 'CreatorAddress' },
2665             CreatorContactInfoCiAdrPcode => { Flat => 1, Name => 'CreatorPostalCode' },
2666             CreatorContactInfoCiAdrRegion => { Flat => 1, Name => 'CreatorRegion' },
2667             CreatorContactInfoCiEmailWork => { Flat => 1, Name => 'CreatorWorkEmail' },
2668             CreatorContactInfoCiTelWork => { Flat => 1, Name => 'CreatorWorkTelephone' },
2669             CreatorContactInfoCiUrlWork => { Flat => 1, Name => 'CreatorWorkURL' },
2670             IntellectualGenre => { Groups => { 2 => 'Other' } },
2671             Location => { Groups => { 2 => 'Location' } },
2672             Scene => { Groups => { 2 => 'Other' }, List => 'Bag' },
2673             SubjectCode => { Groups => { 2 => 'Other' }, List => 'Bag' },
2674             # Copyright - have seen this in a sample (Jan 2021), but I think it is non-standard
2675             # new IPTC Core 1.3 properties
2676             AltTextAccessibility => { Groups => { 2 => 'Other' }, Writable => 'lang-alt' },
2677             ExtDescrAccessibility => { Groups => { 2 => 'Other' }, Writable => 'lang-alt' },
2678             );
2679              
2680             # Adobe Lightroom namespace properties (lr) (ref PH)
2681             %Image::ExifTool::XMP::Lightroom = (
2682             %xmpTableDefaults,
2683             GROUPS => { 1 => 'XMP-lr', 2 => 'Image' },
2684             NAMESPACE => 'lr',
2685             TABLE_DESC => 'XMP Adobe Lightroom',
2686             NOTES => 'Adobe Lightroom "lr" namespace tags.',
2687             privateRTKInfo => { },
2688             hierarchicalSubject => { List => 'Bag' },
2689             weightedFlatSubject => { List => 'Bag' },
2690             );
2691              
2692             # Adobe Album namespace properties (album) (ref PH)
2693             %Image::ExifTool::XMP::Album = (
2694             %xmpTableDefaults,
2695             GROUPS => { 1 => 'XMP-album', 2 => 'Image' },
2696             NAMESPACE => 'album',
2697             TABLE_DESC => 'XMP Adobe Album',
2698             NOTES => 'Adobe Album namespace tags.',
2699             Notes => { },
2700             );
2701              
2702             # ExifTool namespace properties (et)
2703             %Image::ExifTool::XMP::ExifTool = (
2704             %xmpTableDefaults,
2705             GROUPS => { 1 => 'XMP-et', 2 => 'Image' },
2706             NAMESPACE => 'et',
2707             OriginalImageHash => { Notes => 'used to store ExifTool ImageDataHash digest' },
2708             OriginalImageHashType => { Notes => "ImageHashType API setting, default 'MD5'" },
2709             OriginalImageMD5 => { Notes => 'deprecated' },
2710             );
2711              
2712             # table to add tags in other namespaces
2713             %Image::ExifTool::XMP::other = (
2714             GROUPS => { 2 => 'Unknown' },
2715             LANG_INFO => \&GetLangInfo,
2716             );
2717              
2718             # Composite XMP tags
2719             %Image::ExifTool::XMP::Composite = (
2720             # get latitude/longitude reference from XMP lat/long tags
2721             # (used to set EXIF GPS position from XMP tags)
2722             GPSLatitudeRef => {
2723             Require => 'XMP-exif:GPSLatitude',
2724             Groups => { 2 => 'Location' },
2725             # Note: Do not Inihibit based on EXIF:GPSLatitudeRef (see forum10192)
2726             ValueConv => q{
2727             IsFloat($val[0]) and return $val[0] < 0 ? "S" : "N";
2728             $val[0] =~ /^.*([NS])/;
2729             return $1;
2730             },
2731             PrintConv => { N => 'North', S => 'South' },
2732             },
2733             GPSLongitudeRef => {
2734             Require => 'XMP-exif:GPSLongitude',
2735             Groups => { 2 => 'Location' },
2736             ValueConv => q{
2737             IsFloat($val[0]) and return $val[0] < 0 ? "W" : "E";
2738             $val[0] =~ /^.*([EW])/;
2739             return $1;
2740             },
2741             PrintConv => { E => 'East', W => 'West' },
2742             },
2743             GPSDestLatitudeRef => {
2744             Require => 'XMP-exif:GPSDestLatitude',
2745             Groups => { 2 => 'Location' },
2746             ValueConv => q{
2747             IsFloat($val[0]) and return $val[0] < 0 ? "S" : "N";
2748             $val[0] =~ /^.*([NS])/;
2749             return $1;
2750             },
2751             PrintConv => { N => 'North', S => 'South' },
2752             },
2753             GPSDestLongitudeRef => {
2754             Require => 'XMP-exif:GPSDestLongitude',
2755             Groups => { 2 => 'Location' },
2756             ValueConv => q{
2757             IsFloat($val[0]) and return $val[0] < 0 ? "W" : "E";
2758             $val[0] =~ /^.*([EW])/;
2759             return $1;
2760             },
2761             PrintConv => { E => 'East', W => 'West' },
2762             },
2763             LensID => {
2764             Notes => 'attempt to convert numerical XMP-aux:LensID stored by Adobe applications',
2765             Require => {
2766             0 => 'XMP-aux:LensID',
2767             1 => 'Make',
2768             },
2769             Desire => {
2770             2 => 'LensInfo',
2771             3 => 'FocalLength',
2772             4 => 'LensModel',
2773             5 => 'MaxApertureValue',
2774             },
2775             Inhibit => {
2776             6 => 'Composite:LensID', # don't override existing Composite:LensID
2777             },
2778             Groups => { 2 => 'Camera' },
2779             ValueConv => '$val',
2780             PrintConv => 'Image::ExifTool::XMP::PrintLensID($self, @val)',
2781             },
2782             Flash => {
2783             Notes => 'facilitates copying camera flash information between XMP and EXIF',
2784             Desire => {
2785             0 => 'XMP:FlashFired',
2786             1 => 'XMP:FlashReturn',
2787             2 => 'XMP:FlashMode',
2788             3 => 'XMP:FlashFunction',
2789             4 => 'XMP:FlashRedEyeMode',
2790             5 => 'XMP:Flash', # handle structured flash information too
2791             },
2792             Groups => { 2 => 'Camera' },
2793             Writable => 1,
2794             PrintHex => 1,
2795             SeparateTable => 'EXIF Flash',
2796             ValueConv => q{
2797             if (ref $val[5] eq 'HASH') {
2798             # copy structure fields into value array
2799             my $i = 0;
2800             $val[$i++] = $val[5]{$_} foreach qw(Fired Return Mode Function RedEyeMode);
2801             }
2802             return((($val[0] and lc($val[0]) eq 'true') ? 0x01 : 0) |
2803             (($val[1] || 0) << 1) |
2804             (($val[2] || 0) << 3) |
2805             (($val[3] and lc($val[3]) eq 'true') ? 0x20 : 0) |
2806             (($val[4] and lc($val[4]) eq 'true') ? 0x40 : 0));
2807             },
2808             PrintConv => \%Image::ExifTool::Exif::flash,
2809             WriteAlso => {
2810             'XMP:FlashFired' => '$val & 0x01 ? "True" : "False"',
2811             'XMP:FlashReturn' => '($val & 0x06) >> 1',
2812             'XMP:FlashMode' => '($val & 0x18) >> 3',
2813             'XMP:FlashFunction' => '$val & 0x20 ? "True" : "False"',
2814             'XMP:FlashRedEyeMode' => '$val & 0x40 ? "True" : "False"',
2815             },
2816             },
2817             );
2818              
2819             # add our composite tags
2820             Image::ExifTool::AddCompositeTags('Image::ExifTool::XMP');
2821              
2822             #------------------------------------------------------------------------------
2823             # AutoLoad our writer routines when necessary
2824             #
2825             sub AUTOLOAD
2826             {
2827 39     39   335 return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
2828             }
2829              
2830             #------------------------------------------------------------------------------
2831             # Escape necessary XML characters in UTF-8 string
2832             # Inputs: 0) string to be escaped
2833             # Returns: escaped string
2834             my %charName = ('"'=>'quot', '&'=>'amp', "'"=>'#39', '<'=>'lt', '>'=>'gt');
2835             sub EscapeXML($)
2836             {
2837 1383     1383 0 2499 my $str = shift;
2838 1383         3968 $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters
2839 1383         3396 return $str;
2840             }
2841              
2842             #------------------------------------------------------------------------------
2843             # Unescape XML character references (entities and numerical)
2844             # Inputs: 0) string to be unescaped
2845             # 1) optional hash reference to convert entity names to numbers
2846             # 2) optional character encoding
2847             # Returns: unescaped string
2848             my %charNum = ('quot'=>34, 'amp'=>38, 'apos'=>39, 'lt'=>60, 'gt'=>62);
2849             sub UnescapeXML($;$$)
2850             {
2851 4659     4659 0 10633 my ($str, $conv, $enc) = @_;
2852 4659 100       11813 $conv = \%charNum unless $conv;
2853 4659         10716 $str =~ s/&(#?\w+);/UnescapeChar($1,$conv,$enc)/sge;
  229         683  
2854 4659         11773 return $str;
2855             }
2856              
2857             #------------------------------------------------------------------------------
2858             # Escape string for XML, ensuring valid XML and UTF-8
2859             # Inputs: 0) string
2860             # Returns: escaped string
2861             sub FullEscapeXML($)
2862             {
2863 0     0 0 0 my $str = shift;
2864 0         0 $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters
2865 0         0 $str =~ s/\\/\/sg; # escape backslashes too
2866             # then use C-escape sequences for invalid characters
2867 0 0 0     0 if ($str =~ /[\0-\x1f]/ or Image::ExifTool::IsUTF8(\$str) < 0) {
2868 0         0 $str =~ s/([\0-\x1f\x7f-\xff])/sprintf("\\x%.2x",ord $1)/sge;
  0         0  
2869             }
2870 0         0 return $str;
2871             }
2872              
2873             #------------------------------------------------------------------------------
2874             # Unescape XML/C escaped string
2875             # Inputs: 0) string
2876             # Returns: unescaped string
2877             sub FullUnescapeXML($)
2878             {
2879 0     0 0 0 my $str = shift;
2880             # unescape C escape sequences first
2881 0         0 $str =~ s/\\x([\da-f]{2})/chr(hex($1))/sge;
  0         0  
2882 0         0 my $conv = \%charNum;
2883 0         0 $str =~ s/&(#?\w+);/UnescapeChar($1,$conv)/sge;
  0         0  
2884 0         0 return $str;
2885             }
2886              
2887             #------------------------------------------------------------------------------
2888             # Convert XML character reference to UTF-8
2889             # Inputs: 0) XML character reference stripped of the '&' and ';' (eg. 'quot', '#34', '#x22')
2890             # 1) hash reference for looking up character numbers by name
2891             # 2) optional character encoding (default 'UTF8')
2892             # Returns: UTF-8 equivalent (or original character on conversion error)
2893             sub UnescapeChar($$;$)
2894             {
2895 229     229 0 648 my ($ch, $conv, $enc) = @_;
2896 229         631 my $val = $$conv{$ch};
2897 229 100       601 unless (defined $val) {
2898 122 100       405 if ($ch =~ /^#x([0-9a-fA-F]+)$/) {
    50          
2899 112         251 $val = hex($1);
2900             } elsif ($ch =~ /^#(\d+)$/) {
2901 10         36 $val = $1;
2902             } else {
2903 0         0 return "&$ch;"; # should issue a warning here? [no]
2904             }
2905             }
2906 229 100       899 return chr($val) if $val < 0x80; # simple ASCII
2907 154 50       413 $val = $] >= 5.006001 ? pack('C0U', $val) : Image::ExifTool::PackUTF8($val);
2908 154 50 66     568 $val = Image::ExifTool::Decode(undef, $val, 'UTF8', undef, $enc) if $enc and $enc ne 'UTF8';
2909 154         688 return $val;
2910             }
2911              
2912             #------------------------------------------------------------------------------
2913             # Fix malformed UTF8 (by replacing bad bytes with specified character)
2914             # Inputs: 0) string reference, 1) string to replace each bad byte,
2915             # may be '' to delete bad bytes, or undef to use '?'
2916             # Returns: true if string was fixed, and updates string
2917             sub FixUTF8($;$)
2918             {
2919 1369     1369 0 3188 my ($strPt, $bad) = @_;
2920 1369         2402 my $fixed;
2921 1369         5593 pos($$strPt) = 0; # start at beginning of string
2922 1369         3096 for (;;) {
2923 1395 100       6081 last unless $$strPt =~ /([\x80-\xff])/g;
2924 26         108 my $ch = ord($1);
2925 26         214 my $pos = pos($$strPt);
2926             # (see comments in Image::ExifTool::IsUTF8())
2927 26 50 33     156 if ($ch >= 0xc2 and $ch < 0xf8) {
2928 26 0       96 my $n = $ch < 0xe0 ? 1 : ($ch < 0xf0 ? 2 : 3);
    50          
2929 26 50       423 if ($$strPt =~ /\G([\x80-\xbf]{$n})/g) {
2930 26 50       115 next if $n == 1;
2931 0 0       0 if ($n == 2) {
2932 0 0 0     0 next unless ($ch == 0xe0 and (ord($1) & 0xe0) == 0x80) or
      0        
      0        
      0        
      0        
      0        
2933             ($ch == 0xed and (ord($1) & 0xe0) == 0xa0) or
2934             ($ch == 0xef and ord($1) == 0xbf and
2935             (ord(substr $1, 1) & 0xfe) == 0xbe);
2936             } else {
2937 0 0 0     0 next unless ($ch == 0xf0 and (ord($1) & 0xf0) == 0x80) or
      0        
      0        
      0        
2938             ($ch == 0xf4 and ord($1) > 0x8f) or $ch > 0xf4;
2939             }
2940             }
2941             }
2942             # replace bad character
2943 0 0       0 $bad = '?' unless defined $bad;
2944 0         0 substr($$strPt, $pos-1, 1) = $bad;
2945 0         0 pos($$strPt) = $pos-1 + length $bad;
2946 0         0 $fixed = 1;
2947             }
2948 1369         5441 return $fixed;
2949             }
2950              
2951             #------------------------------------------------------------------------------
2952             # Utility routine to decode a base64 string
2953             # Inputs: 0) base64 string
2954             # Returns: reference to decoded data
2955             sub DecodeBase64($)
2956             {
2957 9     9 0 59 local($^W) = 0; # unpack('u',...) gives bogus warning in 5.00[123]
2958 9         26 my $str = shift;
2959              
2960             # truncate at first unrecognized character (base 64 data
2961             # may only contain A-Z, a-z, 0-9, +, /, =, or white space)
2962 9         750 $str =~ s/[^A-Za-z0-9+\/= \t\n\r\f].*//s;
2963             # translate to uucoded and remove padding and white space
2964 9         377 $str =~ tr/A-Za-z0-9+\/= \t\n\r\f/ -_/d;
2965              
2966             # convert the data to binary in chunks
2967 9         24 my $chunkSize = 60;
2968 9         73 my $uuLen = pack('c', 32 + $chunkSize * 3 / 4); # calculate length byte
2969 9         21 my $dat = '';
2970 9         21 my ($i, $substr);
2971             # loop through the whole chunks
2972 9         27 my $len = length($str) - $chunkSize;
2973 9         50 for ($i=0; $i<=$len; $i+=$chunkSize) {
2974 685         1091 $substr = substr($str, $i, $chunkSize); # get a chunk of the data
2975 685         2415 $dat .= unpack('u', $uuLen . $substr); # decode it
2976             }
2977 9         22 $len += $chunkSize;
2978             # handle last partial chunk if necessary
2979 9 100       31 if ($i < $len) {
2980 8         37 $uuLen = pack('c', 32 + ($len-$i) * 3 / 4); # recalculate length
2981 8         24 $substr = substr($str, $i, $len-$i); # get the last partial chunk
2982 8         43 $dat .= unpack('u', $uuLen . $substr); # decode it
2983             }
2984 9         85 return \$dat;
2985             }
2986              
2987             #------------------------------------------------------------------------------
2988             # Generate a tag ID for this XMP tag
2989             # Inputs: 0) tag property name list ref, 1) array ref for receiving structure property list
2990             # 2) array for receiving namespace list
2991             # Returns: tagID and outtermost interesting namespace (or '' if no namespace)
2992             sub GetXMPTagID($;$$)
2993             {
2994 18189     18189 0 31472 my ($props, $structProps, $nsList) = @_;
2995 18189         23858 my ($tag, $prop, $namespace);
2996 18189         39566 foreach $prop (@$props) {
2997             # split name into namespace and property name
2998             # (Note: namespace can be '' for property qualifiers)
2999 74474 100       260186 my ($ns, $nm) = ($prop =~ /(.*?):(.*)/) ? ($1, $2) : ('', $prop);
3000 74474 100 100     222672 if ($ignoreNamespace{$ns} or $ignoreProp{$prop} or $ignoreEtProp{$prop}) {
      66        
3001             # special case: don't ignore rdf numbered items
3002             # (not technically allowed in XMP, but used in RDF/XML)
3003 43067 50       73261 unless ($prop =~ /^rdf:(_\d+)$/) {
3004             # save list index if necessary for structures
3005 43067 100 100     81055 if ($structProps and @$structProps and $prop =~ /^rdf:li (\d+)$/) {
      100        
3006 440         722 push @{$$structProps[-1]}, $1;
  440         1376  
3007             }
3008 43067         62635 next;
3009             }
3010 0 0       0 $tag .= $1 if defined $tag;
3011             } else {
3012 31407         44362 $nm =~ s/ .*//; # remove nodeID if it exists
3013             # all uppercase is ugly, so convert it
3014 31407 100       71567 if ($nm !~ /[a-z]/) {
3015 301   66     985 my $xlat = $stdXlatNS{$ns} || $ns;
3016 301         550 my $info = $Image::ExifTool::XMP::Main{$xlat};
3017 301         428 my $table;
3018 301 50 66     948 if (ref $info eq 'HASH' and $$info{SubDirectory}) {
3019 63         329 $table = GetTagTable($$info{SubDirectory}{TagTable});
3020             }
3021 301 100 100     856 unless ($table and $$table{$nm}) {
3022 286         531 $nm = lc($nm);
3023 286         935 $nm =~ s/_([a-z])/\u$1/g;
3024             }
3025             }
3026 31407 100       46878 if (defined $tag) {
3027 13235         24235 $tag .= ucfirst($nm); # add to tag name
3028             } else {
3029 18172         24520 $tag = $nm;
3030             }
3031             # save structure information if necessary
3032 31407 100       48751 if ($structProps) {
3033 1393         3380 push @$structProps, [ $nm ];
3034 1393 100       3260 push @$nsList, $ns if $nsList;
3035             }
3036             }
3037             # save namespace of first property to contribute to tag name
3038 31407 100       61022 $namespace = $ns unless $namespace;
3039             }
3040 18189 100       28226 if (wantarray) {
3041 3892   100     16472 return ($tag, $namespace || '');
3042             } else {
3043 14297         39198 return $tag;
3044             }
3045             }
3046              
3047             #------------------------------------------------------------------------------
3048             # Register namespace for specified user-defined table
3049             # Inputs: 0) tag/structure table ref
3050             # Returns: namespace prefix
3051             sub RegisterNamespace($)
3052             {
3053 572     572 0 1206 my $table = shift;
3054 572 100       2943 return $$table{NAMESPACE} unless ref $$table{NAMESPACE};
3055 32         72 my $nsRef = $$table{NAMESPACE};
3056             # recognize as either a list or hash
3057 32         68 my $ns;
3058 32 50       110 if (ref $nsRef eq 'ARRAY') {
3059 0         0 $ns = $$nsRef[0];
3060 0         0 $nsURI{$ns} = $$nsRef[1];
3061 0         0 $uri2ns{$$nsRef[1]} = $ns;
3062             } else { # must be a hash
3063 32         154 my @ns = sort keys %$nsRef; # allow multiple namespace definitions
3064 32         104 while (@ns) {
3065 32         80 $ns = pop @ns;
3066 32 50 66     215 if ($nsURI{$ns} and $nsURI{$ns} ne $$nsRef{$ns}) {
3067 0         0 warn "User-defined namespace prefix '${ns}' conflicts with existing namespace\n";
3068             }
3069 32         107 $nsURI{$ns} = $$nsRef{$ns};
3070 32         147 $uri2ns{$$nsRef{$ns}} = $ns;
3071             }
3072             }
3073 32         146 return $$table{NAMESPACE} = $ns;
3074             }
3075              
3076             #------------------------------------------------------------------------------
3077             # Generate flattened tags and add to table
3078             # Inputs: 0) tag table ref, 1) tag ID for Struct tag (if not defined, whole table is done),
3079             # 2) flag to not expand sub-structures, 3) Hidden flag
3080             # Returns: number of tags added (not counting those just initialized)
3081             # Notes: Must have verified that $$tagTablePtr{$tagID}{Struct} exists before calling this routine
3082             # - makes sure that the tagInfo Struct is a HASH reference
3083             sub AddFlattenedTags($;$$$)
3084             {
3085 5682     5682 0 6924 local $_;
3086 5682         9435 my ($tagTablePtr, $tagID, $noSubStruct, $hidden) = @_;
3087 5682         6686 my $count = 0;
3088 5682         6422 my @tagIDs;
3089              
3090 5682 50       7623 if (defined $tagID) {
3091 5682         7593 push @tagIDs, $tagID;
3092             } else {
3093 0         0 foreach $tagID (TagTableKeys($tagTablePtr)) {
3094 0         0 my $tagInfo = $$tagTablePtr{$tagID};
3095 0 0 0     0 next unless ref $tagInfo eq 'HASH' and $$tagInfo{Struct};
3096 0         0 push @tagIDs, $tagID;
3097             }
3098             }
3099              
3100             # loop through specified tags
3101 5682         7367 foreach $tagID (@tagIDs) {
3102              
3103 5682         8073 my $tagInfo = $$tagTablePtr{$tagID};
3104              
3105 5682 100       10862 $$tagInfo{Flattened} and next; # only generate flattened tags once
3106 558         1234 $$tagInfo{Flattened} = 1;
3107              
3108 558         1045 my $strTable = $$tagInfo{Struct};
3109 558 50       1307 unless (ref $strTable) { # (allow a structure name for backward compatibility only)
3110 0         0 my $strName = $strTable;
3111 0 0       0 $strTable = $Image::ExifTool::UserDefined::xmpStruct{$strTable} or next;
3112 0 0       0 $$strTable{STRUCT_NAME} or $$strTable{STRUCT_NAME} = "XMP $strName";
3113 0         0 $$tagInfo{Struct} = $strTable; # replace old-style name with HASH ref
3114 0         0 delete $$tagInfo{SubDirectory}; # deprecated use of SubDirectory in Struct tags
3115             }
3116              
3117             # get prefix for flattened tag names
3118 558 100       1771 my $flat = (defined $$tagInfo{FlatName} ? $$tagInfo{FlatName} : $$tagInfo{Name});
3119              
3120             # get family 2 group name for this structure tag
3121 558         1005 my ($tagG2, $field);
3122 558 100       1343 $tagG2 = $$tagInfo{Groups}{2} if $$tagInfo{Groups};
3123 558 100       1640 $tagG2 or $tagG2 = $$tagTablePtr{GROUPS}{2};
3124              
3125 558         3019 foreach $field (keys %$strTable) {
3126 6733 100       12982 next if $specialStruct{$field};
3127 5550         7736 my $fieldInfo = $$strTable{$field};
3128 5550 100       9177 next if $$fieldInfo{LangCode}; # don't flatten lang-alt tags
3129 5545 100 100     10267 next if $$fieldInfo{Struct} and $noSubStruct; # don't expand sub-structures if specified
3130             # build a tag ID for the corresponding flattened tag
3131 5493         7971 my $fieldName = ucfirst($field);
3132 5493   66     13144 my $flatField = $$fieldInfo{FlatName} || $fieldName;
3133 5493         8927 my $flatID = $tagID . $fieldName;
3134 5493         8817 my $flatInfo = $$tagTablePtr{$flatID};
3135 5493 100       7820 if ($flatInfo) {
3136 315 50       685 ref $flatInfo eq 'HASH' or warn("$flatInfo is not a HASH!\n"), next; # (to be safe)
3137             # pre-defined flattened tags should have Flat flag set
3138 315 100       732 if (not defined $$flatInfo{Flat}) {
3139 4 50       21 next if $$flatInfo{NotFlat};
3140 0 0       0 warn "Missing Flat flag for $$flatInfo{Name}\n" if $Image::ExifTool::debug;
3141             }
3142 311         454 $$flatInfo{Flat} = 0;
3143             # copy all missing entries from field information
3144 311         708 foreach (keys %$fieldInfo) {
3145             # must not copy PropertyPath (but can't delete it afterwards
3146             # because the flat tag may already have this set)
3147 262 100 100     846 next if $_ eq 'PropertyPath' or defined $$flatInfo{$_};
3148             # copy the property (making a copy of the Groups hash)
3149 229 100       666 $$flatInfo{$_} = $_ eq 'Groups' ? { %{$$fieldInfo{$_}} } : $$fieldInfo{$_};
  12         69  
3150             }
3151             # (NOTE: Can NOT delete Groups because we need them if GotGroups was done)
3152             # re-generate List flag unless it is set to 0
3153 311 100       741 delete $$flatInfo{List} if $$flatInfo{List};
3154             } else {
3155             # generate new flattened tag information based on structure field
3156 5178         6820 my $flatName = $flat . $flatField;
3157 5178         20396 $flatInfo = { %$fieldInfo, Name => $flatName, Flat => 0 };
3158 5178 50       11088 $$flatInfo{Hidden} = 0 unless $hidden;
3159 5178 100       8556 $$flatInfo{FlatName} = $flatName if $$fieldInfo{FlatName};
3160             # make a copy of the Groups hash if necessary
3161 5178 100       8550 $$flatInfo{Groups} = { %{$$fieldInfo{Groups}} } if $$fieldInfo{Groups};
  165         553  
3162             # add new flattened tag to table
3163 5178         11707 AddTagToTable($tagTablePtr, $flatID, $flatInfo);
3164 5178         7014 ++$count;
3165             }
3166             # propagate List flag (unless set to 0 in pre-defined flattened tag)
3167 5489 100       9626 unless (defined $$flatInfo{List}) {
3168 2971 100 100     12186 $$flatInfo{List} = $$fieldInfo{List} || 1 if $$fieldInfo{List} or $$tagInfo{List};
      100        
3169             }
3170             # set group 2 name from the first existing family 2 group in the:
3171             # 1) structure field Groups, 2) structure table GROUPS, 3) structure tag Groups
3172 5489 100 66     15308 if ($$fieldInfo{Groups} and $$fieldInfo{Groups}{2}) {
    100 66        
3173 177         350 $$flatInfo{Groups}{2} = $$fieldInfo{Groups}{2};
3174             } elsif ($$strTable{GROUPS} and $$strTable{GROUPS}{2}) {
3175 91         216 $$flatInfo{Groups}{2} = $$strTable{GROUPS}{2};
3176             } else {
3177 5221         7657 $$flatInfo{Groups}{2} = $tagG2;
3178             }
3179             # save reference to top-level and parent structures
3180 5489   66     14280 $$flatInfo{RootTagInfo} = $$tagInfo{RootTagInfo} || $tagInfo;
3181 5489         8795 $$flatInfo{ParentTagInfo} = $tagInfo;
3182             # recursively generate flattened tags for sub-structures
3183 5489 100       12895 next unless $$flatInfo{Struct};
3184 243 50       667 length($flatID) > 250 and warn("Possible deep recursion for tag $flatID\n"), last;
3185             # reset flattened tag just in case we flattened hierarchy in the wrong order
3186             # because we must start from the outtermost structure to get the List flags right
3187             # (this should only happen when building tag tables)
3188 243         526 delete $$flatInfo{Flattened};
3189 243         1143 $count += AddFlattenedTags($tagTablePtr, $flatID, $$flatInfo{NoSubStruct});
3190             }
3191             }
3192 5682         11079 return $count;
3193             }
3194              
3195             #------------------------------------------------------------------------------
3196             # Get localized version of tagInfo hash
3197             # Inputs: 0) tagInfo hash ref, 1) language code (eg. "x-default")
3198             # Returns: new tagInfo hash ref, or undef if invalid
3199             sub GetLangInfo($$)
3200             {
3201 119     119 0 301 my ($tagInfo, $langCode) = @_;
3202             # only allow alternate language tags in lang-alt lists
3203 119 100 66     773 return undef unless $$tagInfo{Writable} and $$tagInfo{Writable} eq 'lang-alt';
3204 107         274 $langCode =~ tr/_/-/; # RFC 3066 specifies '-' as a separator
3205 107         428 my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode);
3206 107         253 return $langInfo;
3207             }
3208              
3209             #------------------------------------------------------------------------------
3210             # Get standard case for language code
3211             # Inputs: 0) Language code
3212             # Returns: Language code in standard case
3213             sub StandardLangCase($)
3214             {
3215 53     53 0 135 my $lang = shift;
3216             # make 2nd subtag uppercase only if it is 2 letters
3217 53 100       299 return lc($1) . uc($2) . lc($3) if $lang =~ /^([a-z]{2,3}|[xi])(-[a-z]{2})\b(.*)/i;
3218 40         121 return lc($lang);
3219             }
3220              
3221             #------------------------------------------------------------------------------
3222             # Scan for XMP in a file
3223             # Inputs: 0) ExifTool object ref, 1) RAF reference
3224             # Returns: 1 if xmp was found, 0 otherwise
3225             # Notes: Currently only recognizes UTF8-encoded XMP
3226             sub ScanForXMP($$)
3227             {
3228 0     0 0 0 my ($et, $raf) = @_;
3229 0         0 my ($buff, $xmp);
3230 0         0 my $lastBuff = '';
3231              
3232 0         0 $et->VPrint(0,"Scanning for XMP\n");
3233 0         0 for (;;) {
3234 0 0 0     0 defined $buff or $raf->Read($buff, 65536) or return 0;
3235 0 0       0 unless (defined $xmp) {
3236 0         0 $lastBuff .= $buff;
3237 0 0       0 unless ($lastBuff =~ /(<\?xpacket begin=)/g) {
3238             # must keep last 15 bytes to match 16-byte "xpacket begin" string
3239 0 0       0 $lastBuff = length($buff) <= 15 ? $buff : substr($buff, -15);
3240 0         0 undef $buff;
3241 0         0 next;
3242             }
3243 0         0 $xmp = $1;
3244 0         0 $buff = substr($lastBuff, pos($lastBuff));
3245             }
3246 0         0 my $pos = length($xmp) - 18; # (18 = length("
3247 0         0 $xmp .= $buff; # add new data to our XMP
3248 0 0       0 pos($xmp) = $pos if $pos > 0; # set start for "xpacket end" scan
3249 0 0       0 if ($xmp =~ /<\?xpacket end=['"][wr]['"]\?>/g) {
3250 0         0 $buff = substr($xmp, pos($xmp)); # save data after end of XMP
3251 0         0 $xmp = substr($xmp, 0, pos($xmp)); # isolate XMP
3252             # check XMP for validity (not valid if it contains null bytes)
3253 0 0       0 $pos = rindex($xmp, "\0") + 1 or last;
3254 0         0 $lastBuff = substr($xmp, $pos); # re-parse beginning after last null byte
3255 0         0 undef $xmp;
3256             } else {
3257 0         0 undef $buff;
3258             }
3259             }
3260 0 0       0 unless ($$et{FileType}) {
3261 0         0 $$et{FILE_TYPE} = $$et{FILE_EXT};
3262 0         0 $et->SetFileType('', undef, '');
3263             }
3264 0         0 my %dirInfo = (
3265             DataPt => \$xmp,
3266             DirLen => length $xmp,
3267             DataLen => length $xmp,
3268             );
3269 0         0 ProcessXMP($et, \%dirInfo);
3270 0         0 return 1;
3271             }
3272              
3273             #------------------------------------------------------------------------------
3274             # Print conversion for XMP-aux:LensID
3275             # Inputs: 0) ExifTool ref, 1) LensID, 2) Make, 3) LensInfo, 4) FocalLength,
3276             # 5) LensModel, 6) MaxApertureValue
3277             # (yes, this is ugly -- blame Adobe)
3278             sub PrintLensID(@)
3279             {
3280 0     0 0 0 local $_;
3281 0         0 my ($et, $id, $make, $info, $focalLength, $lensModel, $maxAv) = @_;
3282 0         0 my ($mk, $printConv);
3283 0         0 my %alt = ( Pentax => 'Ricoh' ); # Pentax changed its name to Ricoh
3284             # missing: Olympus (no XMP:LensID written by Adobe)
3285 0         0 foreach $mk (qw(Canon Nikon Pentax Sony Sigma Samsung Leica)) {
3286 0 0 0     0 next unless $make =~ /$mk/i or ($alt{$mk} and $make =~ /$alt{$mk}/i);
      0        
3287             # get name of module containing the lens lookup (default "Make.pm")
3288 0   0     0 my $mod = { Sigma => 'SigmaRaw', Leica => 'Panasonic' }->{$mk} || $mk;
3289 0         0 require "Image/ExifTool/$mod.pm";
3290             # get the name of the lens name lookup (default "makeLensTypes")
3291             # (canonLensTypes, pentaxLensTypes, nikonLensIDs, etc)
3292             my $convName = "Image::ExifTool::${mod}::" .
3293 0   0     0 ({ Nikon => 'nikonLensIDs' }->{$mk} || lc($mk) . 'LensTypes');
3294 65     65   858 no strict 'refs';
  65         157  
  65         6096  
3295 0 0       0 %$convName or last;
3296 0         0 my $printConv = \%$convName;
3297 65     65   454 use strict 'refs';
  65         154  
  65         919763  
3298             # sf = short focal
3299             # lf = long focal
3300             # sa = max aperture at short focal
3301             # la = max aperture at long focal
3302 0         0 my ($sf, $lf, $sa, $la);
3303 0 0       0 if ($info) {
3304 0         0 my @a = split ' ', $info;
3305 0   0     0 $_ eq 'undef' and $_ = undef foreach @a;
3306 0         0 ($sf, $lf, $sa, $la) = @a;
3307             # for Sony and ambiguous LensID, $info data may be incorrect:
3308             # use only if it agrees with $focalLength and $maxAv (ref JR)
3309 0 0 0     0 if ($mk eq 'Sony' and
    0 0        
3310             (($focalLength and (($sf and $focalLength < $sf - 0.5) or
3311             ($lf and $focalLength > $lf + 0.5))) or
3312             ($maxAv and (($sa and $maxAv < $sa - 0.15) or
3313             ($la and $maxAv > $la + 0.15)))))
3314             {
3315 0         0 undef $sf;
3316 0         0 undef $lf;
3317 0         0 undef $sa;
3318 0         0 undef $la;
3319             } elsif ($maxAv) {
3320             # (using the short-focal-length max aperture in place of MaxAperture
3321             # is a bad approximation, so don't do this if MaxApertureValue exists)
3322 0         0 undef $sa;
3323             }
3324             }
3325 0 0 0     0 if ($mk eq 'Pentax' and $id =~ /^\d+$/) {
3326             # for Pentax, CS4 stores an int16u, but we use 2 x int8u
3327 0         0 $id = join(' ', unpack('C*', pack('n', $id)));
3328             }
3329             # Nikon is a special case because Adobe doesn't store the full LensID
3330             # (Apple Photos does, but we have to convert back to hex)
3331 0 0       0 if ($mk eq 'Nikon') {
3332 0         0 $id = sprintf('%X', $id);
3333 0 0       0 $id = "0$id" if length($id) & 0x01; # pad with leading 0 if necessary
3334 0 0       0 $id =~ s/(..)/$1 /g and $id =~ s/ $//; # put spaces between bytes
3335 0         0 my (%newConv, %used);
3336 0         0 my $i = 0;
3337 0         0 foreach (grep /^$id/, keys %$printConv) {
3338 0         0 my $lens = $$printConv{$_};
3339 0 0       0 next if $used{$lens}; # avoid duplicates
3340 0         0 $used{$lens} = 1;
3341 0 0       0 $newConv{$i ? "$id.$i" : $id} = $lens;
3342 0         0 ++$i;
3343             }
3344 0         0 $printConv = \%newConv;
3345             }
3346 0   0     0 my $str = $$printConv{$id} || "Unknown ($id)";
3347 0         0 return Image::ExifTool::Exif::PrintLensID($et, $str, $printConv,
3348             undef, $id, $focalLength, $sa, $maxAv, $sf, $lf, $lensModel);
3349             }
3350 0         0 return "Unknown ($id)";
3351             }
3352              
3353             #------------------------------------------------------------------------------
3354             # Convert XMP date/time to EXIF format
3355             # Inputs: 0) XMP date/time string, 1) set if we aren't sure this is a date
3356             # Returns: EXIF date/time, and flag in list context if this was a standard date/time value
3357             sub ConvertXMPDate($;$)
3358             {
3359 435     435 0 984 my ($val, $unsure) = @_;
3360 435 100 66     2732 if ($val =~ /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}:\d{2})(:\d{2})?\s*(\S*)$/) {
    100          
3361 124   100     649 my $s = $5 || ''; # seconds may be missing
3362 124         1056 $val = "$1:$2:$3 $4$s$6"; # convert back to EXIF time format
3363 124 100       602 return($val, 1) if wantarray;
3364             } elsif (not $unsure and $val =~ /^(\d{4})(-\d{2}){0,2}/) {
3365 74         260 $val =~ tr/-/:/;
3366             }
3367 325         980 return $val;
3368             }
3369              
3370             #------------------------------------------------------------------------------
3371             # Convert rational string value
3372             # Inputs: 0) string (converted to number, 'inf' or 'undef' on return if rational)
3373             # Returns: true if value was converted
3374             sub ConvertRational($)
3375             {
3376 444     444 0 762 my $val = $_[0];
3377 444 100       3314 $val =~ m{^(-?\d+)/(-?\d+)$} or return undef;
3378 204 100       792 if ($2 != 0) {
    50          
3379 202         602 $_[0] = $1 / $2; # calculate quotient
3380             } elsif ($1) {
3381 0         0 $_[0] = 'inf';
3382             } else {
3383 2         5 $_[0] = 'undef';
3384             }
3385 204         574 return 1;
3386             }
3387              
3388             #------------------------------------------------------------------------------
3389             # Convert a string of floating point values to rationals
3390             # Inputs: 0) string of floating point numbers separated by spaces
3391             # Returns: string of rational numbers separated by spaces
3392             sub ConvertRationalList($)
3393             {
3394 1     1 0 6 my $val = shift;
3395 1         5 my @vals = split ' ', $val;
3396 1 50       6 return $val unless @vals == 4;
3397 1         3 foreach (@vals) {
3398 4 50       11 ConvertRational($_) or return $val;
3399             }
3400 1         16 return join ' ', @vals;
3401             }
3402              
3403             #------------------------------------------------------------------------------
3404             # We found an XMP property name/value
3405             # Inputs: 0) ExifTool object ref, 1) Pointer to tag table
3406             # 2) reference to array of XMP property names (last is current property)
3407             # 3) property value, 4) attribute hash ref (for 'xml:lang' or 'rdf:datatype')
3408             # Returns: 1 if valid tag was found
3409             sub FoundXMP($$$$;$)
3410             {
3411 3745     3745 0 5793 local $_;
3412 3745         8641 my ($et, $tagTablePtr, $props, $val, $attrs) = @_;
3413 3745         7433 my ($lang, @structProps, $rawVal, $rational);
3414 3745 100       15819 my ($tag, $ns) = GetXMPTagID($props, $$et{OPTIONS}{Struct} ? \@structProps : undef);
3415 3745 100       9559 return 0 unless $tag; # ignore things that aren't valid tags
3416              
3417             # translate namespace if necessary
3418 3728 100       10169 $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns};
3419 3728         7688 my $info = $$tagTablePtr{$ns};
3420 3728         6194 my ($table, $added, $xns, $tagID);
3421 3728 100       7951 if ($info) {
    100          
3422 3339 50       10924 $table = $$info{SubDirectory}{TagTable} or warn "Missing TagTable for $tag!\n";
3423             } elsif ($$props[0] eq 'svg:svg') {
3424 10 100       47 if (not $ns) {
    50          
3425             # disambiguate MetadataID by adding back the 'metadata' we ignored
3426 4 50 33     10 $tag = 'metadataId' if $tag eq 'id' and $$props[1] eq 'svg:metadata';
3427             # use SVG namespace in SVG files if nothing better to use
3428 4         5 $table = 'Image::ExifTool::XMP::SVG';
3429             } elsif (not grep /^rdf:/, @$props) {
3430             # only other SVG information if not inside RDF (call it XMP if in RDF)
3431 6         8 $table = 'Image::ExifTool::XMP::otherSVG';
3432             }
3433             }
3434              
3435 3728         5737 my $xmlGroups;
3436 3728         9302 my $grp0 = $$tagTablePtr{GROUPS}{0};
3437 3728 100 100     13763 if (not $ns and $grp0 ne 'XMP') {
    100 66        
3438 219         422 $tagID = $tag;
3439             } elsif ($grp0 eq 'XML' and not $table) {
3440             # this is an XML table (no namespace lookup)
3441 4         12 $tagID = "$ns:$tag";
3442             } else {
3443 3505 50       7107 $xmlGroups = 1 if $grp0 eq 'XML';
3444             # look up this tag in the appropriate table
3445 3505 100       6567 $table or $table = 'Image::ExifTool::XMP::other';
3446 3505         12844 $tagTablePtr = GetTagTable($table);
3447 3505 100       9644 if ($$tagTablePtr{NAMESPACE}) {
3448 3343         5687 $tagID = $tag;
3449             } else {
3450 162         279 $xns = $xmpNS{$ns};
3451 162 50       277 unless (defined $xns) {
3452 162         196 $xns = $ns;
3453             # validate namespace prefix
3454 162 50 33     585 unless ($ns =~ /^[A-Z_a-z\x80-\xff][-.0-9A-Z_a-z\x80-\xff]*$/ or $ns eq '') {
3455 0         0 $et->Warn("Invalid XMP namespace prefix '${ns}'");
3456             # clean up prefix for use as an ExifTool group name
3457 0         0 $ns =~ tr/-.0-9A-Z_a-z\x80-\xff//dc;
3458 0 0       0 $ns =~ /^[A-Z_a-z\x80-\xff]/ or $ns = "ns_$ns";
3459 0         0 $stdXlatNS{$xns} = $ns;
3460 0         0 $xmpNS{$ns} = $xns;
3461             }
3462             }
3463             # add XMP namespace prefix to avoid collisions in variable-namespace tables
3464 162         259 $tagID = "$xns:$tag";
3465             # add namespace to top-level structure property
3466 162 100       293 $structProps[0][0] = "$xns:" . $structProps[0][0] if @structProps;
3467             }
3468             }
3469 3728         13871 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tagID);
3470              
3471 3728 100       10447 $lang = $$attrs{'xml:lang'} if $attrs;
3472              
3473             # must add a new tag table entry if this tag isn't pre-defined
3474             # (or initialize from structure field if this is a pre-defined flattened tag)
3475             NoLoop:
3476 3728   100     16687 while (not $tagInfo or $$tagInfo{Flat}) {
3477 227         382 my (@tagList, @nsList);
3478 227         617 GetXMPTagID($props, \@tagList, \@nsList);
3479 227         446 my ($ta, $t, $ti, $addedFlat, $i, $j);
3480             # build tag ID strings for each level in the property path
3481 227         359 foreach $ta (@tagList) {
3482             # insert tag ID in index 1 of tagList list
3483 451 100       1347 $t = $$ta[1] = $t ? $t . ucfirst($$ta[0]) : $$ta[0];
3484             # generate flattened tags for top-level structure if necessary
3485 451 100       804 next if defined $addedFlat;
3486 370 100       886 $ti = $$tagTablePtr{$t} or next;
3487 48 100 66     294 next unless ref $ti eq 'HASH' and $$ti{Struct};
3488 46         664 $addedFlat = AddFlattenedTags($tagTablePtr, $t);
3489             # all done if we generated the tag we are looking for
3490 46 100 50     301 $tagInfo = $$tagTablePtr{$tagID} and last NoLoop if $addedFlat;
3491             }
3492 204         436 my $name = ucfirst($tag);
3493              
3494             # search for the innermost containing structure
3495             # (in case tag is an unknown field in a known structure)
3496             # (only necessary if we found a structure above)
3497 204 100       577 if (defined $addedFlat) {
3498 23         49 my $t2 = '';
3499 23         85 for ($i=$#tagList-1; $i>=0; --$i) {
3500 41         73 $t = $tagList[$i][1];
3501 41         96 $t2 = $tagList[$i+1][0] . ucfirst($t2); # build relative tag id
3502 41 100       124 $ti = $$tagTablePtr{$t} or next;
3503 33 50       79 next unless ref $ti eq 'HASH';
3504 33 100       109 my $strTable = $$ti{Struct} or next;
3505 23 50       84 my $flat = (defined $$ti{FlatName} ? $$ti{FlatName} : $$ti{Name});
3506 23         51 $name = $flat . ucfirst($t2);
3507             # don't continue if structure is known but field is not
3508 23 100 66     126 last if $$strTable{NAMESPACE} or not exists $$strTable{NAMESPACE};
3509             # this is a variable-namespace structure, so we must:
3510             # 1) get tagInfo from corresponding top-level XMP tag if it exists
3511             # 2) add new entry in this tag table, but with namespace prefix on tag ID
3512 22         49 my $n = $nsList[$i+1]; # namespace of structure field
3513             # translate to standard ExifTool namespace
3514 22 100       71 $n = $stdXlatNS{$n} if $stdXlatNS{$n};
3515 22   66     96 my $xn = $xmpNS{$n} || $n; # standard XMP namespace
3516             # no need to continue with variable-namespace logic if
3517             # we are in our own namespace (right?)
3518 22 50 50     74 last if $xn eq ($$tagTablePtr{NAMESPACE} || '');
3519 22         52 $tagID = "$xn:$tag"; # add namespace to avoid collisions
3520             # change structure properties to add the standard XMP namespace
3521             # prefix for this field (needed for variable-namespace fields)
3522 22 100       50 if (@structProps) {
3523 17         62 $structProps[$i+1][0] = "$xn:" . $structProps[$i+1][0];
3524             }
3525             # copy tagInfo entries from the existing top-level XMP tag
3526 22         57 my $tg = $Image::ExifTool::XMP::Main{$n};
3527 22 50 33     112 last unless ref $tg eq 'HASH' and $$tg{SubDirectory};
3528 22 50       113 my $tbl = GetTagTable($$tg{SubDirectory}{TagTable}) or last;
3529 22         82 my $sti = $et->GetTagInfo($tbl, $t2);
3530 22 50 33     110 if (not $sti or $$sti{Flat}) {
3531             # again, we must initialize flattened tags if necessary
3532             # (but don't bother to recursively apply full logic to
3533             # allow nested variable-namespace strucures until someone
3534             # actually wants to do such a silly thing)
3535 0         0 my $t3 = '';
3536 0         0 for ($j=$i+1; $j<@tagList; ++$j) {
3537 0         0 $t3 = $tagList[$j][0] . ucfirst($t3);
3538 0 0       0 my $ti3 = $$tbl{$t3} or next;
3539 0 0 0     0 next unless ref $ti3 eq 'HASH' and $$ti3{Struct};
3540 0 0       0 last unless AddFlattenedTags($tbl, $t3);
3541 0         0 $sti = $$tbl{$t2};
3542 0         0 last;
3543             }
3544 0 0       0 last unless $sti;
3545             }
3546             # use existing definition if we already added this tag
3547 22 100       61 if ($$tagTablePtr{$tagID}) {
3548 15         30 $tagInfo = $$tagTablePtr{$tagID};
3549             } else {
3550             # generate new tagInfo hash based on existing top-level tag
3551 7         143 $tagInfo = { %$sti, Name => $flat . $$sti{Name} };
3552             # be careful not to copy elements we shouldn't...
3553 7         21 delete $$tagInfo{Description}; # Description will be different
3554             # can't copy group hash because group 1 will be different and
3555             # we need to check this when writing tag to a specific group
3556 7         14 delete $$tagInfo{Groups};
3557 7 50       40 $$tagInfo{Groups}{2} = $$sti{Groups}{2} if $$sti{Groups};
3558             }
3559 22         60 last;
3560             }
3561             }
3562             # generate a default tagInfo hash if necessary
3563 204 100       409 unless ($tagInfo) {
3564             # shorten tag name if necessary
3565 181 100       389 if ($$et{ShortenXmpTags}) {
3566 27         48 my $shorten = $$et{ShortenXmpTags};
3567 27         118 $name = &$shorten($name);
3568             }
3569 181         740 $tagInfo = { Name => $name, IsDefault => 1, Priority => 0 };
3570             }
3571             # add tag Namespace entry for tags in variable-namespace tables
3572 204 100       581 $$tagInfo{Namespace} = $xns if $xns;
3573 204 100 100     1009 if ($$et{curURI}{$ns} and $$et{curURI}{$ns} =~ m{^http://ns.exiftool.(?:ca|org)/(.*?)/(.*?)/}) {
3574 84         361 my %grps = ( 0 => $1, 1 => $2 );
3575             # apply a little magic to recover original group names
3576             # from this exiftool-written RDF/XML file
3577 84 100       265 if ($grps{1} eq 'System') {
    100          
3578 7         10 $grps{1} = 'XML-System';
3579 7         10 $grps{0} = 'XML';
3580             } elsif ($grps{1} =~ /^\d/) {
3581             # URI's with only family 0 are internal tags from the source file,
3582             # so change the group name to avoid confusion with tags from this file
3583 17         28 $grps{1} = "XML-$grps{0}";
3584 17         25 $grps{0} = 'XML';
3585             }
3586 84         153 $$tagInfo{Groups} = \%grps;
3587             # flag to avoid setting group 1 later
3588 84         200 $$tagInfo{StaticGroup1} = 1;
3589             }
3590             # construct tag information for this unknown tag
3591             # -> make this a List or lang-alt tag if necessary
3592 204 100 100     1021 if (@$props > 2 and $$props[-1] =~ /^rdf:li \d+$/ and
      66        
3593             $$props[-2] =~ /^rdf:(Bag|Seq|Alt)$/)
3594             {
3595 17 100 66     91 if ($lang and $1 eq 'Alt') {
3596 12         37 $$tagInfo{Writable} = 'lang-alt';
3597             } else {
3598 5         16 $$tagInfo{List} = $1;
3599             }
3600             # tried this, but maybe not a good idea for complex structures:
3601             #} elsif (grep / /, @$props) {
3602             # $$tagInfo{List} = 1;
3603             }
3604 204 100 66     567 unless ($$tagTablePtr{$tagID} and $$tagTablePtr{$tagID} eq $tagInfo) {
3605             # save property list for verbose "adding" message unless this tag already exists
3606 188 50       420 $added = \@tagList unless $$tagTablePtr{$tagID};
3607             # if this is an empty structure, we must add a Struct field
3608 188 50 66     398 if (not length $val and $$attrs{'rdf:parseType'} and $$attrs{'rdf:parseType'} eq 'Resource') {
      33        
3609 0 0       0 $$tagInfo{Struct} = { STRUCT_NAME => 'XMP Unknown' } unless $$tagInfo{Struct};
3610             }
3611 188         333 $$tagInfo{Hidden} = 2; # (don't show in -list outputs)
3612 188         542 AddTagToTable($tagTablePtr, $tagID, $tagInfo);
3613             }
3614 204         531 last;
3615             }
3616             # decode value if necessary (et:encoding was used before exiftool 7.71)
3617 3728 100       7917 if ($attrs) {
3618 3499   33     10669 my $enc = $$attrs{'rdf:datatype'} || $$attrs{'et:encoding'};
3619 3499 50 33     8451 if ($enc and $enc =~ /base64/) {
3620 0         0 $val = DecodeBase64($val); # (now a value ref)
3621 0 0 0     0 $val = $$val unless length $$val > 100 or $$val =~ /[\0-\x08\x0b\0x0c\x0e-\x1f]/;
3622             }
3623             }
3624 3728 100 100     9472 if (defined $lang and lc($lang) ne 'x-default') {
3625 53         189 $lang = StandardLangCase($lang);
3626 53         241 my $langInfo = GetLangInfo($tagInfo, $lang);
3627 53 50       169 $tagInfo = $langInfo if $langInfo;
3628             }
3629             # un-escape XML character entities (handling CDATA)
3630 3728         12174 pos($val) = 0;
3631 3728 100       10476 if ($val =~ //sg) {
3632 9         24 my $p = pos $val;
3633             # unescape everything up to the start of the CDATA section
3634             # (the length of "<[[CDATA[]]>" is 12 characters)
3635 9         45 my $v = UnescapeXML(substr($val, 0, $p - length($1) - 12)) . $1;
3636 9         40 while ($val =~ //sg) {
3637 0         0 my $p1 = pos $val;
3638 0         0 $v .= UnescapeXML(substr($val, $p, $p1 - length($1) - 12)) . $1;
3639 0         0 $p = $p1;
3640             }
3641 9         32 $val = $v . UnescapeXML(substr($val, $p));
3642             } else {
3643 3719         8931 $val = UnescapeXML($val);
3644             }
3645             # decode from UTF8
3646 3728         13882 $val = $et->Decode($val, 'UTF8');
3647             # convert rational and date values to a more sensible format
3648 3728         7951 my $fmt = $$tagInfo{Writable};
3649 3728   66     9760 my $new = $$tagInfo{IsDefault} && $$et{OPTIONS}{XMPAutoConv};
3650 3728 100 100     11469 if ($fmt or $new) {
3651 1653         3089 $rawVal = $val; # save raw value for verbose output
3652 1653 100 100     7206 if (($new or $fmt eq 'rational') and ConvertRational($val)) {
      100        
3653 199         414 $rational = $rawVal;
3654             } else {
3655 1454         1934 my $stdDate;
3656 1454 100 100     5098 ($val, $stdDate) = ConvertXMPDate($val, $new) if $new or $fmt eq 'date';
3657 1454 100 100     3941 if ($stdDate and $added) {
3658 2         5 $$tagInfo{Groups}{2} = 'Time';
3659 2         5 $$tagInfo{PrintConv} = '$self->ConvertDateTime($val)';
3660             }
3661             }
3662 1653 0 33     4893 if ($$et{XmpValidate} and $fmt and $fmt eq 'boolean' and $val!~/^True|False$/) {
      33        
      0        
3663 0 0       0 if ($val =~ /^true|false$/) {
3664 0         0 $et->Warn("Boolean value for XMP-$ns:$$tagInfo{Name} should be capitalized",1);
3665             } else {
3666 0         0 $et->Warn(qq(Boolean value for XMP-$ns:$$tagInfo{Name} should be "True" or "False"),1);
3667             }
3668             }
3669             # protect against large binary data in unknown tags
3670 1653 50 66     4117 $$tagInfo{Binary} = 1 if $new and length($val) > 65536;
3671             }
3672 3728 100       9623 if ($$et{OPTIONS}{Verbose}) {
3673 1         9 my $tagID = join('/',@$props);
3674 1   33     9 $et->VerboseInfo($tagID, $tagInfo, Value => $rawVal || $val);
3675             }
3676             # store the value for this tag
3677 3728 50       12104 my $key = $et->FoundTag($tagInfo, $val) or return 0;
3678             # save original components of rational numbers (used when copying)
3679 3728 100       9066 $$et{TAG_EXTRA}{$key}{Rational} = $rational if defined $rational;
3680             # save structure/list information if necessary
3681 3728 100 100     11493 if (@structProps and (@structProps > 1 or defined $structProps[0][1]) and
      100        
      66        
3682             not $$et{NO_STRUCT})
3683             {
3684 329         1092 $$et{TAG_EXTRA}{$key}{Struct} = \@structProps;
3685 329         771 $$et{IsStruct} = 1;
3686             }
3687 3728 50 100     15555 if ($xmlGroups) {
    100          
3688 0         0 $et->SetGroup($key, 'XML', 0);
3689 0         0 $et->SetGroup($key, "XML-$ns", 1);
3690             } elsif ($ns and not $$tagInfo{StaticGroup1}) {
3691             # set group1 dynamically according to the namespace
3692 3392         16537 $et->SetGroup($key, "$$tagTablePtr{GROUPS}{0}-$ns");
3693             }
3694 3728 50 66     8930 if ($added and $$et{OPTIONS}{Verbose}) {
3695 0         0 my $props;
3696 0 0       0 if (@$added > 1) {
3697 0         0 $$tagInfo{Flat} = 0; # this is a flattened tag
3698 0         0 my @props = map { $$_[0] } @$added;
  0         0  
3699 0         0 $props = ' (' . join('/',@props) . ')';
3700             } else {
3701 0         0 $props = '';
3702             }
3703 0         0 my $g1 = $et->GetGroup($key, 1);
3704 0         0 $et->VPrint(0, $$et{INDENT}, "[adding $g1:$tag]$props\n");
3705             }
3706             # allow read-only subdirectories (eg. embedded base64 XMP/IPTC in NKSC files)
3707 3728 100 66     9282 if ($$tagInfo{SubDirectory} and not $$et{IsWriting}) {
3708 2         5 my $subdir = $$tagInfo{SubDirectory};
3709 2 100       11 my $dataPt = ref $$et{VALUE}{$key} ? $$et{VALUE}{$key} : \$$et{VALUE}{$key};
3710             # decode if necessary (eg. Nikon XMP-ast:XMLPackets)
3711 2 50 33     9 $dataPt = DecodeBase64($$dataPt) if $$tagInfo{Encoding} and $$tagInfo{Encoding} eq 'Base64';
3712             # process subdirectory information
3713             my %dirInfo = (
3714             DirName => $$subdir{DirName} || $$tagInfo{Name},
3715             DataPt => $dataPt,
3716             DirLen => length $$dataPt,
3717             TagInfo => $tagInfo,
3718             IgnoreProp => $$subdir{IgnoreProp}, # (allow XML to ignore specified properties)
3719 2   33     28 IsExtended => 1, # (hack to avoid Duplicate warning for embedded XMP)
3720             NoStruct => 1, # (don't try to build structures since this isn't true XMP)
3721             NoBlockSave => 1,# (don't save as a block because we already did this)
3722             );
3723 2         14 my $oldOrder = GetByteOrder();
3724 2 100       38 SetByteOrder($$subdir{ByteOrder}) if $$subdir{ByteOrder};
3725 2         8 my $oldNS = $$et{definedNS};
3726 2         5 delete $$et{definedNS};
3727 2   33     8 my $subTablePtr = GetTagTable($$subdir{TagTable}) || $tagTablePtr;
3728 2         19 $et->ProcessDirectory(\%dirInfo, $subTablePtr, $$subdir{ProcessProc});
3729 2         10 SetByteOrder($oldOrder);
3730 2         8 $$et{definedNS} = $oldNS;
3731             }
3732 3728         16980 return 1;
3733             }
3734              
3735             #------------------------------------------------------------------------------
3736             # Recursively parse nested XMP data element
3737             # Inputs: 0) ExifTool ref, 1) tag table ref, 2) XMP data ref
3738             # 3) offset to start of XMP element, 4) offset to end of XMP element
3739             # 5) reference to array of enclosing XMP property names (undef if none)
3740             # 6) reference to blank node information hash
3741             # Returns: Number of contained XMP elements
3742             sub ParseXMPElement($$$;$$$$)
3743             {
3744 7886     7886 0 12415 local $_;
3745 7886         18778 my ($et, $tagTablePtr, $dataPt, $start, $end, $propList, $blankInfo) = @_;
3746 7886         15031 my ($count, $nItems) = (0, 0);
3747 7886         14585 my $isWriting = $$et{XMP_CAPTURE};
3748 7886         14762 my $isSVG = $$et{XMP_IS_SVG};
3749 7886         10736 my $saveNS; # save xlatNS lookup if changed for the scope of this element
3750 7886         18619 my (%definedNS, %usedNS); # namespaces defined and used in this scope
3751              
3752             # get our parse procs
3753 7886         0 my ($attrProc, $foundProc);
3754 7886 100       16589 if ($$et{XMPParseOpts}) {
3755 164         284 $attrProc = $$et{XMPParseOpts}{AttrProc};
3756 164   100     346 $foundProc = $$et{XMPParseOpts}{FoundProc} || \&FoundXMP;
3757             } else {
3758 7722         15739 $foundProc = \&FoundXMP;
3759             }
3760 7886 100       16841 $start or $start = 0;
3761 7886 50       15520 $end or $end = length $$dataPt;
3762 7886 100       15285 $propList or $propList = [ ];
3763              
3764 7886         13612 my $processBlankInfo;
3765             # create empty blank node information hash if necessary
3766 7886 100       15155 $blankInfo or $blankInfo = $processBlankInfo = { Prop => { } };
3767             # keep track of current nodeID at this nesting level
3768 7886         13673 my $oldNodeID = $$blankInfo{NodeID};
3769 7886         21573 pos($$dataPt) = $start;
3770              
3771             # lookup for translating namespace prefixes
3772 7886         16957 my $xlatNS = $$et{xlatNS};
3773              
3774 7886         11618 Element: for (;;) {
3775             # all done if there isn't enough data for another element
3776             # (the smallest possible element is 4 bytes, eg. "")
3777 16251 100       36911 last if pos($$dataPt) > $end - 4;
3778             # reset nodeID before processing each element
3779 11479         20657 my $nodeID = $$blankInfo{NodeID} = $oldNodeID;
3780             # get next element
3781 11479 100 100     77936 last if $$dataPt !~ m{<([?/]?)([-\w:.\x80-\xff]+|!--)([^>]*)>}sg or pos($$dataPt) > $end;
3782             # (the only reason we match '<[?/]' is to keep from scanning past the
3783             # "
3784 8442 100       24523 next if $1;
3785 7858         25627 my ($prop, $attrs) = ($2, $3);
3786             # skip comments
3787 7858 100       19312 if ($prop eq '!--') {
3788 159 50 33     611 next if $attrs =~ /--$/ or $$dataPt =~ /-->/sg;
3789 0         0 last;
3790             }
3791 7699         12357 my $valStart = pos($$dataPt);
3792 7699         10299 my $valEnd;
3793             # only look for closing token if this is not an empty element
3794             # (empty elements end with '/', eg. )
3795 7699 100       18630 if ($attrs !~ s/\/$//) {
3796 7573         10751 my $nesting = 1;
3797 7573         11030 for (;;) {
3798             # this match fails with perl 5.6.2 (perl bug!), but it works without
3799             # the '(.*?)', so we must do it differently...
3800             # $$dataPt =~ m/(.*?)<\/$prop>/sg or last Element;
3801             # my $val2 = $1;
3802             # find next matching closing token, or the next opening token
3803             # of a nested same-named element
3804 7895 50 33     557760 if ($$dataPt !~ m{<(/?)$prop([-\w:.\x80-\xff]*)(.*?(/?))>}sg or
3805             pos($$dataPt) > $end)
3806             {
3807 0         0 $et->Warn("XMP format error (no closing tag for $prop)");
3808 0         0 last Element;
3809             }
3810 7895 100       31890 next if $2; # ignore opening properties with different names
3811 7857 100       18496 if ($1) {
3812 7713 100       16102 next if --$nesting;
3813 7573         16799 $valEnd = pos($$dataPt) - length($prop) - length($3) - 3;
3814 7573         15017 last; # this element is complete
3815             }
3816             # this is a nested opening token (or empty element)
3817 144 100       419 ++$nesting unless $4;
3818             }
3819             } else {
3820 126         215 $valEnd = $valStart;
3821             }
3822 7699         12275 $start = pos($$dataPt); # start from here the next time around
3823              
3824             # ignore specified XMP namespaces/properties
3825 7699 0 33     22565 if ($$et{EXCL_XMP_LOOKUP} and not $isWriting and $prop =~ /^(.+):(.*)/) {
      33        
3826 0   0     0 my ($ns, $nm) = (lc($stdXlatNS{$1} || $1), lc($2));
3827 0 0 0     0 if ($$et{EXCL_XMP_LOOKUP}{"xmp-$ns:all"} or $$et{EXCL_XMP_LOOKUP}{"xmp-$ns:$nm"} or
      0        
3828             $$et{EXCL_XMP_LOOKUP}{"xmp-all:$nm"})
3829             {
3830 0         0 ++$count; # (pretend we found something so we don't store as a tag value)
3831 0         0 next;
3832             }
3833             }
3834              
3835             # extract property attributes
3836 7699         13545 my ($parseResource, %attrs, @attrs);
3837             # this hangs Perl (v5.18.4) for a specific capture string [patched in ExifTool 12.98]
3838             # while ($attrs =~ m/(\S+?)\s*=\s*(['"])(.*?)\2/sg) {
3839             # 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]
3840             # while ($attrs =~ /(\S+?)\s*=\s*(['"])/g) {
3841 7699         10938 for (;;) {
3842 12154         18044 my ($attr, $quote);
3843 12154 100       22632 if (length($attrs) < 2000) { # (do it the easy way if attributes aren't stupid long)
3844 11523 100       43208 last unless $attrs =~ /(\S+?)\s*=\s*(['"])/g;
3845 3836         10760 ($attr, $quote) = ($1, $2);
3846             } else {
3847             # 13.23 patch to avoid capturing tons of garbage if XMP is corrupted
3848 631 100       2122 last unless $attrs =~ /=\s*(['"])/g;
3849 619         1176 $quote = $1;
3850 619 100       1509 my $p = pos($attrs) > 1000 ? pos($attrs) - 1000 : 0;
3851 619         1421 my $tmp = substr($attrs, $p, pos($attrs)-$p);
3852 619 50       59223 last unless $tmp =~ /(\S+)\s*=\s*$quote$/;
3853 619         1943 $attr = $1;
3854             }
3855 4455         7531 my $p0 = pos($attrs);
3856 4455 50       19778 last unless $attrs =~ /$quote/g;
3857 4455         11306 my $val = substr($attrs, $p0, pos($attrs)-$p0-1);
3858             # handle namespace prefixes (defined by xmlns:PREFIX, or used with PREFIX:tag)
3859 4455 100       14622 if ($attr =~ /(.*?):/) {
3860 3948 100       9555 if ($1 eq 'xmlns') {
3861 1602         3843 my $ns = substr($attr, 6);
3862 1602         4942 my $stdNS = $uri2ns{$val};
3863             # keep track of namespace prefixes defined in this scope (for Validate)
3864 1602 100       7768 $$et{definedNS}{$ns} = $definedNS{$ns} = 1 unless $$et{definedNS}{$ns};
3865 1602 100       3551 unless ($stdNS) {
3866 50         98 my $try = $val;
3867             # patch for Nikon NX2 URI bug for Microsoft PhotoInfo namespace
3868 50 100       182 $try =~ s{/$}{} or $try .= '/';
3869 50         74 $stdNS = $uri2ns{$try};
3870 50 50       133 if ($stdNS) {
    50          
3871 0         0 $val = $try;
3872 0         0 $et->Warn("Fixed incorrect URI for xmlns:$ns", 1);
3873             } elsif ($val =~ m(^http://ns.nikon.com/BASIC_PARAM)) {
3874 0         0 $et->OverrideFileType('NXD','application/x-nikon-nxd');
3875             } else {
3876             # look for same namespace with different version number
3877 50         95 $try = quotemeta $val; # (note: escapes slashes too)
3878 50         266 $try =~ s{\\/\d+\\\.\d+(\\/|$)}{\\/\\d+\\\.\\d+$1};
3879 50         4512 my ($good) = grep /^$try$/, keys %uri2ns;
3880 50 50       296 if ($good) {
3881 0         0 $stdNS = $uri2ns{$good};
3882 0         0 $et->VPrint(0, $$et{INDENT}, "[different $stdNS version: $val]\n");
3883             }
3884             }
3885             }
3886             # tame wild namespace prefixes (patches Microsoft stupidity)
3887 1602         2581 my $newNS;
3888 1602 100       3211 if ($stdNS) {
    50          
3889             # use standard namespace prefix if pre-defined
3890 1552 100       5022 if ($stdNS ne $ns) {
    100          
3891 209         516 $newNS = $stdNS;
3892             } elsif ($$xlatNS{$ns}) {
3893             # this prefix is re-defined to the standard prefix in this scope
3894 2         6 $newNS = '';
3895             }
3896             } elsif ($$et{curNS}{$val}) {
3897             # use a consistent prefix over the entire XMP for a given namespace URI
3898 0 0       0 $newNS = $$et{curNS}{$val} if $$et{curNS}{$val} ne $ns;
3899             } else {
3900 50         72 my $curURI = $$et{curURI};
3901 50         66 my $curNS = $$et{curNS};
3902 50         68 my $usedNS = $ns;
3903             # use unique prefixes for all namespaces across the entire XMP
3904 50 100 66     198 if ($$curURI{$ns} or $nsURI{$ns}) {
3905             # generate a temporary namespace prefix to resolve any conflict
3906 2         4 my $i = 0;
3907 2         35 ++$i while $$curURI{"tmp$i"};
3908 2         6 $newNS = $usedNS = "tmp$i";
3909             }
3910             # keep track of the namespace prefixes and URI's used in this XMP
3911 50         140 $$curNS{$val} = $usedNS;
3912 50         102 $$curURI{$usedNS} = $val;
3913             }
3914 1602 100       4165 if (defined $newNS) {
3915             # save translation used in containing scope if necessary
3916             # create new namespace translation for the scope of this element
3917 213 100       867 $saveNS or $saveNS = $xlatNS, $xlatNS = $$et{xlatNS} = { %$xlatNS };
3918 213 100       578 if (length $newNS) {
3919             # use the new namespace prefix
3920 211         531 $$xlatNS{$ns} = $newNS;
3921 211         495 $attr = 'xmlns:' . $newNS;
3922             # must go through previous attributes and change prefixes if necessary
3923 211         540 foreach (@attrs) {
3924 353 50 66     2116 next unless /(.*?):/ and $1 eq $ns and $1 ne $newNS;
      33        
3925 0         0 my $newAttr = $newNS . substr($_, length($ns));
3926 0         0 $attrs{$newAttr} = $attrs{$_};
3927 0         0 delete $attrs{$_};
3928 0         0 $_ = $newAttr;
3929             }
3930             } else {
3931 2         5 delete $$xlatNS{$ns};
3932             }
3933             }
3934             } else {
3935 2346 100       6279 $attr = $$xlatNS{$1} . substr($attr, length($1)) if $$xlatNS{$1};
3936 2346         5529 $usedNS{$1} = 1;
3937             }
3938             }
3939 4455         9076 push @attrs, $attr; # preserve order
3940 4455         12157 $attrs{$attr} = $val;
3941             }
3942 7699 100       28704 if ($prop =~ /(.*?):/) {
3943 7343         19193 $usedNS{$1} = 1;
3944             # tame wild namespace prefixes (patch for Microsoft stupidity)
3945 7343 100       19034 $prop = $$xlatNS{$1} . substr($prop, length($1)) if $$xlatNS{$1};
3946             }
3947              
3948 7699 100       23213 if ($prop eq 'rdf:li') {
    100          
    50          
3949             # impose a reasonable maximum on the number of items in a list
3950 1435 50       3460 if ($nItems == 1000) {
3951 0         0 my ($tg,$ns) = GetXMPTagID($propList);
3952 0 0       0 if ($isWriting) {
    0          
3953 0         0 $et->Warn("Excessive number of items for $ns:$tg. Processing may be slow", 1);
3954             } elsif (not $$et{OPTIONS}{IgnoreMinorErrors}) {
3955 0         0 $et->Warn("Extracted only 1000 $ns:$tg items. Ignore minor errors to extract all", 2);
3956 0         0 last;
3957             }
3958             }
3959             # add index to list items so we can keep them in order
3960             # (this also enables us to keep structure elements grouped properly
3961             # for lists of structures, like JobRef)
3962             # Note: the list index is prefixed by the number of digits so sorting
3963             # alphabetically gives the correct order while still allowing a flexible
3964             # number of digits -- this scheme allows up to 9 digits in the index,
3965             # with index numbers ranging from 0 to 999999999. The sequence is:
3966             # 10,11,12-19,210,211-299,3100,3101-3999,41000...9999999999.
3967 1435         4130 $prop .= ' ' . length($nItems) . $nItems;
3968             # reset LIST_TAGS at the start of the outtermost list
3969             # (avoids accumulating incorrectly-written elements in a correctly-written list)
3970 1435 100 100     7987 if (not $nItems and not grep /^rdf:li /, @$propList) {
3971 798         3250 $$et{LIST_TAGS} = { };
3972             }
3973 1435         2544 ++$nItems;
3974             } elsif ($prop eq 'rdf:Description') {
3975             # remove unnecessary rdf:Description elements since parseType='Resource'
3976             # is more efficient (also necessary to make property path consistent)
3977 773 100       3486 if (grep /^rdf:Description$/, @$propList) {
3978 4         7 $parseResource = 1;
3979             # set parseType so we know this is a structure
3980 4         8 $attrs{'rdf:parseType'} = 'Resource';
3981             }
3982             } elsif ($prop eq 'xmp:xmpmeta') {
3983             # patch MicrosoftPhoto unconformity
3984 0         0 $prop = 'x:xmpmeta';
3985 0 0       0 $et->Warn('Wrong namespace for xmpmeta') if $$et{XmpValidate};
3986             }
3987              
3988             # hook for special parsing of attributes
3989 7699         11168 my $val;
3990 7699 100       14732 if ($attrProc) {
3991 69         130 $val = substr($$dataPt, $valStart, $valEnd - $valStart);
3992 69 100       210 if (&$attrProc(\@attrs, \%attrs, \$prop, \$val)) {
3993             # the value was changed, so reset $valStart/$valEnd to use $val instead
3994 54         84 $valStart = $valEnd;
3995             }
3996             }
3997              
3998             # add nodeID to property path (with leading ' #') if it exists
3999 7699 100       18032 if (defined $attrs{'rdf:nodeID'}) {
4000 16         36 $nodeID = $$blankInfo{NodeID} = $attrs{'rdf:nodeID'};
4001 16         29 delete $attrs{'rdf:nodeID'};
4002 16         31 $prop .= ' #' . $nodeID;
4003 16         20 undef $parseResource; # can't ignore if this is a node
4004             }
4005              
4006             # push this property name onto our hierarchy list
4007 7699 50       21609 push @$propList, $prop unless $parseResource;
4008              
4009 7699 100       21939 if ($isSVG) {
    100          
4010             # ignore everything but top level SVG tags and metadata unless Unknown set
4011 17 50 33     75 unless ($$et{OPTIONS}{Unknown} > 1 or $$et{OPTIONS}{Verbose}) {
4012 17 50 66     88 if (@$propList > 1 and $$propList[1] !~ /\b(metadata|desc|title)$/) {
4013 0         0 pop @$propList;
4014 0         0 next;
4015             }
4016             }
4017 17 100 100     53 if ($prop eq 'svg' or $prop eq 'metadata') {
4018             # add svg namespace prefix if missing to ignore these entries in the tag name
4019 2         3 $$propList[-1] = "svg:$prop";
4020             }
4021             } elsif ($$et{XmpIgnoreProps}) { # ignore specified properties for tag name
4022 13         23 foreach (@{$$et{XmpIgnoreProps}}) {
  13         38  
4023 38 100       80 last unless @$propList;
4024 36 100       98 pop @$propList if $_ eq $$propList[0];
4025             }
4026             }
4027              
4028             # handle properties inside element attributes (RDF shorthand format):
4029             # (attributes take the form a:b='c' or a:b="c")
4030 7699         13836 my ($shortName, $shorthand, $ignored);
4031 7699         15674 foreach $shortName (@attrs) {
4032 4336 100       12688 next unless defined $attrs{$shortName};
4033 4320         6702 my $propName = $shortName;
4034 4320         6275 my ($ns, $name);
4035 4320 100       16235 if ($propName =~ /(.*?):(.*)/) {
    100          
4036 3932         7731 $ns = $1; # specified namespace
4037 3932         7181 $name = $2;
4038             } elsif ($prop =~ /(\S*?):/) {
4039 250         736 $ns = $1; # assume same namespace as parent
4040 250         451 $name = $propName;
4041 250         565 $propName = "$ns:$name"; # generate full property name
4042             } else {
4043             # a property qualifier is the only property name that may not
4044             # have a namespace, and a qualifier shouldn't have attributes,
4045             # but what the heck, let's allow this anyway
4046 138         198 $ns = '';
4047 138         183 $name = $propName;
4048             }
4049 4320 100       9320 if ($propName eq 'rdf:about') {
4050 759 100       2723 if (not $$et{XmpAbout}) {
    50          
4051 474         1640 $$et{XmpAbout} = $attrs{$shortName};
4052             } elsif ($$et{XmpAbout} ne $attrs{$shortName}) {
4053 0 0       0 if ($isWriting) {
    0          
4054 0         0 my $str = "Different 'rdf:about' attributes not handled";
4055 0 0       0 unless ($$et{WAS_WARNED}{$str}) {
4056 0         0 $et->Error($str, 1);
4057 0         0 $$et{WAS_WARNED}{$str} = 1;
4058             }
4059             } elsif ($$et{XmpValidate}) {
4060 0         0 $et->Warn("Different 'rdf:about' attributes");
4061             }
4062             }
4063             }
4064 4320 100       8227 if ($isWriting) {
4065             # keep track of our namespaces when writing
4066 1268 100       3399 if ($ns eq 'xmlns') {
    100          
4067 360         868 my $stdNS = $uri2ns{$attrs{$shortName}};
4068 360 100 100     1931 unless ($stdNS and ($stdNS eq 'x' or $stdNS eq 'iX')) {
      100        
4069 291         595 my $nsUsed = $$et{XMP_NS};
4070 291 100       1164 $$nsUsed{$name} = $attrs{$shortName} unless defined $$nsUsed{$name};
4071             }
4072 360         878 delete $attrs{$shortName}; # (handled by namespace logic)
4073 360         807 next;
4074             } elsif ($recognizedAttrs{$propName}) {
4075 211         480 next;
4076             }
4077             }
4078 3749         6788 my $shortVal = $attrs{$shortName};
4079             # Note: $prop is the containing property in this loop (not the shorthand property)
4080             # so $ignoreProp ignores all attributes of the ignored property
4081 3749 100 100     12136 if ($ignoreNamespace{$ns} or $ignoreProp{$prop} or $ignoreEtProp{$propName}) {
      100        
4082 2912         4371 $ignored = $propName;
4083             # handle special attributes (extract as tags only once if not empty)
4084 2912 100 100     9261 if (ref $recognizedAttrs{$propName} and $shortVal) {
4085 479         915 my ($tbl, $id, $name) = @{$recognizedAttrs{$propName}};
  479         1933  
4086 479         1760 my $tval = UnescapeXML($shortVal);
4087 479 100 66     3186 unless (defined $$et{VALUE}{$name} and $$et{VALUE}{$name} eq $tval) {
4088 247         1206 $et->HandleTag(GetTagTable($tbl), $id, $tval);
4089             }
4090             }
4091 2912         6857 next;
4092             }
4093 837         1768 delete $attrs{$shortName}; # don't re-use this attribute
4094 837         1542 push @$propList, $propName;
4095             # save this shorthand XMP property
4096 837 100       1950 if (defined $nodeID) {
    100          
4097 4         16 SaveBlankInfo($blankInfo, $propList, $shortVal);
4098             } elsif ($isWriting) {
4099 521         1270 CaptureXMP($et, $propList, $shortVal);
4100             } else {
4101 312 50       817 ValidateProperty($et, $propList) if $$et{XmpValidate};
4102 312         888 &$foundProc($et, $tagTablePtr, $propList, $shortVal);
4103             }
4104 837         1475 pop @$propList;
4105 837         1771 $shorthand = 1;
4106             }
4107 7699 100       16420 if ($isWriting) {
4108 1252 100 66     3737 if (ParseXMPElement($et, $tagTablePtr, $dataPt, $valStart, $valEnd,
    100          
4109             $propList, $blankInfo))
4110             {
4111             # (no value since we found more properties within this one)
4112             # set an error on any ignored attributes here, because they will be lost
4113 599 50       1190 $$et{XMP_ERROR} = "Can't handle XMP attribute '${ignored}'" if $ignored;
4114             } elsif (not $shorthand or $valEnd != $valStart) {
4115 635         1825 $val = substr($$dataPt, $valStart, $valEnd - $valStart);
4116             # remove comments and whitespace from rdf:Description only
4117 635 100       1352 if ($prop eq 'rdf:Description') {
4118 8         58 $val =~ s///g; $val =~ s/^\s+//; $val =~ s/\s+$//;
  8         32  
  8         17  
4119             }
4120 635 100       1194 if (defined $nodeID) {
4121 13         35 SaveBlankInfo($blankInfo, $propList, $val, \%attrs);
4122             } else {
4123 622         2354 CaptureXMP($et, $propList, $val, \%attrs);
4124             }
4125             }
4126             } else {
4127             # look for additional elements contained within this one
4128 6447 100 100     26798 if ($valStart == $valEnd or
4129             !ParseXMPElement($et, $tagTablePtr, $dataPt, $valStart, $valEnd,
4130             $propList, $blankInfo))
4131             {
4132 3697         5657 my $wasEmpty;
4133 3697 100       7630 unless (defined $val) {
4134 3636         9858 $val = substr($$dataPt, $valStart, $valEnd - $valStart);
4135             # remove comments and whitespace from rdf:Description only
4136 3636 100 100     8946 if ($prop eq 'rdf:Description' and $val) {
4137 16         149 $val =~ s///g; $val =~ s/^\s+//; $val =~ s/\s+$//;
  16         67  
  16         34  
4138             }
4139             # if element value is empty, take value from RDF 'value' or 'resource' attribute
4140             # (preferentially) or 'about' attribute (if no 'value' or 'resource')
4141 3636 100 100     9105 if ($val eq '' and ($attrs =~ /\brdf:(?:value|resource)=(['"])(.*?)\1/ or
      100        
4142             $attrs =~ /\brdf:about=(['"])(.*?)\1/))
4143             {
4144 18         53 $val = $2;
4145 18         33 $wasEmpty = 1;
4146             }
4147             }
4148             # there are no contained elements, so this must be a simple property value
4149             # (unless we already extracted shorthand values from this element)
4150 3697 100 100     9577 if (length $val or not $shorthand) {
4151 3680         7300 my $lastProp = $$propList[-1];
4152 3680 50       7328 $lastProp = '' unless defined $lastProp;
4153 3680 100 66     15961 if (defined $nodeID) {
    100 0        
    50 33        
4154 13         71 SaveBlankInfo($blankInfo, $propList, $val);
4155             } elsif ($lastProp eq 'rdf:type' and $wasEmpty) {
4156             # do not extract empty structure types (for now)
4157             } elsif ($lastProp =~ /^et:(desc|prt|val)$/ and ($count or $1 eq 'desc')) {
4158             # ignore et:desc, and et:val if preceded by et:prt
4159 0         0 --$count;
4160             } else {
4161 3659 50       8420 ValidateProperty($et, $propList, \%attrs) if $$et{XmpValidate};
4162 3659         10391 &$foundProc($et, $tagTablePtr, $propList, $val, \%attrs);
4163             }
4164             }
4165             }
4166             }
4167 7699 50       17844 pop @$propList unless $parseResource;
4168 7699         12112 ++$count;
4169              
4170             # validate namespace prefixes used at this level if necessary
4171 7699 50       15931 if ($$et{XmpValidate}) {
4172 0         0 foreach (sort keys %usedNS) {
4173 0 0 0     0 next if $$et{definedNS}{$_} or $_ eq 'xml';
4174 0 0       0 if (defined $$et{definedNS}{$_}) {
4175 0         0 $et->Warn("XMP namespace $_ is used out of scope");
4176             } else {
4177 0         0 $et->Warn("Undefined XMP namespace: $_");
4178             }
4179 0         0 $$et{definedNS}{$_} = -1; # (don't warn again for this namespace)
4180             }
4181             # reset namespaces that went out of scope
4182 0         0 $$et{definedNS}{$_} = 0 foreach keys %definedNS;
4183 0         0 undef %usedNS;
4184 0         0 undef %definedNS;
4185             }
4186              
4187 7699 100       14184 last if $start >= $end;
4188 7622         16795 pos($$dataPt) = $start;
4189 7622         34949 $$dataPt =~ /\G\s+/gc; # skip white space after closing token
4190             }
4191             #
4192             # process resources referenced by blank nodeID's
4193             #
4194 7886 100 100     17754 if ($processBlankInfo and %{$$blankInfo{Prop}}) {
  294         1261  
4195 4         17 ProcessBlankInfo($et, $tagTablePtr, $blankInfo, $isWriting);
4196 4         14 %$blankInfo = (); # free some memory
4197             }
4198             # restore namespace lookup from the containing scope
4199 7886 100       14461 $$et{xlatNS} = $saveNS if $saveNS;
4200              
4201 7886         32293 return $count; # return the number of elements found at this level
4202             }
4203              
4204             #------------------------------------------------------------------------------
4205             # Process XMP data
4206             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
4207             # Returns: 1 on success
4208             # Notes: The following flavours of XMP files are currently recognized:
4209             # - standard XMP with xpacket, x:xmpmeta and rdf:RDF elements
4210             # - XMP that is missing the xpacket and/or x:xmpmeta elements
4211             # - mutant Microsoft XMP with xmp:xmpmeta element
4212             # - XML files beginning with "
4213             # - SVG files that begin with "
4214             # - XMP and XML files beginning with a UTF-8 byte order mark
4215             # - UTF-8, UTF-16 and UTF-32 encoded XMP
4216             # - erroneously double-UTF8 encoded XMP
4217             # - otherwise valid files with leading XML comment
4218             sub ProcessXMP($$;$)
4219             {
4220 318     318 0 1117 my ($et, $dirInfo, $tagTablePtr) = @_;
4221 318         1107 my $dataPt = $$dirInfo{DataPt};
4222 318         1316 my ($dirStart, $dirLen, $dataLen, $double);
4223 318         0 my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG);
4224 318         619 my $rtnVal = 0;
4225 318         681 my $bom = 0;
4226 318         1795 my $path = $et->MetadataPath();
4227              
4228             # namespaces and prefixes currently in effect while parsing the file,
4229             # and lookup to translate brain-dead-Microsoft-Photo-software prefixes
4230 318         1281 $$et{curURI} = { };
4231 318         1061 $$et{curNS} = { };
4232 318         1089 $$et{xlatNS} = { };
4233 318         963 $$et{definedNS} = { };
4234 318         906 delete $$et{XmpAbout};
4235 318         741 delete $$et{XmpValidate}; # don't validate by default
4236 318         779 delete $$et{XmpValidateLangAlt};
4237              
4238             # ignore non-standard XMP while in strict MWG compatibility mode
4239 318 100 66     3042 if (($Image::ExifTool::MWG::strict or $$et{OPTIONS}{Validate}) and
      66        
      100        
      100        
      100        
4240             not ($$et{XMP_CAPTURE} or $$et{DOC_NUM}) and
4241             (($$dirInfo{DirName} || '') eq 'XMP' or $$et{FILE_TYPE} eq 'XMP'))
4242             {
4243 7 50       34 $$et{XmpValidate} = { } if $$et{OPTIONS}{Validate};
4244 7   66     72 my $nonStd = ($stdPath{$$et{FILE_TYPE}} and $path ne $stdPath{$$et{FILE_TYPE}});
4245 7 0 33     37 if ($nonStd and $Image::ExifTool::MWG::strict) {
4246 0         0 $et->Warn("Ignored non-standard XMP at $path");
4247 0         0 return 1;
4248             }
4249 7 50       36 if ($nonStd) {
    50          
4250 0         0 $et->Warn("Non-standard XMP at $path", 1);
4251             } elsif (not $$dirInfo{IsExtended}) {
4252 7 50       28 $et->Warn("Duplicate XMP at $path") if $$et{DIR_COUNT}{XMP};
4253 7   50     43 $$et{DIR_COUNT}{XMP} = ($$et{DIR_COUNT}{XMP} || 0) + 1; # count standard XMP
4254             }
4255             }
4256 318 100       1094 if ($dataPt) {
4257 233   100     1183 $dirStart = $$dirInfo{DirStart} || 0;
4258 233   66     976 $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
4259 233   66     937 $dataLen = $$dirInfo{DataLen} || length($$dataPt);
4260             # check leading BOM (may indicate double-encoded UTF)
4261 233         1197 pos($$dataPt) = $dirStart;
4262 233 50       2413 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) {
4263 0         0 $double = $1;
4264             } else {
4265             # handle UTF-16/32 XML
4266 233         675 pos($$dataPt) = $dirStart;
4267 233 50       1534 if ($$dataPt =~ /\G((\0\0)?\xfe\xff|\xff\xfe(\0\0)?|\xef\xbb\xbf)\0*<\0*\?\0*x\0*m\0*l\0* /g) {
4268 0         0 my $tmp = $1;
4269 0 0       0 $fmt = $tmp =~ /\xfe\xff/ ? 'n' : 'v';
4270 0 0       0 $fmt = uc($fmt) if $tmp =~ /\0\0/;
4271 0         0 $isXML = 1;
4272             }
4273             }
4274             } else {
4275 85         208 my ($type, $mime, $buf2, $buf3);
4276             # read information from XMP file
4277 85 50       361 my $raf = $$dirInfo{RAF} or return 0;
4278 85 100       338 $raf->Read($buff, 256) or return 0;
4279 68         296 ($buf2 = $buff) =~ tr/\0//d; # cheap conversion to UTF-8
4280             # remove leading comments if they exist (eg. ImageIngester)
4281 68         381 while ($buf2 =~ /^\s*\s+//s) {
4284             # continue with parsing if we have more than 128 bytes remaining
4285 0 0       0 next if length $buf2 > 128;
4286             } else {
4287             # don't read more than 10k when looking for the end of comment
4288 0 0       0 return 0 if length($buf2) > 10000;
4289             }
4290 0 0       0 $raf->Read($buf3, 256) or last; # read more data if available
4291 0         0 $buff .= $buf3;
4292 0         0 $buf3 =~ tr/\0//d;
4293 0         0 $buf2 .= $buf3;
4294             }
4295             # check to see if this is XMP format
4296             # (CS2 writes .XMP files without the "xpacket begin")
4297 68 100       499 if ($buf2 =~ /^\s*(<\?xpacket begin=|
4298 54         148 $hasXMP = 1;
4299             } else {
4300             # also recognize XML files and .XMP files with BOM and without x:xmpmeta
4301 14 50       147 if ($buf2 =~ /^(\xfe\xff)(<\?xml|
    50          
    100          
    50          
4302 0         0 $fmt = 'n'; # UTF-16 or 32 MM with BOM
4303             } elsif ($buf2 =~ /^(\xff\xfe)(<\?xml|
4304 0         0 $fmt = 'v'; # UTF-16 or 32 II with BOM
4305             } elsif ($buf2 =~ /^(\xef\xbb\xbf)?(<\?xml|
4306 7         15 $fmt = 0; # UTF-8 with BOM or unknown encoding without BOM
4307             } elsif ($buf2 =~ /^(\xfe\xff|\xff\xfe|\xef\xbb\xbf)(<\?xpacket begin=)/g) {
4308 0         0 $double = $1; # double-encoded UTF
4309             } else {
4310 7         26 return 0; # not recognized XMP or XML
4311             }
4312 7 50       33 $bom = 1 if $1;
4313 7 50       28 if ($2 eq '
    0          
    0          
4314 7 100 33     80 if (defined $fmt and not $fmt and $buf2 =~ /^[^\n\r]*[\n\r]+<\?aid /s) {
    50 66        
4315 1         2 undef $$et{XmpValidate}; # don't validate INX
4316 1 50       5 if ($$et{XMP_CAPTURE}) {
4317 0         0 $et->Error("ExifTool does not yet support writing of INX files");
4318 0         0 return 0;
4319             }
4320 1         2 $type = 'INX';
4321             } elsif ($buf2 =~ /
4322 0         0 $hasXMP = 1;
4323             } else {
4324 6         18 undef $$et{XmpValidate}; # don't validate XML
4325             # identify SVG images and PLIST files by DOCTYPE if available
4326 6 100       42 if ($buf2 =~ /
    100          
    50          
    0          
4327 2 50       20 if ($1 eq 'svg') {
    50          
    0          
    0          
4328 0         0 $isSVG = 1;
4329             } elsif ($1 eq 'plist') {
4330 2         17 $type = 'PLIST';
4331             } elsif ($1 eq 'REDXIF') {
4332 0         0 $type = 'RMD';
4333 0         0 $mime = 'application/xml';
4334             } elsif ($1 ne 'fcpxml') { # Final Cut Pro XML
4335 0         0 return 0;
4336             }
4337             } elsif ($buf2 =~ /]/) {
4338 1         1 $isSVG = 1;
4339             } elsif ($buf2 =~ /
4340 3         7 $isRDF = 1;
4341             } elsif ($buf2 =~ /]/) {
4342 0         0 $type = 'PLIST';
4343             }
4344             }
4345 7         20 $isXML = 1;
4346             } elsif ($2 eq '
4347 0         0 $isRDF = 1; # recognize XMP without x:xmpmeta element
4348             } elsif ($2 eq '
4349 0         0 $isSVG = $isXML = 1;
4350             }
4351 7 50 66     46 if ($isSVG and $$et{XMP_CAPTURE}) {
4352 0         0 $et->Error("ExifTool does not yet support writing of SVG images");
4353 0         0 return 0;
4354             }
4355 7 50       47 if ($buff =~ /^\0\0/) {
    50          
    50          
4356 0         0 $fmt = 'N'; # UTF-32 MM with or without BOM
4357             } elsif ($buff =~ /^..\0\0/s) {
4358 0         0 $fmt = 'V'; # UTF-32 II with or without BOM
4359             } elsif (not $fmt) {
4360 7 50       37 if ($buff =~ /^\0/) {
    50          
4361 0         0 $fmt = 'n'; # UTF-16 MM without BOM
4362             } elsif ($buff =~ /^.\0/s) {
4363 0         0 $fmt = 'v'; # UTF-16 II without BOM
4364             }
4365             }
4366             }
4367 61         129 my $size;
4368 61 100       196 if ($type) {
4369 3 100       11 if ($type eq 'PLIST') {
4370 2         7 my $ext = $$et{FILE_EXT};
4371 2 50 33     12 $type = $ext if $ext and $ext eq 'MODD';
4372 2         10 $tagTablePtr = GetTagTable('Image::ExifTool::PLIST::Main');
4373 2         10 $$dirInfo{XMPParseOpts}{FoundProc} = \&Image::ExifTool::PLIST::FoundTag;
4374             }
4375             } else {
4376 58 100 66     449 if ($isSVG) {
    50 66        
4377 1         3 $type = 'SVG';
4378             } elsif ($isXML and not $hasXMP and not $isRDF) {
4379 0         0 $type = 'XML';
4380 0         0 my $ext = $$et{FILE_EXT};
4381 0 0 0     0 $type = $ext if $ext and $ext eq 'COS'; # recognize COS by extension
4382             }
4383             }
4384 61         373 $et->SetFileType($type, $mime);
4385              
4386 61         274 my $fast = $et->Options('FastScan');
4387 61 50 33     404 return 1 if $fast and $fast == 3;
4388              
4389 61 100 100     309 if ($type and $type eq 'INX') {
4390             # brute force search for first XMP packet in INX file
4391             # start: '
4392             # end: ']]>' (22 bytes)
4393 1 50       6 $raf->Seek(0, 0) or return 0;
4394 1 50       6 $raf->Read($buff, 65536) or return 1;
4395 1         3 for (;;) {
4396 1 50       10 last if $buff =~ /
4397 0 0       0 $raf->Read($buf2, 65536) or return 1;
4398 0         0 $buff = substr($buff, -24) . $buf2;
4399             }
4400 1         7 $buff = substr($buff, pos($buff) - 15); # (discard '
4401 1         2 for (;;) {
4402 1 50       11 last if $buff =~ /<\?xpacket end="[rw]"\?>\]\]>/g;
4403 0         0 my $n = length $buff;
4404 0 0       0 $raf->Read($buf2, 65536) or $et->Warn('Missing xpacket end'), return 1;
4405 0         0 $buff .= $buf2;
4406 0         0 pos($buff) = $n - 22; # don't miss end pattern if it was split
4407             }
4408 1         4 $size = pos($buff) - 3; # (discard ']]>' and after)
4409 1         4 $buff = substr($buff, 0, $size);
4410             } else {
4411             # read the entire file
4412 60 50       290 $raf->Seek(0, 2) or return 0;
4413 60 50       219 $size = $raf->Tell() or return 0;
4414 60 50       178 $raf->Seek(0, 0) or return 0;
4415 60 50       255 $raf->Read($buff, $size) == $size or return 0;
4416             }
4417 61         147 $dataPt = \$buff;
4418 61         152 $dirStart = 0;
4419 61         225 $dirLen = $dataLen = $size;
4420             }
4421              
4422             # decode the first layer of double-encoded UTF text (if necessary)
4423 294 50       1062 if ($double) {
4424 0         0 my ($buf2, $fmt);
4425 0         0 $buff = substr($$dataPt, $dirStart + length $double); # remove leading BOM
4426 0         0 Image::ExifTool::SetWarning(undef); # clear old warning
4427 0         0 local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning;
4428             # assume that character data has been re-encoded in UTF, so re-pack
4429             # as characters and look for warnings indicating a false assumption
4430 0 0       0 if ($double eq "\xef\xbb\xbf") {
4431 0         0 require Image::ExifTool::Charset;
4432 0         0 my $uni = Image::ExifTool::Charset::Decompose(undef,$buff,'UTF8');
4433 0         0 $buf2 = pack('C*', @$uni);
4434             } else {
4435 0 0       0 if (length($double) == 2) {
4436 0 0       0 $fmt = ($double eq "\xfe\xff") ? 'n' : 'v';
4437             } else {
4438 0 0       0 $fmt = ($double eq "\0\0\xfe\xff") ? 'N' : 'V';
4439             }
4440 0         0 $buf2 = pack('C*', unpack("$fmt*",$buff));
4441             }
4442 0 0       0 if (Image::ExifTool::GetWarning()) {
4443 0 0       0 $et->Warn('Superfluous BOM at start of XMP') unless $$dirInfo{RAF};
4444 0         0 $dataPt = \$buff; # use XMP with the BOM removed
4445             } else {
4446 0         0 $et->Warn('XMP is double UTF-encoded');
4447 0         0 $dataPt = \$buf2; # use the decoded XMP
4448             }
4449 0         0 $dirStart = 0;
4450 0         0 $dirLen = $dataLen = length $$dataPt;
4451             }
4452              
4453             # extract XMP/XML as a block if specified
4454 294 100       1262 my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : 'XMP';
4455 294         1581 my $blockExtract = $et->Options('BlockExtract');
4456 294 100 66     4276 if (($$et{REQ_TAG_LOOKUP}{lc $blockName} or ($$et{TAGS_FROM_FILE} and
      100        
      100        
4457             not $$et{EXCL_TAG_LOOKUP}{lc $blockName}) or $blockExtract) and
4458             (($$et{FileType} eq 'XMP' and $blockName eq 'XMP') or
4459             ($$dirInfo{DirName} and $$dirInfo{DirName} eq $blockName)))
4460             {
4461 40   100     462 $et->FoundTag($$dirInfo{BlockInfo} || 'XMP', substr($$dataPt, $dirStart, $dirLen));
4462 40 50 33     230 return 1 if $blockExtract and $blockExtract > 1;
4463             }
4464              
4465 294 100       1119 $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
4466 294 100 66     1066 if ($et->Options('Verbose') and not $$et{XMP_CAPTURE}) {
4467 1 50       11 my $dirType = $isSVG ? 'SVG' : $$tagTablePtr{GROUPS}{1};
4468 1         9 $et->VerboseDir($dirType, 0, $dirLen);
4469             }
4470             #
4471             # convert UTF-16 or UTF-32 encoded XMP to UTF-8 if necessary
4472             #
4473 294         667 my $begin = '
4474 294         676 my $dirEnd = $dirStart + $dirLen;
4475 294         1074 pos($$dataPt) = $dirStart;
4476 294         994 delete $$et{XMP_IS_XML};
4477 294         650 delete $$et{XMP_IS_SVG};
4478 294 100 66     5984 if ($isXML or $isRDF) {
    100 66        
    100 66        
4479 7         22 $$et{XMP_IS_XML} = $isXML;
4480 7         41 $$et{XMP_IS_SVG} = $isSVG;
4481 7         21 $$et{XMP_NO_XPACKET} = 1 + $bom;
4482             } elsif ($$dataPt =~ /\G\Q$begin\E/gc) {
4483 217         690 delete $$et{XMP_NO_XPACKET};
4484             } elsif ($$dataPt =~ /
4485             pos($$dataPt) > $dirStart and pos($$dataPt) < $dirEnd)
4486             {
4487 3         10 $$et{XMP_NO_XPACKET} = 1 + $bom;
4488             } else {
4489 67         207 delete $$et{XMP_NO_XPACKET};
4490             # check for UTF-16 encoding (insert one \0 between characters)
4491 67         709 $begin = join "\0", split //, $begin;
4492             # must reset pos because it was killed by previous unsuccessful //g match
4493 67         280 pos($$dataPt) = $dirStart;
4494 67         168 my $badEnc;
4495 67 100       1173 if ($$dataPt =~ /\G(\0)?\Q$begin\E\0./sg) {
4496             # validate byte ordering by checking for U+FEFF character
4497 34 50       172 if ($1) {
4498             # should be big-endian since we had a leading \0
4499 34         103 $fmt = 'n';
4500 34 50       203 $badEnc = 1 unless $$dataPt =~ /\G\xfe\xff/g;
4501             } else {
4502 0         0 $fmt = 'v';
4503 0 0       0 $badEnc = 1 unless $$dataPt =~ /\G\0\xff\xfe/g;
4504             }
4505             } else {
4506             # check for UTF-32 encoding (with three \0's between characters)
4507 33         306 $begin =~ s/\0/\0\0\0/g;
4508 33         97 pos($$dataPt) = $dirStart;
4509 33 50       440 if ($$dataPt !~ /\G(\0\0\0)?\Q$begin\E\0\0\0./sg) {
    0          
4510 33         108 $fmt = 0; # set format to zero as indication we didn't find encoded XMP
4511             } elsif ($1) {
4512             # should be big-endian
4513 0         0 $fmt = 'N';
4514 0 0       0 $badEnc = 1 unless $$dataPt =~ /\G\0\0\xfe\xff/g;
4515             } else {
4516 0         0 $fmt = 'V';
4517 0 0       0 $badEnc = 1 unless $$dataPt =~ /\G\0\0\0\xff\xfe\0\0/g;
4518             }
4519             }
4520 67 50       274 $badEnc and $et->Warn('Invalid XMP encoding marker');
4521             }
4522             # warn if standard XMP is missing xpacket wrapper
4523 294 0 66     1200 if ($$et{XMP_NO_XPACKET} and $$et{OPTIONS}{Validate} and
      33        
      33        
      0        
      0        
4524             $stdPath{$$et{FILE_TYPE}} and $path eq $stdPath{$$et{FILE_TYPE}} and
4525             not $$dirInfo{IsExtended} and not $$et{DOC_NUM})
4526             {
4527 0         0 $et->Warn('XMP is missing xpacket wrapper', 1);
4528             }
4529 294 100       1040 if ($fmt) {
4530             # trim if necessary to avoid converting non-UTF data
4531 34 100 66     175 if ($dirStart or $dirEnd != length($$dataPt)) {
4532 33         369 $buff = substr($$dataPt, $dirStart, $dirLen);
4533 33         102 $dataPt = \$buff;
4534             }
4535             # convert into UTF-8
4536 34 50       125 if ($] >= 5.006001) {
4537 34         10348 $buff = pack('C0U*', unpack("$fmt*",$$dataPt));
4538             } else {
4539 0         0 $buff = Image::ExifTool::PackUTF8(unpack("$fmt*",$$dataPt));
4540             }
4541 34         2182 $dataPt = \$buff;
4542 34         88 $dirStart = 0;
4543 34         74 $dirLen = length $$dataPt;
4544 34         109 $dirEnd = $dirStart + $dirLen;
4545             }
4546             # avoid scanning for XMP later in case ScanForXMP is set
4547 294 100       1991 $$et{FoundXMP} = 1 if $tagTablePtr eq \%Image::ExifTool::XMP::Main;
4548              
4549             # set XMP parsing options
4550 294         1136 $$et{XMPParseOpts} = $$dirInfo{XMPParseOpts};
4551              
4552             # ignore any specified properties (XML hack)
4553 294 100       943 if ($$dirInfo{IgnoreProp}) {
4554 1         1 %ignoreProp = %{$$dirInfo{IgnoreProp}};
  1         5  
4555             } else {
4556 293         902 undef %ignoreProp;
4557             }
4558              
4559             # need to preserve list indices to be able to handle multi-dimensional lists
4560 294         684 my $keepFlat;
4561 294 100       1125 if ($$et{OPTIONS}{Struct}) {
4562 36 100       196 if ($$et{OPTIONS}{Struct} eq '2') {
4563 21         59 $keepFlat = 1; # preserve flattened tags
4564             # setting NO_LIST to 0 combines list items in a TAG_EXTRA "NoList" element
4565             # to allow them to be re-listed later if necessary. A "NoListDel" element
4566             # is also created for tags that wouldn't have existed.
4567 21         81 $$et{NO_LIST} = 0;
4568             } else {
4569 15         40 $$et{NO_LIST} = 1;
4570             }
4571             }
4572              
4573             # don't generate structures if this isn't real XMP
4574 294 100 66     2181 $$et{NO_STRUCT} = 1 if $$dirInfo{BlockInfo} or $$dirInfo{NoStruct};
4575              
4576             # parse the XMP
4577 294 50 0     1580 if (ParseXMPElement($et, $tagTablePtr, $dataPt, $dirStart, $dirEnd)) {
    0          
4578 294         779 $rtnVal = 1;
4579             } elsif ($$dirInfo{DirName} and $$dirInfo{DirName} eq 'XMP') {
4580             # if DirName was 'XMP' we expect well-formed XMP, so set Warning since it wasn't
4581             # (but allow empty XMP as written by some PhaseOne cameras)
4582 0         0 my $xmp = substr($$dataPt, $dirStart, $dirLen);
4583 0 0       0 if ($xmp =~ /^ *\0*$/) {
4584 0         0 $et->Warn('Invalid XMP');
4585             } else {
4586 0         0 $et->Warn('Empty XMP',1);
4587 0         0 $rtnVal = 1;
4588             }
4589             }
4590 294         832 delete $$et{NO_STRUCT};
4591              
4592             # return DataPt if successful in case we want it for writing
4593 294 100 66     2153 $$dirInfo{DataPt} = $dataPt if $rtnVal and $$dirInfo{RAF};
4594              
4595             # restore structures if necessary
4596 294 100       1109 if ($$et{IsStruct}) {
4597 28 50       155 unless ($$dirInfo{NoStruct}) {
4598 28         4982 require 'Image/ExifTool/XMPStruct.pl';
4599 28         227 RestoreStruct($et, $keepFlat);
4600             }
4601 28         101 delete $$et{IsStruct};
4602             }
4603             # reset NO_LIST flag (must do this _after_ RestoreStruct() above)
4604 294         680 delete $$et{NO_LIST};
4605 294         836 delete $$et{XMPParseOpts};
4606 294         1031 delete $$et{curURI};
4607 294         781 delete $$et{curNS};
4608 294         673 delete $$et{xlatNS};
4609 294         1022 delete $$et{definedNS};
4610              
4611 294         1831 return $rtnVal;
4612             }
4613              
4614              
4615             1; #end
4616              
4617             __END__