source: gs2-extensions/parallel-building/trunk/src/perllib/cpan/Image/ExifTool/XMP.pm@ 24626

Last change on this file since 24626 was 24626, checked in by jmt12, 13 years ago

An (almost) complete copy of the perllib directory from a (circa SEP2011) head checkout from Greenstone 2 trunk - in order to try and make merging in this extension a little easier later on (as there have been some major changes to buildcol.pl commited in the main trunk but not in the x64 branch)

File size: 127.2 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#
27# Notes: - Property qualifiers are handled as if they were separate
28# properties (with no associated namespace).
29#
30# - Currently, there is no special treatment of the following
31# properties which could potentially affect the extracted
32# information: xml:base, rdf:parseType (note that parseType
33# Literal isn't allowed by the XMP spec).
34#
35# - The family 2 group names will be set to 'Unknown' for any XMP
36# tags not found in the XMP or Exif tag tables.
37#------------------------------------------------------------------------------
38
39package Image::ExifTool::XMP;
40
41use strict;
42use vars qw($VERSION $AUTOLOAD @ISA @EXPORT_OK $xlatNamespace %nsURI %dateTimeInfo
43 %xmpTableDefaults %specialStruct %sDimensions %sArea %sColorant);
44use Image::ExifTool qw(:Utils);
45use Image::ExifTool::Exif;
46require Exporter;
47
48$VERSION = '2.37';
49@ISA = qw(Exporter);
50@EXPORT_OK = qw(EscapeXML UnescapeXML);
51
52sub ProcessXMP($$;$);
53sub WriteXMP($$;$);
54sub CheckXMP($$$);
55sub ParseXMPElement($$$;$$$);
56sub DecodeBase64($);
57sub SaveBlankInfo($$$;$);
58sub ProcessBlankInfo($$$;$);
59sub ValidateXMP($;$);
60sub UnescapeChar($$);
61sub AddFlattenedTags($$);
62sub FormatXMPDate($);
63sub ConvertRational($);
64
65my %curNS; # namespaces currently in effect while parsing the file
66
67# lookup for translating to ExifTool namespaces
68# Note: Use $xlatNamespace (only valid during processing) to do the translation
69my %stdXlatNS = (
70 # shorten ugly namespace prefixes
71 'Iptc4xmpCore' => 'iptcCore',
72 'Iptc4xmpExt' => 'iptcExt',
73 'photomechanic'=> 'photomech',
74 'MicrosoftPhoto' => 'microsoft',
75 'prismusagerights' => 'pur',
76);
77
78# translate ExifTool namespaces to standard XMP namespace prefixes
79my %xmpNS = (
80 # shorten ugly namespace prefixes
81 'iptcCore' => 'Iptc4xmpCore',
82 'iptcExt' => 'Iptc4xmpExt',
83 'photomechanic'=> 'photomech',
84 'microsoft' => 'MicrosoftPhoto',
85 # (prism changed their spec to now use 'pur')
86 # 'pur' => 'prismusagerights',
87);
88
89# Lookup to translate our namespace prefixes into URI's. This list need
90# not be complete, but it must contain an entry for each namespace prefix
91# (NAMESPACE) for writable tags in the XMP tables or in structures
92%nsURI = (
93 aux => 'http://ns.adobe.com/exif/1.0/aux/',
94 album => 'http://ns.adobe.com/album/1.0/',
95 cc => 'http://creativecommons.org/ns#', # changed 2007/12/21 - PH
96 crs => 'http://ns.adobe.com/camera-raw-settings/1.0/',
97 crss => 'http://ns.adobe.com/camera-raw-saved-settings/1.0/',
98 dc => 'http://purl.org/dc/elements/1.1/',
99 exif => 'http://ns.adobe.com/exif/1.0/',
100 iX => 'http://ns.adobe.com/iX/1.0/',
101 pdf => 'http://ns.adobe.com/pdf/1.3/',
102 pdfx => 'http://ns.adobe.com/pdfx/1.3/',
103 photoshop => 'http://ns.adobe.com/photoshop/1.0/',
104 rdf => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
105 rdfs => 'http://www.w3.org/2000/01/rdf-schema#',
106 stDim => 'http://ns.adobe.com/xap/1.0/sType/Dimensions#',
107 stArea => 'http://ns.adobe.com/xap/1.0/sType/Area#',
108 stEvt => 'http://ns.adobe.com/xap/1.0/sType/ResourceEvent#',
109 stFnt => 'http://ns.adobe.com/xap/1.0/sType/Font#',
110 stJob => 'http://ns.adobe.com/xap/1.0/sType/Job#',
111 stRef => 'http://ns.adobe.com/xap/1.0/sType/ResourceRef#',
112 stVer => 'http://ns.adobe.com/xap/1.0/sType/Version#',
113 stMfs => 'http://ns.adobe.com/xap/1.0/sType/ManifestItem#',
114 tiff => 'http://ns.adobe.com/tiff/1.0/',
115 'x' => 'adobe:ns:meta/',
116 xmpG => 'http://ns.adobe.com/xap/1.0/g/',
117 xmpGImg => 'http://ns.adobe.com/xap/1.0/g/img/',
118 xmp => 'http://ns.adobe.com/xap/1.0/',
119 xmpBJ => 'http://ns.adobe.com/xap/1.0/bj/',
120 xmpDM => 'http://ns.adobe.com/xmp/1.0/DynamicMedia/',
121 xmpMM => 'http://ns.adobe.com/xap/1.0/mm/',
122 xmpRights => 'http://ns.adobe.com/xap/1.0/rights/',
123 xmpNote => 'http://ns.adobe.com/xmp/note/',
124 xmpTPg => 'http://ns.adobe.com/xap/1.0/t/pg/',
125 xmpidq => 'http://ns.adobe.com/xmp/Identifier/qual/1.0/',
126 xmpPLUS => 'http://ns.adobe.com/xap/1.0/PLUS/',
127 dex => 'http://ns.optimasc.com/dex/1.0/',
128 mediapro => 'http://ns.iview-multimedia.com/mediapro/1.0/',
129 Iptc4xmpCore => 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/',
130 Iptc4xmpExt => 'http://iptc.org/std/Iptc4xmpExt/2008-02-29/',
131 MicrosoftPhoto => 'http://ns.microsoft.com/photo/1.0',
132 MP1 => 'http://ns.microsoft.com/photo/1.1', #PH (MP1 is fabricated)
133 MP => 'http://ns.microsoft.com/photo/1.2/',
134 MPRI => 'http://ns.microsoft.com/photo/1.2/t/RegionInfo#',
135 MPReg => 'http://ns.microsoft.com/photo/1.2/t/Region#',
136 lr => 'http://ns.adobe.com/lightroom/1.0/',
137 DICOM => 'http://ns.adobe.com/DICOM/',
138 svg => 'http://www.w3.org/2000/svg',
139 et => 'http://ns.exiftool.ca/1.0/',
140 # namespaces defined in XMP2.pl:
141 plus => 'http://ns.useplus.org/ldf/xmp/1.0/',
142 prism => 'http://prismstandard.org/namespaces/basic/2.1/',
143 prl => 'http://prismstandard.org/namespaces/prl/2.1/',
144 pur => 'http://prismstandard.org/namespaces/prismusagerights/2.1/',
145 acdsee => 'http://ns.acdsee.com/iptc/1.0/',
146 digiKam => 'http://www.digikam.org/ns/1.0/',
147 swf => 'http://ns.adobe.com/swf/1.0',
148 cell => 'http://developer.sonyericsson.com/cell/1.0/',
149 'mwg-rs' => 'http://www.metadataworkinggroup.com/schemas/regions/',
150 'mwg-kw' => 'http://www.metadataworkinggroup.com/schemas/keywords/',
151 'mwg-coll' => 'http://www.metadataworkinggroup.com/schemas/collections/',
152);
153
154# build reverse namespace lookup
155my %uri2ns;
156{
157 my $ns;
158 foreach $ns (keys %nsURI) {
159 $uri2ns{$nsURI{$ns}} = $ns;
160 }
161}
162
163# conversions for GPS coordinates
164sub ToDegrees
165{
166 require Image::ExifTool::GPS;
167 Image::ExifTool::GPS::ToDegrees($_[0], 1);
168}
169my %latConv = (
170 ValueConv => \&ToDegrees,
171 RawConv => 'require Image::ExifTool::GPS; $val', # to load Composite tags and routines
172 ValueConvInv => q{
173 require Image::ExifTool::GPS;
174 Image::ExifTool::GPS::ToDMS($self, $val, 2, "N");
175 },
176 PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
177 PrintConvInv => \&ToDegrees,
178);
179my %longConv = (
180 ValueConv => \&ToDegrees,
181 RawConv => 'require Image::ExifTool::GPS; $val',
182 ValueConvInv => q{
183 require Image::ExifTool::GPS;
184 Image::ExifTool::GPS::ToDMS($self, $val, 2, "E");
185 },
186 PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
187 PrintConvInv => \&ToDegrees,
188);
189%dateTimeInfo = (
190 # NOTE: Do NOT put "Groups" here because Groups hash must not be common!
191 Writable => 'date',
192 Shift => 'Time',
193 PrintConv => '$self->ConvertDateTime($val)',
194 PrintConvInv => '$self->InverseDateTime($val,undef,1)',
195);
196
197# XMP namespaces which we don't want to contribute to generated EXIF tag names
198# (Note: namespaces with non-standard prefixes aren't currently ignored)
199my %ignoreNamespace = ( 'x'=>1, rdf=>1, xmlns=>1, xml=>1, svg=>1, et=>1, office=>1 );
200
201# these are the attributes that we handle for properties that contain
202# sub-properties. Attributes for simple properties are easy, and we
203# just copy them over. These are harder since we don't store attributes
204# for properties without simple values. (maybe this will change...)
205# (special attributes are indicated by a list reference of tag information)
206my %recognizedAttrs = (
207 'rdf:about' => [ 'Image::ExifTool::XMP::rdf', 'about', 'About' ],
208 'x:xmptk' => [ 'Image::ExifTool::XMP::x', 'xmptk', 'XMPToolkit' ],
209 'x:xaptk' => [ 'Image::ExifTool::XMP::x', 'xmptk', 'XMPToolkit' ],
210 'rdf:parseType' => 1,
211 'rdf:nodeID' => 1,
212 'et:toolkit' => 1,
213 'rdf:xmlns' => 1, # this is presumably the default namespace, which we currently ignore
214);
215
216# special tags in structures below
217# NOTE: this lookup is duplicated in TagLookup.pm!!
218%specialStruct = (
219 STRUCT_NAME => 1, # [optional] name of structure
220 NAMESPACE => 1, # [mandatory] namespace prefix used for fields of this structure
221 NOTES => 1, # [optional] notes for documentation about this structure
222 TYPE => 1, # [optional] rdf:type resource for struct (if used, the StructType flag
223 # will be set automatically for all derived flattened tags when writing)
224);
225# XMP structures (each structure is similar to a tag table so we can
226# recurse through them in SetPropertyPath() as if they were tag tables)
227# The main differences between structure field information and tagInfo hashes are:
228# 1) Field information hashes do not contain Name, Groups or Table entries, and
229# 2) The TagID entry is optional, and is used only if the key in the structure hash
230# is different from the TagID (currently only true for alternate language fields)
231# 3) Field information hashes support a additional "Namespace" property.
232my %sResourceRef = (
233 STRUCT_NAME => 'ResourceRef',
234 NAMESPACE => 'stRef',
235 documentID => { },
236 instanceID => { },
237 manager => { },
238 managerVariant => { },
239 manageTo => { },
240 manageUI => { },
241 renditionClass => { },
242 renditionParams => { },
243 versionID => { },
244 # added Oct 2008
245 alternatePaths => { List => 'Seq' },
246 filePath => { },
247 fromPart => { },
248 lastModifyDate => { %dateTimeInfo, Groups => { 2 => 'Time' } },
249 maskMarkers => { PrintConv => { All => 'All', None => 'None' } },
250 partMapping => { },
251 toPart => { },
252 # added May 2010
253 originalDocumentID => { }, # (undocumented property written by Adobe InDesign)
254);
255my %sResourceEvent = (
256 STRUCT_NAME => 'ResourceEvent',
257 NAMESPACE => 'stEvt',
258 action => { },
259 instanceID => { },
260 parameters => { },
261 softwareAgent => { },
262 when => { %dateTimeInfo, Groups => { 2 => 'Time' } },
263 # added Oct 2008
264 changed => { },
265);
266my %sJobRef = (
267 STRUCT_NAME => 'JobRef',
268 NAMESPACE => 'stJob',
269 id => { },
270 name => { },
271 url => { },
272);
273my %sVersion = (
274 STRUCT_NAME => 'Version',
275 NAMESPACE => 'stVer',
276 comments => { },
277 event => { Struct => \%sResourceEvent },
278 modifier => { },
279 modifyDate => { %dateTimeInfo, Groups => { 2 => 'Time' } },
280 version => { },
281);
282my %sThumbnail = (
283 STRUCT_NAME => 'Thumbnail',
284 NAMESPACE => 'xmpGImg',
285 height => { Writable => 'integer' },
286 width => { Writable => 'integer' },
287 'format' => { },
288 image => {
289 ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
290 ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
291 },
292);
293my %sPageInfo = (
294 STRUCT_NAME => 'PageInfo',
295 NAMESPACE => 'xmpGImg',
296 PageNumber => { Writable => 'integer', Namespace => 'xmpTPg' }, # override default namespace
297 height => { Writable => 'integer' },
298 width => { Writable => 'integer' },
299 'format' => { },
300 image => {
301 ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
302 ValueConvInv => 'Image::ExifTool::XMP::EncodeBase64($val)',
303 },
304);
305#my %sIdentifierScheme = (
306# NAMESPACE => 'xmpidq',
307# Scheme => { }, # qualifier for xmp:Identifier only
308#);
309%sDimensions = (
310 STRUCT_NAME => 'Dimensions',
311 NAMESPACE => 'stDim',
312 w => { Writable => 'real' },
313 h => { Writable => 'real' },
314 unit => { },
315);
316%sArea = (
317 STRUCT_NAME => 'Area',
318 NAMESPACE => 'stArea',
319 'x' => { Writable => 'real' },
320 'y' => { Writable => 'real' },
321 w => { Writable => 'real' },
322 h => { Writable => 'real' },
323 d => { Writable => 'real' },
324 unit => { },
325);
326%sColorant = (
327 STRUCT_NAME => 'Colorant',
328 NAMESPACE => 'xmpG',
329 swatchName => { },
330 mode => { PrintConv => { CMYK=>'CMYK', RGB=>'RGB', LAB=>'Lab' } },
331 # note: do not implement closed choice for "type" because Adobe can't
332 # get the case right: spec. says "PROCESS" but Indesign writes "Process"
333 type => { },
334 cyan => { Writable => 'real' },
335 magenta => { Writable => 'real' },
336 yellow => { Writable => 'real' },
337 black => { Writable => 'real' },
338 red => { Writable => 'integer' },
339 green => { Writable => 'integer' },
340 blue => { Writable => 'integer' },
341 L => { Writable => 'real' },
342 A => { Writable => 'integer' },
343 B => { Writable => 'integer' },
344);
345my %sFont = (
346 STRUCT_NAME => 'Font',
347 NAMESPACE => 'stFnt',
348 fontName => { },
349 fontFamily => { },
350 fontFace => { },
351 fontType => { },
352 versionString => { },
353 composite => { Writable => 'boolean' },
354 fontFileName=> { },
355 childFontFiles => { List => 'Seq' },
356);
357my %sOECF = (
358 NAMESPACE => 'exif',
359 STRUCT_NAME => 'OECF',
360 Columns => { Writable => 'integer' },
361 Rows => { Writable => 'integer' },
362 Names => { List => 'Seq' },
363 Values => { List => 'Seq', Writable => 'rational' },
364);
365
366# new LR2 crs structures (PH)
367my %sCorrectionMask = (
368 STRUCT_NAME => 'CorrectionMask',
369 NAMESPACE => 'crs',
370 What => { },
371 MaskValue => { Writable => 'real' },
372 Radius => { Writable => 'real' },
373 Flow => { Writable => 'real' },
374 CenterWeight => { Writable => 'real' },
375 Dabs => { List => 'Seq' },
376 ZeroX => { Writable => 'real' },
377 ZeroY => { Writable => 'real' },
378 FullX => { Writable => 'real' },
379 FullY => { Writable => 'real' },
380);
381my %sCorrection = (
382 STRUCT_NAME => 'Correction',
383 NAMESPACE => 'crs',
384 What => { },
385 CorrectionAmount => { Writable => 'real' },
386 CorrectionActive => { Writable => 'boolean' },
387 LocalExposure => { Writable => 'real' },
388 LocalSaturation => { Writable => 'real' },
389 LocalContrast => { Writable => 'real' },
390 LocalClarity => { Writable => 'real' },
391 LocalSharpness => { Writable => 'real' },
392 LocalBrightness => { Writable => 'real' },
393 LocalToningHue => { Writable => 'real' },
394 LocalToningSaturation => { Writable => 'real' },
395 CorrectionMasks => { Struct => \%sCorrectionMask, List => 'Seq' },
396);
397
398# IPTC Extension 1.0 structures
399my %sLocationDetails = (
400 NAMESPACE => 'Iptc4xmpExt',
401 STRUCT_NAME => 'LocationDetails',
402 City => { },
403 CountryCode => { },
404 CountryName => { },
405 ProvinceState=> { },
406 Sublocation => { },
407 WorldRegion => { },
408);
409
410# main XMP tag table (tag ID's are used for the family 1 group names)
411%Image::ExifTool::XMP::Main = (
412 GROUPS => { 2 => 'Unknown' },
413 PROCESS_PROC => \&ProcessXMP,
414 WRITE_PROC => \&WriteXMP,
415 dc => {
416 Name => 'dc', # (otherwise generated name would be 'Dc')
417 SubDirectory => { TagTable => 'Image::ExifTool::XMP::dc' },
418 },
419 xmp => {
420 Name => 'xmp',
421 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmp' },
422 },
423 xmpDM => {
424 Name => 'xmpDM',
425 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpDM' },
426 },
427 xmpRights => {
428 Name => 'xmpRights',
429 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpRights' },
430 },
431 xmpNote => {
432 Name => 'xmpNote',
433 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpNote' },
434 },
435 xmpMM => {
436 Name => 'xmpMM',
437 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpMM' },
438 },
439 xmpBJ => {
440 Name => 'xmpBJ',
441 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpBJ' },
442 },
443 xmpTPg => {
444 Name => 'xmpTPg',
445 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpTPg' },
446 },
447 pdf => {
448 Name => 'pdf',
449 SubDirectory => { TagTable => 'Image::ExifTool::XMP::pdf' },
450 },
451 pdfx => {
452 Name => 'pdfx',
453 SubDirectory => { TagTable => 'Image::ExifTool::XMP::pdfx' },
454 },
455 photoshop => {
456 Name => 'photoshop',
457 SubDirectory => { TagTable => 'Image::ExifTool::XMP::photoshop' },
458 },
459 crs => {
460 Name => 'crs',
461 SubDirectory => { TagTable => 'Image::ExifTool::XMP::crs' },
462 },
463 # crss - it would be difficult to add the ability to write this
464 aux => {
465 Name => 'aux',
466 SubDirectory => { TagTable => 'Image::ExifTool::XMP::aux' },
467 },
468 tiff => {
469 Name => 'tiff',
470 SubDirectory => { TagTable => 'Image::ExifTool::XMP::tiff' },
471 },
472 exif => {
473 Name => 'exif',
474 SubDirectory => { TagTable => 'Image::ExifTool::XMP::exif' },
475 },
476 iptcCore => {
477 Name => 'iptcCore',
478 SubDirectory => { TagTable => 'Image::ExifTool::XMP::iptcCore' },
479 },
480 iptcExt => {
481 Name => 'iptcExt',
482 SubDirectory => { TagTable => 'Image::ExifTool::XMP::iptcExt' },
483 },
484 PixelLive => {
485 SubDirectory => { TagTable => 'Image::ExifTool::XMP::PixelLive' },
486 },
487 xmpPLUS => {
488 Name => 'xmpPLUS',
489 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpPLUS' },
490 },
491 plus => {
492 Name => 'plus',
493 SubDirectory => { TagTable => 'Image::ExifTool::XMP::plus' },
494 },
495 cc => {
496 Name => 'cc',
497 SubDirectory => { TagTable => 'Image::ExifTool::XMP::cc' },
498 },
499 dex => {
500 Name => 'dex',
501 SubDirectory => { TagTable => 'Image::ExifTool::XMP::dex' },
502 },
503 photomech => {
504 Name => 'photomech',
505 SubDirectory => { TagTable => 'Image::ExifTool::PhotoMechanic::XMP' },
506 },
507 mediapro => {
508 Name => 'mediapro',
509 SubDirectory => { TagTable => 'Image::ExifTool::XMP::MediaPro' },
510 },
511 microsoft => {
512 Name => 'microsoft',
513 SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::XMP' },
514 },
515 MP => {
516 Name => 'MP',
517 SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::MP' },
518 },
519 MP1 => {
520 Name => 'MP1',
521 SubDirectory => { TagTable => 'Image::ExifTool::Microsoft::MP1' },
522 },
523 lr => {
524 Name => 'lr',
525 SubDirectory => { TagTable => 'Image::ExifTool::XMP::Lightroom' },
526 },
527 DICOM => {
528 Name => 'DICOM',
529 SubDirectory => { TagTable => 'Image::ExifTool::XMP::DICOM' },
530 },
531 album => {
532 Name => 'album',
533 SubDirectory => { TagTable => 'Image::ExifTool::XMP::Album' },
534 },
535 prism => {
536 Name => 'prism',
537 SubDirectory => { TagTable => 'Image::ExifTool::XMP::prism' },
538 },
539 prl => {
540 Name => 'prl',
541 SubDirectory => { TagTable => 'Image::ExifTool::XMP::prl' },
542 },
543 pur => {
544 Name => 'pur',
545 SubDirectory => { TagTable => 'Image::ExifTool::XMP::pur' },
546 },
547 rdf => {
548 Name => 'rdf',
549 SubDirectory => { TagTable => 'Image::ExifTool::XMP::rdf' },
550 },
551 'x' => {
552 Name => 'x',
553 SubDirectory => { TagTable => 'Image::ExifTool::XMP::x' },
554 },
555 acdsee => {
556 Name => 'acdsee',
557 SubDirectory => { TagTable => 'Image::ExifTool::XMP::acdsee' },
558 },
559 digiKam => {
560 Name => 'digiKam',
561 SubDirectory => { TagTable => 'Image::ExifTool::XMP::digiKam' },
562 },
563 swf => {
564 Name => 'swf',
565 SubDirectory => { TagTable => 'Image::ExifTool::XMP::swf' },
566 },
567 cell => {
568 Name => 'cell',
569 SubDirectory => { TagTable => 'Image::ExifTool::XMP::cell' },
570 },
571 'mwg-rs' => {
572 Name => 'mwg-rs',
573 SubDirectory => { TagTable => 'Image::ExifTool::XMP::mwg_rs' },
574 },
575 'mwg-kw' => {
576 Name => 'mwg-kw',
577 SubDirectory => { TagTable => 'Image::ExifTool::XMP::mwg_kw' },
578 },
579 'mwg-coll' => {
580 Name => 'mwg-coll',
581 SubDirectory => { TagTable => 'Image::ExifTool::XMP::mwg_coll' },
582 },
583);
584
585#
586# Tag tables for all XMP schemas:
587#
588# Writable - only need to define this for writable tags if not plain text
589# (boolean, integer, rational, real, date or lang-alt)
590# List - XMP list type (Bag, Seq or Alt, or set to 1 for elements in Struct lists --
591# this is necessary to obtain proper list behaviour when reading/writing)
592#
593# (Note that family 1 group names are generated from the property namespace, not
594# the group1 names below which exist so the groups will appear in the list.)
595#
596%xmpTableDefaults = (
597 WRITE_PROC => \&WriteXMP,
598 CHECK_PROC => \&CheckXMP,
599 WRITABLE => 'string',
600 LANG_INFO => \&GetLangInfo,
601);
602
603# rdf attributes extracted
604%Image::ExifTool::XMP::rdf = (
605 %xmpTableDefaults,
606 GROUPS => { 1 => 'XMP-rdf', 2 => 'Document' },
607 NAMESPACE => 'rdf',
608 NOTES => q{
609 Most RDF attributes are handled internally, but the "about" attribute is
610 treated specially to allow it to be set to a specific value if required.
611 },
612 about => { Protected => 1 },
613);
614
615# x attributes extracted
616%Image::ExifTool::XMP::x = (
617 %xmpTableDefaults,
618 GROUPS => { 1 => 'XMP-x', 2 => 'Document' },
619 NAMESPACE => 'x',
620 NOTES => qq{
621 The "x" namespace is used for the "xmpmeta" wrapper, and may contain an
622 "xmptk" attribute that is extracted as the XMPToolkit tag. When writing,
623 the XMPToolkit tag is automatically generated by ExifTool unless
624 specifically set to another value.
625 },
626 xmptk => { Name => 'XMPToolkit', Protected => 1 },
627);
628
629# Dublin Core schema properties (dc)
630%Image::ExifTool::XMP::dc = (
631 %xmpTableDefaults,
632 GROUPS => { 1 => 'XMP-dc', 2 => 'Other' },
633 NAMESPACE => 'dc',
634 TABLE_DESC => 'XMP Dublin Core',
635 NOTES => 'Dublin Core schema tags.',
636 contributor => { Groups => { 2 => 'Author' }, List => 'Bag' },
637 coverage => { },
638 creator => { Groups => { 2 => 'Author' }, List => 'Seq' },
639 date => { Groups => { 2 => 'Time' }, List => 'Seq', %dateTimeInfo },
640 description => { Groups => { 2 => 'Image' }, Writable => 'lang-alt' },
641 'format' => { Groups => { 2 => 'Image' } },
642 identifier => { Groups => { 2 => 'Image' } },
643 language => { List => 'Bag' },
644 publisher => { Groups => { 2 => 'Author' }, List => 'Bag' },
645 relation => { List => 'Bag' },
646 rights => { Groups => { 2 => 'Author' }, Writable => 'lang-alt' },
647 source => { Groups => { 2 => 'Author' }, Avoid => 1 },
648 subject => { Groups => { 2 => 'Image' }, List => 'Bag' },
649 title => { Groups => { 2 => 'Image' }, Writable => 'lang-alt' },
650 type => { Groups => { 2 => 'Image' }, List => 'Bag' },
651);
652
653# XMP Basic schema properties (xmp, xap)
654%Image::ExifTool::XMP::xmp = (
655 %xmpTableDefaults,
656 GROUPS => { 1 => 'XMP-xmp', 2 => 'Image' },
657 NAMESPACE => 'xmp',
658 NOTES => q{
659 XMP Basic schema tags. If the older "xap", "xapBJ", "xapMM" or "xapRights"
660 namespace prefixes are found, they are translated to the newer "xmp",
661 "xmpBJ", "xmpMM" and "xmpRights" prefixes for use in family 1 group names.
662 },
663 Advisory => { List => 'Bag' }, # (deprecated)
664 BaseURL => { },
665 # (date/time tags not as reliable as EXIF)
666 CreateDate => { Groups => { 2 => 'Time' }, %dateTimeInfo, Priority => 0 },
667 CreatorTool => { },
668 Identifier => { Avoid => 1, List => 'Bag' },
669 Label => { },
670 MetadataDate=> { Groups => { 2 => 'Time' }, %dateTimeInfo },
671 ModifyDate => { Groups => { 2 => 'Time' }, %dateTimeInfo, Priority => 0 },
672 Nickname => { },
673 Rating => { Writable => 'real', Notes => 'a value from 0 to 5, or -1 for "rejected"' },
674 Thumbnails => { Struct => \%sThumbnail, List => 'Alt' },
675 ThumbnailsHeight => { Name => 'ThumbnailHeight', Flat => 1 },
676 ThumbnailsWidth => { Name => 'ThumbnailWidth', Flat => 1 },
677 ThumbnailsFormat => { Name => 'ThumbnailFormat', Flat => 1 },
678 ThumbnailsImage => { Name => 'ThumbnailImage', Flat => 1, Avoid => 1 },
679 # the following written by Adobe InDesign, not part of XMP spec:
680 PageInfo => { Struct => \%sPageInfo, List => 'Seq' },
681 PageInfoPageNumber=>{Name => 'PageImagePageNumber', Flat => 1 },
682 PageInfoHeight => { Name => 'PageImageHeight', Flat => 1 },
683 PageInfoWidth => { Name => 'PageImageWidth', Flat => 1 },
684 PageInfoFormat => { Name => 'PageImageFormat', Flat => 1 },
685 PageInfoImage => { Name => 'PageImage', Flat => 1 },
686);
687
688# XMP Rights Management schema properties (xmpRights, xapRights)
689%Image::ExifTool::XMP::xmpRights = (
690 %xmpTableDefaults,
691 GROUPS => { 1 => 'XMP-xmpRights', 2 => 'Author' },
692 NAMESPACE => 'xmpRights',
693 NOTES => 'XMP Rights Management schema tags.',
694 Certificate => { },
695 Marked => { Writable => 'boolean' },
696 Owner => { List => 'Bag' },
697 UsageTerms => { Writable => 'lang-alt' },
698 WebStatement => { },
699);
700
701# XMP Note schema properties (xmpNote)
702%Image::ExifTool::XMP::xmpNote = (
703 %xmpTableDefaults,
704 GROUPS => { 1 => 'XMP-xmpNote' },
705 NAMESPACE => 'xmpNote',
706 NOTES => 'XMP Note schema tags.',
707 HasExtendedXMP => { Writable => 'boolean', Protected => 2 },
708);
709
710# XMP xmpMM ManifestItem struct (ref PH, written by Adobe PDF library 8.0)
711my %sManifestItem = (
712 NAMESPACE => 'stMfs',
713 STRUCT_NAME => 'ManifestItem',
714 linkForm => { },
715 placedXResolution => { Namespace => 'xmpMM', Writable => 'real' },
716 placedYResolution => { Namespace => 'xmpMM', Writable => 'real' },
717 placedResolutionUnit=> { Namespace => 'xmpMM' },
718 reference => { Struct => \%sResourceRef },
719);
720
721# the xmpMM Pantry
722my %sPantryItem = (
723 NAMESPACE => undef, # stores any top-level XMP tags
724 STRUCT_NAME => 'PantryItem',
725 NOTES => q{
726 This structure must have an InstanceID field, but may also contain any other
727 XMP properties.
728 },
729 InstanceID => { Namespace => 'xmpMM' },
730);
731
732# XMP Media Management schema properties (xmpMM, xapMM)
733%Image::ExifTool::XMP::xmpMM = (
734 %xmpTableDefaults,
735 GROUPS => { 1 => 'XMP-xmpMM', 2 => 'Other' },
736 NAMESPACE => 'xmpMM',
737 TABLE_DESC => 'XMP Media Management',
738 NOTES => 'XMP Media Management schema tags.',
739 DerivedFrom => { Struct => \%sResourceRef },
740 DocumentID => { },
741 History => { Struct => \%sResourceEvent, List => 'Seq' },
742 # we treat these like list items since History is a list
743 Ingredients => { Struct => \%sResourceRef, List => 'Bag' },
744 InstanceID => { }, #PH (CS3)
745 ManagedFrom => { Struct => \%sResourceRef },
746 Manager => { Groups => { 2 => 'Author' } },
747 ManageTo => { Groups => { 2 => 'Author' } },
748 ManageUI => { },
749 ManagerVariant => { },
750 Manifest => { Struct => \%sManifestItem, List => 'Bag' },
751 OriginalDocumentID=> { },
752 Pantry => { Struct => \%sPantryItem, List => 'Bag' },
753 PreservedFileName => { }, # undocumented
754 RenditionClass => { },
755 RenditionParams => { },
756 VersionID => { },
757 Versions => { Struct => \%sVersion, List => 'Seq' },
758 LastURL => { }, # (deprecated)
759 RenditionOf => { Struct => \%sResourceRef }, # (deprecated)
760 SaveID => { Writable => 'integer' }, # (deprecated)
761);
762
763# XMP Basic Job Ticket schema properties (xmpBJ, xapBJ)
764%Image::ExifTool::XMP::xmpBJ = (
765 %xmpTableDefaults,
766 GROUPS => { 1 => 'XMP-xmpBJ', 2 => 'Other' },
767 NAMESPACE => 'xmpBJ',
768 TABLE_DESC => 'XMP Basic Job Ticket',
769 NOTES => 'XMP Basic Job Ticket schema tags.',
770 # Note: JobRef is a List of structures. To accomplish this, we set the XMP
771 # List=>'Bag', but since SubDirectory is defined, this tag isn't writable
772 # directly. Then we need to set List=>1 for the members so the Writer logic
773 # will allow us to add list items.
774 JobRef => { Struct => \%sJobRef, List => 'Bag' },
775);
776
777# XMP Paged-Text schema properties (xmpTPg)
778%Image::ExifTool::XMP::xmpTPg = (
779 %xmpTableDefaults,
780 GROUPS => { 1 => 'XMP-xmpTPg', 2 => 'Image' },
781 NAMESPACE => 'xmpTPg',
782 TABLE_DESC => 'XMP Paged-Text',
783 NOTES => 'XMP Paged-Text schema tags.',
784 MaxPageSize => { Struct => \%sDimensions },
785 NPages => { Writable => 'integer' },
786 Fonts => { Struct => \%sFont, List => 'Bag' },
787 FontsFontName => { Flat => 1, Name => 'FontName' },
788 FontsFontFamily => { Flat => 1, Name => 'FontFamily' },
789 FontsFontFace => { Flat => 1, Name => 'FontFace' },
790 FontsFontType => { Flat => 1, Name => 'FontType' },
791 FontsVersionString => { Flat => 1, Name => 'FontVersion' },
792 FontsComposite => { Flat => 1, Name => 'FontComposite' },
793 FontsFontFileName => { Flat => 1, Name => 'FontFileName' },
794 FontsChildFontFiles => { Flat => 1, Name => 'ChildFontFiles' },
795 Colorants => { Struct => \%sColorant, List => 'Seq' },
796 ColorantsSwatchName => { Flat => 1, Name => 'ColorantSwatchName' },
797 ColorantsMode => { Flat => 1, Name => 'ColorantMode' },
798 ColorantsType => { Flat => 1, Name => 'ColorantType' },
799 ColorantsCyan => { Flat => 1, Name => 'ColorantCyan' },
800 ColorantsMagenta => { Flat => 1, Name => 'ColorantMagenta' },
801 ColorantsYellow => { Flat => 1, Name => 'ColorantYellow' },
802 ColorantsBlack => { Flat => 1, Name => 'ColorantBlack' },
803 ColorantsRed => { Flat => 1, Name => 'ColorantRed' },
804 ColorantsGreen => { Flat => 1, Name => 'ColorantGreen' },
805 ColorantsBlue => { Flat => 1, Name => 'ColorantBlue' },
806 ColorantsL => { Flat => 1, Name => 'ColorantL' },
807 ColorantsA => { Flat => 1, Name => 'ColorantA' },
808 ColorantsB => { Flat => 1, Name => 'ColorantB' },
809 PlateNames => { List => 'Seq' },
810);
811
812# PDF schema properties (pdf)
813%Image::ExifTool::XMP::pdf = (
814 %xmpTableDefaults,
815 GROUPS => { 1 => 'XMP-pdf', 2 => 'Image' },
816 NAMESPACE => 'pdf',
817 TABLE_DESC => 'XMP PDF',
818 NOTES => q{
819 Adobe PDF schema tags. The official XMP specification defines only
820 Keywords, PDFVersion, Producer and Trapped. The other tags are included
821 because they have been observed in PDF files, but some are avoided when
822 writing due to name conflicts with other XMP namespaces.
823 },
824 Author => { Groups => { 2 => 'Author' } }, #PH
825 ModDate => { Groups => { 2 => 'Time' }, %dateTimeInfo }, #PH
826 CreationDate=> { Groups => { 2 => 'Time' }, %dateTimeInfo }, #PH
827 Creator => { Groups => { 2 => 'Author' }, Avoid => 1 },
828 Copyright => { Groups => { 2 => 'Author' }, Avoid => 1 }, #PH
829 Marked => { Avoid => 1, Writable => 'boolean' }, #PH
830 Subject => { Avoid => 1 },
831 Title => { Avoid => 1 },
832 Trapped => { #PH
833 # remove leading '/' from '/True' or '/False'
834 ValueConv => '$val=~s{^/}{}; $val',
835 ValueConvInv => '"/$val"',
836 PrintConv => { True => 'True', False => 'False', Unknown => 'Unknown' },
837 },
838 Keywords => { },
839 PDFVersion => { },
840 Producer => { Groups => { 2 => 'Author' } },
841);
842
843# PDF extension schema properties (pdfx)
844%Image::ExifTool::XMP::pdfx = (
845 %xmpTableDefaults,
846 GROUPS => { 1 => 'XMP-pdfx', 2 => 'Document' },
847 NAMESPACE => 'pdfx',
848 NOTES => q{
849 PDF extension tags. This namespace is used to store application-defined PDF
850 information, so there are no pre-defined tags. User-defined tags must be
851 created to enable writing of XMP-pdfx information.
852 },
853);
854
855# Photoshop schema properties (photoshop)
856%Image::ExifTool::XMP::photoshop = (
857 %xmpTableDefaults,
858 GROUPS => { 1 => 'XMP-photoshop', 2 => 'Image' },
859 NAMESPACE => 'photoshop',
860 TABLE_DESC => 'XMP Photoshop',
861 NOTES => 'Adobe Photoshop schema tags.',
862 AuthorsPosition => { Groups => { 2 => 'Author' } },
863 CaptionWriter => { Groups => { 2 => 'Author' } },
864 Category => { },
865 City => { Groups => { 2 => 'Location' } },
866 ColorMode => {
867 Writable => 'integer', # (as of July 2010 spec, courtesy of yours truly)
868 PrintConvColumns => 2,
869 PrintConv => {
870 0 => 'Bitmap',
871 1 => 'Grayscale',
872 2 => 'Indexed',
873 3 => 'RGB',
874 4 => 'CMYK',
875 7 => 'Multichannel',
876 8 => 'Duotone',
877 9 => 'Lab',
878 },
879 },
880 Country => { Groups => { 2 => 'Location' } },
881 Credit => { Groups => { 2 => 'Author' } },
882 DateCreated => { Groups => { 2 => 'Time' }, %dateTimeInfo },
883 DocumentAncestors => {
884 List => 'bag',
885 Struct => {
886 STRUCT_NAME => 'Ancestor',
887 NAMESPACE => 'photoshop',
888 AncestorID => { },
889 },
890 },
891 DocumentAncestorsAncestorID => { Name => 'DocumentAncestorID', Flat => 1 },
892 Headline => { },
893 History => { }, #PH (CS3)
894 ICCProfile => { Name => 'ICCProfileName' }, #PH
895 Instructions => { },
896 LegacyIPTCDigest=> { }, #PH
897 SidecarForExtension => { }, #PH (CS3)
898 Source => { Groups => { 2 => 'Author' } },
899 State => { Groups => { 2 => 'Location' } },
900 # the XMP spec doesn't show SupplementalCategories as a 'Bag', but
901 # that's the way Photoshop writes it [fixed in the June 2005 XMP spec].
902 # Also, it is incorrectly listed as "SupplementalCategory" in the
903 # IPTC Standard Photo Metadata docs (2008rev2 and July 2009rev1) - PH
904 SupplementalCategories => { List => 'Bag' },
905 TextLayers => {
906 List => 'seq',
907 Struct => {
908 STRUCT_NAME => 'Layer',
909 NAMESPACE => 'photoshop',
910 LayerName => { },
911 LayerText => { },
912 },
913 },
914 TextLayersLayerName => { Flat => 1, Name => 'TextLayerName' },
915 TextLayersLayerText => { Flat => 1, Name => 'TextLayerText' },
916 TransmissionReference => { },
917 Urgency => {
918 Writable => 'integer',
919 Notes => 'should be in the range 1-8 to conform with the XMP spec',
920 PrintConv => { # (same values as IPTC:Urgency)
921 0 => '0 (reserved)', # (not standard XMP)
922 1 => '1 (most urgent)',
923 2 => 2,
924 3 => 3,
925 4 => 4,
926 5 => '5 (normal urgency)',
927 6 => 6,
928 7 => 7,
929 8 => '8 (least urgent)',
930 9 => '9 (user-defined priority)', # (not standard XMP)
931 },
932 },
933);
934
935# Photoshop Camera Raw Schema properties (crs) - (ref 8,PH)
936%Image::ExifTool::XMP::crs = (
937 %xmpTableDefaults,
938 GROUPS => { 1 => 'XMP-crs', 2 => 'Image' },
939 NAMESPACE => 'crs',
940 TABLE_DESC => 'Photoshop Camera Raw Schema',
941 NOTES => 'Photoshop Camera Raw Schema tags.',
942 AlreadyApplied => { Writable => 'boolean' }, #PH (written by LightRoom beta 4.1)
943 AutoBrightness => { Writable => 'boolean' },
944 AutoContrast => { Writable => 'boolean' },
945 AutoExposure => { Writable => 'boolean' },
946 AutoShadows => { Writable => 'boolean' },
947 BlueHue => { Writable => 'integer' },
948 BlueSaturation => { Writable => 'integer' },
949 Brightness => { Writable => 'integer' },
950 CameraProfile => { },
951 ChromaticAberrationB=> { Writable => 'integer' },
952 ChromaticAberrationR=> { Writable => 'integer' },
953 ColorNoiseReduction => { Writable => 'integer' },
954 Contrast => { Writable => 'integer', Avoid => 1 },
955 Converter => { }, #PH guess (found in EXIF)
956 CropTop => { Writable => 'real' },
957 CropLeft => { Writable => 'real' },
958 CropBottom => { Writable => 'real' },
959 CropRight => { Writable => 'real' },
960 CropAngle => { Writable => 'real' },
961 CropWidth => { Writable => 'real' },
962 CropHeight => { Writable => 'real' },
963 CropUnits => {
964 Writable => 'integer',
965 PrintConv => {
966 0 => 'pixels',
967 1 => 'inches',
968 2 => 'cm',
969 },
970 },
971 Exposure => { Writable => 'real' },
972 GreenHue => { Writable => 'integer' },
973 GreenSaturation => { Writable => 'integer' },
974 HasCrop => { Writable => 'boolean' },
975 HasSettings => { Writable => 'boolean' },
976 LuminanceSmoothing => { Writable => 'integer' },
977 MoireFilter => { PrintConv => { Off=>'Off', On=>'On' } },
978 RawFileName => { },
979 RedHue => { Writable => 'integer' },
980 RedSaturation => { Writable => 'integer' },
981 Saturation => { Writable => 'integer', Avoid => 1 },
982 Shadows => { Writable => 'integer' },
983 ShadowTint => { Writable => 'integer' },
984 Sharpness => { Writable => 'integer', Avoid => 1 },
985 Smoothness => { Writable => 'integer' },
986 Temperature => { Writable => 'integer', Avoid => 1, Name => 'ColorTemperature' },
987 Tint => { Writable => 'integer' },
988 ToneCurve => { List => 'Seq' },
989 ToneCurveName => {
990 PrintConv => {
991 Linear => 'Linear',
992 'Medium Contrast' => 'Medium Contrast',
993 'Strong Contrast' => 'Strong Contrast',
994 Custom => 'Custom',
995 },
996 },
997 Version => { },
998 VignetteAmount => { Writable => 'integer' },
999 VignetteMidpoint=> { Writable => 'integer' },
1000 WhiteBalance => {
1001 Avoid => 1,
1002 PrintConv => {
1003 'As Shot' => 'As Shot',
1004 Auto => 'Auto',
1005 Daylight => 'Daylight',
1006 Cloudy => 'Cloudy',
1007 Shade => 'Shade',
1008 Tungsten => 'Tungsten',
1009 Fluorescent => 'Fluorescent',
1010 Flash => 'Flash',
1011 Custom => 'Custom',
1012 },
1013 },
1014 # new tags observed in Adobe Lightroom output - PH
1015 CameraProfileDigest => { },
1016 Clarity => { Writable => 'integer' },
1017 ConvertToGrayscale => { Writable => 'boolean' },
1018 Defringe => { Writable => 'integer' },
1019 FillLight => { Writable => 'integer' },
1020 HighlightRecovery => { Writable => 'integer' },
1021 HueAdjustmentAqua => { Writable => 'integer' },
1022 HueAdjustmentBlue => { Writable => 'integer' },
1023 HueAdjustmentGreen => { Writable => 'integer' },
1024 HueAdjustmentMagenta => { Writable => 'integer' },
1025 HueAdjustmentOrange => { Writable => 'integer' },
1026 HueAdjustmentPurple => { Writable => 'integer' },
1027 HueAdjustmentRed => { Writable => 'integer' },
1028 HueAdjustmentYellow => { Writable => 'integer' },
1029 IncrementalTemperature => { Writable => 'integer' },
1030 IncrementalTint => { Writable => 'integer' },
1031 LuminanceAdjustmentAqua => { Writable => 'integer' },
1032 LuminanceAdjustmentBlue => { Writable => 'integer' },
1033 LuminanceAdjustmentGreen => { Writable => 'integer' },
1034 LuminanceAdjustmentMagenta => { Writable => 'integer' },
1035 LuminanceAdjustmentOrange => { Writable => 'integer' },
1036 LuminanceAdjustmentPurple => { Writable => 'integer' },
1037 LuminanceAdjustmentRed => { Writable => 'integer' },
1038 LuminanceAdjustmentYellow => { Writable => 'integer' },
1039 ParametricDarks => { Writable => 'integer' },
1040 ParametricHighlights => { Writable => 'integer' },
1041 ParametricHighlightSplit => { Writable => 'integer' },
1042 ParametricLights => { Writable => 'integer' },
1043 ParametricMidtoneSplit => { Writable => 'integer' },
1044 ParametricShadows => { Writable => 'integer' },
1045 ParametricShadowSplit => { Writable => 'integer' },
1046 SaturationAdjustmentAqua => { Writable => 'integer' },
1047 SaturationAdjustmentBlue => { Writable => 'integer' },
1048 SaturationAdjustmentGreen => { Writable => 'integer' },
1049 SaturationAdjustmentMagenta => { Writable => 'integer' },
1050 SaturationAdjustmentOrange => { Writable => 'integer' },
1051 SaturationAdjustmentPurple => { Writable => 'integer' },
1052 SaturationAdjustmentRed => { Writable => 'integer' },
1053 SaturationAdjustmentYellow => { Writable => 'integer' },
1054 SharpenDetail => { Writable => 'integer' },
1055 SharpenEdgeMasking => { Writable => 'integer' },
1056 SharpenRadius => { Writable => 'real' },
1057 SplitToningBalance => { Writable => 'integer' },
1058 SplitToningHighlightHue => { Writable => 'integer' },
1059 SplitToningHighlightSaturation => { Writable => 'integer' },
1060 SplitToningShadowHue => { Writable => 'integer' },
1061 SplitToningShadowSaturation => { Writable => 'integer' },
1062 Vibrance => { Writable => 'integer' },
1063 # new tags written by LR 1.4 (not sure in what version they first appeared)
1064 GrayMixerRed => { Writable => 'integer' },
1065 GrayMixerOrange => { Writable => 'integer' },
1066 GrayMixerYellow => { Writable => 'integer' },
1067 GrayMixerGreen => { Writable => 'integer' },
1068 GrayMixerAqua => { Writable => 'integer' },
1069 GrayMixerBlue => { Writable => 'integer' },
1070 GrayMixerPurple => { Writable => 'integer' },
1071 GrayMixerMagenta => { Writable => 'integer' },
1072 RetouchInfo => { List => 'Seq' },
1073 RedEyeInfo => { List => 'Seq' },
1074 # new tags written by LR 2.0 (ref PH)
1075 CropUnit => { # was the XMP documentation wrong with "CropUnits"??
1076 Writable => 'integer',
1077 PrintConv => {
1078 0 => 'pixels',
1079 1 => 'inches',
1080 2 => 'cm',
1081 # have seen a value of 3 here! - PH
1082 },
1083 },
1084 PostCropVignetteAmount => { Writable => 'integer' },
1085 PostCropVignetteMidpoint => { Writable => 'integer' },
1086 PostCropVignetteFeather => { Writable => 'integer' },
1087 PostCropVignetteRoundness => { Writable => 'integer' },
1088 PostCropVignetteStyle => { Writable => 'integer' },
1089 # disable List behaviour of flattened Gradient/PaintBasedCorrections
1090 # because these are nested in lists and the flattened tags can't
1091 # do justice to this complex structure
1092 GradientBasedCorrections => { Struct => \%sCorrection, List => 'Seq' },
1093 GradientBasedCorrectionsWhat => {
1094 Name => 'GradientBasedCorrWhat',
1095 Flat => 1, List => 0,
1096 },
1097 GradientBasedCorrectionsCorrectionAmount => {
1098 Name => 'GradientBasedCorrAmount',
1099 Flat => 1, List => 0,
1100 },
1101 GradientBasedCorrectionsCorrectionActive => {
1102 Name => 'GradientBasedCorrActive',
1103 Flat => 1, List => 0,
1104 },
1105 GradientBasedCorrectionsLocalExposure => {
1106 Name => 'GradientBasedCorrExposure',
1107 Flat => 1, List => 0,
1108 },
1109 GradientBasedCorrectionsLocalSaturation => {
1110 Name => 'GradientBasedCorrSaturation',
1111 Flat => 1, List => 0,
1112 },
1113 GradientBasedCorrectionsLocalContrast => {
1114 Name => 'GradientBasedCorrContrast',
1115 Flat => 1, List => 0,
1116 },
1117 GradientBasedCorrectionsLocalClarity => {
1118 Name => 'GradientBasedCorrClarity',
1119 Flat => 1, List => 0,
1120 },
1121 GradientBasedCorrectionsLocalSharpness => {
1122 Name => 'GradientBasedCorrSharpness',
1123 Flat => 1, List => 0,
1124 },
1125 GradientBasedCorrectionsLocalBrightness => {
1126 Name => 'GradientBasedCorrBrightness',
1127 Flat => 1, List => 0,
1128 },
1129 GradientBasedCorrectionsLocalToningHue => {
1130 Name => 'GradientBasedCorrHue',
1131 Flat => 1, List => 0,
1132 },
1133 GradientBasedCorrectionsLocalToningSaturation => {
1134 Name => 'GradientBasedCorrSaturation',
1135 Flat => 1, List => 0,
1136 },
1137 GradientBasedCorrectionsCorrectionMasks => {
1138 Name => 'GradientBasedCorrMasks',
1139 Flat => 1
1140 },
1141 GradientBasedCorrectionsCorrectionMasksWhat => {
1142 Name => 'GradientBasedCorrMaskWhat',
1143 Flat => 1, List => 0,
1144 },
1145 GradientBasedCorrectionsCorrectionMasksMaskValue => {
1146 Name => 'GradientBasedCorrMaskValue',
1147 Flat => 1, List => 0,
1148 },
1149 GradientBasedCorrectionsCorrectionMasksRadius => {
1150 Name => 'GradientBasedCorrMaskRadius',
1151 Flat => 1, List => 0,
1152 },
1153 GradientBasedCorrectionsCorrectionMasksFlow => {
1154 Name => 'GradientBasedCorrMaskFlow',
1155 Flat => 1, List => 0,
1156 },
1157 GradientBasedCorrectionsCorrectionMasksCenterWeight => {
1158 Name => 'GradientBasedCorrMaskCenterWeight',
1159 Flat => 1, List => 0,
1160 },
1161 GradientBasedCorrectionsCorrectionMasksDabs => {
1162 Name => 'GradientBasedCorrMaskDabs',
1163 Flat => 1, List => 0,
1164 },
1165 GradientBasedCorrectionsCorrectionMasksZeroX => {
1166 Name => 'GradientBasedCorrMaskZeroX',
1167 Flat => 1, List => 0,
1168 },
1169 GradientBasedCorrectionsCorrectionMasksZeroY => {
1170 Name => 'GradientBasedCorrMaskZeroY',
1171 Flat => 1, List => 0,
1172 },
1173 GradientBasedCorrectionsCorrectionMasksFullX => {
1174 Name => 'GradientBasedCorrMaskFullX',
1175 Flat => 1, List => 0,
1176 },
1177 GradientBasedCorrectionsCorrectionMasksFullY => {
1178 Name => 'GradientBasedCorrMaskFullY',
1179 Flat => 1, List => 0,
1180 },
1181 PaintBasedCorrections => { Struct => \%sCorrection, List => 'Seq' },
1182 PaintBasedCorrectionsWhat => {
1183 Name => 'PaintCorrectionWhat',
1184 Flat => 1, List => 0,
1185 },
1186 PaintBasedCorrectionsCorrectionAmount => {
1187 Name => 'PaintCorrectionAmount',
1188 Flat => 1, List => 0,
1189 },
1190 PaintBasedCorrectionsCorrectionActive => {
1191 Name => 'PaintCorrectionActive',
1192 Flat => 1, List => 0,
1193 },
1194 PaintBasedCorrectionsLocalExposure => {
1195 Name => 'PaintCorrectionExposure',
1196 Flat => 1, List => 0,
1197 },
1198 PaintBasedCorrectionsLocalSaturation => {
1199 Name => 'PaintCorrectionSaturation',
1200 Flat => 1, List => 0,
1201 },
1202 PaintBasedCorrectionsLocalContrast => {
1203 Name => 'PaintCorrectionContrast',
1204 Flat => 1, List => 0,
1205 },
1206 PaintBasedCorrectionsLocalClarity => {
1207 Name => 'PaintCorrectionClarity',
1208 Flat => 1, List => 0,
1209 },
1210 PaintBasedCorrectionsLocalSharpness => {
1211 Name => 'PaintCorrectionSharpness',
1212 Flat => 1, List => 0,
1213 },
1214 PaintBasedCorrectionsLocalBrightness => {
1215 Name => 'PaintCorrectionBrightness',
1216 Flat => 1, List => 0,
1217 },
1218 PaintBasedCorrectionsLocalToningHue => {
1219 Name => 'PaintCorrectionHue',
1220 Flat => 1, List => 0,
1221 },
1222 PaintBasedCorrectionsLocalToningSaturation => {
1223 Name => 'PaintCorrectionSaturation',
1224 Flat => 1, List => 0,
1225 },
1226 PaintBasedCorrectionsCorrectionMasks => {
1227 Name => 'PaintBasedCorrectionMasks',
1228 Flat => 1,
1229 },
1230 PaintBasedCorrectionsCorrectionMasksWhat => {
1231 Name => 'PaintCorrectionMaskWhat',
1232 Flat => 1, List => 0,
1233 },
1234 PaintBasedCorrectionsCorrectionMasksMaskValue => {
1235 Name => 'PaintCorrectionMaskValue',
1236 Flat => 1, List => 0,
1237 },
1238 PaintBasedCorrectionsCorrectionMasksRadius => {
1239 Name => 'PaintCorrectionMaskRadius',
1240 Flat => 1, List => 0,
1241 },
1242 PaintBasedCorrectionsCorrectionMasksFlow => {
1243 Name => 'PaintCorrectionMaskFlow',
1244 Flat => 1, List => 0,
1245 },
1246 PaintBasedCorrectionsCorrectionMasksCenterWeight => {
1247 Name => 'PaintCorrectionMaskCenterWeight',
1248 Flat => 1, List => 0,
1249 },
1250 PaintBasedCorrectionsCorrectionMasksDabs => {
1251 Name => 'PaintCorrectionMaskDabs',
1252 Flat => 1, List => 0,
1253 },
1254 PaintBasedCorrectionsCorrectionMasksZeroX => {
1255 Name => 'PaintCorrectionMaskZeroX',
1256 Flat => 1, List => 0,
1257 },
1258 PaintBasedCorrectionsCorrectionMasksZeroY => {
1259 Name => 'PaintCorrectionMaskZeroY',
1260 Flat => 1, List => 0,
1261 },
1262 PaintBasedCorrectionsCorrectionMasksFullX => {
1263 Name => 'PaintCorrectionMaskFullX',
1264 Flat => 1, List => 0,
1265 },
1266 PaintBasedCorrectionsCorrectionMasksFullY => {
1267 Name => 'PaintCorrectionMaskFullY',
1268 Flat => 1, List => 0,
1269 },
1270 # new tags written by LR 3 (thanks Wolfgang Guelcker)
1271 ProcessVersion => { },
1272 LensProfileEnable => { Writable => 'integer' },
1273 LensProfileSetup => { },
1274 LensProfileName => { },
1275 LensProfileFilename => { },
1276 LensProfileDigest => { },
1277 LensProfileDistortionScale => { Writable => 'integer' },
1278 LensProfileChromaticAberrationScale => { Writable => 'integer' },
1279 LensProfileVignettingScale => { Writable => 'integer' },
1280 LensManualDistortionAmount => { Writable => 'integer' },
1281 PerspectiveVertical => { Writable => 'integer' },
1282 PerspectiveHorizontal => { Writable => 'integer' },
1283 PerspectiveRotate => { Writable => 'real' },
1284 PerspectiveScale => { Writable => 'integer' },
1285 CropConstrainToWarp => { Writable => 'integer' },
1286 LuminanceNoiseReductionDetail => { Writable => 'integer' },
1287 LuminanceNoiseReductionContrast => { Writable => 'integer' },
1288 ColorNoiseReductionDetail => { Writable => 'integer' },
1289 GrainAmount => { Writable => 'integer' },
1290 GrainSize => { Writable => 'integer' },
1291 GrainFrequency => { Writable => 'integer' },
1292);
1293
1294# Tiff schema properties (tiff)
1295%Image::ExifTool::XMP::tiff = (
1296 %xmpTableDefaults,
1297 GROUPS => { 1 => 'XMP-tiff', 2 => 'Image' },
1298 NAMESPACE => 'tiff',
1299 PRIORITY => 0, # not as reliable as actual TIFF tags
1300 TABLE_DESC => 'XMP TIFF',
1301 NOTES => 'EXIF schema for TIFF tags.',
1302 ImageWidth => { Writable => 'integer' },
1303 ImageLength => { Writable => 'integer', Name => 'ImageHeight' },
1304 BitsPerSample => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
1305 Compression => {
1306 Writable => 'integer',
1307 SeparateTable => 'EXIF Compression',
1308 PrintConv => \%Image::ExifTool::Exif::compression,
1309 },
1310 PhotometricInterpretation => {
1311 Writable => 'integer',
1312 PrintConv => \%Image::ExifTool::Exif::photometricInterpretation,
1313 },
1314 Orientation => {
1315 Writable => 'integer',
1316 PrintConv => \%Image::ExifTool::Exif::orientation,
1317 },
1318 SamplesPerPixel => { Writable => 'integer' },
1319 PlanarConfiguration => {
1320 Writable => 'integer',
1321 PrintConv => {
1322 1 => 'Chunky',
1323 2 => 'Planar',
1324 },
1325 },
1326 YCbCrSubSampling => { PrintConv => \%Image::ExifTool::JPEG::yCbCrSubSampling },
1327 YCbCrPositioning => {
1328 Writable => 'integer',
1329 PrintConv => {
1330 1 => 'Centered',
1331 2 => 'Co-sited',
1332 },
1333 },
1334 XResolution => { Writable => 'rational' },
1335 YResolution => { Writable => 'rational' },
1336 ResolutionUnit => {
1337 Writable => 'integer',
1338 Notes => 'the value 1 is not standard EXIF',
1339 PrintConv => {
1340 1 => 'None',
1341 2 => 'inches',
1342 3 => 'cm',
1343 },
1344 },
1345 TransferFunction => { Writable => 'integer', List => 'Seq' },
1346 WhitePoint => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1347 PrimaryChromaticities => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1348 YCbCrCoefficients => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1349 ReferenceBlackWhite => { Writable => 'rational', List => 'Seq', AutoSplit => 1 },
1350 DateTime => { # (EXIF tag named ModifyDate, but this exists in XMP-xmp)
1351 Description => 'Date/Time Modified',
1352 Groups => { 2 => 'Time' },
1353 %dateTimeInfo,
1354 },
1355 ImageDescription => { Writable => 'lang-alt' },
1356 Make => { Groups => { 2 => 'Camera' } },
1357 Model => { Groups => { 2 => 'Camera' }, Description => 'Camera Model Name' },
1358 Software => { },
1359 Artist => { Groups => { 2 => 'Author' } },
1360 Copyright => { Groups => { 2 => 'Author' }, Writable => 'lang-alt' },
1361 NativeDigest => { }, #PH
1362);
1363
1364# Exif schema properties (exif)
1365%Image::ExifTool::XMP::exif = (
1366 %xmpTableDefaults,
1367 GROUPS => { 1 => 'XMP-exif', 2 => 'Image' },
1368 NAMESPACE => 'exif',
1369 PRIORITY => 0, # not as reliable as actual EXIF tags
1370 NOTES => 'EXIF schema for EXIF tags.',
1371 ExifVersion => { },
1372 FlashpixVersion => { },
1373 ColorSpace => {
1374 Writable => 'integer',
1375 # (some applications incorrectly write -1 as a long integer)
1376 ValueConv => '$val == 0xffffffff ? 0xffff : $val',
1377 ValueConvInv => '$val',
1378 PrintConv => {
1379 1 => 'sRGB',
1380 2 => 'Adobe RGB',
1381 0xffff => 'Uncalibrated',
1382 },
1383 },
1384 ComponentsConfiguration => {
1385 List => 'Seq',
1386 Writable => 'integer',
1387 AutoSplit => 1,
1388 PrintConvColumns => 2,
1389 PrintConv => {
1390 0 => '-',
1391 1 => 'Y',
1392 2 => 'Cb',
1393 3 => 'Cr',
1394 4 => 'R',
1395 5 => 'G',
1396 6 => 'B',
1397 },
1398 },
1399 CompressedBitsPerPixel => { Writable => 'rational' },
1400 PixelXDimension => { Name => 'ExifImageWidth', Writable => 'integer' },
1401 PixelYDimension => { Name => 'ExifImageHeight', Writable => 'integer' },
1402 MakerNote => { },
1403 UserComment => { Writable => 'lang-alt' },
1404 RelatedSoundFile => { },
1405 DateTimeOriginal => {
1406 Description => 'Date/Time Original',
1407 Groups => { 2 => 'Time' },
1408 %dateTimeInfo,
1409 },
1410 DateTimeDigitized => { # (EXIF tag named CreateDate, but this exists in XMP-xmp)
1411 Description => 'Date/Time Digitized',
1412 Groups => { 2 => 'Time' },
1413 %dateTimeInfo,
1414 },
1415 ExposureTime => {
1416 Writable => 'rational',
1417 PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
1418 PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
1419 },
1420 FNumber => {
1421 Writable => 'rational',
1422 PrintConv => 'sprintf("%.1f",$val)',
1423 PrintConvInv => '$val',
1424 },
1425 ExposureProgram => {
1426 Groups => { 2 => 'Camera' },
1427 Writable => 'integer',
1428 PrintConv => {
1429 0 => 'Not Defined',
1430 1 => 'Manual',
1431 2 => 'Program AE',
1432 3 => 'Aperture-priority AE',
1433 4 => 'Shutter speed priority AE',
1434 5 => 'Creative (Slow speed)',
1435 6 => 'Action (High speed)',
1436 7 => 'Portrait',
1437 8 => 'Landscape',
1438 },
1439 },
1440 SpectralSensitivity => { Groups => { 2 => 'Camera' } },
1441 ISOSpeedRatings => {
1442 Name => 'ISO',
1443 Writable => 'integer',
1444 List => 'Seq',
1445 AutoSplit => 1,
1446 },
1447 OECF => {
1448 Name => 'Opto-ElectricConvFactor',
1449 Groups => { 2 => 'Camera' },
1450 Struct => \%sOECF,
1451 },
1452 OECFColumns => { Flat => 1 },
1453 OECFRows => { Flat => 1 },
1454 OECFNames => { Flat => 1 },
1455 OECFValues => { Flat => 1 },
1456 ShutterSpeedValue => {
1457 Writable => 'rational',
1458 ValueConv => 'abs($val)<100 ? 1/(2**$val) : 0',
1459 PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
1460 ValueConvInv => '$val>0 ? -log($val)/log(2) : 0',
1461 PrintConvInv => 'Image::ExifTool::Exif::ConvertFraction($val)',
1462 },
1463 ApertureValue => {
1464 Writable => 'rational',
1465 ValueConv => 'sqrt(2) ** $val',
1466 PrintConv => 'sprintf("%.1f",$val)',
1467 ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
1468 PrintConvInv => '$val',
1469 },
1470 BrightnessValue => { Writable => 'rational' },
1471 ExposureBiasValue => {
1472 Name => 'ExposureCompensation',
1473 Writable => 'rational',
1474 PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)',
1475 PrintConvInv => '$val',
1476 },
1477 MaxApertureValue => {
1478 Groups => { 2 => 'Camera' },
1479 Writable => 'rational',
1480 ValueConv => 'sqrt(2) ** $val',
1481 PrintConv => 'sprintf("%.1f",$val)',
1482 ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
1483 PrintConvInv => '$val',
1484 },
1485 SubjectDistance => {
1486 Groups => { 2 => 'Camera' },
1487 Writable => 'rational',
1488 PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"',
1489 PrintConvInv => '$val=~s/\s*m$//;$val',
1490 },
1491 MeteringMode => {
1492 Groups => { 2 => 'Camera' },
1493 Writable => 'integer',
1494 PrintConv => {
1495 1 => 'Average',
1496 2 => 'Center-weighted average',
1497 3 => 'Spot',
1498 4 => 'Multi-spot',
1499 5 => 'Multi-segment',
1500 6 => 'Partial',
1501 255 => 'Other',
1502 },
1503 },
1504 LightSource => {
1505 Groups => { 2 => 'Camera' },
1506 SeparateTable => 'EXIF LightSource',
1507 PrintConv => \%Image::ExifTool::Exif::lightSource,
1508 },
1509 Flash => {
1510 Groups => { 2 => 'Camera' },
1511 Struct => {
1512 STRUCT_NAME => 'Flash',
1513 NAMESPACE => 'exif',
1514 Fired => { Writable => 'boolean' },
1515 Return => {
1516 Writable => 'integer',
1517 PrintConv => {
1518 0 => 'No return detection',
1519 2 => 'Return not detected',
1520 3 => 'Return detected',
1521 },
1522 },
1523 Mode => {
1524 Writable => 'integer',
1525 PrintConv => {
1526 0 => 'Unknown',
1527 1 => 'On',
1528 2 => 'Off',
1529 3 => 'Auto',
1530 },
1531 },
1532 Function => { Writable => 'boolean' },
1533 RedEyeMode => { Writable => 'boolean' },
1534 },
1535 },
1536 FocalLength=> {
1537 Groups => { 2 => 'Camera' },
1538 Writable => 'rational',
1539 PrintConv => 'sprintf("%.1f mm",$val)',
1540 PrintConvInv => '$val=~s/\s*mm$//;$val',
1541 },
1542 SubjectArea => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
1543 FlashEnergy => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
1544 SpatialFrequencyResponse => {
1545 Groups => { 2 => 'Camera' },
1546 Struct => \%sOECF,
1547 },
1548 FocalPlaneXResolution => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
1549 FocalPlaneYResolution => { Groups => { 2 => 'Camera' }, Writable => 'rational' },
1550 FocalPlaneResolutionUnit => {
1551 Groups => { 2 => 'Camera' },
1552 Writable => 'integer',
1553 Notes => 'values 1, 4 and 5 are not standard EXIF',
1554 PrintConv => {
1555 1 => 'None', # (not standard EXIF)
1556 2 => 'inches',
1557 3 => 'cm',
1558 4 => 'mm', # (not standard EXIF)
1559 5 => 'um', # (not standard EXIF)
1560 },
1561 },
1562 SubjectLocation => { Writable => 'integer', List => 'Seq', AutoSplit => 1 },
1563 ExposureIndex => { Writable => 'rational' },
1564 SensingMethod => {
1565 Groups => { 2 => 'Camera' },
1566 Writable => 'integer',
1567 Notes => 'values 1 and 6 are not standard EXIF',
1568 PrintConv => {
1569 1 => 'Monochrome area', # (not standard EXIF)
1570 2 => 'One-chip color area',
1571 3 => 'Two-chip color area',
1572 4 => 'Three-chip color area',
1573 5 => 'Color sequential area',
1574 6 => 'Monochrome linear', # (not standard EXIF)
1575 7 => 'Trilinear',
1576 8 => 'Color sequential linear',
1577 },
1578 },
1579 FileSource => {
1580 Writable => 'integer',
1581 PrintConv => {
1582 1 => 'Film Scanner',
1583 2 => 'Reflection Print Scanner',
1584 3 => 'Digital Camera',
1585 }
1586 },
1587 SceneType => { Writable => 'integer', PrintConv => { 1 => 'Directly photographed' } },
1588 CFAPattern => {
1589 Struct => {
1590 STRUCT_NAME => 'CFAPattern',
1591 NAMESPACE => 'exif',
1592 Columns => { Writable => 'integer' },
1593 Rows => { Writable => 'integer' },
1594 Values => { Writable => 'integer', List => 'Seq' },
1595 },
1596 },
1597 CustomRendered => {
1598 Writable => 'integer',
1599 PrintConv => {
1600 0 => 'Normal',
1601 1 => 'Custom',
1602 },
1603 },
1604 ExposureMode => {
1605 Groups => { 2 => 'Camera' },
1606 Writable => 'integer',
1607 PrintConv => {
1608 0 => 'Auto',
1609 1 => 'Manual',
1610 2 => 'Auto bracket',
1611 },
1612 },
1613 WhiteBalance => {
1614 Groups => { 2 => 'Camera' },
1615 Writable => 'integer',
1616 PrintConv => {
1617 0 => 'Auto',
1618 1 => 'Manual',
1619 },
1620 },
1621 DigitalZoomRatio => { Writable => 'rational' },
1622 FocalLengthIn35mmFilm => {
1623 Name => 'FocalLengthIn35mmFormat',
1624 Writable => 'integer',
1625 Groups => { 2 => 'Camera' },
1626 PrintConv => '"$val mm"',
1627 PrintConvInv => '$val=~s/\s*mm$//;$val',
1628 },
1629 SceneCaptureType => {
1630 Groups => { 2 => 'Camera' },
1631 Writable => 'integer',
1632 PrintConv => {
1633 0 => 'Standard',
1634 1 => 'Landscape',
1635 2 => 'Portrait',
1636 3 => 'Night',
1637 },
1638 },
1639 GainControl => {
1640 Groups => { 2 => 'Camera' },
1641 Writable => 'integer',
1642 PrintConv => {
1643 0 => 'None',
1644 1 => 'Low gain up',
1645 2 => 'High gain up',
1646 3 => 'Low gain down',
1647 4 => 'High gain down',
1648 },
1649 },
1650 Contrast => {
1651 Groups => { 2 => 'Camera' },
1652 Writable => 'integer',
1653 PrintConv => {
1654 0 => 'Normal',
1655 1 => 'Low',
1656 2 => 'High',
1657 },
1658 PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
1659 },
1660 Saturation => {
1661 Groups => { 2 => 'Camera' },
1662 Writable => 'integer',
1663 PrintConv => {
1664 0 => 'Normal',
1665 1 => 'Low',
1666 2 => 'High',
1667 },
1668 PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
1669 },
1670 Sharpness => {
1671 Groups => { 2 => 'Camera' },
1672 Writable => 'integer',
1673 PrintConv => {
1674 0 => 'Normal',
1675 1 => 'Soft',
1676 2 => 'Hard',
1677 },
1678 PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
1679 },
1680 DeviceSettingDescription => {
1681 Groups => { 2 => 'Camera' },
1682 Struct => {
1683 STRUCT_NAME => 'DeviceSettings',
1684 NAMESPACE => 'exif',
1685 Columns => { Writable => 'integer' },
1686 Rows => { Writable => 'integer' },
1687 Settings => { List => 'Seq' },
1688 },
1689 },
1690 SubjectDistanceRange => {
1691 Groups => { 2 => 'Camera' },
1692 Writable => 'integer',
1693 PrintConv => {
1694 0 => 'Unknown',
1695 1 => 'Macro',
1696 2 => 'Close',
1697 3 => 'Distant',
1698 },
1699 },
1700 ImageUniqueID => { },
1701 GPSVersionID => { Groups => { 2 => 'Location' } },
1702 GPSLatitude => { Groups => { 2 => 'Location' }, %latConv },
1703 GPSLongitude => { Groups => { 2 => 'Location' }, %longConv },
1704 GPSAltitudeRef => {
1705 Groups => { 2 => 'Location' },
1706 Writable => 'integer',
1707 PrintConv => {
1708 0 => 'Above Sea Level',
1709 1 => 'Below Sea Level',
1710 },
1711 },
1712 GPSAltitude => {
1713 Groups => { 2 => 'Location' },
1714 Writable => 'rational',
1715 RawConv => 'require Image::ExifTool::GPS; $val', # to load Composite tags and routines
1716 # extricate unsigned decimal number from string
1717 ValueConvInv => '$val=~/((?=\d|\.\d)\d*(?:\.\d*)?)/ ? $1 : undef',
1718 PrintConv => '$val =~ /^(inf|undef)$/ ? $val : "$val m"',
1719 PrintConvInv => '$val=~s/\s*m$//;$val',
1720 },
1721 GPSTimeStamp => {
1722 Name => 'GPSDateTime',
1723 Description => 'GPS Date/Time',
1724 Groups => { 2 => 'Time' },
1725 Notes => q{
1726 a date/time tag called GPSTimeStamp by the XMP specification. This tag is
1727 renamed here to prevent direct copy from EXIF:GPSTimeStamp which is a
1728 time-only tag. Instead, the value of this tag should be taken from
1729 Composite:GPSDateTime when copying from EXIF
1730 },
1731 %dateTimeInfo,
1732 },
1733 GPSSatellites => { Groups => { 2 => 'Location' } },
1734 GPSStatus => {
1735 Groups => { 2 => 'Location' },
1736 PrintConv => {
1737 A => 'Measurement Active',
1738 V => 'Measurement Void',
1739 },
1740 },
1741 GPSMeasureMode => {
1742 Groups => { 2 => 'Location' },
1743 Writable => 'integer',
1744 PrintConv => {
1745 2 => '2-Dimensional',
1746 3 => '3-Dimensional',
1747 },
1748 },
1749 GPSDOP => { Groups => { 2 => 'Location' }, Writable => 'rational' },
1750 GPSSpeedRef => {
1751 Groups => { 2 => 'Location' },
1752 PrintConv => {
1753 K => 'km/h',
1754 M => 'mph',
1755 N => 'knots',
1756 },
1757 },
1758 GPSSpeed => { Groups => { 2 => 'Location' }, Writable => 'rational' },
1759 GPSTrackRef => {
1760 Groups => { 2 => 'Location' },
1761 PrintConv => {
1762 M => 'Magnetic North',
1763 T => 'True North',
1764 },
1765 },
1766 GPSTrack => { Groups => { 2 => 'Location' }, Writable => 'rational' },
1767 GPSImgDirectionRef => {
1768 PrintConv => {
1769 M => 'Magnetic North',
1770 T => 'True North',
1771 },
1772 },
1773 GPSImgDirection => { Groups => { 2 => 'Location' }, Writable => 'rational' },
1774 GPSMapDatum => { Groups => { 2 => 'Location' } },
1775 GPSDestLatitude => { Groups => { 2 => 'Location' }, %latConv },
1776 GPSDestLongitude=> { Groups => { 2 => 'Location' }, %longConv },
1777 GPSDestBearingRef => {
1778 Groups => { 2 => 'Location' },
1779 PrintConv => {
1780 M => 'Magnetic North',
1781 T => 'True North',
1782 },
1783 },
1784 GPSDestBearing => { Groups => { 2 => 'Location' }, Writable => 'rational' },
1785 GPSDestDistanceRef => {
1786 Groups => { 2 => 'Location' },
1787 PrintConv => {
1788 K => 'Kilometers',
1789 M => 'Miles',
1790 N => 'Nautical Miles',
1791 },
1792 },
1793 GPSDestDistance => {
1794 Groups => { 2 => 'Location' },
1795 Writable => 'rational',
1796 },
1797 GPSProcessingMethod => { Groups => { 2 => 'Location' } },
1798 GPSAreaInformation => { Groups => { 2 => 'Location' } },
1799 GPSDifferential => {
1800 Groups => { 2 => 'Location' },
1801 Writable => 'integer',
1802 PrintConv => {
1803 0 => 'No Correction',
1804 1 => 'Differential Corrected',
1805 },
1806 },
1807 NativeDigest => { }, #PH
1808);
1809
1810# Auxiliary schema properties (aux) - not fully documented (ref PH)
1811%Image::ExifTool::XMP::aux = (
1812 %xmpTableDefaults,
1813 GROUPS => { 1 => 'XMP-aux', 2 => 'Camera' },
1814 NAMESPACE => 'aux',
1815 NOTES => 'Photoshop Auxiliary schema tags.',
1816 Firmware => { }, #7
1817 FlashCompensation => { Writable => 'rational' }, #7
1818 ImageNumber => { }, #7
1819 LensInfo => { #7
1820 Notes => '4 rational values giving focal and aperture ranges',
1821 # convert to floating point values (or 'inf' or 'undef')
1822 ValueConv => sub {
1823 my $val = shift;
1824 my @vals = split ' ', $val;
1825 return $val unless @vals == 4;
1826 foreach (@vals) {
1827 ConvertRational($_) or return $val;
1828 }
1829 return join ' ', @vals;
1830 },
1831 ValueConvInv => sub {
1832 my $val = shift;
1833 my @vals = split ' ', $val;
1834 return $val unless @vals == 4;
1835 foreach (@vals) {
1836 $_ eq 'inf' and $_ = '1/0', next;
1837 $_ eq 'undef' and $_ = '0/0', next;
1838 Image::ExifTool::IsFloat($_) or return $val;
1839 my @a = Image::ExifTool::Rationalize($_);
1840 $_ = join '/', @a;
1841 }
1842 return join ' ', @vals;
1843 },
1844 # convert to the form "12-20mm f/3.8-4.5" or "50mm f/1.4"
1845 PrintConv => \&Image::ExifTool::Exif::PrintLensInfo,
1846 PrintConvInv => \&Image::ExifTool::Exif::ConvertLensInfo,
1847 },
1848 Lens => { },
1849 OwnerName => { }, #7
1850 SerialNumber => { },
1851 LensID => {
1852 Priority => 0,
1853 # prevent this from getting set from a LensID that has been converted
1854 ValueConvInv => q{
1855 warn "Expected one or more integer values" if $val =~ /[^\d ]/;
1856 return $val;
1857 },
1858 },
1859 ApproximateFocusDistance => { Writable => 'rational' }, #PH (LR3)
1860);
1861
1862# IPTC Core schema properties (Iptc4xmpCore) (ref 4)
1863%Image::ExifTool::XMP::iptcCore = (
1864 %xmpTableDefaults,
1865 GROUPS => { 1 => 'XMP-iptcCore', 2 => 'Author' },
1866 NAMESPACE => 'Iptc4xmpCore',
1867 TABLE_DESC => 'XMP IPTC Core',
1868 NOTES => q{
1869 IPTC Core schema tags. The actual IPTC Core namespace prefix is
1870 "Iptc4xmpCore", which is the prefix recorded in the file, but ExifTool
1871 shortens this for the "XMP-iptcCore" family 1 group name. (see
1872 L<http://www.iptc.org/IPTC4XMP/>)
1873 },
1874 CountryCode => { Groups => { 2 => 'Location' } },
1875 CreatorContactInfo => {
1876 Struct => {
1877 STRUCT_NAME => 'ContactInfo',
1878 NAMESPACE => 'Iptc4xmpCore',
1879 CiAdrCity => { },
1880 CiAdrCtry => { },
1881 CiAdrExtadr => { },
1882 CiAdrPcode => { },
1883 CiAdrRegion => { },
1884 CiEmailWork => { },
1885 CiTelWork => { },
1886 CiUrlWork => { },
1887 },
1888 },
1889 CreatorContactInfoCiAdrCity => { Flat => 1, Name => 'CreatorCity' },
1890 CreatorContactInfoCiAdrCtry => { Flat => 1, Name => 'CreatorCountry' },
1891 CreatorContactInfoCiAdrExtadr => { Flat => 1, Name => 'CreatorAddress' },
1892 CreatorContactInfoCiAdrPcode => { Flat => 1, Name => 'CreatorPostalCode' },
1893 CreatorContactInfoCiAdrRegion => { Flat => 1, Name => 'CreatorRegion' },
1894 CreatorContactInfoCiEmailWork => { Flat => 1, Name => 'CreatorWorkEmail' },
1895 CreatorContactInfoCiTelWork => { Flat => 1, Name => 'CreatorWorkTelephone' },
1896 CreatorContactInfoCiUrlWork => { Flat => 1, Name => 'CreatorWorkURL' },
1897 IntellectualGenre => { Groups => { 2 => 'Other' } },
1898 Location => { Groups => { 2 => 'Location' } },
1899 Scene => { Groups => { 2 => 'Other' }, List => 'Bag' },
1900 SubjectCode => { Groups => { 2 => 'Other' }, List => 'Bag' },
1901);
1902
1903# IPTC Extension schema properties (Iptc4xmpExt) (ref 4)
1904%Image::ExifTool::XMP::iptcExt = (
1905 %xmpTableDefaults,
1906 GROUPS => { 1 => 'XMP-iptcExt', 2 => 'Author' },
1907 NAMESPACE => 'Iptc4xmpExt',
1908 TABLE_DESC => 'XMP IPTC Extension',
1909 NOTES => q{
1910 IPTC Extension schema tags. The actual namespace prefix is "Iptc4xmpExt",
1911 but ExifTool shortens this for the "XMP-iptcExt" family 1 group name.
1912 (see L<http://www.iptc.org/IPTC4XMP/>)
1913 },
1914 AddlModelInfo => { Name => 'AdditionalModelInformation' },
1915 ArtworkOrObject => {
1916 Struct => {
1917 STRUCT_NAME => 'ArtworkOrObjectDetails',
1918 NAMESPACE => 'Iptc4xmpExt',
1919 AOCopyrightNotice => { },
1920 AOCreator => { List => 'Seq' },
1921 AODateCreated=> { Groups => { 2 => 'Time' }, %dateTimeInfo },
1922 AOSource => { },
1923 AOSourceInvNo=> { },
1924 AOTitle => { Writable => 'lang-alt' },
1925 },
1926 List => 'Bag',
1927 },
1928 ArtworkOrObjectAOCopyrightNotice=> { Flat => 1, Name => 'ArtworkCopyrightNotice' },
1929 ArtworkOrObjectAOCreator => { Flat => 1, Name => 'ArtworkCreator' },
1930 ArtworkOrObjectAODateCreated => { Flat => 1, Name => 'ArtworkDateCreated' },
1931 ArtworkOrObjectAOSource => { Flat => 1, Name => 'ArtworkSource' },
1932 ArtworkOrObjectAOSourceInvNo => { Flat => 1, Name => 'ArtworkSourceInventoryNo' },
1933 ArtworkOrObjectAOTitle => { Flat => 1, Name => 'ArtworkTitle' },
1934 OrganisationInImageCode => { List => 'Bag' },
1935 CVterm => {
1936 Name => 'ControlledVocabularyTerm',
1937 List => 'Bag',
1938 },
1939 LocationShown => {
1940 Struct => \%sLocationDetails,
1941 Groups => { 2 => 'Location' },
1942 List => 'Bag',
1943 },
1944 ModelAge => { List => 'Bag', Writable => 'integer' },
1945 OrganisationInImageName => { List => 'Bag' },
1946 PersonInImage => { List => 'Bag' },
1947 DigImageGUID => { Name => 'DigitalImageGUID' },
1948 DigitalSourcefileType => {
1949 Name => 'DigitalSourceFileType',
1950 Notes => 'now deprecated -- replaced by DigitalSourceType',
1951 },
1952 DigitalSourceType => { Name => 'DigitalSourceType' },
1953 Event => { Writable => 'lang-alt' },
1954 RegistryId => {
1955 Struct => {
1956 STRUCT_NAME => 'RegistryEntryDetails',
1957 NAMESPACE => 'Iptc4xmpExt',
1958 RegItemId => { },
1959 RegOrgId => { },
1960 },
1961 List => 'Bag',
1962 },
1963 RegistryIdRegItemId => { Flat => 1, Name => 'RegistryItemID' },
1964 RegistryIdRegOrgId => { Flat => 1, Name => 'RegistryOrganisationID' },
1965 IptcLastEdited => { Groups => { 2 => 'Time' }, %dateTimeInfo },
1966 LocationCreated => {
1967 Struct => \%sLocationDetails,
1968 Groups => { 2 => 'Location' },
1969 List => 'Bag',
1970 },
1971 MaxAvailHeight => { Writable => 'integer' },
1972 MaxAvailWidth => { Writable => 'integer' },
1973);
1974
1975# Adobe Lightroom schema properties (lr) (ref PH)
1976%Image::ExifTool::XMP::Lightroom = (
1977 %xmpTableDefaults,
1978 GROUPS => { 1 => 'XMP-lr', 2 => 'Image' },
1979 NAMESPACE => 'lr',
1980 TABLE_DESC => 'XMP Adobe Lightroom',
1981 NOTES => 'Adobe Lightroom "lr" schema tags.',
1982 privateRTKInfo => { },
1983 hierarchicalSubject => { List => 'Bag' },
1984);
1985
1986# Adobe Album schema properties (album) (ref PH)
1987%Image::ExifTool::XMP::Album = (
1988 %xmpTableDefaults,
1989 GROUPS => { 1 => 'XMP-album', 2 => 'Image' },
1990 NAMESPACE => 'album',
1991 TABLE_DESC => 'XMP Adobe Album',
1992 NOTES => 'Adobe Album schema tags.',
1993 Notes => { },
1994);
1995
1996# table to add tags in other namespaces
1997%Image::ExifTool::XMP::other = (
1998 GROUPS => { 2 => 'Unknown' },
1999 LANG_INFO => \&GetLangInfo,
2000);
2001
2002# Composite XMP tags
2003%Image::ExifTool::XMP::Composite = (
2004 # get latitude/logitude reference from XMP lat/long tags
2005 # (used to set EXIF GPS position from XMP tags)
2006 GPSLatitudeRef => {
2007 Require => 'XMP:GPSLatitude',
2008 ValueConv => q{
2009 IsFloat($val[0]) and return $val[0] < 0 ? "S" : "N";
2010 $val[0] =~ /.*([NS])/;
2011 return $1;
2012 },
2013 PrintConv => {
2014 N => 'North',
2015 S => 'South',
2016 },
2017 },
2018 GPSLongitudeRef => {
2019 Require => 'XMP:GPSLongitude',
2020 ValueConv => q{
2021 IsFloat($val[0]) and return $val[0] < 0 ? "W" : "E";
2022 $val[0] =~ /.*([EW])/;
2023 return $1;
2024 },
2025 PrintConv => {
2026 E => 'East',
2027 W => 'West',
2028 },
2029 },
2030);
2031
2032# add our composite tags
2033Image::ExifTool::AddCompositeTags('Image::ExifTool::XMP');
2034
2035#------------------------------------------------------------------------------
2036# AutoLoad our writer routines when necessary
2037#
2038sub AUTOLOAD
2039{
2040 return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
2041}
2042
2043#------------------------------------------------------------------------------
2044# Escape necessary XML characters in UTF-8 string
2045# Inputs: 0) string to be escaped
2046# Returns: escaped string
2047my %charName = ('"'=>'quot', '&'=>'amp', "'"=>'#39', '<'=>'lt', '>'=>'gt');
2048sub EscapeXML($)
2049{
2050 my $str = shift;
2051 $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters
2052 return $str;
2053}
2054
2055#------------------------------------------------------------------------------
2056# Unescape XML character references (entities and numerical)
2057# Inputs: 0) string to be unescaped
2058# 1) optional hash reference to convert entity names to numbers
2059# Returns: unescaped string
2060my %charNum = ('quot'=>34, 'amp'=>38, 'apos'=>39, 'lt'=>60, 'gt'=>62);
2061sub UnescapeXML($;$)
2062{
2063 my ($str, $conv) = @_;
2064 $conv = \%charNum unless $conv;
2065 $str =~ s/&(#?\w+);/UnescapeChar($1,$conv)/sge;
2066 return $str;
2067}
2068
2069#------------------------------------------------------------------------------
2070# Escape string for XML, ensuring valid XML and UTF-8
2071# Inputs: 0) string
2072# Returns: escaped string
2073sub FullEscapeXML($)
2074{
2075 my $str = shift;
2076 $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters
2077 $str =~ s/\\/&#92;/sg; # escape backslashes too
2078 # then use C-escape sequences for invalid characters
2079 if ($str =~ /[\0-\x1f]/ or IsUTF8(\$str) < 0) {
2080 $str =~ s/([\0-\x1f\x80-\xff])/sprintf("\\x%.2x",ord $1)/sge;
2081 }
2082 return $str;
2083}
2084
2085#------------------------------------------------------------------------------
2086# Unescape XML/C escaped string
2087# Inputs: 0) string
2088# Returns: unescaped string
2089sub FullUnescapeXML($)
2090{
2091 my $str = shift;
2092 # unescape C escape sequences first
2093 $str =~ s/\\x([\da-f]{2})/chr(hex($1))/sge;
2094 my $conv = \%charNum;
2095 $str =~ s/&(#?\w+);/UnescapeChar($1,$conv)/sge;
2096 return $str;
2097}
2098
2099#------------------------------------------------------------------------------
2100# Convert XML character reference to UTF-8
2101# Inputs: 0) XML character reference stripped of the '&' and ';' (ie. 'quot', '#34', '#x22')
2102# 1) hash reference for looking up character numbers by name
2103# Returns: UTF-8 equivalent (or original character on conversion error)
2104sub UnescapeChar($$)
2105{
2106 my ($ch, $conv) = @_;
2107 my $val = $$conv{$ch};
2108 unless (defined $val) {
2109 if ($ch =~ /^#x([0-9a-fA-F]+)$/) {
2110 $val = hex($1);
2111 } elsif ($ch =~ /^#(\d+)$/) {
2112 $val = $1;
2113 } else {
2114 return "&$ch;"; # should issue a warning here? [no]
2115 }
2116 }
2117 return chr($val) if $val < 0x80; # simple ASCII
2118 return pack('C0U', $val) if $] >= 5.006001;
2119 return Image::ExifTool::PackUTF8($val);
2120}
2121
2122#------------------------------------------------------------------------------
2123# Does a string contain valid UTF-8 characters?
2124# Inputs: 0) string reference
2125# Returns: 0=regular ASCII, -1=invalid UTF-8, 1=valid UTF-8 with maximum 16-bit
2126# wide characters, 2=valid UTF-8 requiring 32-bit wide characters
2127# Notes: Changes current string position
2128sub IsUTF8($)
2129{
2130 my $strPt = shift;
2131 pos($$strPt) = 0; # start at beginning of string
2132 return 0 unless $$strPt =~ /([\x80-\xff])/g;
2133 my $rtnVal = 1;
2134 for (;;) {
2135 my $ch = ord($1);
2136 # minimum lead byte for 2-byte sequence is 0xc2 (overlong sequences
2137 # not allowed), 0xf8-0xfd are restricted by RFC 3629 (no 5 or 6 byte
2138 # sequences), and 0xfe and 0xff are not valid in UTF-8 strings
2139 return -1 if $ch < 0xc2 or $ch >= 0xf8;
2140 # determine number of bytes remaining in sequence
2141 my $n;
2142 if ($ch < 0xe0) {
2143 $n = 1;
2144 } elsif ($ch < 0xf0) {
2145 $n = 2;
2146 } else {
2147 $n = 3;
2148 # character code is greater than 0xffff if more than 2 extra bytes
2149 # were required in the UTF-8 character
2150 $rtnVal = 2;
2151 }
2152 return -1 unless $$strPt =~ /\G[\x80-\xbf]{$n}/g;
2153 last unless $$strPt =~ /([\x80-\xff])/g;
2154 }
2155 return $rtnVal;
2156}
2157
2158#------------------------------------------------------------------------------
2159# Fix malformed UTF8 (by replacing bad bytes with '?')
2160# Inputs: 0) string reference
2161# Returns: true if string was fixed, and updates string
2162sub FixUTF8($)
2163{
2164 my $strPt = shift;
2165 my $fixed;
2166 pos($$strPt) = 0; # start at beginning of string
2167 for (;;) {
2168 last unless $$strPt =~ /([\x80-\xff])/g;
2169 my $ch = ord($1);
2170 my $pos = pos($$strPt);
2171 # (see comments in IsUTF8() above)
2172 if ($ch >= 0xc2 and $ch < 0xf8) {
2173 my $n = $ch < 0xe0 ? 1 : ($ch < 0xf0 ? 2 : 3);
2174 next if $$strPt =~ /\G[\x80-\xbf]{$n}/g;
2175 }
2176 # replace bad character with '?'
2177 substr($$strPt, $pos-1, 1) = '?';
2178 pos($$strPt) = $fixed = $pos;
2179 }
2180 return $fixed;
2181}
2182
2183#------------------------------------------------------------------------------
2184# Utility routine to decode a base64 string
2185# Inputs: 0) base64 string
2186# Returns: reference to decoded data
2187sub DecodeBase64($)
2188{
2189 local($^W) = 0; # unpack('u',...) gives bogus warning in 5.00[123]
2190 my $str = shift;
2191
2192 # truncate at first unrecognized character (base 64 data
2193 # may only contain A-Z, a-z, 0-9, +, /, =, or white space)
2194 $str =~ s/[^A-Za-z0-9+\/= \t\n\r\f].*//s;
2195 # translate to uucoded and remove padding and white space
2196 $str =~ tr/A-Za-z0-9+\/= \t\n\r\f/ -_/d;
2197
2198 # convert the data to binary in chunks
2199 my $chunkSize = 60;
2200 my $uuLen = pack('c', 32 + $chunkSize * 3 / 4); # calculate length byte
2201 my $dat = '';
2202 my ($i, $substr);
2203 # loop through the whole chunks
2204 my $len = length($str) - $chunkSize;
2205 for ($i=0; $i<=$len; $i+=$chunkSize) {
2206 $substr = substr($str, $i, $chunkSize); # get a chunk of the data
2207 $dat .= unpack('u', $uuLen . $substr); # decode it
2208 }
2209 $len += $chunkSize;
2210 # handle last partial chunk if necessary
2211 if ($i < $len) {
2212 $uuLen = pack('c', 32 + ($len-$i) * 3 / 4); # recalculate length
2213 $substr = substr($str, $i, $len-$i); # get the last partial chunk
2214 $dat .= unpack('u', $uuLen . $substr); # decode it
2215 }
2216 return \$dat;
2217}
2218
2219#------------------------------------------------------------------------------
2220# Generate a name for this XMP tag
2221# Inputs: 0) tag property name list ref, 1) array ref for receiving structure property list
2222# 2) array for receiving namespace list
2223# Returns: tagID and outtermost interesting namespace (or '' if no namespace)
2224sub GetXMPTagID($;$$)
2225{
2226 my ($props, $structProps, $nsList) = @_;
2227 my ($tag, $prop, $namespace);
2228 foreach $prop (@$props) {
2229 # split name into namespace and property name
2230 # (Note: namespace can be '' for property qualifiers)
2231 my ($ns, $nm) = ($prop =~ /(.*?):(.*)/) ? ($1, $2) : ('', $prop);
2232 if ($ignoreNamespace{$ns}) {
2233 # special case: don't ignore rdf numbered items
2234 unless ($prop =~ /^rdf:(_\d+)$/) {
2235 # save list index if necessary for structures
2236 if ($structProps and @$structProps and $prop =~ /^rdf:li (\d+)$/) {
2237 push @{$$structProps[-1]}, $1;
2238 }
2239 next;
2240 }
2241 $tag .= $1 if defined $tag;
2242 } else {
2243 $nm =~ s/ .*//; # remove nodeID if it exists
2244 # all uppercase is ugly, so convert it
2245 if ($nm !~ /[a-z]/) {
2246 my $xlatNS = $$xlatNamespace{$ns} || $ns;
2247 my $info = $Image::ExifTool::XMP::Main{$xlatNS};
2248 my $table;
2249 if (ref $info eq 'HASH' and $info->{SubDirectory}) {
2250 $table = GetTagTable($info->{SubDirectory}{TagTable});
2251 }
2252 unless ($table and $table->{$nm}) {
2253 $nm = lc($nm);
2254 $nm =~ s/_([a-z])/\u$1/g;
2255 }
2256 }
2257 if (defined $tag) {
2258 $tag .= ucfirst($nm); # add to tag name
2259 } else {
2260 $tag = $nm;
2261 }
2262 # save structure information if necessary
2263 if ($structProps) {
2264 push @$structProps, [ $nm ];
2265 push @$nsList, $ns if $nsList;
2266 }
2267 }
2268 # save namespace of first property to contribute to tag name
2269 $namespace = $ns unless $namespace;
2270 }
2271 if (wantarray) {
2272 return ($tag, $namespace || '');
2273 } else {
2274 return $tag;
2275 }
2276}
2277
2278#------------------------------------------------------------------------------
2279# Register namespace for specified user-defined table
2280# Inputs: 0) tag or structure table ref
2281# Returns: namespace prefix
2282sub RegisterNamespace($)
2283{
2284 my $table = shift;
2285 return $$table{NAMESPACE} unless ref $$table{NAMESPACE};
2286 my $nsRef = $$table{NAMESPACE};
2287 # recognize as either a list or hash
2288 my $ns;
2289 if (ref $nsRef eq 'ARRAY') {
2290 $ns = $$nsRef[0];
2291 $nsURI{$ns} = $$nsRef[1];
2292 } else { # must be a hash
2293 my @ns = sort keys %$nsRef; # allow multiple namespace definitions
2294 while (@ns) {
2295 $ns = pop @ns;
2296 if ($nsURI{$ns} and $nsURI{$ns} ne $$nsRef{$ns}) {
2297 warn "User-defined namespace prefix '$ns' conflicts with existing namespace\n";
2298 }
2299 $nsURI{$ns} = $$nsRef{$ns};
2300 }
2301 }
2302 return $$table{NAMESPACE} = $ns;
2303}
2304
2305#------------------------------------------------------------------------------
2306# Generate flattened tags and add to table
2307# Inputs: 0) tag table ref, 1) tag ID for Struct tag in table
2308# Returns: number of tags added (not counting those just initialized)
2309# Notes: Must have verified that $$tagTablePtr{$tagID}{Struct} exists before calling this routine
2310# - makes sure that the tagInfo Struct is a HASH reference
2311sub AddFlattenedTags($$)
2312{
2313 local $_;
2314 my ($tagTablePtr, $tagID) = @_;
2315 my $tagInfo = $$tagTablePtr{$tagID};
2316
2317 $$tagInfo{Flattened} and return 0; # only generate flattened tags once
2318 $$tagInfo{Flattened} = 1;
2319
2320 my $strTable = $$tagInfo{Struct};
2321 unless (ref $strTable) { # (allow a structure name for backward compatibility only)
2322 my $strName = $strTable;
2323 $strTable = $Image::ExifTool::UserDefined::xmpStruct{$strTable} or return 0;
2324 $$strTable{STRUCT_NAME} or $$strTable{STRUCT_NAME} = $strName;
2325 $$tagInfo{Struct} = $strTable; # replace old-style name with HASH ref
2326 delete $$tagInfo{SubDirectory}; # deprecated use of SubDirectory in Struct tags
2327 }
2328 # do not add flattened tags to variable-namespace structures
2329 return 0 if exists $$strTable{NAMESPACE} and not defined $$strTable{NAMESPACE};
2330
2331 # get family 2 group name for this structure tag
2332 my ($tagG2, $field);
2333 $tagG2 = $$tagInfo{Groups}{2} if $$tagInfo{Groups};
2334 $tagG2 or $tagG2 = $$tagTablePtr{GROUPS}{2};
2335
2336 my $count = 0;
2337 foreach $field (keys %$strTable) {
2338 next if $specialStruct{$field};
2339 my $fieldInfo = $$strTable{$field};
2340 next if $$fieldInfo{LangCode}; # don't flatten lang-alt tags
2341 # build a tag ID for the corresponding flattened tag
2342 my $fieldName = ucfirst($field);
2343 my $flatID = $tagID . $fieldName;
2344 my $flatInfo = $$tagTablePtr{$flatID};
2345 if ($flatInfo) {
2346 ref $flatInfo eq 'HASH' or warn("$flatInfo is not a HASH!\n"), next; # (to be safe)
2347 # pre-defined flattened tags should have Flat flag set
2348 if (not defined $$flatInfo{Flat} and $Image::ExifTool::debug) {
2349 warn "Missing Flat flag for $$flatInfo{Name}\n";
2350 }
2351 $$flatInfo{Flat} = 0;
2352 # copy all missing entries from field information
2353 foreach (keys %$fieldInfo) {
2354 # must not copy PropertyPath (but can't delete it afterwards
2355 # because the flat tag may already have this set)
2356 next if $_ eq 'PropertyPath';
2357 $$flatInfo{$_} = $$fieldInfo{$_} unless defined $$flatInfo{$_};
2358 }
2359 # NOTE: Do NOT delete Groups because we need them if GotGroups was done
2360 # --> just override group 2 later according to field group
2361 # re-generate List flag unless it is set to 0
2362 delete $$flatInfo{List} if $$flatInfo{List};
2363 } else {
2364 # generate new flattened tag information based on structure field
2365 $flatInfo = { %$fieldInfo, Name => $$tagInfo{Name} . $fieldName, Flat => 0 };
2366 # add new flattened tag to table
2367 Image::ExifTool::AddTagToTable($tagTablePtr, $flatID, $flatInfo);
2368 ++$count;
2369 }
2370 # propagate List flag (unless set to 0 in pre-defined flattened tag)
2371 unless (defined $$flatInfo{List}) {
2372 $$flatInfo{List} = $$fieldInfo{List} || 1 if $$fieldInfo{List} or $$tagInfo{List};
2373 }
2374 # set group 2 name from the first existing family 2 group in the:
2375 # 1) structure field Groups, 2) structure table GROUPS, 3) structure tag Groups
2376 if ($$fieldInfo{Groups} and $$fieldInfo{Groups}{2}) {
2377 $$flatInfo{Groups}{2} = $$fieldInfo{Groups}{2};
2378 } elsif ($$strTable{GROUPS} and $$strTable{GROUPS}{2}) {
2379 $$flatInfo{Groups}{2} = $$strTable{GROUPS}{2};
2380 } else {
2381 $$flatInfo{Groups}{2} = $tagG2;
2382 }
2383 # save reference to top-level structure
2384 $$flatInfo{RootTagInfo} = $$tagInfo{RootTagInfo} || $tagInfo;
2385 # recursively generate flattened tags for sub-structures
2386 next unless $$flatInfo{Struct};
2387 length($flatID) > 150 and warn("Possible deep recursion for tag $flatID\n"), last;
2388 # reset flattened tag just in case we flattened hierarchy in the wrong order
2389 # because we must start from the outtermost structure to get the List flags right
2390 # (this should only happen when building tag tables)
2391 delete $$flatInfo{Flattened};
2392 $count += AddFlattenedTags($tagTablePtr, $flatID);
2393 }
2394 return $count;
2395}
2396
2397#------------------------------------------------------------------------------
2398# Get localized version of tagInfo hash
2399# Inputs: 0) tagInfo hash ref, 1) language code (ie. "x-default")
2400# Returns: new tagInfo hash ref, or undef if invalid
2401sub GetLangInfo($$)
2402{
2403 my ($tagInfo, $langCode) = @_;
2404 # only allow alternate language tags in lang-alt lists
2405 return undef unless $$tagInfo{Writable} and $$tagInfo{Writable} eq 'lang-alt';
2406 $langCode =~ tr/_/-/; # RFC 3066 specifies '-' as a separator
2407 my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $langCode);
2408 # save reference to source tagInfo hash in case we need to set the PropertyPath later
2409 $$langInfo{SrcTagInfo} = $tagInfo;
2410 return $langInfo;
2411}
2412
2413#------------------------------------------------------------------------------
2414# Get standard case for language code
2415# Inputs: 0) Language code
2416# Returns: Language code in standard case
2417sub StandardLangCase($)
2418{
2419 my $lang = shift;
2420 # make 2nd subtag uppercase only if it is 2 letters
2421 return lc($1) . uc($2) . lc($3) if $lang =~ /^([a-z]{2,3}|[xi])(-[a-z]{2})\b(.*)/i;
2422 return lc($lang);
2423}
2424
2425#------------------------------------------------------------------------------
2426# Scan for XMP in a file
2427# Inputs: 0) ExifTool object ref, 1) RAF reference
2428# Returns: 1 if xmp was found, 0 otherwise
2429# Notes: Currently only recognizes UTF8-encoded XMP
2430sub ScanForXMP($$)
2431{
2432 my ($exifTool, $raf) = @_;
2433 my ($buff, $xmp);
2434 my $lastBuff = '';
2435
2436 $exifTool->VPrint(0,"Scanning for XMP\n");
2437 for (;;) {
2438 defined $buff or $raf->Read($buff, 65536) or return 0;
2439 unless (defined $xmp) {
2440 $lastBuff .= $buff;
2441 unless ($lastBuff =~ /(<\?xpacket begin=)/g) {
2442 # must keep last 15 bytes to match 16-byte "xpacket begin" string
2443 $lastBuff = length($buff) <= 15 ? $buff : substr($buff, -15);
2444 undef $buff;
2445 next;
2446 }
2447 $xmp = $1;
2448 $buff = substr($lastBuff, pos($lastBuff));
2449 }
2450 my $pos = length($xmp) - 18; # (18 = length("<?xpacket end...") - 1)
2451 $xmp .= $buff; # add new data to our XMP
2452 pos($xmp) = $pos if $pos > 0; # set start for "xpacket end" scan
2453 if ($xmp =~ /<\?xpacket end=['"][wr]['"]\?>/g) {
2454 $buff = substr($xmp, pos($xmp)); # save data after end of XMP
2455 $xmp = substr($xmp, 0, pos($xmp)); # isolate XMP
2456 # check XMP for validity (not valid if it contains null bytes)
2457 $pos = rindex($xmp, "\0") + 1 or last;
2458 $lastBuff = substr($xmp, $pos); # re-parse beginning after last null byte
2459 undef $xmp;
2460 } else {
2461 undef $buff;
2462 }
2463 }
2464 unless ($exifTool->{VALUE}{FileType}) {
2465 $exifTool->{FILE_TYPE} = $exifTool->{FILE_EXT};
2466 $exifTool->SetFileType('<unknown file containing XMP>');
2467 }
2468 my %dirInfo = (
2469 DataPt => \$xmp,
2470 DirLen => length $xmp,
2471 DataLen => length $xmp,
2472 );
2473 ProcessXMP($exifTool, \%dirInfo);
2474 return 1;
2475}
2476
2477#------------------------------------------------------------------------------
2478# Convert XMP date/time to EXIF format
2479# Inputs: 0) XMP date/time string, 1) set if we aren't sure this is a date
2480# Returns: EXIF date/time
2481sub ConvertXMPDate($;$)
2482{
2483 my ($val, $unsure) = @_;
2484 if ($val =~ /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}:\d{2})(:\d{2})?\s*(\S*)$/) {
2485 my $s = $5 || ''; # seconds may be missing
2486 $val = "$1:$2:$3 $4$s$6"; # convert back to EXIF time format
2487 } elsif (not $unsure and $val =~ /^(\d{4})(-\d{2}){0,2}/) {
2488 $val =~ tr/-/:/;
2489 }
2490 return $val;
2491}
2492
2493#------------------------------------------------------------------------------
2494# Convert rational string value
2495# Inputs: 0) string (converted to number, 'inf' or 'undef' on return if rational)
2496# Returns: true if value was converted
2497sub ConvertRational($)
2498{
2499 my $val = $_[0];
2500 $val =~ m{^(-?\d+)/(-?\d+)$} or return undef;
2501 if ($2) {
2502 $_[0] = $1 / $2; # calculate quotient
2503 } elsif ($1) {
2504 $_[0] = 'inf';
2505 } else {
2506 $_[0] = 'undef';
2507 }
2508 return 1;
2509}
2510
2511#------------------------------------------------------------------------------
2512# We found an XMP property name/value
2513# Inputs: 0) ExifTool object ref, 1) Pointer to tag table
2514# 2) reference to array of XMP property names (last is current property)
2515# 3) property value, 4) attribute hash ref (for 'xml:lang' or 'rdf:datatype')
2516# Returns: 1 if valid tag was found
2517sub FoundXMP($$$$;$)
2518{
2519 local $_;
2520 my ($exifTool, $tagTablePtr, $props, $val, $attrs) = @_;
2521 my ($lang, @structProps);
2522 my ($tag, $ns) = GetXMPTagID($props, $exifTool->{OPTIONS}{Struct} ? \@structProps : undef);
2523 return 0 unless $tag; # ignore things that aren't valid tags
2524
2525 # translate namespace if necessary
2526 $ns = $$xlatNamespace{$ns} if $$xlatNamespace{$ns};
2527 my $info = $tagTablePtr->{$ns};
2528 my ($table, $added, $xns, $tagID);
2529 if ($info) {
2530 $table = $info->{SubDirectory}{TagTable} or warn "Missing TagTable for $tag!\n";
2531 } elsif ($$props[0] eq 'svg:svg') {
2532 if (not $ns) {
2533 # disambiguate MetadataID by adding back the 'metadata' we ignored
2534 $tag = 'metadataId' if $tag eq 'id' and $$props[1] eq 'svg:metadata';
2535 # use SVG namespace in SVG files if nothing better to use
2536 $table = 'Image::ExifTool::XMP::SVG';
2537 } elsif (not grep /^rdf:/, @$props) {
2538 # only other SVG information if not inside RDF (call it XMP if in RDF)
2539 $table = 'Image::ExifTool::XMP::otherSVG';
2540 }
2541 }
2542
2543 # look up this tag in the appropriate table
2544 $table or $table = 'Image::ExifTool::XMP::other';
2545 $tagTablePtr = GetTagTable($table);
2546 if ($$tagTablePtr{NAMESPACE}) {
2547 $tagID = $tag;
2548 } else {
2549 # add XMP namespace prefix to avoid collisions in variable-namespace tables
2550 $xns = $xmpNS{$ns} || $ns;
2551 $tagID = "$xns:$tag";
2552 # add namespace to top-level structure property
2553 $structProps[0][0] = "$xns:" . $structProps[0][0] if @structProps;
2554 }
2555 my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tagID);
2556
2557 $lang = $$attrs{'xml:lang'} if $attrs;
2558
2559 # must add a new tag table entry if this tag isn't pre-defined
2560 # (or initialize from structure field if this is a pre-defined flattened tag)
2561NoLoop:
2562 while (not $tagInfo or $$tagInfo{Flat}) {
2563 my (@tagList, @nsList);
2564 GetXMPTagID($props, \@tagList, \@nsList);
2565 my ($ta, $t, $ti, $addedFlat, $i, $j);
2566 # build tag ID strings for each level in the property path
2567 foreach $ta (@tagList) {
2568 # insert tag ID in index 1 of tagList list
2569 $t = $$ta[1] = $t ? $t . ucfirst($$ta[0]) : $$ta[0];
2570 # generate flattened tags for top-level structure if necessary
2571 next if defined $addedFlat;
2572 $ti = $$tagTablePtr{$t} or next;
2573 next unless ref $ti eq 'HASH' and $$ti{Struct};
2574 $addedFlat = AddFlattenedTags($tagTablePtr, $t);
2575 if ($tagInfo) {
2576 # all done if we just wanted to initialize the flattened tag
2577 if ($$tagInfo{Flat}) {
2578 warn "Orphan tagInfo with Flat flag set: $$tagInfo{Name}\n";
2579 delete $$tagInfo{Flat};
2580 }
2581 last NoLoop;
2582 }
2583 # all done if we generated the tag we are looking for
2584 $tagInfo = $$tagTablePtr{$tagID} and last NoLoop if $addedFlat;
2585 }
2586 my $name = ucfirst($tag);
2587
2588 # search for the innermost containing structure
2589 # (in case tag is an unknown field in a known structure)
2590 # (only necessary if we found a structure above)
2591 if (defined $addedFlat) {
2592 my $t2 = '';
2593 for ($i=$#tagList-1; $i>=0; --$i) {
2594 $t = $tagList[$i][1];
2595 $t2 = $tagList[$i+1][0] . ucfirst($t2); # build relative tag id
2596 $ti = $$tagTablePtr{$t} or next;
2597 next unless ref $ti eq 'HASH';
2598 my $strTable = $$ti{Struct} or next;
2599 $name = $$ti{Name} . ucfirst($t2);
2600 # don't continue if structure is known but field is not
2601 last if $$strTable{NAMESPACE} or not exists $$strTable{NAMESPACE};
2602 # this is a variable-namespace structure, so we must:
2603 # 1) get tagInfo from corresponding top-level XMP tag if it exists
2604 # 2) add new entry in this tag table, but with namespace prefix on tag ID
2605 my $n = $nsList[$i+1]; # namespace of structure field
2606 # translate to standard ExifTool namespace
2607 $n = $$xlatNamespace{$n} if $$xlatNamespace{$n};
2608 my $xn = $xmpNS{$n} || $n; # standard XMP namespace
2609 # no need to continue with variable-namespace logic if
2610 # we are in our own namespace (right?)
2611 last if $xn eq ($$tagTablePtr{NAMESPACE} || '');
2612 $tagID = "$xn:$tag"; # add namespace to avoid collisions
2613 # change structure properties to add the standard XMP namespace
2614 # prefix for this field (needed for variable-namespace fields)
2615 if (@structProps) {
2616 $structProps[$i+1][0] = "$xn:" . $structProps[$i+1][0];
2617 }
2618 # copy tagInfo entries from the existing top-level XMP tag
2619 my $tg = $Image::ExifTool::XMP::Main{$n};
2620 last unless ref $tg eq 'HASH' and $$tg{SubDirectory};
2621 my $tbl = GetTagTable($$tg{SubDirectory}{TagTable}) or last;
2622 my $sti = $exifTool->GetTagInfo($tbl, $t2);
2623 if (not $sti or $$sti{Flat}) {
2624 # again, we must initialize flattened tags if necessary
2625 # (but don't bother to recursively apply full logic to
2626 # allow nest variable-namespace strucures until someone
2627 # actually wants to do such a silly thing)
2628 my $t3 = '';
2629 for ($j=$i+1; $j<@tagList; ++$j) {
2630 $t3 = $tagList[$j][0] . ucfirst($t3);
2631 my $ti3 = $$tbl{$t3} or next;
2632 next unless ref $ti3 eq 'HASH' and $$ti3{Struct};
2633 last unless AddFlattenedTags($tbl, $t3);
2634 $sti = $$tbl{$t2};
2635 last;
2636 }
2637 last unless $sti;
2638 }
2639 $tagInfo = {
2640 %$sti,
2641 Name => $$ti{Name} . $$sti{Name},
2642 WasAdded => 1,
2643 };
2644 # be careful not to copy elements we shouldn't...
2645 delete $$tagInfo{Description}; # Description will be different
2646 # can't copy group hash because group 1 will be different and
2647 # we need to check this when writing tag to a specific group
2648 delete $$tagInfo{Groups};
2649 $$tagInfo{Groups}{2} = $$sti{Groups}{2} if $$sti{Groups};
2650 last;
2651 }
2652 }
2653 $tagInfo or $tagInfo = { Name => $name, WasAdded => 1 };
2654
2655 # add tag Namespace entry for tags in variable-namespace tables
2656 $$tagInfo{Namespace} = $xns if $xns;
2657 if ($curNS{$ns} and $curNS{$ns} =~ m{^http://ns.exiftool.ca/(.*?)/(.*?)/}) {
2658 my %grps = ( 0 => $1, 1 => $2 );
2659 # apply a little magic to recover original group names
2660 # from this exiftool-written RDF/XML file
2661 if ($grps{1} =~ /^\d/) {
2662 # URI's with only family 0 are internal tags from the source file,
2663 # so change the group name to avoid confusion with tags from this file
2664 $grps{1} = "XML-$grps{0}";
2665 $grps{0} = 'XML';
2666 }
2667 $$tagInfo{Groups} = \%grps;
2668 # flag to avoid setting group 1 later
2669 $$tagInfo{StaticGroup1} = 1;
2670 }
2671 # construct tag information for this unknown tag
2672 # -> make this a List or lang-alt tag if necessary
2673 if (@$props > 2 and $$props[-1] =~ /^rdf:li \d+$/ and
2674 $$props[-2] =~ /^rdf:(Bag|Seq|Alt)$/)
2675 {
2676 if ($lang and $1 eq 'Alt') {
2677 $$tagInfo{Writable} = 'lang-alt';
2678 } else {
2679 $$tagInfo{List} = $1;
2680 }
2681 # tried this, but maybe not a good idea for complex structures:
2682 #} elsif (grep / /, @$props) {
2683 # $$tagInfo{List} = 1;
2684 }
2685 Image::ExifTool::AddTagToTable($tagTablePtr, $tagID, $tagInfo);
2686 $added = 1;
2687 last;
2688 }
2689 # decode value if necessary (et:encoding was used before exiftool 7.71)
2690 if ($attrs) {
2691 my $enc = $$attrs{'rdf:datatype'} || $$attrs{'et:encoding'};
2692 if ($enc and $enc =~ /base64/) {
2693 $val = DecodeBase64($val); # (now a value ref)
2694 $val = $$val unless length $$val > 100 or $$val =~ /[\0-\x08\x0b\0x0c\x0e-\x1f]/;
2695 }
2696 }
2697 if (defined $lang and lc($lang) ne 'x-default') {
2698 $lang = StandardLangCase($lang);
2699 my $langInfo = GetLangInfo($tagInfo, $lang);
2700 $tagInfo = $langInfo if $langInfo;
2701 }
2702 # un-escape XML character entities (handling CDATA)
2703 pos($val) = 0;
2704 if ($val =~ /<!\[CDATA\[(.*?)\]\]>/sg) {
2705 my $p = pos $val;
2706 # unescape everything up to the start of the CDATA section
2707 # (the length of "<[[CDATA[]]>" is 12 characters)
2708 my $v = UnescapeXML(substr($val, 0, $p - length($1) - 12)) . $1;
2709 while ($val =~ /<!\[CDATA\[(.*?)\]\]>/sg) {
2710 my $p1 = pos $val;
2711 $v .= UnescapeXML(substr($val, $p, $p1 - length($1) - 12)) . $1;
2712 $p = $p1;
2713 }
2714 $val = $v . UnescapeXML(substr($val, $p));
2715 } else {
2716 $val = UnescapeXML($val);
2717 }
2718 # decode from UTF8
2719 $val = $exifTool->Decode($val, 'UTF8');
2720 # convert rational and date values to a more sensible format
2721 my $fmt = $$tagInfo{Writable};
2722 my $new = $$tagInfo{WasAdded};
2723 if (($fmt or $new)) {
2724 unless (($new or $fmt eq 'rational') and ConvertRational($val)) {
2725 $val = ConvertXMPDate($val, $new) if $new or $fmt eq 'date';
2726 }
2727 }
2728 # store the value for this tag
2729 my $key = $exifTool->FoundTag($tagInfo, $val);
2730 # save structure/list information if necessary
2731 if (@structProps and (@structProps > 1 or defined $structProps[0][1])) {
2732 $exifTool->{TAG_EXTRA}{$key}{Struct} = \@structProps;
2733 $exifTool->{IsStruct} = 1;
2734 }
2735 if ($ns and not $$tagInfo{StaticGroup1}) {
2736 # set group1 dynamically according to the namespace
2737 $exifTool->SetGroup($key, "$tagTablePtr->{GROUPS}{0}-$ns");
2738 }
2739 if ($exifTool->{OPTIONS}{Verbose}) {
2740 if ($added) {
2741 my $g1 = $exifTool->GetGroup($key, 1);
2742 $exifTool->VPrint(0, $exifTool->{INDENT}, "[adding $g1:$tag]\n");
2743 }
2744 my $tagID = join('/',@$props);
2745 $exifTool->VerboseInfo($tagID, $tagInfo, Value=>$val);
2746 }
2747 return 1;
2748}
2749
2750#------------------------------------------------------------------------------
2751# Recursively parse nested XMP data element
2752# Inputs: 0) ExifTool object reference
2753# 1) Pointer to tag table
2754# 2) reference to XMP data
2755# 3) start of xmp element
2756# 4) reference to array of enclosing XMP property names (undef if none)
2757# 5) reference to blank node information hash
2758# Returns: Number of contained XMP elements
2759sub ParseXMPElement($$$;$$$)
2760{
2761 my ($exifTool, $tagTablePtr, $dataPt, $start, $propListPt, $blankInfo) = @_;
2762 my ($count, $nItems) = (0, 0);
2763 my $isWriting = $exifTool->{XMP_CAPTURE};
2764 my $isSVG = $$exifTool{XMP_IS_SVG};
2765
2766 # get our parse procs
2767 my ($attrProc, $foundProc);
2768 if ($$exifTool{XMPParseOpts}) {
2769 $attrProc = $$exifTool{XMPParseOpts}{AttrProc};
2770 $foundProc = $$exifTool{XMPParseOpts}{FoundProc} || \&FoundXMP;
2771 } else {
2772 $foundProc = \&FoundXMP;
2773 }
2774 $start or $start = 0;
2775 $propListPt or $propListPt = [ ];
2776
2777 my $processBlankInfo;
2778 # create empty blank node information hash if necessary
2779 $blankInfo or $blankInfo = $processBlankInfo = { Prop => { } };
2780 # keep track of current nodeID at this nesting level
2781 my $oldNodeID = $$blankInfo{NodeID};
2782
2783 pos($$dataPt) = $start;
2784 Element: for (;;) {
2785 # reset nodeID before processing each element
2786 my $nodeID = $$blankInfo{NodeID} = $oldNodeID;
2787 # get next element
2788 last unless $$dataPt =~ m/<([-\w:.\x80-\xff]+)(.*?)>/sg;
2789 my ($prop, $attrs) = ($1, $2);
2790 my $val = '';
2791 # only look for closing token if this is not an empty element
2792 # (empty elements end with '/', ie. <a:b/>)
2793 if ($attrs !~ s/\/$//) {
2794 my $nesting = 1;
2795 for (;;) {
2796# this match fails with perl 5.6.2 (perl bug!), but it works without
2797# the '(.*?)', so do it the hard way instead...
2798# $$dataPt =~ m/(.*?)<\/$prop>/sg or last Element;
2799# my $val2 = $1;
2800 my $pos = pos($$dataPt);
2801 unless ($$dataPt =~ m/<\/$prop>/sg) {
2802 $exifTool->Warn("XMP format error (no closing tag for $prop)");
2803 last Element;
2804 }
2805 my $len = pos($$dataPt) - $pos - length($prop) - 3;
2806 my $val2 = substr($$dataPt, $pos, $len);
2807 # increment nesting level for each contained similar opening token
2808 ++$nesting while $val2 =~ m/<$prop\b.*?(\/?)>/sg and $1 ne '/';
2809 $val .= $val2;
2810 --$nesting or last;
2811 $val .= "</$prop>";
2812 }
2813 }
2814 my $parseResource;
2815 if ($prop eq 'rdf:li') {
2816 # add index to list items so we can keep them in order
2817 # (this also enables us to keep structure elements grouped properly
2818 # for lists of structures, like JobRef)
2819 # Note: the list index is prefixed by the number of digits so sorting
2820 # alphabetically gives the correct order while still allowing a flexible
2821 # number of digits -- this scheme allows up to 9 digits in the index,
2822 # with index numbers ranging from 0 to 999999999. The sequence is:
2823 # 10,11,12-19,210,211-299,3100,3101-3999,41000...9999999999.
2824 $prop .= ' ' . length($nItems) . $nItems;
2825 ++$nItems;
2826 } elsif ($prop eq 'rdf:Description') {
2827 # trim comments and whitespace from rdf:Description properties only
2828 $val =~ s/<!--.*?-->//g;
2829 $val =~ s/^\s*(.*)\s*$/$1/;
2830 # remove unnecessary rdf:Description elements since parseType='Resource'
2831 # is more efficient (also necessary to make property path consistent)
2832 $parseResource = 1 if grep /^rdf:Description$/, @$propListPt;
2833 } elsif ($prop eq 'xmp:xmpmeta') {
2834 # patch MicrosoftPhoto unconformity
2835 $prop = 'x:xmpmeta';
2836 }
2837
2838 # extract property attributes
2839 my (%attrs, @attrs);
2840 while ($attrs =~ m/(\S+?)\s*=\s*(['"])(.*?)\2/sg) {
2841 push @attrs, $1; # preserve order
2842 $attrs{$1} = $3;
2843 }
2844
2845 # hook for special parsing of attributes
2846 $attrProc and &$attrProc(\@attrs, \%attrs, \$prop, \$val);
2847
2848 # add nodeID to property path (with leading ' #') if it exists
2849 if (defined $attrs{'rdf:nodeID'}) {
2850 $nodeID = $$blankInfo{NodeID} = $attrs{'rdf:nodeID'};
2851 delete $attrs{'rdf:nodeID'};
2852 $prop .= ' #' . $nodeID;
2853 undef $parseResource; # can't ignore if this is a node
2854 }
2855
2856 # push this property name onto our hierarchy list
2857 push @$propListPt, $prop unless $parseResource;
2858
2859 if ($isSVG) {
2860 # ignore everything but top level SVG tags and metadata unless Unknown set
2861 unless ($exifTool->{OPTIONS}{Unknown} > 1 or $exifTool->{OPTIONS}{Verbose}) {
2862 if (@$propListPt > 1 and $$propListPt[1] !~ /\b(metadata|desc|title)$/) {
2863 pop @$propListPt;
2864 next;
2865 }
2866 }
2867 if ($prop eq 'svg' or $prop eq 'metadata') {
2868 # add svg namespace prefix if missing to ignore these entries in the tag name
2869 $$propListPt[-1] = "svg:$prop";
2870 }
2871 }
2872
2873 # handle properties inside element attributes (RDF shorthand format):
2874 # (attributes take the form a:b='c' or a:b="c")
2875 my ($shortName, $shorthand, $ignored);
2876 foreach $shortName (@attrs) {
2877 my $propName = $shortName;
2878 my ($ns, $name);
2879 if ($propName =~ /(.*?):(.*)/) {
2880 $ns = $1; # specified namespace
2881 $name = $2;
2882 } elsif ($prop =~ /(\S*?):/) {
2883 $ns = $1; # assume same namespace as parent
2884 $name = $propName;
2885 $propName = "$ns:$name"; # generate full property name
2886 } else {
2887 # a property qualifier is the only property name that may not
2888 # have a namespace, and a qualifier shouldn't have attributes,
2889 # but what the heck, let's allow this anyway
2890 $ns = '';
2891 $name = $propName;
2892 }
2893 # keep track of the namespace prefixes used
2894 if ($ns eq 'xmlns') {
2895 unless ($attrs{$shortName}) {
2896 $exifTool->WarnOnce("Duplicate namespace '$shortName'");
2897 next;
2898 }
2899 $curNS{$name} = $attrs{$shortName};
2900 my $stdNS = $uri2ns{$attrs{$shortName}};
2901 # translate namespace if non-standard (except 'x' and 'iX')
2902 if ($stdNS and $name ne $stdNS and $stdNS ne 'x' and $stdNS ne 'iX') {
2903 # make a copy of the standard translations so we can modify it
2904 $xlatNamespace = { %stdXlatNS } if $xlatNamespace eq \%stdXlatNS;
2905 # translate this namespace prefix to the standard version
2906 $$xlatNamespace{$name} = $stdXlatNS{$stdNS} || $stdNS;
2907 }
2908 }
2909 if ($isWriting) {
2910 # keep track of our namespaces when writing
2911 if ($ns eq 'xmlns') {
2912 my $stdNS = $uri2ns{$attrs{$shortName}};
2913 unless ($stdNS and ($stdNS eq 'x' or $stdNS eq 'iX')) {
2914 my $nsUsed = $exifTool->{XMP_NS};
2915 $$nsUsed{$name} = $attrs{$shortName} unless defined $$nsUsed{$name};
2916 }
2917 delete $attrs{$shortName}; # (handled by namespace logic)
2918 next;
2919 } elsif ($recognizedAttrs{$propName}) {
2920 # save UUID to use same ID when writing
2921 if ($propName eq 'rdf:about') {
2922 if (not $exifTool->{XMP_ABOUT}) {
2923 $exifTool->{XMP_ABOUT} = $attrs{$shortName};
2924 } elsif ($exifTool->{XMP_ABOUT} ne $attrs{$shortName}) {
2925 $exifTool->Error("Different 'rdf:about' attributes not handled", 1);
2926 }
2927 }
2928 next;
2929 }
2930 }
2931 my $shortVal = $attrs{$shortName};
2932 if ($ignoreNamespace{$ns}) {
2933 $ignored = $propName;
2934 # handle special attributes (extract as tags only once if not empty)
2935 if (ref $recognizedAttrs{$propName} and $shortVal) {
2936 my ($tbl, $id, $name) = @{$recognizedAttrs{$propName}};
2937 my $val = UnescapeXML($shortVal);
2938 unless (defined $$exifTool{VALUE}{$name} and $$exifTool{VALUE}{$name} eq $val) {
2939 $exifTool->HandleTag(GetTagTable($tbl), $id, $val);
2940 }
2941 }
2942 next;
2943 }
2944 delete $attrs{$shortName}; # don't re-use this attribute
2945 push @$propListPt, $propName;
2946 # save this shorthand XMP property
2947 if (defined $nodeID) {
2948 SaveBlankInfo($blankInfo, $propListPt, $shortVal);
2949 } elsif ($isWriting) {
2950 CaptureXMP($exifTool, $propListPt, $shortVal);
2951 } else {
2952 &$foundProc($exifTool, $tagTablePtr, $propListPt, $shortVal);
2953 }
2954 pop @$propListPt;
2955 $shorthand = 1;
2956 }
2957 if ($isWriting) {
2958 if (ParseXMPElement($exifTool, $tagTablePtr, \$val, 0, $propListPt, $blankInfo)) {
2959 # undefine value since we found more properties within this one
2960 undef $val;
2961 # set an error on any ignored attributes here, because they will be lost
2962 $exifTool->{XMP_ERROR} = "Can't handle XMP attribute '$ignored'" if $ignored;
2963 }
2964 if (defined $val and (length $val or not $shorthand)) {
2965 if (defined $nodeID) {
2966 SaveBlankInfo($blankInfo, $propListPt, $val, \%attrs);
2967 } else {
2968 CaptureXMP($exifTool, $propListPt, $val, \%attrs);
2969 }
2970 }
2971 } else {
2972 # if element value is empty, take value from 'resource' attribute
2973 # (preferentially) or 'about' attribute (if no 'resource')
2974 my $wasEmpty;
2975 if ($val eq '' and ($attrs =~ /\bresource=(['"])(.*?)\1/ or
2976 $attrs =~ /\babout=(['"])(.*?)\1/))
2977 {
2978 $val = $2;
2979 $wasEmpty = 1;
2980 }
2981 # look for additional elements contained within this one
2982 if (!ParseXMPElement($exifTool, $tagTablePtr, \$val, 0, $propListPt, $blankInfo)) {
2983 # there are no contained elements, so this must be a simple property value
2984 # (unless we already extracted shorthand values from this element)
2985 if (length $val or not $shorthand) {
2986 my $lastProp = $$propListPt[-1];
2987 if (defined $nodeID) {
2988 SaveBlankInfo($blankInfo, $propListPt, $val);
2989 } elsif ($lastProp eq 'rdf:type' and $wasEmpty) {
2990 # do not extract empty structure types (for now)
2991 } elsif ($lastProp =~ /^et:(desc|prt|val)$/ and ($count or $1 eq 'desc')) {
2992 # ignore et:desc, and et:val if preceeded by et:prt
2993 --$count;
2994 } else {
2995 &$foundProc($exifTool, $tagTablePtr, $propListPt, $val, \%attrs);
2996 }
2997 }
2998 }
2999 }
3000 pop @$propListPt unless $parseResource;
3001 ++$count;
3002 }
3003#
3004# process resources referenced by blank nodeID's
3005#
3006 if ($processBlankInfo and %{$$blankInfo{Prop}}) {
3007 ProcessBlankInfo($exifTool, $tagTablePtr, $blankInfo, $isWriting);
3008 %$blankInfo = (); # free some memory
3009 }
3010 return $count; # return the number of elements found at this level
3011}
3012
3013#------------------------------------------------------------------------------
3014# Process XMP data
3015# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) Pointer to tag table
3016# Returns: 1 on success
3017# Notes: The following flavours of XMP files are currently recognized:
3018# - standard XMP with xpacket, x:xmpmeta and rdf:RDF elements
3019# - XMP that is missing the xpacket and/or x:xmpmeta elements
3020# - mutant Microsoft XMP with xmp:xmpmeta element
3021# - XML files beginning with "<xml"
3022# - SVG files that begin with "<svg" or "<!DOCTYPE svg"
3023# - XMP and XML files beginning with a UTF-8 byte order mark
3024# - UTF-8, UTF-16 and UTF-32 encoded XMP
3025# - erroneously double-UTF8 encoded XMP
3026# - otherwise valid files with leading XML comment
3027sub ProcessXMP($$;$)
3028{
3029 my ($exifTool, $dirInfo, $tagTablePtr) = @_;
3030 my $dataPt = $$dirInfo{DataPt};
3031 my ($dirStart, $dirLen, $dataLen);
3032 my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG);
3033 my $rtnVal = 0;
3034 my $bom = 0;
3035 undef %curNS;
3036
3037 # ignore non-standard XMP while in strict MWG compatibility mode
3038 if ($Image::ExifTool::MWG::strict and not $$exifTool{XMP_CAPTURE} and
3039 $$exifTool{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/)
3040 {
3041 my $path = $exifTool->MetadataPath();
3042 unless ($path =~ /^(JPEG-APP1-XMP|TIFF-IFD0-XMP|PSD-XMP)$/) {
3043 $exifTool->Warn("Ignored non-standard XMP at $path");
3044 return 1;
3045 }
3046 }
3047 if ($dataPt) {
3048 $dirStart = $$dirInfo{DirStart} || 0;
3049 $dirLen = $$dirInfo{DirLen} || (length($$dataPt) - $dirStart);
3050 $dataLen = $$dirInfo{DataLen} || length($$dataPt);
3051 } else {
3052 my $type;
3053 # read information from XMP file
3054 my $raf = $$dirInfo{RAF} or return 0;
3055 $raf->Read($buff, 256) or return 0;
3056 my ($buf2, $buf3, $double);
3057 ($buf2 = $buff) =~ tr/\0//d; # cheap conversion to UTF-8
3058 # remove leading comments if they exist (ie. ImageIngester)
3059 while ($buf2 =~ /^\s*<!--/) {
3060 # remove the comment if it is complete
3061 if ($buf2 =~ s/^\s*<!--.*?-->\s+//s) {
3062 # continue with parsing if we have more than 128 bytes remaining
3063 next if length $buf2 > 128;
3064 } else {
3065 # don't read more than 10k when looking for the end of comment
3066 return 0 if length($buf2) > 10000;
3067 }
3068 $raf->Read($buf3, 256) or last; # read more data if available
3069 $buff .= $buf3;
3070 $buf3 =~ tr/\0//d;
3071 $buf2 .= $buf3;
3072 }
3073 # check to see if this is XMP format
3074 # (CS2 writes .XMP files without the "xpacket begin")
3075 if ($buf2 =~ /^\s*(<\?xpacket begin=|<x(mp)?:x[ma]pmeta)/) {
3076 $hasXMP = 1;
3077 } else {
3078 # also recognize XML files and .XMP files with BOM and without x:xmpmeta
3079 if ($buf2 =~ /^(\xfe\xff)(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta)/g) {
3080 $fmt = 'n'; # UTF-16 or 32 MM with BOM
3081 } elsif ($buf2 =~ /^(\xff\xfe)(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta)/g) {
3082 $fmt = 'v'; # UTF-16 or 32 II with BOM
3083 } elsif ($buf2 =~ /^(\xef\xbb\xbf)?(<\?xml|<rdf:RDF|<x(mp)?:x[ma]pmeta)/g) {
3084 $fmt = 0; # UTF-8 with BOM or unknown encoding without BOM
3085 } elsif ($buf2 =~ /^(\xfe\xff|\xff\xfe|\xef\xbb\xbf)(<\?xpacket begin=)/g) {
3086 $double = $1; # double-encoded UTF
3087 } else {
3088 return 0; # not recognized XMP or XML
3089 }
3090 $bom = 1 if $1;
3091 if ($2 eq '<?xml') {
3092 if ($buf2 =~ /<x(mp)?:x[ma]pmeta/) {
3093 $hasXMP = 1;
3094 } else {
3095 # identify SVG images by DOCTYPE if available
3096 if ($buf2 =~ /<!DOCTYPE\s+(\w+)/) {
3097 if ($1 eq 'svg') {
3098 $isSVG = 1;
3099 } elsif ($1 eq 'plist') {
3100 $type = 'PLIST';
3101 } else {
3102 return 0;
3103 }
3104 } elsif ($buf2 =~ /<svg[\s>]/) {
3105 $isSVG = 1;
3106 } elsif ($buf2 =~ /<rdf:RDF/) {
3107 $isRDF = 1;
3108 }
3109 if ($isSVG and $exifTool->{XMP_CAPTURE}) {
3110 $exifTool->Error("ExifTool does not yet support writing of SVG images");
3111 return 0;
3112 }
3113 }
3114 $isXML = 1;
3115 } elsif ($2 eq '<rdf:RDF') {
3116 $isRDF = 1; # recognize XMP without x:xmpmeta element
3117 }
3118 if ($buff =~ /^\0\0/) {
3119 $fmt = 'N'; # UTF-32 MM with or without BOM
3120 } elsif ($buff =~ /^..\0\0/) {
3121 $fmt = 'V'; # UTF-32 II with or without BOM
3122 } elsif (not $fmt) {
3123 if ($buff =~ /^\0/) {
3124 $fmt = 'n'; # UTF-16 MM without BOM
3125 } elsif ($buff =~ /^.\0/) {
3126 $fmt = 'v'; # UTF-16 II without BOM
3127 }
3128 }
3129 }
3130 $raf->Seek(0, 2) or return 0;
3131 my $size = $raf->Tell() or return 0;
3132 $raf->Seek(0, 0) or return 0;
3133 $raf->Read($buff, $size) == $size or return 0;
3134 # decode the first layer of double-encoded UTF text
3135 if ($double) {
3136 $buff = substr($buff, length $double); # remove leading BOM
3137 Image::ExifTool::SetWarning(undef); # clear old warning
3138 local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning;
3139 my $tmp;
3140 # assume that character data has been re-encoded in UTF, so re-pack
3141 # as characters and look for warnings indicating a false assumption
3142 if ($double eq "\xef\xbb\xbf") {
3143 require Image::ExifTool::Charset;
3144 my $uni = Image::ExifTool::Charset::Decompose(undef,$buff,'UTF8');
3145 $tmp = pack('C*', @$uni);
3146 } else {
3147 my $fmt = ($double eq "\xfe\xff") ? 'n' : 'v';
3148 $tmp = pack('C*', unpack("$fmt*",$buff));
3149 }
3150 if (Image::ExifTool::GetWarning()) {
3151 $exifTool->Warn('Superfluous BOM at start of XMP');
3152 } else {
3153 $exifTool->Warn('XMP is double UTF-encoded');
3154 $buff = $tmp; # use the decoded XMP
3155 }
3156 $size = length $buff;
3157 }
3158 $dataPt = \$buff;
3159 $dirStart = 0;
3160 $dirLen = $dataLen = $size;
3161 unless ($type) {
3162 if ($isSVG) {
3163 $type = 'SVG';
3164 } elsif ($isXML and not $hasXMP and not $isRDF) {
3165 $type = 'XML';
3166 }
3167 }
3168 $exifTool->SetFileType($type);
3169 }
3170
3171 # take substring if necessary
3172 if ($dataLen != $dirStart + $dirLen) {
3173 $buff = substr($$dataPt, $dirStart, $dirLen);
3174 $dataPt = \$buff;
3175 $dirStart = 0;
3176 }
3177 # extract XMP as a block if specified
3178 if (($exifTool->{REQ_TAG_LOOKUP}{xmp} or $exifTool->{OPTIONS}{Binary}) and not $isSVG) {
3179 $exifTool->FoundTag('XMP', substr($$dataPt, $dirStart, $dirLen));
3180 }
3181 if ($exifTool->Options('Verbose') and not $exifTool->{XMP_CAPTURE}) {
3182 $exifTool->VerboseDir($isSVG ? 'SVG' : 'XMP', 0, $dirLen);
3183 }
3184#
3185# convert UTF-16 or UTF-32 encoded XMP to UTF-8 if necessary
3186#
3187 my $begin = '<?xpacket begin=';
3188 pos($$dataPt) = $dirStart;
3189 delete $$exifTool{XMP_IS_XML};
3190 delete $$exifTool{XMP_IS_SVG};
3191 if ($isXML or $isRDF) {
3192 $$exifTool{XMP_IS_XML} = $isXML;
3193 $$exifTool{XMP_IS_SVG} = $isSVG;
3194 $$exifTool{XMP_NO_XPACKET} = 1 + $bom;
3195 } elsif ($$dataPt =~ /\G\Q$begin\E/gc) {
3196 delete $$exifTool{XMP_NO_XPACKET};
3197 } elsif ($$dataPt =~ /<x(mp)?:x[ma]pmeta/gc) {
3198 $$exifTool{XMP_NO_XPACKET} = 1 + $bom;
3199 } else {
3200 delete $$exifTool{XMP_NO_XPACKET};
3201 # check for UTF-16 encoding (insert one \0 between characters)
3202 $begin = join "\0", split //, $begin;
3203 # must reset pos because it was killed by previous unsuccessful //g match
3204 pos($$dataPt) = $dirStart;
3205 if ($$dataPt =~ /\G(\0)?\Q$begin\E\0./g) {
3206 # validate byte ordering by checking for U+FEFF character
3207 if ($1) {
3208 # should be big-endian since we had a leading \0
3209 $fmt = 'n' if $$dataPt =~ /\G\xfe\xff/g;
3210 } else {
3211 $fmt = 'v' if $$dataPt =~ /\G\0\xff\xfe/g;
3212 }
3213 } else {
3214 # check for UTF-32 encoding (with three \0's between characters)
3215 $begin =~ s/\0/\0\0\0/g;
3216 pos($$dataPt) = $dirStart;
3217 if ($$dataPt !~ /\G(\0\0\0)?\Q$begin\E\0\0\0./g) {
3218 $fmt = 0; # set format to zero as indication we didn't find encoded XMP
3219 } elsif ($1) {
3220 # should be big-endian
3221 $fmt = 'N' if $$dataPt =~ /\G\0\0\xfe\xff/g;
3222 } else {
3223 $fmt = 'V' if $$dataPt =~ /\G\0\0\0\xff\xfe\0\0/g;
3224 }
3225 }
3226 defined $fmt or $exifTool->Warn('XMP character encoding error');
3227 }
3228 if ($fmt) {
3229 # trim if necessary to avoid converting non-UTF data
3230 if ($dirStart or $dirLen != length($$dataPt) - $dirStart) {
3231 $buff = substr($$dataPt, $dirStart, $dirLen);
3232 $dataPt = \$buff;
3233 }
3234 # convert into UTF-8
3235 if ($] >= 5.006001) {
3236 $buff = pack('C0U*', unpack("$fmt*",$$dataPt));
3237 } else {
3238 $buff = Image::ExifTool::PackUTF8(unpack("$fmt*",$$dataPt));
3239 }
3240 $dataPt = \$buff;
3241 $dirStart = 0;
3242 $dirLen = length $$dataPt;
3243 }
3244 # initialize namespace translation
3245 $xlatNamespace = \%stdXlatNS;
3246
3247 # avoid scanning for XMP later in case ScanForXMP is set
3248 $$exifTool{FoundXMP} = 1;
3249
3250 # set XMP parsing options
3251 $$exifTool{XMPParseOpts} = $$dirInfo{XMPParseOpts};
3252
3253 # need to preserve list indices to be able to handle multi-dimensional lists
3254 $$exifTool{NO_LIST} = 1 if $exifTool->Options('Struct');
3255
3256 # parse the XMP
3257 $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
3258 $rtnVal = 1 if ParseXMPElement($exifTool, $tagTablePtr, $dataPt, $dirStart);
3259
3260 # return DataPt if successful in case we want it for writing
3261 $$dirInfo{DataPt} = $dataPt if $rtnVal and $$dirInfo{RAF};
3262
3263 # restore structures if necessary
3264 if ($$exifTool{IsStruct}) {
3265 require 'Image/ExifTool/XMPStruct.pl';
3266 RestoreStruct($exifTool);
3267 delete $$exifTool{IsStruct};
3268 }
3269 # reset NO_LIST flag (must do this _after_ RestoreStruct() above)
3270 delete $$exifTool{NO_LIST};
3271
3272 undef %curNS;
3273 return $rtnVal;
3274}
3275
3276
32771; #end
3278
3279__END__
3280
3281=head1 NAME
3282
3283Image::ExifTool::XMP - Read XMP meta information
3284
3285=head1 SYNOPSIS
3286
3287This module is loaded automatically by Image::ExifTool when required.
3288
3289=head1 DESCRIPTION
3290
3291XMP stands for Extensible Metadata Platform. It is a format based on XML
3292that Adobe developed for embedding metadata information in image files.
3293This module contains the definitions required by Image::ExifTool to read XMP
3294information.
3295
3296=head1 AUTHOR
3297
3298Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)
3299
3300This library is free software; you can redistribute it and/or modify it
3301under the same terms as Perl itself.
3302
3303=head1 REFERENCES
3304
3305=over 4
3306
3307=item L<http://www.adobe.com/devnet/xmp/>
3308
3309=item L<http://www.w3.org/TR/rdf-syntax-grammar/>
3310
3311=item L<http://www.iptc.org/IPTC4XMP/>
3312
3313=back
3314
3315=head1 SEE ALSO
3316
3317L<Image::ExifTool::TagNames/XMP Tags>,
3318L<Image::ExifTool(3pm)|Image::ExifTool>
3319
3320=cut
Note: See TracBrowser for help on using the repository browser.