source: gsdl/trunk/perllib/cpan/Image/ExifTool/XMP.pm@ 16842

Last change on this file since 16842 was 16842, checked in by davidb, 16 years ago

ExifTool added to cpan area to support metadata extraction from files such as JPEG. Primarily targetted as Image files (hence the Image folder name decided upon by the ExifTool author) it also can handle video such as flash and audio such as Wav

File size: 79.6 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#
14# References: 1) http://www.adobe.com/products/xmp/pdfs/xmpspec.pdf
15# 2) http://www.w3.org/TR/rdf-syntax-grammar/ (20040210)
16# 3) http://www.portfoliofaq.com/pfaq/v7mappings.htm
17# 4) http://www.iptc.org/IPTC4XMP/
18# 5) http://creativecommons.org/technology/xmp
19# 6) http://www.optimasc.com/products/fileid/xmp-extensions.pdf
20# 7) Lou Salkind private communication
21# 8) http://partners.adobe.com/public/developer/en/xmp/sdk/XMPspecification.pdf
22#
23# Notes: - I am handling property qualifiers as if they were separate
24# properties (with no associated namespace).
25#
26# - Currently, there is no special treatment of the following
27# properties which could potentially affect the extracted
28# information: xml:base, rdf:parseType (note that parseType
29# Literal isn't allowed by the XMP spec).
30#
31# - The family 2 group names will be set to 'Unknown' for any XMP
32# tags not found in the XMP or Exif tag tables.
33#------------------------------------------------------------------------------
34
35package Image::ExifTool::XMP;
36
37use strict;
38use vars qw($VERSION $AUTOLOAD @ISA @EXPORT_OK);
39use Image::ExifTool qw(:Utils);
40use Image::ExifTool::Exif;
41require Exporter;
42
43$VERSION = '1.78';
44@ISA = qw(Exporter);
45@EXPORT_OK = qw(EscapeXML UnescapeXML);
46
47sub ProcessXMP($$;$);
48sub WriteXMP($$;$);
49sub ParseXMPElement($$$;$$$);
50sub DecodeBase64($);
51sub SaveBlankInfo($$$;$);
52sub ProcessBlankInfo($$$;$);
53sub ValidateXMP($;$);
54sub UnescapeChar($$);
55
56# conversions for GPS coordinates
57sub ToDegrees
58{
59 require Image::ExifTool::GPS;
60 Image::ExifTool::GPS::ToDegrees($_[0], 1);
61}
62my %latConv = (
63 ValueConv => \&ToDegrees,
64 RawConv => 'require Image::ExifTool::GPS; $val', # to load Composite tags and routines
65 ValueConvInv => q{
66 require Image::ExifTool::GPS;
67 Image::ExifTool::GPS::ToDMS($self, $val, 2, "N");
68 },
69 PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
70 PrintConvInv => \&ToDegrees,
71);
72my %longConv = (
73 ValueConv => \&ToDegrees,
74 RawConv => 'require Image::ExifTool::GPS; $val',
75 ValueConvInv => q{
76 require Image::ExifTool::GPS;
77 Image::ExifTool::GPS::ToDMS($self, $val, 2, "E");
78 },
79 PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
80 PrintConvInv => \&ToDegrees,
81);
82my %dateTimeInfo = (
83 Groups => { 2 => 'Time' },
84 Writable => 'date',
85 Shift => 'Time',
86 PrintConv => '$self->ConvertDateTime($val)',
87);
88
89# XMP namespaces which we don't want to contribute to generated EXIF tag names
90my %ignoreNamespace = ( 'x'=>1, 'rdf'=>1, 'xmlns'=>1, 'xml'=>1);
91
92# translate XMP namespaces when reading
93my %xlatNamespace = (
94 # shorten ugly IPTC Core namespace prefix
95 'Iptc4xmpCore' => 'iptcCore',
96 'photomechanic'=> 'photomech',
97 'MicrosoftPhoto' => 'microsoft',
98 # also translate older 'xap...' prefixes to 'xmp...'
99 'xap' => 'xmp',
100 'xapBJ' => 'xmpBJ',
101 'xapMM' => 'xmpMM',
102 'xapRights' => 'xmpRights',
103);
104
105# these are the attributes that we handle for properties that contain
106# sub-properties. Attributes for simple properties are easy, and we
107# just copy them over. These are harder since we don't store attributes
108# for properties without simple values. (maybe this will change...)
109my %recognizedAttrs = (
110 'x:xaptk' => 1,
111 'x:xmptk' => 1,
112 'rdf:about' => 1,
113 'rdf:parseType' => 1,
114 'rdf:nodeID' => 1,
115);
116
117# main XMP tag table
118%Image::ExifTool::XMP::Main = (
119 GROUPS => { 2 => 'Unknown' },
120 PROCESS_PROC => \&ProcessXMP,
121 WRITE_PROC => \&WriteXMP,
122 dc => {
123 Name => 'dc', # (otherwise generated name would be 'Dc')
124 SubDirectory => { TagTable => 'Image::ExifTool::XMP::dc' },
125 },
126 xmp => {
127 Name => 'xmp',
128 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmp' },
129 },
130 xmpDM => {
131 Name => 'xmpDM',
132 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpDM' },
133 },
134 xmpRights => {
135 Name => 'xmpRights',
136 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpRights' },
137 },
138 xmpMM => {
139 Name => 'xmpMM',
140 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpMM' },
141 },
142 xmpBJ => {
143 Name => 'xmpBJ',
144 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpBJ' },
145 },
146 xmpTPg => {
147 Name => 'xmpTPg',
148 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpTPg' },
149 },
150 pdf => {
151 Name => 'pdf',
152 SubDirectory => { TagTable => 'Image::ExifTool::XMP::pdf' },
153 },
154 photoshop => {
155 Name => 'photoshop',
156 SubDirectory => { TagTable => 'Image::ExifTool::XMP::photoshop' },
157 },
158 crs => {
159 Name => 'crs',
160 SubDirectory => { TagTable => 'Image::ExifTool::XMP::crs' },
161 },
162 # crss - it would be difficult to add the ability to write this
163 aux => {
164 Name => 'aux',
165 SubDirectory => { TagTable => 'Image::ExifTool::XMP::aux' },
166 },
167 tiff => {
168 Name => 'tiff',
169 SubDirectory => { TagTable => 'Image::ExifTool::XMP::tiff' },
170 },
171 exif => {
172 Name => 'exif',
173 SubDirectory => { TagTable => 'Image::ExifTool::XMP::exif' },
174 },
175 iptcCore => {
176 Name => 'iptcCore',
177 SubDirectory => { TagTable => 'Image::ExifTool::XMP::iptcCore' },
178 },
179 PixelLive => {
180 SubDirectory => { TagTable => 'Image::ExifTool::XMP::PixelLive' },
181 },
182 xmpPLUS => {
183 Name => 'xmpPLUS',
184 SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpPLUS' },
185 },
186 cc => {
187 Name => 'cc',
188 SubDirectory => { TagTable => 'Image::ExifTool::XMP::cc' },
189 },
190 dex => {
191 Name => 'dex',
192 SubDirectory => { TagTable => 'Image::ExifTool::XMP::dex' },
193 },
194 photomech => {
195 Name => 'photomech',
196 SubDirectory => { TagTable => 'Image::ExifTool::PhotoMechanic::XMP' },
197 },
198 mediapro => {
199 Name => 'mediapro',
200 SubDirectory => { TagTable => 'Image::ExifTool::XMP::MediaPro' },
201 },
202 microsoft => {
203 Name => 'microsoft',
204 SubDirectory => { TagTable => 'Image::ExifTool::XMP::Microsoft' },
205 },
206 lr => {
207 Name => 'lr',
208 SubDirectory => { TagTable => 'Image::ExifTool::XMP::Lightroom' },
209 },
210 DICOM => {
211 Name => 'DICOM',
212 SubDirectory => { TagTable => 'Image::ExifTool::XMP::DICOM' },
213 },
214);
215
216#
217# Tag tables for all XMP schemas:
218#
219# Writable - only need to define this for writable tags if not plain text
220# (boolean, integer, rational, real, date or lang-alt)
221# List - XMP list type (Bag, Seq or Alt, or set to 1 for elements in Struct lists)
222#
223# (Note that family 1 group names are generated from the property namespace, not
224# the group1 names below which exist so the groups will appear in the list.)
225#
226my %xmpTableDefaults = (
227 WRITE_PROC => \&WriteXMP,
228 WRITABLE => 'string',
229 LANG_INFO => \&GetLangInfo,
230);
231
232# Dublin Core schema properties (dc)
233%Image::ExifTool::XMP::dc = (
234 %xmpTableDefaults,
235 GROUPS => { 1 => 'XMP-dc', 2 => 'Other' },
236 NAMESPACE => 'dc',
237 NOTES => 'Dublin Core schema tags.',
238 contributor => { Groups => { 2 => 'Author' }, List => 'Bag' },
239 coverage => { },
240 creator => { Groups => { 2 => 'Author' }, List => 'Seq' },
241 date => { %dateTimeInfo, List => 'Seq' },
242 description => { Groups => { 2 => 'Image' }, Writable => 'lang-alt' },
243 'format' => { Groups => { 2 => 'Image' } },
244 identifier => { Groups => { 2 => 'Image' } },
245 language => { List => 'Bag' },
246 publisher => { Groups => { 2 => 'Author' }, List => 'Bag' },
247 relation => { List => 'Bag' },
248 rights => { Groups => { 2 => 'Author' }, Writable => 'lang-alt' },
249 source => { Groups => { 2 => 'Author' } },
250 subject => { Groups => { 2 => 'Image' }, List => 'Bag' },
251 title => { Groups => { 2 => 'Image' }, Writable => 'lang-alt' },
252 type => { Groups => { 2 => 'Image' }, List => 'Bag' },
253);
254
255# XMP Basic schema properties (xmp, xap)
256%Image::ExifTool::XMP::xmp = (
257 %xmpTableDefaults,
258 GROUPS => { 1 => 'XMP-xmp', 2 => 'Image' },
259 NAMESPACE => 'xmp',
260 NOTES => q{
261 XMP Basic schema tags. If the older "xap", "xapBJ", "xapMM" or "xapRights"
262 namespace prefixes are found, they are translated to the newer "xmp",
263 "xmpBJ", "xmpMM" and "xmpRights" prefixes for use in family 1 group names.
264 },
265 Advisory => { List => 'Bag' },
266 BaseURL => { },
267 CreateDate => { %dateTimeInfo },
268 CreatorTool => { },
269 Identifier => { Avoid => 1, List => 'Bag' },
270 Label => { },
271 MetadataDate=> { %dateTimeInfo },
272 ModifyDate => { %dateTimeInfo },
273 Nickname => { },
274 Rating => { Writable => 'integer' },
275 Thumbnails => {
276 SubDirectory => { },
277 Struct => 'Thumbnail',
278 List => 'Alt',
279 },
280 ThumbnailsHeight => { List => 1 },
281 ThumbnailsWidth => { List => 1 },
282 ThumbnailsFormat => { List => 1 },
283 ThumbnailsImage => {
284 # Eventually may want to handle this like a normal thumbnail image
285 # -- but not yet! (need to write EncodeBase64() routine....)
286 # Name => 'ThumbnailImage',
287 List => 1,
288 # translate Base64-encoded thumbnail
289 ValueConv => 'Image::ExifTool::XMP::DecodeBase64($val)',
290 },
291);
292
293# XMP Rights Management schema properties (xmpRights, xapRights)
294%Image::ExifTool::XMP::xmpRights = (
295 %xmpTableDefaults,
296 GROUPS => { 1 => 'XMP-xmpRights', 2 => 'Author' },
297 NAMESPACE => 'xmpRights',
298 NOTES => 'XMP Rights Management schema tags.',
299 Certificate => { },
300 Marked => { Writable => 'boolean' },
301 Owner => { List => 'Bag' },
302 UsageTerms => { Writable => 'lang-alt' },
303 WebStatement => { },
304);
305
306# XMP Media Management schema properties (xmpMM, xapMM)
307%Image::ExifTool::XMP::xmpMM = (
308 %xmpTableDefaults,
309 GROUPS => { 1 => 'XMP-xmpMM', 2 => 'Other' },
310 NAMESPACE => 'xmpMM',
311 NOTES => 'XMP Media Management schema tags.',
312 DerivedFrom => {
313 SubDirectory => { },
314 Struct => 'ResourceRef',
315 },
316 DerivedFromInstanceID => { },
317 DerivedFromDocumentID => { },
318 DerivedFromVersionID => { },
319 DerivedFromRenditionClass => { },
320 DerivedFromRenditionParams => { },
321 DerivedFromManager => { },
322 DerivedFromManagerVariant => { },
323 DerivedFromManageTo => { },
324 DerivedFromManageUI => { },
325 DocumentID => { },
326 History => {
327 SubDirectory => { },
328 Struct => 'ResourceEvent',
329 List => 'Seq',
330 },
331 # we treat these like list items since History is a list
332 HistoryAction => { List => 1 },
333 HistoryInstanceID => { List => 1 },
334 HistoryParameters => { List => 1 },
335 HistorySoftwareAgent => { List => 1 },
336 HistoryWhen => { List => 1, %dateTimeInfo },
337 InstanceID => { }, #PH (CS3)
338 ManagedFrom => { SubDirectory => { }, Struct => 'ResourceRef' },
339 ManagedFromInstanceID => { },
340 ManagedFromDocumentID => { },
341 ManagedFromVersionID => { },
342 ManagedFromRenditionClass => { },
343 ManagedFromRenditionParams => { },
344 ManagedFromManager => { },
345 ManagedFromManagerVariant => { },
346 ManagedFromManageTo => { },
347 ManagedFromManageUI => { },
348 Manager => { Groups => { 2 => 'Author' } },
349 ManageTo => { Groups => { 2 => 'Author' } },
350 ManageUI => { },
351 ManagerVariant => { },
352 PreservedFileName => { }, # undocumented
353 RenditionClass => { },
354 RenditionParams => { },
355 VersionID => { },
356 Versions => {
357 SubDirectory => { },
358 Struct => 'Version',
359 List => 'Seq',
360 },
361 VersionsComments => { List => 1 }, # we treat these like list items
362 VersionsEvent => { SubDirectory => { }, Struct => 'ResourceEvent' },
363 VersionsEventAction => { List => 1 },
364 VersionsEventInstanceID => { List => 1 },
365 VersionsEventParameters => { List => 1 },
366 VersionsEventSoftwareAgent => { List => 1 },
367 VersionsEventWhen => { List => 1, %dateTimeInfo },
368 VersionsModifyDate => { List => 1, %dateTimeInfo },
369 VersionsModifier => { List => 1 },
370 VersionsVersion => { List => 1 },
371 LastURL => { },
372 RenditionOf => { SubDirectory => { }, Struct => 'ResourceRef' },
373 RenditionOfInstanceID => { },
374 RenditionOfDocumentID => { },
375 RenditionOfVersionID => { },
376 RenditionOfRenditionClass => { },
377 RenditionOfRenditionParams => { },
378 RenditionOfManager => { },
379 RenditionOfManagerVariant => { },
380 RenditionOfManageTo => { },
381 RenditionOfManageUI => { },
382 SaveID => { Writable => 'integer' },
383);
384
385# XMP Basic Job Ticket schema properties (xmpBJ, xapBJ)
386%Image::ExifTool::XMP::xmpBJ = (
387 %xmpTableDefaults,
388 GROUPS => { 1 => 'XMP-xmpBJ', 2 => 'Other' },
389 NAMESPACE => 'xmpBJ',
390 NOTES => 'XMP Basic Job Ticket schema tags.',
391 # Note: JobRef is a List of structures. To accomplish this, we set the XMP
392 # List=>'Bag', but since SubDirectory is defined, this tag isn't writable
393 # directly. Then we need to set List=>1 for the members so the Writer logic
394 # will allow us to add list elements.
395 JobRef => {
396 SubDirectory => { },
397 Struct => 'JobRef',
398 List => 'Bag',
399 },
400 JobRefName => { List => 1 }, # we treat these like list items
401 JobRefId => { List => 1 },
402 JobRefUrl => { List => 1 },
403);
404
405# XMP Paged-Text schema properties (xmpTPg)
406%Image::ExifTool::XMP::xmpTPg = (
407 %xmpTableDefaults,
408 GROUPS => { 1 => 'XMP-xmpTPg', 2 => 'Image' },
409 NAMESPACE => 'xmpTPg',
410 NOTES => 'XMP Paged-Text schema tags.',
411 MaxPageSize => { SubDirectory => { }, Struct => 'Dimensions' },
412 MaxPageSizeW => { Writable => 'real' },
413 MaxPageSizeH => { Writable => 'real' },
414 MaxPageSizeUnit => { },
415 NPages => { Writable => 'integer' },
416 Fonts => {
417 SubDirectory => { },
418 Struct => 'Font',
419 List => 'Bag',
420 },
421 FontsFontName => { List => 1 },
422 FontsFontFamily => { List => 1 },
423 FontsFontFace => { List => 1 },
424 FontsFontType => { List => 1 },
425 FontsVersionString => { List => 1 },
426 FontsComposite => { List => 1, Writable => 'boolean' },
427 FontsFontFileName => { List => 1 },
428 FontsChildFontFiles => { List => 1 },
429 Colorants => {
430 SubDirectory => { },
431 Struct => 'Colorant',
432 List => 'Seq',
433 },
434 ColorantsSwatchName => { List => 1 },
435 ColorantsMode => { List => 1 },
436 ColorantsType => { List => 1 },
437 ColorantsCyan => { List => 1, Writable => 'real' },
438 ColorantsMagenta => { List => 1, Writable => 'real' },
439 ColorantsYellow => { List => 1, Writable => 'real' },
440 ColorantsBlack => { List => 1, Writable => 'real' },
441 ColorantsRed => { List => 1, Writable => 'integer' },
442 ColorantsGreen => { List => 1, Writable => 'integer' },
443 ColorantsBlue => { List => 1, Writable => 'integer' },
444 ColorantsL => { List => 1, Writable => 'real' },
445 ColorantsA => { List => 1, Writable => 'integer' },
446 ColorantsB => { List => 1, Writable => 'integer' },
447 PlateNames => { List => 'Seq' },
448);
449
450# XMP Dynamic Media schema properties (xmpDM)
451%Image::ExifTool::XMP::xmpDM = (
452 %xmpTableDefaults,
453 GROUPS => { 1 => 'XMP-xmpDM', 2 => 'Image' },
454 NAMESPACE => 'xmpDM',
455 NOTES => 'XMP Dynamic Media schema tags.',
456 projectRef => {
457 SubDirectory => { },
458 Struct => 'ProjectLink',
459 },
460 projectRefType => { },
461 projectRefPath => { },
462 videoFrameRate => { },
463 videoFrameSize => {
464 SubDirectory => { },
465 Struct => 'Dimensions',
466 },
467 videoFrameSizeW => { Writable => 'real' },
468 videoFrameSizeH => { Writable => 'real' },
469 videoFrameSizeUnit => { },
470 videoPixelAspectRatio => { Writable => 'rational' },
471 videoPixelDepth => { },
472 videoColorSpace => { },
473 videoAlphaMode => { },
474 videoAlphaPremultipleColor => {
475 SubDirectory => { },
476 Struct => 'Colorant',
477 },
478 videoAlphaPremultipleColorSwatchName => { },
479 videoAlphaPremultipleColorMode => { },
480 videoAlphaPremultipleColorType => { },
481 videoAlphaPremultipleColorCyan => { Writable => 'real' },
482 videoAlphaPremultipleColorMagenta => { Writable => 'real' },
483 videoAlphaPremultipleColorYellow => { Writable => 'real' },
484 videoAlphaPremultipleColorBlack => { Writable => 'real' },
485 videoAlphaPremultipleColorRed => { Writable => 'integer' },
486 videoAlphaPremultipleColorGreen => { Writable => 'integer' },
487 videoAlphaPremultipleColorBlue => { Writable => 'integer' },
488 videoAlphaPremultipleColorL => { Writable => 'real' },
489 videoAlphaPremultipleColorA => { Writable => 'integer' },
490 videoAlphaPremultipleColorB => { Writable => 'integer' },
491 videoAlphaUnityIsTransparent => { Writable => 'boolean' },
492 videoCompressor => { },
493 videoFieldOrder => { },
494 pullDown => { },
495 audioSampleRate => { Writable => 'integer' },
496 audioSampleType => { },
497 audioChannelType => { },
498 audioCompressor => { },
499 speakerPlacement => { },
500 fileDataRate => { Writable => 'rational' },
501 tapeName => { },
502 altTapeName => { },
503 startTimecode => {
504 SubDirectory => { },
505 Struct => 'Timecode',
506 },
507 startTimecodeTimeValue => { },
508 startTimecodeTimeFormat => { },
509 altTimecode => {
510 SubDirectory => { },
511 Struct => 'Timecode',
512 },
513 altTimecodeTimeValue => { },
514 altTimecodeTimeFormat => { },
515 duration => { },
516 scene => { Avoid => 1 },
517 shotName => { },
518 shotDate => { %dateTimeInfo },
519 shotLocation => { },
520 logComment => { },
521 markers => {
522 SubDirectory => { },
523 Struct => 'Marker',
524 List => 'Seq',
525 },
526 markersStartTime => { List => 1 },
527 markersDuration => { List => 1 },
528 markersComment => { List => 1 },
529 markersName => { List => 1 },
530 markersLocation => { List => 1 },
531 markersTarget => { List => 1 },
532 markersType => { List => 1 },
533 contributedMedia => {
534 SubDirectory => { },
535 Struct => 'Media',
536 List => 'Bag',
537 },
538 contributedMediaPath => { List => 1 },
539 contributedMediaTrack => { List => 1 },
540 contributedMediaStartTime => { List => 1 },
541 contributedMediaDuration => { List => 1 },
542 contributedMediaManaged => { List => 1, Writable => 'boolean' },
543 contributedMediaWebStatement => { List => 1 },
544 absPeakAudioFilePath => { },
545 relativePeakAudioFilePath => { },
546 videoModDate => { %dateTimeInfo },
547 audioModDate => { %dateTimeInfo },
548 metadataModDate => { %dateTimeInfo },
549 artist => { Avoid => 1, Groups => { 2 => 'Author' } },
550 album => { },
551 trackNumber => { Writable => 'integer' },
552 genre => { },
553 copyright => { Avoid => 1, Groups => { 2 => 'Author' } },
554 releaseDate => { %dateTimeInfo },
555 composer => { Groups => { 2 => 'Author' } },
556 engineer => { },
557 tempo => { Writable => 'real' },
558 instrument => { },
559 introTime => { },
560 outCue => { },
561 relativeTimestamp => { },
562 loop => { Writable => 'boolean' },
563 numberOfBeats => { Writable => 'real' },
564 key => { },
565 stretchMode => { },
566 timeScaleParams => {
567 SubDirectory => { },
568 Struct => 'TimeScaleStretch',
569 },
570 timeScaleParamsQuality => { },
571 timeScaleParamsFrameSize=> { Writable => 'real' },
572 timeScaleParamsFrameOverlappingPercentage => { Writable => 'real' },
573 resampleParams => {
574 SubDirectory => { },
575 Struct => 'ResampleStretch',
576 },
577 resampleParamsQuality => { },
578 beatSpliceParams => {
579 SubDirectory => { },
580 Struct => 'BeatSpliceStretch',
581 },
582 beatSpliceParamsUseFileBeatsMarker => { Writable => 'boolean' },
583 beatSpliceParamsRiseInDecibel => { Writable => 'real' },
584 beatSpliceParamsRiseInTimeDuration => { },
585 timeSignature => { },
586 scaleType => { },
587);
588
589# PDF schema properties (pdf)
590%Image::ExifTool::XMP::pdf = (
591 %xmpTableDefaults,
592 GROUPS => { 1 => 'XMP-pdf', 2 => 'Image' },
593 NAMESPACE => 'pdf',
594 NOTES => q{
595 Adobe PDF schema tags. The official XMP specification defines only
596 Keywords, PDFVersion and Producer. The other tags are included because they
597 have been observed in PDF files, but Creator, Subject and Title are avoided
598 when writing due to name conflicts with XMP-dc tags.
599 },
600 Author => { Groups => { 2 => 'Author' } }, #PH
601 ModDate => { %dateTimeInfo }, #PH
602 CreationDate=> { %dateTimeInfo }, #PH
603 Creator => { Groups => { 2 => 'Author' }, Avoid => 1 },
604 Subject => { Avoid => 1 },
605 Title => { Avoid => 1 },
606 Keywords => { },
607 PDFVersion => { },
608 Producer => { Groups => { 2 => 'Author' } },
609);
610
611# Photoshop schema properties (photoshop)
612%Image::ExifTool::XMP::photoshop = (
613 %xmpTableDefaults,
614 GROUPS => { 1 => 'XMP-photoshop', 2 => 'Image' },
615 NAMESPACE => 'photoshop',
616 NOTES => 'Adobe Photoshop schema tags.',
617 AuthorsPosition => { Groups => { 2 => 'Author' } },
618 CaptionWriter => { Groups => { 2 => 'Author' } },
619 Category => { },
620 City => { Groups => { 2 => 'Location' } },
621 Country => { Groups => { 2 => 'Location' } },
622 ColorMode => { }, #PH
623 Credit => { Groups => { 2 => 'Author' } },
624 DateCreated => { %dateTimeInfo },
625 History => { }, #PH (CS3)
626 Headline => { },
627 Instructions => { },
628 ICCProfile => { Name => 'ICCProfileName' }, #PH
629 LegacyIPTCDigest=> { }, #PH
630 SidecarForExtension => { }, #PH (CS3)
631 Source => { Groups => { 2 => 'Author' }, Avoid => 1 },
632 State => { Groups => { 2 => 'Location' } },
633 # the documentation doesn't show this as a 'Bag', but that's the
634 # way Photoshop7.0 writes it - PH
635 SupplementalCategories => { List => 'Bag' },
636 TransmissionReference => { },
637 Urgency => { Writable => 'integer' },
638);
639
640# Photoshop Camera Raw Schema properties (crs) - (ref 8,PH)
641%Image::ExifTool::XMP::crs = (
642 %xmpTableDefaults,
643 GROUPS => { 1 => 'XMP-crs', 2 => 'Image' },
644 NAMESPACE => 'crs',
645 NOTES => 'Photoshop Camera Raw Schema tags.',
646 AlreadyApplied => { Writable => 'boolean' }, #PH (written by LightRoom beta 4.1)
647 AutoBrightness => { Writable => 'boolean' },
648 AutoContrast => { Writable => 'boolean' },
649 AutoExposure => { Writable => 'boolean' },
650 AutoShadows => { Writable => 'boolean' },
651 BlueHue => { Writable => 'integer' },
652 BlueSaturation => { Writable => 'integer' },
653 Brightness => { Writable => 'integer' },
654 CameraProfile => { },
655 ChromaticAberrationB=> { Writable => 'integer' },
656 ChromaticAberrationR=> { Writable => 'integer' },
657 ColorNoiseReduction => { Writable => 'integer' },
658 Contrast => { Writable => 'integer', Avoid => 1 },
659 CropTop => { Writable => 'real' },
660 CropLeft => { Writable => 'real' },
661 CropBottom => { Writable => 'real' },
662 CropRight => { Writable => 'real' },
663 CropAngle => { Writable => 'real' },
664 CropWidth => { Writable => 'real' },
665 CropHeight => { Writable => 'real' },
666 CropUnits => {
667 Writable => 'integer',
668 PrintConv => {
669 0 => 'pixels',
670 1 => 'inches',
671 2 => 'cm',
672 },
673 },
674 Exposure => { Writable => 'real' },
675 GreenHue => { Writable => 'integer' },
676 GreenSaturation => { Writable => 'integer' },
677 HasCrop => { Writable => 'boolean' },
678 HasSettings => { Writable => 'boolean' },
679 LuminanceSmoothing => { Writable => 'integer' },
680 RawFileName => { },
681 RedHue => { Writable => 'integer' },
682 RedSaturation => { Writable => 'integer' },
683 Saturation => { Writable => 'integer', Avoid => 1 },
684 Shadows => { Writable => 'integer' },
685 ShadowTint => { Writable => 'integer' },
686 Sharpness => { Writable => 'integer', Avoid => 1 },
687 Temperature => { Writable => 'integer' },
688 Tint => { Writable => 'integer' },
689 ToneCurve => { List => 'Seq' },
690 ToneCurveName => {
691 PrintConv => {
692 Linear => 'Linear',
693 'Medium Contrast' => 'Medium Contrast',
694 'Strong Contrast' => 'Strong Contrast',
695 Custom => 'Custom',
696 },
697 },
698 Version => { },
699 VignetteAmount => { Writable => 'integer' },
700 VignetteMidpoint=> { Writable => 'integer' },
701 WhiteBalance => {
702 Avoid => 1,
703 PrintConv => {
704 'As Shot' => 'As Shot',
705 Auto => 'Auto',
706 Daylight => 'Daylight',
707 Cloudy => 'Cloudy',
708 Shade => 'Shade',
709 Tungsten => 'Tungsten',
710 Fluorescent => 'Fluorescent',
711 Flash => 'Flash',
712 Custom => 'Custom',
713 },
714 },
715 # new tags observed in Adobe Lightroom output - PH
716 CameraProfileDigest => { },
717 Clarity => { Writable => 'integer' },
718 ConvertToGrayscale => { Writable => 'boolean' },
719 Defringe => { Writable => 'integer' },
720 FillLight => { Writable => 'integer' },
721 HighlightRecovery => { Writable => 'integer' },
722 HueAdjustmentAqua => { Writable => 'integer' },
723 HueAdjustmentBlue => { Writable => 'integer' },
724 HueAdjustmentGreen => { Writable => 'integer' },
725 HueAdjustmentMagenta => { Writable => 'integer' },
726 HueAdjustmentOrange => { Writable => 'integer' },
727 HueAdjustmentPurple => { Writable => 'integer' },
728 HueAdjustmentRed => { Writable => 'integer' },
729 HueAdjustmentYellow => { Writable => 'integer' },
730 IncrementalTemperature => { Writable => 'integer' },
731 IncrementalTint => { Writable => 'integer' },
732 LuminanceAdjustmentAqua => { Writable => 'integer' },
733 LuminanceAdjustmentBlue => { Writable => 'integer' },
734 LuminanceAdjustmentGreen => { Writable => 'integer' },
735 LuminanceAdjustmentMagenta => { Writable => 'integer' },
736 LuminanceAdjustmentOrange => { Writable => 'integer' },
737 LuminanceAdjustmentPurple => { Writable => 'integer' },
738 LuminanceAdjustmentRed => { Writable => 'integer' },
739 LuminanceAdjustmentYellow => { Writable => 'integer' },
740 ParametricDarks => { Writable => 'integer' },
741 ParametricHighlights => { Writable => 'integer' },
742 ParametricHighlightSplit => { Writable => 'integer' },
743 ParametricLights => { Writable => 'integer' },
744 ParametricMidtoneSplit => { Writable => 'integer' },
745 ParametricShadows => { Writable => 'integer' },
746 ParametricShadowSplit => { Writable => 'integer' },
747 SaturationAdjustmentAqua => { Writable => 'integer' },
748 SaturationAdjustmentBlue => { Writable => 'integer' },
749 SaturationAdjustmentGreen => { Writable => 'integer' },
750 SaturationAdjustmentMagenta => { Writable => 'integer' },
751 SaturationAdjustmentOrange => { Writable => 'integer' },
752 SaturationAdjustmentPurple => { Writable => 'integer' },
753 SaturationAdjustmentRed => { Writable => 'integer' },
754 SaturationAdjustmentYellow => { Writable => 'integer' },
755 SharpenDetail => { Writable => 'integer' },
756 SharpenEdgeMasking => { Writable => 'integer' },
757 SharpenRadius => { Writable => 'real' },
758 SplitToningBalance => { Writable => 'integer' },
759 SplitToningHighlightHue => { Writable => 'integer' },
760 SplitToningHighlightSaturation => { Writable => 'integer' },
761 SplitToningShadowHue => { Writable => 'integer' },
762 SplitToningShadowSaturation => { Writable => 'integer' },
763 Vibrance => { Writable => 'integer' },
764);
765
766# Tiff schema properties (tiff)
767%Image::ExifTool::XMP::tiff = (
768 %xmpTableDefaults,
769 GROUPS => { 1 => 'XMP-tiff', 2 => 'Image' },
770 NAMESPACE => 'tiff',
771 NOTES => 'EXIF schema for TIFF tags.',
772 ImageWidth => { Writable => 'integer' },
773 ImageLength => {
774 Name => 'ImageHeight',
775 Writable => 'integer',
776 },
777 BitsPerSample => { Writable => 'integer', List => 'Seq' },
778 Compression => {
779 Writable => 'integer',
780 PrintConv => \%Image::ExifTool::Exif::compression,
781 },
782 PhotometricInterpretation => {
783 Writable => 'integer',
784 PrintConv => \%Image::ExifTool::Exif::photometricInterpretation,
785 },
786 Orientation => {
787 Writable => 'integer',
788 PrintConv => \%Image::ExifTool::Exif::orientation,
789 },
790 SamplesPerPixel => { Writable => 'integer' },
791 PlanarConfiguration => {
792 Writable => 'integer',
793 PrintConv => {
794 1 => 'Chunky',
795 2 => 'Planar',
796 },
797 },
798 YCbCrSubSampling => {
799 PrintConv => {
800 '1 1' => 'YCbCr4:4:4',
801 '2 1' => 'YCbCr4:2:2',
802 '2 2' => 'YCbCr4:2:0',
803 '4 1' => 'YCbCr4:1:1',
804 '4 2' => 'YCbCr4:1:0',
805 '1 2' => 'YCbCr4:4:0',
806 },
807 },
808 YCbCrPositioning => {
809 Writable => 'integer',
810 PrintConv => {
811 1 => 'Centered',
812 2 => 'Co-sited',
813 },
814 },
815 XResolution => { Writable => 'rational' },
816 YResolution => { Writable => 'rational' },
817 ResolutionUnit => {
818 Writable => 'integer',
819 PrintConv => {
820 1 => 'None',
821 2 => 'inches',
822 3 => 'cm',
823 },
824 },
825 TransferFunction => { Writable => 'integer', List => 'Seq' },
826 WhitePoint => { Writable => 'rational', List => 'Seq' },
827 PrimaryChromaticities => { Writable => 'rational', List => 'Seq' },
828 YCbCrCoefficients => { Writable => 'rational', List => 'Seq' },
829 ReferenceBlackWhite => { Writable => 'rational', List => 'Seq' },
830 DateTime => {
831 Description => 'Date/Time Modified',
832 %dateTimeInfo,
833 },
834 ImageDescription => { Writable => 'lang-alt' },
835 Make => { Groups => { 2 => 'Camera' } },
836 Model => {
837 Description => 'Camera Model Name',
838 Groups => { 2 => 'Camera' },
839 },
840 Software => { },
841 Artist => { Groups => { 2 => 'Author' } },
842 Copyright => {
843 Groups => { 2 => 'Author' },
844 Writable => 'lang-alt',
845 },
846 NativeDigest => { }, #PH
847);
848
849# Exif schema properties (exif)
850%Image::ExifTool::XMP::exif = (
851 %xmpTableDefaults,
852 GROUPS => { 1 => 'XMP-exif', 2 => 'Image' },
853 NAMESPACE => 'exif',
854 NOTES => 'EXIF schema for EXIF tags.',
855 ExifVersion => { },
856 FlashpixVersion => { },
857 ColorSpace => {
858 Writable => 'integer',
859 PrintConv => {
860 1 => 'sRGB',
861 2 => 'Adobe RGB',
862 0xffff => 'Uncalibrated',
863 0xffffffff => 'Uncalibrated',
864 },
865 },
866 ComponentsConfiguration => {
867 List => 'Seq',
868 Writable => 'integer',
869 PrintConv => {
870 0 => '.',
871 1 => 'Y',
872 2 => 'Cb',
873 3 => 'Cr',
874 4 => 'R',
875 5 => 'G',
876 6 => 'B',
877 },
878 },
879 CompressedBitsPerPixel => {
880 Writable => 'rational',
881 },
882 PixelXDimension => {
883 Name => 'ExifImageWidth',
884 Writable => 'integer',
885 },
886 PixelYDimension => {
887 Name => 'ExifImageLength',
888 Writable => 'integer',
889 },
890 MakerNote => { },
891 UserComment => {
892 Writable => 'lang-alt',
893 },
894 RelatedSoundFile => { },
895 DateTimeOriginal => {
896 Description => 'Date/Time Original',
897 %dateTimeInfo,
898 },
899 DateTimeDigitized => {
900 Description => 'Date/Time Digitized',
901 %dateTimeInfo,
902 },
903 ExposureTime => {
904 Writable => 'rational',
905 PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
906 PrintConvInv => 'eval $val',
907 },
908 FNumber => {
909 Writable => 'rational',
910 PrintConv => 'sprintf("%.1f",$val)',
911 PrintConvInv => '$val',
912 },
913 ExposureProgram => {
914 Groups => { 2 => 'Camera' },
915 Writable => 'integer',
916 PrintConv => {
917 1 => 'Manual',
918 2 => 'Program AE',
919 3 => 'Aperture-priority AE',
920 4 => 'Shutter speed priority AE',
921 5 => 'Creative (Slow speed)',
922 6 => 'Action (High speed)',
923 7 => 'Portrait',
924 8 => 'Landscape',
925 },
926 },
927 SpectralSensitivity => {
928 Groups => { 2 => 'Camera' },
929 },
930 ISOSpeedRatings => {
931 Name => 'ISO',
932 Writable => 'integer',
933 List => 'Seq',
934 },
935 OECF => {
936 Name => 'Opto-ElectricConvFactor',
937 Groups => { 2 => 'Camera' },
938 SubDirectory => { },
939 Struct => 'OECF',
940 },
941 OECFColumns => {
942 Groups => { 2 => 'Camera' },
943 Writable => 'integer',
944 },
945 OECFRows => {
946 Groups => { 2 => 'Camera' },
947 Writable => 'integer',
948 },
949 OECFNames => {
950 Groups => { 2 => 'Camera' },
951 List => 'Seq',
952 },
953 OECFValues => {
954 Groups => { 2 => 'Camera' },
955 Writable => 'rational',
956 List => 'Seq',
957 },
958 ShutterSpeedValue => {
959 Writable => 'rational',
960 ValueConv => 'abs($val)<100 ? 1/(2**$val) : 0',
961 PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)',
962 ValueConvInv => '$val>0 ? -log($val)/log(2) : 0',
963 # do eval to convert things like '1/100'
964 PrintConvInv => 'eval $val',
965 },
966 ApertureValue => {
967 Writable => 'rational',
968 ValueConv => 'sqrt(2) ** $val',
969 PrintConv => 'sprintf("%.1f",$val)',
970 ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
971 PrintConvInv => '$val',
972 },
973 BrightnessValue => {
974 Writable => 'rational',
975 },
976 ExposureBiasValue => {
977 Name => 'ExposureCompensation',
978 Writable => 'rational',
979 PrintConv => 'Image::ExifTool::Exif::ConvertFraction($val)',
980 PrintConvInv => '$val',
981 },
982 MaxApertureValue => {
983 Groups => { 2 => 'Camera' },
984 Writable => 'rational',
985 ValueConv => 'sqrt(2) ** $val',
986 PrintConv => 'sprintf("%.1f",$val)',
987 ValueConvInv => '$val>0 ? 2*log($val)/log(2) : 0',
988 PrintConvInv => '$val',
989 },
990 SubjectDistance => {
991 Groups => { 2 => 'Camera' },
992 Writable => 'rational',
993 PrintConv => '$val eq "inf" ? $val : "$val m"',
994 PrintConvInv => '$val=~s/\s*m$//;$val',
995 },
996 MeteringMode => {
997 Groups => { 2 => 'Camera' },
998 Writable => 'integer',
999 PrintConv => {
1000 1 => 'Average',
1001 2 => 'Center-weighted average',
1002 3 => 'Spot',
1003 4 => 'Multi-spot',
1004 5 => 'Multi-segment',
1005 6 => 'Partial',
1006 255 => 'Other',
1007 },
1008 },
1009 LightSource => {
1010 Groups => { 2 => 'Camera' },
1011 PrintConv => \%Image::ExifTool::Exif::lightSource,
1012 },
1013 Flash => {
1014 Groups => { 2 => 'Camera' },
1015 SubDirectory => { },
1016 Struct => 'Flash',
1017 },
1018 FlashFired => {
1019 Groups => { 2 => 'Camera' },
1020 Writable => 'boolean',
1021 },
1022 FlashReturn => {
1023 Groups => { 2 => 'Camera' },
1024 Writable => 'integer',
1025 PrintConv => {
1026 0 => 'No return detection',
1027 2 => 'Return not detected',
1028 3 => 'Return detected',
1029 },
1030 },
1031 FlashMode => {
1032 Groups => { 2 => 'Camera' },
1033 Writable => 'integer',
1034 PrintConv => {
1035 0 => 'Unknown',
1036 1 => 'On',
1037 2 => 'Off',
1038 3 => 'Auto',
1039 },
1040 },
1041 FlashFunction => {
1042 Groups => { 2 => 'Camera' },
1043 Writable => 'boolean',
1044 },
1045 FlashRedEyeMode => {
1046 Groups => { 2 => 'Camera' },
1047 Writable => 'boolean',
1048 },
1049 FocalLength=> {
1050 Groups => { 2 => 'Camera' },
1051 Writable => 'rational',
1052 PrintConv => 'sprintf("%.1fmm",$val)',
1053 PrintConvInv => '$val=~s/mm$//;$val',
1054 },
1055 SubjectArea => {
1056 Writable => 'integer',
1057 List => 'Seq',
1058 },
1059 FlashEnergy => {
1060 Groups => { 2 => 'Camera' },
1061 Writable => 'rational',
1062 },
1063 SpatialFrequencyResponse => {
1064 Groups => { 2 => 'Camera' },
1065 SubDirectory => { },
1066 Struct => 'OECF',
1067 },
1068 SpatialFrequencyResponseColumns => {
1069 Groups => { 2 => 'Camera' },
1070 Writable => 'integer',
1071 },
1072 SpatialFrequencyResponseRows => {
1073 Groups => { 2 => 'Camera' },
1074 Writable => 'integer',
1075 },
1076 SpatialFrequencyResponseNames => {
1077 Groups => { 2 => 'Camera' },
1078 List => 'Seq',
1079 },
1080 SpatialFrequencyResponseValues => {
1081 Groups => { 2 => 'Camera' },
1082 Writable => 'rational',
1083 List => 'Seq',
1084 },
1085 FocalPlaneXResolution => {
1086 Groups => { 2 => 'Camera' },
1087 Writable => 'rational',
1088 },
1089 FocalPlaneYResolution => {
1090 Groups => { 2 => 'Camera' },
1091 Writable => 'rational',
1092 },
1093 FocalPlaneResolutionUnit => {
1094 Groups => { 2 => 'Camera' },
1095 Writable => 'integer',
1096 PrintConv => {
1097 1 => 'None', # (not standard EXIF)
1098 2 => 'inches',
1099 3 => 'cm',
1100 4 => 'mm', # (not standard EXIF)
1101 5 => 'um', # (not standard EXIF)
1102 },
1103 },
1104 SubjectLocation => {
1105 Writable => 'integer',
1106 List => 'Seq',
1107 },
1108 ExposureIndex => {
1109 Writable => 'rational',
1110 },
1111 SensingMethod => {
1112 Groups => { 2 => 'Camera' },
1113 Writable => 'integer',
1114 PrintConv => {
1115 1 => 'Not defined',
1116 2 => 'One-chip color area',
1117 3 => 'Two-chip color area',
1118 4 => 'Three-chip color area',
1119 5 => 'Color sequential area',
1120 7 => 'Trilinear',
1121 8 => 'Color sequential linear',
1122 },
1123 },
1124 FileSource => {
1125 Writable => 'integer',
1126 PrintConv => {
1127 1 => 'Film Scanner',
1128 2 => 'Reflection Print Scanner',
1129 3 => 'Digital Camera',
1130 }
1131 },
1132 SceneType => { Writable => 'integer', PrintConv => { 1 => 'Directly photographed' } },
1133 CFAPattern => {
1134 SubDirectory => { },
1135 Struct => 'CFAPattern',
1136 },
1137 CFAPatternColumns => { Writable => 'integer' },
1138 CFAPatternRows => { Writable => 'integer' },
1139 CFAPatternValues => { List => 'Seq', Writable => 'integer' },
1140 CustomRendered => {
1141 Writable => 'integer',
1142 PrintConv => {
1143 0 => 'Normal',
1144 1 => 'Custom',
1145 },
1146 },
1147 ExposureMode => {
1148 Groups => { 2 => 'Camera' },
1149 Writable => 'integer',
1150 PrintConv => {
1151 0 => 'Auto',
1152 1 => 'Manual',
1153 2 => 'Auto bracket',
1154 },
1155 },
1156 WhiteBalance => {
1157 Groups => { 2 => 'Camera' },
1158 Writable => 'integer',
1159 PrintConv => {
1160 0 => 'Auto',
1161 1 => 'Manual',
1162 },
1163 },
1164 DigitalZoomRatio => { Writable => 'rational' },
1165 FocalLengthIn35mmFilm => {
1166 Name => 'FocalLengthIn35mmFormat',
1167 Writable => 'integer',
1168 Groups => { 2 => 'Camera' },
1169 },
1170 SceneCaptureType => {
1171 Groups => { 2 => 'Camera' },
1172 Writable => 'integer',
1173 PrintConv => {
1174 0 => 'Standard',
1175 1 => 'Landscape',
1176 2 => 'Portrait',
1177 3 => 'Night',
1178 },
1179 },
1180 GainControl => {
1181 Groups => { 2 => 'Camera' },
1182 Writable => 'integer',
1183 PrintConv => {
1184 0 => 'None',
1185 1 => 'Low gain up',
1186 2 => 'High gain up',
1187 3 => 'Low gain down',
1188 4 => 'High gain down',
1189 },
1190 },
1191 Contrast => {
1192 Groups => { 2 => 'Camera' },
1193 Writable => 'integer',
1194 PrintConv => {
1195 0 => 'Normal',
1196 1 => 'Low',
1197 2 => 'High',
1198 },
1199 PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
1200 },
1201 Saturation => {
1202 Groups => { 2 => 'Camera' },
1203 Writable => 'integer',
1204 PrintConv => {
1205 0 => 'Normal',
1206 1 => 'Low',
1207 2 => 'High',
1208 },
1209 PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
1210 },
1211 Sharpness => {
1212 Groups => { 2 => 'Camera' },
1213 Writable => 'integer',
1214 PrintConv => {
1215 0 => 'Normal',
1216 1 => 'Soft',
1217 2 => 'Hard',
1218 },
1219 PrintConvInv => 'Image::ExifTool::Exif::ConvertParameter($val)',
1220 },
1221 DeviceSettingDescription => {
1222 Groups => { 2 => 'Camera' },
1223 SubDirectory => { },
1224 Struct => 'DeviceSettings',
1225 },
1226 DeviceSettingDescriptionColumns => {
1227 Groups => { 2 => 'Camera' },
1228 Writable => 'integer',
1229 },
1230 DeviceSettingDescriptionRows => {
1231 Groups => { 2 => 'Camera' },
1232 Writable => 'integer',
1233 },
1234 DeviceSettingDescriptionSettings => {
1235 Groups => { 2 => 'Camera' },
1236 List => 'Seq',
1237 },
1238 SubjectDistanceRange => {
1239 Groups => { 2 => 'Camera' },
1240 Writable => 'integer',
1241 PrintConv => {
1242 0 => 'Unknown',
1243 1 => 'Macro',
1244 2 => 'Close',
1245 3 => 'Distant',
1246 },
1247 },
1248 ImageUniqueID => { },
1249 GPSVersionID => { Groups => { 2 => 'Location' } },
1250 GPSLatitude => { Groups => { 2 => 'Location' }, %latConv },
1251 GPSLongitude => { Groups => { 2 => 'Location' }, %longConv },
1252 GPSAltitudeRef => {
1253 Groups => { 2 => 'Location' },
1254 Writable => 'integer',
1255 PrintConv => {
1256 0 => 'Above Sea Level',
1257 1 => 'Below Sea Level',
1258 },
1259 },
1260 GPSAltitude => {
1261 Groups => { 2 => 'Location' },
1262 Writable => 'rational',
1263 PrintConv => '$val eq "inf" ? $val : "$val m"',
1264 PrintConvInv => '$val=~s/\s*m$//;$val',
1265 },
1266 GPSTimeStamp => { %dateTimeInfo },
1267 GPSSatellites => { Groups => { 2 => 'Location' } },
1268 GPSStatus => {
1269 Groups => { 2 => 'Location' },
1270 PrintConv => {
1271 A => 'Measurement In Progress',
1272 V => 'Measurement Interoperability',
1273 },
1274 },
1275 GPSMeasureMode => {
1276 Groups => { 2 => 'Location' },
1277 Writable => 'integer',
1278 PrintConv => {
1279 2 => '2-Dimensional',
1280 3 => '3-Dimensional',
1281 },
1282 },
1283 GPSDOP => {
1284 Groups => { 2 => 'Location' },
1285 Writable => 'rational',
1286 },
1287 GPSSpeedRef => {
1288 Groups => { 2 => 'Location' },
1289 PrintConv => {
1290 K => 'km/h',
1291 M => 'mph',
1292 N => 'knots',
1293 },
1294 },
1295 GPSSpeed => {
1296 Groups => { 2 => 'Location' },
1297 Writable => 'rational',
1298 },
1299 GPSTrackRef => {
1300 Groups => { 2 => 'Location' },
1301 PrintConv => {
1302 M => 'Magnetic North',
1303 T => 'True North',
1304 },
1305 },
1306 GPSTrack => {
1307 Groups => { 2 => 'Location' },
1308 Writable => 'rational',
1309 },
1310 GPSImgDirectionRef => {
1311 PrintConv => {
1312 M => 'Magnetic North',
1313 T => 'True North',
1314 },
1315 },
1316 GPSImgDirection => {
1317 Groups => { 2 => 'Location' },
1318 Writable => 'rational',
1319 },
1320 GPSMapDatum => { Groups => { 2 => 'Location' } },
1321 GPSDestLatitude => { Groups => { 2 => 'Location' }, %latConv },
1322 GPSDestLongitude=> { Groups => { 2 => 'Location' }, %longConv },
1323 GPSDestBearingRef => {
1324 Groups => { 2 => 'Location' },
1325 PrintConv => {
1326 M => 'Magnetic North',
1327 T => 'True North',
1328 },
1329 },
1330 GPSDestBearing => {
1331 Groups => { 2 => 'Location' },
1332 Writable => 'rational',
1333 },
1334 GPSDestDistanceRef => {
1335 Groups => { 2 => 'Location' },
1336 PrintConv => {
1337 K => 'Kilometers',
1338 M => 'Miles',
1339 N => 'Nautical Miles',
1340 },
1341 },
1342 GPSDestDistance => {
1343 Groups => { 2 => 'Location' },
1344 Writable => 'rational',
1345 },
1346 GPSProcessingMethod => { Groups => { 2 => 'Location' } },
1347 GPSAreaInformation => { Groups => { 2 => 'Location' } },
1348 GPSDifferential => {
1349 Groups => { 2 => 'Location' },
1350 Writable => 'integer',
1351 PrintConv => {
1352 0 => 'No Correction',
1353 1 => 'Differential Corrected',
1354 },
1355 },
1356 NativeDigest => { }, #PH
1357);
1358
1359# Auxiliary schema properties (aux) - not fully documented
1360%Image::ExifTool::XMP::aux = (
1361 %xmpTableDefaults,
1362 GROUPS => { 1 => 'XMP-aux', 2 => 'Camera' },
1363 NAMESPACE => 'aux',
1364 NOTES => 'Photoshop Auxiliary schema tags.',
1365 Firmware => { }, #7
1366 FlashCompensation => { Writable => 'rational' }, #7
1367 ImageNumber => { }, #7
1368 LensInfo => { }, #7
1369 Lens => { },
1370 OwnerName => { }, #7
1371 SerialNumber => { },
1372);
1373
1374# IPTC Core schema properties (Iptc4xmpCore)
1375%Image::ExifTool::XMP::iptcCore = (
1376 %xmpTableDefaults,
1377 GROUPS => { 1 => 'XMP-iptcCore', 2 => 'Author' },
1378 NAMESPACE => 'Iptc4xmpCore',
1379 NOTES => q{
1380 IPTC Core schema tags. The actual IPTC Core namespace schema prefix is
1381 "Iptc4xmpCore", which is the prefix recorded in the file, but ExifTool
1382 shortens this for the "XMP-iptcCore" family 1 group name.
1383 },
1384 CountryCode => { Groups => { 2 => 'Location' } },
1385 CreatorContactInfo => {
1386 SubDirectory => { },
1387 Struct => 'ContactInfo',
1388 },
1389 CreatorContactInfoCiAdrCity => { Description => 'Creator City' },
1390 CreatorContactInfoCiAdrCtry => { Description => 'Creator Country' },
1391 CreatorContactInfoCiAdrExtadr => { Description => 'Creator Address' },
1392 CreatorContactInfoCiAdrPcode => { Description => 'Creator Postal Code' },
1393 CreatorContactInfoCiAdrRegion => { Description => 'Creator Region' },
1394 CreatorContactInfoCiEmailWork => { Description => 'Creator Work Email' },
1395 CreatorContactInfoCiTelWork => { Description => 'Creator Work Telephone' },
1396 CreatorContactInfoCiUrlWork => { Description => 'Creator Work URL' },
1397 IntellectualGenre => { Groups => { 2 => 'Other' } },
1398 Location => { Groups => { 2 => 'Location' } },
1399 Scene => { Groups => { 2 => 'Other' }, List => 'Bag' },
1400 SubjectCode => { Groups => { 2 => 'Other' }, List => 'Bag' },
1401);
1402
1403# PixelLive schema properties (PixelLive) (ref 3)
1404%Image::ExifTool::XMP::PixelLive = (
1405 GROUPS => { 1 => 'XMP-PixelLive', 2 => 'Image' },
1406 NAMESPACE => 'PixelLive',
1407 WRITE_PROC => \&WriteXMP,
1408 NOTES => q{
1409 PixelLive schema tags. These tags are not writable becase they are very
1410 uncommon and I haven't been able to locate a reference which gives the
1411 namespace URI.
1412 },
1413 AUTHOR => { Name => 'Author', Avoid => 1, Groups => { 2 => 'Author'} },
1414 COMMENTS => { Name => 'Comments', Avoid => 1 },
1415 COPYRIGHT => { Name => 'Copyright',Avoid => 1, Groups => { 2 => 'Author'} },
1416 DATE => { Name => 'Date', Avoid => 1, Groups => { 2 => 'Time'} },
1417 GENRE => { Name => 'Genre', Avoid => 1 },
1418 TITLE => { Name => 'Title', Avoid => 1 },
1419);
1420
1421# Picture Licensing Universal System schema properties (xmpPLUS)
1422%Image::ExifTool::XMP::xmpPLUS = (
1423 %xmpTableDefaults,
1424 GROUPS => { 1 => 'XMP-xmpPLUS', 2 => 'Author' },
1425 NAMESPACE => 'xmpPLUS',
1426 NOTES => 'XMP Picture Licensing Universal System (PLUS) schema tags.',
1427 CreditLineReq => { Writable => 'boolean' },
1428 ReuseAllowed => { Writable => 'boolean' },
1429);
1430
1431# Creative Commons schema properties (cc) (ref 5)
1432%Image::ExifTool::XMP::cc = (
1433 %xmpTableDefaults,
1434 GROUPS => { 1 => 'XMP-cc', 2 => 'Author' },
1435 NAMESPACE => 'cc',
1436 NOTES => 'Creative Commons schema tags.',
1437 license => { },
1438);
1439
1440# Description Explorer schema properties (dex) (ref 6)
1441%Image::ExifTool::XMP::dex = (
1442 %xmpTableDefaults,
1443 GROUPS => { 1 => 'XMP-dex', 2 => 'Image' },
1444 NAMESPACE => 'dex',
1445 NOTES => q{
1446 Description Explorer schema tags. These tags are not very common. The
1447 Source and Rating tags are avoided when writing due to name conflicts with
1448 other XMP tags.
1449 },
1450 crc32 => { Name => 'CRC32', Writable => 'integer' },
1451 source => { Avoid => 1 },
1452 shortdescription => {
1453 Name => 'ShortDescription',
1454 Writable => 'lang-alt',
1455 },
1456 licensetype => {
1457 Name => 'LicenseType',
1458 PrintConv => {
1459 unknown => 'Unknown',
1460 shareware => 'Shareware',
1461 freeware => 'Freeware',
1462 adware => 'Adware',
1463 demo => 'Demo',
1464 commercial => 'Commercial',
1465 'public domain' => 'Public Domain',
1466 'open source' => 'Open Source',
1467 },
1468 },
1469 revision => { },
1470 rating => { Avoid => 1 },
1471 os => { Name => 'OS', Writable => 'integer' },
1472 ffid => { Name => 'FFID' },
1473);
1474
1475# IView MediaPro schema properties (mediapro) (ref PH)
1476%Image::ExifTool::XMP::MediaPro = (
1477 %xmpTableDefaults,
1478 GROUPS => { 1 => 'XMP-mediapro', 2 => 'Image' },
1479 NAMESPACE => 'mediapro',
1480 NOTES => 'IView MediaPro schema tags.',
1481 Event => { },
1482 Location => {
1483 Avoid => 1,
1484 Groups => { 2 => 'Location' },
1485 Notes => 'avoided due to conflict with XMP-iptcCore:Location',
1486 },
1487 Status => { },
1488 People => { List => 'Bag' },
1489 UserFields => { List => 'Bag' },
1490 CatalogSets => { List => 'Bag' },
1491);
1492
1493# Microsoft Photo schema properties (MicrosoftPhoto) (ref PH)
1494%Image::ExifTool::XMP::Microsoft = (
1495 %xmpTableDefaults,
1496 GROUPS => { 1 => 'XMP-microsoft', 2 => 'Image' },
1497 NAMESPACE => 'MicrosoftPhoto',
1498 NOTES => q{
1499 Microsoft Photo schema tags. This is likely not a complete list, but
1500 represents tags which have been observed in sample images. The actual
1501 namespace prefix is "MicrosoftPhoto", but ExifTool shortens this to
1502 "XMP-microsoft" in the family 1 group name.
1503 },
1504 CameraSerialNumber => { },
1505 DateAcquired => { %dateTimeInfo },
1506 FlashManufacturer => { },
1507 FlashModel => { },
1508 LastKeywordIPTC => { List => 'Bag' },
1509 LastKeywordXMP => { List => 'Bag' },
1510 LensManufacturer => { },
1511 LensModel => { },
1512 Rating => {
1513 Name => 'RatingPercent',
1514 Notes => q{
1515 normal Rating values of 1,2,3,4 and 5 stars correspond to RatingPercent
1516 values of 1,25,50,75 and 99 respectively
1517 },
1518 },
1519);
1520
1521# Adobe Lightroom schema properties (lr) (ref PH)
1522%Image::ExifTool::XMP::Lightroom = (
1523 %xmpTableDefaults,
1524 GROUPS => { 1 => 'XMP-lr', 2 => 'Image' },
1525 NAMESPACE => 'lr',
1526 NOTES => 'Adobe Lightroom "lr" schema tags.',
1527 privateRTKInfo => { },
1528 hierarchicalSubject => { List => 'Bag' },
1529);
1530
1531# DICOM schema properties (DICOM) (ref PH, written by CS3)
1532%Image::ExifTool::XMP::DICOM = (
1533 %xmpTableDefaults,
1534 GROUPS => { 1 => 'XMP-DICOM', 2 => 'Image' },
1535 NAMESPACE => 'DICOM',
1536 NOTES => 'DICOM schema tags.',
1537 # change some tag names to correspond with DICOM tags
1538 PatientName => { Name => 'PatientsName' },
1539 PatientID => { },
1540 PatientSex => { Name => 'PatientsSex' },
1541 PatientDOB => {
1542 Name => 'PatientsBirthDate',
1543 %dateTimeInfo,
1544 },
1545 StudyID => { },
1546 StudyPhysician => { },
1547 StudyDateTime => { %dateTimeInfo },
1548 StudyDescription => { },
1549 SeriesNumber => { },
1550 SeriesModality => { },
1551 SeriesDateTime => { %dateTimeInfo },
1552 SeriesDescription => { },
1553 EquipmentInstitution => { },
1554 EquipmentManufacturer => { },
1555);
1556
1557# table to add tags in other namespaces
1558%Image::ExifTool::XMP::other = (
1559 GROUPS => { 2 => 'Unknown' },
1560 LANG_INFO => \&GetLangInfo,
1561);
1562
1563# Composite XMP tags
1564%Image::ExifTool::XMP::Composite = (
1565 # get latitude/logitude reference from XMP lat/long tags
1566 # (used to set EXIF GPS position from XMP tags)
1567 GPSLatitudeRef => {
1568 Require => {
1569 0 => 'XMP:GPSLatitude',
1570 },
1571 ValueConv => '$val[0] < 0 ? "S" : "N"',
1572 PrintConv => {
1573 N => 'North',
1574 S => 'South',
1575 },
1576 },
1577 GPSLongitudeRef => {
1578 Require => {
1579 0 => 'XMP:GPSLongitude',
1580 },
1581 ValueConv => '$val[0] < 0 ? "W" : "E"',
1582 PrintConv => {
1583 E => 'East',
1584 W => 'West',
1585 },
1586 },
1587);
1588
1589# add our composite tags
1590Image::ExifTool::AddCompositeTags('Image::ExifTool::XMP');
1591
1592#------------------------------------------------------------------------------
1593# AutoLoad our writer routines when necessary
1594#
1595sub AUTOLOAD
1596{
1597 return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
1598}
1599
1600#------------------------------------------------------------------------------
1601# Escape necessary XML characters in UTF-8 string
1602# Inputs: 0) string to be escaped
1603# Returns: escaped string
1604my %charName = ('"'=>'quot', '&'=>'amp', "'"=>'#39', '<'=>'lt', '>'=>'gt');
1605sub EscapeXML($)
1606{
1607 my $str = shift;
1608 $str =~ s/([&><'"])/&$charName{$1};/sg; # escape necessary XML characters
1609 return $str;
1610}
1611
1612#------------------------------------------------------------------------------
1613# Unescape XML character references (entities and numerical)
1614# Inputs: 0) string to be unescaped
1615# 1) optional hash reference to convert entity names to numbers
1616# Returns: unescaped string
1617my %charNum = ('quot'=>34, 'amp'=>38, 'apos'=>39, 'lt'=>60, 'gt'=>62);
1618sub UnescapeXML($;$)
1619{
1620 my ($str, $conv) = @_;
1621 $conv = \%charNum unless $conv;
1622 $str =~ s/&(#?\w+);/UnescapeChar($1,$conv)/sge;
1623 return $str;
1624}
1625
1626#------------------------------------------------------------------------------
1627# Convert XML character reference to UTF-8
1628# Inputs: 0) XML character reference stripped of the '&' and ';' (ie. 'quot', '#34', '#x22')
1629# 1) hash reference for looking up character numbers by name
1630# Returns: UTF-8 equivalent (or original character on conversion error)
1631sub UnescapeChar($$)
1632{
1633 my ($ch, $conv) = @_;
1634 my $val = $$conv{$ch};
1635 unless (defined $val) {
1636 if ($ch =~ /^#x([0-9a-fA-F]+)$/) {
1637 $val = hex($1);
1638 } elsif ($ch =~ /^#(\d+)$/) {
1639 $val = $1;
1640 } else {
1641 return "&$ch;"; # should issue a warning here?
1642 }
1643 }
1644 if ($val < 0x80) {
1645 return chr($val); # simple ASCII
1646 } elsif ($] >= 5.006001) {
1647 return pack('C0U', $val);
1648 } else {
1649 return Image::ExifTool::PackUTF8($val);
1650 }
1651}
1652
1653#------------------------------------------------------------------------------
1654# Utility routine to decode a base64 string
1655# Inputs: 0) base64 string
1656# Returns: reference to decoded data
1657sub DecodeBase64($)
1658{
1659 local($^W) = 0; # unpack('u',...) gives bogus warning in 5.00[123]
1660 my $str = shift;
1661
1662 # truncate at first unrecognized character (base 64 data
1663 # may only contain A-Z, a-z, 0-9, +, /, =, or white space)
1664 $str =~ s/[^A-Za-z0-9+\/= \t\n\r\f].*//;
1665 # translate to uucoded and remove padding and white space
1666 $str =~ tr/A-Za-z0-9+\/= \t\n\r\f/ -_/d;
1667
1668 # convert the data to binary in chunks
1669 my $chunkSize = 60;
1670 my $uuLen = pack('c', 32 + $chunkSize * 3 / 4); # calculate length byte
1671 my $dat = '';
1672 my ($i, $substr);
1673 # loop through the whole chunks
1674 my $len = length($str) - $chunkSize;
1675 for ($i=0; $i<=$len; $i+=$chunkSize) {
1676 $substr = substr($str, $i, $chunkSize); # get a chunk of the data
1677 $dat .= unpack('u', $uuLen . $substr); # decode it
1678 }
1679 $len += $chunkSize;
1680 # handle last partial chunk if necessary
1681 if ($i < $len) {
1682 $uuLen = pack('c', 32 + ($len-$i) * 3 / 4); # recalculate length
1683 $substr = substr($str, $i, $len-$i); # get the last partial chunk
1684 $dat .= unpack('u', $uuLen . $substr); # decode it
1685 }
1686 return \$dat;
1687}
1688
1689#------------------------------------------------------------------------------
1690# Generate a name for this XMP tag
1691# Inputs: 0) reference to tag property name list
1692# Returns: tagID and outtermost interesting namespace
1693sub GetXMPTagID($)
1694{
1695 my $props = shift;
1696 my ($tag, $prop, $namespace);
1697 foreach $prop (@$props) {
1698 # split name into namespace and property name
1699 # (Note: namespace can be '' for property qualifiers)
1700 my ($ns, $nm) = ($prop =~ /(.*?):(.*)/) ? ($1, $2) : ('', $prop);
1701 $nm =~ s/ .*//; # remove nodeID if it exists
1702 if ($ignoreNamespace{$ns}) {
1703 # special case: don't ignore rdf numbered items
1704 next unless $prop =~ /^rdf:(_\d+)$/;
1705 $tag .= $1;
1706 } else {
1707 # all uppercase is ugly, so convert it
1708 if ($nm !~ /[a-z]/) {
1709 my $xlatNS = $xlatNamespace{$ns} || $ns;
1710 my $info = $Image::ExifTool::XMP::Main{$xlatNS};
1711 my $table;
1712 if (ref $info eq 'HASH' and $info->{SubDirectory}) {
1713 $table = GetTagTable($info->{SubDirectory}->{TagTable});
1714 }
1715 unless ($table and $table->{$nm}) {
1716 $nm = lc($nm);
1717 $nm =~ s/_([a-z])/\u$1/g;
1718 }
1719 }
1720 if (defined $tag) {
1721 $tag .= ucfirst($nm); # add to tag name
1722 } else {
1723 $tag = $nm;
1724 }
1725 }
1726 # save namespace of first property to contribute to tag name
1727 $namespace = $ns unless defined $namespace;
1728 }
1729 if (wantarray) {
1730 return ($tag, $namespace);
1731 } else {
1732 return $tag;
1733 }
1734}
1735
1736#------------------------------------------------------------------------------
1737# Get localized version of tagInfo hash
1738# Inputs: 0) tagInfo hash ref, 1) language code (ie. "x-default")
1739# Returns: new tagInfo hash ref, or undef if invalid
1740sub GetLangInfo($$)
1741{
1742 my ($tagInfo, $langCode) = @_;
1743 # only allow alternate language tags in lang-alt lists
1744 return undef unless $$tagInfo{Writable} and $$tagInfo{Writable} eq 'lang-alt';
1745 $langCode =~ tr/_/-/; # RFC 3066 specifies '-' as a separator
1746 return Image::ExifTool::GetLangInfo($tagInfo, $langCode);
1747}
1748
1749#------------------------------------------------------------------------------
1750# Get standard case for language code
1751# Inputs: 0) Language code
1752# Returns: Language code in standard case
1753sub StandardLangCase($)
1754{
1755 my $lang = shift;
1756 # make 2nd subtag uppercase only if it is 2 letters
1757 return lc($1) . uc($2) . lc($3) if $lang =~ /^([a-z]{2,3}|[xi])(-[a-z]{2})\b(.*)/i;
1758 return lc($lang);
1759}
1760
1761#------------------------------------------------------------------------------
1762# Scan for XMP in a file
1763# Inputs: 0) ExifTool object ref, 1) RAF reference
1764# Returns: 1 if xmp was found, 0 otherwise
1765# Notes: Currently only recognizes UTF8-encoded XMP
1766sub ScanForXMP($$)
1767{
1768 my ($exifTool, $raf) = @_;
1769 my ($buff, $xmp);
1770 my $lastBuff = '';
1771
1772 $exifTool->VPrint(0,"Scanning for XMP\n");
1773 for (;;) {
1774 defined $buff or $raf->Read($buff, 65536) or return 0;
1775 unless (defined $xmp) {
1776 $lastBuff .= $buff;
1777 unless ($lastBuff =~ /(<\?xpacket begin=)/g) {
1778 # must keep last 15 bytes to match 16-byte "xpacket begin" string
1779 $lastBuff = length($buff) <= 15 ? $buff : substr($buff, -15);
1780 undef $buff;
1781 next;
1782 }
1783 $xmp = $1;
1784 $buff = substr($lastBuff, pos($lastBuff));
1785 }
1786 $xmp .= $buff;
1787 my $pos = length($xmp) - length($buff) - 18; # 18 is length("<xpacket end...")-1
1788 pos($xmp) = $pos if $pos > 0;
1789 if ($xmp =~ /<\?xpacket end=['"][wr]['"]\?>/g) {
1790 $xmp = substr($xmp, 0, pos($xmp));
1791 # XMP is not valid if it contains null bytes
1792 pos($xmp) = 0;
1793 last unless $xmp =~ /\0/g;
1794 my $null = pos $xmp;
1795 while ($xmp =~ /\0/g) {
1796 $null = pos($xmp);
1797 }
1798 # re-parse beginning after last null byte
1799 $buff = substr($xmp, $null);
1800 $lastBuff = '';
1801 undef $xmp;
1802 } else {
1803 undef $buff;
1804 }
1805 }
1806 unless ($exifTool->{VALUE}->{FileType}) {
1807 $exifTool->{FILE_TYPE} = $exifTool->{FILE_EXT};
1808 $exifTool->SetFileType('<unknown file containing XMP>');
1809 }
1810 my %dirInfo = (
1811 DataPt => \$xmp,
1812 DirLen => length $xmp,
1813 DataLen => length $xmp,
1814 );
1815 ProcessXMP($exifTool, \%dirInfo);
1816 return 1;
1817}
1818
1819#------------------------------------------------------------------------------
1820# We found an XMP property name/value
1821# Inputs: 0) ExifTool object ref, 1) Pointer to tag table
1822# 2) reference to array of XMP property names (last is current property)
1823# 3) property value, 4) xml:lang attribute (or undef)
1824# Returns: 1 if valid tag was found
1825sub FoundXMP($$$$;$)
1826{
1827 local $_;
1828 my ($exifTool, $tagTablePtr, $props, $val, $lang) = @_;
1829
1830 my ($tag, $namespace) = GetXMPTagID($props);
1831 return 0 unless $tag; # ignore things that aren't valid tags
1832
1833 # translate namespace if necessary
1834 $namespace = $xlatNamespace{$namespace} if $xlatNamespace{$namespace};
1835 my $info = $tagTablePtr->{$namespace};
1836 my $table;
1837 if ($info) {
1838 $table = $info->{SubDirectory}->{TagTable} or warn "Missing TagTable for $tag!\n";
1839 } else {
1840 $table = 'Image::ExifTool::XMP::other';
1841 }
1842 # change pointer to the table for this namespace
1843 $tagTablePtr = GetTagTable($table);
1844
1845 # look up this tag in the appropriate table
1846 my $tagInfo = $exifTool->GetTagInfo($tagTablePtr, $tag);
1847
1848 unless ($tagInfo) {
1849 if ($exifTool->{OPTIONS}->{Verbose}) {
1850 my $group1 = $namespace ? "XMP-$namespace" : $tagTablePtr->{GROUPS}->{1};
1851 $exifTool->VPrint(0, $exifTool->{INDENT}, "[adding $group1:$tag]\n");
1852 }
1853 # construct tag information for this unknown tag
1854 $tagInfo = { Name => ucfirst($tag), WasAdded => 1 };
1855 # make this a List type if necessary and not lang-alt
1856 $$tagInfo{List} = $1 if @$props > 2 and not $lang and
1857 $$props[-1] =~ /^rdf:li \d+$/ and $$props[-2] =~ /^rdf:(Bag|Seq|Alt)/;
1858 Image::ExifTool::AddTagToTable($tagTablePtr, $tag, $tagInfo);
1859 }
1860 if (defined $lang and lc($lang) ne 'x-default') {
1861 $lang = StandardLangCase($lang);
1862 my $langInfo = GetLangInfo($tagInfo, $lang);
1863 $tagInfo = $langInfo if $langInfo;
1864 }
1865 if ($exifTool->{OPTIONS}->{Charset} ne 'UTF8' and $val =~ /[\x80-\xff]/) {
1866 # convert from UTF-8 to specified characterset
1867 $val = $exifTool->UTF82Charset($val);
1868 }
1869 # convert rational and date values to a more sensible format
1870 my $fmt = $$tagInfo{Writable};
1871 my $new = $$tagInfo{WasAdded};
1872 if ($fmt or $new) {
1873 if (($new or $fmt eq 'rational') and $val =~ m{^(-?\d+)/(-?\d+)$}) {
1874 $val = $1 / $2 if $2; # calculate quotient
1875 } elsif ($new or $fmt eq 'date') {
1876 if ($val =~ /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}:\d{2})(:\d{2})?(\S*)$/) {
1877 my $s = $5 || ':00'; # seconds may be missing
1878 $val = "$1:$2:$3 $4$s$6"; # convert back to EXIF time format
1879 } elsif ($fmt and $fmt eq 'date' and $val =~ /^(\d{4})(-\d{2}){0,2}/) {
1880 $val =~ tr/-/:/;
1881 }
1882 }
1883 }
1884 $tag = $exifTool->FoundTag($tagInfo, UnescapeXML($val));
1885 $exifTool->SetGroup1($tag, "XMP-$namespace") if $namespace;
1886
1887 if ($exifTool->Options('Verbose')) {
1888 my $tagID = join('/',@$props);
1889 $exifTool->VerboseInfo($tagID, $tagInfo, Value=>$val);
1890 }
1891 return 1;
1892}
1893
1894#------------------------------------------------------------------------------
1895# Recursively parse nested XMP data element
1896# Inputs: 0) ExifTool object reference
1897# 1) Pointer to tag table
1898# 2) reference to XMP data
1899# 3) start of xmp element
1900# 4) reference to array of enclosing XMP property names (undef if none)
1901# 5) reference to blank node information hash
1902# Returns: Number of contained XMP elements
1903sub ParseXMPElement($$$;$$$)
1904{
1905 my ($exifTool, $tagTablePtr, $dataPt, $start, $propListPt, $blankInfo) = @_;
1906 my $count = 0;
1907 my $isWriting = $exifTool->{XMP_CAPTURE};
1908 $start or $start = 0;
1909 $propListPt or $propListPt = [ ];
1910
1911 my $processBlankInfo;
1912 # create empty blank node information hash if necessary
1913 $blankInfo or $blankInfo = $processBlankInfo = { Prop => { } };
1914 # keep track of current nodeID at this nesting level
1915 my $oldNodeID = $$blankInfo{NodeID};
1916
1917 pos($$dataPt) = $start;
1918 Element: for (;;) {
1919 # reset nodeID before processing each element
1920 my $nodeID = $$blankInfo{NodeID} = $oldNodeID;
1921 # get next element
1922 last unless $$dataPt =~ m/<([\w:-]+)(.*?)>/sg;
1923 my ($prop, $attrs) = ($1, $2);
1924 my $val = '';
1925 # only look for closing token if this is not an empty element
1926 # (empty elements end with '/', ie. <a:b/>)
1927 if ($attrs !~ s/\/$//) {
1928 my $nesting = 1;
1929 for (;;) {
1930# this match fails with perl 5.6.2 (perl bug!), but it works without
1931# the '(.*?)', so do it the hard way instead...
1932# $$dataPt =~ m/(.*?)<\/$prop>/sg or last Element;
1933# my $val2 = $1;
1934 my $pos = pos($$dataPt);
1935 $$dataPt =~ m/<\/$prop>/sg or last Element;
1936 my $len = pos($$dataPt) - $pos - length($prop) - 3;
1937 my $val2 = substr($$dataPt, $pos, $len);
1938 # increment nesting level for each contained similar opening token
1939 ++$nesting while $val2 =~ m/<$prop\b.*?(\/?)>/sg and $1 ne '/';
1940 $val .= $val2;
1941 --$nesting or last;
1942 $val .= "</$prop>";
1943 }
1944 }
1945 if ($prop eq 'rdf:li') {
1946 # add index to list items so we can keep them in order
1947 # (this also enables us to keep structure elements grouped properly
1948 # for lists of structures, like JobRef)
1949 $prop .= sprintf(' %.3d', $count);
1950 } elsif ($prop eq 'rdf:Description') {
1951 # trim comments and whitespace from rdf:Description properties only
1952 $val =~ s/<!--.*?-->//g;
1953 $val =~ s/^\s*(.*)\s*$/$1/;
1954 } elsif ($prop eq 'xmp:xmpmeta') {
1955 # patch MicrosoftPhoto unconformity
1956 $prop = 'x:xmpmeta';
1957 }
1958
1959 # extract property attributes
1960 my (%attrs, @attrs);
1961 while ($attrs =~ m/(\S+?)=(['"])(.*?)\2/sg) {
1962 push @attrs, $1; # preserve order
1963 $attrs{$1} = $3;
1964 }
1965
1966 # add nodeID to property path (with leading ' #') if it exists
1967 if (defined $attrs{'rdf:nodeID'}) {
1968 $nodeID = $$blankInfo{NodeID} = $attrs{'rdf:nodeID'};
1969 delete $attrs{'rdf:nodeID'};
1970 $prop .= ' #' . $nodeID;
1971 }
1972
1973 # push this property name onto our hierarchy list
1974 push @$propListPt, $prop;
1975
1976 # handle properties inside element attributes (RDF shorthand format):
1977 # (attributes take the form a:b='c' or a:b="c")
1978 my ($shortName, $shorthand, $ignored);
1979 foreach $shortName (@attrs) {
1980 my $propName = $shortName;
1981 my ($ns, $name);
1982 if ($propName =~ /(.*?):(.*)/) {
1983 $ns = $1; # specified namespace
1984 $name = $2;
1985 } elsif ($prop =~ /(\S*?):/) {
1986 $ns = $1; # assume same namespace as parent
1987 $name = $propName;
1988 $propName = "$ns:$name"; # generate full property name
1989 } else {
1990 # a property qualifier is the only property name that may not
1991 # have a namespace, and a qualifier shouldn't have attributes,
1992 # but what the heck, let's allow this anyway
1993 $ns = '';
1994 $name = $propName;
1995 }
1996 if ($isWriting) {
1997 # keep track of our namespaces when writing
1998 if ($ns eq 'xmlns') {
1999 unless ($name eq 'x' or $name eq 'iX') {
2000 my $nsUsed = $exifTool->{XMP_NS};
2001 $$nsUsed{$name} = $attrs{$shortName} unless defined $$nsUsed{$name};
2002 }
2003 next;
2004 } elsif ($recognizedAttrs{$propName}) {
2005 # save UUID to use same ID when writing
2006 if ($propName eq 'rdf:about') {
2007 if (not $exifTool->{XMP_UUID}) {
2008 $exifTool->{XMP_UUID} = $attrs{$shortName};
2009 } elsif ($exifTool->{XMP_UUID} ne $attrs{$shortName}) {
2010 $exifTool->Error("Multiple XMP UUID's not handled", 1);
2011 }
2012 }
2013 next;
2014 }
2015 }
2016 if ($ignoreNamespace{$ns}) {
2017 $ignored = $propName;
2018 next;
2019 }
2020 my $shortVal = $attrs{$shortName};
2021 delete $attrs{$shortName}; # don't re-use this attribute
2022 push @$propListPt, $propName;
2023 # save this shorthand XMP property
2024 if (defined $nodeID) {
2025 SaveBlankInfo($blankInfo, $propListPt, $shortVal);
2026 } elsif ($isWriting) {
2027 CaptureXMP($exifTool, $propListPt, $shortVal);
2028 } else {
2029 FoundXMP($exifTool, $tagTablePtr, $propListPt, $shortVal);
2030 }
2031 pop @$propListPt;
2032 $shorthand = 1;
2033 }
2034 if ($isWriting) {
2035 if (ParseXMPElement($exifTool, $tagTablePtr, \$val, 0, $propListPt, $blankInfo)) {
2036 # undefine value since we found more properties within this one
2037 undef $val;
2038 # set an error on any ignored attributes here, because they will be lost
2039 $exifTool->{XMP_ERROR} = "Can't handle XMP attribute '$ignored'" if $ignored;
2040 }
2041 if (defined $val and (length $val or not $shorthand)) {
2042 if (defined $nodeID) {
2043 SaveBlankInfo($blankInfo, $propListPt, $val, \%attrs);
2044 } else {
2045 CaptureXMP($exifTool, $propListPt, $val, \%attrs);
2046 }
2047 }
2048 } else {
2049 # if element value is empty, take value from 'resource' attribute
2050 # (preferentially) or 'about' attribute (if no 'resource')
2051 $val = $2 if $val eq '' and ($attrs =~ /\bresource=(['"])(.*?)\1/ or
2052 $attrs =~ /\babout=(['"])(.*?)\1/);
2053
2054 # look for additional elements contained within this one
2055 if (!ParseXMPElement($exifTool, $tagTablePtr, \$val, 0, $propListPt, $blankInfo)) {
2056 # there are no contained elements, so this must be a simple property value
2057 # (unless we already extracted shorthand values from this element)
2058 if (length $val or not $shorthand) {
2059 if (defined $nodeID) {
2060 SaveBlankInfo($blankInfo, $propListPt, $val);
2061 } else {
2062 FoundXMP($exifTool, $tagTablePtr, $propListPt, $val, $attrs{'xml:lang'});
2063 }
2064 }
2065 }
2066 }
2067 pop @$propListPt;
2068 ++$count;
2069 }
2070#
2071# process resources referenced by blank nodeID's
2072#
2073 if ($processBlankInfo and %{$$blankInfo{Prop}}) {
2074 ProcessBlankInfo($exifTool, $tagTablePtr, $blankInfo, $isWriting);
2075 %$blankInfo = (); # free some memory
2076 }
2077 return $count; # return the number of elements found at this level
2078}
2079
2080#------------------------------------------------------------------------------
2081# Process XMP data
2082# Inputs: 0) ExifTool object reference, 1) DirInfo reference, 2) Pointer to tag table
2083# Returns: 1 on success
2084sub ProcessXMP($$;$)
2085{
2086 my ($exifTool, $dirInfo, $tagTablePtr) = @_;
2087 my $dataPt = $$dirInfo{DataPt};
2088 my $dirStart = $$dirInfo{DirStart} || 0;
2089 my $dirLen = $$dirInfo{DirLen};
2090 my $dataLen = $$dirInfo{DataLen};
2091 my $rtnVal = 0;
2092 my $bom = 0;
2093 my ($buff, $fmt, $isXML);
2094
2095 # read information from XMP file if necessary
2096 unless ($dataPt) {
2097 my $raf = $$dirInfo{RAF} or return 0;
2098 $raf->Read($buff, 128) or return 0;
2099 my ($buf2, $double);
2100 ($buf2 = $buff) =~ tr/\0//d; # cheap conversion to UTF-8
2101 # check to see if this is XMP format
2102 # - CS2 writes .XMP files without the "xpacket begin"
2103 unless ($buf2 =~ /^(<\?xpacket begin=|<x(mp)?:xmpmeta)/) {
2104 # also recognize XML files (and .XMP files with BOM)
2105 if ($buf2 =~ /^(\xfe\xff)(<\?xml|<x(mp)?:xmpmeta)/g) {
2106 $fmt = 'n'; # UTF-16 or 32 MM with BOM
2107 } elsif ($buf2 =~ /^(\xff\xfe)(<\?xml|<x(mp)?:xmpmeta)/g) {
2108 $fmt = 'v'; # UTF-16 or 32 II with BOM
2109 } elsif ($buf2 =~ /^(\xef\xbb\xbf)?(<\?xml|<x(mp)?:xmpmeta)/g) {
2110 $fmt = 0; # UTF-8 with BOM or unknown encoding without BOM
2111 } elsif ($buf2 =~ /^(\xfe\xff|\xff\xfe|\xef\xbb\xbf)(<\?xpacket begin=)/g) {
2112 $double = $1; # double-encoded UTF
2113 } else {
2114 return 0; # not XMP or XML
2115 }
2116 $bom = 1 if $1;
2117 if ($2 eq '<?xml') {
2118 return 0 unless $buf2 =~ /<x(mp)?:xmpmeta/;
2119 $isXML = 1;
2120 }
2121 if ($buff =~ /^\0\0/) {
2122 $fmt = 'N'; # UTF-32 MM with or without BOM
2123 } elsif ($buff =~ /^..\0\0/) {
2124 $fmt = 'V'; # UTF-32 II with or without BOM
2125 } elsif (not $fmt) {
2126 if ($buff =~ /^\0/) {
2127 $fmt = 'n'; # UTF-16 MM without BOM
2128 } elsif ($buff =~ /^.\0/) {
2129 $fmt = 'v'; # UTF-16 II without BOM
2130 }
2131 }
2132 }
2133 $raf->Seek(0, 2) or return 0;
2134 my $size = $raf->Tell() or return 0;
2135 $raf->Seek(0, 0) or return 0;
2136 $raf->Read($buff, $size) == $size or return 0;
2137 # decode the first layer of double-encoded UTF text
2138 if ($double) {
2139 $buff = substr($buff, length $double); # remove leading BOM
2140 Image::ExifTool::SetWarning(undef); # clear old warning
2141 local $SIG{'__WARN__'} = \&Image::ExifTool::SetWarning;
2142 my $tmp;
2143 # assume that character data has been re-encoded in UTF, so re-pack
2144 # as characters and look for warnings indicating a false assumption
2145 if ($double eq "\xef\xbb\xbf") {
2146 $tmp = Image::ExifTool::UTF82Unicode($buff, 'C');
2147 } else {
2148 my $fmt = ($double eq "\xfe\xff") ? 'n' : 'v';
2149 $tmp = pack('C*', unpack("$fmt*",$buff));
2150 }
2151 if (Image::ExifTool::GetWarning()) {
2152 $exifTool->Warn('Superfluous BOM at start of XMP');
2153 } else {
2154 $exifTool->Warn('XMP is double UTF-encoded');
2155 $buff = $tmp; # use the decoded XMP
2156 }
2157 $size = length $buff;
2158 }
2159 $dataPt = \$buff;
2160 $dirStart = 0;
2161 $dirLen = $dataLen = $size;
2162 $exifTool->SetFileType();
2163 }
2164
2165 # take substring if necessary
2166 if ($dataLen != $dirStart + $dirLen) {
2167 $buff = substr($$dataPt, $dirStart, $dirLen);
2168 $dataPt = \$buff;
2169 $dirStart = 0;
2170 }
2171 if ($exifTool->{REQ_TAG_LOOKUP}->{xmp}) {
2172 $exifTool->FoundTag('XMP', substr($$dataPt, $dirStart, $dirLen));
2173 }
2174 if ($exifTool->Options('Verbose') and not $exifTool->{XMP_CAPTURE}) {
2175 $exifTool->VerboseDir('XMP', 0, $dirLen);
2176 }
2177#
2178# convert UTF-16 or UTF-32 encoded XMP to UTF-8 if necessary
2179#
2180 my $begin = '<?xpacket begin=';
2181 pos($$dataPt) = $dirStart;
2182 delete $$exifTool{XMP_IS_XML};
2183 if ($isXML) {
2184 $$exifTool{XMP_IS_XML} = 1;
2185 $$exifTool{XMP_NO_XPACKET} = 1 + $bom;
2186 } elsif ($$dataPt =~ /\G\Q$begin\E/gc) {
2187 delete $$exifTool{XMP_NO_XPACKET};
2188 } elsif ($$dataPt =~ /<x(mp)?:xmpmeta/gc) {
2189 $$exifTool{XMP_NO_XPACKET} = 1 + $bom;
2190 } else {
2191 delete $$exifTool{XMP_NO_XPACKET};
2192 # check for UTF-16 encoding (insert one \0 between characters)
2193 $begin = join "\0", split //, $begin;
2194 if ($$dataPt =~ /\G(\0)?\Q$begin\E\0./g) {
2195 # validate byte ordering by checking for U+FEFF character
2196 if ($1) {
2197 # should be big-endian since we had a leading \0
2198 $fmt = 'n' if $$dataPt =~ /\G\xfe\xff/g;
2199 } else {
2200 $fmt = 'v' if $$dataPt =~ /\G\0\xff\xfe/g;
2201 }
2202 } else {
2203 # check for UTF-32 encoding (with three \0's between characters)
2204 $begin =~ s/\0/\0\0\0/g;
2205 # must reset pos because it was killed by previous unsuccessful //g match
2206 pos($$dataPt) = $dirStart;
2207 if ($$dataPt !~ /\G(\0\0\0)?\Q$begin\E\0\0\0./g) {
2208 $fmt = 0; # set format to zero as indication we didn't find encoded XMP
2209 } elsif ($1) {
2210 # should be big-endian
2211 $fmt = 'N' if $$dataPt =~ /\G\0\0\xfe\xff/g;
2212 } else {
2213 $fmt = 'V' if $$dataPt =~ /\G\0\0\0\xff\xfe\0\0/g;
2214 }
2215 }
2216 defined $fmt or $exifTool->Warn('XMP character encoding error');
2217 }
2218 if ($fmt) {
2219 # translate into UTF-8
2220 if ($] >= 5.006001) {
2221 $buff = pack('C0U*', unpack("x$dirStart$fmt*",$$dataPt));
2222 } else {
2223 $buff = Image::ExifTool::PackUTF8(unpack("x$dirStart$fmt*",$$dataPt));
2224 }
2225 $dataPt = \$buff;
2226 $dirStart = 0;
2227 }
2228 # avoid scanning for XMP later in case ScanForXMP is set
2229 $$exifTool{FoundXMP} = 1;
2230
2231 # parse the XMP
2232 $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
2233 $rtnVal = 1 if ParseXMPElement($exifTool, $tagTablePtr, $dataPt, $dirStart);
2234
2235 # return DataPt if successful in case we want it for writing
2236 $$dirInfo{DataPt} = $dataPt if $rtnVal and $$dirInfo{RAF};
2237
2238 return $rtnVal;
2239}
2240
2241
22421; #end
2243
2244__END__
2245
2246=head1 NAME
2247
2248Image::ExifTool::XMP - Read XMP meta information
2249
2250=head1 SYNOPSIS
2251
2252This module is loaded automatically by Image::ExifTool when required.
2253
2254=head1 DESCRIPTION
2255
2256XMP stands for Extensible Metadata Platform. It is a format based on XML
2257that Adobe developed for embedding metadata information in image files.
2258This module contains the definitions required by Image::ExifTool to read XMP
2259information.
2260
2261=head1 AUTHOR
2262
2263Copyright 2003-2007, Phil Harvey (phil at owl.phy.queensu.ca)
2264
2265This library is free software; you can redistribute it and/or modify it
2266under the same terms as Perl itself.
2267
2268=head1 REFERENCES
2269
2270=over 4
2271
2272=item L<http://www.adobe.com/products/xmp/pdfs/xmpspec.pdf>
2273
2274=item L<http://www.w3.org/TR/rdf-syntax-grammar/>
2275
2276=item L<http://www.portfoliofaq.com/pfaq/v7mappings.htm>
2277
2278=item L<http://www.iptc.org/IPTC4XMP/>
2279
2280=item L<http://creativecommons.org/technology/xmp>
2281
2282=item L<http://www.optimasc.com/products/fileid/xmp-extensions.pdf>
2283
2284=back
2285
2286=head1 SEE ALSO
2287
2288L<Image::ExifTool::TagNames/XMP Tags>,
2289L<Image::ExifTool(3pm)|Image::ExifTool>
2290
2291=cut
Note: See TracBrowser for help on using the repository browser.