exiftool_vendored 13.30.0 → 13.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +57 -1
  3. data/bin/MANIFEST +5 -0
  4. data/bin/META.json +4 -3
  5. data/bin/META.yml +3 -2
  6. data/bin/README +47 -46
  7. data/bin/exiftool +88 -60
  8. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +7 -5
  9. data/bin/lib/Image/ExifTool/Canon.pm +16 -5
  10. data/bin/lib/Image/ExifTool/Exif.pm +7 -4
  11. data/bin/lib/Image/ExifTool/FlashPix.pm +4 -159
  12. data/bin/lib/Image/ExifTool/FujiFilm.pm +13 -3
  13. data/bin/lib/Image/ExifTool/Geotag.pm +5 -3
  14. data/bin/lib/Image/ExifTool/GoPro.pm +23 -4
  15. data/bin/lib/Image/ExifTool/LNK.pm +24 -3
  16. data/bin/lib/Image/ExifTool/Lang/cs.pm +0 -1
  17. data/bin/lib/Image/ExifTool/Lang/de.pm +2 -2
  18. data/bin/lib/Image/ExifTool/Lang/fr.pm +2 -2
  19. data/bin/lib/Image/ExifTool/Lang/it.pm +0 -1
  20. data/bin/lib/Image/ExifTool/Lang/ja.pm +0 -1
  21. data/bin/lib/Image/ExifTool/Lang/nl.pm +0 -1
  22. data/bin/lib/Image/ExifTool/Lang/pl.pm +0 -1
  23. data/bin/lib/Image/ExifTool/Lang/zh_cn.pm +0 -1
  24. data/bin/lib/Image/ExifTool/LigoGPS.pm +14 -6
  25. data/bin/lib/Image/ExifTool/Microsoft.pm +158 -1
  26. data/bin/lib/Image/ExifTool/Minolta.pm +1 -1
  27. data/bin/lib/Image/ExifTool/Nikon.pm +80 -39
  28. data/bin/lib/Image/ExifTool/NikonCustom.pm +40 -10
  29. data/bin/lib/Image/ExifTool/Olympus.pm +240 -35
  30. data/bin/lib/Image/ExifTool/PDF.pm +1 -0
  31. data/bin/lib/Image/ExifTool/Panasonic.pm +4 -4
  32. data/bin/lib/Image/ExifTool/Parrot.pm +1 -1
  33. data/bin/lib/Image/ExifTool/Pentax.pm +276 -56
  34. data/bin/lib/Image/ExifTool/Plot.pm +2 -3
  35. data/bin/lib/Image/ExifTool/QuickTime.pm +13 -5
  36. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +41 -17
  37. data/bin/lib/Image/ExifTool/Sigma.pm +19 -1
  38. data/bin/lib/Image/ExifTool/Sony.pm +5 -2
  39. data/bin/lib/Image/ExifTool/TNEF.pm +487 -0
  40. data/bin/lib/Image/ExifTool/TagLookup.pm +4374 -4262
  41. data/bin/lib/Image/ExifTool/TagNames.pod +259 -22
  42. data/bin/lib/Image/ExifTool/WriteExif.pl +14 -12
  43. data/bin/lib/Image/ExifTool/WritePDF.pl +1 -0
  44. data/bin/lib/Image/ExifTool/Writer.pl +142 -139
  45. data/bin/lib/Image/ExifTool/XMPStruct.pl +1 -1
  46. data/bin/lib/Image/ExifTool.pm +16 -7
  47. data/bin/lib/Image/ExifTool.pod +45 -44
  48. data/bin/perl-Image-ExifTool.spec +46 -45
  49. data/lib/exiftool_vendored/version.rb +1 -1
  50. metadata +3 -2
@@ -111,7 +111,7 @@ my %insvLimit = (
111
111
  The tags below are extracted from timed metadata in QuickTime and other
112
112
  formats of video files when the ExtractEmbedded option is used. Although
113
113
  most of these tags are combined into the single table below, ExifTool
114
- currently reads 107 different types of timed GPS metadata from video files.
114
+ currently reads 110 different types of timed GPS metadata from video files.
115
115
  },
116
116
  GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' },
117
117
  GPSLongitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")' },
@@ -210,8 +210,8 @@ my %insvLimit = (
210
210
  ProcessProc => \&ProcessFMAS,
211
211
  },
212
212
  },{
213
- Name => 'gpmd_Wolfbox', # Wolfbox G900 Dashcam
214
- Condition => '$$valPt =~ /^.{136}0{16}HYTH/s',
213
+ Name => 'gpmd_Wolfbox', # Wolfbox G900 Dashcam and Redtiger F9 4K
214
+ Condition => '$$valPt =~ /^.{136}0{16}(HYTH|XXXX)/s',
215
215
  SubDirectory => {
216
216
  TagTable => 'Image::ExifTool::QuickTime::Stream',
217
217
  ProcessProc => \&ProcessWolfbox,
@@ -317,6 +317,8 @@ my %insvLimit = (
317
317
  ByteOrder => 'Little-Endian',
318
318
  },
319
319
  }],
