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.
- package/bin/README.txt +14 -14
- package/bin/exiftool.exe +0 -0
- package/bin/exiftool_files/exiftool.pl +118 -47
- package/bin/exiftool_files/lib/Image/ExifTool/Apple.pm +12 -2
- package/bin/exiftool_files/lib/Image/ExifTool/BuildTagLookup.pm +16 -10
- package/bin/exiftool_files/lib/Image/ExifTool/Canon.pm +3 -2
- package/bin/exiftool_files/lib/Image/ExifTool/CanonRaw.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/DJI.pm +190 -29
- package/bin/exiftool_files/lib/Image/ExifTool/DarwinCore.pm +22 -11
- package/bin/exiftool_files/lib/Image/ExifTool/EXE.pm +2 -9
- package/bin/exiftool_files/lib/Image/ExifTool/GM.pm +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/GPS.pm +3 -3
- package/bin/exiftool_files/lib/Image/ExifTool/Geolocation.dat +0 -0
- package/bin/exiftool_files/lib/Image/ExifTool/GoPro.pm +86 -48
- package/bin/exiftool_files/lib/Image/ExifTool/ICO.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/JPEG.pm +12 -2
- package/bin/exiftool_files/lib/Image/ExifTool/JSON.pm +5 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Kodak.pm +3 -2
- package/bin/exiftool_files/lib/Image/ExifTool/Nikon.pm +1003 -1399
- package/bin/exiftool_files/lib/Image/ExifTool/NikonCustom.pm +4 -4
- package/bin/exiftool_files/lib/Image/ExifTool/Olympus.pm +2 -1
- package/bin/exiftool_files/lib/Image/ExifTool/PCAP.pm +462 -0
- package/bin/exiftool_files/lib/Image/ExifTool/PDF.pm +10 -1
- package/bin/exiftool_files/lib/Image/ExifTool/PLIST.pm +92 -29
- package/bin/exiftool_files/lib/Image/ExifTool/PNG.pm +7 -1
- package/bin/exiftool_files/lib/Image/ExifTool/Photoshop.pm +2 -2
- package/bin/exiftool_files/lib/Image/ExifTool/Plot.pm +713 -0
- package/bin/exiftool_files/lib/Image/ExifTool/Protobuf.pm +24 -11
- package/bin/exiftool_files/lib/Image/ExifTool/Qualcomm.pm +78 -1
- package/bin/exiftool_files/lib/Image/ExifTool/QuickTime.pm +348 -318
- package/bin/exiftool_files/lib/Image/ExifTool/QuickTimeStream.pl +75 -27
- package/bin/exiftool_files/lib/Image/ExifTool/Samsung.pm +4 -0
- package/bin/exiftool_files/lib/Image/ExifTool/Sony.pm +34 -15
- package/bin/exiftool_files/lib/Image/ExifTool/TagLookup.pm +5061 -4967
- package/bin/exiftool_files/lib/Image/ExifTool/TagNames.pod +8302 -8160
- package/bin/exiftool_files/lib/Image/ExifTool/Trailer.pm +318 -0
- package/bin/exiftool_files/lib/Image/ExifTool/Validate.pm +4 -4
- package/bin/exiftool_files/lib/Image/ExifTool/WriteCanonRaw.pl +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WriteExif.pl +9 -4
- package/bin/exiftool_files/lib/Image/ExifTool/WritePDF.pl +1 -1
- package/bin/exiftool_files/lib/Image/ExifTool/WriteQuickTime.pl +62 -5
- package/bin/exiftool_files/lib/Image/ExifTool/Writer.pl +14 -13
- package/bin/exiftool_files/lib/Image/ExifTool/XMP.pm +34 -6
- package/bin/exiftool_files/lib/Image/ExifTool/XMP2.pl +5 -2
- package/bin/exiftool_files/lib/Image/ExifTool.pm +193 -92
- package/bin/exiftool_files/lib/Image/ExifTool.pod +121 -124
- package/bin/exiftool_files/windows_exiftool.txt +95 -71
- package/package.json +4 -4
- 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 =
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
1300
|
-
ExtractEmbedded FastScan Filter FixBase Geolocation
|
|
1301
|
-
GeolocFeature GeolocMinPop GeolocMaxDist
|
|
1302
|
-
HexTagIDs IgnoreGroups IgnoreMinorErrors
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
TimeZone Unknown UserParam
|
|
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 (
|
|
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.
|
|
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
|
|
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
|
-
|
|
3808
|
-
|
|
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);
|