exiftool_vendored 13.16.0 → 13.17.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.
@@ -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.00';
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};
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
+
@@ -1061,6 +1061,10 @@ sub WriteQuickTime($$$)
1061
1061
  $et->Error("Can't yet write compressed movie metadata");
1062
1062
  return $rtnVal;
1063
1063
  } elsif ($tag eq 'wide') {
1064
+ if ($size) {
1065
+ $et->Warn("Incorrect size for 'wide' atom ($size bytes)");
1066
+ $raf->Seek($size, 1) or $et->Error('Truncated wide atom');
1067
+ }
1064
1068
  next; # drop 'wide' tag
1065
1069
  }
1066
1070
 
@@ -1995,7 +1995,8 @@ my %sACDSeeRegionStruct = (
1995
1995
  Struct => {
1996
1996
  STRUCT_NAME => 'DeviceItem',
1997
1997
  NAMESPACE => { Item => 'http://ns.google.com/photos/dd/1.0/item/' },
1998
- Mime => { },
1998
+ # use this as a key to process Google trailer
1999
+ Mime => { RawConv => '$$self{ProcessGoogleTrailer} = $val' },
1999
2000
  Length => { Writable => 'integer' },
2000
2001
  Padding => { Writable => 'integer' },
2001
2002
  DataURI => { },
@@ -2149,7 +2150,7 @@ my %sACDSeeRegionStruct = (
2149
2150
  STRUCT_NAME => 'Item',
2150
2151
  # (use 'GItem' to avoid conflict with Google Device Container Item)
2151
2152
  NAMESPACE => { GItem => 'http://ns.google.com/photos/1.0/container/item/'},
2152
- Mime => { },
2153
+ Mime => { RawConv => '$$self{ProcessGoogleTrailer} = $val' },
2153
2154
  Semantic => { },
2154
2155
  Length => { Writable => 'integer' },
2155
2156
  Label => { },
@@ -29,7 +29,7 @@ use vars qw($VERSION $RELEASE @ISA @EXPORT_OK %EXPORT_TAGS $AUTOLOAD @fileTypes
29
29
  %jpegMarker %specialTags %fileTypeLookup $testLen $exeDir
30
30
  %static_vars $advFmtSelf);
31
31
 
32
- $VERSION = '13.16';
32
+ $VERSION = '13.17';
33
33
  $RELEASE = '';
34
34
  @ISA = qw(Exporter);
35
35
  %EXPORT_TAGS = (
@@ -6898,6 +6898,8 @@ sub IdentifyTrailer($;$)
6898
6898
  $type = 'NikonApp';
6899
6899
  } elsif ($buff =~ /\xff{4}\x1b\*9HWfu\x84\x93\xa2\xb1$/) {
6900
6900
  $type = 'Vivo';
6901
+ } elsif ($buff =~ /jxrs...\0$/s) {
6902
+ $type = 'OnePlus';
6901
6903
  }
6902
6904
  last;
6903
6905
  }
@@ -6929,18 +6931,24 @@ sub ProcessTrailers($$)
6929
6931
  my $success = 1;
6930
6932
  my $path = $$self{PATH};
6931
6933
 
6934
+ # get position of end of file
6935
+ $raf->Seek(0,2);
6936
+ $$self{FileEnd} = $raf->Tell();
6937
+
6932
6938
  for (;;) { # loop through all trailers
6939
+ $raf->Seek($pos);
6933
6940
  my ($proc, $outBuff);
6934
- if ($dirName eq 'Insta360') {
6935
- require 'Image/ExifTool/QuickTimeStream.pl';
6936
- $proc = 'Image::ExifTool::QuickTime::ProcessInsta360';
6937
- } elsif ($dirName eq 'NikonApp') {
6938
- require Image::ExifTool::Nikon;
6939
- $proc = 'Image::ExifTool::Nikon::ProcessNikonApp';
6940
- } else {
6941
- require "Image/ExifTool/$dirName.pm";
6942
- $proc = "Image::ExifTool::${dirName}::Process$dirName";
6943
- }
6941
+ # trailer-processing procs residing in modules of a different name
6942
+ my $module = {
6943
+ Insta360 => 'QuickTimeStream.pl',
6944
+ NikonApp => 'Nikon.pm',
6945
+ Vivo => 'Trailer.pm',
6946
+ OnePlus => 'Trailer.pm',
6947
+ Google => 'Trailer.pm',
6948
+ }->{$dirName} || "$dirName.pm";
6949
+ require "Image/ExifTool/$module";
6950
+ $module =~ s/(Stream)?\..*//; # remove extension and change QuickTimeStream to QuickTime
6951
+ $proc = "Image::ExifTool::${module}::Process$dirName";
6944
6952
  if ($outfile) {
6945
6953
  # write to local buffer so we can add trailer in proper order later
6946
6954
  $$outfile and $$dirInfo{OutFile} = \$outBuff, $outBuff = '';
@@ -6953,11 +6961,38 @@ sub ProcessTrailers($$)
6953
6961
  $$dirInfo{Trailer} = 1; # set Trailer flag in case proc cares
6954
6962
  # add trailer and DirName to SubDirectory PATH
6955
6963
  push @$path, 'Trailer', $dirName;
6956
-
6957
- # read or write this trailer
6958
- # (proc takes Offset as positive offset from end of trailer to end of file,
6959
- # and returns DataPos and DirLen, and Fixup if applicable, and updates
6960
- # OutFile when writing. Returns < 0 if we must scan for this trailer)
6964
+ #
6965
+ # Call proc to read or write this trailer
6966
+ #
6967
+ # Proc inputs:
6968
+ # 0) ExifTool ref, with FileEnd set, and TrailerStart possibly set (start of all trailers)
6969
+ # 1) DirInfo with the following elements:
6970
+ # DirName - name of this trailer
6971
+ # RAF - RAF reference
6972
+ # Offset - positive offset from end of this trailer to the end of file
6973
+ # OutFile - (write mode) scalar reference for output buffer consisting of an empty string
6974
+ # Trailer - flag set so proc knows we are processing a trailer (if it cares)
6975
+ # Fixup - optional fixup for pointers in trailer
6976
+ # ScanForTrailer - set if we should now scan for the trailer start. For JPEG
6977
+ # images the ExifTool TrailerStart member will also be set, but for TIFF
6978
+ # images TrailerStart will only be set when writing, so the proc should
6979
+ # scan from the current file position when reading in a TIFF image.
6980
+ # Proc returns in read mode (OutFile not set):
6981
+ # 1 = success
6982
+ # 0 = error processing trailer (no warning will be issued and remaining trailers will be ignored)
6983
+ # -1 = must scan from TrailerStart since length can not be determined
6984
+ # (in which case this routine will be called again later when TrailerStart is known)
6985
+ # Proc returns in write mode:
6986
+ # 1 = success (and proc updates OutFile with the trailer to write, or empty string to delete)
6987
+ # 0 = error processing trailer (will issue minor error)
6988
+ # -1 = caller to copy or delete the trailer as-is (from TrailerStart if DataPos isn't set)
6989
+ # - TrailerStart will always be set in write mode
6990
+ # - the write routine will not be called if all trailers are being deleted
6991
+ # Proc sets the following elements of $dirInfo in both read and write mode:
6992
+ # DataPos - file position for start of this trailer
6993
+ # DirLen - length of this trailer (subsequent trailers are not processed if this is not set)
6994
+ # Fixup - for any pointers in the trailer that need adjusting
6995
+ #
6961
6996
  no strict 'refs';
6962
6997
  my $result = &$proc($self, $dirInfo);
6963
6998
  use strict 'refs';
@@ -6965,8 +7000,27 @@ sub ProcessTrailers($$)
6965
7000
  # restore PATH (pop last 2 items)
6966
7001
  splice @$path, -2;
6967
7002
 
6968
- # check result
7003
+ my ($dataPos, $dirLen) = @$dirInfo{'DataPos','DirLen'};
6969
7004
  if ($outfile) {
7005
+ if ($result < 0) {
7006
+ # copy or delete the trailer ourself
7007
+ $result = 1;
7008
+ if ($$self{TrailerStart}) {
7009
+ $dataPos or $dataPos = $$self{TrailerStart};
7010
+ $dirLen or $dirLen = $$self{FileEnd} - $offset - $dataPos;
7011
+ }
7012
+ if ($$self{DEL_GROUP}{Trailer} or $$self{DEL_GROUP}{$dirName}) {
7013
+ my $bytes = $dirLen ? " ($dirLen bytes)" : '';
7014
+ $self->VPrint(0, "Deleting $dirName trailer$bytes\n");
7015
+ ++$$self{CHANGED};
7016
+ } elsif ($dataPos and $dirLen) {
7017
+ $self->VPrint(0, "Copying $dirName trailer ($dirLen bytes)\n");
7018
+ $result = 0 unless $raf->Seek($dataPos) and
7019
+ $raf->Read(${$$dirInfo{OutFile}}, $dirLen) == $dirLen;
7020
+ } else {
7021
+ $result = 0;
7022
+ }
7023
+ }
6970
7024
  if ($result > 0) {
6971
7025
  if ($outBuff) {
6972
7026
  # write trailers to OutFile in original order
@@ -6994,15 +7048,20 @@ sub ProcessTrailers($$)
6994
7048
  $success = 0;
6995
7049
  last;
6996
7050
  }
6997
- last unless $result > 0 and $$dirInfo{DirLen};
7051
+ last unless $result > 0 and $dirLen;
7052
+ $offset += $dirLen;
7053
+ last if $dataPos and $$self{TrailerStart} and $dataPos <= $$self{TrailerStart};
6998
7054
  # look for next trailer
6999
- $offset += $$dirInfo{DirLen};
7000
- my $nextTrail = IdentifyTrailer($raf, $offset) or last;
7055
+ my $nextTrail = IdentifyTrailer($raf, $offset);
7056
+ # process Google trailer after all others if necessary and not done already
7057
+ unless ($nextTrail) {
7058
+ last unless $$self{ProcessGoogleTrailer};
7059
+ $nextTrail = { DirName => 'Google', RAF => $raf };
7060
+ }
7001
7061
  $dirName = $$dirInfo{DirName} = $$nextTrail{DirName};
7002
- $raf->Seek($pos, 0);
7003
7062
  }
7004
7063
  SetByteOrder($byteOrder); # restore original byte order
7005
- $raf->Seek($pos, 0); # restore original file position
7064
+ $raf->Seek($pos); # restore original file position
7006
7065
  $$dirInfo{OutFile} = $outfile; # restore original outfile
7007
7066
  $$dirInfo{Offset} = $offset; # return offset from EOF to start of first trailer
7008
7067
  $$dirInfo{Fixup} = $fixup; # return fixup information
@@ -7399,12 +7458,61 @@ sub ProcessJPEG($$;$)
7399
7458
  $foundSOS = 1;
7400
7459
  # all done with meta information unless we have a trailer
7401
7460
  $verbose and print $out "${indent}JPEG SOS\n";
7461
+ # process extended XMP now if it existed
7462
+ # (must do this before trailers because XMP is required to process Google trailer)
7463
+ if (%extendedXMP) {
7464
+ my $guid;
7465
+ # GUID indicated by the last main XMP segment
7466
+ my $goodGuid = $$self{VALUE}{HasExtendedXMP} || '';
7467
+ # GUID of the extended XMP that we will process ('2' for all)
7468
+ my $readGuid = $$options{ExtendedXMP} || 0;
7469
+ $readGuid = $goodGuid if $readGuid eq '1';
7470
+ foreach $guid (sort keys %extendedXMP) {
7471
+ next unless length $guid == 32; # ignore other (internal) keys
7472
+ my $extXMP = $extendedXMP{$guid};
7473
+ my ($off, @offsets, $warn);
7474
+ # make sure we have all chunks, and create a list of sorted offsets
7475
+ for ($off=0; $off<$$extXMP{Size}; ) {
7476
+ last unless defined $$extXMP{$off};
7477
+ push @offsets, $off;
7478
+ $off += length $$extXMP{$off};
7479
+ }
7480
+ unless ($off == $$extXMP{Size}) {
7481
+ $self->Warn("Incomplete extended XMP (GUID $guid)");
7482
+ next;
7483
+ }
7484
+ if ($guid eq $readGuid or $readGuid eq '2') {
7485
+ $warn = 'Reading non-' if $guid ne $goodGuid;
7486
+ my $buff = '';
7487
+ # assemble XMP all together
7488
+ $buff .= $$extXMP{$_} foreach @offsets;
7489
+ my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
7490
+ my %dirInfo = (
7491
+ DataPt => \$buff,
7492
+ Parent => 'APP1',
7493
+ IsExtended => 1,
7494
+ );
7495
+ $$path[$pn] = 'APP1';
7496
+ $self->ProcessDirectory(\%dirInfo, $tagTablePtr);
7497
+ pop @$path;
7498
+ } else {
7499
+ $warn = 'Ignored ';
7500
+ $warn .= 'non-' if $guid ne $goodGuid;
7501
+ }
7502
+ $self->Warn("${warn}standard extended XMP (GUID $guid)") if $warn;
7503
+ delete $extendedXMP{$guid};
7504
+ }
7505
+ }
7402
7506
  unless ($fast) {
7403
7507
  $trailInfo = IdentifyTrailer($raf);
7508
+ # check for Google trailer information if specific XMP tags exist
7509
+ if (not $trailInfo and $$self{ProcessGoogleTrailer}) {
7510
+ $trailInfo = { DirName => 'Google', RAF => $raf };
7511
+ }
7404
7512
  # process trailer now unless we are doing verbose dump
7405
7513
  if ($trailInfo and $verbose < 3 and not $htmlDump) {
7406
7514
  # process trailers (keep trailInfo to finish processing later
7407
- # only if we can't finish without scanning from end of file)
7515
+ # only if we can't finish without scanning from JPEG EOF)
7408
7516
  $self->ProcessTrailers($trailInfo) and undef $trailInfo;
7409
7517
  }
7410
7518
  if ($wantTrailer and $$self{PreviewImageStart}) {
@@ -7577,7 +7685,7 @@ sub ProcessJPEG($$;$)
7577
7685
  my $n = length($1) + 1;
7578
7686
  $self->HDump($segPos+pos($$dataPt)-$n, $n, '[Vivo HiddenData]', undef, 0x08);
7579
7687
  }
7580
- my $tbl = GetTagTable('Image::ExifTool::Vivo::Main');
7688
+ my $tbl = GetTagTable('Image::ExifTool::Trailer::Vivo');
7581
7689
  $self->HandleTag($tbl, HiddenData => $1);
7582
7690
  }
7583
7691
  # avoid looking for preview unless necessary because it really slows
@@ -8237,50 +8345,6 @@ sub ProcessJPEG($$;$)
8237
8345
  }
8238
8346
  undef $$segDataPt;
8239
8347
  }
8240
- # process extended XMP now if it existed
8241
- if (%extendedXMP) {
8242
- my $guid;
8243
- # GUID indicated by the last main XMP segment
8244
- my $goodGuid = $$self{VALUE}{HasExtendedXMP} || '';
8245
- # GUID of the extended XMP that we will process ('2' for all)
8246
- my $readGuid = $$options{ExtendedXMP} || 0;
8247
- $readGuid = $goodGuid if $readGuid eq '1';
8248
- foreach $guid (sort keys %extendedXMP) {
8249
- next unless length $guid == 32; # ignore other (internal) keys
8250
- my $extXMP = $extendedXMP{$guid};
8251
- my ($off, @offsets, $warn);
8252
- # make sure we have all chunks, and create a list of sorted offsets
8253
- for ($off=0; $off<$$extXMP{Size}; ) {
8254
- last unless defined $$extXMP{$off};
8255
- push @offsets, $off;
8256
- $off += length $$extXMP{$off};
8257
- }
8258
- unless ($off == $$extXMP{Size}) {
8259
- $self->Warn("Incomplete extended XMP (GUID $guid)");
8260
- next;
8261
- }
8262
- if ($guid eq $readGuid or $readGuid eq '2') {
8263
- $warn = 'Reading non-' if $guid ne $goodGuid;
8264
- my $buff = '';
8265
- # assemble XMP all together
8266
- $buff .= $$extXMP{$_} foreach @offsets;
8267
- my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
8268
- my %dirInfo = (
8269
- DataPt => \$buff,
8270
- Parent => 'APP1',
8271
- IsExtended => 1,
8272
- );
8273
- $$path[$pn] = 'APP1';
8274
- $self->ProcessDirectory(\%dirInfo, $tagTablePtr);
8275
- pop @$path;
8276
- } else {
8277
- $warn = 'Ignored ';
8278
- $warn .= 'non-' if $guid ne $goodGuid;
8279
- }
8280
- $self->Warn("${warn}standard extended XMP (GUID $guid)") if $warn;
8281
- delete $extendedXMP{$guid};
8282
- }
8283
- }
8284
8348
  # print verbose hash message if necessary
8285
8349
  print $out "${indent}(ImageDataHash: $hashsize bytes of JPEG image data)\n" if $hashsize and $verbose;
8286
8350
  # calculate JPEGDigest if requested
@@ -8557,7 +8621,9 @@ sub DoProcessTIFF($$;$)
8557
8621
  if ($raf) {
8558
8622
  my $trailInfo = IdentifyTrailer($raf);
8559
8623
  if ($trailInfo) {
8560
- $$trailInfo{ScanForTrailer} = 1; # scan to find AFCP if necessary
8624
+ # scan to find AFCP if necessary (Note: we are scanning
8625
+ # from a random file position in the TIFF)
8626
+ $$trailInfo{ScanForTrailer} = 1;
8561
8627
  $self->ProcessTrailers($trailInfo);
8562
8628
  }
8563
8629
  # dump any other known trailer (eg. A100 RAW Data)
@@ -8663,6 +8729,7 @@ sub DoProcessTIFF($$;$)
8663
8729
  my $tbuf = '';
8664
8730
  $$trailInfo{OutFile} = \$tbuf; # rewrite trailer(s)
8665
8731
  $$trailInfo{ScanForTrailer} = 1; # scan for AFCP if necessary
8732
+ $$self{TrailerStart} = $tiffEnd;
8666
8733
  # rewrite all trailers to buffer
8667
8734
  unless ($self->ProcessTrailers($trailInfo)) {
8668
8735
  undef $trailInfo;