320
+ # (have also seen unknown mett from Google Pixel with MetaType 'application/meta'
321
+ # and 'application/microvideo-image-meta')
320
322
  mett => { # Parrot drones and iPhone/Android using ARCore
321
323
  Name => 'mett',
322
324
  SubDirectory => { TagTable => 'Image::ExifTool::Parrot::mett' },
@@ -2288,6 +2290,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2288
2290
  $lon = ($lon - 2199.19873715495) / 2;
2289
2291
  $ddd = 1;
2290
2292
  } elsif (Get32u($dataPt,0) == 0x400000 and abs($lat) <= 90 and abs($lon) <= 180) {
2293
+ $debug and $et->FoundTag(GPSType => '17c');
2291
2294
  # Transcend Drive Body Camera 70
2292
2295
  # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 4c 00 00 00 [..@.freeGPS L...]
2293
2296
  # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
@@ -3567,14 +3570,14 @@ sub ProcessFMAS($$$)
3567
3570
  }
3568
3571
 
3569
3572
  #------------------------------------------------------------------------------
3570
- # Process GPS from Wolfbox G900 Dashcam
3573
+ # Process GPS from Wolfbox G900 Dashcam and Redtiger F9 4K
3571
3574
  # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
3572
3575
  # Returns: 1 on success
3573
3576
  sub ProcessWolfbox($$$)
3574
3577
  {
3575
3578
  my ($et, $dirInfo, $tagTbl) = @_;
3576
3579
  my $dataPt = $$dirInfo{DataPt};
3577
- return 0 if length($$dataPt) < 0xc8;
3580
+ return 0 if length($$dataPt) < 0xf8;
3578
3581
  $et->VerboseDir('Wolfbox', undef, length($$dataPt));
3579
3582
  # 0000: 65 00 00 00 00 00 00 00 31 01 01 00 e3 ff 00 00 [e.......1.......]
3580
3583
  # 0010: 04 00 00 00 10 00 00 00 2a 00 00 00 00 00 00 00 [........*.......]
@@ -3593,22 +3596,43 @@ sub ProcessWolfbox($$$)
3593
3596
  # 00e0: 0a 00 00 00 00 00 00 00 e8 03 00 00 00 00 00 00 [................]
3594
3597
  # 00f0: 0a 00 00 00 00 00 00 00 4d 00 00 00 00 00 00 00 [........M.......]
3595
3598
  # lat/lon at 0xb0/0xc0 and 0x128/0x138
3596
- # h/m/s at 0x10 and 0xa0 and 0x148 (the first imprinted on the video, the latter 2 presumed UTC)
3599
+ # h/m/s at 0x10 and 0xa0 and 0x148 (the first imprinted on the video, and
3600
+ # the latter 2 presumed UTC, but there is a 1 second offset for the Redtiger)
3597
3601
  # spd at 0x48, dir at 0x58, alt at 0xe8
3602
+ # Redtiger F9 4K Dual Front and Rear Mini Dash Cam
3603
+ # 0000: 01 00 00 00 00 00 00 00 f4 ff 5d fe 24 00 00 00 [..........].$...]
3604
+ # 0010: 10 00 00 00 2d 00 00 00 25 00 00 00 00 00 00 00 [....-...%.......]
3605
+ # 0020: 01 00 00 00 00 00 00 00 44 eb 8f 00 00 00 00 00 [........D.......]
3606
+ # 0030: 10 27 00 00 00 00 00 00 1b 94 8a 04 00 00 00 00 [.'..............]
3607
+ # 0040: 10 27 00 00 00 00 00 00 8c 69 00 00 00 00 00 00 [.'.......i......]
3608
+ # 0050: e8 03 00 00 00 00 00 00 ba 47 00 00 00 00 00 00 [.........G......]
3609
+ # 0060: 64 00 00 00 00 00 00 00 19 00 00 00 05 00 00 00 [d...............]
3610
+ # 0070: e9 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
3611
+ # 0080: 00 00 00 00 00 00 00 00 30 30 30 30 30 30 30 30 [........00000000]
3612
+ # 0090: 30 30 30 30 30 30 30 30 58 58 58 58 00 00 00 00 [00000000XXXX....]
3613
+ # 00a0: 08 00 00 00 2d 00 00 00 24 00 00 00 00 00 00 00 [....-...$.......]
3614
+ # 00b0: 90 eb 8f 00 00 00 00 00 10 27 00 00 00 00 00 00 [.........'......]
3615
+ # 00c0: 20 94 8a 04 00 00 00 00 10 27 00 00 00 00 00 00 [ ........'......]
3616
+ # 00d0: 01 00 00 00 11 00 00 00 40 00 00 00 00 00 00 00 [........@.......]
3617
+ # 00e0: 64 00 00 00 00 00 00 00 8a 00 00 00 00 00 00 00 [d...............]
3618
+ # 00f0: 0a 00 00 00 00 00 00 00 4d 00 00 00 00 00 00 00 [........M.......]
3598
3619
  SetByteOrder('II');
3599
- my ($spd,$dir,$d,$mo,$yr,$h,$m,$s) = unpack('x72Vx12Vx12V3x44V3',$$dataPt);
3600
- # offset 0xa0 also stores hh mm ss, but is out by 8 hours!
3620
+ my ($d,$mo,$yr,$h,$m,$s) = unpack('x104V3x44V3',$$dataPt);
3601
3621
  my $time = sprintf '%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $yr, $mo, $d, $h, $m, $s;
3602
- my ($lat, $lon) = (Get32s($dataPt, 0xb0) / 1e5, Get32s($dataPt, 0xc0) / 1e5);
3603
- my $alt = Get32s($dataPt, 0xe8);
3604
- ConvertLatLon($lat, $lon);
3622
+ my ($pos, @a);
3623
+ # 0=spd 1=dir 2=lat 3=lon 4=alt
3624
+ foreach $pos (0x48, 0x58, 0xb0, 0xc0, 0xe8) {
3625
+ my $val = Get64s($dataPt, $pos);
3626
+ my $scl = Get64s($dataPt, $pos + 8);
3627
+ push @a, $val / ($scl || 1);
3628
+ }
3629
+ ConvertLatLon($a[2], $a[3]);
3605
3630
  $et->HandleTag($tagTbl, GPSDateTime => $time);
3606
- $et->HandleTag($tagTbl, GPSLatitude => $lat);
3607
- $et->HandleTag($tagTbl, GPSLongitude => $lon);
3608
- $et->HandleTag($tagTbl, GPSSpeed => $spd * $knotsToKph / 100);
3609
- $et->HandleTag($tagTbl, GPSTrack => $dir / 100);
3610
- $et->HandleTag($tagTbl, GPSAltitude => $alt / 10); # (NC)
3611
- SetByteOrder('MM');
3631
+ $et->HandleTag($tagTbl, GPSLatitude => $a[2]);
3632
+ $et->HandleTag($tagTbl, GPSLongitude => $a[3]);
3633
+ $et->HandleTag($tagTbl, GPSSpeed => $a[0] * $knotsToKph);
3634
+ $et->HandleTag($tagTbl, GPSTrack => $a[1]);
3635
+ $et->HandleTag($tagTbl, GPSAltitude => $a[4]);
3612
3636
  return 1;
3613
3637
  }
