exiftool_vendored 12.18.0 → 12.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +236 -4
  3. data/bin/MANIFEST +23 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +45 -43
  7. data/bin/arg_files/xmp2exif.args +2 -1
  8. data/bin/config_files/acdsee.config +193 -6
  9. data/bin/config_files/convert_regions.config +25 -14
  10. data/bin/config_files/cuepointlist.config +70 -0
  11. data/bin/config_files/example.config +2 -9
  12. data/bin/exiftool +152 -97
  13. data/bin/fmt_files/gpx.fmt +2 -2
  14. data/bin/fmt_files/gpx_wpt.fmt +2 -2
  15. data/bin/fmt_files/kml.fmt +1 -1
  16. data/bin/fmt_files/kml_track.fmt +1 -1
  17. data/bin/lib/Image/ExifTool/Apple.pm +3 -2
  18. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +31 -13
  19. data/bin/lib/Image/ExifTool/CBOR.pm +331 -0
  20. data/bin/lib/Image/ExifTool/Canon.pm +44 -19
  21. data/bin/lib/Image/ExifTool/DJI.pm +6 -6
  22. data/bin/lib/Image/ExifTool/DPX.pm +13 -2
  23. data/bin/lib/Image/ExifTool/DjVu.pm +6 -5
  24. data/bin/lib/Image/ExifTool/Exif.pm +124 -13
  25. data/bin/lib/Image/ExifTool/FITS.pm +13 -2
  26. data/bin/lib/Image/ExifTool/FlashPix.pm +35 -10
  27. data/bin/lib/Image/ExifTool/FujiFilm.pm +19 -8
  28. data/bin/lib/Image/ExifTool/GPS.pm +22 -11
  29. data/bin/lib/Image/ExifTool/Geotag.pm +13 -2
  30. data/bin/lib/Image/ExifTool/GoPro.pm +16 -1
  31. data/bin/lib/Image/ExifTool/ICC_Profile.pm +2 -2
  32. data/bin/lib/Image/ExifTool/ID3.pm +15 -3
  33. data/bin/lib/Image/ExifTool/JPEG.pm +74 -4
  34. data/bin/lib/Image/ExifTool/JSON.pm +30 -5
  35. data/bin/lib/Image/ExifTool/Jpeg2000.pm +395 -16
  36. data/bin/lib/Image/ExifTool/LIF.pm +153 -0
  37. data/bin/lib/Image/ExifTool/Lang/nl.pm +60 -59
  38. data/bin/lib/Image/ExifTool/M2TS.pm +137 -5
  39. data/bin/lib/Image/ExifTool/MIE.pm +4 -3
  40. data/bin/lib/Image/ExifTool/MRC.pm +341 -0
  41. data/bin/lib/Image/ExifTool/MWG.pm +3 -3
  42. data/bin/lib/Image/ExifTool/MXF.pm +1 -1
  43. data/bin/lib/Image/ExifTool/MacOS.pm +3 -3
  44. data/bin/lib/Image/ExifTool/Microsoft.pm +298 -82
  45. data/bin/lib/Image/ExifTool/Nikon.pm +18 -5
  46. data/bin/lib/Image/ExifTool/NikonSettings.pm +19 -2
  47. data/bin/lib/Image/ExifTool/Olympus.pm +10 -3
  48. data/bin/lib/Image/ExifTool/Other.pm +93 -0
  49. data/bin/lib/Image/ExifTool/PDF.pm +9 -12
  50. data/bin/lib/Image/ExifTool/PNG.pm +8 -7
  51. data/bin/lib/Image/ExifTool/Panasonic.pm +28 -3
  52. data/bin/lib/Image/ExifTool/Pentax.pm +28 -5
  53. data/bin/lib/Image/ExifTool/PhaseOne.pm +4 -3
  54. data/bin/lib/Image/ExifTool/Photoshop.pm +6 -0
  55. data/bin/lib/Image/ExifTool/QuickTime.pm +234 -75
  56. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +283 -141
  57. data/bin/lib/Image/ExifTool/README +5 -2
  58. data/bin/lib/Image/ExifTool/RIFF.pm +89 -12
  59. data/bin/lib/Image/ExifTool/Samsung.pm +48 -10
  60. data/bin/lib/Image/ExifTool/Shortcuts.pm +9 -0
  61. data/bin/lib/Image/ExifTool/Sony.pm +230 -69
  62. data/bin/lib/Image/ExifTool/TagInfoXML.pm +1 -0
  63. data/bin/lib/Image/ExifTool/TagLookup.pm +4145 -4029
  64. data/bin/lib/Image/ExifTool/TagNames.pod +671 -287
  65. data/bin/lib/Image/ExifTool/Torrent.pm +18 -11
  66. data/bin/lib/Image/ExifTool/WriteExif.pl +1 -1
  67. data/bin/lib/Image/ExifTool/WriteIPTC.pl +1 -1
  68. data/bin/lib/Image/ExifTool/WritePDF.pl +1 -0
  69. data/bin/lib/Image/ExifTool/WritePNG.pl +2 -0
  70. data/bin/lib/Image/ExifTool/WritePostScript.pl +1 -0
  71. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +55 -21
  72. data/bin/lib/Image/ExifTool/WriteXMP.pl +7 -3
  73. data/bin/lib/Image/ExifTool/Writer.pl +47 -10
  74. data/bin/lib/Image/ExifTool/XMP.pm +45 -15
  75. data/bin/lib/Image/ExifTool/XMP2.pl +3 -1
  76. data/bin/lib/Image/ExifTool/XMPStruct.pl +3 -1
  77. data/bin/lib/Image/ExifTool/ZISRAW.pm +121 -2
  78. data/bin/lib/Image/ExifTool.pm +233 -81
  79. data/bin/lib/Image/ExifTool.pod +114 -93
  80. data/bin/perl-Image-ExifTool.spec +43 -42
  81. data/lib/exiftool_vendored/version.rb +1 -1
  82. metadata +28 -13
