exiftool_vendored 12.62.0 → 12.63.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/Changes +29 -1
- data/bin/MANIFEST +1 -0
- data/bin/META.json +4 -1
- data/bin/META.yml +4 -1
- data/bin/Makefile.PL +7 -1
- data/bin/README +10 -7
- data/bin/exiftool +22 -16
- data/bin/lib/Image/ExifTool/7Z.pm +793 -0
- data/bin/lib/Image/ExifTool/Canon.pm +1 -0
- data/bin/lib/Image/ExifTool/CanonRaw.pm +4 -4
- data/bin/lib/Image/ExifTool/Exif.pm +31 -14
- data/bin/lib/Image/ExifTool/FujiFilm.pm +3 -3
- data/bin/lib/Image/ExifTool/GPS.pm +5 -2
- data/bin/lib/Image/ExifTool/Geotag.pm +4 -1
- data/bin/lib/Image/ExifTool/Jpeg2000.pm +225 -28
- data/bin/lib/Image/ExifTool/MPF.pm +2 -1
- data/bin/lib/Image/ExifTool/MinoltaRaw.pm +2 -2
- data/bin/lib/Image/ExifTool/PNG.pm +6 -6
- data/bin/lib/Image/ExifTool/PhaseOne.pm +5 -5
- data/bin/lib/Image/ExifTool/QuickTime.pm +41 -12
- data/bin/lib/Image/ExifTool/QuickTimeStream.pl +18 -18
- data/bin/lib/Image/ExifTool/README +1 -1
- data/bin/lib/Image/ExifTool/RIFF.pm +11 -9
- data/bin/lib/Image/ExifTool/Shortcuts.pm +2 -1
- data/bin/lib/Image/ExifTool/SigmaRaw.pm +4 -4
- data/bin/lib/Image/ExifTool/Sony.pm +102 -8
- data/bin/lib/Image/ExifTool/TagLookup.pm +982 -953
- data/bin/lib/Image/ExifTool/TagNames.pod +75 -5
- data/bin/lib/Image/ExifTool/Validate.pm +17 -1
- data/bin/lib/Image/ExifTool/WriteExif.pl +9 -7
- data/bin/lib/Image/ExifTool/Writer.pl +7 -6
- data/bin/lib/Image/ExifTool/XMP.pm +14 -2
- data/bin/lib/Image/ExifTool/XMP2.pl +32 -0
- data/bin/lib/Image/ExifTool/ZIP.pm +5 -5
- data/bin/lib/Image/ExifTool.pm +54 -33
- data/bin/lib/Image/ExifTool.pod +17 -6
- data/bin/perl-Image-ExifTool.spec +6 -6
- data/lib/exiftool_vendored/version.rb +1 -1
- metadata +3 -2
@@ -21,7 +21,7 @@ use Image::ExifTool qw(:DataAccess :Utils);
|
|
21
21
|
use Image::ExifTool::Exif;
|
22
22
|
use Image::ExifTool::Canon;
|
23
23
|
|
24
|
-
$VERSION = '1.
|
24
|
+
$VERSION = '1.61';
|
25
25
|
|
26
26
|
sub WriteCRW($$);
|
27
27
|
sub ProcessCanonRaw($$$);
|
@@ -698,9 +698,9 @@ sub ProcessCanonRaw($$$)
|
|
698
698
|
$format ne 'string' and not $subdir;
|
699
699
|
} else {
|
700
700
|
$valueDataPos = $ptr;
|
701
|
-
# do
|
702
|
-
if ($$et{
|
703
|
-
$raf->Seek($ptr, 0) and $et->
|
701
|
+
# do hash of image data if requested
|
702
|
+
if ($$et{ImageDataHash} and $tagID == 0x2005) {
|
703
|
+
$raf->Seek($ptr, 0) and $et->ImageDataHash($raf, $size, 'raw');
|
704
704
|
}
|
705
705
|
if ($size <= 512 or ($verbose > 2 and $size <= 65536)
|
706
706
|
or ($tagInfo and ($$tagInfo{SubDirectory}
|
@@ -43,6 +43,7 @@
|
|
43
43
|
# 30) http://geotiff.maptools.org/spec/geotiffhome.html
|
44
44
|
# 31) https://android.googlesource.com/platform/external/dng_sdk/+/refs/heads/master/source/dng_tag_codes.h
|
45
45
|
# 32) Jeffry Friedl private communication
|
46
|
+
# 33) https://www.cipa.jp/std/documents/download_e.html?DC-008-Translation-2023-E (Exif 3.0)
|
46
47
|
# IB) Iliah Borg private communication (LibRaw)
|
47
48
|
# JD) Jens Duttke private communication
|
48
49
|
#------------------------------------------------------------------------------
|
@@ -65,7 +66,7 @@ sub RebuildMakerNotes($$$);
|
|
65
66
|
sub EncodeExifText($$);
|
66
67
|
sub ValidateIFD($;$);
|
67
68
|
sub ValidateImageData($$$;$);
|
68
|
-
sub
|
69
|
+
sub AddImageDataHash($$$);
|
69
70
|
sub ProcessTiffIFD($$$);
|
70
71
|
sub PrintParameter($$$);
|
71
72
|
sub GetOffList($$$$$);
|
@@ -79,6 +80,7 @@ sub BINARY_DATA_LIMIT { return 10 * 1024 * 1024; }
|
|
79
80
|
|
80
81
|
# byte sizes for the various EXIF format types below
|
81
82
|
@formatSize = (undef,1,1,2,4,8,1,1,2,4,8,4,8,4,2,8,8,8,8);
|
83
|
+
$formatSize[129] = 1; # (Exif 3.0)
|
82
84
|
|
83
85
|
@formatName = (
|
84
86
|
undef, 'int8u', 'string', 'int16u',
|
@@ -87,6 +89,7 @@ sub BINARY_DATA_LIMIT { return 10 * 1024 * 1024; }
|
|
87
89
|
'double', 'ifd', 'unicode', 'complex',
|
88
90
|
'int64u', 'int64s', 'ifd64', # (new BigTIFF formats)
|
89
91
|
);
|
92
|
+
$formatName[129] = 'utf8'; # (Exif 3.0)
|
90
93
|
|
91
94
|
# hash to look up EXIF format numbers by name
|
92
95
|
# (format types are all lower case)
|
@@ -110,6 +113,7 @@ sub BINARY_DATA_LIMIT { return 10 * 1024 * 1024; }
|
|
110
113
|
'int64u' => 16, # LONG8 [BigTIFF]
|
111
114
|
'int64s' => 17, # SLONG8 [BigTIFF]
|
112
115
|
'ifd64' => 18, # IFD8 (with int64u format) [BigTIFF]
|
116
|
+
'utf8' => 129,# UTF-8 (Exif 3.0)
|
113
117
|
# Note: unicode and complex types are not yet properly supported by ExifTool.
|
114
118
|
# These are types which have been observed in the Adobe DNG SDK code, but
|
115
119
|
# aren't fully supported there either. We know the sizes, but that's about it.
|
@@ -2823,6 +2827,13 @@ my %opcodeInfo = (
|
|
2823
2827
|
0xa433 => { Name => 'LensMake', Writable => 'string' }, #24
|
2824
2828
|
0xa434 => { Name => 'LensModel', Writable => 'string' }, #24
|
2825
2829
|
0xa435 => { Name => 'LensSerialNumber', Writable => 'string' }, #24
|
2830
|
+
0xa436 => { Name => 'Title', Writable => 'string', Avoid => 1 }, #33
|
2831
|
+
0xa437 => { Name => 'Photographer', Writable => 'string' }, #33
|
2832
|
+
0xa438 => { Name => 'ImageEditor', Writable => 'string' }, #33
|
2833
|
+
0xa439 => { Name => 'CameraFirmware', Writable => 'string' }, #33
|
2834
|
+
0xa43a => { Name => 'RAWDevelopingSoftware', Writable => 'string' }, #33
|
2835
|
+
0xa43b => { Name => 'ImageEditingSoftware', Writable => 'string' }, #33
|
2836
|
+
0xa43c => { Name => 'MetadataEditingSoftware', Writable => 'string' }, #33
|
2826
2837
|
0xa460 => { #Exif2.32
|
2827
2838
|
Name => 'CompositeImage',
|
2828
2839
|
Writable => 'int16u',
|
@@ -4910,10 +4921,10 @@ my %subSecConv = (
|
|
4910
4921
|
Writable => 1,
|
4911
4922
|
Protected => 1,
|
4912
4923
|
WriteAlso => {
|
4913
|
-
GPSLatitude => '(defined $val and $val =~ /(
|
4914
|
-
GPSLatitudeRef => '(defined $val and $val =~ /(-?)(.*?)
|
4915
|
-
GPSLongitude => '(defined $val and $val =~
|
4916
|
-
GPSLongitudeRef => '(defined $val and $val =~
|
4924
|
+
GPSLatitude => '(defined $val and $val =~ /(.*) /) ? $1 : undef',
|
4925
|
+
GPSLatitudeRef => '(defined $val and $val =~ /(-?)(.*?) /) ? ($1 ? "S" : "N") : undef',
|
4926
|
+
GPSLongitude => '(defined $val and $val =~ / (.*)$/) ? $1 : undef',
|
4927
|
+
GPSLongitudeRef => '(defined $val and $val =~ / (-?)/) ? ($1 ? "W" : "E") : undef',
|
4917
4928
|
},
|
4918
4929
|
PrintConvInv => q{
|
4919
4930
|
return undef unless $val =~ /(.*? ?[NS]?), ?(.*? ?[EW]?)$/;
|
@@ -4921,7 +4932,7 @@ my %subSecConv = (
|
|
4921
4932
|
require Image::ExifTool::GPS;
|
4922
4933
|
$lat = Image::ExifTool::GPS::ToDegrees($lat, 1, "lat");
|
4923
4934
|
$lon = Image::ExifTool::GPS::ToDegrees($lon, 1, "lon");
|
4924
|
-
return "$lat
|
4935
|
+
return "$lat $lon";
|
4925
4936
|
},
|
4926
4937
|
Require => {
|
4927
4938
|
0 => 'GPSLatitude',
|
@@ -5929,12 +5940,12 @@ sub ProcessExif($$$)
|
|
5929
5940
|
my ($verbose,$validate,$saveFormat) = @{$$et{OPTIONS}}{qw(Verbose Validate SaveFormat)};
|
5930
5941
|
my $htmlDump = $$et{HTML_DUMP};
|
5931
5942
|
my $success = 1;
|
5932
|
-
my ($tagKey, $dirSize, $makerAddr, $strEnc, %offsetInfo, $offName, $nextOffName, $
|
5943
|
+
my ($tagKey, $dirSize, $makerAddr, $strEnc, %offsetInfo, $offName, $nextOffName, $doHash);
|
5933
5944
|
my $inMakerNotes = $$tagTablePtr{GROUPS}{0} eq 'MakerNotes';
|
5934
5945
|
my $isExif = ($tagTablePtr eq \%Image::ExifTool::Exif::Main);
|
5935
5946
|
|
5936
|
-
# set flag to calculate image data
|
5937
|
-
$
|
5947
|
+
# set flag to calculate image data hash if requested
|
5948
|
+
$doHash = 1 if $$et{ImageDataHash} and (($$et{FILE_TYPE} eq 'TIFF' and not $base and not $inMakerNotes) or
|
5938
5949
|
($$et{FILE_TYPE} eq 'RAF' and $dirName eq 'FujiIFD'));
|
5939
5950
|
|
5940
5951
|
# set encoding to assume for strings
|
@@ -6088,7 +6099,7 @@ sub ProcessExif($$$)
|
|
6088
6099
|
my $format = Get16u($dataPt, $entry+2);
|
6089
6100
|
my $count = Get32u($dataPt, $entry+4);
|
6090
6101
|
# (Apple uses the BigTIFF format code 16 in the maker notes of their ProRaw DNG files)
|
6091
|
-
if (($format < 1 or $format > 13) and not ($format == 16 and $$et{Make} eq 'Apple' and $inMakerNotes)) {
|
6102
|
+
if (($format < 1 or $format > 13) and $format != 129 and not ($format == 16 and $$et{Make} eq 'Apple' and $inMakerNotes)) {
|
6092
6103
|
if ($mapFmt and $$mapFmt{$format}) {
|
6093
6104
|
$format = $$mapFmt{$format};
|
6094
6105
|
} else {
|
@@ -6404,7 +6415,13 @@ sub ProcessExif($$$)
|
|
6404
6415
|
# convert according to specified format
|
6405
6416
|
$val = ReadValue($valueDataPt,$valuePtr,$formatStr,$count,$readSize,\$rational);
|
6406
6417
|
# re-code if necessary
|
6407
|
-
|
6418
|
+
if (defined $val) {
|
6419
|
+
if ($formatStr eq 'utf8') {
|
6420
|
+
$val = $et->Decode($val, 'UTF8');
|
6421
|
+
} elsif ($strEnc and $formatStr eq 'string') {
|
6422
|
+
$val = $et->Decode($val, $strEnc);
|
6423
|
+
}
|
6424
|
+
}
|
6408
6425
|
}
|
6409
6426
|
}
|
6410
6427
|
|
@@ -6781,7 +6798,7 @@ sub ProcessExif($$$)
|
|
6781
6798
|
}
|
6782
6799
|
$val = join(' ', @vals);
|
6783
6800
|
}
|
6784
|
-
if ($validate or $
|
6801
|
+
if ($validate or $doHash) {
|
6785
6802
|
if ($$tagInfo{OffsetPair}) {
|
6786
6803
|
$offsetInfo{$tagID} = [ $tagInfo, $val ];
|
6787
6804
|
} elsif ($saveForValidate{$tagID} and $isExif) {
|
@@ -6800,8 +6817,8 @@ sub ProcessExif($$$)
|
|
6800
6817
|
}
|
6801
6818
|
|
6802
6819
|
if (%offsetInfo) {
|
6803
|
-
# calculate image data
|
6804
|
-
|
6820
|
+
# calculate image data hash if requested
|
6821
|
+
AddImageDataHash($et, $dirInfo, \%offsetInfo) if $doHash;
|
6805
6822
|
# validate image data offsets for this IFD (note: modifies %offsetInfo)
|
6806
6823
|
Image::ExifTool::Validate::ValidateOffsetInfo($et, \%offsetInfo, $dirName, $inMakerNotes) if $validate;
|
6807
6824
|
}
|
@@ -31,7 +31,7 @@ use vars qw($VERSION);
|
|
31
31
|
use Image::ExifTool qw(:DataAccess :Utils);
|
32
32
|
use Image::ExifTool::Exif;
|
33
33
|
|
34
|
-
$VERSION = '1.
|
34
|
+
$VERSION = '1.88';
|
35
35
|
|
36
36
|
sub ProcessFujiDir($$$);
|
37
37
|
sub ProcessFaceRec($$$);
|
@@ -1687,8 +1687,8 @@ sub ProcessRAF($$)
|
|
1687
1687
|
my $tagTablePtr = GetTagTable('Image::ExifTool::FujiFilm::IFD');
|
1688
1688
|
# this is TIFF-format data only for some models, so no warning if it fails
|
1689
1689
|
unless ($et->ProcessTIFF(\%dirInfo, $tagTablePtr, \&Image::ExifTool::ProcessTIFF)) {
|
1690
|
-
# do
|
1691
|
-
$et->
|
1690
|
+
# do hash of image data if necessary
|
1691
|
+
$et->ImageDataHash($raf, $len, 'raw') if $$et{ImageDataHash} and $raf->Seek($start,0);
|
1692
1692
|
}
|
1693
1693
|
delete $$et{SET_GROUP1};
|
1694
1694
|
$ifdNum = ($ifdNum || 1) + 1;
|
@@ -106,8 +106,10 @@ my %coordConv = (
|
|
106
106
|
return undef unless $inv and $val =~ /^([-+0-9])/;
|
107
107
|
return($1 eq '-' ? 1 : 0);
|
108
108
|
},
|
109
|
-
0 => 'Above Sea Level',
|
110
|
-
1 => 'Below Sea Level',
|
109
|
+
0 => 'Above Sea Level', # (ellipsoidal surface, Exif 3.0)
|
110
|
+
1 => 'Below Sea Level', # (ellipsoidal surface, Exif 3.0)
|
111
|
+
# 2 => 'Above Sea Level', # (Exif 3.0)
|
112
|
+
# 3 => 'Below Sea Level', # (Exif 3.0)
|
111
113
|
},
|
112
114
|
},
|
113
115
|
0x0006 => {
|
@@ -289,6 +291,7 @@ my %coordConv = (
|
|
289
291
|
Name => 'GPSProcessingMethod',
|
290
292
|
Writable => 'undef',
|
291
293
|
Notes => 'values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec.',
|
294
|
+
# (or QZZSS, GALILEO, GLONASS, BEIDOU or NAVIC in Exif 3.0)
|
292
295
|
RawConv => 'Image::ExifTool::Exif::ConvertExifText($self,$val,1,$tag)',
|
293
296
|
RawConvInv => 'Image::ExifTool::Exif::EncodeExifText($self,$val)',
|
294
297
|
},
|
@@ -29,7 +29,7 @@ use vars qw($VERSION);
|
|
29
29
|
use Image::ExifTool qw(:Public);
|
30
30
|
use Image::ExifTool::GPS;
|
31
31
|
|
32
|
-
$VERSION = '1.
|
32
|
+
$VERSION = '1.72';
|
33
33
|
|
34
34
|
sub JITTER() { return 2 } # maximum time jitter
|
35
35
|
|
@@ -224,6 +224,9 @@ sub LoadTrackLog($$;$)
|
|
224
224
|
# determine file format
|
225
225
|
if (not $format) {
|
226
226
|
s/^\xef\xbb\xbf//; # remove leading BOM if it exists
|
227
|
+
if (/^\xff\xfe|\xfe\xff/) {
|
228
|
+
return "ExifTool doesn't yet read UTF16-format track logs";
|
229
|
+
}
|
227
230
|
if (/^<(\?xml|gpx)[\s>]/) { # look for XML or GPX header
|
228
231
|
$format = 'XML';
|
229
232
|
# check for NMEA sentence
|
@@ -16,7 +16,7 @@ use strict;
|
|
16
16
|
use vars qw($VERSION);
|
17
17
|
use Image::ExifTool qw(:DataAccess :Utils);
|
18
18
|
|
19
|
-
$VERSION = '1.
|
19
|
+
$VERSION = '1.35';
|
20
20
|
|
21
21
|
sub ProcessJpeg2000Box($$$);
|
22
22
|
sub ProcessJUMD($$$);
|
@@ -130,7 +130,10 @@ my %j2cMarker = (
|
|
130
130
|
NOTES => q{
|
131
131
|
The tags below are found in JPEG 2000 images and the JUMBF metadata in JPEG
|
132
132
|
images, but not all of these are extracted. Note that ExifTool currently
|
133
|
-
writes only EXIF, IPTC and XMP tags in Jpeg2000 images
|
133
|
+
writes only EXIF, IPTC and XMP tags in Jpeg2000 images, and EXIF and XMP in
|
134
|
+
JXL images. ExifTool will read/write Brotli-compressed EXIF and XMP in JXL
|
135
|
+
images, but the API Compress option must be set to create new EXIF and XMP
|
136
|
+
in compressed format.
|
134
137
|
},
|
135
138
|
#
|
136
139
|
# NOTE: ONLY TAGS WITH "Format" DEFINED ARE EXTRACTED!
|
@@ -262,7 +265,7 @@ my %j2cMarker = (
|
|
262
265
|
uuid => [
|
263
266
|
{
|
264
267
|
Name => 'UUID-EXIF',
|
265
|
-
# (this is the EXIF that we create)
|
268
|
+
# (this is the EXIF that we create in JP2)
|
266
269
|
Condition => '$$valPt=~/^JpgTiffExif->JP2(?!Exif\0\0)/',
|
267
270
|
SubDirectory => {
|
268
271
|
TagTable => 'Image::ExifTool::Exif::Main',
|
@@ -298,7 +301,7 @@ my %j2cMarker = (
|
|
298
301
|
},
|
299
302
|
{
|
300
303
|
Name => 'UUID-IPTC',
|
301
|
-
# (this is the IPTC that we create)
|
304
|
+
# (this is the IPTC that we create in JP2)
|
302
305
|
Condition => '$$valPt=~/^\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\xba\xf1\xa3\xe0\x97\xad\x38/',
|
303
306
|
SubDirectory => {
|
304
307
|
TagTable => 'Image::ExifTool::IPTC::Main',
|
@@ -431,7 +434,6 @@ my %j2cMarker = (
|
|
431
434
|
# stuff seen in JPEG XL images:
|
432
435
|
#
|
433
436
|
# jbrd - JPEG Bitstream Reconstruction Data (allows lossless conversion back to original JPG)
|
434
|
-
# jxlp - partial JXL codestream
|
435
437
|
jxlc => {
|
436
438
|
Name => 'JXLCodestream',
|
437
439
|
Format => 'undef',
|
@@ -441,6 +443,15 @@ my %j2cMarker = (
|
|
441
443
|
},
|
442
444
|
RawConv => 'Image::ExifTool::Jpeg2000::ProcessJXLCodestream($self,\$val); undef',
|
443
445
|
},
|
446
|
+
jxlp => {
|
447
|
+
Name => 'PartialJXLCodestream',
|
448
|
+
Format => 'undef',
|
449
|
+
Notes => q{
|
450
|
+
Partial codestreams in JPEG XL image. Currently processed only to determine
|
451
|
+
ImageSize
|
452
|
+
},
|
453
|
+
RawConv => 'Image::ExifTool::Jpeg2000::ProcessJXLCodestream($self,\$val); undef',
|
454
|
+
},
|
444
455
|
Exif => {
|
445
456
|
Name => 'EXIF',
|
446
457
|
SubDirectory => {
|
@@ -451,6 +462,38 @@ my %j2cMarker = (
|
|
451
462
|
Start => '$valuePtr + 4 + (length($$dataPt)-$valuePtr > 4 ? unpack("N", $$dataPt) : 0)',
|
452
463
|
},
|
453
464
|
},
|
465
|
+
hrgm => {
|
466
|
+
Name => 'GainMapImage',
|
467
|
+
Groups => { 2 => 'Preview' },
|
468
|
+
Format => 'undef',
|
469
|
+
Binary => 1,
|
470
|
+
},
|
471
|
+
brob => [{ # Brotli-encoded metadata (see https://libjxl.readthedocs.io/en/latest/api_decoder.html)
|
472
|
+
Name => 'BrotliXMP',
|
473
|
+
Condition => '$$valPt =~ /^xml /i',
|
474
|
+
SubDirectory => {
|
475
|
+
TagTable => 'Image::ExifTool::XMP::Main',
|
476
|
+
ProcessProc => \&ProcessBrotli,
|
477
|
+
WriteProc => \&ProcessBrotli,
|
478
|
+
# (don't set DirName to 'XMP' because this would enable a block write of raw XMP)
|
479
|
+
},
|
480
|
+
},{
|
481
|
+
Name => 'BrotliEXIF',
|
482
|
+
Condition => '$$valPt =~ /^exif/i',
|
483
|
+
SubDirectory => {
|
484
|
+
TagTable => 'Image::ExifTool::Exif::Main',
|
485
|
+
ProcessProc => \&ProcessBrotli,
|
486
|
+
WriteProc => \&ProcessBrotli,
|
487
|
+
# (don't set DirName to 'EXIF' because this would enable a block write of raw EXIF)
|
488
|
+
},
|
489
|
+
},{
|
490
|
+
Name => 'BrotliJUMB',
|
491
|
+
Condition => '$$valPt =~ /^jumb/i',
|
492
|
+
SubDirectory => {
|
493
|
+
TagTable => 'Image::ExifTool::Jpeg2000::Main',
|
494
|
+
ProcessProc => \&ProcessBrotli,
|
495
|
+
},
|
496
|
+
}],
|
454
497
|
);
|
455
498
|
|
456
499
|
%Image::ExifTool::Jpeg2000::ImageHeader = (
|
@@ -840,12 +883,31 @@ sub CreateNewBoxes($$)
|
|
840
883
|
$tagTable = GetTagTable('Image::ExifTool::XMP::Main') if $dir eq 'XMP';
|
841
884
|
my %dirInfo = (
|
842
885
|
DirName => $dir,
|
843
|
-
Parent =>
|
886
|
+
Parent => $tag,
|
844
887
|
);
|
888
|
+
my $compress = $et->Options('Compress');
|
889
|
+
$dirInfo{Compact} = 1 if $$et{IsJXL} and $compress;
|
845
890
|
my $newdir = $et->WriteDirectory(\%dirInfo, $tagTable, $$subdir{WriteProc});
|
846
891
|
if (defined $newdir and length $newdir) {
|
847
892
|
# not sure why, but EXIF box is padded with leading 0's in my sample
|
848
893
|
my $pad = $dirName eq 'Exif' ? "\0\0\0\0" : '';
|
894
|
+
if ($$et{IsJXL} and $compress) {
|
895
|
+
# create as Brotli-compressed metadata
|
896
|
+
if (eval { require IO::Compress::Brotli }) {
|
897
|
+
my $compressed;
|
898
|
+
eval { $compressed = IO::Compress::Brotli::bro($pad . $newdir) };
|
899
|
+
if ($@ or not $compressed) {
|
900
|
+
$et->Warn("Error encoding $dirName brob box");
|
901
|
+
} else {
|
902
|
+
$et->VPrint(0, " Writing Brotli-compressed $dir\n");
|
903
|
+
$newdir = $compressed;
|
904
|
+
$pad = $tag;
|
905
|
+
$tag = 'brob';
|
906
|
+
}
|
907
|
+
} else {
|
908
|
+
$et->WarnOnce('Install IO::Compress::Brotli to create Brotli-compressed metadata');
|
909
|
+
}
|
910
|
+
}
|
849
911
|
my $boxhdr = pack('N', length($newdir) + length($pad) + 8) . $tag;
|
850
912
|
Write($outfile, $boxhdr, $pad, $newdir) or return 0;
|
851
913
|
next;
|
@@ -934,7 +996,7 @@ sub ProcessJpeg2000Box($$$)
|
|
934
996
|
my $raf = $$dirInfo{RAF};
|
935
997
|
my $outfile = $$dirInfo{OutFile};
|
936
998
|
my $dirEnd = $dirStart + $dirLen;
|
937
|
-
my ($err, $outBuff, $verbose, $doColour, $
|
999
|
+
my ($err, $outBuff, $verbose, $doColour, $hash);
|
938
1000
|
|
939
1001
|
if ($outfile) {
|
940
1002
|
unless ($raf) {
|
@@ -952,8 +1014,8 @@ sub ProcessJpeg2000Box($$$)
|
|
952
1014
|
# (must not set verbose flag when writing!)
|
953
1015
|
$verbose = $$et{OPTIONS}{Verbose};
|
954
1016
|
$et->VerboseDir($$dirInfo{DirName}) if $verbose;
|
955
|
-
# do
|
956
|
-
$
|
1017
|
+
# do hash if requested, but only for top-level image data
|
1018
|
+
$hash = $$et{ImageDataHash} if $raf;
|
957
1019
|
}
|
958
1020
|
# loop through all contained boxes
|
959
1021
|
my ($pos, $boxLen, $lastBox);
|
@@ -1023,8 +1085,8 @@ sub ProcessJpeg2000Box($$$)
|
|
1023
1085
|
my $msg = sprintf("offset 0x%.4x to end of file", $dataPos + $base + $pos);
|
1024
1086
|
$et->VPrint(0, "$$et{INDENT}- Tag '${boxID}' ($msg)\n");
|
1025
1087
|
}
|
1026
|
-
if ($
|
1027
|
-
$et->
|
1088
|
+
if ($hash and $isImageData{$boxID}) {
|
1089
|
+
$et->ImageDataHash($raf, undef, $boxID);
|
1028
1090
|
}
|
1029
1091
|
}
|
1030
1092
|
last; # (ignore the rest of the file when reading)
|
@@ -1042,8 +1104,8 @@ sub ProcessJpeg2000Box($$$)
|
|
1042
1104
|
Write($outfile, $$dataPt) or $err = 1;
|
1043
1105
|
$raf->Read($buff,$boxLen) == $boxLen or $err = '', last;
|
1044
1106
|
Write($outfile, $buff) or $err = 1;
|
1045
|
-
} elsif ($
|
1046
|
-
$et->
|
1107
|
+
} elsif ($hash and $isImageData{$boxID}) {
|
1108
|
+
$et->ImageDataHash($raf, $boxLen, $boxID);
|
1047
1109
|
} else {
|
1048
1110
|
$raf->Seek($boxLen, 1) or $err = 'Seek error', last;
|
1049
1111
|
}
|
@@ -1056,9 +1118,9 @@ sub ProcessJpeg2000Box($$$)
|
|
1056
1118
|
# read the box data
|
1057
1119
|
$dataPos = $raf->Tell() - $base;
|
1058
1120
|
$raf->Read($buff,$boxLen) == $boxLen or $err = '', last;
|
1059
|
-
if ($
|
1060
|
-
$
|
1061
|
-
$et->VPrint(0, "$$et{INDENT}(
|
1121
|
+
if ($hash and $isImageData{$boxID}) {
|
1122
|
+
$hash->add($buff);
|
1123
|
+
$et->VPrint(0, "$$et{INDENT}(ImageDataHash: $boxLen bytes of $boxID data)\n");
|
1062
1124
|
}
|
1063
1125
|
$valuePtr = 0;
|
1064
1126
|
$dataLen = $boxLen;
|
@@ -1144,19 +1206,66 @@ sub ProcessJpeg2000Box($$$)
|
|
1144
1206
|
$subdirInfo{DirName} =~ s/^UUID-//;
|
1145
1207
|
my $subTable = GetTagTable($$subdir{TagTable}) || $tagTablePtr;
|
1146
1208
|
if ($outfile) {
|
1147
|
-
#
|
1148
|
-
|
1149
|
-
|
1209
|
+
# (special case for brob box, which may be EXIF or XMP)
|
1210
|
+
my $fakeID = $boxID;
|
1211
|
+
if ($boxID eq 'brob') {
|
1212
|
+
# I have seen 'brob' ID's with funny cases, so standardize these
|
1213
|
+
$fakeID = 'xml ' if $$dataPt =~ /^xml /i;
|
1214
|
+
$fakeID = 'Exif' if $$dataPt =~ /^Exif/i;
|
1215
|
+
}
|
1150
1216
|
my $newdir;
|
1151
1217
|
# only edit writable UUID, Exif and jp2h boxes
|
1152
|
-
if ($uuid or $
|
1218
|
+
if ($uuid or $fakeID eq 'Exif' or ($fakeID eq 'xml ' and $$et{IsJXL}) or
|
1153
1219
|
($boxID eq 'jp2h' and $$et{EDIT_DIRS}{jp2h}))
|
1154
1220
|
{
|
1221
|
+
my $compress = $et->Options('Compress');
|
1222
|
+
$subdirInfo{Parent} = $fakeID;
|
1223
|
+
$subdirInfo{Compact} = 1 if $compress and $$et{IsJXL};
|
1155
1224
|
$newdir = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc});
|
1156
1225
|
next if defined $newdir and not length $newdir; # next if deleting the box
|
1226
|
+
# compress JXL EXIF or XMP metadata if requested
|
1227
|
+
if (defined $newdir and $$et{IsJXL} and defined $compress and
|
1228
|
+
($fakeID eq 'Exif' or $fakeID eq 'xml '))
|
1229
|
+
{
|
1230
|
+
if ($compress and $boxID ne 'brob') {
|
1231
|
+
# rewrite as a Brotli-compressed 'brob' box
|
1232
|
+
if (eval { require IO::Compress::Brotli }) {
|
1233
|
+
my $pad = $boxID eq 'Exif' ? "\0\0\0\0" : '';
|
1234
|
+
my $compressed;
|
1235
|
+
eval { $compressed = IO::Compress::Brotli::bro($pad . $newdir) };
|
1236
|
+
if ($@ or not $compressed) {
|
1237
|
+
$et->Warn("Error encoding $boxID brob box");
|
1238
|
+
} else {
|
1239
|
+
$et->VPrint(0, " Writing Brotli-compressed $boxID\n");
|
1240
|
+
$newdir = $boxID . $compressed;
|
1241
|
+
$boxID = 'brob';
|
1242
|
+
$subdirStart = $valuePtr = 0;
|
1243
|
+
++$$et{CHANGED};
|
1244
|
+
}
|
1245
|
+
} else {
|
1246
|
+
$et->WarnOnce('Install IO::Compress::Brotli to write Brotli-compressed metadata');
|
1247
|
+
}
|
1248
|
+
} elsif (not $compress and $boxID eq 'brob') {
|
1249
|
+
# (in this case, ProcessBrotli has returned uncompressed data,
|
1250
|
+
# so change to the uncompressed 'xml ' or 'Exif' box type)
|
1251
|
+
$et->VPrint(0, " Writing uncompressed $fakeID\n");
|
1252
|
+
$boxID = $fakeID;
|
1253
|
+
$subdirStart = $valuePtr = 0;
|
1254
|
+
++$$et{CHANGED};
|
1255
|
+
}
|
1256
|
+
}
|
1157
1257
|
} elsif (defined $uuid) {
|
1158
1258
|
$et->Warn("Not editing $$tagInfo{Name} box", 1);
|
1159
1259
|
}
|
1260
|
+
# remove this directory from our create list
|
1261
|
+
delete $$et{AddJp2Dirs}{$fakeID}; # (eg. 'Exif' or 'xml ')
|
1262
|
+
if ($boxID eq 'brob') {
|
1263
|
+
# (can't make tag Name 'XMP' or 'Exif' for Brotli-compressed tags because it
|
1264
|
+
# would break the logic in WriteDirectory(), so we do a lookup here instead)
|
1265
|
+
delete $$et{AddJp2Dirs}{{'xml '=>'XMP','Exif'=>'EXIF'}->{$fakeID}};
|
1266
|
+
} else {
|
1267
|
+
delete $$et{AddJp2Dirs}{$$tagInfo{Name}}; # (eg. 'EXIF' or 'XMP')
|
1268
|
+
}
|
1160
1269
|
# use old box data if not changed
|
1161
1270
|
defined $newdir or $newdir = substr($$dataPt, $subdirStart, $subdirLen);
|
1162
1271
|
my $prefixLen = $subdirStart - $valuePtr;
|
@@ -1232,19 +1341,107 @@ sub GetBits($$)
|
|
1232
1341
|
return $v;
|
1233
1342
|
}
|
1234
1343
|
|
1344
|
+
#------------------------------------------------------------------------------
|
1345
|
+
# Read/write Brotli-encoded metadata
|
1346
|
+
# Inputs: 0) ExifTool ref, 1) dirInfoRef, 2) tag table ref
|
1347
|
+
# Returns: 1 on success when reading, or new data when writing (undef if unchanged)
|
1348
|
+
# (ref https://libjxl.readthedocs.io/en/latest/api_decoder.html)
|
1349
|
+
sub ProcessBrotli($$$)
|
1350
|
+
{
|
1351
|
+
my ($et, $dirInfo, $tagTablePtr) = @_;
|
1352
|
+
my $dataPt = $$dirInfo{DataPt};
|
1353
|
+
|
1354
|
+
return 0 unless length($$dataPt) > 4;
|
1355
|
+
|
1356
|
+
my $isWriting = $$dirInfo{IsWriting};
|
1357
|
+
my $type = substr($$dataPt, 0, 4);
|
1358
|
+
$et->VerboseDir("Decrypted Brotli '${type}'") unless $isWriting;
|
1359
|
+
my %knownType = ( exif => 'Exif', 'xml ' => 'xml ', jumb => 'jumb' );
|
1360
|
+
my $stdType = $knownType{lc $type};
|
1361
|
+
unless ($stdType) {
|
1362
|
+
$et->Warn('Unknown Brotli box type', 1);
|
1363
|
+
return 1;
|
1364
|
+
}
|
1365
|
+
if ($type ne $stdType) {
|
1366
|
+
$et->Warn("Incorrect case for Brotli '${type}' data (should be '${stdType}')");
|
1367
|
+
$type = $stdType;
|
1368
|
+
}
|
1369
|
+
if (eval { require IO::Uncompress::Brotli }) {
|
1370
|
+
if ($isWriting and not eval { require IO::Compress::Brotli }) {
|
1371
|
+
$et->WarnOnce('Install IO::Compress::Brotli to write Brotli-compressed metadata');
|
1372
|
+
return undef;
|
1373
|
+
}
|
1374
|
+
my $compress = $et->Options('Compress');
|
1375
|
+
my $verbose = $isWriting ? 0 : $et->Options('Verbose');
|
1376
|
+
my $dat = substr($$dataPt, 4);
|
1377
|
+
eval { $dat = IO::Uncompress::Brotli::unbro($dat, 100000000) };
|
1378
|
+
$@ and $et->Warn("Error decoding $type brob box"), return 1;
|
1379
|
+
$verbose > 2 and $et->VerboseDump(\$dat, Prefix => $$et{INDENT} . ' ');
|
1380
|
+
my %dirInfo = ( DataPt => \$dat );
|
1381
|
+
if ($type eq 'xml ') {
|
1382
|
+
$dirInfo{DirName} = 'XMP'; # (necessary for block read/write)
|
1383
|
+
require Image::ExifTool::XMP;
|
1384
|
+
if ($isWriting) {
|
1385
|
+
$dirInfo{Compact} = 1 if $compress; # (no need to add padding if writing compressed)
|
1386
|
+
$dat = $et->WriteDirectory(\%dirInfo, $tagTablePtr);
|
1387
|
+
} else {
|
1388
|
+
Image::ExifTool::XMP::ProcessXMP($et, \%dirInfo, $tagTablePtr);
|
1389
|
+
}
|
1390
|
+
} elsif ($type eq 'Exif') {
|
1391
|
+
$dirInfo{DirName} = 'EXIF'; # (necessary for block read/write)
|
1392
|
+
$dirInfo{DirStart} = 4 + (length($dat) > 4 ? unpack("N", $dat) : 0);
|
1393
|
+
if ($dirInfo{DirStart} > length $dat) {
|
1394
|
+
$et->Warn("Corrupted Brotli '${type}' data");
|
1395
|
+
} elsif ($isWriting) {
|
1396
|
+
$dat = $et->WriteDirectory(\%dirInfo, $tagTablePtr, \&Image::ExifTool::WriteTIFF);
|
1397
|
+
# add back header word
|
1398
|
+
$dat = "\0\0\0\0" . $dat if defined $dat and length $dat;
|
1399
|
+
} else {
|
1400
|
+
$et->ProcessTIFF(\%dirInfo, $tagTablePtr);
|
1401
|
+
}
|
1402
|
+
} elsif ($type eq 'jumb') {
|
1403
|
+
return undef if $isWriting; # (can't yet write JUMBF)
|
1404
|
+
Image::ExifTool::ProcessJUMB($et, \%dirInfo, $tagTablePtr); # (untested)
|
1405
|
+
}
|
1406
|
+
if ($isWriting) {
|
1407
|
+
return undef unless defined $dat;
|
1408
|
+
# rewrite as uncompressed if Compress option is set to 0 (or '')
|
1409
|
+
return $dat if defined $compress and not $compress;
|
1410
|
+
eval { $dat = IO::Compress::Brotli::bro($dat) };
|
1411
|
+
$@ and $et->Warn("Error encoding $type brob box"), return undef;
|
1412
|
+
$et->VPrint(0, " Writing Brotli-compressed $type\n");
|
1413
|
+
return $type . $dat;
|
1414
|
+
}
|
1415
|
+
} else {
|
1416
|
+
$et->WarnOnce('Install IO::Uncompress::Brotli to decode Brotli-compressed metadata');
|
1417
|
+
return undef if $isWriting;
|
1418
|
+
}
|
1419
|
+
return 1;
|
1420
|
+
}
|
1421
|
+
|
1235
1422
|
#------------------------------------------------------------------------------
|
1236
1423
|
# Extract parameters from JPEG XL codestream [unverified!]
|
1237
1424
|
# Inputs: 0) ExifTool ref, 1) codestream ref
|
1238
|
-
# Returns: 1
|
1425
|
+
# Returns: 1 on success
|
1239
1426
|
sub ProcessJXLCodestream($$)
|
1240
1427
|
{
|
1241
1428
|
my ($et, $dataPt) = @_;
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1429
|
+
|
1430
|
+
return 0 unless $$dataPt =~ /^(\0\0\0\0)?\xff\x0a/; # validate codestream
|
1431
|
+
# ignore if already extracted (ie. subsequent jxlp boxes)
|
1432
|
+
return 0 if $$et{VALUE}{ImageWidth};
|
1433
|
+
# work with first 64 bytes of codestream data
|
1434
|
+
# (and add padding if necessary to avoid unpacking past end of data)
|
1435
|
+
my $dat;
|
1436
|
+
if (length $$dataPt > 64) {
|
1437
|
+
$dat = substr($$dataPt, 0, 64);
|
1438
|
+
} elsif (length $$dataPt < 18) {
|
1439
|
+
$dat = $$dataPt . ("\0" x 18); # (so we'll have a minimum 14 bytes to work with)
|
1440
|
+
} else {
|
1441
|
+
$dat = $$dataPt;
|
1246
1442
|
}
|
1247
|
-
|
1443
|
+
$dat =~ s/^\0\0\0\0//; # remove jxlp header word
|
1444
|
+
my @a = unpack 'x2C12', $dat;
|
1248
1445
|
my ($x, $y);
|
1249
1446
|
my $small = GetBits(\@a, 1);
|
1250
1447
|
if ($small) {
|
@@ -1362,8 +1559,8 @@ sub ProcessJXL($$)
|
|
1362
1559
|
$$dirInfo{RAF} = new File::RandomAccess(\$buff);
|
1363
1560
|
} else {
|
1364
1561
|
$et->SetFileType('JXL Codestream','image/jxl', 'jxl');
|
1365
|
-
if ($$et{
|
1366
|
-
$et->
|
1562
|
+
if ($$et{ImageDataHash} and $raf->Seek(0,0)) {
|
1563
|
+
$et->ImageDataHash($raf, undef, 'JXL');
|
1367
1564
|
}
|
1368
1565
|
return ProcessJXLCodestream($et, \$hdr);
|
1369
1566
|
}
|
@@ -15,7 +15,7 @@ use vars qw($VERSION);
|
|
15
15
|
use Image::ExifTool qw(:DataAccess :Utils);
|
16
16
|
use Image::ExifTool::Exif;
|
17
17
|
|
18
|
-
$VERSION = '1.
|
18
|
+
$VERSION = '1.15';
|
19
19
|
|
20
20
|
sub ProcessMPImageList($$$);
|
21
21
|
|
@@ -131,6 +131,7 @@ sub ProcessMPImageList($$$);
|
|
131
131
|
0x020002 => 'Multi-frame Disparity',
|
132
132
|
0x020003 => 'Multi-angle',
|
133
133
|
0x030000 => 'Baseline MP Primary Image',
|
134
|
+
0x040000 => 'Original Preservation Image', # (Exif 3.0)
|
134
135
|
},
|
135
136
|
},
|
136
137
|
4 => {
|
@@ -17,7 +17,7 @@ use vars qw($VERSION);
|
|
17
17
|
use Image::ExifTool qw(:DataAccess :Utils);
|
18
18
|
use Image::ExifTool::Minolta;
|
19
19
|
|
20
|
-
$VERSION = '1.
|
20
|
+
$VERSION = '1.19';
|
21
21
|
|
22
22
|
sub ProcessMRW($$;$);
|
23
23
|
sub WriteMRW($$;$);
|
@@ -489,7 +489,7 @@ sub ProcessMRW($$;$)
|
|
489
489
|
$err and $et->Error("MRW format error", $$et{TIFF_TYPE} eq 'ARW');
|
490
490
|
} else {
|
491
491
|
$err and $et->Warn("MRW format error");
|
492
|
-
$et->
|
492
|
+
$et->ImageDataHash($raf, undef, 'raw') unless $$et{A100DataOffset};
|
493
493
|
}
|
494
494
|
return $rtnVal;
|
495
495
|
}
|