exiftool_vendored 12.06.0 → 12.08.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of exiftool_vendored might be problematic. Click here for more details.

@@ -35,7 +35,7 @@ use Image::ExifTool::Sony;
35
35
  use Image::ExifTool::Validate;
36
36
  use Image::ExifTool::MacOS;
37
37
 
38
- $VERSION = '3.37';
38
+ $VERSION = '3.38';
39
39
  @ISA = qw(Exporter);
40
40
 
41
41
  sub NumbersFirst($$);
@@ -633,7 +633,8 @@ L<http://www.metadataworkinggroup.org/> for the official MWG specification.
633
633
  MacOS => q{
634
634
  On MacOS systems, the there are additional MDItem and XAttr Finder tags that
635
635
  may be extracted. These tags are not extracted by default -- they must be
636
- specifically requested or enabled via an API option.
636
+ specifically requested or enabled via an API option. (Except when reading
637
+ MacOS "._" files directly, see below.)
637
638
 
638
639
  The tables below list some of the tags that may be extracted, but ExifTool
639
640
  will extract all available information even for tags not listed.
@@ -566,14 +566,27 @@ $VERSION = '4.39';
566
566
  61494 => 'Canon CN-E 85mm T1.3 L F', #PH
567
567
  61495 => 'Canon CN-E 135mm T2.2 L F', #PH
568
568
  61496 => 'Canon CN-E 35mm T1.5 L F', #PH
569
- 61182 => 'Canon RF 35mm F1.8 Macro IS STM or other Canon RF Lens', #IB
570
- 61182.1 => 'Canon RF 50mm F1.2 L USM', #IB
571
- 61182.2 => 'Canon RF 24-105mm F4 L IS USM', #IB
572
- 61182.3 => 'Canon RF 28-70mm F2 L USM', #IB
573
- 61182.4 => 'Canon RF 85mm F1.2L USM', #IB
574
- 61182.5 => 'Canon RF 24-240mm F4-6.3 IS USM', #IB
575
- 61182.6 => 'Canon RF 24-70mm F2.8 L IS USM', #PH
576
- 61182.7 => 'Canon RF 15-35mm F2.8 L IS USM', #PH
569
+ # see RFLensType tag for master list of 61182 RF lenses
570
+ 61182 => 'Canon RF 50mm F1.2L USM or other Canon RF Lens',
571
+ 61182.1 => 'Canon RF 24-105mm F4L IS USM',
572
+ 61182.2 => 'Canon RF 28-70mm F2L USM',
573
+ 61182.3 => 'Canon RF 35mm F1.8 MACRO IS STM',
574
+ 61182.4 => 'Canon RF 85mm F1.2L USM',
575
+ 61182.5 => 'Canon RF 85mm F1.2L USM DS',
576
+ 61182.6 => 'Canon RF 24-70mm F2.8L IS USM',
577
+ 61182.7 => 'Canon RF 15-35mm F2.8L IS USM',
578
+ 61182.8 => 'Canon RF 24-240mm F4-6.3 IS USM',
579
+ 61182.9 => 'Canon RF 70-200mm F2.8L IS USM',
580
+ 61182.10 => 'Canon RF 600mm F11 IS STM',
581
+ 61182.11 => 'Canon RF 600mm F11 IS STM + RF1.4x',
582
+ 61182.12 => 'Canon RF 600mm F11 IS STM + RF2x',
583
+ 61182.13 => 'Canon RF 800mm F11 IS STM',
584
+ 61182.14 => 'Canon RF 800mm F11 IS STM + RF1.4x',
585
+ 61182.15 => 'Canon RF 800mm F11 IS STM + RF2x',
586
+ 61182.16 => 'Canon RF 24-105mm F4-7.1 IS STM',
587
+ 61182.17 => 'Canon RF 100-500mm F4.5-7.1L IS USM',
588
+ 61182.18 => 'Canon RF 100-500mm F4.5-7.1L IS USM + RF1.4x',
589
+ 61182.19 => 'Canon RF 100-500mm F4.5-7.1L IS USM + RF2x',
577
590
  65535 => 'n/a',
578
591
  );
579
592
 
