exiftool-vendored.pl 12.44.0 → 12.49.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.
- package/bin/Changes +112 -3
- package/bin/MANIFEST +9 -0
- package/bin/META.json +1 -1
- package/bin/META.yml +1 -1
- package/bin/README +45 -44
- package/bin/config_files/acdsee.config +2 -1
- package/bin/config_files/frameCount.config +56 -0
- package/bin/config_files/tiff_version.config +1 -1
- package/bin/exiftool +85 -73
- package/bin/fmt_files/gpx.fmt +3 -0
- package/bin/fmt_files/gpx_wpt.fmt +3 -0
- package/bin/lib/Image/ExifTool/Apple.pm +6 -2
- package/bin/lib/Image/ExifTool/BuildTagLookup.pm +17 -9
- package/bin/lib/Image/ExifTool/Canon.pm +33 -15
- package/bin/lib/Image/ExifTool/CanonRaw.pm +8 -1
- package/bin/lib/Image/ExifTool/CanonVRD.pm +7 -8
- package/bin/lib/Image/ExifTool/EXE.pm +9 -1
- package/bin/lib/Image/ExifTool/Exif.pm +11 -7
- package/bin/lib/Image/ExifTool/FLAC.pm +17 -3
- package/bin/lib/Image/ExifTool/FLIR.pm +4 -3
- package/bin/lib/Image/ExifTool/FlashPix.pm +4 -2
- package/bin/lib/Image/ExifTool/FujiFilm.pm +31 -5
- package/bin/lib/Image/ExifTool/GPS.pm +2 -2
- package/bin/lib/Image/ExifTool/ICC_Profile.pm +3 -2
- package/bin/lib/Image/ExifTool/ICO.pm +141 -0
- package/bin/lib/Image/ExifTool/ID3.pm +6 -6
- package/bin/lib/Image/ExifTool/M2TS.pm +55 -8
- package/bin/lib/Image/ExifTool/MIE.pm +9 -3
- package/bin/lib/Image/ExifTool/MISB.pm +494 -0
- package/bin/lib/Image/ExifTool/MakerNotes.pm +3 -1
- package/bin/lib/Image/ExifTool/Matroska.pm +24 -16
- package/bin/lib/Image/ExifTool/Nikon.pm +39 -31
- package/bin/lib/Image/ExifTool/NikonSettings.pm +5 -3
- package/bin/lib/Image/ExifTool/Panasonic.pm +21 -4
- package/bin/lib/Image/ExifTool/PanasonicRaw.pm +25 -5
- package/bin/lib/Image/ExifTool/Photoshop.pm +29 -3
- package/bin/lib/Image/ExifTool/QuickTime.pm +113 -8
- package/bin/lib/Image/ExifTool/QuickTimeStream.pl +44 -6
- package/bin/lib/Image/ExifTool/README +1 -1
- package/bin/lib/Image/ExifTool/RIFF.pm +106 -9
- package/bin/lib/Image/ExifTool/Samsung.pm +2 -2
- package/bin/lib/Image/ExifTool/Sigma.pm +27 -1
- package/bin/lib/Image/ExifTool/SigmaRaw.pm +37 -13
- package/bin/lib/Image/ExifTool/Sony.pm +8 -3
- package/bin/lib/Image/ExifTool/TagLookup.pm +188 -7
- package/bin/lib/Image/ExifTool/TagNames.pod +3051 -2732
- package/bin/lib/Image/ExifTool/Text.pm +3 -4
- package/bin/lib/Image/ExifTool/Torrent.pm +2 -3
- package/bin/lib/Image/ExifTool/Validate.pm +3 -3
- package/bin/lib/Image/ExifTool/WriteCanonRaw.pl +7 -0
- package/bin/lib/Image/ExifTool/WriteExif.pl +100 -23
- package/bin/lib/Image/ExifTool/WriteIPTC.pl +2 -6
- package/bin/lib/Image/ExifTool/WriteRIFF.pl +359 -0
- package/bin/lib/Image/ExifTool/Writer.pl +10 -3
- package/bin/lib/Image/ExifTool/XMP.pm +76 -58
- package/bin/lib/Image/ExifTool/XMP2.pl +11 -4
- package/bin/lib/Image/ExifTool.pm +75 -15
- package/bin/lib/Image/ExifTool.pod +61 -57
- package/bin/perl-Image-ExifTool.spec +43 -43
- package/bin/pp_build_exe.args +7 -4
- package/package.json +2 -2
|
@@ -14,9 +14,8 @@ package Image::ExifTool::Text;
|
|
|
14
14
|
use strict;
|
|
15
15
|
use vars qw($VERSION);
|
|
16
16
|
use Image::ExifTool qw(:DataAccess :Utils);
|
|
17
|
-
use Image::ExifTool::XMP;
|
|
18
17
|
|
|
19
|
-
$VERSION = '1.
|
|
18
|
+
$VERSION = '1.04';
|
|
20
19
|
|
|
21
20
|
# Text tags
|
|
22
21
|
%Image::ExifTool::Text::Main = (
|
|
@@ -97,7 +96,7 @@ sub ProcessTXT($$)
|
|
|
97
96
|
$nl =~ tr/\0//d; # remove nulls from newline sequence
|
|
98
97
|
$isBOM = 1; # (we don't recognize UTF-16/UTF-32 without one)
|
|
99
98
|
} else {
|
|
100
|
-
$isUTF8 = Image::ExifTool::
|
|
99
|
+
$isUTF8 = Image::ExifTool::IsUTF8($dataPt, 1);
|
|
101
100
|
if ($isUTF8 == 0) {
|
|
102
101
|
$enc = 'us-ascii';
|
|
103
102
|
} elsif ($isUTF8 > 0) {
|
|
@@ -183,7 +182,7 @@ sub ProcessTXT($$)
|
|
|
183
182
|
next if $raf->Tell() < 65536;
|
|
184
183
|
# continue to check encoding after the first 64 kB
|
|
185
184
|
if ($isUTF8 >= 0) { # (if ascii or utf8)
|
|
186
|
-
$isUTF8 = Image::ExifTool::
|
|
185
|
+
$isUTF8 = Image::ExifTool::IsUTF8(\$buff);
|
|
187
186
|
if ($isUTF8 > 0) {
|
|
188
187
|
$enc = 'utf-8';
|
|
189
188
|
} elsif ($isUTF8 < 0) {
|
|
@@ -13,9 +13,8 @@ package Image::ExifTool::Torrent;
|
|
|
13
13
|
use strict;
|
|
14
14
|
use vars qw($VERSION);
|
|
15
15
|
use Image::ExifTool qw(:DataAccess :Utils);
|
|
16
|
-
use Image::ExifTool::XMP;
|
|
17
16
|
|
|
18
|
-
$VERSION = '1.
|
|
17
|
+
$VERSION = '1.06';
|
|
19
18
|
|
|
20
19
|
sub ReadBencode($$$);
|
|
21
20
|
sub ExtractTags($$$;$$@);
|
|
@@ -169,7 +168,7 @@ sub ReadBencode($$$)
|
|
|
169
168
|
if (length($value) > 256) {
|
|
170
169
|
$val = \$value;
|
|
171
170
|
} elsif ($value =~ /[^\t\x20-\x7e]/) {
|
|
172
|
-
if (Image::ExifTool::
|
|
171
|
+
if (Image::ExifTool::IsUTF8(\$value) >= 0) {
|
|
173
172
|
$val = $et->Decode($value, 'UTF8');
|
|
174
173
|
} else {
|
|
175
174
|
$val = \$value;
|
|
@@ -17,7 +17,7 @@ package Image::ExifTool::Validate;
|
|
|
17
17
|
use strict;
|
|
18
18
|
use vars qw($VERSION %exifSpec);
|
|
19
19
|
|
|
20
|
-
$VERSION = '1.
|
|
20
|
+
$VERSION = '1.19';
|
|
21
21
|
|
|
22
22
|
use Image::ExifTool qw(:Utils);
|
|
23
23
|
use Image::ExifTool::Exif;
|
|
@@ -82,8 +82,8 @@ my %otherSpec = (
|
|
|
82
82
|
CR2 => { 0xc5d8 => 1, 0xc5d9 => 1, 0xc5e0 => 1, 0xc640 => 1, 0xc6dc => 1, 0xc6dd => 1 },
|
|
83
83
|
NEF => { 0x9216 => 1, 0x9217 => 1 },
|
|
84
84
|
DNG => { 0x882a => 1, 0x9211 => 1, 0x9216 => 1 },
|
|
85
|
-
ARW => { 0x7000 => 1, 0x7001 => 1, 0x7010 => 1, 0x7011 => 1, 0x7020 => 1,
|
|
86
|
-
|
|
85
|
+
ARW => { 0x7000 => 1, 0x7001 => 1, 0x7010 => 1, 0x7011 => 1, 0x7020 => 1, 0x7031 => 1,
|
|
86
|
+
0x7032 => 1, 0x7034 => 1, 0x7035 => 1, 0x7036 => 1, 0x7037 => 1, 0x7038 => 1,
|
|
87
87
|
0x7310 => 1, 0x7313 => 1, 0x7316 => 1, 0x74c7 => 1, 0x74c8 => 1, 0xa500 => 1 },
|
|
88
88
|
RW2 => { All => 1 }, # ignore all unknown tags in RW2
|
|
89
89
|
RWL => { All => 1 },
|
|
@@ -271,6 +271,13 @@ sub WriteCanonRaw($$$)
|
|
|
271
271
|
$raf->Seek($blockStart+$blockSize-4, 0) or return 0;
|
|
272
272
|
$raf->Read($buff, 4) == 4 or return 0;
|
|
273
273
|
my $dirOffset = Get32u(\$buff,0) + $blockStart;
|
|
274
|
+
# avoid infinite recursion
|
|
275
|
+
$$et{ProcessedCanonRaw} or $$et{ProcessedCanonRaw} = { };
|
|
276
|
+
if ($$et{ProcessedCanonRaw}{$dirOffset}) {
|
|
277
|
+
$et->Error("Double-referenced $$dirInfo{DirName} directory");
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
$$et{ProcessedCanonRaw}{$dirOffset} = 1;
|
|
274
281
|
$raf->Seek($dirOffset, 0) or return 0;
|
|
275
282
|
$raf->Read($buff, 2) == 2 or return 0;
|
|
276
283
|
my $entries = Get16u(\$buff,0); # get number of entries in directory
|
|
@@ -586,7 +586,7 @@ sub WriteExif($$$)
|
|
|
586
586
|
$et->Error("$str $name directory", 1);
|
|
587
587
|
}
|
|
588
588
|
}
|
|
589
|
-
my ($index, $dirEnd, $numEntries);
|
|
589
|
+
my ($index, $dirEnd, $numEntries, %hasOldID, $unsorted);
|
|
590
590
|
if ($dirStart + 4 < $dataLen) {
|
|
591
591
|
$numEntries = Get16u($dataPt, $dirStart);
|
|
592
592
|
$dirEnd = $dirStart + 2 + 12 * $numEntries;
|
|
@@ -596,19 +596,20 @@ sub WriteExif($$$)
|
|
|
596
596
|
return undef unless $n and defined $rtn;
|
|
597
597
|
$numEntries = $n; # continue processing the entries we have
|
|
598
598
|
}
|
|
599
|
-
#
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
599
|
+
# create lookup for existing tag ID's and determine if directory is sorted
|
|
600
|
+
my $lastID = -1;
|
|
601
|
+
for ($index=0; $index<$numEntries; ++$index) {
|
|
602
|
+
my $tagID = Get16u($dataPt, $dirStart + 2 + 12 * $index);
|
|
603
|
+
$hasOldID{$tagID} = 1;
|
|
604
|
+
# check for proper sequence (but ignore null entries at end)
|
|
605
|
+
$unsorted = 1 if $tagID < $lastID and ($tagID or $$tagTablePtr{0});
|
|
606
|
+
$lastID = $tagID;
|
|
607
|
+
}
|
|
608
|
+
# sort entries if out-of-order (but not in maker notes IFDs or RAW files)
|
|
609
|
+
if ($unsorted and not ($inMakerNotes or $et->IsRawType())) {
|
|
610
|
+
SortIFD($dataPt, $dirStart, $numEntries, $$tagTablePtr{0});
|
|
611
|
+
$et->Warn("Entries in $name were out of sequence. Fixed.",1);
|
|
612
|
+
$unsorted = 0;
|
|
612
613
|
}
|
|
613
614
|
} else {
|
|
614
615
|
$numEntries = 0;
|
|
@@ -616,11 +617,12 @@ sub WriteExif($$$)
|
|
|
616
617
|
}
|
|
617
618
|
|
|
618
619
|
# loop through new values and accumulate all information for this IFD
|
|
619
|
-
my (%set, %mayDelete, $tagInfo);
|
|
620
|
+
my (%set, %mayDelete, $tagInfo, %hasNewID);
|
|
620
621
|
my $wrongDir = $crossDelete{$dirName};
|
|
621
622
|
my @newTagInfo = $et->GetNewTagInfoList($tagTablePtr);
|
|
622
623
|
foreach $tagInfo (@newTagInfo) {
|
|
623
624
|
my $tagID = $$tagInfo{TagID};
|
|
625
|
+
$hasNewID{$tagID} = 1;
|
|
624
626
|
# must evaluate Condition later when we have all DataMember's available
|
|
625
627
|
$set{$tagID} = (ref $$tagTablePtr{$tagID} eq 'ARRAY' or $$tagInfo{Condition}) ? '' : $tagInfo;
|
|
626
628
|
}
|
|
@@ -723,7 +725,7 @@ Entry: for (;;) {
|
|
|
723
725
|
$readFormat = $oldFormat = Get16u($dataPt, $entry+2);
|
|
724
726
|
$readCount = $oldCount = Get32u($dataPt, $entry+4);
|
|
725
727
|
undef $oldImageData;
|
|
726
|
-
if ($oldFormat < 1 or $oldFormat > 13) {
|
|
728
|
+
if ($oldFormat < 1 or $oldFormat > 13 and not ($oldFormat == 16 and $$et{Make} eq 'Apple' and $inMakerNotes)) {
|
|
727
729
|
my $msg = "Bad format ($oldFormat) for $name entry $index";
|
|
728
730
|
# patch to preserve invalid directory entries in SubIFD3 of
|
|
729
731
|
# various Kodak Z-series cameras (Z812, Z1085IS, Z1275)
|
|
@@ -972,7 +974,7 @@ Entry: for (;;) {
|
|
|
972
974
|
$readCount = $oldSize / $formatSize[$readFormat];
|
|
973
975
|
}
|
|
974
976
|
}
|
|
975
|
-
if ($oldID <= $lastTagID and not $inMakerNotes) {
|
|
977
|
+
if ($oldID <= $lastTagID and not ($inMakerNotes or $et->IsRawType())) {
|
|
976
978
|
my $str = $oldInfo ? "$$oldInfo{Name} tag" : sprintf('tag 0x%x',$oldID);
|
|
977
979
|
if ($oldID == $lastTagID) {
|
|
978
980
|
$et->Warn("Duplicate $str in $name");
|
|
@@ -1006,6 +1008,23 @@ Entry: for (;;) {
|
|
|
1006
1008
|
}
|
|
1007
1009
|
} else {
|
|
1008
1010
|
$isNew = $oldID <=> $newID;
|
|
1011
|
+
# special logic needed if directory has out-of-order entries
|
|
1012
|
+
if ($unsorted and $isNew) {
|
|
1013
|
+
if ($isNew > 0 and $hasOldID{$newID}) {
|
|
1014
|
+
# we wanted to create the new tag, but an old tag
|
|
1015
|
+
# does exist with this ID, so defer writing the new tag
|
|
1016
|
+
$isNew = -1;
|
|
1017
|
+
}
|
|
1018
|
+
if ($isNew < 0 and $hasNewID{$oldID}) {
|
|
1019
|
+
# we wanted to write the old tag, but we have
|
|
1020
|
+
# a new tag with this ID, so move it up in the order
|
|
1021
|
+
my @tmpTags = ( $oldID );
|
|
1022
|
+
$_ == $oldID or push @tmpTags, $_ foreach @newTags;
|
|
1023
|
+
@newTags = @tmpTags;
|
|
1024
|
+
$newID = $oldID;
|
|
1025
|
+
$isNew = 0;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1009
1028
|
}
|
|
1010
1029
|
my $newInfo = $oldInfo;
|
|
1011
1030
|
my $newFormat = $oldFormat;
|
|
@@ -2170,17 +2189,39 @@ NoOverwrite: next if $isNew > 0;
|
|
|
2170
2189
|
my @offsetList;
|
|
2171
2190
|
if ($ifd >= 0) {
|
|
2172
2191
|
my $offsetInfo = $offsetInfo[$ifd] or next;
|
|
2192
|
+
if ($$offsetInfo{0x111} and $$offsetInfo{0x144}) {
|
|
2193
|
+
# SubIFD may contain double-referenced data as both strips and tiles
|
|
2194
|
+
# for Sony ARW files when SonyRawFileType is "Lossless Compressed RAW 2"
|
|
2195
|
+
if ($dirName eq 'SubIFD' and $$et{TIFF_TYPE} eq 'ARW' and
|
|
2196
|
+
$$offsetInfo{0x117} and $$offsetInfo{0x145} and
|
|
2197
|
+
$$offsetInfo{0x111}[2]==1) # (must be a single strip or the tile offsets could get out of sync)
|
|
2198
|
+
{
|
|
2199
|
+
# some Sony ARW images contain double-referenced raw data stored as both strips
|
|
2200
|
+
# and tiles. Copy the data using only the strip tags, but store the TileOffets
|
|
2201
|
+
# information for updating later (see PanasonicRaw:PatchRawDataOffset for a
|
|
2202
|
+
# description of offsetInfo elements)
|
|
2203
|
+
$$offsetInfo{0x111}[5] = $$offsetInfo{0x144}; # hack to save TileOffsets
|
|
2204
|
+
# delete tile information from offsetInfo because we will copy as strips
|
|
2205
|
+
delete $$offsetInfo{0x144};
|
|
2206
|
+
delete $$offsetInfo{0x145};
|
|
2207
|
+
} else {
|
|
2208
|
+
$et->Error("TIFF $dirName contains both strip and tile data");
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2173
2211
|
# patch Panasonic RAW/RW2 StripOffsets/StripByteCounts if necessary
|
|
2174
2212
|
my $stripOffsets = $$offsetInfo{0x111};
|
|
2175
|
-
|
|
2213
|
+
my $rawDataOffset = $$offsetInfo{0x118};
|
|
2214
|
+
if ($stripOffsets and $$stripOffsets[0]{PanasonicHack} or
|
|
2215
|
+
$rawDataOffset and $$rawDataOffset[0]{PanasonicHack})
|
|
2216
|
+
{
|
|
2176
2217
|
require Image::ExifTool::PanasonicRaw;
|
|
2177
2218
|
my $err = Image::ExifTool::PanasonicRaw::PatchRawDataOffset($offsetInfo, $raf, $ifd);
|
|
2178
2219
|
$err and $et->Error($err);
|
|
2179
2220
|
}
|
|
2180
2221
|
my $tagID;
|
|
2181
|
-
# loop through all tags in reverse order so we save thumbnail
|
|
2222
|
+
# loop through all tags in reverse numerical order so we save thumbnail
|
|
2182
2223
|
# data before main image data if both exist in the same IFD
|
|
2183
|
-
foreach $tagID (reverse sort keys %$offsetInfo) {
|
|
2224
|
+
foreach $tagID (reverse sort { $a <=> $b } keys %$offsetInfo) {
|
|
2184
2225
|
my $tagInfo = $$offsetInfo{$tagID}[0];
|
|
2185
2226
|
next unless $$tagInfo{IsOffset}; # handle byte counts with offsets
|
|
2186
2227
|
my $sizeInfo = $$offsetInfo{$$tagInfo{OffsetPair}};
|
|
@@ -2203,6 +2244,7 @@ NoOverwrite: next if $isNew > 0;
|
|
|
2203
2244
|
}
|
|
2204
2245
|
} else {
|
|
2205
2246
|
last unless @writeLater;
|
|
2247
|
+
# finally, copy all deferred data
|
|
2206
2248
|
@offsetList = @writeLater;
|
|
2207
2249
|
}
|
|
2208
2250
|
my $offsetPair;
|
|
@@ -2296,6 +2338,23 @@ NoOverwrite: next if $isNew > 0;
|
|
|
2296
2338
|
$size = length($buff);
|
|
2297
2339
|
Set32u($size, \$newData, $byteCountPos);
|
|
2298
2340
|
} elsif ($ifd < 0) {
|
|
2341
|
+
# hack for fixed-offset data (Panasonic GH6)
|
|
2342
|
+
if ($$offsetPair[0][6]) {
|
|
2343
|
+
if ($count > 1) {
|
|
2344
|
+
$et->Error("Can't handle fixed offsets with count > 1");
|
|
2345
|
+
} else {
|
|
2346
|
+
my $fixedOffset = Get32u(\$newData, $offsets);
|
|
2347
|
+
my $padToFixedOffset = $fixedOffset - ($newOffset + $dpos);
|
|
2348
|
+
if ($padToFixedOffset < 0) {
|
|
2349
|
+
$et->Error('Metadata too large to fit before fixed-offset image data');
|
|
2350
|
+
} else {
|
|
2351
|
+
# add necessary padding before raw data
|
|
2352
|
+
push @imageData, [$offset+$dbase+$dpos, 0, $padToFixedOffset];
|
|
2353
|
+
$newOffset += $padToFixedOffset;
|
|
2354
|
+
$et->Warn("Adding $padToFixedOffset bytes of padding before fixed-offset image data", 1);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2299
2358
|
# pad if necessary (but don't pad contiguous image blocks)
|
|
2300
2359
|
my $pad = 0;
|
|
2301
2360
|
++$pad if ($blockSize + $size) & 0x01 and ($n+1 >= $count or
|
|
@@ -2406,10 +2465,28 @@ NoOverwrite: next if $isNew > 0;
|
|
|
2406
2465
|
# also add to subIfdDataFixup if necessary
|
|
2407
2466
|
$subIfdDataFixup->AddFixup($offsetPos, $dataTag) if $subIfdDataFixup;
|
|
2408
2467
|
# must also (sometimes) update StripOffsets in Panasonic RW2 images
|
|
2468
|
+
# and TileOffsets in Sony ARW images
|
|
2409
2469
|
my $otherPos = $$offsetPair[0][5];
|
|
2410
|
-
if ($otherPos
|
|
2411
|
-
|
|
2412
|
-
|
|
2470
|
+
if ($otherPos) {
|
|
2471
|
+
if ($$tagInfo{PanasonicHack}) {
|
|
2472
|
+
Set32u($newOffset, \$newData, $otherPos);
|
|
2473
|
+
$fixup->AddFixup($otherPos, $dataTag);
|
|
2474
|
+
} elsif (ref $otherPos eq 'ARRAY') {
|
|
2475
|
+
# the image data was copied as one large strip, and is double-referenced
|
|
2476
|
+
# as tile data, so all we need to do now is properly update the tile offsets
|
|
2477
|
+
my $oldRawDataOffset = $$offsetPair[0][3][0];
|
|
2478
|
+
my $count = $$otherPos[2];
|
|
2479
|
+
my $i;
|
|
2480
|
+
# point to offsets in value data if more than one pointer
|
|
2481
|
+
$$otherPos[1] = Get32u(\$newData, $$otherPos[1]) if $count > 1;
|
|
2482
|
+
for ($i=0; $i<$count; ++$i) {
|
|
2483
|
+
my $oldTileOffset = $$otherPos[3][$i];
|
|
2484
|
+
my $ptrPos = $$otherPos[1] + 4 * $i;
|
|
2485
|
+
Set32u($newOffset + $oldTileOffset - $oldRawDataOffset, \$newData, $ptrPos);
|
|
2486
|
+
$fixup->AddFixup($ptrPos, $dataTag);
|
|
2487
|
+
$subIfdDataFixup->AddFixup($ptrPos, $dataTag) if $subIfdDataFixup;
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2413
2490
|
}
|
|
2414
2491
|
if ($ifd >= 0) {
|
|
2415
2492
|
# buff length must be even (Note: may have changed since $size was set)
|
|
@@ -334,13 +334,9 @@ sub DoWriteIPTC($$$)
|
|
|
334
334
|
# - improves speed
|
|
335
335
|
# - avoids changing current MD5 digest unnecessarily
|
|
336
336
|
# - avoids adding mandatory tags unless some other IPTC is changed
|
|
337
|
-
unless
|
|
337
|
+
return undef unless exists $$et{EDIT_DIRS}{$$dirInfo{DirName}} or
|
|
338
338
|
# standard IPTC tags in other locations should be edited too (eg. AFCP_IPTC)
|
|
339
|
-
($tagTablePtr eq \%Image::ExifTool::IPTC::Main and exists $$et{EDIT_DIRS}{IPTC})
|
|
340
|
-
{
|
|
341
|
-
print $out "$$et{INDENT} [nothing changed]\n" if $verbose;
|
|
342
|
-
return undef;
|
|
343
|
-
}
|
|
339
|
+
($tagTablePtr eq \%Image::ExifTool::IPTC::Main and exists $$et{EDIT_DIRS}{IPTC});
|
|
344
340
|
my $dataPt = $$dirInfo{DataPt};
|
|
345
341
|
unless ($dataPt) {
|
|
346
342
|
my $emptyData = '';
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#------------------------------------------------------------------------------
|
|
2
|
+
# File: WriteRIFF.pl
|
|
3
|
+
#
|
|
4
|
+
# Description: Write RIFF-format files
|
|
5
|
+
#
|
|
6
|
+
# Revisions: 2020-09-26 - P. Harvey Created
|
|
7
|
+
#
|
|
8
|
+
# Notes: Currently writes only WEBP files
|
|
9
|
+
#
|
|
10
|
+
# References: https://developers.google.com/speed/webp/docs/riff_container
|
|
11
|
+
#------------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
package Image::ExifTool::RIFF;
|
|
14
|
+
|
|
15
|
+
use strict;
|
|
16
|
+
|
|
17
|
+
# map of where information is stored in WebP image
|
|
18
|
+
my %webpMap = (
|
|
19
|
+
'XMP ' => 'RIFF', # (the RIFF chunk name is 'XMP ')
|
|
20
|
+
EXIF => 'RIFF',
|
|
21
|
+
ICCP => 'RIFF',
|
|
22
|
+
XMP => 'XMP ',
|
|
23
|
+
IFD0 => 'EXIF',
|
|
24
|
+
IFD1 => 'IFD0',
|
|
25
|
+
ICC_Profile => 'ICCP',
|
|
26
|
+
ExifIFD => 'IFD0',
|
|
27
|
+
GPS => 'IFD0',
|
|
28
|
+
SubIFD => 'IFD0',
|
|
29
|
+
GlobParamIFD => 'IFD0',
|
|
30
|
+
PrintIM => 'IFD0',
|
|
31
|
+
InteropIFD => 'ExifIFD',
|
|
32
|
+
MakerNotes => 'ExifIFD',
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
#------------------------------------------------------------------------------
|
|
36
|
+
# Write RIFF file (currently WebP-type only)
|
|
37
|
+
# Inputs: 0) ExifTool object ref, 1) dirInfo ref
|
|
38
|
+
# Returns: 1 on success, 0 if this wasn't a valid RIFF file, or -1 if
|
|
39
|
+
# an output file was specified and a write error occurred
|
|
40
|
+
sub WriteRIFF($$)
|
|
41
|
+
{
|
|
42
|
+
my ($et, $dirInfo) = @_;
|
|
43
|
+
$et or return 1; # allow dummy access to autoload this package
|
|
44
|
+
my $outfile = $$dirInfo{OutFile};
|
|
45
|
+
my $outsize = 0;
|
|
46
|
+
my $raf = $$dirInfo{RAF};
|
|
47
|
+
my ($buff, $err, $pass, %has, %dirDat, $imageWidth, $imageHeight);
|
|
48
|
+
|
|
49
|
+
# do this in 2 passes so we can set the size of the containing RIFF chunk
|
|
50
|
+
# without having to buffer the output (also to set the WebP_Flags)
|
|
51
|
+
for ($pass=0; ; ++$pass) {
|
|
52
|
+
my %doneDir;
|
|
53
|
+
# verify this is a valid RIFF file
|
|
54
|
+
return 0 unless $raf->Read($buff, 12) == 12;
|
|
55
|
+
return 0 unless $buff =~ /^(RIFF|RF64)....(.{4})/s;
|
|
56
|
+
|
|
57
|
+
unless ($1 eq 'RIFF' and $2 eq 'WEBP') {
|
|
58
|
+
my $type = $2;
|
|
59
|
+
$type =~ tr/-_a-zA-Z//dc;
|
|
60
|
+
$et->Error("Can't currently write $1 $type files");
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
SetByteOrder('II');
|
|
64
|
+
|
|
65
|
+
# determine which directories we must write for this file type
|
|
66
|
+
$et->InitWriteDirs(\%webpMap);
|
|
67
|
+
my $addDirs = $$et{ADD_DIRS};
|
|
68
|
+
my $editDirs = $$et{EDIT_DIRS};
|
|
69
|
+
my ($createVP8X, $deleteVP8X);
|
|
70
|
+
|
|
71
|
+
# write header
|
|
72
|
+
if ($pass) {
|
|
73
|
+
my $needsVP8X = ($has{ANIM} or $has{'XMP '} or $has{EXIF} or
|
|
74
|
+
$has{ALPH} or $has{ICCP});
|
|
75
|
+
if ($has{VP8X} and not $needsVP8X and $$et{CHANGED}) {
|
|
76
|
+
$deleteVP8X = 1; # delete the VP8X chunk
|
|
77
|
+
$outsize -= 18; # account for missing VP8X
|
|
78
|
+
} elsif ($needsVP8X and not $has{VP8X}) {
|
|
79
|
+
if (defined $imageWidth) {
|
|
80
|
+
++$$et{CHANGED};
|
|
81
|
+
$createVP8X = 1; # add VP8X chunk
|
|
82
|
+
$outsize += 18; # account for VP8X size
|
|
83
|
+
} else {
|
|
84
|
+
$et->Warn('Error getting image size for required VP8X chunk');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
# finally we can set the overall RIFF chunk size:
|
|
88
|
+
Set32u($outsize - 8, \$buff, 4);
|
|
89
|
+
Write($outfile, $buff) or $err = 1;
|
|
90
|
+
# create VP8X chunk if necessary
|
|
91
|
+
if ($createVP8X) {
|
|
92
|
+
$et->VPrint(0," Adding required VP8X chunk (Extended WEBP)\n");
|
|
93
|
+
my $flags = 0;
|
|
94
|
+
$flags |= 0x02 if $has{ANIM};
|
|
95
|
+
$flags |= 0x04 if $has{'XMP '};
|
|
96
|
+
$flags |= 0x08 if $has{EXIF};
|
|
97
|
+
$flags |= 0x10 if $has{ALPH};
|
|
98
|
+
$flags |= 0x20 if $has{ICCP};
|
|
99
|
+
Write($outfile, 'VP8X', pack('V3v', 10, $flags,
|
|
100
|
+
($imageWidth-1) | ((($imageHeight-1) & 0xff) << 24),
|
|
101
|
+
($imageHeight-1) >> 8));
|
|
102
|
+
# write ICCP after VP8X
|
|
103
|
+
Write($outfile, $dirDat{ICCP}) or $err = 1 if $dirDat{ICCP};
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
$outsize += length $buff;
|
|
107
|
+
}
|
|
108
|
+
my $pos = 12;
|
|
109
|
+
#
|
|
110
|
+
# Read chunks in RIFF image
|
|
111
|
+
#
|
|
112
|
+
for (;;) {
|
|
113
|
+
my ($tag, $len);
|
|
114
|
+
my $num = $raf->Read($buff, 8);
|
|
115
|
+
if ($num < 8) {
|
|
116
|
+
$num and $et->Error('RIFF format error'), return 1;
|
|
117
|
+
# all done if we hit end of file unless we need to add EXIF or XMP
|
|
118
|
+
last unless $$addDirs{EXIF} or $$addDirs{'XMP '} or $$addDirs{ICCP};
|
|
119
|
+
# continue to add required EXIF or XMP chunks
|
|
120
|
+
$num = $len = 0;
|
|
121
|
+
$buff = $tag = '';
|
|
122
|
+
} else {
|
|
123
|
+
$pos += 8;
|
|
124
|
+
($tag, $len) = unpack('a4V', $buff);
|
|
125
|
+
if ($len <= 0) {
|
|
126
|
+
if ($len < 0) {
|
|
127
|
+
$et->Error('Invalid chunk length');
|
|
128
|
+
return 1;
|
|
129
|
+
} elsif ($tag eq "\0\0\0\0") {
|
|
130
|
+
# avoid reading through corrupted files filled with nulls because it takes forever
|
|
131
|
+
$et->Error('Encountered empty null chunk. Processing aborted');
|
|
132
|
+
return 1;
|
|
133
|
+
} else { # (just in case a tag may have no data)
|
|
134
|
+
if ($pass) {
|
|
135
|
+
Write($outfile, $buff) or $err = 1;
|
|
136
|
+
} else {
|
|
137
|
+
$outsize += length $buff;
|
|
138
|
+
}
|
|
139
|
+
next;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
# RIFF chunks are padded to an even number of bytes
|
|
144
|
+
my $len2 = $len + ($len & 0x01);
|
|
145
|
+
# edit/add/delete necessary metadata chunks (EXIF must come before XMP)
|
|
146
|
+
if ($$editDirs{$tag} or $tag eq '' or ($tag eq 'XMP ' and $$addDirs{EXIF})) {
|
|
147
|
+
my $handledTag;
|
|
148
|
+
if ($len2) {
|
|
149
|
+
$et->Warn("Duplicate '${tag}' chunk") if $doneDir{$tag} and not $pass;
|
|
150
|
+
$doneDir{$tag} = 1;
|
|
151
|
+
$raf->Read($buff, $len2) == $len2 or $et->Error("Truncated '${tag}' chunk"), last;
|
|
152
|
+
$pos += $len2; # update current position
|
|
153
|
+
} else {
|
|
154
|
+
$buff = '';
|
|
155
|
+
}
|
|
156
|
+
#
|
|
157
|
+
# add/edit/delete EXIF/XMP/ICCP (note: EXIF must come before XMP, and ICCP is written elsewhere)
|
|
158
|
+
#
|
|
159
|
+
my %dirName = ( EXIF => 'IFD0', 'XMP ' => 'XMP', ICCP => 'ICC_Profile' );
|
|
160
|
+
my %tblName = ( EXIF => 'Exif', 'XMP ' => 'XMP', ICCP => 'ICC_Profile' );
|
|
161
|
+
my $dir;
|
|
162
|
+
foreach $dir ('EXIF', 'XMP ', 'ICCP' ) {
|
|
163
|
+
next unless $tag eq $dir or ($$addDirs{$dir} and
|
|
164
|
+
($tag eq '' or ($tag eq 'XMP ' and $dir eq 'EXIF')));
|
|
165
|
+
delete $$addDirs{$dir}; # (don't try to add again)
|
|
166
|
+
my $start;
|
|
167
|
+
unless ($pass) {
|
|
168
|
+
# write the EXIF and save the result for the next pass
|
|
169
|
+
my $dataPt = \$buff;
|
|
170
|
+
if ($tag eq 'EXIF') {
|
|
171
|
+
# (only need to set directory $start for EXIF)
|
|
172
|
+
if ($buff =~ /^Exif\0\0/) {
|
|
173
|
+
$et->Warn('Improper EXIF header') unless $pass;
|
|
174
|
+
$start = 6;
|
|
175
|
+
} else {
|
|
176
|
+
$start = 0;
|
|
177
|
+
}
|
|
178
|
+
} elsif ($dir ne $tag) {
|
|
179
|
+
# create from scratch
|
|
180
|
+
my $buf2 = '';
|
|
181
|
+
$dataPt = \$buf2;
|
|
182
|
+
}
|
|
183
|
+
# write the new directory to memory
|
|
184
|
+
my %dirInfo = (
|
|
185
|
+
DataPt => $dataPt,
|
|
186
|
+
DataPos => 0, # (relative to Base)
|
|
187
|
+
DirStart => $start,
|
|
188
|
+
Base => $pos - $len2,
|
|
189
|
+
Parent => $dir,
|
|
190
|
+
DirName => $dirName{$dir},
|
|
191
|
+
);
|
|
192
|
+
my $tagTablePtr = GetTagTable("Image::ExifTool::$tblName{$dir}::Main");
|
|
193
|
+
# (override writeProc for EXIF because it has the TIFF header)
|
|
194
|
+
my $writeProc = $dir eq 'EXIF' ? \&Image::ExifTool::WriteTIFF : undef;
|
|
195
|
+
$dirDat{$dir} = $et->WriteDirectory(\%dirInfo, $tagTablePtr, $writeProc);
|
|
196
|
+
}
|
|
197
|
+
if (defined $dirDat{$dir}) {
|
|
198
|
+
if ($dir eq $tag) {
|
|
199
|
+
$handledTag = 1; # set flag indicating we edited this tag
|
|
200
|
+
# increment CHANGED count if we are deleting the directory
|
|
201
|
+
++$$et{CHANGED} unless length $dirDat{$dir};
|
|
202
|
+
}
|
|
203
|
+
if (length $dirDat{$dir}) {
|
|
204
|
+
if ($pass) {
|
|
205
|
+
# write metadata chunk now (but not ICCP because it was added earlier)
|
|
206
|
+
Write($outfile, $dirDat{$dir}) or $err = 1 unless $dir eq 'ICCP';
|
|
207
|
+
} else {
|
|
208
|
+
# preserve (incorrect EXIF) header if it existed
|
|
209
|
+
my $hdr = $start ? substr($buff,0,$start) : '';
|
|
210
|
+
# (don't overwrite $len here because it may be XMP length)
|
|
211
|
+
my $dirLen = length($dirDat{$dir}) + length($hdr);
|
|
212
|
+
# add chunk header and padding
|
|
213
|
+
my $pad = $dirLen & 0x01 ? "\0" : '';
|
|
214
|
+
$dirDat{$dir} = $dir . Set32u($dirLen) . $hdr . $dirDat{$dir} . $pad;
|
|
215
|
+
$outsize += length($dirDat{$dir});
|
|
216
|
+
$has{$dir} = 1;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
#
|
|
222
|
+
# just copy XMP, EXIF or ICC if nothing changed
|
|
223
|
+
#
|
|
224
|
+
if (not $handledTag and length $buff) {
|
|
225
|
+
# write the chunk without changes
|
|
226
|
+
if ($pass) {
|
|
227
|
+
Write($outfile, $tag, Set32u($len), $buff) or $err = 1;
|
|
228
|
+
} else {
|
|
229
|
+
$outsize += 8 + length($buff);
|
|
230
|
+
$has{$tag} = 1;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
next;
|
|
234
|
+
}
|
|
235
|
+
$pos += $len2; # set read position at end of chunk data
|
|
236
|
+
#
|
|
237
|
+
# update necessary flags in VP8X chunk
|
|
238
|
+
#
|
|
239
|
+
if ($tag eq 'VP8X') {
|
|
240
|
+
my $buf2;
|
|
241
|
+
if ($len2 < 10 or $raf->Read($buf2, $len2) != $len2) {
|
|
242
|
+
$et->Error('Truncated VP8X chunk');
|
|
243
|
+
return 1;
|
|
244
|
+
}
|
|
245
|
+
if ($pass) {
|
|
246
|
+
if ($deleteVP8X) {
|
|
247
|
+
$et->VPrint(0," Deleting unnecessary VP8X chunk (Standard WEBP)\n");
|
|
248
|
+
next;
|
|
249
|
+
}
|
|
250
|
+
# ...but first set the VP8X flags
|
|
251
|
+
my $flags = Get32u(\$buf2, 0);
|
|
252
|
+
$flags &= ~0x2c; # (reset flags for everything we can write)
|
|
253
|
+
$flags |= 0x04 if $has{'XMP '};
|
|
254
|
+
$flags |= 0x08 if $has{EXIF};
|
|
255
|
+
$flags |= 0x20 if $has{ICCP};
|
|
256
|
+
Set32u($flags, \$buf2, 0);
|
|
257
|
+
Write($outfile, $buff, $buf2) or $err = 1;
|
|
258
|
+
} else {
|
|
259
|
+
# get the image size
|
|
260
|
+
$imageWidth = (Get32u(\$buf2, 4) & 0xffffff) + 1;
|
|
261
|
+
$imageHeight = (Get32u(\$buf2, 6) >> 8) + 1;
|
|
262
|
+
$outsize += 8 + $len2;
|
|
263
|
+
$has{$tag} = 1;
|
|
264
|
+
}
|
|
265
|
+
# write ICCP after VP8X
|
|
266
|
+
Write($outfile, $dirDat{ICCP}) or $err = 1 if $dirDat{ICCP};
|
|
267
|
+
next;
|
|
268
|
+
}
|
|
269
|
+
#
|
|
270
|
+
# just copy all other chunks
|
|
271
|
+
#
|
|
272
|
+
if ($pass) {
|
|
273
|
+
# write chunk header (still in $buff)
|
|
274
|
+
Write($outfile, $buff) or $err = 1;
|
|
275
|
+
} else {
|
|
276
|
+
$outsize += length $buff;
|
|
277
|
+
$has{$tag} = 1;
|
|
278
|
+
}
|
|
279
|
+
unless ($pass or defined $imageWidth) {
|
|
280
|
+
# get WebP image size from VP8 or VP8L header
|
|
281
|
+
if ($tag eq 'VP8 ' and $len2 >= 16) {
|
|
282
|
+
$raf->Read($buff, 16) == 16 or $et->Error('Truncated VP8 chunk'), return 1;
|
|
283
|
+
$outsize += 16;
|
|
284
|
+
if ($buff =~ /^...\x9d\x01\x2a/s) {
|
|
285
|
+
$imageWidth = Get16u(\$buff, 6) & 0x3fff;
|
|
286
|
+
$imageHeight = Get16u(\$buff, 8) & 0x3fff;
|
|
287
|
+
}
|
|
288
|
+
$len2 -= 16;
|
|
289
|
+
} elsif ($tag eq 'VP8L' and $len2 >= 6) {
|
|
290
|
+
$raf->Read($buff, 6) == 6 or $et->Error('Truncated VP8L chunk'), return 1;
|
|
291
|
+
$outsize += 6;
|
|
292
|
+
if ($buff =~ /^\x2f/s) {
|
|
293
|
+
$imageWidth = (Get16u(\$buff, 1) & 0x3fff) + 1;
|
|
294
|
+
$imageHeight = ((Get32u(\$buff, 2) >> 6) & 0x3fff) + 1;
|
|
295
|
+
}
|
|
296
|
+
$len2 -= 6;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if ($pass) {
|
|
300
|
+
# copy the chunk data in 64k blocks
|
|
301
|
+
while ($len2) {
|
|
302
|
+
my $num = $len2;
|
|
303
|
+
$num = 65536 if $num > 65536;
|
|
304
|
+
$raf->Read($buff, $num) == $num or $et->Error('Truncated RIFF chunk'), last;
|
|
305
|
+
Write($outfile, $buff) or $err = 1, last;
|
|
306
|
+
$len2 -= $num;
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
$raf->Seek($len2, 1) or $et->Error('Seek error'), last;
|
|
310
|
+
$outsize += $len2;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
last if $pass;
|
|
314
|
+
$raf->Seek(0,0) or $et->Error('Seek error'), last;
|
|
315
|
+
}
|
|
316
|
+
return $err ? -1 : 1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
1; # end
|
|
320
|
+
|
|
321
|
+
__END__
|
|
322
|
+
|
|
323
|
+
=head1 NAME
|
|
324
|
+
|
|
325
|
+
Image::ExifTool::WriteRIFF.pl - Write RIFF-format files
|
|
326
|
+
|
|
327
|
+
=head1 SYNOPSIS
|
|
328
|
+
|
|
329
|
+
This file is autoloaded by Image::ExifTool::RIFF.
|
|
330
|
+
|
|
331
|
+
=head1 DESCRIPTION
|
|
332
|
+
|
|
333
|
+
This file contains routines to write metadata to RIFF-format files.
|
|
334
|
+
|
|
335
|
+
=head1 NOTES
|
|
336
|
+
|
|
337
|
+
Currently writes only WebP files.
|
|
338
|
+
|
|
339
|
+
=head1 AUTHOR
|
|
340
|
+
|
|
341
|
+
Copyright 2003-2022, Phil Harvey (philharvey66 at gmail.com)
|
|
342
|
+
|
|
343
|
+
This library is free software; you can redistribute it and/or modify it
|
|
344
|
+
under the same terms as Perl itself.
|
|
345
|
+
|
|
346
|
+
=head1 REFERENCES
|
|
347
|
+
|
|
348
|
+
=over 4
|
|
349
|
+
|
|
350
|
+
=item L<https://developers.google.com/speed/webp/docs/riff_container>
|
|
351
|
+
|
|
352
|
+
=back
|
|
353
|
+
|
|
354
|
+
=head1 SEE ALSO
|
|
355
|
+
|
|
356
|
+
L<Image::ExifTool::Photoshop(3pm)|Image::ExifTool::RIFF>,
|
|
357
|
+
L<Image::ExifTool(3pm)|Image::ExifTool>
|
|
358
|
+
|
|
359
|
+
=cut
|