3614
3638
 
@@ -19,7 +19,7 @@ use strict;
19
19
  use vars qw($VERSION %sigmaLensTypes);
20
20
  use Image::ExifTool::Exif;
21
21
 
22
- $VERSION = '1.35';
22
+ $VERSION = '1.36';
23
23
 
24
24
  # sigma LensType lookup (ref IB)
25
25
  %sigmaLensTypes = (
@@ -704,6 +704,12 @@ $VERSION = '1.35';
704
704
  Name => 'PictureMode',
705
705
  Notes => 'same as ColorMode, but "Standard" when ColorMode is Sepia or B&W',
706
706
  },
707
+ 0x0047 => { #forum17338
708
+ Name => 'ExposureCompensation',
709
+ Writable => 'rational64s',
710
+ PrintConv => '$val and $val =~ s/^(\d)/\+$1/; $val',
711
+ PrintConvInv => '$val',
712
+ },
707
713
  0x0048 => { #PH
708
714
  Name => 'LensApertureRange',
709
715
  Condition => '$$self{MakerNoteSigmaVer} >= 3',
@@ -797,6 +803,10 @@ $VERSION = '1.35';
797
803
  0x0087 => 'ResolutionMode', #PH (Quattro models)
798
804
  0x0088 => 'WhiteBalance', #PH (Quattro models)
799
805
  0x008c => 'Firmware', #PH (Quattro models)
806
+ 0x0113 => { #forum17338
807
+ Name => 'PictureModeStrength',
808
+ Writable => 'int32s',
809
+ },
800
810
  0x011f => { #IB (FP DNG images)
801
811
  Name => 'CameraCalibration',
802
812
  Writable => 'float',
@@ -810,6 +820,14 @@ $VERSION = '1.35';
810
820
  Name => 'WBSettings2',
811
821
  SubDirectory => { TagTable => 'Image::ExifTool::Sigma::WBSettings2' },
812
822
  },
823
+ 0x0138 => { #forum17338
824
+ Name => 'Fade',
825
+ Writable => 'rational64u',
826
+ },
827
+ 0x0139 => { #forum17338
828
+ Name => 'Vignette',
829
+ Writable => 'rational64u',
830
+ },
813
831
  );
814
832
 
815
833
  # WB settings (ref IB)
@@ -34,7 +34,7 @@ use Image::ExifTool qw(:DataAccess :Utils);
34
34
  use Image::ExifTool::Exif;
35
35
  use Image::ExifTool::Minolta;
36
36
 
37
- $VERSION = '3.73';
37
+ $VERSION = '3.74';
38
38
 
39
39
  sub ProcessSRF($$$);
40
40
  sub ProcessSR2($$$);
@@ -176,6 +176,7 @@ sub PrintInvLensSpec($;$$);
176
176
  32888 => 'Sony FE 85mm F1.4 GM II', #JR
177
177
  32889 => 'Sony FE 28-70mm F2 GM',
178
178
  32890 => 'Sony FE 400-800mm F6.3-8 G OSS', #JR
179
+ 32891 => 'Sony FE 50-150mm F2 GM', #github335
179
180
 