@@ -6694,7 +6707,17 @@ my %ciMaxFocal = (
6694
6707
  264 => 'Canon RF 15-35mm F2.8L IS USM',
6695
6708
  265 => 'Canon RF 24-240mm F4-6.3 IS USM',
6696
6709
  266 => 'Canon RF 70-200mm F2.8L IS USM',
6710
+ 268 => 'Canon RF 600mm F11 IS STM',
6711
+ 269 => 'Canon RF 600mm F11 IS STM + RF1.4x',
6712
+ 270 => 'Canon RF 600mm F11 IS STM + RF2x',
6713
+ 271 => 'Canon RF 800mm F11 IS STM',
6714
+ 272 => 'Canon RF 800mm F11 IS STM + RF1.4x',
6715
+ 273 => 'Canon RF 800mm F11 IS STM + RF2x',
6697
6716
  274 => 'Canon RF 24-105mm F4-7.1 IS STM',
6717
+ 275 => 'Canon RF 100-500mm F4.5-7.1L IS USM',
6718
+ 276 => 'Canon RF 100-500mm F4.5-7.1L IS USM + RF1.4x',
6719
+ 277 => 'Canon RF 100-500mm F4.5-7.1L IS USM + RF2x',
6720
+ # Note: add new RF lenses to %canonLensTypes with ID 61182
6698
6721
  },
6699
6722
  },
6700
6723
  );
@@ -44,14 +44,18 @@ my %event = (
44
44
  Groups => { 2 => 'Time' },
45
45
  Writable => 'string', # (so we can format this ourself)
46
46
  Shift => 'Time',
47
- # (pass straight through if this isn't a full date/time value)
47
+ # (allow date/time or just time value)
48
48
  ValueConv => 'Image::ExifTool::XMP::ConvertXMPDate($val)',
49
49
  PrintConv => '$self->ConvertDateTime($val)',
50
50
  ValueConvInv => 'Image::ExifTool::XMP::FormatXMPDate($val) or $val',
51
51
  PrintConvInv => q{
52
52
  my $v = $self->InverseDateTime($val,undef,1);
53
53
  undef $Image::ExifTool::evalWarning;
54
- return $v || $val;
54
+ return $v if $v;
55
+ my @a = ($val =~ /\d{1,2}/g); # get HH, MM and maybe SS
56
+ return undef unless @a >= 2;
57
+ $a[2] or $a[2] = 0;
58
+ return sprintf('%.2d:%.2d:%.2d', @a);
55
59
  },
56
60
  },
57
61
  fieldNotes => { },
@@ -21,7 +21,7 @@ use strict;
21
21
  use vars qw($VERSION);
22
22
  use Image::ExifTool qw(:DataAccess :Utils);
23
23
 
24
- $VERSION = '1.16';
24
+ $VERSION = '1.17';
25
25
 
26
26
  sub ProcessPEResources($$);
27
27
  sub ProcessPEVersion($$);
@@ -1009,7 +1009,7 @@ sub ProcessPEDict($$)
1009
1009
  my $raf = $$dirInfo{RAF};
1010
1010
  my $dataPt = $$dirInfo{DataPt};
1011
1011
  my $dirLen = length($$dataPt);
1012
- my ($pos, @sections, %dirInfo);
1012
+ my ($pos, @sections, %dirInfo, $rsrcFound);
1013
1013
 
1014
1014
  # loop through all sections
1015
1015
  for ($pos=0; $pos+40<=$dirLen; $pos+=40) {
@@ -1019,14 +1019,16 @@ sub ProcessPEDict($$)
1019
1019
  my $offset = Get32u($dataPt, $pos + 20);
1020
1020
  # remember the section offsets for the VirtualAddress lookup later
1021
1021
  push @sections, { Base => $offset, Size => $size, VirtualAddress => $va };
1022
- # save details of the first resource section
1022
+ # save details of the first resource section (or .text if .rsrc not found, ref forum11465)
1023
+ next unless ($name eq ".rsrc\0\0\0" and not $rsrcFound and defined($rsrcFound = 1)) or
1024
+ ($name eq ".text\0\0\0" and not %dirInfo);
1023
1025
  %dirInfo = (
1024
1026
  RAF => $raf,
1025
1027
  Base => $offset,
1026
1028
  DirStart => 0, # (relative to Base)
1027
1029
  DirLen => $size,
1028
1030
  Sections => \@sections,
1029
- ) if $name eq ".rsrc\0\0\0" and not %dirInfo;
1031
+ );
1030
1032
  }
1031
1033
  # process the first resource section
1032
1034
  ProcessPEResources($et, \%dirInfo) or return 0 if %dirInfo;
@@ -1144,7 +1146,8 @@ sub ProcessEXE($$)
1144
1146
  my $fileSize = ($cp - ($cblp ? 1 : 0)) * 512 + $cblp;
1145
1147
  #(patch to accommodate observed 64-bit files)
1146
1148
  #return 0 if $fileSize < 0x40 or $fileSize < $lfarlc;
