exiftool_vendored 12.16.0 → 12.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +137 -1
  3. data/bin/MANIFEST +12 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +44 -43
  7. data/bin/config_files/acdsee.config +193 -6
  8. data/bin/config_files/cuepointlist.config +70 -0
  9. data/bin/config_files/example.config +1 -8
  10. data/bin/exiftool +139 -98
  11. data/bin/fmt_files/gpx.fmt +1 -1
  12. data/bin/fmt_files/gpx_wpt.fmt +1 -1
  13. data/bin/fmt_files/kml.fmt +1 -1
  14. data/bin/fmt_files/kml_track.fmt +1 -1
  15. data/bin/lib/Image/ExifTool.pm +158 -49
  16. data/bin/lib/Image/ExifTool.pod +94 -75
  17. data/bin/lib/Image/ExifTool/Apple.pm +3 -2
  18. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +25 -14
  19. data/bin/lib/Image/ExifTool/Canon.pm +28 -3
  20. data/bin/lib/Image/ExifTool/CanonCustom.pm +19 -1
  21. data/bin/lib/Image/ExifTool/DJI.pm +6 -6
  22. data/bin/lib/Image/ExifTool/DjVu.pm +6 -5
  23. data/bin/lib/Image/ExifTool/Exif.pm +50 -22
  24. data/bin/lib/Image/ExifTool/FITS.pm +13 -2
  25. data/bin/lib/Image/ExifTool/FujiFilm.pm +19 -8
  26. data/bin/lib/Image/ExifTool/GPS.pm +24 -13
  27. data/bin/lib/Image/ExifTool/H264.pm +20 -5
  28. data/bin/lib/Image/ExifTool/ICC_Profile.pm +2 -2
  29. data/bin/lib/Image/ExifTool/JPEG.pm +6 -2
  30. data/bin/lib/Image/ExifTool/JSON.pm +24 -3
  31. data/bin/lib/Image/ExifTool/Jpeg2000.pm +361 -16
  32. data/bin/lib/Image/ExifTool/M2TS.pm +40 -4
  33. data/bin/lib/Image/ExifTool/MIE.pm +2 -2
  34. data/bin/lib/Image/ExifTool/MRC.pm +341 -0
  35. data/bin/lib/Image/ExifTool/MWG.pm +3 -3
  36. data/bin/lib/Image/ExifTool/MXF.pm +1 -1
  37. data/bin/lib/Image/ExifTool/MacOS.pm +1 -1
  38. data/bin/lib/Image/ExifTool/Microsoft.pm +298 -82
  39. data/bin/lib/Image/ExifTool/Nikon.pm +5 -5
  40. data/bin/lib/Image/ExifTool/NikonSettings.pm +25 -16
  41. data/bin/lib/Image/ExifTool/Olympus.pm +2 -2
  42. data/bin/lib/Image/ExifTool/PNG.pm +2 -2
  43. data/bin/lib/Image/ExifTool/Panasonic.pm +14 -1
  44. data/bin/lib/Image/ExifTool/PhaseOne.pm +4 -3
  45. data/bin/lib/Image/ExifTool/QuickTime.pm +148 -68
  46. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +94 -34
  47. data/bin/lib/Image/ExifTool/README +5 -4
  48. data/bin/lib/Image/ExifTool/RIFF.pm +84 -12
  49. data/bin/lib/Image/ExifTool/Samsung.pm +2 -1
  50. data/bin/lib/Image/ExifTool/Shortcuts.pm +9 -0
  51. data/bin/lib/Image/ExifTool/Sony.pm +157 -49
  52. data/bin/lib/Image/ExifTool/TagInfoXML.pm +1 -0
  53. data/bin/lib/Image/ExifTool/TagLookup.pm +4079 -3987
  54. data/bin/lib/Image/ExifTool/TagNames.pod +642 -273
  55. data/bin/lib/Image/ExifTool/WriteExif.pl +1 -1
  56. data/bin/lib/Image/ExifTool/WritePostScript.pl +1 -0
  57. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +44 -17
  58. data/bin/lib/Image/ExifTool/WriteXMP.pl +15 -8
  59. data/bin/lib/Image/ExifTool/Writer.pl +50 -14
  60. data/bin/lib/Image/ExifTool/XMP.pm +50 -11
  61. data/bin/perl-Image-ExifTool.spec +42 -42
  62. data/lib/exiftool_vendored/version.rb +1 -1
  63. metadata +52 -12
@@ -89,7 +89,7 @@ my $parsePictureTiming; # flag to enable parsing of picture timing information (
89
89
  Notes => 'hours:minutes:seconds:frames',
