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.
Files changed (61) hide show
  1. package/bin/Changes +112 -3
  2. package/bin/MANIFEST +9 -0
  3. package/bin/META.json +1 -1
  4. package/bin/META.yml +1 -1
  5. package/bin/README +45 -44
  6. package/bin/config_files/acdsee.config +2 -1
  7. package/bin/config_files/frameCount.config +56 -0
  8. package/bin/config_files/tiff_version.config +1 -1
  9. package/bin/exiftool +85 -73
  10. package/bin/fmt_files/gpx.fmt +3 -0
  11. package/bin/fmt_files/gpx_wpt.fmt +3 -0
  12. package/bin/lib/Image/ExifTool/Apple.pm +6 -2
  13. package/bin/lib/Image/ExifTool/BuildTagLookup.pm +17 -9
  14. package/bin/lib/Image/ExifTool/Canon.pm +33 -15
  15. package/bin/lib/Image/ExifTool/CanonRaw.pm +8 -1
  16. package/bin/lib/Image/ExifTool/CanonVRD.pm +7 -8
  17. package/bin/lib/Image/ExifTool/EXE.pm +9 -1
  18. package/bin/lib/Image/ExifTool/Exif.pm +11 -7
  19. package/bin/lib/Image/ExifTool/FLAC.pm +17 -3
  20. package/bin/lib/Image/ExifTool/FLIR.pm +4 -3
  21. package/bin/lib/Image/ExifTool/FlashPix.pm +4 -2
  22. package/bin/lib/Image/ExifTool/FujiFilm.pm +31 -5
  23. package/bin/lib/Image/ExifTool/GPS.pm +2 -2
  24. package/bin/lib/Image/ExifTool/ICC_Profile.pm +3 -2
  25. package/bin/lib/Image/ExifTool/ICO.pm +141 -0
  26. package/bin/lib/Image/ExifTool/ID3.pm +6 -6
  27. package/bin/lib/Image/ExifTool/M2TS.pm +55 -8
  28. package/bin/lib/Image/ExifTool/MIE.pm +9 -3
  29. package/bin/lib/Image/ExifTool/MISB.pm +494 -0
  30. package/bin/lib/Image/ExifTool/MakerNotes.pm +3 -1
  31. package/bin/lib/Image/ExifTool/Matroska.pm +24 -16
  32. package/bin/lib/Image/ExifTool/Nikon.pm +39 -31
  33. package/bin/lib/Image/ExifTool/NikonSettings.pm +5 -3
  34. package/bin/lib/Image/ExifTool/Panasonic.pm +21 -4
  35. package/bin/lib/Image/ExifTool/PanasonicRaw.pm +25 -5
  36. package/bin/lib/Image/ExifTool/Photoshop.pm +29 -3
  37. package/bin/lib/Image/ExifTool/QuickTime.pm +113 -8
  38. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +44 -6
  39. package/bin/lib/Image/ExifTool/README +1 -1
  40. package/bin/lib/Image/ExifTool/RIFF.pm +106 -9
  41. package/bin/lib/Image/ExifTool/Samsung.pm +2 -2
  42. package/bin/lib/Image/ExifTool/Sigma.pm +27 -1
  43. package/bin/lib/Image/ExifTool/SigmaRaw.pm +37 -13
  44. package/bin/lib/Image/ExifTool/Sony.pm +8 -3
  45. package/bin/lib/Image/ExifTool/TagLookup.pm +188 -7
  46. package/bin/lib/Image/ExifTool/TagNames.pod +3051 -2732
  47. package/bin/lib/Image/ExifTool/Text.pm +3 -4
  48. package/bin/lib/Image/ExifTool/Torrent.pm +2 -3
  49. package/bin/lib/Image/ExifTool/Validate.pm +3 -3
  50. package/bin/lib/Image/ExifTool/WriteCanonRaw.pl +7 -0
  51. package/bin/lib/Image/ExifTool/WriteExif.pl +100 -23
  52. package/bin/lib/Image/ExifTool/WriteIPTC.pl +2 -6
  53. package/bin/lib/Image/ExifTool/WriteRIFF.pl +359 -0
  54. package/bin/lib/Image/ExifTool/Writer.pl +10 -3
  55. package/bin/lib/Image/ExifTool/XMP.pm +76 -58
  56. package/bin/lib/Image/ExifTool/XMP2.pl +11 -4
  57. package/bin/lib/Image/ExifTool.pm +75 -15
  58. package/bin/lib/Image/ExifTool.pod +61 -57
  59. package/bin/perl-Image-ExifTool.spec +43 -43
  60. package/bin/pp_build_exe.args +7 -4
  61. 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.03';
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::XMP::IsUTF8($dataPt, 1);
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::XMP::IsUTF8(\$buff);
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.05';
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::XMP::IsUTF8(\$value) >= 0) {
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.18';
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
- 0x7031 => 1, 0x7032 => 1, 0x7034 => 1, 0x7035 => 1, 0x7036 => 1, 0x7037 => 1,
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
- # sort entries if necessary (but not in maker notes IFDs)
600
- unless ($inMakerNotes) {
601
- my $lastID = -1;
602
- for ($index=0; $index<$numEntries; ++$index) {
603
- my $tagID = Get16u($dataPt, $dirStart + 2 + 12 * $index);
604
- # check for proper sequence (but ignore null entries at end)
605
- if ($tagID < $lastID and ($tagID or $$tagTablePtr{0})) {
606
- SortIFD($dataPt, $dirStart, $numEntries, $$tagTablePtr{0});
607
- $et->Warn("Entries in $name were out of sequence. Fixed.",1);
608
- last;
609
- }
610
- $lastID = $tagID;
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
- if ($stripOffsets and $$stripOffsets[0]{PanasonicHack}) {
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 and $$tagInfo{PanasonicHack}) {
2411
- Set32u($newOffset, \$newData, $otherPos);
2412
- $fixup->AddFixup($otherPos, $dataTag);
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 (exists $$et{EDIT_DIRS}{$$dirInfo{DirName}} or
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