180
181
  # (comment this out so LensID will report the LensModel, which is more useful)
181
182
  # 32952 => 'Metabones Canon EF Speed Booster Ultra', #JR (corresponds to 184, but 'Advanced' mode, LensMount reported as E-mount)
@@ -201,7 +202,9 @@ sub PrintInvLensSpec($;$$);
201
202
  33091 => 'Sony FE 400-800mm F6.3-8 G OSS + 1.4X Teleconverter', #JR
202
203
  33092 => 'Sony FE 400-800mm F6.3-8 G OSS + 2X Teleconverter', #JR
203
204
 
204
- 49201 => 'Zeiss Touit 12mm F2.8', #JR (lens firmware Ver.02)
205
+ 49201 => 'Zeiss Touit 12mm F2.8 or other Touit lens', #JR (lens firmware Ver.02) / github342 (firmware Ver.02.001)
206
+ 49201.1 => 'Zeiss Touit 32mm F1.8', #github342 (firmware Ver.02.001)
207
+ 49201.2 => 'Zeiss Touit 50mm F2.8', #github342 (firmware Ver.02.001) (NC)
205
208
  49202 => 'Zeiss Touit 32mm F1.8', #JR (lens firmware Ver.02)
206
209
  49203 => 'Zeiss Touit 50mm F2.8 Macro', #JR (lens firmware Ver.02)
207
210
  49216 => 'Zeiss Batis 25mm F2', #JR
