File Coverage

blib/lib/Image/ExifTool/PLIST.pm
Criterion Covered Total %
statement 178 222 80.1
branch 102 166 61.4
condition 47 86 54.6
subroutine 9 10 90.0
pod 0 5 0.0
total 336 489 68.7


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: PLIST.pm
3             #
4             # Description: Read Apple PLIST information
5             #
6             # Revisions: 2013-02-01 - P. Harvey Created
7             #
8             # References: 1) http://www.apple.com/DTDs/PropertyList-1.0.dtd
9             # 2) http://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c
10             #
11             # Notes: - Sony MODD files also use XML PLIST format, but with a few quirks
12             #
13             # - Decodes both the binary and XML-based PLIST formats
14             #------------------------------------------------------------------------------
15              
16             package Image::ExifTool::PLIST;
17              
18 9     9   6955 use strict;
  9         21  
  9         415  
19 9     9   51 use vars qw($VERSION);
  9         21  
  9         498  
20 9     9   54 use Image::ExifTool qw(:DataAccess :Utils);
  9         19  
  9         2856  
21 9     9   3962 use Image::ExifTool::XMP;
  9         32  
  9         1172  
22 9     9   110 use Image::ExifTool::GPS;
  9         30  
  9         35753  
23              
24             $VERSION = '1.15';
25              
26             sub ExtractObject($$;$);
27             sub Get24u($$);
28              
29             # access routines to read various-sized integer/real values (add 0x100 to size for reals)
30             my %readProc = (
31             1 => \&Get8u,
32             2 => \&Get16u,
33             3 => \&Get24u,
34             4 => \&Get32u,
35             8 => \&Get64u,
36             0x104 => \&GetFloat,
37             0x108 => \&GetDouble,
38             );
39              
40             # recognize different types of PLIST files based on certain tags
41             my %plistType = (
42             adjustmentBaseVersion => 'AAE',
43             );
44              
45             # PLIST tags (generated on-the-fly for most tags)
46             %Image::ExifTool::PLIST::Main = (
47             PROCESS_PROC => \&ProcessPLIST,
48             GROUPS => { 0 => 'PLIST', 1 => 'XML', 2 => 'Document' },
49             VARS => { LONG_TAGS => 12 },
50             NOTES => q{
51             Apple Property List tags. ExifTool reads both XML and binary-format PLIST
52             files, and will extract any existing tags even if they aren't listed below.
53             These tags belong to the family 0 "PLIST" group, but family 1 group may be
54             either "XML" or "PLIST" depending on whether the format is XML or binary.
55             },
56             #
57             # tags found in PLIST information of QuickTime iTunesInfo iTunMOVI atom (ref PH)
58             #
59             'cast//name' => { Name => 'Cast', List => 1 },
60             'directors//name' => { Name => 'Directors', List => 1 },
61             'producers//name' => { Name => 'Producers', List => 1 },
62             'screenwriters//name' => { Name => 'Screenwriters', List => 1 },
63             'codirectors//name' => { Name => 'Codirectors', List => 1 }, # (NC)
64             'studio//name' => { Name => 'Studio', List => 1 }, # (NC)
65             #
66             # tags found in MODD files (ref PH)
67             #
68             'MetaDataList//DateTimeOriginal' => {
69             Name => 'DateTimeOriginal',
70             Description => 'Date/Time Original',
71             Groups => { 2 => 'Time' },
72             # Sony uses a "real" here -- number of days since Dec 31, 1899
73             ValueConv => 'IsFloat($val) ? ConvertUnixTime(($val - 25569) * 24 * 3600) : $val',
74             PrintConv => '$self->ConvertDateTime($val)',
75             },
76             'MetaDataList//Duration' => {
77             Name => 'Duration',
78             Groups => { 2 => 'Video' },
79             PrintConv => 'ConvertDuration($val)',
80             },
81             'MetaDataList//Geolocation/Latitude' => {
82             Name => 'GPSLatitude',
83             Groups => { 2 => 'Location' },
84             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
85             },
86             'MetaDataList//Geolocation/Longitude' => {
87             Name => 'GPSLongitude',
88             Groups => { 2 => 'Location' },
89             PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
90             },
91             'MetaDataList//Geolocation/MapDatum' => {
92             Name => 'GPSMapDatum',
93             Groups => { 2 => 'Location' },
94             },
95             # slow motion stuff found in AAE files
96             'slowMotion/regions/timeRange/start/flags' => {
97             Name => 'SlowMotionRegionsStartTimeFlags',
98             PrintConv => { BITMASK => {
99             0 => 'Valid',
100             1 => 'Has been rounded',
101             2 => 'Positive infinity',
102             3 => 'Negative infinity',
103             4 => 'Indefinite',
104             }},
105             },
106             'slowMotion/regions/timeRange/start/value' => 'SlowMotionRegionsStartTimeValue',
107             'slowMotion/regions/timeRange/start/timescale' => 'SlowMotionRegionsStartTimeScale',
108             'slowMotion/regions/timeRange/start/epoch' => 'SlowMotionRegionsStartTimeEpoch',
109             'slowMotion/regions/timeRange/duration/flags' => {
110             Name => 'SlowMotionRegionsDurationFlags',
111             PrintConv => { BITMASK => {
112             0 => 'Valid',
113             1 => 'Has been rounded',
114             2 => 'Positive infinity',
115             3 => 'Negative infinity',
116             4 => 'Indefinite',
117             }},
118             },
119             'slowMotion/regions/timeRange/duration/value' => 'SlowMotionRegionsDurationValue',
120             'slowMotion/regions/timeRange/duration/timescale' => 'SlowMotionRegionsDurationTimeScale',
121             'slowMotion/regions/timeRange/duration/epoch' => 'SlowMotionRegionsDurationEpoch',
122             'slowMotion/regions' => 'SlowMotionRegions',
123             'slowMotion/rate' => 'SlowMotionRate',
124             XMLFileType => {
125             # recognize MODD files by their content
126             RawConv => q{
127             if ($val eq 'ModdXML' and $$self{FILE_TYPE} eq 'XMP') {
128             $self->OverrideFileType('MODD');
129             }
130             return $val;
131             },
132             },
133             adjustmentData => { # AAE file
134             Name => 'AdjustmentData',
135             CompressedPLIST => 1,
136             SubDirectory => { TagTable => 'Image::ExifTool::PLIST::Main' },
137             },
138             );
139              
140             #------------------------------------------------------------------------------
141             # We found a PLIST XML property name/value
142             # Inputs: 0) ExifTool object ref, 1) tag table ref
143             # 2) reference to array of XML property names (last is current property)
144             # 3) property value, 4) attribute hash ref (not used here)
145             # Returns: 1 if valid tag was found
146             sub FoundTag($$$$;$)
147             {
148 37     37 0 106 my ($et, $tagTablePtr, $props, $val, $attrs) = @_;
149 37 50       89 return 0 unless @$props;
150 37         159 my $verbose = $et->Options('Verbose');
151 37   100     128 my $keys = $$et{PListKeys} || ( $$et{PListKeys} = [] );
152              
153 37         70 my $prop = $$props[-1];
154 37 50       86 if ($verbose > 1) {
155 0         0 $et->VPrint(0, $$et{INDENT}, '[', join('/',@$props), ' = ',
156             $et->Printable($val), "]\n");
157             }
158             # un-escape XML character entities
159 37         125 $val = Image::ExifTool::XMP::UnescapeXML($val);
160              
161             # handle the various PLIST properties
162 37 100 66     201 if ($prop eq 'data') {
    100          
    100          
163 2 50 33     17 if ($val =~ /^[0-9a-f]+$/ and not length($val) & 0x01) {
164             # MODD files use ASCII-hex encoded "data"...
165 0         0 my $buff = pack('H*', $val);
166 0         0 $val = \$buff;
167             } else {
168             # ...but the PLIST DTD specifies Base64 encoding
169 2         13 $val = Image::ExifTool::XMP::DecodeBase64($val);
170             }
171             } elsif ($prop eq 'date') {
172 2         9 $val = Image::ExifTool::XMP::ConvertXMPDate($val);
173             } elsif ($prop eq 'true' or $prop eq 'false') {
174 1         3 $val = ucfirst $prop;
175             } else {
176             # convert from UTF8 to ExifTool Charset
177 32         129 $val = $et->Decode($val, 'UTF8');
178 32 100       80 if ($prop eq 'key') {
179 17 100       45 if (@$props <= 3) { # top-level key should be plist/dict/key
180 15         47 @$keys = ( $val );
181             } else {
182             # save key names to be used in tag name
183 2         9 push @$keys, '' while @$keys < @$props - 3;
184 2         12 pop @$keys while @$keys > @$props - 2;
185 2         6 $$keys[@$props - 3] = $val;
186             }
187 17         61 return 0;
188             }
189             }
190              
191 20 100       56 return 0 unless @$keys; # can't store value if no associated key
192              
193 18         61 my $tag = join '/', @$keys; # generate tag ID from 'key' values
194 18         67 my $tagInfo = $$tagTablePtr{$tag};
195 18 100       45 unless ($tagInfo) {
196 15 50       37 $et->VPrint(0, $$et{INDENT}, "[adding $tag]\n") if $verbose;
197             # generate tag name from ID
198 15         28 my $name = $tag;
199 15         36 $name =~ s{^MetaDataList//}{}; # shorten long MODD metadata tag names
200 15         37 $name =~ s{//name$}{}; # remove unnecessary MODD "name" property
201 15         43 $name =~ s/([^A-Za-z])([a-z])/$1\u$2/g; # capitalize words
202 15         41 $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters
203 15         80 $tagInfo = { Name => ucfirst($name), List => 1 };
204 15 100       42 if ($prop eq 'date') {
205 2         8 $$tagInfo{Groups}{2} = 'Time';
206 2         6 $$tagInfo{PrintConv} = '$self->ConvertDateTime($val)';
207             }
208 15         54 AddTagToTable($tagTablePtr, $tag, $tagInfo);
209             }
210             # allow list-behaviour only for consecutive tags with the same ID
211 18 100 100     109 if ($$et{LastPListTag} and $$et{LastPListTag} ne $tagInfo) {
212 14         65 delete $$et{LIST_TAGS}{$$et{LastPListTag}};
213             }
214 18         56 $$et{LastPListTag} = $tagInfo;
215             # override file type if applicable
216 18 50 66     62 $et->OverrideFileType($plistType{$tag}) if $plistType{$tag} and $$et{FILE_TYPE} eq 'XMP';
217             # handle compressed PLIST/JSON data
218 18         30 my $proc;
219 18 50 66     67 if ($$tagInfo{CompressedPLIST} and ref $val eq 'SCALAR' and $$val !~ /^bplist00/) {
      66        
220 0 0       0 if (eval { require IO::Uncompress::RawInflate }) {
  0         0  
221 0         0 my $inflated;
222 0 0       0 if (IO::Uncompress::RawInflate::rawinflate($val => \$inflated)) {
223 0         0 $val = \$inflated;
224             } else {
225 0         0 $et->Warn("Error inflating PLIST::$$tagInfo{Name}");
226             }
227             } else {
228 0         0 $et->Warn('Install IO::Uncompress to decode compressed PLIST data');
229             }
230             }
231             # save the tag
232 18         95 $et->HandleTag($tagTablePtr, $tag, $val, ProcessProc => $proc);
233              
234 18         109 return 1;
235             }
236              
237             #------------------------------------------------------------------------------
238             # Get big-endian 24-bit integer
239             # Inputs: 0) data ref, 1) offset
240             # Returns: integer value
241             sub Get24u($$)
242             {
243 0     0 0 0 my ($dataPt, $off) = @_;
244 0         0 return unpack 'N', "\0" . substr($$dataPt, $off, 3);
245             }
246              
247             #------------------------------------------------------------------------------
248             # Extract object from binary PLIST file at the current file position (ref 2)
249             # Inputs: 0) ExifTool ref, 1) PLIST info ref, 2) parent tag ID (undef for top)
250             # Returns: the object, or undef on error
251             sub ExtractObject($$;$)
252             {
253 81     81 0 203 my ($et, $plistInfo, $parent) = @_;
254 81         168 my $raf = $$plistInfo{RAF};
255 81         177 my ($buff, $val);
256              
257 81 50       305 $raf->Read($buff, 1) == 1 or return undef;
258 81         192 my $type = ord($buff) >> 4;
259 81         156 my $size = ord($buff) & 0x0f;
260 81 100 100     561 if ($type == 0) { # null/bool/fill
    100 100        
    50          
261 1         13 $val = { 0x00=>'', 0x08=>'True', 0x09=>'False', 0x0f=>'' }->{$size};
262             } elsif ($type == 1 or $type == 2 or $type == 3) { # int, float or date
263 23         53 $size = 1 << $size;
264 23 100       130 my $proc = ($type == 1 ? $readProc{$size} : $readProc{$size + 0x100}) or return undef;
    50          
265 23 50       77 $val = &$proc(\$buff, 0) if $raf->Read($buff, $size) == $size;
266 23 100 66     107 if ($type == 3 and defined $val) { # date
267             # dates are referenced to Jan 1, 2001 (11323 days from Unix time zero)
268 3         21 $val = Image::ExifTool::ConvertUnixTime($val + 11323 * 24 * 3600, 1);
269 3         16 $$plistInfo{DateFormat} = 1;
270             }
271             } elsif ($type == 8) { # UID
272 0         0 ++$size;
273 0 0       0 $raf->Read($buff, $size) == $size or return undef;
274 0         0 my $proc = $readProc{$size};
275 0 0       0 if ($proc) {
    0          
276 0         0 $val = &$proc(\$buff, 0);
277             } elsif ($size == 16) {
278 0         0 require Image::ExifTool::ASF;
279 0         0 $val = Image::ExifTool::ASF::GetGUID($buff);
280             } else {
281 0         0 $val = "0x" . unpack 'H*', $buff;
282             }
283             } else {
284             # $size is the size of the remaining types
285 57 100       163 if ($size == 0x0f) {
286             # size is stored in extra integer object
287 5         29 $size = ExtractObject($et, $plistInfo);
288 5 50 33     68 return undef unless defined $size and $size =~ /^\d+$/;
289             }
290 57 100 66     249 if ($type == 4) { # data
    100 66        
    100          
    50          
291 2 50 33     14 if ($size < 1000000 or $et->Options('Binary')) {
292 2 50       10 $raf->Read($buff, $size) == $size or return undef;
293             } else {
294 0         0 $buff = "Binary data $size bytes";
295             }
296 2         6 $val = \$buff; # (return reference for binary data)
297             } elsif ($type == 5) { # ASCII string
298 40 50       153 $raf->Read($val, $size) == $size or return undef;
299             } elsif ($type == 6) { # UCS-2BE string
300 1         5 $size *= 2;
301 1 50       5 $raf->Read($buff, $size) == $size or return undef;
302 1         8 $val = $et->Decode($buff, 'UCS2');
303             } elsif ($type == 10 or $type == 12 or $type == 13) { # array, set or dict
304             # the remaining types store a list of references
305 14         100 my $refSize = $$plistInfo{RefSize};
306 14         36 my $refProc = $$plistInfo{RefProc};
307 14 100       55 my $num = $type == 13 ? $size * 2 : $size;
308 14         27 my $len = $num * $refSize;
309 14 50       49 $raf->Read($buff, $len) == $len or return undef;
310 14         38 my $table = $$plistInfo{Table};
311 14         31 my ($i, $ref, @refs, @array);
312 14         67 for ($i=0; $i<$num; ++$i) {
313 68         160 my $ref = &$refProc(\$buff, $i * $refSize);
314 68 50       170 return 0 if $ref >= @$table;
315 68         184 push @refs, $ref;
316             }
317 14 100       43 if ($type == 13) { # dict
318             # prevent infinite recursion
319 9 50 66     50 if (defined $parent and length $parent > 1000) {
320 0         0 $et->Warn('Possible deep recursion while parsing PLIST');
321 0         0 return undef;
322             }
323 9         27 my $tagTablePtr = $$plistInfo{TagTablePtr};
324 9         48 my $verbose = $et->Options('Verbose');
325 9         24 $val = { }; # initialize return dictionary (will stay empty if tags are saved)
326 9         30 for ($i=0; $i<$size; ++$i) {
327             # get the entry key
328 29 50       156 $raf->Seek($$table[$refs[$i]], 0) or return undef;
329 29         136 my $key = ExtractObject($et, $plistInfo);
330 29 50 33     140 next unless defined $key and length $key; # silently ignore bad dict entries
331             # get the entry value
332 29 50       108 $raf->Seek($$table[$refs[$i+$size]], 0) or return undef;
333             # generate an ID for this tag
334 29 100       144 my $tag = defined $parent ? "$parent/$key" : $key;
335 29         85 undef $$plistInfo{DateFormat};
336 29         114 my $obj = ExtractObject($et, $plistInfo, $tag);
337 29 50       88 next if not defined $obj;
338 29 50       73 unless ($tagTablePtr) {
339             # make sure this is a valid structure field name
340 0 0 0     0 if (not defined $key or $key !~ /^[-_a-zA-Z0-9]+$/) {
    0          
341 0         0 $key = "Tag$i"; # (generate fake tag name if it had illegal characters)
342             } elsif ($key !~ /^[_a-zA-Z]/) {
343 0         0 $key = "_$key"; # (must begin with alpha or underline)
344             }
345 0 0       0 $$val{$key} = $obj if defined $obj;
346 0         0 next;
347             }
348 29 100       103 next if ref($obj) eq 'HASH';
349 24         104 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
350 24 50       102 unless ($tagInfo) {
351 0 0       0 $et->VPrint(0, $$et{INDENT}, "[adding $tag]\n") if $verbose;
352 0         0 my $name = $tag;
353 0         0 $name =~ s/([^A-Za-z])([a-z])/$1\u$2/g; # capitalize words
354 0         0 $name =~ tr/-_a-zA-Z0-9//dc; # remove illegal characters
355 0 0 0     0 $name = 'Tag'.ucfirst($name) if length($name) < 2 or $name =~ /^[-0-9]/;
356 0         0 $tagInfo = { Name => ucfirst($name), List => 1 };
357 0 0       0 if ($$plistInfo{DateFormat}) {
358 0         0 $$tagInfo{Groups}{2} = 'Time';
359 0         0 $$tagInfo{PrintConv} = '$self->ConvertDateTime($val)';
360             }
361 0         0 AddTagToTable($tagTablePtr, $tag, $tagInfo);
362             }
363             # allow list-behaviour only for consecutive tags with the same ID
364 24 100 66     185 if ($$et{LastPListTag} and $$et{LastPListTag} ne $tagInfo) {
365 22         93 delete $$et{LIST_TAGS}{$$et{LastPListTag}};
366             }
367 24         76 $$et{LastPListTag} = $tagInfo;
368 24         117 $et->HandleTag($tagTablePtr, $tag, $obj);
369             }
370             } else {
371             # extract the referenced objects
372 5         19 foreach $ref (@refs) {
373 10 50       40 $raf->Seek($$table[$ref], 0) or return undef; # seek to this object
374 10         53 $val = ExtractObject($et, $plistInfo, $parent);
375 10 100 66     58 next unless defined $val and ref $val ne 'HASH';
376 9         29 push @array, $val;
377             }
378 5         20 $val = \@array;
379             }
380             }
381             }
382 81         296 return $val;
383             }
384              
385             #------------------------------------------------------------------------------
386             # Process binary PLIST data (ref 2)
387             # Inputs: 0) ExifTool object ref, 1) DirInfo ref, 2) tag table ref
388             # Returns: 1 on success (and returns plist value as $$dirInfo{Value})
389             sub ProcessBinaryPLIST($$;$)
390             {
391 8     8 0 26 my ($et, $dirInfo, $tagTablePtr) = @_;
392 8         20 my ($i, $buff, @table);
393 8         22 my $dataPt = $$dirInfo{DataPt};
394              
395 8 100       66 $et->VerboseDir('Binary PLIST') unless $$dirInfo{NoVerboseDir};
396 8         36 SetByteOrder('MM');
397              
398 8 100       32 if ($dataPt) {
399 7         40 my $start = $$dirInfo{DirStart};
400 7 100 33     69 if ($start or ($$dirInfo{DirLen} and $$dirInfo{DirLen} != length $$dataPt)) {
      66        
401 1   50     7 my $buf2 = substr($$dataPt, $start || 0, $$dirInfo{DirLen});
402 1         9 $$dirInfo{RAF} = File::RandomAccess->new(\$buf2);
403             } else {
404 6         54 $$dirInfo{RAF} = File::RandomAccess->new($dataPt);
405             }
406 7   100     38 my $strt = $$dirInfo{DirStart} || 0;
407             }
408             # read and parse the trailer
409 8 50       36 my $raf = $$dirInfo{RAF} or return 0;
410 8 50 33     39 $raf->Seek(-32,2) and $raf->Read($buff,32)==32 or return 0;
411 8         43 my $intSize = Get8u(\$buff, 6);
412 8         34 my $refSize = Get8u(\$buff, 7);
413 8         63 my $numObj = Get64u(\$buff, 8);
414 8         77 my $topObj = Get64u(\$buff, 16);
415 8         31 my $tableOff = Get64u(\$buff, 24);
416              
417 8 50       36 return 0 if $topObj >= $numObj;
418 8 50       52 my $intProc = $readProc{$intSize} or return 0;
419 8 50       47 my $refProc = $readProc{$refSize} or return 0;
420              
421             # read and parse the offset table
422 8         24 my $tableSize = $intSize * $numObj;
423 8 50 33     46 $raf->Seek($tableOff, 0) and $raf->Read($buff, $tableSize) == $tableSize or return 0;
424 8         39 for ($i=0; $i<$numObj; ++$i) {
425 69         171 push @table, &$intProc(\$buff, $i * $intSize);
426             }
427 8         74 my %plistInfo = (
428             RAF => $raf,
429             RefSize => $refSize,
430             RefProc => $refProc,
431             Table => \@table,
432             TagTablePtr => $tagTablePtr,
433             );
434             # position file pointer at the top object, and extract it
435 8 50       42 $raf->Seek($table[$topObj], 0) or return 0;
436 8         44 $$dirInfo{Value} = ExtractObject($et, \%plistInfo);
437 8 50       78 return defined $$dirInfo{Value} ? 1 : 0;
438             }
439              
440             #------------------------------------------------------------------------------
441             # Extract information from a PLIST file (binary, XML or JSON format)
442             # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
443             # Returns: 1 on success, 0 if this wasn't valid PLIST
444             sub ProcessPLIST($$;$)
445             {
446 7     7 0 25 my ($et, $dirInfo, $tagTablePtr) = @_;
447 7         21 my $dataPt = $$dirInfo{DataPt};
448 7         17 my ($result, $notXML);
449              
450 7 100       27 if ($dataPt) {
451 1   50     9 pos($$dataPt) = $$dirInfo{DirStart} || 0;
452 1 50       7 $notXML = 1 unless $$dataPt =~ /\G
453             }
454 7 100       27 unless ($notXML) {
455             # process XML PLIST data using the XMP module
456 6         35 $$dirInfo{XMPParseOpts}{FoundProc} = \&FoundTag;
457 6         47 $result = Image::ExifTool::XMP::ProcessXMP($et, $dirInfo, $tagTablePtr);
458 6         24 delete $$dirInfo{XMPParseOpts};
459 6 100       28 return $result if $result;
460             }
461 5         14 my $buff;
462 5         15 my $raf = $$dirInfo{RAF};
463 5 100       19 if ($raf) {
464 4 50 33     18 $raf->Seek(0,0) and $raf->Read($buff, 64) or return 0;
465 4         12 $dataPt = \$buff;
466             } else {
467 1 50       4 return 0 unless $dataPt;
468             }
469 5 100 33     55 if ($$dataPt =~ /^bplist0/) { # binary PLIST
    50 33        
    50          
470             # binary PLIST file
471 2         10 my $tagTablePtr = GetTagTable('Image::ExifTool::PLIST::Main');
472 2         10 $et->SetFileType('PLIST', 'application/x-plist');
473 2         7 $$et{SET_GROUP1} = 'PLIST';
474 2 50       10 unless (ProcessBinaryPLIST($et, $dirInfo, $tagTablePtr)) {
475 0         0 $et->Error('Error reading binary PLIST file');
476             }
477 2         8 delete $$et{SET_GROUP1};
478 2         8 $result = 1;
479             } elsif ($$dataPt =~ /^\{"/) { # JSON PLIST
480 0 0       0 $raf and $raf->Seek(0);
481 0         0 require Image::ExifTool::JSON;
482 0         0 $result = Image::ExifTool::JSON::ProcessJSON($et, $dirInfo);
483             } elsif ($$et{FILE_EXT} and $$et{FILE_EXT} eq 'PLIST' and
484             $$dataPt =~ /^\xfe\xff\x00/)
485             {
486             # (have seen very old PLIST files encoded as UCS-2BE with leading BOM)
487 0         0 $et->Error('Old PLIST format currently not supported');
488 0         0 $result = 1;
489             }
490 5         20 return $result;
491             }
492              
493             1; # end
494              
495             __END__