exiftool_vendored 12.17.1 → 12.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/Changes +225 -1
- data/bin/MANIFEST +23 -0
- data/bin/META.json +1 -1
- data/bin/META.yml +1 -1
- data/bin/README +45 -43
- data/bin/arg_files/xmp2exif.args +2 -1
- data/bin/config_files/acdsee.config +193 -6
- data/bin/config_files/convert_regions.config +25 -14
- data/bin/config_files/cuepointlist.config +70 -0
- data/bin/config_files/example.config +2 -9
- data/bin/exiftool +142 -87
- data/bin/fmt_files/gpx.fmt +2 -2
- data/bin/fmt_files/gpx_wpt.fmt +2 -2
- data/bin/fmt_files/kml.fmt +1 -1
- data/bin/fmt_files/kml_track.fmt +1 -1
- data/bin/lib/Image/ExifTool/Apple.pm +3 -2
- data/bin/lib/Image/ExifTool/BuildTagLookup.pm +30 -12
- data/bin/lib/Image/ExifTool/CBOR.pm +277 -0
- data/bin/lib/Image/ExifTool/Canon.pm +49 -18
- data/bin/lib/Image/ExifTool/DJI.pm +6 -6
- data/bin/lib/Image/ExifTool/DPX.pm +13 -2
- data/bin/lib/Image/ExifTool/DjVu.pm +6 -5
- data/bin/lib/Image/ExifTool/Exif.pm +28 -11
- data/bin/lib/Image/ExifTool/FITS.pm +13 -2
- data/bin/lib/Image/ExifTool/FlashPix.pm +35 -10
- data/bin/lib/Image/ExifTool/FujiFilm.pm +19 -8
- data/bin/lib/Image/ExifTool/GPS.pm +22 -11
- data/bin/lib/Image/ExifTool/Geotag.pm +13 -2
- data/bin/lib/Image/ExifTool/GoPro.pm +16 -1
- data/bin/lib/Image/ExifTool/ICC_Profile.pm +2 -2
- data/bin/lib/Image/ExifTool/ID3.pm +15 -3
- data/bin/lib/Image/ExifTool/JPEG.pm +74 -4
- data/bin/lib/Image/ExifTool/JSON.pm +27 -4
- data/bin/lib/Image/ExifTool/Jpeg2000.pm +393 -16
- data/bin/lib/Image/ExifTool/LIF.pm +153 -0
- data/bin/lib/Image/ExifTool/Lang/nl.pm +60 -59
- data/bin/lib/Image/ExifTool/M2TS.pm +137 -5
- data/bin/lib/Image/ExifTool/MIE.pm +4 -3
- data/bin/lib/Image/ExifTool/MRC.pm +341 -0
- data/bin/lib/Image/ExifTool/MWG.pm +3 -3
- data/bin/lib/Image/ExifTool/MXF.pm +1 -1
- data/bin/lib/Image/ExifTool/MacOS.pm +1 -1
- data/bin/lib/Image/ExifTool/Microsoft.pm +298 -82
- data/bin/lib/Image/ExifTool/Nikon.pm +19 -8
- data/bin/lib/Image/ExifTool/NikonSettings.pm +28 -11
- data/bin/lib/Image/ExifTool/Olympus.pm +6 -3
- data/bin/lib/Image/ExifTool/Other.pm +93 -0
- data/bin/lib/Image/ExifTool/PDF.pm +9 -12
- data/bin/lib/Image/ExifTool/PNG.pm +8 -7
- data/bin/lib/Image/ExifTool/Panasonic.pm +28 -3
- data/bin/lib/Image/ExifTool/Pentax.pm +28 -5
- data/bin/lib/Image/ExifTool/PhaseOne.pm +4 -3
- data/bin/lib/Image/ExifTool/Photoshop.pm +6 -0
- data/bin/lib/Image/ExifTool/QuickTime.pm +247 -88
- data/bin/lib/Image/ExifTool/QuickTimeStream.pl +283 -141
- data/bin/lib/Image/ExifTool/README +3 -0
- data/bin/lib/Image/ExifTool/RIFF.pm +89 -12
- data/bin/lib/Image/ExifTool/Samsung.pm +48 -10
- data/bin/lib/Image/ExifTool/Shortcuts.pm +9 -0
- data/bin/lib/Image/ExifTool/Sony.pm +237 -78
- data/bin/lib/Image/ExifTool/TagInfoXML.pm +1 -0
- data/bin/lib/Image/ExifTool/TagLookup.pm +4125 -4028
- data/bin/lib/Image/ExifTool/TagNames.pod +644 -286
- data/bin/lib/Image/ExifTool/Torrent.pm +18 -11
- data/bin/lib/Image/ExifTool/WriteExif.pl +1 -1
- data/bin/lib/Image/ExifTool/WriteIPTC.pl +1 -1
- data/bin/lib/Image/ExifTool/WritePDF.pl +1 -0
- data/bin/lib/Image/ExifTool/WritePNG.pl +2 -0
- data/bin/lib/Image/ExifTool/WritePostScript.pl +1 -0
- data/bin/lib/Image/ExifTool/WriteQuickTime.pl +55 -21
- data/bin/lib/Image/ExifTool/WriteXMP.pl +7 -3
- data/bin/lib/Image/ExifTool/Writer.pl +47 -10
- data/bin/lib/Image/ExifTool/XMP.pm +39 -14
- data/bin/lib/Image/ExifTool/XMP2.pl +2 -1
- data/bin/lib/Image/ExifTool/XMPStruct.pl +3 -1
- data/bin/lib/Image/ExifTool/ZISRAW.pm +121 -2
- data/bin/lib/Image/ExifTool.pm +223 -72
- data/bin/lib/Image/ExifTool.pod +114 -93
- data/bin/perl-Image-ExifTool.spec +43 -42
- data/lib/exiftool_vendored/version.rb +1 -1
- 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.
|
|
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
|
|
111
|
-
|
|
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 =>
|
|
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.
|
|
217
|
-
|
|
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,20 @@ 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
|
+
},
|
|
347
|
+
{
|
|
348
|
+
Name => 'UUID-C2PAClaimSignature', # (seen in JUMB data of JPEG images)
|
|
349
|
+
Condition => '$$valPt=~/^c2cs\x00\x11\x00\x10\x80\x00\x00\xaa\x00\x38\x9b\x71/',
|
|
350
|
+
SubDirectory => {
|
|
351
|
+
TagTable => 'Image::ExifTool::CBOR::Main',
|
|
352
|
+
Start => '$valuePtr + 16',
|
|
353
|
+
},
|
|
354
|
+
},
|
|
305
355
|
{
|
|
306
356
|
Name => 'UUID-Unknown',
|
|
307
357
|
},
|
|
@@ -321,6 +371,73 @@ my %j2cMarker = (
|
|
|
321
371
|
Name => 'URL',
|
|
322
372
|
Format => 'string',
|
|
323
373
|
},
|
|
374
|
+
# JUMBF boxes (ref https://github.com/thorfdbg/codestream-parser)
|
|
375
|
+
jumd => {
|
|
376
|
+
Name => 'JUMBFDescr',
|
|
377
|
+
SubDirectory => { TagTable => 'Image::ExifTool::Jpeg2000::JUMD' },
|
|
378
|
+
},
|
|
379
|
+
jumb => {
|
|
380
|
+
Name => 'JUMBFBox',
|
|
381
|
+
SubDirectory => {
|
|
382
|
+
TagTable => 'Image::ExifTool::Jpeg2000::Main',
|
|
383
|
+
ProcessProc => \&ProcessJUMB,
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
json => {
|
|
387
|
+
Name => 'JSONData',
|
|
388
|
+
Flags => [ 'Binary', 'Protected', 'BlockExtract' ],
|
|
389
|
+
Notes => q{
|
|
390
|
+
by default, data in this tag is parsed using the ExifTool JSON module to to
|
|
391
|
+
allow individual tags to be accessed when reading, but it may also be
|
|
392
|
+
extracted as a block via the "JSONData" tag or by setting the API
|
|
393
|
+
BlockExtract option
|
|
394
|
+
},
|
|
395
|
+
SubDirectory => { TagTable => 'Image::ExifTool::JSON::Main' },
|
|
396
|
+
},
|
|
397
|
+
cbor => {
|
|
398
|
+
Name => 'CBORData',
|
|
399
|
+
Flags => [ 'Binary', 'Protected' ],
|
|
400
|
+
SubDirectory => { TagTable => 'Image::ExifTool::CBOR::Main' },
|
|
401
|
+
},
|
|
402
|
+
bfdb => { # used in JUMBF (see # (used when tag is renamed according to JUMDLabel)
|
|
403
|
+
Name => 'BinaryDataType',
|
|
404
|
+
Notes => 'JUMBF, MIME type and optional file name',
|
|
405
|
+
Format => 'undef',
|
|
406
|
+
# (ignore "toggles" byte and just extract MIME type and file name)
|
|
407
|
+
ValueConv => '$_=substr($val,1); s/\0+$//; s/\0/, /; $_',
|
|
408
|
+
JUMBF_Suffix => 'Type', # (used when tag is renamed according to JUMDLabel)
|
|
409
|
+
},
|
|
410
|
+
bidb => { # used in JUMBF
|
|
411
|
+
Name => 'BinaryData',
|
|
412
|
+
Notes => 'JUMBF',
|
|
413
|
+
Groups => { 2 => 'Preview' },
|
|
414
|
+
Format => 'undef',
|
|
415
|
+
Binary => 1,
|
|
416
|
+
JUMBF_Suffix => 'Data', # (used when tag is renamed according to JUMDLabel)
|
|
417
|
+
},
|
|
418
|
+
#
|
|
419
|
+
# stuff seen in JPEG XL images:
|
|
420
|
+
#
|
|
421
|
+
# jbrd - JPEG Bitstream Reconstruction Data (allows lossless conversion back to original JPG)
|
|
422
|
+
jxlc => {
|
|
423
|
+
Name => 'JXLCodestream',
|
|
424
|
+
Format => 'undef',
|
|
425
|
+
Notes => q{
|
|
426
|
+
Codestream in JPEG XL image. Currently processed only to determine
|
|
427
|
+
ImageSize
|
|
428
|
+
},
|
|
429
|
+
RawConv => 'Image::ExifTool::Jpeg2000::ProcessJXLCodestream($self,\$val); undef',
|
|
430
|
+
},
|
|
431
|
+
Exif => {
|
|
432
|
+
Name => 'EXIF',
|
|
433
|
+
SubDirectory => {
|
|
434
|
+
TagTable => 'Image::ExifTool::Exif::Main',
|
|
435
|
+
ProcessProc => \&Image::ExifTool::ProcessTIFF,
|
|
436
|
+
WriteProc => \&Image::ExifTool::WriteTIFF,
|
|
437
|
+
DirName => 'EXIF',
|
|
438
|
+
Start => '$valuePtr + 4',
|
|
439
|
+
},
|
|
440
|
+
},
|
|
324
441
|
);
|
|
325
442
|
|
|
326
443
|
%Image::ExifTool::Jpeg2000::ImageHeader = (
|
|
@@ -375,6 +492,7 @@ my %j2cMarker = (
|
|
|
375
492
|
'jp2 ' => 'JPEG 2000 Image (.JP2)', # image/jp2
|
|
376
493
|
'jpm ' => 'JPEG 2000 Compound Image (.JPM)', # image/jpm
|
|
377
494
|
'jpx ' => 'JPEG 2000 with extensions (.JPX)', # image/jpx
|
|
495
|
+
'jxl ' => 'JPEG XL Image (.JXL)', # image/jxl
|
|
378
496
|
},
|
|
379
497
|
},
|
|
380
498
|
1 => {
|
|
@@ -513,6 +631,114 @@ my %j2cMarker = (
|
|
|
513
631
|
],
|
|
514
632
|
);
|
|
515
633
|
|
|
634
|
+
# JUMBF description box
|
|
635
|
+
%Image::ExifTool::Jpeg2000::JUMD = (
|
|
636
|
+
PROCESS_PROC => \&ProcessJUMD,
|
|
637
|
+
GROUPS => { 0 => 'JUMBF', 1 => 'JUMBF', 2 => 'Image' },
|
|
638
|
+
NOTES => 'Information extracted from the JUMBF description box.',
|
|
639
|
+
'type' => {
|
|
640
|
+
Name => 'JUMDType',
|
|
641
|
+
ValueConv => 'unpack "H*", $val',
|
|
642
|
+
PrintConv => q{
|
|
643
|
+
my @a = $val =~ /^(\w{8})(\w{4})(\w{4})(\w{16})$/;
|
|
644
|
+
return $val unless @a;
|
|
645
|
+
my $ascii = pack 'H*', $a[0];
|
|
646
|
+
$a[0] = "($ascii)" if $ascii =~ /^[a-zA-Z0-9]{4}$/;
|
|
647
|
+
return join '-', @a;
|
|
648
|
+
},
|
|
649
|
+
# seen:
|
|
650
|
+
# cacb/cast/caas/cacl/casg/json-00110010800000aa00389b71
|
|
651
|
+
# 6579d6fbdba2446bb2ac1b82feeb89d1 - JPEG image
|
|
652
|
+
},
|
|
653
|
+
'label' => { Name => 'JUMDLabel' },
|
|
654
|
+
'toggles' => {
|
|
655
|
+
Name => 'JUMDToggles',
|
|
656
|
+
Unknown => 1,
|
|
657
|
+
PrintConv => { BITMASK => {
|
|
658
|
+
0 => 'Requestable',
|
|
659
|
+
1 => 'Label',
|
|
660
|
+
2 => 'ID',
|
|
661
|
+
3 => 'Signature',
|
|
662
|
+
}},
|
|
663
|
+
},
|
|
664
|
+
'id' => { Name => 'JUMDID', Description => 'JUMD ID' },
|
|
665
|
+
'sig' => { Name => 'JUMDSignature', PrintConv => 'unpack "H*", $val' },
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
#------------------------------------------------------------------------------
|
|
669
|
+
# Read JUMBF box to keep track of sub-document numbers
|
|
670
|
+
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
|
671
|
+
# Returns: 1 on success
|
|
672
|
+
sub ProcessJUMB($$$)
|
|
673
|
+
{
|
|
674
|
+
my ($et, $dirInfo, $tagTablePtr) = @_;
|
|
675
|
+
if ($$et{jumd_level}) {
|
|
676
|
+
++$$et{jumd_level}[-1]; # increment current sub-document number
|
|
677
|
+
} else {
|
|
678
|
+
$$et{jumd_level} = [ ++$$et{DOC_COUNT} ]; # new top-level sub-document
|
|
679
|
+
$$et{SET_GROUP0} = 'JUMBF';
|
|
680
|
+
}
|
|
681
|
+
$$et{DOC_NUM} = join '-', @{$$et{jumd_level}};
|
|
682
|
+
push @{$$et{jumd_level}}, 0;
|
|
683
|
+
ProcessJpeg2000Box($et, $dirInfo, $tagTablePtr);
|
|
684
|
+
delete $$et{DOC_NUM};
|
|
685
|
+
delete $$et{JUMBFLabel};
|
|
686
|
+
pop @{$$et{jumd_level}};
|
|
687
|
+
if (@{$$et{jumd_level}} < 2) {
|
|
688
|
+
delete $$et{jumd_level};
|
|
689
|
+
delete $$et{SET_GROUP0};
|
|
690
|
+
}
|
|
691
|
+
return 1;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
#------------------------------------------------------------------------------
|
|
695
|
+
# Read JUMBF description box (ref https://github.com/thorfdbg/codestream-parser)
|
|
696
|
+
# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
|
|
697
|
+
# Returns: 1 on success
|
|
698
|
+
sub ProcessJUMD($$$)
|
|
699
|
+
{
|
|
700
|
+
my ($et, $dirInfo, $tagTablePtr) = @_;
|
|
701
|
+
my $dataPt = $$dirInfo{DataPt};
|
|
702
|
+
my $pos = $$dirInfo{DirStart};
|
|
703
|
+
my $end = $pos + $$dirInfo{DirLen};
|
|
704
|
+
$et->VerboseDir('JUMD', 0, $end-$pos);
|
|
705
|
+
delete $$et{JUMBFLabel};
|
|
706
|
+
$$dirInfo{DirLen} < 17 and $et->Warn('Truncated JUMD directory'), return 0;
|
|
707
|
+
my $type = substr($$dataPt, $pos, 4);
|
|
708
|
+
$et->HandleTag($tagTablePtr, 'type', substr($$dataPt, $pos, 16));
|
|
709
|
+
$pos += 16;
|
|
710
|
+
my $flags = Get8u($dataPt, $pos++);
|
|
711
|
+
$et->HandleTag($tagTablePtr, 'toggles', $flags);
|
|
712
|
+
if ($flags & 0x02) { # label exists?
|
|
713
|
+
pos($$dataPt) = $pos;
|
|
714
|
+
$$dataPt =~ /\0/g or $et->Warn('Missing JUMD label terminator'), return 0;
|
|
715
|
+
my $len = pos($$dataPt) - $pos;
|
|
716
|
+
my $name = substr($$dataPt, $pos, $len);
|
|
717
|
+
$et->HandleTag($tagTablePtr, 'label', $name);
|
|
718
|
+
$pos += $len;
|
|
719
|
+
if ($len) {
|
|
720
|
+
$name =~ s/[^-_a-zA-Z0-9]([a-z])/\U$1/g; # capitalize characters after illegal characters
|
|
721
|
+
$name =~ tr/-_a-zA-Z0-9//dc; # remove other illegal characters
|
|
722
|
+
$name =~ s/__/_/; # collapse double underlines
|
|
723
|
+
$name = ucfirst $name; # capitalize first letter
|
|
724
|
+
$name = "Tag$name" if length($name) < 2; # must at least 2 characters long
|
|
725
|
+
$$et{JUMBFLabel} = $name;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if ($flags & 0x04) { # ID exists?
|
|
729
|
+
$pos + 4 > $end and $et->Warn('Missing JUMD ID'), return 0;
|
|
730
|
+
$et->HandleTag($tagTablePtr, 'id', Get32u($dataPt, $pos));
|
|
731
|
+
$pos += 4;
|
|
732
|
+
}
|
|
733
|
+
if ($flags & 0x08) { # signature exists?
|
|
734
|
+
$pos + 32 > $end and $et->Warn('Missing JUMD signature'), return 0;
|
|
735
|
+
$et->HandleTag($tagTablePtr, 'sig', substr($$dataPt, $pos, 32));
|
|
736
|
+
$pos += 32;
|
|
737
|
+
}
|
|
738
|
+
$pos == $end or $et->Warn('Extra data in JUMD box'." $pos $end", 1);
|
|
739
|
+
return 1;
|
|
740
|
+
}
|
|
741
|
+
|
|
516
742
|
#------------------------------------------------------------------------------
|
|
517
743
|
# Create new JPEG 2000 boxes when writing
|
|
518
744
|
# (Currently only supports adding top-level Writable JPEG2000 tags and certain UUID boxes)
|
|
@@ -542,8 +768,29 @@ sub CreateNewBoxes($$)
|
|
|
542
768
|
$et->VerboseValue("+ Jpeg2000:$$tagInfo{Name}", $val);
|
|
543
769
|
}
|
|
544
770
|
}
|
|
545
|
-
# add UUID boxes
|
|
771
|
+
# add UUID boxes (and/or JXL Exif/XML boxes)
|
|
546
772
|
foreach $dirName (sort keys %$addDirs) {
|
|
773
|
+
# handle JPEG XL XMP and EXIF
|
|
774
|
+
if ($dirName eq 'XML' or $dirName eq 'Exif') {
|
|
775
|
+
my ($tag, $dir) = $dirName eq 'XML' ? ('xml ', 'XMP') : ('Exif', 'EXIF');
|
|
776
|
+
my $tagInfo = $Image::ExifTool::Jpeg2000::Main{$tag};
|
|
777
|
+
$tagInfo = $$tagInfo[1] if ref $tagInfo eq 'ARRAY'; # (hack for stupid JXL XMP)
|
|
778
|
+
my $subdir = $$tagInfo{SubDirectory};
|
|
779
|
+
my $tagTable = GetTagTable($$subdir{TagTable});
|
|
780
|
+
$tagTable = GetTagTable('Image::ExifTool::XMP::Main') if $dir eq 'XMP';
|
|
781
|
+
my %dirInfo = (
|
|
782
|
+
DirName => $dir,
|
|
783
|
+
Parent => 'JP2',
|
|
784
|
+
);
|
|
785
|
+
my $newdir = $et->WriteDirectory(\%dirInfo, $tagTable, $$subdir{WriteProc});
|
|
786
|
+
if (defined $newdir and length $newdir) {
|
|
787
|
+
# not sure why, but EXIF box is padded with leading 0's in my sample
|
|
788
|
+
my $pad = $dirName eq 'Exif' ? "\0\0\0\0" : '';
|
|
789
|
+
my $boxhdr = pack('N', length($newdir) + length($pad) + 8) . $tag;
|
|
790
|
+
Write($outfile, $boxhdr, $pad, $newdir) or return 0;
|
|
791
|
+
next;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
547
794
|
next unless $uuid{$dirName};
|
|
548
795
|
my $tagInfo;
|
|
549
796
|
foreach $tagInfo (@{$Image::ExifTool::Jpeg2000::Main{uuid}}) {
|
|
@@ -715,6 +962,14 @@ sub ProcessJpeg2000Box($$$)
|
|
|
715
962
|
}
|
|
716
963
|
}
|
|
717
964
|
}
|
|
965
|
+
# create new tag for JUMBF data values with name corresponding to JUMBFLabel
|
|
966
|
+
if ($tagInfo and $$et{JUMBFLabel} and (not $$tagInfo{SubDirectory} or $$tagInfo{BlockExtract})) {
|
|
967
|
+
$tagInfo = { %$tagInfo, Name => $$et{JUMBFLabel} . ($$tagInfo{JUMBF_Suffix} || '') };
|
|
968
|
+
delete $$tagInfo{Description};
|
|
969
|
+
AddTagToTable($tagTablePtr, '_JUMBF_' . $$et{JUMBFLabel}, $tagInfo);
|
|
970
|
+
delete $$tagInfo{Protected}; # (must do this so -j -b returns JUMBF binary data)
|
|
971
|
+
$$tagInfo{TagID} = $boxID;
|
|
972
|
+
}
|
|
718
973
|
if ($verbose) {
|
|
719
974
|
$et->VerboseInfo($boxID, $tagInfo,
|
|
720
975
|
Table => $tagTablePtr,
|
|
@@ -752,8 +1007,8 @@ sub ProcessJpeg2000Box($$$)
|
|
|
752
1007
|
# remove this directory from our create list
|
|
753
1008
|
delete $$et{AddJp2Dirs}{$$tagInfo{Name}};
|
|
754
1009
|
my $newdir;
|
|
755
|
-
# only edit writable UUID boxes
|
|
756
|
-
if ($uuid) {
|
|
1010
|
+
# only edit writable UUID and Exif boxes
|
|
1011
|
+
if ($uuid or $boxID eq 'Exif' or ($boxID eq 'xml ' and $$et{IsJXL})) {
|
|
757
1012
|
$newdir = $et->WriteDirectory(\%subdirInfo, $subTable, $$subdir{WriteProc});
|
|
758
1013
|
next if defined $newdir and not length $newdir; # next if deleting the box
|
|
759
1014
|
} elsif (defined $uuid) {
|
|
@@ -803,6 +1058,68 @@ sub ProcessJpeg2000Box($$$)
|
|
|
803
1058
|
return 1;
|
|
804
1059
|
}
|
|
805
1060
|
|
|
1061
|
+
#------------------------------------------------------------------------------
|
|
1062
|
+
# Return bits from a bitstream object
|
|
1063
|
+
# Inputs: 0) array ref, 1) number of bits
|
|
1064
|
+
# Returns: specified number of bits as an integer, and shifts input bitstream
|
|
1065
|
+
sub GetBits($$)
|
|
1066
|
+
{
|
|
1067
|
+
my ($a, $n) = @_;
|
|
1068
|
+
my $v = 0;
|
|
1069
|
+
my $bit = 1;
|
|
1070
|
+
my $i;
|
|
1071
|
+
while ($n--) {
|
|
1072
|
+
for ($i=0; $i<@$a; ++$i) {
|
|
1073
|
+
# consume bits LSB first
|
|
1074
|
+
my $set = $$a[$i] & 1;
|
|
1075
|
+
$$a[$i] >>= 1;
|
|
1076
|
+
if ($i) {
|
|
1077
|
+
$$a[$i-1] |= 0x80 if $set;
|
|
1078
|
+
} else {
|
|
1079
|
+
$v |= $bit if $set;
|
|
1080
|
+
$bit <<= 1;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return $v;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
#------------------------------------------------------------------------------
|
|
1088
|
+
# Extract parameters from JPEG XL codestream [unverified!]
|
|
1089
|
+
# Inputs: 0) ExifTool ref, 1) codestream ref
|
|
1090
|
+
# Returns: 1
|
|
1091
|
+
sub ProcessJXLCodestream($$)
|
|
1092
|
+
{
|
|
1093
|
+
my ($et, $dataPt) = @_;
|
|
1094
|
+
# add padding if necessary to avoid unpacking past end of data
|
|
1095
|
+
if (length $$dataPt < 14) {
|
|
1096
|
+
my $tmp = $$dataPt . ("\0" x 14);
|
|
1097
|
+
$dataPt = \$tmp;
|
|
1098
|
+
}
|
|
1099
|
+
my @a = unpack 'x2C12', $$dataPt;
|
|
1100
|
+
my ($x, $y);
|
|
1101
|
+
my $small = GetBits(\@a, 1);
|
|
1102
|
+
if ($small) {
|
|
1103
|
+
$y = (GetBits(\@a, 5) + 1) * 8;
|
|
1104
|
+
} else {
|
|
1105
|
+
$y = GetBits(\@a, [9, 13, 18, 30]->[GetBits(\@a, 2)]) + 1;
|
|
1106
|
+
}
|
|
1107
|
+
my $ratio = GetBits(\@a, 3);
|
|
1108
|
+
if ($ratio == 0) {
|
|
1109
|
+
if ($small) {
|
|
1110
|
+
$x = (GetBits(\@a, 5) + 1) * 8;;
|
|
1111
|
+
} else {
|
|
1112
|
+
$x = GetBits(\@a, [9, 13, 18, 30]->[GetBits(\@a, 2)]) + 1;
|
|
1113
|
+
}
|
|
1114
|
+
} else {
|
|
1115
|
+
my $r = [[1,1],[12,10],[4,3],[3,2],[16,9],[5,4],[2,1]]->[$ratio-1];
|
|
1116
|
+
$x = int($y * $$r[0] / $$r[1]);
|
|
1117
|
+
}
|
|
1118
|
+
$et->FoundTag(ImageWidth => $x);
|
|
1119
|
+
$et->FoundTag(ImageHeight => $y);
|
|
1120
|
+
return 1;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
806
1123
|
#------------------------------------------------------------------------------
|
|
807
1124
|
# Read/write meta information from a JPEG 2000 image
|
|
808
1125
|
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
|
@@ -817,8 +1134,9 @@ sub ProcessJP2($$)
|
|
|
817
1134
|
|
|
818
1135
|
# check to be sure this is a valid JPG2000 file
|
|
819
1136
|
return 0 unless $raf->Read($hdr,12) == 12;
|
|
820
|
-
unless ($hdr eq "\
|
|
821
|
-
$hdr eq "\
|
|
1137
|
+
unless ($hdr eq "\0\0\0\x0cjP \x0d\x0a\x87\x0a" or # (ref 1)
|
|
1138
|
+
$hdr eq "\0\0\0\x0cjP\x1a\x1a\x0d\x0a\x87\x0a" or # (ref 2)
|
|
1139
|
+
$$et{IsJXL})
|
|
822
1140
|
{
|
|
823
1141
|
return 0 unless $hdr =~ /^\xff\x4f\xff\x51\0/; # check for JP2 codestream format
|
|
824
1142
|
if ($outfile) {
|
|
@@ -835,17 +1153,23 @@ sub ProcessJP2($$)
|
|
|
835
1153
|
}
|
|
836
1154
|
if ($outfile) {
|
|
837
1155
|
Write($outfile, $hdr) or return -1;
|
|
838
|
-
|
|
1156
|
+
if ($$et{IsJXL}) {
|
|
1157
|
+
$et->InitWriteDirs(\%jxlMap);
|
|
1158
|
+
$$et{AddJp2Tags} = { }; # (don't add JP2 tags in JXL files)
|
|
1159
|
+
} else {
|
|
1160
|
+
$et->InitWriteDirs(\%jp2Map);
|
|
1161
|
+
$$et{AddJp2Tags} = $et->GetNewTagInfoHash(\%Image::ExifTool::Jpeg2000::Main);
|
|
1162
|
+
}
|
|
839
1163
|
# save list of directories to create
|
|
840
|
-
my %addDirs = %{$$et{ADD_DIRS}};
|
|
1164
|
+
my %addDirs = %{$$et{ADD_DIRS}}; # (make a copy)
|
|
841
1165
|
$$et{AddJp2Dirs} = \%addDirs;
|
|
842
|
-
$$et{AddJp2Tags} = $et->GetNewTagInfoHash(\%Image::ExifTool::Jpeg2000::Main);
|
|
843
1166
|
} else {
|
|
844
1167
|
my ($buff, $fileType);
|
|
845
1168
|
# recognize JPX and JPM as unique types of JP2
|
|
846
1169
|
if ($raf->Read($buff, 12) == 12 and $buff =~ /^.{4}ftyp(.{4})/s) {
|
|
847
1170
|
$fileType = 'JPX' if $1 eq 'jpx ';
|
|
848
1171
|
$fileType = 'JPM' if $1 eq 'jpm ';
|
|
1172
|
+
$fileType = 'JXL' if $1 eq 'jxl ';
|
|
849
1173
|
}
|
|
850
1174
|
$raf->Seek(-length($buff), 1) if defined $buff;
|
|
851
1175
|
$et->SetFileType($fileType);
|
|
@@ -860,6 +1184,59 @@ sub ProcessJP2($$)
|
|
|
860
1184
|
return $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
|
|
861
1185
|
}
|
|
862
1186
|
|
|
1187
|
+
#------------------------------------------------------------------------------
|
|
1188
|
+
# Read meta information from a JPEG XL image
|
|
1189
|
+
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
|
|
1190
|
+
# Returns: 1 on success, 0 if this wasn't a valid JPEG XL file, -1 on write error
|
|
1191
|
+
sub ProcessJXL($$)
|
|
1192
|
+
{
|
|
1193
|
+
my ($et, $dirInfo) = @_;
|
|
1194
|
+
my $raf = $$dirInfo{RAF};
|
|
1195
|
+
my $outfile = $$dirInfo{OutFile};
|
|
1196
|
+
my ($hdr, $buff);
|
|
1197
|
+
|
|
1198
|
+
return 0 unless $raf->Read($hdr,12) == 12;
|
|
1199
|
+
if ($hdr eq "\0\0\0\x0cJXL \x0d\x0a\x87\x0a") {
|
|
1200
|
+
# JPEG XL in ISO BMFF container
|
|
1201
|
+
$$et{IsJXL} = 1;
|
|
1202
|
+
} elsif ($hdr =~ /^\xff\x0a/) {
|
|
1203
|
+
# JPEG XL codestream
|
|
1204
|
+
if ($outfile) {
|
|
1205
|
+
if ($$et{OPTIONS}{IgnoreMinorErrors}) {
|
|
1206
|
+
$et->Warn('Wrapped JXL codestream in ISO BMFF container');
|
|
1207
|
+
} else {
|
|
1208
|
+
$et->Error('Will wrap JXL codestream in ISO BMFF container for writing',1);
|
|
1209
|
+
return 0;
|
|
1210
|
+
}
|
|
1211
|
+
$$et{IsJXL} = 2;
|
|
1212
|
+
my $buff = "\0\0\0\x0cJXL \x0d\x0a\x87\x0a\0\0\0\x14ftypjxl \0\0\0\0jxl ";
|
|
1213
|
+
# add metadata to empty ISO BMFF container
|
|
1214
|
+
$$dirInfo{RAF} = new File::RandomAccess(\$buff);
|
|
1215
|
+
} else {
|
|
1216
|
+
$et->SetFileType('JXL Codestream','image/jxl', 'jxl');
|
|
1217
|
+
return ProcessJXLCodestream($et, \$hdr);
|
|
1218
|
+
}
|
|
1219
|
+
} else {
|
|
1220
|
+
return 0;
|
|
1221
|
+
}
|
|
1222
|
+
$raf->Seek(0,0) or $et->Error('Seek error'), return 0;
|
|
1223
|
+
|
|
1224
|
+
my $success = ProcessJP2($et, $dirInfo);
|
|
1225
|
+
|
|
1226
|
+
if ($outfile and $success > 0 and $$et{IsJXL} == 2) {
|
|
1227
|
+
# attach the JXL codestream box to the ISO BMFF file
|
|
1228
|
+
$raf->Seek(0,2) or return -1;
|
|
1229
|
+
my $size = $raf->Tell();
|
|
1230
|
+
$raf->Seek(0,0) or return -1;
|
|
1231
|
+
SetByteOrder('MM');
|
|
1232
|
+
Write($outfile, Set32u($size + 8), 'jxlc') or return -1;
|
|
1233
|
+
while ($raf->Read($buff, 65536)) {
|
|
1234
|
+
Write($outfile, $buff) or return -1;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return $success;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
863
1240
|
1; # end
|
|
864
1241
|
|
|
865
1242
|
__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
|
+
|