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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +29 -1
  3. data/bin/MANIFEST +1 -0
  4. data/bin/META.json +4 -1
  5. data/bin/META.yml +4 -1
  6. data/bin/Makefile.PL +7 -1
  7. data/bin/README +10 -7
  8. data/bin/exiftool +22 -16
  9. data/bin/lib/Image/ExifTool/7Z.pm +793 -0
  10. data/bin/lib/Image/ExifTool/Canon.pm +1 -0
  11. data/bin/lib/Image/ExifTool/CanonRaw.pm +4 -4
  12. data/bin/lib/Image/ExifTool/Exif.pm +31 -14
  13. data/bin/lib/Image/ExifTool/FujiFilm.pm +3 -3
  14. data/bin/lib/Image/ExifTool/GPS.pm +5 -2
  15. data/bin/lib/Image/ExifTool/Geotag.pm +4 -1
  16. data/bin/lib/Image/ExifTool/Jpeg2000.pm +225 -28
  17. data/bin/lib/Image/ExifTool/MPF.pm +2 -1
  18. data/bin/lib/Image/ExifTool/MinoltaRaw.pm +2 -2
  19. data/bin/lib/Image/ExifTool/PNG.pm +6 -6
  20. data/bin/lib/Image/ExifTool/PhaseOne.pm +5 -5
  21. data/bin/lib/Image/ExifTool/QuickTime.pm +41 -12
  22. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +18 -18
  23. data/bin/lib/Image/ExifTool/README +1 -1
  24. data/bin/lib/Image/ExifTool/RIFF.pm +11 -9
  25. data/bin/lib/Image/ExifTool/Shortcuts.pm +2 -1
  26. data/bin/lib/Image/ExifTool/SigmaRaw.pm +4 -4
  27. data/bin/lib/Image/ExifTool/Sony.pm +102 -8
  28. data/bin/lib/Image/ExifTool/TagLookup.pm +982 -953
  29. data/bin/lib/Image/ExifTool/TagNames.pod +75 -5
  30. data/bin/lib/Image/ExifTool/Validate.pm +17 -1
  31. data/bin/lib/Image/ExifTool/WriteExif.pl +9 -7
  32. data/bin/lib/Image/ExifTool/Writer.pl +7 -6
  33. data/bin/lib/Image/ExifTool/XMP.pm +14 -2
  34. data/bin/lib/Image/ExifTool/XMP2.pl +32 -0
  35. data/bin/lib/Image/ExifTool/ZIP.pm +5 -5
  36. data/bin/lib/Image/ExifTool.pm +54 -33
  37. data/bin/lib/Image/ExifTool.pod +17 -6
  38. data/bin/perl-Image-ExifTool.spec +6 -6
  39. data/lib/exiftool_vendored/version.rb +1 -1
  40. metadata +3 -2
@@ -2092,6 +2092,7 @@ my %offOn = ( 0 => 'Off', 1 => 'On' );
2092
2092
  TagTable => 'Image::ExifTool::Canon::AFConfig',
2093
2093
  }
2094
2094
  },
2095
+ # 0x402b - crop information (forum14904)
2095
2096
  0x403f => { #25
2096
2097
  Name => 'RawBurstModeRoll',
2097
2098
  SubDirectory => {
@@ -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.60';
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 MD5 of image data if requested
702
- if ($$et{ImageDataMD5} and $tagID == 0x2005) {
703
- $raf->Seek($ptr, 0) and $et->ImageDataMD5($raf, $size, 'raw');
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 AddImageDataMD5($$$);
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 =~ /(.*?)( ?[NS])?,/) ? $1 : undef',
4914
- GPSLatitudeRef => '(defined $val and $val =~ /(-?)(.*?) ?([NS]?),/) ? ($3 || ($1 ? "S" : "N")) : undef',
4915
- GPSLongitude => '(defined $val and $val =~ /, ?(.*?)( ?[EW]?)$/) ? $1 : undef',
4916
- GPSLongitudeRef => '(defined $val and $val =~ /, ?(-?)(.*?) ?([EW]?)$/) ? ($3 || ($1 ? "W" : "E")) : undef',
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, $lon";
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, $doMD5);
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 MD5 if requested
5937
- $doMD5 = 1 if $$et{ImageDataMD5} and (($$et{FILE_TYPE} eq 'TIFF' and not $base and not $inMakerNotes) or
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
- $val = $et->Decode($val, $strEnc) if $strEnc and $formatStr eq 'string' and defined $val;
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 $doMD5) {
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 MD5 if requested
6804
- AddImageDataMD5($et, $dirInfo, \%offsetInfo) if $doMD5;
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.87';
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 MD5 of image data if necessary
1691
- $et->ImageDataMD5($raf, $len, 'raw') if $$et{ImageDataMD5} and $raf->Seek($start,0);
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.71';
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.34';
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 => 'JP2',
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, $md5);
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 MD5 if requested, but only for top-level image data
956
- $md5 = $$et{ImageDataMD5} if $raf;
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 ($md5 and $isImageData{$boxID}) {
1027
- $et->ImageDataMD5($raf, undef, $boxID);
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 ($md5 and $isImageData{$boxID}) {
1046
- $et->ImageDataMD5($raf, $boxLen, $boxID);
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 ($md5 and $isImageData{$boxID}) {
1060
- $md5->add($buff);
1061
- $et->VPrint(0, "$$et{INDENT}(ImageDataMD5: $boxLen bytes of $boxID data)\n");
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
- # remove this directory from our create list
1148
- delete $$et{AddJp2Dirs}{$$tagInfo{Name}}; # (eg. 'EXIF' or 'XMP')
1149
- delete $$et{AddJp2Dirs}{$boxID}; # (eg. 'Exif' or 'xml ')
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 $boxID eq 'Exif' or ($boxID eq 'xml ' and $$et{IsJXL}) 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
- # add padding if necessary to avoid unpacking past end of data
1243
- if (length $$dataPt < 14) {
1244
- my $tmp = $$dataPt . ("\0" x 14);
1245
- $dataPt = \$tmp;
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
- my @a = unpack 'x2C12', $$dataPt;
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{ImageDataMD5} and $raf->Seek(0,0)) {
1366
- $et->ImageDataMD5($raf, undef, 'JXL');
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.14';
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.18';
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->ImageDataMD5($raf, undef, 'raw') unless $$et{A100DataOffset};
492
+ $et->ImageDataHash($raf, undef, 'raw') unless $$et{A100DataOffset};
493
493
  }
494
494
  return $rtnVal;
495
495
  }