@@ -0,0 +1,487 @@
1
+ #------------------------------------------------------------------------------
2
+ # File: TNEF.pm
3
+ #
4
+ # Description: Read TNEF meta information
5
+ #
6
+ # Revisions: 2025-07-08 - P. Harvey Created
7
+ #
8
+ # References: 1) https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXTNEF/%5bMS-OXTNEF%5d.pdf
9
+ # 2) https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXCMSG/%5bMS-OXCMSG%5d.pdf
10
+ # 3) https://msopenspecs.azureedge.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf
11
+ # 4) https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXCDATA/%5bMS-OXCDATA%5d.pdf
12
+ # 5) https://github.com/echo-devim/pyjacktrick/blob/main/mapi_constants.py
13
+ #------------------------------------------------------------------------------
14
+
15
+ package Image::ExifTool::TNEF;
16
+
17
+ use strict;
18
+ use vars qw($VERSION);
19
+ use Image::ExifTool qw(:DataAccess :Utils);
20
+ use Image::ExifTool::ASF;
21
+ use Image::ExifTool::Microsoft;
22
+
23
+ $VERSION = '1.00';
24
+
25
+ sub ProcessProps($$$);
26
+
27
+ # TNEF property types
28
+ my %propType = (
29
+ 0x01 => 'null',
30
+ 0x02 => 'int16s',
31
+ 0x03 => 'int32s',
32
+ 0x04 => 'float',
33
+ 0x05 => 'double',
34
+ 0x06 => 'int64s', # (currency / 10000)
35
+ 0x07 => 'double', # (days since Dec 30, 1899)
36
+ 0x0a => 'int32s', # (error code)
37
+ 0x0b => 'int16s', # (boolean)
38
+ 0x0d => 'undef', # (object)
39
+ 0x14 => 'int64s',
40
+ 0x1e => 'string', # (with terminating null)
41
+ 0x1f => 'Unicode',# (with terminating null)
42
+ 0x40 => 'int64u', # (time in 100 ns since 1601)
43
+ 0x48 => 'GUID', # (16 bytes)
44
+ 0x102 => 'undef', # (blob)
45
+ );
46
+
47
+ # byte count for non-integer fixed-size formats
48
+ my %fmtSize = (
49
+ null => 0,
50
+ float => 4,
51
+ double => 8,
52
+ GUID => 16,
53
+ );
54
+
55
+ my %dateInfo = (
56
+ Format => 'date',
57
+ Groups => { 2 => 'Time' },
58
+ PrintConv => '$self->ConvertDateTime($val)',
59
+ );
60
+
61
+ %Image::ExifTool::TNEF::Main = (
62
+ GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Other' },
63
+ VARS => { NO_LOOKUP => 1 },
64
+ NOTES => q{
65
+ Information extracted from Transport Neutral Encapsulation Format (TNEF)
66
+ files (eg. winmail.dat). But note that the exiftool application doesn't
67
+ process files with a .DAT extension by default when a directory name is
68
+ given, so in this case either specify the .DAT file(s) by name or add
69
+ C<-ext+ dat> to the command.
70
+ },
71
+ 0x069007 => {
72
+ Name => 'CodePage',
73
+ Format => 'int32u',
74
+ SeparateTable => 'Microsoft CodePage',
75
+ # (ignore secondary code page)
76
+ RawConv => '$val=~s/ .*//;$$self{Charset} = $charsetName{"cp$val"}; $val',
77
+ PrintConv => \%Image::ExifTool::Microsoft::codePage,
78
+ },
79
+ 0x089006 => {
80
+ Name => 'TNEFVersion',
81
+ Format => 'int8u',
82
+ ValueConv => 'my @a = reverse split " ", $val; "@a"',
83
+ PrintConv => '$val =~ tr/ /./; $val',
84
+ },
85
+ 0x078008 => 'MessageClass',
86
+ 0x008000 => 'From',
87
+ 0x018004 => 'Subject',
88
+ 0x038005 => { Name => 'SentDate', %dateInfo },
89
+ 0x038006 => { Name => 'ReceivedDate', %dateInfo },
90
+ 0x068007 => 'MessageStatus',
91
+ 0x018009 => 'MessageID',
92
+ 0x02800C => 'MessageBody',
93
+ 0x04800D => {
94
+ Name => 'Priority',
95
+ Format => 'int16u', # (contrary to documentation which says int32u)
96
+ PrintConv => {
97
+ 0 => 'Low',
98
+ 1 => 'Normal',
99
+ 2 => 'High',
100
+ },
101
+ },
102
+ 0x038020 => { Name => 'MessageModifyDate', %dateInfo }, # (unclear what this really means)
103
+ 0x069003 => {
104
+ Name => 'MessageProps',
105
+ SubDirectory => { TagTable => 'Image::ExifTool::TNEF::MsgProps' },
106
+ },
107
+ 0x069004 => 'RecipientTable',
108
+ 0x070600 => 'OriginalMessageClass',
109
+ 0x060000 => 'Owner',
110
+ 0x060001 => 'SentFor',
111
+ 0x060002 => 'Delegate',
112
+ 0x030006 => { Name => 'StartDate', %dateInfo },
113
+ 0x030007 => { Name => 'EndDate', %dateInfo },
114
+ 0x050008 => 'OwnerAppointmentID',
115
+ 0x040009 => 'ResponseRequested',
116
+ 0x06800F => { Name => 'AttachData', Binary => 1 },
117
+ 0x018010 => 'AttachTitle',
118
+ 0x068011 => { Name => 'AttachMetaFile', Binary => 1 },
119
+ 0x038012 => { Name => 'AttachCreateDate', %dateInfo },
120
+ 0x038013 => { Name => 'AttachModifyDate', %dateInfo },
121
+ 0x069001 => 'AttachTransportFilename',
122
+ 0x069002 => { Name => 'AttachRenderingData', Binary => 1 }, # (start of attachment)
123
+ 0x069005 => { # (end of attachment)
124
+ Name => 'AttachInfo',
125
+ SubDirectory => { TagTable => 'Image::ExifTool::TNEF::AttachInfo' },
126
+ },
127
+ );
128
+
129
+ %Image::ExifTool::TNEF::MsgProps = (
130
+ GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Other' },
131
+ PROCESS_PROC => \&ProcessProps,
132
+ TAG_PREFIX => 'MsgProps',
133
+ VARS => { LONG_TAGS => 0, NO_LOOKUP => 1 }, # (suppress "long tags" warning in BuildTagLookup)
134
+ 0x0002 => 'AlternateRecipientAllowed',
135
+ 0x0039 => { Name => 'ClientSubmitTime', %dateInfo },
136
+ 0x0040 => 'ReceivedByName',
137
+ 0x0044 => 'ReceivedRepresentingName',
138
+ 0x004d => { Name => 'OriginalAuthorName', Groups => { 2 => 'Author' } },
139
+ 0x0055 => { Name => 'OriginalDeliveryTime', %dateInfo },
140
+ 0x0070 => 'Subject',
141
+ 0x0075 => 'ReceivedByAddressType',
142
+ 0x0076 => 'ReceivedByEmailAddress',
143
+ 0x0077 => 'ReceivedRepresentingAddressType',
144
+ 0x0078 => 'ReceivedRepresentingEmailAddress',
145
+ 0x007f => { Name => 'CorrelationKey', RawConv => '$$val' },
146
+ 0x0c1a => 'SenderName',
147
+ 0x0c1d => { Name => 'SenderSearchKey', RawConv => 'ref $val ? $$val : $val' },
148
+ 0x0e06 => { Name => 'MessageDeliveryTime', %dateInfo },
149
+ 0x0e1d => 'NormalizedSubject',
150
+ 0x0e28 => 'PrimarySendAccount',
151
+ 0x0e29 => 'NextSendAccount',
152
+ 0x0f02 => { Name => 'DeliveryOrRenewTime', %dateInfo }, #5
153
+ 0x1000 => { Name => 'MessageBodyText', Binary => 1 },
154
+ 0x1007 => 'SyncBodyCount',
155
+ 0x1008 => 'SyncBodyData',
156
+ 0x1009 => {
157
+ Name => 'MessageBodyRTF',
158
+ Notes => 'RTF message body, decompressed if necessary',
159
+ RawConv => '$$val', # (ValueConv won't convert a scalar ref, so convert to scalar here)
160
+ ValueConv => 'my $dat = Image::ExifTool::TNEF::DecompressRTF($self,$val); \$dat',
161
+ },
162
+ 0x1013 => { Name => 'MessageBodyHTML', Binary => 1 },
163
+ 0x1035 => 'InternetMessageID',
164
+ 0x10f4 => 'Hidden',
165
+ 0x10f6 => 'ReadOnly',
166
+ 0x3007 => { Name => 'CreateDate', %dateInfo },
167
+ 0x3008 => { Name => 'ModifyDate', %dateInfo },
168
+ 0x3fde => 'InternetCodePage',
169
+ 0x3ff1 => 'LocalUserID',
170
+ 0x3ff8 => { Name => 'CreatorName', Groups => { 2 => 'Author' } },
171
+ 0x3ffa => 'LastModifierName',
172
+ 0x3ffd => 'MessageCodePage',
173
+ 0x4076 => { Name => 'SpamConfidenceLevel' },
174
+ # named properties that look interesting
175
+ '00020329_Author' => {
176
+ Name => 'Author',
177
+ Groups => { 2 => 'Author' },
178
+ Notes => q{
179
+ tag ID's for named properties are constructed from the property namespace
180
+ GUID with the ending "-0000-0000-C000-000000000046" removed, followed by the
181
+ string or numerical ID in hex, separated by an underscore
182
+ },
183
+ }, # (NC)
184
+ '00020329_LastAuthor' => { Name => 'LastAuthor', Groups => { 2 => 'Author' } }, # (NC)
185
+ '00062004_0000801A' => 'HomeAddress', # (NC)
186
+ '00062004_000080DA' => 'HomeAddressCountryCode', # (NC)
187
+ '00062008_00008554' => 'AppVersion',
188
+ );
189
+
190
+ # ref https://pkg.go.dev/github.com/axigenmessaging/tnef#section-readme
191
+ %Image::ExifTool::TNEF::AttachInfo = (
192
+ GROUPS => { 0 => 'File', 1 => 'File', 2 => 'Other' },
193
+ PROCESS_PROC => \&ProcessProps,
194
+ TAG_PREFIX => 'Attach',
195
+ 0x0e20 => 'AttachSize',
196
+ 0x0e21 => 'AttachNum',
197
+ 0x0ff8 => { Name => 'MappingSignature', Unknown => 1 },
198
+ 0x3001 => 'AttachFileName',
199
+ 0x3703 => 'AttachFileExtension',
200
+ 0x3701 => 'AttachBinary',
201
+ 0x3705 => {
202
+ Name => 'AttachMethod',
203
+ PrintConv => {
204
+ 0 => 'Attachment Created',
205
+ 1 => 'AttachData', # (contrary to documentation which says the AttachBinary tag)
206
+ 2 => 'AttachLongPathName (recipients with access)',
207
+ 4 => 'AttachLongPathName',
208
+ 5 => 'Embedded Message',
209
+ 6 => 'AttachBinary (object)',
210
+ 7 => 'AttachLongPathName (using AttachmentProviderType)',
211
+ },
212
+ },
213
+ 0x3707 => 'AttachLongFileName',
214
+ 0x3708 => 'AttachPathName',
215
+ 0x370d => 'AttachLongPathName',
216
+ 0x370e => 'AttachMIMEType',
217
+ 0x7ffb => {
218
+ Name => 'ExceptionStartTime',
219
+ %dateInfo,
220
+ Unknown => 1, # (because these values don't make sense in my samples)
221
+ },
222
+ 0x7ffc => { Name => 'ExceptionEndTime', Unknown => 1, %dateInfo },
223
+ );
224
+
225
+ #------------------------------------------------------------------------------
226
+ # Decompress RTF text (ref https://metacpan.org/pod/Mail::Exchange::Message)
227
+ # Inputs: 0) ExifTool ref, 1) compressed RTF
228
+ # Returns: Decompressed RTF or '' on error
229
+ sub DecompressRTF($$)
230
+ {
231
+ my ($et, $cdat) = @_;
232
+ return '' unless length $cdat > 16;
233
+ my $comp = unpack('x8V', $cdat);
234
+
235
+ if ($comp == 0x414c454D) {
236
+ return substr($cdat, 16);
237
+ } elsif ($comp != 0x75465a4c) {
238
+ $et->Warn(sprintf('Unknown RTF compression 0x%x', $comp));
239
+ return '';
240
+ }
241
+ my $dict = '{\rtf1\ansi\mac\deff0\deftab720{\fonttbl;}'.
242
+ '{\f0\fnil \froman \fswiss \fmodern '.
243
+ '\fscript \fdecor MS Sans SerifSymbolArialTimes'.
244
+ ' New RomanCourier{\colortbl\red0\green0\blue0'.
245
+ "\r\n".'\par \pard\plain\f0\fs20\b\i\u\tab\tx';
246
+ my $cpos = 16;
247
+ my $clen = length $cdat;
248
+ my $dpos = length $dict;
249
+ my $rtnVal = '';
250
+ while ($cpos < $clen) {
251
+ my $control = unpack('C', substr($cdat, $cpos++, 1));
252
+ my ($i, $j);
253
+ for ($i=0; $i<8 && $cpos<$clen; ++$i) {
254
+ if ($control & (1<<$i)) {
255
+ return $rtnVal if $cpos + 2 > $clen;
256
+ my $ref = unpack('n', substr($cdat, $cpos, 2));
257
+ $cpos += 2;
258
+ my $off = $ref >> 4;
259
+ my $len = ($ref & 0x0f) + 2;
260
+ return $rtnVal if $off == $dpos % 4096 or $off % 4096 >= length($dict);
261
+ for ($j=0; $j<$len; ++$j) {
262
+ my $ch = substr($dict, ($off++ % 4096), 1);
263
+ substr($dict, ($dpos++ % 4096), 1) = $ch;
264
+ $rtnVal .= $ch;
265
+ }
266
+ } else {
267
+ my $ch = substr($cdat, $cpos++, 1);
268
+ substr($dict, ($dpos++ % 4096), 1) = $ch;
269
+ $rtnVal .= $ch;
270
+ }
271
+ }
272
+ }
273
+ return $rtnVal;
274
+ }
275
+
276
+ #------------------------------------------------------------------------------
277
+ # Process TNEF message properties
278
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
279
+ # Returns: 1 on success
280
+ sub ProcessProps($$$)
281
+ {
282
+ my ($et, $dirInfo, $tagTbl) = @_;
283
+ my $dataPt = $$dirInfo{DataPt};
284
+ my $dataPos = $$dirInfo{DataPos};
285
+ my $dirLen = length $$dataPt;
286
+ return 0 unless $dirLen > 4;
287
+ my $entries = unpack('V', $$dataPt);
288
+ $et->VerboseDir('TNEF Properties', $entries);
289
+ my $pos = 4;
290
+ my $i;
291
+ for ($i=0; $i<$entries; ++$i) {
292
+ last if $pos + 4 > $dirLen;
293
+ my $type = Get16u($dataPt, $pos);
294
+ my $tag = Get16u($dataPt, $pos+2);
295
+ $pos += 4;
296
+ # handle named properties (bit 0x8000 set)
297
+ if ($tag & 0x8000) {
298
+ last if $pos + 24 > $dirLen;
299
+ my $uid = Image::ExifTool::ASF::GetGUID(substr($$dataPt, $pos, 16));
300
+ $uid =~ s/-0000-0000-C000-000000000046$//; # remove common suffix
301
+ my $idtype = Get32u($dataPt, $pos + 16);
302
+ my $num = Get32u($dataPt, $pos + 20);
303
+ $pos += 24;
304
+ if ($idtype == 0) { # number
305
+ $tag = $uid . sprintf('_%.8x', $num);
306
+ } elsif ($idtype == 1) { # string
307
+ last if $pos + $num > $dirLen or $num < 2;
308
+ # decode string (ignoring null terminator)
309
+ my $name = $et->Decode(substr($$dataPt, $pos, $num-2), 'UTF16');
310
+ $tag = "${uid}_$name";
311
+ AddTagToTable($tagTbl, $tag, {
312
+ Name => Image::ExifTool::MakeTagName($name)
313
+ }) unless $$tagTbl{$tag};
314
+ $pos += ($num + 3) & 0xfffffffc; # (padded to an even 4 bytes)
315
+ } else {
316
+ last; # error
317
+ }
318
+ }
319
+ my $count = 1;
320
+ my ($multi, $fmt);
321
+ if ($type & 0x1000) {
322
+ $multi = 1;
323
+ $type &= 0x0fff;
324
+ last if $pos + 4 > $dirLen;
325
+ $count = Get32u($dataPt, $pos);
326
+ $pos += 4;
327
+ }
328
+ $fmt = $propType{$type} or last;
329
+ while ($count) {
330
+ my $size = $fmtSize{$fmt};
331
+ my $val;
332
+ unless ($size) {
333
+ if ($fmt =~ /(\d+)/) {
334
+ $size = $count * $1 / 8;
335
+ } elsif ($fmt eq 'null') {
336
+ $val = ''; # ($size is already 0)
337
+ } else {
338
+ # skip 1 count for "special case" stupidity
339
+ $pos += 4 unless $multi;
340
+ last if $pos + 4 > $dirLen;
341
+ $size = Get32u($dataPt, $pos);
342
+ $pos += 4;
343
+ last if $pos + $size > $dirLen;
344
+ $val = substr($$dataPt, $pos, $size);
345
+ }
346
+ }
347
+ if (not defined $val) {
348
+ $val = ReadValue($dataPt, $pos, $fmt, $count, $size);
349
+ if ($type == 0x06 or $type == 0x07 or $type == 0x0b or $type == 0x40) {
350
+ my @a = split ' ', $val;
351
+ if ($type == 0x06) { # currency
352
+ $_ = $_ / 10000 foreach @a;
353
+ } elsif ($type == 0x07) { # OLE date
354
+ # convert time from days since Dec 30, 1899
355
+ foreach (@a) {
356
+ $_ = ($_ - 25569) * 24 * 3600 if $_ != 0;
357
+ $_ = Image::ExifTool::ConvertUnixTime($_);
358
+ }
359
+ } elsif ($type == 0x0b) { # boolean
360
+ $_ = $_ ? 'True' : 'False' foreach @a;
361
+ } elsif ($type == 0x40) { # SYSTIME
362
+ # convert time from 100-ns intervals since Jan 1, 1601
363
+ $_ = Image::ExifTool::ConvertUnixTime($_/1e7-11644473600,1) foreach @a;
364
+ }
365
+ $val = @a > 1 ? \@a : $a[0];
366
+ }
367
+ $count = 1; # (read them all already)
368
+ } elsif ($fmt eq 'GUID') {
369
+ $val = Image::ExifTool::ASF::GetGUID($val);
370
+ } elsif ($fmt eq 'Unicode') {
371
+ ($val = $et->Decode($val, 'UTF16')) =~ s/\0+$//;
372
+ } elsif ($fmt eq 'string') {
373
+ $val =~ s/\0+$//;
374
+ # convert from specified code page if supported
375
+ $val = $et->Decode($val, $$et{Charset}) if $$et{Charset};
376
+ } elsif ($fmt eq 'undef' and length $val) {
377
+ my $copy = $val;
378
+ $val = \$copy; # change to a binary data reference
379
+ }
380
+ $et->HandleTag($tagTbl, $tag, $val,
381
+ DataPt => $dataPt,
382
+ DataPos => $dataPos,
383
+ Start => $pos,
384
+ Size => $size,
385
+ Format => sprintf('%s, type 0x%.2x', $fmt, $type),
386
+ Index => $i,
387
+ );
388
+ $pos += ($size + 3) & 0xfffffffc;
389
+ --$count;
390
+ }
391
+ }
392
+ $et->Warn('Error parsing message properties') unless $i == $entries;
393
+ return 1;
394
+ }
395
+
396
+ #------------------------------------------------------------------------------
397
+ # Extract EXIF information from a TNEF image
398
+ # Inputs: 0) ExifTool object reference, 1) dirInfo reference
399
+ # Returns: 1 on success, 0 if this wasn't a valid TNEF file
400
+ sub ProcessTNEF($$)
401
+ {
402
+ my ($et, $dirInfo) = @_;
403
+ my $raf = $$dirInfo{RAF};
404
+ my ($buff, $tagTablePtr);
405
+
406
+ # verify this is a valid TNEF file (read TNEFHeader and TNEFVersion)
407
+ return 0 unless $raf->Read($buff, 0x15) == 0x15 and $raf->Seek(6, 0);
408
+ return 0 unless $buff =~ /^\x78\x9f\x3e\x22..\x01\x06\x90\x08\0/s;
409
+ $et->SetFileType('TNEF');
410
+ SetByteOrder('II');
411
+ my $tagTbl = GetTagTable('Image::ExifTool::TNEF::Main');
412
+ # read through the attributes
413
+ while ($raf->Read($buff, 9) == 9) {
414
+ # (ignore the attrLevel byte: 1 for message, 2 for attachment)
415
+ my ($tag, $len) = unpack('x1VV', $buff);
416
+ # increment document number for each attachment
417
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT} if $tag == 0x069002;
418
+ $raf->Read($buff, $len) == $len or last;
419
+ my $tagInfo = $$tagTbl{$tag};
420
+ my ($val, $fmt);
421
+ if ($tagInfo and $$tagInfo{Format}) {
422
+ $fmt = $$tagInfo{Format};
423
+ if ($fmt eq 'date' and length($buff) >= 12) {
424
+ my @date = unpack('v6', $buff);
425
+ $val = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', @date);
426
+ }
427
+ } else {
428
+ $val = $buff;
429
+ }
430
+ $et->HandleTag($tagTbl, $tag, $val,
431
+ DataPt => \$buff,
432
+ DataPos => $raf->Tell() - $len,
433
+ Format => $fmt,
434
+ );
435
+ delete $$et{DOC_NUM} if $tag == 0x069005; # end of attachment
436
+ $raf->Seek(2, 1); # skip checksum
437
+ }
438
+ delete $$et{DOC_NUM};
439
+ return 1;
440
+ }
441
+
442
+ 1; # end
443
+
444
+ __END__
445
+
446
+ =head1 NAME
447
+
448
+ Image::ExifTool::TNEF - Read TNEF meta information
449
+
450
+ =head1 SYNOPSIS
451
+
452
+ This module is used by Image::ExifTool
453
+
454
+ =head1 DESCRIPTION
455
+
456
+ This module contains definitions required by Image::ExifTool to read TNEF
457
+ (Transport Neutral Encapsulation Format) files.
458
+
459
+ =head1 AUTHOR
460
+
461
+ Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
462
+
463
+ This library is free software; you can redistribute it and/or modify it
464
+ under the same terms as Perl itself.
465
+
466
+ =head1 REFERENCES
467
+
468
+ =over 4
469
+
470
+ =item L<https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXTNEF/%5bMS-OXTNEF%5d.pdf>
471
+
472
+ =item L<https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXCMSG/%5bMS-OXCMSG%5d.pdf>
473
+
474
+ =item L<https://msopenspecs.azureedge.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf>
475
+
476
+ =item L<https://officeprotocoldoc.z19.web.core.windows.net/files/MS-OXCDATA/%5bMS-OXCDATA%5d.pdf>
477
+
478
+ =item L<https://github.com/echo-devim/pyjacktrick/blob/main/mapi_constants.py>
479
+
480
+ =back
481
+
482
+ =head1 SEE ALSO
483
+
484
+ L<Image::ExifTool::TagNames/TNEF Tags>,
485
+ L<Image::ExifTool(3pm)|Image::ExifTool>
486
+
487
+ =cut