90
90
  ValueConv => 'sprintf("%.2x:%.2x:%.2x:%.2x",reverse unpack("C*",$val))',
91
91
  },
92
- # 0x14 - TitleBinaryGroup
92
+ # 0x14 - TitleBinaryGroup - val: 0x00000000,0x14200130
93
93
  # 0x15 - TitleCassetteNo (ref 7)
94
94
  # 0x16-0x17 - TitleSoftID (ref 7)
95
95
  # (0x18,0x19 listed as TitleTextHeader/TitleText by ref 7)
@@ -134,8 +134,8 @@ my $parsePictureTiming; # flag to enable parsing of picture timing information (
134
134
  Name => 'Camera2',
135
135
  SubDirectory => { TagTable => 'Image::ExifTool::H264::Camera2' },
136
136
  },
137
- # 0x73 Lens - val: 0x75ffffd3,0x0effffd3,0x59ffffd3,0x79ffffd3,0xffffffd3...
138
- # 0x74 Gain
137
+ # 0x73 Lens - val: 0x04ffffd3,0x0effffd3,0x15ffffd3,0x41ffffd3,0x52ffffd3,0x59ffffd3,0x65ffffd3,0x71ffffd3,0x75ffffd3,0x79ffffd3,0x7fffffd3,0xffffffd3...
138
+ # 0x74 Gain - val: 0xb8ffff0f
139
139
  # 0x75 Pedestal
140
140
  # 0x76 Gamma
141
141
  # 0x77 Detail
@@ -376,34 +376,49 @@ my $parsePictureTiming; # flag to enable parsing of picture timing information (
376
376
  Notes => 'combined with tag 0xc8',
377
377
  },
378
378
  # 0xc9-0xcf - GPSOption
379
+ # 0xc9 - val: 0x001d0203
380
+ 0xca => { #PH (Sony DSC-HX7V)
381
+ Name => 'GPSDateStamp',
382
+ Format => 'string',
383
+ Groups => { 1 => 'GPS', 2 => 'Time' },
384
+ Combine => 2, # the next 2 tags contain the rest of the string
385
+ Notes => 'combined with tags 0xcb and 0xcc',
386
+ ValueConv => 'Image::ExifTool::Exif::ExifDate($val)',
387
+ },
379
388
  0xe0 => {
380
389
  Name => 'MakeModel',
381
390
  SubDirectory => { TagTable => 'Image::ExifTool::H264::MakeModel' },
382
391
  },
383
392
  # 0xe1-0xef - MakerOption
384
393
  # 0xe1 - val: 0x01000670,0x01000678,0x06ffffff,0x01ffffff,0x01000020,0x01000400...
385
- # 0xe2-0xe8 - val: 0x00000000 in many samples
386
394
  0xe1 => { #6
387
395
  Name => 'RecInfo',
388
396
  Condition => '$$self{Make} eq "Canon"',
389
397
  Notes => 'Canon only',
390
398
  SubDirectory => { TagTable => 'Image::ExifTool::H264::RecInfo' },
391
399
  },
400
+ # 0xe2-0xe8 - val: 0x00000000 in many samples
401
+ # 0xe2 - val: 0x00000000,0x01000000,0x01010000,0x8080900c,0x8080a074
402
+ # 0xe3 - val: 0x00801f89,0x00801f8b,0x00c01f89,0xc9c01f80
392
403
  0xe4 => { #PH
393
404
  Name => 'Model',
394
405
  Condition => '$$self{Make} eq "Sony"', # (possibly also Canon models?)
395
406
  Description => 'Camera Model Name',
396
- Notes => 'Sony cameras only, combined with tags 0xe5 and 0xe6',
407
+ Notes => 'Sony only, combined with tags 0xe5 and 0xe6',
397
408
  Format => 'string',
398
409
  Combine => 2, # (not sure about 0xe6, but include it just in case)
399
410
  RawConv => '$val eq "" ? undef : $val',
400
411
  },
412
+ # 0xeb - val: 0x008a0a00,0x0a300000,0x508a0a00,0x52880a00,0x528a0a00
413
+ # 0xec - val: 0x0b700000
414
+ # 0xed - val: 0x0ce0f819
401
415
  0xee => { #6 (HFS200)
402
416
  Name => 'FrameInfo',
403
417
  Condition => '$$self{Make} eq "Canon"',
404
418
  Notes => 'Canon only',
405
419
  SubDirectory => { TagTable => 'Image::ExifTool::H264::FrameInfo' },
406
420
  },
421
+ # 0xef - val: 0x01c00000,0x0e00000c
407
422
  );
408
423
 
