exiftool-vendored.exe 13.16.0 → 13.25.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 (49) hide show
  1. package/bin/README.txt +14 -14
  2. package/bin/exiftool.exe +0 -0
  3. package/bin/exiftool_files/exiftool.pl +118 -47
  4. package/bin/exiftool_files/lib/Image/ExifTool/Apple.pm +12 -2
  5. package/bin/exiftool_files/lib/Image/ExifTool/BuildTagLookup.pm +16 -10
  6. package/bin/exiftool_files/lib/Image/ExifTool/Canon.pm +3 -2
  7. package/bin/exiftool_files/lib/Image/ExifTool/CanonRaw.pm +1 -1
  8. package/bin/exiftool_files/lib/Image/ExifTool/DJI.pm +190 -29
  9. package/bin/exiftool_files/lib/Image/ExifTool/DarwinCore.pm +22 -11
  10. package/bin/exiftool_files/lib/Image/ExifTool/EXE.pm +2 -9
  11. package/bin/exiftool_files/lib/Image/ExifTool/GM.pm +1 -1
  12. package/bin/exiftool_files/lib/Image/ExifTool/GPS.pm +3 -3
  13. package/bin/exiftool_files/lib/Image/ExifTool/Geolocation.dat +0 -0
  14. package/bin/exiftool_files/lib/Image/ExifTool/GoPro.pm +86 -48
  15. package/bin/exiftool_files/lib/Image/ExifTool/ICO.pm +2 -2
  16. package/bin/exiftool_files/lib/Image/ExifTool/JPEG.pm +12 -2
  17. package/bin/exiftool_files/lib/Image/ExifTool/JSON.pm +5 -1
  18. package/bin/exiftool_files/lib/Image/ExifTool/Kodak.pm +3 -2
  19. package/bin/exiftool_files/lib/Image/ExifTool/Nikon.pm +1003 -1399
  20. package/bin/exiftool_files/lib/Image/ExifTool/NikonCustom.pm +4 -4
  21. package/bin/exiftool_files/lib/Image/ExifTool/Olympus.pm +2 -1
  22. package/bin/exiftool_files/lib/Image/ExifTool/PCAP.pm +462 -0
  23. package/bin/exiftool_files/lib/Image/ExifTool/PDF.pm +10 -1
  24. package/bin/exiftool_files/lib/Image/ExifTool/PLIST.pm +92 -29
  25. package/bin/exiftool_files/lib/Image/ExifTool/PNG.pm +7 -1
  26. package/bin/exiftool_files/lib/Image/ExifTool/Photoshop.pm +2 -2
  27. package/bin/exiftool_files/lib/Image/ExifTool/Plot.pm +713 -0
  28. package/bin/exiftool_files/lib/Image/ExifTool/Protobuf.pm +24 -11
  29. package/bin/exiftool_files/lib/Image/ExifTool/Qualcomm.pm +78 -1
  30. package/bin/exiftool_files/lib/Image/ExifTool/QuickTime.pm +348 -318
  31. package/bin/exiftool_files/lib/Image/ExifTool/QuickTimeStream.pl +75 -27
  32. package/bin/exiftool_files/lib/Image/ExifTool/Samsung.pm +4 -0
  33. package/bin/exiftool_files/lib/Image/ExifTool/Sony.pm +34 -15
  34. package/bin/exiftool_files/lib/Image/ExifTool/TagLookup.pm +5061 -4967
  35. package/bin/exiftool_files/lib/Image/ExifTool/TagNames.pod +8302 -8160
  36. package/bin/exiftool_files/lib/Image/ExifTool/Trailer.pm +318 -0
  37. package/bin/exiftool_files/lib/Image/ExifTool/Validate.pm +4 -4
  38. package/bin/exiftool_files/lib/Image/ExifTool/WriteCanonRaw.pl +1 -1
  39. package/bin/exiftool_files/lib/Image/ExifTool/WriteExif.pl +9 -4
  40. package/bin/exiftool_files/lib/Image/ExifTool/WritePDF.pl +1 -1
  41. package/bin/exiftool_files/lib/Image/ExifTool/WriteQuickTime.pl +62 -5
  42. package/bin/exiftool_files/lib/Image/ExifTool/Writer.pl +14 -13
  43. package/bin/exiftool_files/lib/Image/ExifTool/XMP.pm +34 -6
  44. package/bin/exiftool_files/lib/Image/ExifTool/XMP2.pl +5 -2
  45. package/bin/exiftool_files/lib/Image/ExifTool.pm +193 -92
  46. package/bin/exiftool_files/lib/Image/ExifTool.pod +121 -124
  47. package/bin/exiftool_files/windows_exiftool.txt +95 -71
  48. package/package.json +4 -4
  49. package/bin/exiftool_files/lib/Image/ExifTool/Vivo.pm +0 -124