@@ -16,9 +16,10 @@ use strict;
16
16
  use vars qw($VERSION);
17
17
  use Image::ExifTool qw(:DataAccess :Utils);
18
18
 
19
- $VERSION = '1.27';
19
+ $VERSION = '1.31';
20
20
 
21
21
  sub ProcessJpeg2000Box($$$);
22
+ sub ProcessJUMD($$$);
22
23
 
23
24
  my %resolutionUnit = (
24
25
  -3 => 'km',
@@ -54,6 +55,22 @@ my %jp2Map = (
54
55
  MakerNotes => 'ExifIFD',
55
56
  );
56
57
 
58
+ # map of where information is written in a JXL image
59
+ my %jxlMap = (
60
+ IFD0 => 'Exif',
61
+ XMP => 'XML',
62
+ 'Exif' => 'JP2',
63
+ IFD1 => 'IFD0',
64
+ EXIF => 'IFD0', # to write EXIF as a block
65
+ ExifIFD => 'IFD0',
66
+ GPS => 'IFD0',
67
+ SubIFD => 'IFD0',
68
+ GlobParamIFD => 'IFD0',
69
+ PrintIM => 'IFD0',
70
+ InteropIFD => 'ExifIFD',
71
+ MakerNotes => 'ExifIFD',
72
+ );
73
+
57
74
  # UUID's for writable UUID directories (by tag name)
58
75
  my %uuid = (
59
76
  'UUID-EXIF' => 'JpgTiffExif->JP2',
@@ -107,9 +124,13 @@ my %j2cMarker = (
107
124
  WRITE_PROC => \&ProcessJpeg2000Box,
108
125
  PREFERRED => 1, # always add these tags when writing
109
126
  NOTES => q{
110
- The tags below are extracted from JPEG 2000 images, however ExifTool
111
- currently writes only EXIF, IPTC and XMP tags in these images.
127
+ The tags below are found in JPEG 2000 images and the JUMBF metadata in JPEG
128
+ images, but not all of these are extracted. Note that ExifTool currently
129
+ writes only EXIF, IPTC and XMP tags in Jpeg2000 images.
112
130
  },
131
+ #
132
+ # NOTE: ONLY TAGS WITH "Format" DEFINED ARE EXTRACTED!
133
+ #
113
134
  'jP ' => 'JP2Signature', # (ref 1)
114
135
  "jP\x1a\x1a" => 'JP2Signature', # (ref 2)
115
136
  prfl => 'Profile',
@@ -199,13 +220,22 @@ my %j2cMarker = (
199
220
  chck => 'DigitalSignature',
200
221
  mp7b => 'MPEG7Binary',
201
222
  free => 'Free',
202
- jp2c => 'ContiguousCodestream',
223
+ jp2c => [{
224
+ Name => 'ContiguousCodestream',
225
+ Condition => 'not $$self{jumd_level}',
226
+ },{
227
+ Name => 'PreviewImage',
228
+ Groups => { 2 => 'Preview' },
229
+ Format => 'undef',
230
+ Binary => 1,
231
+ }],
203
232
  jp2i => {
204
233
  Name => 'IntellectualProperty',
205
234
  SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
206
235
  },
207
- 'xml '=> {
236
+ 'xml '=> [{
208
237
  Name => 'XML',
238
+ Condition => 'not $$self{IsJXL}',
209
239
  Writable => 'undef',
210
240
  Flags => [ 'Binary', 'Protected', 'BlockExtract' ],
211
241
  List => 1,
@@ -213,12 +243,18 @@ my %j2cMarker = (
213
243
  by default, the XML data in this tag is parsed using the ExifTool XMP module
214
244
  to to allow individual tags to be accessed when reading, but it may also be
215
245
  extracted as a block via the "XML" tag, which is also how this tag is
216
- written and copied. This is a List-type tag because multiple XML blocks may
217
- exist
246
+ written and copied. It may also be extracted as a block by setting the API
247
+ BlockExtract option. This is a List-type tag because multiple XML blocks
248
+ may exist
218
249
  },
219
250
  # (note: extracting as a block was broken in 11.04, and finally fixed in 12.14)
220
251
  SubDirectory => { TagTable => 'Image::ExifTool::XMP::XML' },
221
- },
252
+ },{
253
+ Name => 'XMP',
254
+ Notes => 'used for XMP in JPEG XL files',
255
+ # NOTE: the hacked code relies on this being at index 1 of the tagInfo list!
256
+ SubDirectory => { TagTable => 'Image::ExifTool::XMP::Main' },
257
+ }],
222
258
  uuid => [
223
259
  {
224
260
  Name => 'UUID-EXIF',
@@ -302,6 +338,22 @@ my %j2cMarker = (
302
338
  Start => '$valuePtr + 16',
303
339
  },
304
340
  },
341
+ {
342
+ Name => 'UUID-Signature', # (seen in JUMB data of JPEG images)
343
+ # (may be able to remove this when JUMBF specification is finalized)
344
+ Condition => '$$valPt=~/^casg\x00\x11\x00\x10\x80\x00\x00\xaa\x00\x38\x9b\x71/',
345
+ Format => 'undef',
346
+ ValueConv => 'substr($val,16)',
347
+ },
348
+ {
349
+ Name => 'UUID-C2PAClaimSignature', # (seen in incorrectly-formatted JUMB data of JPEG images)
350
+ # (may be able to remove this when JUMBF specification is finalized)
351
+ Condition => '$$valPt=~/^c2cs\x00\x11\x00\x10\x80\x00\x00\xaa\x00\x38\x9b\x71/',
352
+ SubDirectory => {
353
+ TagTable => 'Image::ExifTool::CBOR::Main',
354
+ Start => '$valuePtr + 16',
355
+ },
356
+ },
305
357
  {
306
358
  Name => 'UUID-Unknown',
307
359
  },
@@ -321,6 +373,73 @@ my %j2cMarker = (
321
373
  Name => 'URL',
322
374
  Format => 'string',
323
375
  },
376
+ # JUMBF boxes (ref https://github.com/thorfdbg/codestream-parser)
377
+ jumd => {
378
+ Name => 'JUMBFDescr',
379
+ SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::JUMD' },
380
+ },
381
+ jumb => {
382
+ Name => 'JUMBFBox',
383
+ SubDirectory => {
384
+ TagTable => 'Image::ExifTool::Jpeg2000::Main',
385
+ ProcessProc => \&ProcessJUMB,
386
+ },
387
+ },
388
+ json => {
389
+ Name => 'JSONData',
390
+ Flags => [ 'Binary', 'Protected', 'BlockExtract' ],
391
+ Notes => q{
392
+ by default, data in this tag is parsed using the ExifTool JSON module to to
393
+ allow individual tags to be accessed when reading, but it may also be
394
+ extracted as a block via the "JSONData" tag or by setting the API
395
+ BlockExtract option
396
+ },
397
+ SubDirectory => { TagTable => 'Image::ExifTool::JSON::Main' },
398
+ },
399
+ cbor => {
400
+ Name => 'CBORData',
401
+ Flags => [ 'Binary', 'Protected' ],
402
+ SubDirectory => { TagTable => 'Image::ExifTool::CBOR::Main' },
403
+ },
404
+ bfdb => { # used in JUMBF (see # (used when tag is renamed according to JUMDLabel)
405
+ Name => 'BinaryDataType',
406
+ Notes => 'JUMBF, MIME type and optional file name',
407
+ Format => 'undef',
408
+ # (ignore "toggles" byte and just extract MIME type and file name)
409
+ ValueConv => '$_=substr($val,1); s/\0+$//; s/\0/, /; $_',
410
+ JUMBF_Suffix => 'Type', # (used when tag is renamed according to JUMDLabel)
411
+ },
412
+ bidb => { # used in JUMBF
413
+ Name => 'BinaryData',
414
+ Notes => 'JUMBF',
415
+ Groups => { 2 => 'Preview' },
416
+ Format => 'undef',
417
+ Binary => 1,
418
+ JUMBF_Suffix => 'Data', # (used when tag is renamed according to JUMDLabel)
419
+ },
420
+ #
421
+ # stuff seen in JPEG XL images:
422
+ #
423
+ # jbrd - JPEG Bitstream Reconstruction Data (allows lossless conversion back to original JPG)
424
+ jxlc => {
425
+ Name => 'JXLCodestream',
426
+ Format => 'undef',
427
+ Notes => q{
428
+ Codestream in JPEG XL image. Currently processed only to determine
429
+ ImageSize
430
+ },
431
+ RawConv => 'Image::ExifTool::Jpeg2000::ProcessJXLCodestream($self,\$val); undef',
432
+ },
433
+ Exif => {
434
+ Name => 'EXIF',
435
+ SubDirectory => {
436
+ TagTable => 'Image::ExifTool::Exif::Main',
437
+ ProcessProc => \&Image::ExifTool::ProcessTIFF,
438
+ WriteProc => \&Image::ExifTool::WriteTIFF,
439
+ DirName => 'EXIF',
440
+ Start => '$valuePtr + 4',
441
+ },
442
+ },
324
443
  );
325
444
 
326
445
  %Image::ExifTool::Jpeg2000::ImageHeader = (
@@ -375,6 +494,7 @@ my %j2cMarker = (
375
494
  'jp2 ' => 'JPEG 2000 Image (.JP2)', # image/jp2
376
495
  'jpm ' => 'JPEG 2000 Compound Image (.JPM)', # image/jpm
377
496
  'jpx ' => 'JPEG 2000 with extensions (.JPX)', # image/jpx
497
+ 'jxl ' => 'JPEG XL Image (.JXL)', # image/jxl
378
498
  },
379
499
  },
380
500
  1 => {
@@ -513,6 +633,114 @@ my %j2cMarker = (
513
633
  ],
514
634
  );
515
635
 
636
+ # JUMBF description box
637
+ %Image::ExifTool::Jpeg2000::JUMD = (
638
+ PROCESS_PROC => \&ProcessJUMD,
639
+ GROUPS => { 0 => 'JUMBF', 1 => 'JUMBF', 2 => 'Image' },
640
+ NOTES => 'Information extracted from the JUMBF description box.',
641
+ 'type' => {
642
+ Name => 'JUMDType',
643
+ ValueConv => 'unpack "H*", $val',
644
+ PrintConv => q{
645
+ my @a = $val =~ /^(\w{8})(\w{4})(\w{4})(\w{16})$/;
646
+ return $val unless @a;
647
+ my $ascii = pack 'H*', $a[0];
648
+ $a[0] = "($ascii)" if $ascii =~ /^[a-zA-Z0-9]{4}$/;
649
+ return join '-', @a;
650
+ },
651
+ # seen:
652
+ # cacb/cast/caas/cacl/casg/json-00110010800000aa00389b71
653
+ # 6579d6fbdba2446bb2ac1b82feeb89d1 - JPEG image
654
+ },
655
+ 'label' => { Name => 'JUMDLabel' },
656
+ 'toggles' => {
657
+ Name => 'JUMDToggles',
658
+ Unknown => 1,
659
+ PrintConv => { BITMASK => {
660
+ 0 => 'Requestable',
661
+ 1 => 'Label',
662
+ 2 => 'ID',
663
+ 3 => 'Signature',
664
+ }},
665
+ },
666
+ 'id' => { Name => 'JUMDID', Description => 'JUMD ID' },
667
+ 'sig' => { Name => 'JUMDSignature', PrintConv => 'unpack "H*", $val' },
668
+ );
669
+
670
+ #------------------------------------------------------------------------------
671
+ # Read JUMBF box to keep track of sub-document numbers
672
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
673
+ # Returns: 1 on success
674
+ sub ProcessJUMB($$$)
675
+ {
676
+ my ($et, $dirInfo, $tagTablePtr) = @_;
677
+ if ($$et{jumd_level}) {
678
+ ++$$et{jumd_level}[-1]; # increment current sub-document number
679
+ } else {
680
+ $$et{jumd_level} = [ ++$$et{DOC_COUNT} ]; # new top-level sub-document
681
+ $$et{SET_GROUP0} = 'JUMBF';
682
+ }
683
+ $$et{DOC_NUM} = join '-', @{$$et{jumd_level}};
684
+ push @{$$et{jumd_level}}, 0;
685
+ ProcessJpeg2000Box($et, $dirInfo, $tagTablePtr);
686
+ delete $$et{DOC_NUM};
687
+ delete $$et{JUMBFLabel};
688
+ pop @{$$et{jumd_level}};
689
+ if (@{$$et{jumd_level}} < 2) {
690
+ delete $$et{jumd_level};
691
+ delete $$et{SET_GROUP0};
692
+ }
693
+ return 1;
694
+ }
695
+
696
+ #------------------------------------------------------------------------------
697
+ # Read JUMBF description box (ref https://github.com/thorfdbg/codestream-parser)
698
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
699
+ # Returns: 1 on success
700
+ sub ProcessJUMD($$$)
701
+ {
702
+ my ($et, $dirInfo, $tagTablePtr) = @_;
703
+ my $dataPt = $$dirInfo{DataPt};
704
+ my $pos = $$dirInfo{DirStart};
705
+ my $end = $pos + $$dirInfo{DirLen};
706
+ $et->VerboseDir('JUMD', 0, $end-$pos);
707
+ delete $$et{JUMBFLabel};
708
+ $$dirInfo{DirLen} < 17 and $et->Warn('Truncated JUMD directory'), return 0;
709
+ my $type = substr($$dataPt, $pos, 4);
710
+ $et->HandleTag($tagTablePtr, 'type', substr($$dataPt, $pos, 16));
711
+ $pos += 16;
712
+ my $flags = Get8u($dataPt, $pos++);
713
+ $et->HandleTag($tagTablePtr, 'toggles', $flags);
714
+ if ($flags & 0x02) { # label exists?
715
+ pos($$dataPt) = $pos;
716
+ $$dataPt =~ /\0/g or $et->Warn('Missing JUMD label terminator'), return 0;
717
+ my $len = pos($$dataPt) - $pos;
718
+ my $name = substr($$dataPt, $pos, $len);
719
+ $et->HandleTag($tagTablePtr, 'label', $name);
720
+ $pos += $len;
721
+ if ($len) {
722
+ $name =~ s/[^-_a-zA-Z0-9]([a-z])/\U$1/g; # capitalize characters after illegal characters
723
+ $name =~ tr/-_a-zA-Z0-9//dc; # remove other illegal characters
724
+ $name =~ s/__/_/; # collapse double underlines
725
+ $name = ucfirst $name; # capitalize first letter
726
+ $name = "Tag$name" if length($name) < 2; # must at least 2 characters long
727
+ $$et{JUMBFLabel} = $name;
728
+ }
729
+ }
730
+ if ($flags & 0x04) { # ID exists?
731
+ $pos + 4 > $end and $et->Warn('Missing JUMD ID'), return 0;
732
+ $et->HandleTag($tagTablePtr, 'id', Get32u($dataPt, $pos));
733
+ $pos += 4;
734
+ }
735
+ if ($flags & 0x08) { # signature exists?
736
+ $pos + 32 > $end and $et->Warn('Missing JUMD signature'), return 0;
737
+ $et->HandleTag($tagTablePtr, 'sig', substr($$dataPt, $pos, 32));
738
+ $pos += 32;
739
+ }
740
+ $pos == $end or $et->Warn('Extra data in JUMD box'." $pos $end", 1);
741
+ return 1;
742
+ }
743
+
516
744
  #------------------------------------------------------------------------------
517
745
  # Create new JPEG 2000 boxes when writing
518
746
  # (Currently only supports adding top-level Writable JPEG2000 tags and certain UUID boxes)
@@ -542,8 +770,29 @@ sub CreateNewBoxes($$)
542
770
  $et->VerboseValue("+ Jpeg2000:$$tagInfo{Name}", $val);
543
771
  }
544
772
  }
545
- # add UUID boxes
773
+ # add UUID boxes (and/or JXL Exif/XML boxes)
546
774
  foreach $dirName (sort keys %$addDirs) {
775
+ # handle JPEG XL XMP and EXIF
776
+ if ($dirName eq 'XML' or $dirName eq 'Exif') {
777
+ my ($tag, $dir) = $dirName eq 'XML' ? ('xml ', 'XMP') : ('Exif', 'EXIF');
778
+ my $tagInfo = $Image::ExifTool::Jpeg2000::Main{$tag};
779
+ $tagInfo = $$tagInfo[1] if ref $tagInfo eq 'ARRAY'; # (hack for stupid JXL XMP)
780
+ my $subdir = $$tagInfo{SubDirectory};
781
+ my $tagTable = GetTagTable($$subdir{TagTable});
782
+ $tagTable = GetTagTable('Image::ExifTool::XMP::Main') if $dir eq 'XMP';
783
+ my %dirInfo = (
784
+ DirName => $dir,
785
+ Parent => 'JP2',
786
+ );
787
+ my $newdir = $et->WriteDirectory(\%dirInfo, $tagTable, $$subdir{WriteProc});
788
+ if (defined $newdir and length $newdir) {
789
+ # not sure why, but EXIF box is padded with leading 0's in my sample
790
+ my $pad = $dirName eq 'Exif' ? "\0\0\0\0" : '';
791
+ my $boxhdr = pack('N', length($newdir) + length($pad) + 8) . $tag;
792
+ Write($outfile, $boxhdr, $pad, $newdir) or return 0;
793
+ next;
794
+ }
795
+ }
547
796
  next unless $uuid{$dirName};
548
797
  my $tagInfo;
549
798
  foreach $tagInfo (@{$Image::ExifTool::Jpeg2000::Main{uuid}}) {
@@ -715,6 +964,14 @@ sub ProcessJpeg2000Box($$$)
715
964
  }
716
965
  }
717
966
  }
967
+ # create new tag for JUMBF data values with name corresponding to JUMBFLabel
968
+ if ($tagInfo and $$et{JUMBFLabel} and (not $$tagInfo{SubDirectory} or $$tagInfo{BlockExtract})) {
969
+ $tagInfo = { %$tagInfo, Name => $$et{JUMBFLabel} . ($$tagInfo{JUMBF_Suffix} || '') };
970
+ delete $$tagInfo{Description};
971
+ AddTagToTable($tagTablePtr, '_JUMBF_' . $$et{JUMBFLabel}, $tagInfo);
972
+ delete $$tagInfo{Protected}; # (must do this so -j -b returns JUMBF binary data)
973
+ $$tagInfo{TagID} = $boxID;
974
+ }
718
975
  if ($verbose) {
719
976
  $et->VerboseInfo($boxID, $tagInfo,
720
977
  Table => $tagTablePtr,
@@ -752,8 +1009,8 @@ sub ProcessJpeg2000Box($$$)
752
1009
  # remove this directory from our create list
753
1010
  delete $$et{AddJp2Dirs}{$$tagInfo{Name}};
754
1011
  my $newdir;
755
- # only edit writable UUID boxes
756
- if ($uuid) {
1012
+ # only edit writable UUID and Exif boxes
1013
+ if ($uuid or $boxID eq 'Exif' or ($boxID eq 'xml ' and $$et{IsJXL})) {
757
1014
  $newdir = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc});
758
1015
  next if defined $newdir and not length $newdir; # next if deleting the box
759
1016
  } elsif (defined $uuid) {
@@ -803,6 +1060,68 @@ sub ProcessJpeg2000Box($$$)
803
1060
  return 1;
804
1061
  }
805
1062
 
1063
+ #------------------------------------------------------------------------------
1064
+ # Return bits from a bitstream object
1065
+ # Inputs: 0) array ref, 1) number of bits
1066
+ # Returns: specified number of bits as an integer, and shifts input bitstream
1067
+ sub GetBits($$)
1068
+ {
1069
+ my ($a, $n) = @_;
1070
+ my $v = 0;
1071
+ my $bit = 1;
1072
+ my $i;
1073
+ while ($n--) {
1074
+ for ($i=0; $i<@$a; ++$i) {
1075
+ # consume bits LSB first
1076
+ my $set = $$a[$i] & 1;
1077
+ $$a[$i] >>= 1;
1078
+ if ($i) {
1079
+ $$a[$i-1] |= 0x80 if $set;
1080
+ } else {
1081
+ $v |= $bit if $set;
1082
+ $bit <<= 1;
1083
+ }
1084
+ }
1085
+ }
1086
+ return $v;
1087
+ }
1088
+
1089
+ #------------------------------------------------------------------------------
1090
+ # Extract parameters from JPEG XL codestream [unverified!]
1091
+ # Inputs: 0) ExifTool ref, 1) codestream ref
1092
+ # Returns: 1
1093
+ sub ProcessJXLCodestream($$)
1094
+ {
1095
+ my ($et, $dataPt) = @_;
1096
+ # add padding if necessary to avoid unpacking past end of data
1097
+ if (length $$dataPt < 14) {
1098
+ my $tmp = $$dataPt . ("\0" x 14);
1099
+ $dataPt = \$tmp;
1100
+ }
1101
+ my @a = unpack 'x2C12', $$dataPt;
1102
+ my ($x, $y);
1103
+ my $small = GetBits(\@a, 1);
1104
+ if ($small) {
1105
+ $y = (GetBits(\@a, 5) + 1) * 8;
1106
+ } else {
1107
+ $y = GetBits(\@a, [9, 13, 18, 30]->[GetBits(\@a, 2)]) + 1;
1108
+ }
1109
+ my $ratio = GetBits(\@a, 3);
1110
+ if ($ratio == 0) {
1111
+ if ($small) {
1112
+ $x = (GetBits(\@a, 5) + 1) * 8;;
1113
+ } else {
1114
+ $x = GetBits(\@a, [9, 13, 18, 30]->[GetBits(\@a, 2)]) + 1;
1115
+ }
1116
+ } else {
1117
+ my $r = [[1,1],[12,10],[4,3],[3,2],[16,9],[5,4],[2,1]]->[$ratio-1];
1118
+ $x = int($y * $$r[0] / $$r[1]);
1119
+ }
1120
+ $et->FoundTag(ImageWidth => $x);
1121
+ $et->FoundTag(ImageHeight => $y);
1122
+ return 1;
1123
+ }
1124
+
806
1125
  #------------------------------------------------------------------------------
807
1126
  # Read/write meta information from a JPEG 2000 image
808
1127
  # Inputs: 0) ExifTool object reference, 1) dirInfo reference
@@ -817,8 +1136,9 @@ sub ProcessJP2($$)
817
1136
 
818
1137
  # check to be sure this is a valid JPG2000 file
819
1138
  return 0 unless $raf->Read($hdr,12) == 12;
820
- unless ($hdr eq "\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a" or # (ref 1)
821
- $hdr eq "\x00\x00\x00\x0cjP\x1a\x1a\x0d\x0a\x87\x0a") # (ref 2)
1139
+ unless ($hdr eq "\0\0\0\x0cjP \x0d\x0a\x87\x0a" or # (ref 1)
1140
+ $hdr eq "\0\0\0\x0cjP\x1a\x1a\x0d\x0a\x87\x0a" or # (ref 2)
1141
+ $$et{IsJXL})
822
1142
  {
823
1143
  return 0 unless $hdr =~ /^\xff\x4f\xff\x51\0/; # check for JP2 codestream format
824
1144
  if ($outfile) {
@@ -835,17 +1155,23 @@ sub ProcessJP2($$)
835
1155
  }
836
1156
  if ($outfile) {
837
1157
  Write($outfile, $hdr) or return -1;
838
- $et->InitWriteDirs(\%jp2Map);
1158
+ if ($$et{IsJXL}) {
1159
+ $et->InitWriteDirs(\%jxlMap);
1160
+ $$et{AddJp2Tags} = { }; # (don't add JP2 tags in JXL files)
1161
+ } else {
1162
+ $et->InitWriteDirs(\%jp2Map);
1163
+ $$et{AddJp2Tags} = $et->GetNewTagInfoHash(\%Image::ExifTool::Jpeg2000::Main);
1164
+ }
839
1165
  # save list of directories to create
840
- my %addDirs = %{$$et{ADD_DIRS}};
1166
+ my %addDirs = %{$$et{ADD_DIRS}}; # (make a copy)
841
1167
  $$et{AddJp2Dirs} = \%addDirs;
842
- $$et{AddJp2Tags} = $et->GetNewTagInfoHash(\%Image::ExifTool::Jpeg2000::Main);
843
1168
  } else {
844
1169
  my ($buff, $fileType);
845
1170
  # recognize JPX and JPM as unique types of JP2
846
1171
  if ($raf->Read($buff, 12) == 12 and $buff =~ /^.{4}ftyp(.{4})/s) {
847
1172
  $fileType = 'JPX' if $1 eq 'jpx ';
848
1173
  $fileType = 'JPM' if $1 eq 'jpm ';
1174
+ $fileType = 'JXL' if $1 eq 'jxl ';
849
1175
  }
850
1176
  $raf->Seek(-length($buff), 1) if defined $buff;
851
1177
  $et->SetFileType($fileType);
@@ -860,6 +1186,59 @@ sub ProcessJP2($$)
860
1186
  return $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
861
1187
  }
862
1188
 
1189
+ #------------------------------------------------------------------------------
1190
+ # Read meta information from a JPEG XL image
1191
+ # Inputs: 0) ExifTool object reference, 1) dirInfo reference
1192
+ # Returns: 1 on success, 0 if this wasn't a valid JPEG XL file, -1 on write error
1193
+ sub ProcessJXL($$)
1194
+ {
1195
+ my ($et, $dirInfo) = @_;
1196
+ my $raf = $$dirInfo{RAF};
1197
+ my $outfile = $$dirInfo{OutFile};
1198
+ my ($hdr, $buff);
1199
+
1200
+ return 0 unless $raf->Read($hdr,12) == 12;
1201
+ if ($hdr eq "\0\0\0\x0cJXL \x0d\x0a\x87\x0a") {
1202
+ # JPEG XL in ISO BMFF container
1203
+ $$et{IsJXL} = 1;
1204
+ } elsif ($hdr =~ /^\xff\x0a/) {
1205
+ # JPEG XL codestream
1206
+ if ($outfile) {
1207
+ if ($$et{OPTIONS}{IgnoreMinorErrors}) {
1208
+ $et->Warn('Wrapped JXL codestream in ISO BMFF container');
1209
+ } else {
1210
+ $et->Error('Will wrap JXL codestream in ISO BMFF container for writing',1);
1211
+ return 0;
1212
+ }
1213
+ $$et{IsJXL} = 2;
1214
+ my $buff = "\0\0\0\x0cJXL \x0d\x0a\x87\x0a\0\0\0\x14ftypjxl \0\0\0\0jxl ";
1215
+ # add metadata to empty ISO BMFF container
1216
+ $$dirInfo{RAF} = new File::RandomAccess(\$buff);
1217
+ } else {
1218
+ $et->SetFileType('JXL Codestream','image/jxl', 'jxl');
1219
+ return ProcessJXLCodestream($et, \$hdr);
1220
+ }
1221
+ } else {
1222
+ return 0;
1223
+ }
1224
+ $raf->Seek(0,0) or $et->Error('Seek error'), return 0;
1225
+
1226
+ my $success = ProcessJP2($et, $dirInfo);
1227
+
1228
+ if ($outfile and $success > 0 and $$et{IsJXL} == 2) {
1229
+ # attach the JXL codestream box to the ISO BMFF file
1230
+ $raf->Seek(0,2) or return -1;
1231
+ my $size = $raf->Tell();
1232
+ $raf->Seek(0,0) or return -1;
1233
+ SetByteOrder('MM');
1234
+ Write($outfile, Set32u($size + 8), 'jxlc') or return -1;
1235
+ while ($raf->Read($buff, 65536)) {
1236
+ Write($outfile, $buff) or return -1;
1237
+ }
1238
+ }
1239
+ return $success;
1240
+ }
1241
+
863
1242
  1; # end
864
1243
 
865
1244
  __END__
@@ -0,0 +1,153 @@
1
+ #------------------------------------------------------------------------------
2
+ # File: LIF.pm
3
+ #
4
+ # Description: Read LIF (Leica Image File) files
5
+ #
6
+ # Revisions: 2021-06-21 - P. Harvey Created
7
+ #------------------------------------------------------------------------------
8
+
9
+ package Image::ExifTool::LIF;
10
+
11
+ use strict;
12
+ use vars qw($VERSION);
13
+ use Image::ExifTool qw(:DataAccess :Utils);
14
+ use Image::ExifTool::XMP;
15
+
16
+ $VERSION = '1.00';
17
+
18
+ %Image::ExifTool::LIF::Main = (
19
+ GROUPS => { 0 => 'XML', 1 => 'XML', 2 => 'Image' },
20
+ PROCESS_PROC => \&Image::ExifTool::XMP::ProcessXMP,
21
+ VARS => { NO_ID => 1 },
22
+ NOTES => q{
23
+ Tags extracted from Leica Image Format (LIF) imaging files. As well as the
24
+ tags listed below, all available information is extracted from the
25
+ XML-format metadata in the LIF header.
26
+ },
27
+ TimeStampList => {
28
+ Groups => { 2 => 'Time' },
29
+ ValueConv => q{
30
+ my $unixTimeZero = 134774 * 24 * 3600;
31
+ my @vals = split ' ', $val;
32
+ foreach (@vals) {
33
+ $_ = 1e-7 * hex($_);
34
+ # shift from Jan 1, 1601 to Jan 1, 1970
35
+ $_ = Image::ExifTool::ConvertUnixTime($_ - $unixTimeZero);
36
+ }
37
+ return \@vals;
38
+ },
39
+ },
40
+ );
41
+
42
+ #------------------------------------------------------------------------------
43
+ # Shorten obscenely long LIF tag names
44
+ # Inputs: Tag name
45
+ # Returns: Shortened tag name
46
+ sub ShortenTagNames($)
47
+ {
48
+ local $_;
49
+ $_ = shift;
50
+ s/DescriptionDimensionsDimensionDescription/Dimensions/;
51
+ s/DescriptionChannelsChannelDescription/Channel/;
52
+ s/ShutterListShutter/Shutter/;
53
+ s/SettingDefinition/Setting/;
54
+ s/AdditionalZPositionListAdditionalZPosition/AdditionalZPosition/;
55
+ s/LMSDataContainerHeader//g;
56
+ s/FilterWheelWheel/FilterWheel/;
57
+ s/FilterWheelFilter/FilterWheel/;
58
+ s/DetectorListDetector/Detector/;
59
+ s/OnlineDyeSeparationOnlineDyeSeparation/OnlineDyeSeparation/;
60
+ s/AotfListAotf/Aotf/;
61
+ s/SettingAotfLaserLineSetting/SettingAotfLaser/;
62
+ s/DataROISetROISet/DataROISet/;
63
+ s/AdditionalZPosition/AddZPos/;
64
+ s/FRAPplusBlock_FRAPBlock_FRAP_PrePost_Info/FRAP_/;
65
+ s/FRAPplusBlock_FRAPBlock_FRAP_(Master)?/FRAP_/;
66
+ s/LDM_Block_SequentialLDM_Block_Sequential_/LDM_/;
67
+ s/ATLConfocalSetting/ATLConfocal/;
68
+ s/LaserArrayLaser/Laser/;
69
+ s/LDM_Master/LDM_/;
70
+ s/(List)?ATLConfocal/ATL_/;
71
+ s/Separation/Sep/;
72
+ s/BleachPointsElement/BleachPoint/;
73
+ s/BeamPositionBeamPosition/BeamPosition/;
74
+ s/DataROISetPossible(ROI)?/DataROISet/;
75
+ s/RoiElementChildrenElementDataROISingle(Roi)?/Roi/;
76
+ s/InfoLaserLineSettingArrayLaserLineSetting/LastLineSetting/;
77
+ s/FilterWheelWheelNameFilterName/FilterWheelFilterName/;
78
+ s/LUT_ListLut/Lut/;
79
+ s/ROI_ListRoiRoidata/ROI_/;
80
+ s/LaserLineSettingArrayLaserLineSetting/LaserLineSetting/;
81
+ return $_;
82
+ }
83
+
84
+ #------------------------------------------------------------------------------
85
+ # Extract metadata from a LIF image
86
+ # Inputs: 0) ExifTool object reference, 1) dirInfo reference
87
+ # Returns: 1 on success, 0 if this wasn't a valid LIF file
88
+ sub ProcessLIF($$)
89
+ {
90
+ my ($et, $dirInfo) = @_;
91
+ my $raf = $$dirInfo{RAF};
92
+ my $buff;
93
+
94
+ # verify this is a valid LIF file
95
+ return 0 unless $raf->Read($buff, 15) == 15 and $buff =~ /^\x70\0{3}.{4}\x2a.{4}<\0/s;
96
+
97
+ $et->SetFileType();
98
+ SetByteOrder('II');
99
+
100
+ my $size = Get32u(\$buff, 4); # XML chunk size
101
+ my $len = Get32u(\$buff, 9) * 2; # XML data length
102
+
103
+ $size < $len and $et->Error('Corrupted LIF XML block'), return 1;
104
+ $size > 100000000 and $et->Error('LIF XML block too large'), return 1;
105
+
106
+ $raf->Seek(-2, 1) and $raf->Read($buff, $len) == $len or $et->Error('Truncated LIF XML block'), return 1;
107
+
108
+ my $tagTablePtr = GetTagTable('Image::ExifTool::LIF::Main');
109
+
110
+ # convert from UCS2 to UTF8
111
+ my $xml = Image::ExifTool::Decode($et, $buff, 'UCS2', 'II', 'UTF8');
112
+
113
+ my %dirInfo = ( DataPt => \$xml );
114
+
115
+ $$et{XmpIgnoreProps} = [ 'LMSDataContainerHeader', 'Element', 'Children', 'Data', 'Image', 'Attachment' ];
116
+ $$et{ShortenXmpTags} = \&ShortenTagNames;
117
+
118
+ $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
119
+
120
+ return 1;
121
+ }
122
+
123
+ 1; # end
124
+
125
+ __END__
126
+
127
+ =head1 NAME
128
+
129
+ Image::ExifTool::LIF - Read LIF meta information
130
+
131
+ =head1 SYNOPSIS
132
+
133
+ This module is used by Image::ExifTool
134
+
135
+ =head1 DESCRIPTION
136
+
137
+ This module contains definitions required by Image::ExifTool to read
138
+ metadata from Leica Image File (LIF) images.
139
+
140
+ =head1 AUTHOR
141
+
142
+ Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
143
+
144
+ This library is free software; you can redistribute it and/or modify it
145
+ under the same terms as Perl itself.
146
+
147
+ =head1 SEE ALSO
148
+
149
+ L<Image::ExifTool::TagNames/LIF Tags>,
150
+ L<Image::ExifTool(3pm)|Image::ExifTool>
151
+
152
+ =cut
153
+