source: main/trunk/greenstone2/perllib/cpan/Image/ExifTool/XMP.pm@ 34921

Last change on this file since 34921 was 34921, checked in by anupama, 3 years ago

Committing the improvements to EmbeddedMetaPlugin's processing of Keywords vs other metadata fields. Keywords were literally stored as arrays of words rather than phrases in PDFs (at least in Diego's sample PDF), whereas other meta fields like Subjects and Creators stored them as arrays of phrases. To get both to work, Kathy updated EXIF to a newer version, to retrieve the actual EXIF values stored in the PDF. And Kathy and Dr Bainbridge came up with a new option that I added called apply_join_before_split_to_metafields that's a regex which can list the metadata fields to apply the join_before_split to and whcih previously always got applied to all metadata fields. Now it's applied to any *Keywords metafields by default, as that's the metafield we have experience of that behaves differently to the others, as it stores by word instead of phrases. Tested on Diego's sample PDF. Diego has double-checked it to works on his sample PDF too, setting the split char to ; and turning on the join_before_split and leaving apply_join_before_split_to_metafields at its default of .*Keywords. File changes are strings.properties for the tooltip, the plugin introducing the option and working with it and Kathy's EXIF updates affecting cpan/File and cpan/Image.

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