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