409
424
  # ConsumerCamera1 information (ref PH)
@@ -24,7 +24,7 @@ use strict;
24
24
  use vars qw($VERSION);
25
25
  use Image::ExifTool qw(:DataAccess :Utils);
26
26
 
27
- $VERSION = '1.35';
27
+ $VERSION = '1.36';
28
28
 
29
29
  sub ProcessICC($$);
30
30
  sub ProcessICC_Profile($$$);
@@ -1225,7 +1225,7 @@ sub ProcessICC_Profile($$$)
1225
1225
  my $type = substr($$dataPt, $valuePtr, 4);
1226
1226
  #### eval Validate ($type)
1227
1227
  if (defined $$subdir{Validate} and not eval $$subdir{Validate}) {
1228
- $et->Warn("Invalid $name data");
1228
+ $et->Warn("Invalid ICC $name data");
1229
1229
  } else {
1230
1230
  $et->ProcessDirectory(\%subdirInfo, $newTagTable, $$subdir{ProcessProc});
1231
1231
  }
@@ -193,11 +193,15 @@ sub ProcessJPEG_HDR($$$);
193
193
  Condition => '$$valPt =~ /^UNICODE\0/',
194
194
  Notes => 'PhotoStudio Unicode comment',
195
195
  },
196
- APP11 => {
196
+ APP11 => [{
197
197
  Name => 'JPEG-HDR',
198
198
  Condition => '$$valPt =~ /^HDR_RI /',
199
199
  SubDirectory => { TagTable => 'Image::ExifTool::JPEG::HDR' },
200
- },
200
+ },{
201
+ Name => 'JUMBF',
202
+ Condition => '$$valPt =~ /^JP/',
203
+ SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::Main' },
204
+ }],
201
205
  APP12 => [{
202
206
  Name => 'PictureInfo',
203
207
  Condition => '$$valPt =~ /(\[picture info\]|Type=)/',
@@ -14,13 +14,15 @@ use vars qw($VERSION);
14
14
  use Image::ExifTool qw(:DataAccess :Utils);
15
15
  use Image::ExifTool::Import;
16
16
 
17
- $VERSION = '1.02';
17
+ $VERSION = '1.03';
18
18
 
19
+ sub ProcessJSON($$);
19
20
  sub ProcessTag($$$$%);
20
21
 
21
22
  %Image::ExifTool::JSON::Main = (
22
23
  GROUPS => { 0 => 'JSON', 1 => 'JSON', 2 => 'Other' },
23
24
  VARS => { NO_ID => 1 },
25
+ PROCESS_PROC => \&ProcessJSON,
24
26
  NOTES => q{
25
27
  Other than a few tags in the table below, JSON tags have not been
26
28
  pre-defined. However, ExifTool will read any existing tags from basic
@@ -103,7 +105,26 @@ sub ProcessJSON($$)
103
105
  my ($et, $dirInfo) = @_;
104
106
  my $raf = $$dirInfo{RAF};
105
107
  my $structOpt = $et->Options('Struct');
106
- my (%database, $key, $tag);
108
+ my (%database, $key, $tag, $dataPt);
109
+
110
+ unless ($raf) {
111
+ $dataPt = $$dirInfo{DataPt};
112
+ if ($$dirInfo{DirStart} or ($$dirInfo{DirLen} and $$dirInfo{DirLen} ne length($$dataPt))) {
113
+ my $buff = substr(${$$dirInfo{DataPt}}, $$dirInfo{DirStart}, $$dirInfo{DirLen});
114
+ $dataPt = \$buff;
115
+ }
116
+ $raf = new File::RandomAccess($dataPt);
117
+ # extract as a block if requested
118
+ my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : '';
119
+ my $blockExtract = $et->Options('BlockExtract');
120
+ if ($blockName and ($blockExtract or $$et{REQ_TAG_LOOKUP}{lc $blockName} or
121
+ ($$et{TAGS_FROM_FILE} and not $$et{EXCL_TAG_LOOKUP}{lc $blockName})))
122
+ {
123
+ $et->FoundTag($$dirInfo{BlockInfo}, $$dataPt);
124
+ return 1 if $blockExtract and $blockExtract > 1;
125
+ }
126
+ $et->VerboseDir('JSON');
127
+ }
107
128
 
108
129
  # read information from JSON file into database structure
109
130
  my $err = Image::ExifTool::Import::ReadJSON($raf, \%database,
@@ -111,7 +132,7 @@ sub ProcessJSON($$)
111
132
 
112
133
  return 0 if $err or not %database;
113
134
 
114
- $et->SetFileType();
135
+ $et->SetFileType() unless $dataPt;
115
136
 
116
137
  my $tagTablePtr = GetTagTable('Image::ExifTool::JSON::Main');
117
138
 
@@ -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.29';
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',
@@ -99,6 +116,9 @@ my %j2cMarker = (
99
116
  0x76 => 'NLT', # non-linearity point transformation
100
117
  );
101
118
 
119
+ my %jumbfTypes = (
120
+ );
121
+
102
122
  # JPEG 2000 "box" (ie. atom) names
103
123
  # Note: only tags with a defined "Format" are extracted
104
124
  %Image::ExifTool::Jpeg2000::Main = (
@@ -107,8 +127,9 @@ my %j2cMarker = (
107
127
  WRITE_PROC => \&ProcessJpeg2000Box,
108
128
  PREFERRED => 1, # always add these tags when writing
109
129
  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.
130
+ The tags below are extracted from JPEG 2000 images and the JUMBF metadata in
131
+ JPEG images. Note that ExifTool currently writes only EXIF, IPTC and XMP
132
+ tags in Jpeg2000 images.
112
133
  },
113
134
  'jP ' => 'JP2Signature', # (ref 1)
114
135
  "jP\x1a\x1a" => 'JP2Signature', # (ref 2)
@@ -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,12 @@ my %j2cMarker = (
302
338
  Start => '$valuePtr + 16',
303
339
  },
304
340
  },
341
+ {
342
+ Name => 'UUID-Signature', # (seen in JUMB data of JPEG images)
343
+ Condition => '$$valPt=~/^casg\x00\x11\x00\x10\x80\x00\x00\xaa\x00\x38\x9b\x71/',
344
+ Format => 'undef',
345
+ ValueConv => 'substr($val,16)',
346
+ },
305
347
  {
306
348
  Name => 'UUID-Unknown',
307
349
  },
@@ -321,6 +363,52 @@ my %j2cMarker = (
321
363
  Name => 'URL',
322
364
  Format => 'string',
323
365
  },
366
+ # JUMBF boxes (ref https://github.com/thorfdbg/codestream-parser)
367
+ jumd => {
368
+ Name => 'JUMBFDescr',
369
+ SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::JUMD' },
370
+ },
371
+ jumb => {
372
+ Name => 'JUMBFBox',
373
+ SubDirectory => {
374
+ TagTable => 'Image::ExifTool::Jpeg2000::Main',
375
+ ProcessProc => \&ProcessJUMB,
376
+ },
377
+ },
378
+ json => {
379
+ Name => 'JSONData',
380
+ Flags => [ 'Binary', 'Protected', 'BlockExtract' ],
381
+ Notes => q{
382
+ by default, data in this tag is parsed using the ExifTool JSON module to to
383
+ allow individual tags to be accessed when reading, but it may also be
384
+ extracted as a block via the "JSONData" tag or by setting the API
385
+ BlockExtract option
386
+ },
387
+ SubDirectory => { TagTable => 'Image::ExifTool::JSON::Main' },
388
+ },
389
+ #
390
+ # stuff seen in JPEG XL images:
391
+ #
392
+ # jbrd - JPEG Bitstream Reconstruction Data (allows lossless conversion back to original JPG)
393
+ jxlc => {
394
+ Name => 'JXLCodestream',
395
+ Format => 'undef',
396
+ Notes => q{
397
+ Codestream in JPEG XL image. Currently processed only to determine
398
+ ImageSize
399
+ },
400
+ RawConv => 'Image::ExifTool::Jpeg2000::ProcessJXLCodestream($self,\$val); undef',
401
+ },
402
+ Exif => {
403
+ Name => 'EXIF',
404
+ SubDirectory => {
405
+ TagTable => 'Image::ExifTool::Exif::Main',
406
+ ProcessProc => \&Image::ExifTool::ProcessTIFF,
407
+ WriteProc => \&Image::ExifTool::WriteTIFF,
408
+ DirName => 'EXIF',
409
+ Start => '$valuePtr + 4',
410
+ },
411
+ },
324
412
  );
325
413
 
326
414
  %Image::ExifTool::Jpeg2000::ImageHeader = (
@@ -375,6 +463,7 @@ my %j2cMarker = (
375
463
  'jp2 ' => 'JPEG 2000 Image (.JP2)', # image/jp2
376
464
  'jpm ' => 'JPEG 2000 Compound Image (.JPM)', # image/jpm
377
465
  'jpx ' => 'JPEG 2000 with extensions (.JPX)', # image/jpx
466
+ 'jxl ' => 'JPEG XL Image (.JXL)', # image/jxl
378
467
  },
379
468
  },
380
469
  1 => {
@@ -513,6 +602,112 @@ my %j2cMarker = (
513
602
  ],
514
603
  );
515
604
 
605
+ # JUMBF description box
606
+ %Image::ExifTool::Jpeg2000::JUMD = (
607
+ PROCESS_PROC => \&ProcessJUMD,
608
+ GROUPS => { 0 => 'JUMBF', 1 => 'JUMBF', 2 => 'Image' },
609
+ NOTES => 'Information extracted from the JUMBF description box.',
610
+ 'jumd-type' => {
611
+ Name => 'JUMDType',
612
+ ValueConv => 'unpack "H*", $val',
613
+ PrintConv => q{
614
+ my @a = $val =~ /^(\w{8})(\w{4})(\w{4})(\w{16})$/;
615
+ return $val unless @a;
616
+ my $ascii = pack 'H*', $a[0];
617
+ $a[0] = $ascii if $ascii =~ /^[a-zA-Z0-9]{4}$/;
618
+ return join '-', @a;
619
+ },
620
+ # seen:
621
+ # cacb/cast/caas/cacl/casg/json-00110010800000aa00389b71
622
+ # 6579d6fbdba2446bb2ac1b82feeb89d1 - JPEG image
623
+ },
624
+ 'jumd-label' => { Name => 'JUMDLabel' },
625
+ 'jumd-flags' => {
626
+ Name => 'JUMDFlags',
627
+ PrintConv => { BITMASK => {
628
+ 0 => 'Requestable',
629
+ 1 => 'Label',
630
+ 2 => 'ID',
631
+ 3 => 'Signature',
632
+ }},
633
+ },
634
+ 'jumd-id' => { Name => 'JUMDID', Description => 'JUMD ID' },
635
+ 'jumd-sig' => { Name => 'JUMDSignature', PrintConv => 'unpack "H*", $val' },
636
+ );
637
+
638
+ #------------------------------------------------------------------------------
639
+ # Read JUMBF box to keep track of sub-document numbers
640
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
641
+ # Returns: 1 on success
642
+ sub ProcessJUMB($$$)
643
+ {
644
+ my ($et, $dirInfo, $tagTablePtr) = @_;
645
+ if ($$et{jumd_level}) {
646
+ ++$$et{jumd_level}[-1]; # increment current sub-document number
647
+ } else {
648
+ $$et{jumd_level} = [ ++$$et{DOC_COUNT} ]; # new top-level sub-document
649
+ $$et{SET_GROUP0} = 'JUMBF';
650
+ }
651
+ $$et{DOC_NUM} = join '-', @{$$et{jumd_level}};
652
+ push @{$$et{jumd_level}}, 0;
653
+ ProcessJpeg2000Box($et, $dirInfo, $tagTablePtr);
654
+ delete $$et{DOC_NUM};
655
+ delete $$et{JUMBFLabel};
656
+ pop @{$$et{jumd_level}};
657
+ if (@{$$et{jumd_level}} < 2) {
658
+ delete $$et{jumd_level};
659
+ delete $$et{SET_GROUP0};
660
+ }
661
+ return 1;
662
+ }
663
+
664
+ #------------------------------------------------------------------------------
665
+ # Read JUMBF description box (ref https://github.com/thorfdbg/codestream-parser)
666
+ # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
667
+ # Returns: 1 on success
668
+ sub ProcessJUMD($$$)
669
+ {
670
+ my ($et, $dirInfo, $tagTablePtr) = @_;
671
+ my $dataPt = $$dirInfo{DataPt};
672
+ my $pos = $$dirInfo{DirStart};
673
+ my $end = $pos + $$dirInfo{DirLen};
674
+ $et->VerboseDir('JUMD', 0, $end-$pos);
675
+ delete $$et{JUMBFLabel};
676
+ $$dirInfo{DirLen} < 17 and $et->Warn('Truncated JUMD directory'), return 0;
677
+ my $type = substr($$dataPt, $pos, 4);
678
+ $et->HandleTag($tagTablePtr, 'jumd-type', substr($$dataPt, $pos, 16));
679
+ $pos += 16;
680
+ my $flags = Get8u($dataPt, $pos++);
681
+ $et->HandleTag($tagTablePtr, 'jumd-flags', $flags);
682
+ if ($flags & 0x02) { # label exists?
683
+ pos($$dataPt) = $pos;
684
+ $$dataPt =~ /\0/g or $et->Warn('Missing JUMD label terminator'), return 0;
685
+ my $len = pos($$dataPt) - $pos;
686
+ my $name = substr($$dataPt, $pos, $len);
687
+ $et->HandleTag($tagTablePtr, 'jumd-label', $name);
688
+ $pos += $len;
689
+ if ($len) {
690
+ $name =~ s/[^-_a-zA-Z0-9]([a-z])/\U$1/g; # capitalize characters after illegal characters
691
+ $name =~ tr/-_a-zA-Z0-9//dc; # remove other illegal characters
692
+ $name = ucfirst $name; # capitalize first letter
693
+ $name = "Tag$name" if length($name) < 2; # must at least 2 characters long
694
+ $$et{JUMBFLabel} = $name;
695
+ }
696
+ }
697
+ if ($flags & 0x04) { # ID exists?
698
+ $pos + 4 > $end and $et->Warn('Missing JUMD ID'), return 0;
699
+ $et->HandleTag($tagTablePtr, 'jumd-id', Get32u($dataPt, $pos));
700
+ $pos += 4;
701
+ }
702
+ if ($flags & 0x08) { # signature exists?
703
+ $pos + 32 > $end and $et->Warn('Missing JUMD signature'), return 0;
704
+ $et->HandleTag($tagTablePtr, 'jumd-sig', substr($$dataPt, $pos, 32));
705
+ $pos += 32;
706
+ }
707
+ $pos == $end or $et->Warn('Extra data in JUMD box'." $pos $end", 1);
708
+ return 1;
709
+ }
710
+
516
711
  #------------------------------------------------------------------------------
517
712
  # Create new JPEG 2000 boxes when writing
518
713
  # (Currently only supports adding top-level Writable JPEG2000 tags and certain UUID boxes)
@@ -542,8 +737,29 @@ sub CreateNewBoxes($$)
542
737
  $et->VerboseValue("+ Jpeg2000:$$tagInfo{Name}", $val);
543
738
  }
544
739
  }
545
- # add UUID boxes
740
+ # add UUID boxes (and/or JXL Exif/XML boxes)
546
741
  foreach $dirName (sort keys %$addDirs) {
742
+ # handle JPEG XL XMP and EXIF
743
+ if ($dirName eq 'XML' or $dirName eq 'Exif') {
744
+ my ($tag, $dir) = $dirName eq 'XML' ? ('xml ', 'XMP') : ('Exif', 'EXIF');
745
+ my $tagInfo = $Image::ExifTool::Jpeg2000::Main{$tag};
746
+ $tagInfo = $$tagInfo[1] if ref $tagInfo eq 'ARRAY'; # (hack for stupid JXL XMP)
747
+ my $subdir = $$tagInfo{SubDirectory};
748
+ my $tagTable = GetTagTable($$subdir{TagTable});
749
+ $tagTable = GetTagTable('Image::ExifTool::XMP::Main') if $dir eq 'XMP';
750
+ my %dirInfo = (
751
+ DirName => $dir,
752
+ Parent => 'JP2',
753
+ );
754
+ my $newdir = $et->WriteDirectory(\%dirInfo, $tagTable, $$subdir{WriteProc});
755
+ if (defined $newdir and length $newdir) {
756
+ # not sure why, but EXIF box is padded with leading 0's in my sample
757
+ my $pad = $dirName eq 'Exif' ? "\0\0\0\0" : '';
758
+ my $boxhdr = pack('N', length($newdir) + length($pad) + 8) . $tag;
759
+ Write($outfile, $boxhdr, $pad, $newdir) or return 0;
760
+ next;
761
+ }
762
+ }
547
763
  next unless $uuid{$dirName};
548
764
  my $tagInfo;
549
765
  foreach $tagInfo (@{$Image::ExifTool::Jpeg2000::Main{uuid}}) {
@@ -725,6 +941,13 @@ sub ProcessJpeg2000Box($$$)
725
941
  );
726
942
  next unless $tagInfo;
727
943
  }
944
+ # create new tag for JUMBF data values with name corresponding to JUMBFLabel
945
+ if ($$et{JUMBFLabel} and (not $$tagInfo{SubDirectory} or $$tagInfo{BlockExtract})) {
946
+ $tagInfo = { %$tagInfo, Name => $$et{JUMBFLabel} };
947
+ AddTagToTable($tagTablePtr, '_JUMBF_' . $$et{JUMBFLabel}, $tagInfo);
948
+ delete $$tagInfo{Protected}; # (must do this so -j -b returns JUMBF binary data)
949
+ $$tagInfo{TagID} = $boxID;
950
+ }
728
951
  if ($$tagInfo{SubDirectory}) {
729
952
  my $subdir = $$tagInfo{SubDirectory};
730
953
  my $subdirStart = $valuePtr;
@@ -752,8 +975,8 @@ sub ProcessJpeg2000Box($$$)
752
975
  # remove this directory from our create list
753
976
  delete $$et{AddJp2Dirs}{$$tagInfo{Name}};
754
977
  my $newdir;
755
- # only edit writable UUID boxes
756
- if ($uuid) {
978
+ # only edit writable UUID and Exif boxes
979
+ if ($uuid or $boxID eq 'Exif' or ($boxID eq 'xml ' and $$et{IsJXL})) {
757
980
  $newdir = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc});
758
981
  next if defined $newdir and not length $newdir; # next if deleting the box
759
982
  } elsif (defined $uuid) {
@@ -803,6 +1026,68 @@ sub ProcessJpeg2000Box($$$)
803
1026
  return 1;
804
1027
  }
805
1028
 
1029
+ #------------------------------------------------------------------------------
1030
+ # Return bits from a bitstream object
1031
+ # Inputs: 0) array ref, 1) number of bits
1032
+ # Returns: specified number of bits as an integer, and shifts input bitstream
1033
+ sub GetBits($$)
1034
+ {
1035
+ my ($a, $n) = @_;
1036
+ my $v = 0;
1037
+ my $bit = 1;
1038
+ my $i;
1039
+ while ($n--) {
1040
+ for ($i=0; $i<@$a; ++$i) {
1041
+ # consume bits LSB first
1042
+ my $set = $$a[$i] & 1;
1043
+ $$a[$i] >>= 1;
1044
+ if ($i) {
1045
+ $$a[$i-1] |= 0x80 if $set;
1046
+ } else {
1047
+ $v |= $bit if $set;
1048
+ $bit <<= 1;
1049
+ }
1050
+ }
1051
+ }
1052
+ return $v;
1053
+ }
1054
+
1055
+ #------------------------------------------------------------------------------
1056
+ # Extract parameters from JPEG XL codestream [unverified!]
1057
+ # Inputs: 0) ExifTool ref, 1) codestream ref
1058
+ # Returns: 1
1059
+ sub ProcessJXLCodestream($$)
1060
+ {
1061
+ my ($et, $dataPt) = @_;
1062
+ # add padding if necessary to avoid unpacking past end of data
1063
+ if (length $$dataPt < 14) {
1064
+ my $tmp = $$dataPt . ("\0" x 14);
1065
+ $dataPt = \$tmp;
1066
+ }
1067
+ my @a = unpack 'x2C12', $$dataPt;
1068
+ my ($x, $y);
1069
+ my $small = GetBits(\@a, 1);
1070
+ if ($small) {
1071
+ $y = (GetBits(\@a, 5) + 1) * 8;
1072
+ } else {
1073
+ $y = GetBits(\@a, [9, 13, 18, 30]->[GetBits(\@a, 2)]) + 1;
1074
+ }
1075
+ my $ratio = GetBits(\@a, 3);
1076
+ if ($ratio == 0) {
1077
+ if ($small) {
1078
+ $x = (GetBits(\@a, 5) + 1) * 8;;
1079
+ } else {
1080
+ $x = GetBits(\@a, [9, 13, 18, 30]->[GetBits(\@a, 2)]) + 1;
1081
+ }
1082
+ } else {
1083
+ my $r = [[1,1],[12,10],[4,3],[3,2],[16,9],[5,4],[2,1]]->[$ratio-1];
1084
+ $x = int($y * $$r[0] / $$r[1]);
1085
+ }
1086
+ $et->FoundTag(ImageWidth => $x);
1087
+ $et->FoundTag(ImageHeight => $y);
1088
+ return 1;
1089
+ }
1090
+
806
1091
  #------------------------------------------------------------------------------
807
1092
  # Read/write meta information from a JPEG 2000 image
808
1093
  # Inputs: 0) ExifTool object reference, 1) dirInfo reference
@@ -817,8 +1102,9 @@ sub ProcessJP2($$)
817
1102
 
818
1103
  # check to be sure this is a valid JPG2000 file
819
1104
  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)
1105
+ unless ($hdr eq "\0\0\0\x0cjP \x0d\x0a\x87\x0a" or # (ref 1)
1106
+ $hdr eq "\0\0\0\x0cjP\x1a\x1a\x0d\x0a\x87\x0a" or # (ref 2)
1107
+ $$et{IsJXL})
822
1108
  {
823
1109
  return 0 unless $hdr =~ /^\xff\x4f\xff\x51\0/; # check for JP2 codestream format
824
1110
  if ($outfile) {
@@ -835,17 +1121,23 @@ sub ProcessJP2($$)
835
1121
  }
836
1122
  if ($outfile) {
837
1123
  Write($outfile, $hdr) or return -1;
838
- $et->InitWriteDirs(\%jp2Map);
1124
+ if ($$et{IsJXL}) {
1125
+ $et->InitWriteDirs(\%jxlMap);
1126
+ $$et{AddJp2Tags} = { }; # (don't add JP2 tags in JXL files)
1127
+ } else {
1128
+ $et->InitWriteDirs(\%jp2Map);
1129
+ $$et{AddJp2Tags} = $et->GetNewTagInfoHash(\%Image::ExifTool::Jpeg2000::Main);
1130
+ }
839
1131
  # save list of directories to create
840
- my %addDirs = %{$$et{ADD_DIRS}};
1132
+ my %addDirs = %{$$et{ADD_DIRS}}; # (make a copy)
841
1133
  $$et{AddJp2Dirs} = \%addDirs;
842
- $$et{AddJp2Tags} = $et->GetNewTagInfoHash(\%Image::ExifTool::Jpeg2000::Main);
843
1134
  } else {
844
1135
  my ($buff, $fileType);
845
1136
  # recognize JPX and JPM as unique types of JP2
846
1137
  if ($raf->Read($buff, 12) == 12 and $buff =~ /^.{4}ftyp(.{4})/s) {
847
1138
  $fileType = 'JPX' if $1 eq 'jpx ';
848
1139
  $fileType = 'JPM' if $1 eq 'jpm ';
1140
+ $fileType = 'JXL' if $1 eq 'jxl ';
849
1141
  }
850
1142
  $raf->Seek(-length($buff), 1) if defined $buff;
851
1143
  $et->SetFileType($fileType);
@@ -860,6 +1152,59 @@ sub ProcessJP2($$)
860
1152
  return $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
861
1153
  }
862
1154
 
1155
+ #------------------------------------------------------------------------------
1156
+ # Read meta information from a JPEG XL image
1157
+ # Inputs: 0) ExifTool object reference, 1) dirInfo reference
1158
+ # Returns: 1 on success, 0 if this wasn't a valid JPEG XL file, -1 on write error
1159
+ sub ProcessJXL($$)
1160
+ {
1161
+ my ($et, $dirInfo) = @_;
1162
+ my $raf = $$dirInfo{RAF};
1163
+ my $outfile = $$dirInfo{OutFile};
1164
+ my ($hdr, $buff);
1165
+
1166
+ return 0 unless $raf->Read($hdr,12) == 12;
1167
+ if ($hdr eq "\0\0\0\x0cJXL \x0d\x0a\x87\x0a") {
1168
+ # JPEG XL in ISO BMFF container
1169
+ $$et{IsJXL} = 1;
1170
+ } elsif ($hdr =~ /^\xff\x0a/) {
1171
+ # JPEG XL codestream
1172
+ if ($outfile) {
1173
+ if ($$et{OPTIONS}{IgnoreMinorErrors}) {
1174
+ $et->Warn('Wrapped JXL codestream in ISO BMFF container');
1175
+ } else {
1176
+ $et->Error('Will wrap JXL codestream in ISO BMFF container for writing',1);
1177
+ return 0;
1178
+ }
1179
+ $$et{IsJXL} = 2;
1180
+ my $buff = "\0\0\0\x0cJXL \x0d\x0a\x87\x0a\0\0\0\x14ftypjxl \0\0\0\0jxl ";
1181
+ # add metadata to empty ISO BMFF container
1182
+ $$dirInfo{RAF} = new File::RandomAccess(\$buff);
1183
+ } else {
1184
+ $et->SetFileType('JXL Codestream','image/jxl', 'jxl');
1185
+ return ProcessJXLCodestream($et, \$hdr);
1186
+ }
1187
+ } else {
1188
+ return 0;
1189
+ }
1190
+ $raf->Seek(0,0) or $et->Error('Seek error'), return 0;
1191
+
1192
+ my $success = ProcessJP2($et, $dirInfo);
1193
+
1194
+ if ($outfile and $success > 0 and $$et{IsJXL} == 2) {
1195
+ # attach the JXL codestream box to the ISO BMFF file
1196
+ $raf->Seek(0,2) or return -1;
1197
+ my $size = $raf->Tell();
1198
+ $raf->Seek(0,0) or return -1;
1199
+ SetByteOrder('MM');
1200
+ Write($outfile, Set32u($size + 8), 'jxlc') or return -1;
1201
+ while ($raf->Read($buff, 65536)) {
1202
+ Write($outfile, $buff) or return -1;
1203
+ }
1204
+ }
1205
+ return $success;
1206
+ }
1207
+
863
1208
  1; # end
864
1209
 
865
1210
  __END__