@@ -0,0 +1,318 @@
1
+ #------------------------------------------------------------------------------
2
+ # File: Trailer.pm
3
+ #
4
+ # Description: Read JPEG trailer written by various makes of phone
5
+ #
6
+ # Revisions: 2025-01-27 - P. Harvey Created
7
+ #------------------------------------------------------------------------------
8
+
9
+ package Image::ExifTool::Trailer;
10
+
11
+ use strict;
12
+ use vars qw($VERSION);
13
+ use Image::ExifTool qw(:DataAccess :Utils);
14
+
15
+ $VERSION = '1.01';
16
+
17
+ %Image::ExifTool::Trailer::Vivo = (
18
+ GROUPS => { 0 => 'Trailer', 1 => 'Vivo', 2 => 'Image' },
19
+ VARS => { NO_ID => 1 },
20
+ NOTES => 'Information written in JPEG trailer by some Vivo phones.',
21
+ # (don't know for sure what type of image this is, but it is in JPEG format)
22
+ HDRImage => {
23
+ Notes => 'highlights of HDR image',
24
+ Groups => { 2 => 'Preview' },
25
+ Binary => 1,
26
+ },
27
+ JSONInfo => { },
28
+ HiddenData => {
29
+ Notes => 'hidden in EXIF, not in trailer. This data is lost if the file is edited',
30
+ Groups => { 0 => 'EXIF' },
31
+ },
32
+ );
33
+
34
+ %Image::ExifTool::Trailer::OnePlus = (
35
+ GROUPS => { 0 => 'Trailer', 1 => 'OnePlus', 2 => 'Image' },
36
+ NOTES => 'Information written in JPEG trailer by some OnePlus phones.',
37
+ JSONInfo => { },
38
+ 'private.emptyspace' => { # length of the entire OnePlus trailer
39
+ Name => 'OnePlusTrailerLen',
40
+ ValueConv => 'length $val == 4 ? unpack("N", $val) : $val',
41
+ Unknown => 1,
42
+ },
43
+ 'watermark.device' => {
44
+ Name => 'Device',
45
+ ValueConv => '"0x" . join(" ", unpack("H10Z*", $val))',
46
+ Format => 'string',
47
+ },
48
+ );
49
+
50
+ # Google and/or Android information in JPEG trailer
51
+ %Image::ExifTool::Trailer::Google = (
52
+ GROUPS => { 0 => 'Trailer', 1 => 'Google', 2 => 'Image' },
53
+ NOTES => q{
54
+ Google-defined information written in the trailer of JPEG images by some
55
+ phones. This information is referenced by DirectoryItem entries in the XMP.
56
+ Note that some of this information may also be referenced from other
57
+ metadata formats, and hence may be extracted twice. For example,
58
+ MotionPhotoVideo may also exist within a Samsung trailer as
59
+ EmbbededVideoFile, or GainMapImage may also exist in an MPF trailer as
60
+ MPImage2.
61
+ },
62
+ MotionPhoto => { Name => 'MotionPhotoVideo', Groups => { 2 => 'Video' } },
63
+ GainMap => { Name => 'GainMapImage', Groups => { 2 => 'Preview' } },
64
+ Depth => { Name => 'DepthMapImage', Groups => { 2 => 'Preview' } },
65
+ Confidence => { Name => 'ConfidenceMapImage',Groups => { 2 => 'Preview' } },
66
+ 'android/depthmap' => { Name => 'DepthMapImage', Groups => { 2 => 'Preview' } },
67
+ 'android/confidencemap' => { Name => 'ConfidenceMapImage', Groups => { 2 => 'Preview' } },
68
+ );
69
+
70
+ #------------------------------------------------------------------------------
71
+ # Process Vivo trailer
72
+ # Inputs: 0) ExifTool object reference, 1) dirInfo reference
73
+ # Returns: 1 on success, 0 on failure, -1 if we must scan for the start
74
+ # of the trailer to set the ExifTool TrailerStart member
75
+ # - takes Offset as positive offset from end of trailer to end of file,
76
+ # and returns DataPos and DirLen, and updates OutFile when writing
77
+ sub ProcessVivo($$)
78
+ {
79
+ my ($et, $dirInfo) = @_;
80
+ my $raf = $$dirInfo{RAF};
81
+ my $buff;
82
+
83
+ # return now unless we are at a position to scan for the trailer
84
+ # (must scan because the trailer footer doesn't indicate the trailer length)
85
+ return -1 unless $$dirInfo{ScanForTrailer};
86
+
87
+ my $pos = $$et{TrailerStart} or return 0;
88
+ my $len = $$et{FileEnd} - $pos - $$dirInfo{Offset};
89
+ $raf->Seek($pos, 0) or return 0;
90
+ return 0 unless $len > 0 and $len < 1e7 and $raf->Read($buff, $len) == $len and
91
+ $buff =~ /\xff{4}\x1b\*9HWfu\x84\x93\xa2\xb1$/ and # validate footer
92
+ $buff =~ /(streamdata|vivo\{")/g; # find start
93
+ my $start = pos($buff) - length($1);
94
+ if ($start) {
95
+ $pos += $start;
96
+ $len -= $start;
97
+ $buff = substr($buff, $start);
98
+ }
99
+ # set trailer position and length
100
+ @$dirInfo{'DataPos','DirLen'} = ($pos, $len);
101
+
102
+ # let ProcessTrailers copy or delete this trailer
103
+ return -1 if $$dirInfo{OutFile};
104
+
105
+ $et->DumpTrailer($dirInfo) if $$et{OPTIONS}{Verbose} or $$et{HTML_DUMP};
106
+ my $tbl = GetTagTable('Image::ExifTool::Trailer::Vivo');
107
+ pos($buff) = 0; # rewind search to start of buffer
108
+ if ($buff =~ /^streamdata\xff\xd8\xff/ and $buff =~ /\xff\xd9stream(info|coun)/g) {
109
+ $et->HandleTag($tbl, HDRImage => substr($buff, 10, pos($buff)-20));
110
+ }
111
+ # continue looking for Vivo JSON data
112
+ if ($buff =~ /vivo\{"/g) {
113
+ my $jsonStart = pos($buff) - 2;
114
+ if ($buff =~ /\}\0/g) {
115
+ my $jsonLen = pos($buff) - 1 - $jsonStart;
116
+ $et->HandleTag($tbl, JSONInfo => substr($buff, $jsonStart, $jsonLen));
117
+ }
118
+ }
119
+ return 1;
120
+ }
121
+
122
+ #------------------------------------------------------------------------------
123
+ # Process OnePlus trailer
124
+ # Inputs: 0) ExifTool object reference, 1) dirInfo reference
125
+ # Returns: 1 on success, 0 on failure, -1 if we must scan for the start
126
+ # of the trailer to set the ExifTool TrailerStart member
127
+ # - takes Offset as positive offset from end of trailer to end of file,
128
+ # and returns DataPos and DirLen, and updates OutFile when writing
129
+ sub ProcessOnePlus($$)
130
+ {
131
+ my ($et, $dirInfo) = @_;
132
+ my $raf = $$dirInfo{RAF};
133
+ my ($buff, $buf2);
134
+
135
+ # return now unless we are at a position to scan for the trailer
136
+ # (must scan because the trailer footer doesn't indicate the entire trailer length)
137
+ return -1 unless $$dirInfo{ScanForTrailer};
138
+
139
+ # return -1 to let ProcessTrailers copy or delete the entire trailer
140
+ return -1 if $$dirInfo{OutFile};
141
+
142
+ my $start = $$et{TrailerStart} or return 0;
143
+ $raf->Seek(-8-$$dirInfo{Offset}, 2) and $raf->Read($buff, 8) == 8 or return 0;
144
+ my $end = $raf->Tell(); # (same as FileEnd - Offset)
145
+
146
+ my $dump = ($$et{OPTIONS}{Verbose} or $$et{HTML_DUMP});
147
+ my $tagTable = GetTagTable('Image::ExifTool::Trailer::OnePlus');
148
+ my $trailLen = 0;
149
+ if ($buff =~ /^jxrs...\0$/) {
150
+ my $jlen = unpack('x4V', $buff);
151
+ my $maxOff = 0;
152
+ if ($jlen < $end-$start and $jlen > 8 and $raf->Seek($end-$jlen) and
153
+ $raf->Read($buff, $jlen-8) == $jlen-8)
154
+ {
155
+ $buff =~ s/\0+$//; # remove trailing null(s)
156
+ require Image::ExifTool::Import;
157
+ my $list = Image::ExifTool::Import::ReadJSONObject(undef, \$buff);
158
+ if (ref $list eq 'ARRAY') {
159
+ $$_{offset} and $$_{offset} > $maxOff and $maxOff = $$_{offset} foreach @$list;
160
+ $trailLen = $maxOff + $jlen;
161
+ if ($dump and $trailLen) {
162
+ $et->DumpTrailer({
163
+ RAF => $raf,
164
+ DirName => 'OnePlus',
165
+ DataPos => $end-$trailLen,
166
+ DirLen => $trailLen,
167
+ });
168
+ }
169
+ $et->HandleTag($tagTable, JSONInfo => $buff);
170
+ foreach (@$list) {
171
+ my ($off, $name, $len) = @$_{qw(offset name length)};
172
+ next unless $off and $name and $len;
173
+ if ($raf->Seek($end-$jlen-$off) and $raf->Read($buf2, $len) == $len) {
174
+ $et->HandleTag($tagTable, $name, $buf2, DataPos => $end-$jlen-$off, DataPt => \$buf2);
175
+ }
176
+ }
177
+ } else {
178
+ $et->HandleTag($tagTable, JSONInfo => $buff);
179
+ $et->Warn('Error parsing OnePlus JSON information');
180
+ }
181
+ }
182
+ }
183
+ @$dirInfo{'DataPos','DirLen'} = ($end - $trailLen, $trailLen);
184
+
185
+ return 1;
186
+ }
187
+
188
+ #------------------------------------------------------------------------------
189
+ # Process Google trailer
190
+ # Inputs: 0) ExifTool object reference, 1) dirInfo reference
191
+ # Returns: 1 on success, 0 on failure, -1 if we must scan for the start
192
+ # of the trailer to set the ExifTool TrailerStart member
193
+ # - this trailer won't be identified when writing because XMP isn't extracted then
194
+ sub ProcessGoogle($$)
195
+ {
196
+ my ($et, $dirInfo) = @_;
197
+ my $raf = $$dirInfo{RAF};
198
+ my $info = $$et{VALUE};
199
+
200
+ my ($tag, $mime, $len, $pad) = @$info{qw(DirectoryItemSemantic DirectoryItemMime
201
+ DirectoryItemLength DirectoryItemPadding)};
202
+
203
+ unless (ref $tag eq 'ARRAY' and ref $mime eq 'ARRAY') {
204
+ undef $pad;
205
+ ($tag, $mime, $len) = @$info{qw(ContainerDirectoryItemDataURI
206
+ ContainerDirectoryItemMime ContainerDirectoryItemLength)};
207
+ unless (ref $mime eq 'ARRAY' and ref $tag eq 'ARRAY') {
208
+ delete $$et{ProcessGoogleTrailer};
209
+ return 0;
210
+ }
211
+ }
212
+ # we need to know TrailerStart to be able to read/write this trailer
213
+ return -1 unless $$dirInfo{ScanForTrailer};
214
+
215
+ delete $$et{ProcessGoogleTrailer}; # reset flag to process the Google trailer
216
+
217
+ return -1 if $$dirInfo{OutFile}; # let caller handle the writing
218
+
219
+ # sometimes DirectoryItemLength is missing the Primary entry
220
+ $len = [ $len ] unless ref $len eq 'ARRAY';
221
+ unshift @$len, 0 while @$len < @$mime;
222
+
223
+ my $start = $$et{TrailerStart} or return 0;
224
+ my $end = $$et{FileEnd}; # (ignore Offset for now because some entries may run into other trailers)
225
+
226
+ my $dump = ($$et{OPTIONS}{Verbose} or $$et{HTML_DUMP});
227
+ my $tagTable = GetTagTable('Image::ExifTool::Trailer::Google');
228
+
229
+ # (ignore first entry: "Primary" or "primary_image")
230
+ my ($i, $pos, $buff, $regex, $grp, $type);
231
+ for ($i=1, $pos=0; defined $$mime[$i]; ++$i) {
232
+ my $more = $end - $start - $pos;
233
+ last if $more < 16;
234
+ next unless $$len[$i] and defined $$tag[$i];
235
+ last if $$len[$i] > $more;
236
+ $raf->Seek($start+$pos) and $raf->Read($buff, 16) == 16 and $raf->Seek($start+$pos) or last;
237
+ if ($$mime[$i] eq 'image/jpeg') {
238
+ $regex = '\xff\xd8\xff[\xdb\xe0\xe1]';
239
+ } elsif ($$mime[$i] eq 'video/mp4') {
240
+ $regex = '\0\0\0.ftyp(mp42|isom)';
241
+ } else {
242
+ $et->Warn("Google trailer $$tag[$i] $$mime[$i] not handled");
243
+ next;
244
+ }
245
+ if ($buff =~ /^$regex/s) {
246
+ last unless $raf->Read($buff, $$len[$i]) == $$len[$i];
247
+ } else {
248
+ last if $pos; # don't skip unknown information again
249
+ last unless $raf->Read($buff, $more) == $more;
250
+ last unless $buff =~ /($regex)/sg;
251
+ $pos += pos($buff) - length($1);
252
+ $more = $end - $start - $pos;
253
+ last if $$len[$i] > $end - $start - $pos;
254
+ $buff = substr($buff, $pos, $$len[$i]);
255
+ }
256
+ unless ($$tagTable{$$tag[$i]}) {
257
+ my $name = $$tag[$i];
258
+ $name =~ s/([^A-Za-z])([a-z])/$1\u$2/g; # capitalize words
259
+ $name = Image::ExifTool::MakeTagName($$tag[$i]);
260
+ if ($$mime[$i] eq 'image/jpeg') {
261
+ ($type, $grp) = ('Image', 'Preview');
262
+ } else {
263
+ ($type, $grp) = ('Video', 'Video');
264
+ }
265
+ $et->VPrint(0, $$et{INDENT}, "[adding Google:$name]\n");
266
+ AddTagToTable($tagTable, $$tag[$i], { Name => "$name$type", Groups => { 2 => $grp } });
267
+ }
268
+ $dump and $et->DumpTrailer({
269
+ RAF => $raf,
270
+ DirName => $$tag[$i],
271
+ DataPos => $start + $pos,
272
+ DirLen => $$len[$i],
273
+ });
274
+ $et->HandleTag($tagTable, $$tag[$i], \$buff, DataPos => $start + $pos, DataPt => \$buff);
275
+ # (haven't seen non-zero padding, but I assume this is how it works
276
+ $pos += $$len[$i] + (($pad and $$pad[$i]) ? $$pad[$i] : 0);
277
+ }
278
+ if (defined $$tag[$i] and defined $$mime[$i]) {
279
+ $et->Warn("Error reading $$tag[$i] $$mime[$i] from trailer", 1);
280
+ }
281
+ return 0 unless $pos;
282
+
283
+ @$dirInfo{'DataPos','DirLen'} = ($start, $pos);
284
+
285
+ return 1;
286
+ }
287
+
288
+ 1; # end
289
+
290
+ __END__
291
+
292
+ =head1 NAME
293
+
294
+ Image::ExifTool::Trailer - Read JPEG trailer written by various phone makes
295
+
296
+ =head1 SYNOPSIS
297
+
298
+ This module is used by Image::ExifTool
299
+
300
+ =head1 DESCRIPTION
301
+
302
+ This module contains definitions required by Image::ExifTool to read
303
+ metadata the trailer written by some Vivo, OnePlus and Google phones.
304
+
305
+ =head1 AUTHOR
306
+
307
+ Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
308
+
309
+ This library is free software; you can redistribute it and/or modify it
310
+ under the same terms as Perl itself.
311
+
312
+ =head1 SEE ALSO
313
+
314
+ L<Image::ExifTool::TagNames/Trailer Tags>,
315
+ L<Image::ExifTool(3pm)|Image::ExifTool>
316
+
317
+ =cut
318
+
@@ -188,10 +188,10 @@ my %validValue = (
188
188
  0x115 => undef, # SamplesPerPixel
189
189
  0x116 => undef, # RowsPerStrip
190
190
  0x117 => undef, # StripByteCounts
191
- 0x11a => 'defined $val', # XResolution
192
- 0x11b => 'defined $val', # YResolution
191
+ # (optional as of 3.0) 0x11a => 'defined $val', # XResolution
192
+ # (optional as of 3.0) 0x11b => 'defined $val', # YResolution
193
193
  0x11c => undef, # PlanarConfiguration
194
- 0x128 => '$val =~ /^[123]$/', # ResolutionUnit
194
+ # (optional as of 3.0) 0x128 => '$val =~ /^[123]$/', # ResolutionUnit
195
195
  0x201 => undef, # JPEGInterchangeFormat
196
196
  0x202 => undef, # JPEGInterchangeFormatLength
197
197
  0x212 => undef, # YCbCrSubSampling
@@ -218,7 +218,7 @@ my %validValue = (
218
218
  ExifIFD => {
219
219
  0x9000 => 'defined $val and $val =~ /^\d{4}$/', # ExifVersion
220
220
  0x9101 => 'defined $val', # ComponentsConfiguration
221
- 0xa000 => 'defined $val', # FlashpixVersion
221
+ # (optional as of 3.0) 0xa000 => 'defined $val', # FlashpixVersion
222
222
  0xa001 => '$val == 1 or $val == 0xffff', # ColorSpace
223
223
  0xa002 => 'defined $val', # PixelXDimension
224
224
  0xa003 => 'defined $val', # PixelYDimension
@@ -566,7 +566,7 @@ sub WriteCRW($$)
566
566
  my $trailPt;
567
567
  while ($success) {
568
568
  # check to see if trailer(s) exist(s)
569
- my $trailInfo = Image::ExifTool::IdentifyTrailer($raf) or last;
569
+ my $trailInfo = $et->IdentifyTrailer($raf) or last;
570
570
  # rewrite the trailer(s)
571
571
  $buff = '';
572
572
  $$trailInfo{OutFile} = \$buff;
@@ -25,9 +25,9 @@ my %crossDelete = (
25
25
  # mandatory tag default values
26
26
  my %mandatory = (
27
27
  IFD0 => {
28
- 0x011a => 72, # XResolution
29
- 0x011b => 72, # YResolution
30
- 0x0128 => 2, # ResolutionUnit (inches)
28
+ # (optional as of 3.0) 0x011a => 72, # XResolution
29
+ # (optional as of 3.0) 0x011b => 72, # YResolution
30
+ # (optional as of 3.0) 0x0128 => 2, # ResolutionUnit (inches)
31
31
  0x0213 => 1, # YCbCrPositioning (centered)
32
32
  # 0x8769 => ????, # ExifOffset
33
33
  },
@@ -40,7 +40,7 @@ my %mandatory = (
40
40
  ExifIFD => {
41
41
  0x9000 => '0232', # ExifVersion
42
42
  0x9101 => "1 2 3 0",# ComponentsConfiguration
43
- 0xa000 => '0100', # FlashpixVersion
43
+ # (optional as of 3.0) 0xa000 => '0100', # FlashpixVersion
44
44
  0xa001 => 0xffff, # ColorSpace (uncalibrated)
45
45
  # 0xa002 => ????, # ExifImageWidth
46
46
  # 0xa003 => ????, # ExifImageHeight
@@ -2269,6 +2269,11 @@ NoOverwrite: next if $isNew > 0;
2269
2269
  # build list of offsets to process
2270
2270
  my @offsetList;
2271
2271
  if ($ifd >= 0) {
2272
+ $dirName = $$dirInfo{DirName} || 'unknown';
2273
+ if ($ifd) {
2274
+ $dirName =~ s/\d+$//;
2275
+ $dirName .= $ifd;
2276
+ }
2272
2277
  my $offsetInfo = $offsetInfo[$ifd] or next;
2273
2278
  if ($$offsetInfo{0x111} and $$offsetInfo{0x144}) {
2274
2279
  # SubIFD may contain double-referenced data as both strips and tiles
@@ -546,7 +546,7 @@ sub WritePDF($$)
546
546
  }
547
547
  if ($metaChanged) {
548
548
  if ($newXMP) {
549
- unless ($metaRef) {
549
+ unless (ref $metaRef) {
550
550
  # allocate new PDF object
551
551
  $metaRef = \ "$nextObject 0 R";
552
552
  ++$nextObject;
@@ -891,7 +891,7 @@ sub WriteQuickTime($$$)
891
891
  $et or return 1; # allow dummy access to autoload this package
892
892
  my ($mdat, @mdat, @mdatEdit, $edit, $track, $outBuff, $co, $term, $delCount);
893
893
  my (%langTags, $canCreate, $delGrp, %boxPos, %didDir, $writeLast, $err, $atomCount);
894
- my ($tag, $lastTag, $lastPos, $errStr, $trailer, $buf2, $keysGrp, $keysPath);
894
+ my ($tag, $lastTag, $lastPos, $errStr, $trailer, $buf2, $keysGrp, $keysPath, $itemIndex);
895
895
  my $outfile = $$dirInfo{OutFile} || return 0;
896
896
  my $raf = $$dirInfo{RAF}; # (will be null for lower-level atoms)
897
897
  my $dataPt = $$dirInfo{DataPt}; # (will be null for top-level atoms)
@@ -982,8 +982,25 @@ sub WriteQuickTime($$$)
982
982
  $atomCount = $$tagTablePtr{VARS}{ATOM_COUNT} if $$tagTablePtr{VARS};
983
983
 
984
984
  $tag = $lastTag = '';
985
-
985
+ $itemIndex = 0 if $dirName eq 'ItemPropertyContainer';
986
+
987
+ # read ahead to parse item property associations if this is 'iprp' ItemProperties
988
+ # (necessary to determine which properties belong to primary item in HEIC file)
989
+ if ($dirName eq 'ItemProperties') {
990
+ my $pos = $raf->Tell();
991
+ for (;;) {
992
+ $raf->Read($buf2, 8) == 8 or last;
993
+ my $size = Get32u(\$buf2, 0) - 8; # (atom size without 8-byte header)
994
+ $tag = substr($buf2, 4, 4);
995
+ last if $size < 0;
996
+ $tag eq 'ipma' or $raf->Seek($size, 1), next;
997
+ ParseItemPropAssoc($buf2,$et) if $raf->Read($buf2,$size) == $size;
998
+ last;
999
+ }
1000
+ $raf->Seek($pos);
1001
+ }
986
1002
  for (;;) { # loop through all atoms at this level
1003
+ ++$itemIndex if defined $itemIndex;
987
1004
  $lastPos = $raf->Tell();
988
1005
  # stop processing if we reached a known trailer
989
1006
  if ($trailer and $lastPos >= $$trailer[1]) {
@@ -1061,6 +1078,10 @@ sub WriteQuickTime($$$)
1061
1078
  $et->Error("Can't yet write compressed movie metadata");
1062
1079
  return $rtnVal;
1063
1080
  } elsif ($tag eq 'wide') {
1081
+ if ($size) {
1082
+ $et->Warn("Incorrect size for 'wide' atom ($size bytes)");
1083
+ $raf->Seek($size, 1) or $et->Error('Truncated wide atom');
1084
+ }
1064
1085
  next; # drop 'wide' tag
1065
1086
  }
1066
1087
 
@@ -1182,6 +1203,36 @@ sub WriteQuickTime($$$)
1182
1203
  }
1183
1204
  undef $tagInfo if $tagInfo and $$tagInfo{AddedUnknown};
1184
1205
 
1206
+ # don't write this tag unless associated with the primary item
1207
+ # (Note: This relies on iinf and dimg coming before iprp)
1208
+ if (defined $itemIndex and $$et{ItemInfo}) {
1209
+ my $items = $$et{ItemInfo};
1210
+ my ($id, $prop, $isPrimary);
1211
+ my $primary = $$et{PrimaryItem};
1212
+ unless (defined $primary) {
1213
+ ($primary) = sort { $a <=> $b } keys %{$$et{ItemInfo}} if $$et{ItemInfo};
1214
+ $primary = 0 unless defined $primary;
1215
+ }
1216
+ my $pitem = $$items{$primary} || { };
1217
+ $$pitem{RefersTo} or $$pitem{RefersTo} = { };
1218
+ ItemID2: foreach $id (reverse sort { $a <=> $b } keys %$items) {
1219
+ next unless $$items{$id}{Association};
1220
+ my $item = $$items{$id};
1221
+ foreach $prop (@{$$item{Association}}) {
1222
+ next unless $prop == $itemIndex;
1223
+ my $dont = $dontInherit{$tag} || 0;
1224
+ last unless $id == $primary or (not $dont and
1225
+ ($$item{RefersTo} and $$item{RefersTo}{$primary})) or
1226
+ # special case to assume this property belongs to the primary
1227
+ # image if it belongs to an item referred to by the primary
1228
+ ($dont != 1 and $$pitem{RefersTo}{$id});
1229
+ $isPrimary = 1;
1230
+ last ItemID2;
1231
+ }
1232
+ }
1233
+ undef $tagInfo unless $isPrimary;
1234
+ }
1235
+
1185
1236
  if ($tagInfo and (not defined $$tagInfo{Writable} or $$tagInfo{Writable})) {
1186
1237
  my $subdir = $$tagInfo{SubDirectory};
1187
1238
  my ($newData, @chunkOffset);
@@ -1221,6 +1272,7 @@ sub WriteQuickTime($$$)
1221
1272
  OutFile => $outfile,
1222
1273
  NoRefTest=> 1, # don't check directory references
1223
1274
  WriteGroup => $$tagInfo{WriteGroup},
1275
+ Permanent => $$tagInfo{Permanent},
1224
1276
  # initialize array to hold details about chunk offset table
1225
1277
  # (each entry has 3-5 items: 0=atom type, 1=table offset, 2=table size,
1226
1278
  # 3=optional base offset, 4=optional item ID)
@@ -1464,7 +1516,10 @@ sub WriteQuickTime($$$)
1464
1516
  ++$$et{CHANGED};
1465
1517
  my $grp = $et->GetGroup($langInfo, 1);
1466
1518
  $et->VerboseValue("- $grp:$$langInfo{Name}", $val);
1467
- next unless defined $newData and not $$didTag{$nvHash};
1519
+ unless (defined $newData and not $$didTag{$nvHash}) {
1520
+ # must not delete items from iprp because it will mess up the ordering
1521
+ next unless defined $itemIndex;
1522
+ }
1468
1523
  $et->VerboseValue("+ $grp:$$langInfo{Name}", $newData);
1469
1524
  # add back necessary header and encode as necessary
1470
1525
  if (defined $lang) {
@@ -1493,7 +1548,7 @@ sub WriteQuickTime($$$)
1493
1548
  $newData = $et->Encode($newData, 'UTF8');
1494
1549
  } elsif ($format and not $$tagInfo{Binary}) {
1495
1550
  # format new value for writing
1496
- $newData = WriteValue($newData, $format);
1551
+ $newData = WriteValue($newData, $format, $$tagInfo{Count});
1497
1552
  }
1498
1553
  }
1499
1554
  $$didTag{$nvHash} = 1; # set flag so we don't add this tag again
@@ -1560,7 +1615,9 @@ sub WriteQuickTime($$$)
1560
1615
  }
1561
1616
  if ($msg) {
1562
1617
  # (allow empty sample description for non-audio/video handler types, eg. 'url ', 'meta')
1563
- if ($$et{MediaType}) {
1618
+ # (also, incorrectly written 'mett' SampleEntry by Google phones,
1619
+ # see https://exiftool.org/forum/index.php?msg=91158)
1620
+ if ($avType{$$et{MediaType}}) {
1564
1621
  my $grp = $$et{CUR_WRITE_GROUP} || $parent;
1565
1622
  $et->Error("$msg for $grp");
1566
1623
  return $rtnErr;
@@ -1296,15 +1296,15 @@ sub SetNewValuesFromFile($$;@)
1296
1296
  # ! option to decide how it is handled here. !
1297
1297
  # +------------------------------------------+
1298
1298
  foreach (qw(ByteUnit Charset CharsetEXIF CharsetFileName CharsetID3 CharsetIPTC
1299
- CharsetPhotoshop Composite DateFormat Debug EncodeHangs Escape ExtendedXMP
1300
- ExtractEmbedded FastScan Filter FixBase Geolocation GeolocAltNames
1301
- GeolocFeature GeolocMinPop GeolocMaxDist GlobalTimeShift GPSQuadrant
1302
- HexTagIDs IgnoreGroups IgnoreMinorErrors IgnoreTags ImageHashType Lang
1303
- LargeFileSupport LigoGPSScale ListItem ListSep MDItemTags
1304
- MissingTagValue NoPDFList NoWarning Password PrintConv QuickTimeUTC
1305
- RequestTags SaveFormat SavePath ScanForXMP StructFormat SystemTags
1306
- TimeZone Unknown UserParam Validate WindowsLongPath WindowsWideFile
1307
- XAttrTags XMPAutoConv))
1299
+ CharsetPhotoshop Composite DateFormat Debug EncodeHangs Escape
1300
+ ExtendedXMP ExtractEmbedded FastScan Filter FixBase Geolocation
1301
+ GeolocAltNames GeolocFeature GeolocMinPop GeolocMaxDist
1302
+ GlobalTimeShift GPSQuadrant HexTagIDs IgnoreGroups IgnoreMinorErrors
1303
+ IgnoreTags ImageHashType KeepUTCTime Lang LargeFileSupport
1304
+ LigoGPSScale ListItem ListSep MDItemTags MissingTagValue NoPDFList
1305
+ NoWarning Password PrintConv QuickTimeUTC RequestTags SaveFormat
1306
+ SavePath ScanForXMP StructFormat SystemTags TimeZone Unknown UserParam
1307
+ Validate WindowsLongPath WindowsWideFile XAttrTags XMPAutoConv))
1308
1308
  {
1309
1309
  $srcExifTool->Options($_ => $$options{$_});
1310
1310
  }
@@ -4262,9 +4262,11 @@ sub WriteDirectory($$$;$)
4262
4262
  if ($permanentDir{$grp0} and not ($$dirInfo{TagInfo} and $$dirInfo{TagInfo}{Deletable})) {
4263
4263
  undef $delFlag;
4264
4264
  }
4265
- # (never delete an entire QuickTime group)
4266
4265
  if ($delFlag) {
4267
- if (($grp0 =~ /^(MakerNotes)$/ or $grp1 =~ /^(IFD0|ExifIFD|MakerNotes)$/) and
4266
+ if ($$dirInfo{Permanent}) {
4267
+ $self->Warn("Not deleting permanent $dirName directory");
4268
+ undef $grp1;
4269
+ } elsif (($grp0 =~ /^(MakerNotes)$/ or $grp1 =~ /^(IFD0|ExifIFD|MakerNotes)$/) and
4268
4270
  $self->IsRawType() and
4269
4271
  # allow non-permanent MakerNote directories to be deleted (ie. NikonCapture)
4270
4272
  (not $$dirInfo{TagInfo} or not defined $$dirInfo{TagInfo}{Permanent} or
@@ -5044,7 +5046,6 @@ TryLib: for ($lib=$strptimeLib; ; $lib='') {
5044
5046
  last;
5045
5047
  }
5046
5048
  if (not $lib) {
5047
- last unless $$self{OPTIONS}{StrictDate};
5048
5049
  warn $wrn || "Install POSIX::strptime or Time::Piece for inverse date/time conversions\n";
5049
5050
  return undef;
5050
5051
  } elsif ($lib eq 'POSIX::strptime') {
@@ -6027,7 +6028,7 @@ sub WriteJPEG($$)
6027
6028
  Write($outfile, $hdr, $s, $segData) or $err = 1;
6028
6029
  my ($buff, $endPos, $trailInfo);
6029
6030
  my $delPreview = $$self{DEL_PREVIEW};
6030
- $trailInfo = IdentifyTrailer($raf) unless $$delGroup{Trailer};
6031
+ $trailInfo = $self->IdentifyTrailer($raf) unless $$delGroup{Trailer};
6031
6032
  my $nvTrail = $self->GetNewValueHash($Image::ExifTool::Extra{Trailer});
6032
6033
  unless ($oldOutfile or $delPreview or $trailInfo or $$delGroup{Trailer} or $nvTrail or
6033
6034
  $$self{HiddenData})
@@ -50,7 +50,7 @@ use Image::ExifTool::Exif;
50
50
  use Image::ExifTool::GPS;
51
51
  require Exporter;
52
52
 
53
- $VERSION = '3.70';
53
+ $VERSION = '3.72';
54
54
  @ISA = qw(Exporter);
55
55
  @EXPORT_OK = qw(EscapeXML UnescapeXML);
56
56
 
@@ -165,7 +165,7 @@ my %xmpNS = (
165
165
  #
166
166
  plus => 'http://ns.useplus.org/ldf/xmp/1.0/',
167
167
  # (prism recommendations from http://www.prismstandard.org/specifications/3.0/Image_Guide_3.0.htm)
168
- prism => 'http://prismstandard.org/namespaces/basic/2.0/',
168
+ prism => 'http://prismstandard.org/namespaces/basic/2.0/', # (maybe left at 2.0 to avoid compatibility issues -- think hard before changing this)
169
169
  prl => 'http://prismstandard.org/namespaces/prl/2.1/',
170
170
  pur => 'http://prismstandard.org/namespaces/prismusagerights/2.1/',
171
171
  pmi => 'http://prismstandard.org/namespaces/pmi/2.2/',
@@ -1247,8 +1247,22 @@ my %sPantryItem = (
1247
1247
  NAMESPACE => 'pdfx',
1248
1248
  NOTES => q{
1249
1249
  PDF extension tags. This namespace is used to store application-defined PDF
1250
- information, so there are no pre-defined tags. User-defined tags must be
1251
- created to enable writing of XMP-pdfx information.
1250
+ information, so there are few pre-defined tags. User-defined tags must be
1251
+ created to enable writing of other XMP-pdfx information.
1252
+ },
1253
+ SourceModified => {
1254
+ Name => 'SourceModified',
1255
+ Groups => { 2 => 'Time' },
1256
+ Shift => 'Time',
1257
+ ValueConv => 'require Image::ExifTool::PDF; $val = Image::ExifTool::PDF::ConvertPDFDate($val)',
1258
+ ValueConvInv => q{
1259
+ require Image::ExifTool::PDF;
1260
+ $val = Image::ExifTool::PDF::WritePDFValue($self,$val,"date");
1261
+ $val =~ tr/()//d;
1262
+ return $val;
1263
+ },
1264
+ PrintConv => '$self->ConvertDateTime($val)',
1265
+ PrintConvInv => '$self->InverseDateTime($val)',
1252
1266
  },
1253
1267
  );
1254
1268
 
@@ -3804,8 +3818,22 @@ sub ParseXMPElement($$$;$$$$)
3804
3818
  my ($parseResource, %attrs, @attrs);
3805
3819
  # this hangs Perl (v5.18.4) for a specific capture string [patched in ExifTool 12.98]
3806
3820
  # while ($attrs =~ m/(\S+?)\s*=\s*(['"])(.*?)\2/sg) {
3807
- while ($attrs =~ /(\S+?)\s*=\s*(['"])/g) {
3808
- my ($attr, $quote) = ($1, $2);
3821
+ # this may hang Perl v5.26.3 (but not v5.18.4) if there is lots of garbage in the XMP [patched in 13.23]
3822
+ # while ($attrs =~ /(\S+?)\s*=\s*(['"])/g) {
3823
+ for (;;) {
3824
+ my ($attr, $quote);
3825
+ if (length($attrs) < 2000) { # (do it the easy way if attributes aren't stupid long)
3826
+ last unless $attrs =~ /(\S+)\s*=\s*(['"])/g;
3827
+ ($attr, $quote) = ($1, $2);
3828
+ } else {
3829
+ # 13.23 patch to avoid capturing tons of garbage if XMP is corrupted
3830
+ last unless $attrs =~ /=\s*(['"])/g;
3831
+ $quote = $1;
3832
+ my $p = pos($attrs) > 1000 ? pos($attrs) - 1000 : 0;
3833
+ my $tmp = substr($attrs, $p, pos($attrs)-$p);
3834
+ last unless $tmp =~ /(\S+)\s*=\s*$quote$/;
3835
+ $attr = $1;
3836
+ }
3809
3837
  my $p0 = pos($attrs);
3810
3838
  last unless $attrs =~ /$quote/g;
3811
3839
  my $val = substr($attrs, $p0, pos($attrs)-$p0-1);