1147
- return 0 if $fileSize < 0x40;
1149
+ #return 0 if $fileSize < 0x40; (changed to warning in ExifTool 12.08)
1150
+ $et->Warn('Invalid file size in DOS header') if $fileSize < 0x40;
1148
1151
  # read the Windows NE, PE or LE (virtual device driver) header
1149
1152
  #if ($lfarlc == 0x40 and $fileSize > $lfanew + 2 and ...
1150
1153
  if ($raf->Seek($lfanew, 0) and $raf->Read($buff, 0x40) and $buff =~ /^(NE|PE|LE)/) {
@@ -27,7 +27,7 @@ use vars qw($VERSION);
27
27
  use Image::ExifTool qw(:Public);
28
28
  use Image::ExifTool::GPS;
29
29
 
30
- $VERSION = '1.62';
30
+ $VERSION = '1.63';
31
31
 
32
32
  sub JITTER() { return 2 } # maximum time jitter
33
33
 
@@ -217,7 +217,7 @@ sub LoadTrackLog($$;$)
217
217
  # (don't set format yet because we want to read HFDTE first)
218
218
  $nmeaStart = 'B' ;
219
219
  next;
220
- } elsif (/^HFDTE(\d{2})(\d{2})(\d{2})/) {
220
+ } elsif (/^HFDTE(?:DATE:)?(\d{2})(\d{2})(\d{2})/) {
221
221
  my $year = $3 + ($3 >= 70 ? 1900 : 2000);
222
222
  $dateFlarm = Time::Local::timegm(0,0,0,$1,$2-1,$year);
223
223
  $nmeaStart = 'B' ;
@@ -16,7 +16,7 @@ use vars qw($VERSION);
16
16
  use Image::ExifTool qw(:DataAccess :Utils);
17
17
  use Image::ExifTool::QuickTime;
18
18
 
19
- $VERSION = '1.04';
19
+ $VERSION = '1.05';
20
20
 
21
21
  sub ProcessGoPro($$$);
22
22
  sub ProcessString($$$);
@@ -77,6 +77,9 @@ my %addUnits = (
77
77
  Notes => 'accelerator readings in m/s2',
78
78
  Binary => 1,
79
79
  },
80
+ # ANGX (GPMF-GEOC) - seen -0.05 (fmt d, Max)
81
+ # ANGY (GPMF-GEOC) - seen 179.9 (fmt d, Max)
82
+ # ANGZ (GPMF-GEOC) - seen 0.152 (fmt d, Max)
80
83
  ALLD => 'AutoLowLightDuration', #1 (gpmd) (untested)
81
84
  # APTO (GPMF) - seen: 'RAW', 'DYNM' (fmt c)
82
85
  ATTD => { #PH (Karma)
@@ -105,16 +108,14 @@ my %addUnits = (
105
108
  },
106
109
  # BRID (GPMF) - seen: 0 (fmt B)
107
110
  # BROD (GPMF) - seen: 'ASK','' (fmt c)
111
+ # CALH (GPMF-GEOC) - seen 3040 (fmt L, Max)
112
+ # CALW (GPMF-GEOC) - seen 4056 (fmt L, Max)
108
113
  CASN => 'CameraSerialNumber', #PH (GPMF - seen: 'C3221324545448', fmt c)
109
- # CINF (GPMF) - seen: 0x67376be7709bc8876a8baf3940908618, 0xe230988539b30cf5f016627ae8fc5395 (fmt B) (Camera INFormation?)
110
- # CMOD (GPMF) - seen: 12,13,17 [13 time-laps video, 17 JPEG] (fmt B)
111
- CYTS => { #PH (Karma)
112
- Name => 'CoyoteStatus',
113
- # UNIT=s,,,,,rad,rad,rad,,
114
- # TYPE=LLLLLfffBB
115
- # SCAL=1000 1 1 1 1 1 1 1 1 1
116
- Binary => 1,
117
- },
114
+ # CINF (GPMF) - seen: 0x67376be7709bc8876a8baf3940908618, 0xe230988539b30cf5f016627ae8fc5395,
115
+ # 0x8bcbe424acc5b37d7d77001635198b3b (fmt B) (Camera INFormation?)
116
+ # CMOD (GPMF) - seen: 12,13,17 [12 360 video, 13 time-laps video, 17 JPEG] (fmt B)
117
+ # CRTX (GPMF-BACK/FRNT) - double[1]
118
+ # CRTY (GPMF-BACK/FRNT) - double[1]
118
119
  CSEN => { #PH (Karma)
119
120
  Name => 'CoyoteSense',
120
121
  # UNIT=s,rad/s,rad/s,rad/s,g,g,g,,,,
@@ -122,13 +123,27 @@ my %addUnits = (
122
123
  # SCAL=1000 1 1 1 1 1 1 1 1 1 1
123
124
  Binary => 1,
124
125
  },
126
+ CYTS => { #PH (Karma)
127
+ Name => 'CoyoteStatus',
128
+ # UNIT=s,,,,,rad,rad,rad,,
129
+ # TYPE=LLLLLfffBB
130
+ # SCAL=1000 1 1 1 1 1 1 1 1 1
131
+ Binary => 1,
132
+ },
125
133
  DEVC => { #PH (gpmd,GPMF, fmt \0)
126
134
  Name => 'DeviceContainer',
127
135
  SubDirectory => { TagTable => 'Image::ExifTool::GoPro::GPMF' },
128
- },
129
- # DVID (GPMF) - DeviceID; seen: 1 (fmt L), HLMT (fmt F)
136
+ # (Max) DVID=1,DVNM='Global Settings',VERS,FMWR,LINF,CINF,CASN,MINF,MUID,CMOD,MTYP,OREN,
137
+ # DZOM,DZST,SMTR,PRTN,PTWB,PTSH,PTCL,EXPT,PIMX,PIMN,PTEV,RATE,SROT,ZFOV,VLTE,VLTA,
138
+ # EISE,EISA,AUPT,AUDO,BROD,BRID,PVUL,PRJT,SOFF
139
+ # (Max) DVID='GEOC',DVNM='Geometry Calibrations',SHFX,SHFY,SHFZ,ANGX,ANGY,ANGZ,CALW,CALH
140
+ # (Max) DVID='BACK',DVNM='Back Lens',KLNS,CTRX,CTRY,MFOV,SFTR
141
+ # (Max) DVID='FRNT',DVNM='Front Lens',KLNS,CTRX,CTRY,MFOV,SFTR
142
+ # (Max) DVID='HLMT',DVNM='Highlights'
143
+ },
144
+ # DVID (GPMF) - DeviceID; seen: 1 (fmt L), HLMT (fmt F), GEOC (fmt F), 'BACK' (fmt F, Max)
130
145
  DVID => { Name => 'DeviceID', Unknown => 1 }, #2 (gpmd)
131
- # DVNM (GPMF) seen: 'Video Global Settings' (fmt c), 'Highlights' (fmt c)
146
+ # DVNM (GPMF) seen: 'Video Global Settings' (fmt c), 'Highlights' (fmt c), 'Geometry Calibrations' (Max)
132
147
  # DVNM (gpmd) seen: 'Camera' (Hero5), 'Hero6 Black' (Hero6), 'GoPro Karma v1.0' (Karma)
133
148
  DVNM => 'DeviceName', #PH (n/c)
134
149
  DZOM => { #PH (GPMF - seen: 'Y', fmt c)
@@ -136,10 +151,10 @@ my %addUnits = (
136
151
  PrintConv => { N => 'No', Y => 'Yes' },
137
152
  },
138
153
  # DZST (GPMF) - seen: 0 (fmt L) (something to do with digital zoom maybe?)
139
- EISA => { #PH (GPMF) - seen: 'Y','N', 'HS EIS' (fmt c) [N was for a time-lapse video]
154
+ EISA => { #PH (GPMF) - seen: 'Y','N','HS EIS','N/A' (fmt c) [N was for a time-lapse video]
140
155
  Name => 'ElectronicImageStabilization',
141
156
  },
142
- # EISE (GPMF) - seen: 'Y' (fmt c)
157
+ # EISE (GPMF) - seen: 'Y','N' (fmt c)
143
158
  EMPT => { Name => 'Empty', Unknown => 1 }, #2 (gpmd)
144
159
  ESCS => { #PH (Karma)
145
160
  Name => 'EscapeStatus',
@@ -215,7 +230,8 @@ my %addUnits = (
215
230
  RawConv => '$val', # necessary to use scaled value instead of raw data as subdir data
216
231
  SubDirectory => { TagTable => 'Image::ExifTool::GoPro::KBAT' },
217
232
  },
218
- # LINF (GPMF) - seen: LAJ7061916601668, C3341326002180 (fmt c) (Lens INFormation?)
233
+ # KLNS (GPMF-BACK/FRNT) - double[5] (fmt d, Max)
234
+ # LINF (GPMF) - seen: LAJ7061916601668,C3341326002180,C33632245450981 (fmt c) (Lens INFormation?)
219
235
  LNED => { #PH (Karma)
220
236
  Name => 'LocalPositionNED',
221
237
  # UNIT=s,m,m,m,m/s,m/s,m/s
@@ -224,12 +240,13 @@ my %addUnits = (
224
240
  Binary => 1,
225
241
  },
226
242
  MAGN => 'Magnetometer', #1 (gpmd) (units of uT)
243
+ # MFOV (GPMF-BACK/FRNT) - seen: 100 (fmt d, Max)
227
244
  MINF => { #PH (GPMF - seen: HERO6 Black, fmt c)
228
245
  Name => 'Model',
229
246
  Groups => { 2 => 'Camera' },
230
247
  Description => 'Camera Model Name',
231
248
  },
232
- # MTYP (GPMF) - seen: 0,1,11 [1 for time-lapse video, 11 for JPEG] (fmt B)
249
+ # MTYP (GPMF) - seen: 0,1,5,11 [1 for time-lapse video, 5 for 360 video, 11 for JPEG] (fmt B)
233
250
  # MUID (GPMF) - seen: 3882563431 2278071152 967805802 411471936 0 0 0 0 (fmt L)
234
251
  OREN => { #PH (GPMF - seen: 'U', fmt c)
235
252
  Name => 'AutoRotation',
@@ -245,7 +262,7 @@ my %addUnits = (
245
262
  PIMX => 'AutoISOMax', #PH (GPMF - seen: 1600, fmt L)
246
263
  # PRAW (APP6) - seen: 0, 'N', 'Y' (fmt c)
247
264
  PRES => 'PhotoResolution', #PH (APP6 - seen: '12MP_W')
248
- # PRJT (APP6) - seen: 'GPRO' (fmt F, Hero8)
265
+ # PRJT (APP6) - seen: 'GPRO','EACO' (fmt F, Hero8, Max)
249
266
  PRTN => { #PH (GPMF - seen: 'N', fmt c)
250
267
  Name => 'ProTune',
251
268
  PrintConv => {
@@ -257,7 +274,7 @@ my %addUnits = (
257
274
  PTEV => 'ExposureCompensation', #PH (GPMF - seen: '0.0', fmt c)
258
275
  PTSH => 'Sharpness', #PH (GPMF - seen: 'HIGH', fmt c)
259
276
  PTWB => 'WhiteBalance', #PH (GPMF - seen: 'AUTO', fmt c)
260
- # PVUL (APP6) - seen: 'F' (fmt c, Hero8)
277
+ # PVUL (APP6) - seen: 'F' (fmt c, Hero8, Max)
261
278
  RATE => 'Rate', #PH (GPMF - seen: '0_5SEC', fmt c; APP6 - seen: '4_1SEC')
262
279
  RMRK => { #2 (gpmd)
263
280
  Name => 'Comments',
@@ -274,6 +291,10 @@ my %addUnits = (
274
291
  # SCAL=1000 0.00999999977648258 0.00999999977648258 100
275
292
  %addUnits,
276
293
  },
294
+ # SFTR (GPMF-BACK/FRNT) - seen 0.999,1.00004 (fmt d, Max)
295
+ # SHFX (GPMF-GEOC) - seen 22.92 (fmt d, Max)
296
+ # SHFY (GPMF-GEOC) - seen 0.123 (fmt d, Max)
297
+ # SHFZ (GPMF-GEOC) - seen 36.06 (fmt d, Max)
277
298
  SHUT => { #2 (gpmd)
278
299
  Name => 'ExposureTimes',
279
300
  PrintConv => q{
@@ -295,7 +316,8 @@ my %addUnits = (
295
316
  ValueConv => '$self->Decode($val, "Latin")',
296
317
  },
297
318
  # SMTR (GPMF) - seen: 'N' (fmt c)
298
- # SOFF (APP6) - seen: 0 (fmt L, Hero8)
319
+ # SOFF (APP6) - seen: 0 (fmt L, Hero8, Max)
320
+ # SROT (GPMF) - seen 20.60 (fmt f, Max)
299
321
  STMP => { #1 (gpmd)
300
322
  Name => 'TimeStamp',
301
323
  ValueConv => '$val / 1e6',
@@ -338,7 +360,10 @@ my %addUnits = (
338
360
  Unknown => 1,
339
361
  ValueConv => '$self->Decode($val, "Latin")',
340
362
  },
341
- # VERS (APP6) - seen: '7 6 4' (fmt B, Hero8)
363
+ VERS => {
364
+ Name => 'MetadataVersion',
365
+ PrintConv => '$val =~ tr/ /./; $val',
366
+ },
342
367
  VFOV => { #PH (GPMF - seen: 'W', fmt c)
343
368
  Name => 'FieldOfView',
344
369
  PrintConv => {
@@ -360,7 +385,7 @@ my %addUnits = (
360
385
  Name => 'WhiteBalanceRGB',
361
386
  Binary => 1,
362
387
  },
363
- # ZFOV (APP6) - seen: 148.34 (fmt f, Hero8)
388
+ # ZFOV (APP6,GPMF) - seen: 148.34, 0 (fmt f, Hero8, Max)
364
389
  );
365
390
 
366
391
  # GoPro GPS5 tags (ref 2) (Hero5,Hero6)
@@ -497,6 +497,7 @@ my %fileFormat = (
497
497
  103 => {
498
498
  Name => 'OriginalTransmissionReference',
499
499
  Format => 'string[0,32]',
500
+ Notes => 'now used as a job identifier',
500
501
  },
501
502
  105 => {
502
503
  Name => 'Headline',
@@ -4,6 +4,7 @@
4
4
  # Description: Read/write MacOS system tags
5
5
  #
6
6
  # Revisions: 2017/03/01 - P. Harvey Created
7
+ # 2020/10/13 - PH Added ability to read MacOS "._" files
7
8
  #------------------------------------------------------------------------------
8
9
 
9
10
  package Image::ExifTool::MacOS;
@@ -11,15 +12,38 @@ use strict;
11
12
  use vars qw($VERSION);
12
13
  use Image::ExifTool qw(:DataAccess :Utils);
13
14
 
14
- $VERSION = '1.09';
15
+ $VERSION = '1.10';
15
16
 
16
17
  sub MDItemLocalTime($);
18
+ sub ProcessATTR($$$);
17
19
 
18
20
  my %mdDateInfo = (
19
21
  ValueConv => \&MDItemLocalTime,
20
22
  PrintConv => '$self->ConvertDateTime($val)',
21
23
  );
22
24
 
25
+ # Information decoded from Mac OS sidecar files
26
+ %Image::ExifTool::MacOS::Main = (
27
+ GROUPS => { 0 => 'File', 1 => 'MacOS' },
28
+ NOTES => q{
29
+ Note that on some filesystems, MacOS creates sidecar files with names that
30
+ begin with "._". ExifTool will read these files if specified, and extract
31
+ the information listed in the following table without the need for extra
32
+ options, but these files are not writable directly.
33
+ },
34
+ 2 => {
35
+ Name => 'RSRC',
36
+ SubDirectory => { TagTable => 'Image::ExifTool::RSRC::Main' },
37
+ },
38
+ 9 => {
39
+ Name => 'ATTR',
40
+ SubDirectory => {
41
+ TagTable => 'Image::ExifTool::MacOS::XAttr',
42
+ ProcessProc => \&ProcessATTR,
43
+ },
44
+ },
45
+ );
46
+
23
47
  # "mdls" tags (ref PH)
24
48
  %Image::ExifTool::MacOS::MDItem = (
25
49
  WRITE_PROC => \&Image::ExifTool::DummyWriteProc,
@@ -221,6 +245,8 @@ my %mdDateInfo = (
221
245
  XAttr tags are extracted using the "xattr" utility. They are extracted if
222
246
  any "XAttr*" tag or the MacOS group is specifically requested, or by setting
223
247
  the L<XAttrTags|../ExifTool.html#XAttrTags> API option to 1 or the L<RequestAll|../ExifTool.html#RequestAll> API option to 2 or higher.
248
+ And they extracted by default from MacOS "._" files when reading
249
+ these files directly.
224
250
  },
225
251
  'com.apple.FinderInfo' => {
226
252
  Name => 'XAttrFinderInfo',
@@ -486,8 +512,50 @@ sub ExtractMDItemTags($$)
486
512
  $$et{INDENT} =~ s/\| $//;
487
513
  }
488
514
 
515
+
489
516
  #------------------------------------------------------------------------------
490
- # Extract MacOS extended attribute tags
517
+ # Read MacOS XAttr value
518
+ # Inputs: 0) ExifTool object ref, 1) file name
519
+ sub ReadXAttrValue($$$$)
520
+ {
521
+ my ($et, $tagTablePtr, $tag, $val) = @_;
522
+ # add to our table if necessary
523
+ unless ($$tagTablePtr{$tag}) {
524
+ my $name;
525
+ # generate tag name from attribute name
526
+ if ($tag =~ /^com\.apple\.(.*)$/) {
527
+ ($name = $1) =~ s/^metadata:_?k//;
528
+ $name =~ s/^metadata:(com_)?//;
529
+ } else {
530
+ $name = $tag;
531
+ }
532
+ $name =~ s/[.:_]([a-z])/\U$1/g;
533
+ $name = 'XAttr' . ucfirst $name;
534
+ my %tagInfo = ( Name => $name );
535
+ $tagInfo{Groups} = { 2 => 'Time' } if $tag=~/Date$/;
536
+ $et->VPrint(0, " [adding $tag]\n");
537
+ AddTagToTable($tagTablePtr, $tag, \%tagInfo);
538
+ }
539
+ if ($val =~ /^bplist0/) {
540
+ my %dirInfo = ( DataPt => \$val );
541
+ require Image::ExifTool::PLIST;
542
+ if (Image::ExifTool::PLIST::ProcessBinaryPLIST($et, \%dirInfo, $tagTablePtr)) {
543
+ return undef if ref $dirInfo{Value} eq 'HASH';
544
+ $val = $dirInfo{Value}
545
+ } else {
546
+ $et->Warn("Error decoding $$tagTablePtr{$tag}{Name}");
547
+ return undef;
548
+ }
549
+ }
550
+ if (not ref $val and ($val =~ /\0/ or length($val) > 200) or $tag eq 'XAttrMDLabel') {
551
+ my $buff = $val;
552
+ $val = \$buff;
553
+ }
554
+ return $val;
555
+ }
556
+
557
+ #------------------------------------------------------------------------------
558
+ # Read MacOS extended attribute tags using 'xattr' utility
491
559
  # Inputs: 0) ExifTool object ref, 1) file name
492
560
  sub ExtractXAttrTags($$)
493
561
  {
@@ -517,39 +585,8 @@ sub ExtractXAttrTags($$)
517
585
  $val .= pack('H*', $_);
518
586
  next;
519
587
  } elsif ($tag and defined $val) {
520
- # add to our table if necessary
521
- unless ($$tagTablePtr{$tag}) {
522
- my $name;
523
- # generate tag name from attribute name
524
- if ($tag =~ /^com\.apple\.(.*)$/) {
525
- ($name = $1) =~ s/^metadata:_?k//;
526
- $name =~ s/^metadata:(com_)?//;
527
- } else {
528
- $name = $tag;
529
- }
530
- $name =~ s/[.:_]([a-z])/\U$1/g;
531
- $name = 'XAttr' . ucfirst $name;
532
- my %tagInfo = ( Name => $name );
533
- $tagInfo{Groups} = { 2 => 'Time' } if $tag=~/Date$/;
534
- $et->VPrint(0, " [adding $tag]\n");
535
- AddTagToTable($tagTablePtr, $tag, \%tagInfo);
536
- }
537
- if ($val =~ /^bplist0/) {
538
- my %dirInfo = ( DataPt => \$val );
539
- require Image::ExifTool::PLIST;
540
- if (Image::ExifTool::PLIST::ProcessBinaryPLIST($et, \%dirInfo, $tagTablePtr)) {
541
- next if ref $dirInfo{Value} eq 'HASH';
542
- $val = $dirInfo{Value}
543
- } else {
544
- $et->Warn("Error decoding $$tagTablePtr{$tag}{Name}");
545
- next;
546
- }
547
- }
548
- if (not ref $val and ($val =~ /\0/ or length($val) > 200) or $tag eq 'XAttrMDLabel') {
549
- my $buff = $val;
550
- $val = \$buff;
551
- }
552
- $et->HandleTag($tagTablePtr, $tag, $val);
588
+ $val = ReadXAttrValue($et, $tagTablePtr, $tag, $val);
589
+ $et->HandleTag($tagTablePtr, $tag, $val) if defined $val;
553
590
  undef $tag;
554
591
  undef $val;
555
592
  }
@@ -584,6 +621,82 @@ sub GetFileCreateDate($$)
584
621
  delete $$et{SET_GROUP1};
585
622
  }
586
623
 
624
+ #------------------------------------------------------------------------------
625
+ # Read ATTR metadata from "._" file
626
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
627
+ # Return: 1 on success
628
+ # (ref https://www.swiftforensics.com/2018/11/the-dot-underscore-file-format.html)
629
+ sub ProcessATTR($$$)
630
+ {
631
+ my ($et, $dirInfo, $tagTablePtr) = @_;
632
+ my $dataPt = $$dirInfo{DataPt};
633
+ my $dataPos = $$dirInfo{DataPos};
634
+ my $dataLen = length $$dataPt;
635
+
636
+ $dataLen >= 58 and $$dataPt =~ /^.{34}ATTR/s or $et->Warn('Invalid ATTR header'), return 0;
637
+ my $entries = Get32u($dataPt, 66);
638
+ $et->VerboseDir('ATTR', $entries);
639
+ # (Note: The RAF is not in $dirInfo because it would break RSRC reading --
640
+ # the RSCR block uses relative offsets, while the ATTR block uses absolute! grrr!)
641
+ my $raf = $$et{RAF};
642
+ my $pos = 70; # first entry is after ATTR header
643
+ my $i;
644
+ for ($i=0; $i<$entries; ++$i) {
645
+ $pos + 12 > $dataLen and $et->Warn('Truncated ATTR entry'), last;
646
+ my $off = Get32u($dataPt, $pos);
647
+ my $len = Get32u($dataPt, $pos + 4);
648
+ my $n = Get8u($dataPt, $pos + 10); # number of characters in tag name
649
+ $pos + 11 + $n > $dataLen and $et->Warn('Truncated ATTR name'), last;
650
+ $off -= $dataPos; # convert to relative offset (grrr!)
651
+ $off < 0 or $off > $dataLen and $et->Warn('Invalid ATTR offset'), last;
652
+ my $tag = substr($$dataPt, $pos + 11, $n);
653
+ $tag =~ s/\0+$//; # remove null terminator
654
+ $off + $len > $dataLen and $et->Warn('Truncated ATTR value'), last;
655
+ my $val = ReadXAttrValue($et, $tagTablePtr, $tag, substr($$dataPt, $off, $len));
656
+ $et->HandleTag($tagTablePtr, $tag, $val,
657
+ DataPt => $dataPt,
658
+ DataPos => $dataPos,
659
+ Start => $off,
660
+ Size => $len,
661
+ ) if defined $val;
662
+ $pos += (11 + $n + 3) & -4; # step to next entry (on even 4-byte boundary)
663
+ }
664
+ return 1;
665
+ }
666
+
667
+ #------------------------------------------------------------------------------
668
+ # Read information from a MacOS "._" sidecar file
669
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref
670
+ # Returns: 1 on success, 0 if this wasn't a valid "._" file
671
+ # (ref https://www.swiftforensics.com/2018/11/the-dot-underscore-file-format.html)
672
+ sub ProcessMacOS($$)
673
+ {
674
+ my ($et, $dirInfo) = @_;
675
+ my $raf = $$dirInfo{RAF};
676
+ my ($hdr, $buff, $i);
677
+
678
+ return 0 unless $raf->Read($hdr, 26) == 26 and $hdr =~ /^\0\x05\x16\x07\0(.)\0\0Mac OS X /s;
679
+ my $ver = ord $1;
680
+ # (extension may be anything, so just echo back the incoming file extension if it exists)
681
+ $et->SetFileType(undef, undef, $$et{FILE_EXT});
682
+ $ver == 2 or $et->Warn("Unsupported file version $ver"), return 1;
683
+ SetByteOrder('MM');
684
+ my $tagTablePtr = GetTagTable('Image::ExifTool::MacOS::Main');
685
+ my $entries = Get16u(\$hdr, 0x18);
686
+ $et->VerboseDir('MacOS', $entries);
687
+ $raf->Read($hdr, $entries * 12) == $entries * 12 or $et->Warn('Truncated header'), return 1;
688
+ for ($i=0; $i<$entries; ++$i) {
689
+ my $pos = $i * 12;
690
+ my $tag = Get32u(\$hdr, $pos);
691
+ my $off = Get32u(\$hdr, $pos + 4);
692
+ my $len = Get32u(\$hdr, $pos + 8);
693
+ $len > 100000000 and $et->Warn('Record size too large'), last;
694
+ $raf->Seek($off,0) and $raf->Read($buff,$len) == $len or $et->Warn('Truncated record'), last;
695
+ $et->HandleTag($tagTablePtr, $tag, undef, DataPt => \$buff, DataPos => $off, Index => $i);
696
+ }
697
+ return 1;
698
+ }
699
+
587
700
  1; # end
588
701
 
589
702
  __END__
@@ -600,8 +713,9 @@ This module is used by Image::ExifTool
600
713
 
601
714
  This module contains definitions required by Image::ExifTool to extract
602
715
  MDItem* and XAttr* tags on MacOS systems using the "mdls" and "xattr"
603
- utilities respectively. Writable tags use "xattr", "setfile" or "osascript"
604
- for writing.
716
+ utilities respectively. It also reads metadata directly from the MacOS "_."
717
+ sidecar files that are used on some filesystems to store file attributes.
718
+ Writable tags use "xattr", "setfile" or "osascript" for writing.
605
719
 
606
720
  =head1 AUTHOR
607
721