exiftool-vendored.pl 12.62.0 → 12.67.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 (56) hide show
  1. package/bin/Changes +96 -1
  2. package/bin/MANIFEST +4 -0
  3. package/bin/META.json +5 -3
  4. package/bin/META.yml +5 -3
  5. package/bin/Makefile.PL +7 -1
  6. package/bin/README +50 -46
  7. package/bin/config_files/guano.config +161 -0
  8. package/bin/exiftool +116 -79
  9. package/bin/lib/Image/ExifTool/7Z.pm +793 -0
  10. package/bin/lib/Image/ExifTool/Apple.pm +19 -8
  11. package/bin/lib/Image/ExifTool/BigTIFF.pm +8 -1
  12. package/bin/lib/Image/ExifTool/Canon.pm +33 -12
  13. package/bin/lib/Image/ExifTool/CanonRaw.pm +4 -4
  14. package/bin/lib/Image/ExifTool/CanonVRD.pm +4 -1
  15. package/bin/lib/Image/ExifTool/Exif.pm +31 -14
  16. package/bin/lib/Image/ExifTool/FlashPix.pm +8 -2
  17. package/bin/lib/Image/ExifTool/FujiFilm.pm +6 -3
  18. package/bin/lib/Image/ExifTool/GPS.pm +5 -2
  19. package/bin/lib/Image/ExifTool/Geotag.pm +5 -2
  20. package/bin/lib/Image/ExifTool/Jpeg2000.pm +226 -28
  21. package/bin/lib/Image/ExifTool/Lang/fr.pm +1467 -202
  22. package/bin/lib/Image/ExifTool/MPF.pm +2 -1
  23. package/bin/lib/Image/ExifTool/Matroska.pm +16 -1
  24. package/bin/lib/Image/ExifTool/MinoltaRaw.pm +2 -2
  25. package/bin/lib/Image/ExifTool/Nikon.pm +948 -31
  26. package/bin/lib/Image/ExifTool/NikonCustom.pm +874 -63
  27. package/bin/lib/Image/ExifTool/PDF.pm +23 -5
  28. package/bin/lib/Image/ExifTool/PLIST.pm +8 -1
  29. package/bin/lib/Image/ExifTool/PLUS.pm +19 -4
  30. package/bin/lib/Image/ExifTool/PNG.pm +6 -6
  31. package/bin/lib/Image/ExifTool/Pentax.pm +3 -1
  32. package/bin/lib/Image/ExifTool/PhaseOne.pm +5 -5
  33. package/bin/lib/Image/ExifTool/QuickTime.pm +91 -30
  34. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +20 -19
  35. package/bin/lib/Image/ExifTool/README +2 -2
  36. package/bin/lib/Image/ExifTool/RIFF.pm +11 -9
  37. package/bin/lib/Image/ExifTool/Samsung.pm +227 -227
  38. package/bin/lib/Image/ExifTool/Shortcuts.pm +2 -1
  39. package/bin/lib/Image/ExifTool/SigmaRaw.pm +4 -4
  40. package/bin/lib/Image/ExifTool/Sony.pm +229 -30
  41. package/bin/lib/Image/ExifTool/TagLookup.pm +4758 -4633
  42. package/bin/lib/Image/ExifTool/TagNames.pod +715 -23
  43. package/bin/lib/Image/ExifTool/Validate.pm +17 -1
  44. package/bin/lib/Image/ExifTool/WriteExif.pl +9 -7
  45. package/bin/lib/Image/ExifTool/WriteQuickTime.pl +21 -9
  46. package/bin/lib/Image/ExifTool/WriteXMP.pl +2 -2
  47. package/bin/lib/Image/ExifTool/Writer.pl +36 -12
  48. package/bin/lib/Image/ExifTool/XMP.pm +14 -2
  49. package/bin/lib/Image/ExifTool/XMP2.pl +33 -1
  50. package/bin/lib/Image/ExifTool/XMPStruct.pl +96 -28
  51. package/bin/lib/Image/ExifTool/ZIP.pm +5 -5
  52. package/bin/lib/Image/ExifTool.pm +184 -132
  53. package/bin/lib/Image/ExifTool.pod +124 -58
  54. package/bin/perl-Image-ExifTool.spec +44 -44
  55. package/bin/pp_build_exe.args +7 -4
  56. package/package.json +3 -3
@@ -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';
20
+ $VERSION = '1.21';
21
21
 
22
22
  use Image::ExifTool qw(:Utils);
23
23
  use Image::ExifTool::Exif;
@@ -56,6 +56,15 @@ use Image::ExifTool::Exif;
56
56
  0x212 => 1, 0x9204 => 1, 0xa210 => 1, 0xa500 => 221,
57
57
  0x213 => 1, 0x9205 => 1, 0xa214 => 1,
58
58
  0x214 => 1, 0x9206 => 1, 0xa215 => 1,
59
+
60
+ # new Exif 3.0 tags
61
+ 0xa436 => 300,
62
+ 0xa437 => 300,
63
+ 0xa438 => 300,
64
+ 0xa439 => 300,
65
+ 0xa43a => 300,
66
+ 0xa43b => 300,
67
+ 0xa43c => 300,
59
68
  );
60
69
 
61
70
  # GPSVersionID numbers when each tag was introduced
@@ -147,6 +156,13 @@ my %stdFormat = (
147
156
  0xc68d => 'int(16|32)u', 0xc791 => 'int(16|32)u',
148
157
  0xc68e => 'int(16|32)u', 0xc792 => 'int(16|32)u',
149
158
  0xc6d2 => '', 0xc793 => '(int16u|int32u|rational64u)',
159
+ # Exif 3.0 spec
160
+ 0x10e => 'string|utf8', 0xa430 => 'string|utf8', 0xa439 => 'string|utf8',
161
+ 0x10f => 'string|utf8', 0xa433 => 'string|utf8', 0xa43a => 'string|utf8',
162
+ 0x110 => 'string|utf8', 0xa434 => 'string|utf8', 0xa43b => 'string|utf8',
163
+ 0x131 => 'string|utf8', 0xa436 => 'string|utf8', 0xa43c => 'string|utf8',
164
+ 0x13b => 'string|utf8', 0xa437 => 'string|utf8', 0xa43a => 'string|utf8',
165
+ 0x8298 => 'string|utf8', 0xa438 => 'string|utf8',
150
166
  },
151
167
  );
152
168
 
@@ -420,15 +420,15 @@ sub ValidateImageData($$$;$)
420
420
  }
421
421
 
422
422
  #------------------------------------------------------------------------------
423
- # Add specified image data to ImageDataMD5 hash
423
+ # Add specified image data to ImageDataHash hash
424
424
  # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) lookup for [tagInfo,value] based on tagID
425
- sub AddImageDataMD5($$$)
425
+ sub AddImageDataHash($$$)
426
426
  {
427
427
  my ($et, $dirInfo, $offsetInfo) = @_;
428
428
  my ($tagID, $offset, $buff);
429
429
 
430
430
  my $verbose = $et->Options('Verbose');
431
- my $md5 = $$et{ImageDataMD5};
431
+ my $hash = $$et{ImageDataHash};
432
432
  my $raf = $$dirInfo{RAF};
433
433
 
434
434
  foreach $tagID (sort keys %$offsetInfo) {
@@ -451,12 +451,12 @@ sub AddImageDataMD5($$$)
451
451
  my $size = shift @sizes;
452
452
  next unless $offset =~ /^\d+$/ and $size and $size =~ /^\d+$/ and $size;
453
453
  next unless $raf->Seek($offset, 0); # (offset is absolute)
454
- $total += $et->ImageDataMD5($raf, $size);
454
+ $total += $et->ImageDataHash($raf, $size);
455
455
  }
456
456
  if ($verbose) {
457
457
  my $name = "$$dirInfo{DirName}:$$tagInfo{Name}";
458
458
  $name =~ s/Offsets?|Start$//;
459
- $et->VPrint(0, "$$et{INDENT}(ImageDataMD5: $total bytes of $name data)\n");
459
+ $et->VPrint(0, "$$et{INDENT}(ImageDataHash: $total bytes of $name data)\n");
460
460
  }
461
461
  }
462
462
  }
@@ -780,7 +780,7 @@ Entry: for (;;) {
780
780
  $readFormat = $oldFormat = Get16u($dataPt, $entry+2);
781
781
  $readCount = $oldCount = Get32u($dataPt, $entry+4);
782
782
  undef $oldImageData;
783
- if ($oldFormat < 1 or $oldFormat > 13 and not ($oldFormat == 16 and $$et{Make} eq 'Apple' and $inMakerNotes)) {
783
+ if (($oldFormat < 1 or $oldFormat > 13) and $oldFormat != 129 and not ($oldFormat == 16 and $$et{Make} eq 'Apple' and $inMakerNotes)) {
784
784
  my $msg = "Bad format ($oldFormat) for $name entry $index";
785
785
  # patch to preserve invalid directory entries in SubIFD3 of
786
786
  # various Kodak Z-series cameras (Z812, Z1085IS, Z1275)
@@ -1277,7 +1277,9 @@ NoWrite: next if $isNew > 0;
1277
1277
  $et->Warn("Writing large value for $name",1);
1278
1278
  }
1279
1279
  # re-code if necessary
1280
- if ($strEnc and $newFormName eq 'string') {
1280
+ if ($newFormName eq 'utf8') {
1281
+ $newValue = $et->Encode($newValue, 'UTF8');
1282
+ } elsif ($strEnc and $newFormName eq 'string') {
1281
1283
  $newValue = $et->Encode($newValue, $strEnc);
1282
1284
  }
1283
1285
  } else {
@@ -973,16 +973,14 @@ sub WriteQuickTime($$$)
973
973
  }
974
974
  } elsif ($tag eq 'CTBO' or $tag eq 'uuid') { # hack for updating CR3 CTBO offsets
975
975
  push @{$$dirInfo{ChunkOffset}}, [ $tag, length($$outfile), length($hdr) + $size ];
976
- } elsif (not $flg) {
977
- my $grp = $$et{CUR_WRITE_GROUP} || $parent;
978
- $et->Error("Can't locate data reference to update offsets for $grp");
979
- return $rtnVal;
976
+ } elsif (not $flg or $flg == 1) {
977
+ # assume "1" if stsd is yet to be read
978
+ $flg or $$et{AssumedDataRef} = 1;
979
+ # must update offsets since the data is in this file
980
+ push @{$$dirInfo{ChunkOffset}}, [ $tag, length($$outfile) + length($hdr), $size ];
980
981
  } elsif ($flg == 3) {
981
982
  $et->Error("Can't write files with mixed internal/external media data");
982
983
  return $rtnVal;
983
- } elsif ($flg == 1) {
984
- # must update offsets since the data is in this file
985
- push @{$$dirInfo{ChunkOffset}}, [ $tag, length($$outfile) + length($hdr), $size ];
986
984
  }
987
985
  }
988
986
 
@@ -1036,8 +1034,10 @@ sub WriteQuickTime($$$)
1036
1034
 
1037
1035
  if ($subdir) { # process atoms in this container from a buffer in memory
1038
1036
 
1039
- undef $$et{HandlerType} if $tag eq 'trak'; # init handler type for this track
1040
-
1037
+ if ($tag eq 'trak') {
1038
+ undef $$et{HandlerType}; # init handler type for this track
1039
+ delete $$et{AssumedDataRef};
1040
+ }
1041
1041
  my $subName = $$subdir{DirName} || $$tagInfo{Name};
1042
1042
  my $start = $$subdir{Start} || 0;
1043
1043
  my $base = ($$dirInfo{Base} || 0) + $raf->Tell() - $size;
@@ -1103,6 +1103,11 @@ sub WriteQuickTime($$$)
1103
1103
  $$et{CHANGED} = $oldChanged;
1104
1104
  undef $newData;
1105
1105
  }
1106
+ if ($tag eq 'trak' and $$et{AssumedDataRef}) {
1107
+ my $grp = $$et{CUR_WRITE_GROUP} || $dirName;
1108
+ $et->Error("Can't locate data reference to update offsets for $grp");
1109
+ delete $$et{AssumedDataRef};
1110
+ }
1106
1111
  $$et{CUR_WRITE_GROUP} = $oldWriteGroup;
1107
1112
  SetByteOrder('MM');
1108
1113
  # add back header if necessary
@@ -1405,6 +1410,13 @@ sub WriteQuickTime($$$)
1405
1410
  $flg = 1; # (this seems to be the case)
1406
1411
  }
1407
1412
  $$et{QtDataFlg} = $flg;
1413
+ if ($$et{AssumedDataRef}) {
1414
+ if ($flg != $$et{AssumedDataRef}) {
1415
+ my $grp = $$et{CUR_WRITE_GROUP} || $parent;
1416
+ $et->Error("Assumed incorrect data reference for $grp (was $flg)");
1417
+ }
1418
+ delete $$et{AssumedDataRef};
1419
+ }
1408
1420
  }
1409
1421
  if ($tagInfo and $$tagInfo{WriteLast}) {
1410
1422
  $writeLast = ($writeLast || '') . $hdr . $buff;
@@ -176,7 +176,7 @@ sub CheckXMP($$$;$)
176
176
  require 'Image/ExifTool/XMPStruct.pl';
177
177
  my ($item, $err, $w, $warn);
178
178
  unless (ref $$valPtr) {
179
- ($$valPtr, $warn) = InflateStruct($valPtr);
179
+ ($$valPtr, $warn) = InflateStruct($et, $valPtr);
180
180
  # expect a structure HASH ref or ARRAY of structures
181
181
  unless (ref $$valPtr) {
182
182
  $$valPtr eq '' and $$valPtr = { }, return undef; # allow empty structures
@@ -189,7 +189,7 @@ sub CheckXMP($$$;$)
189
189
  $$valPtr = \@copy; # return the copy
190
190
  foreach $item (@copy) {
191
191
  unless (ref $item eq 'HASH') {
192
- ($item, $w) = InflateStruct(\$item); # deserialize structure
192
+ ($item, $w) = InflateStruct($et, \$item); # deserialize structure
193
193
  $w and $warn = $w;
194
194
  next if ref $item eq 'HASH';
195
195
  $err = 'Improperly formed structure';
@@ -139,7 +139,7 @@ my @delGroups = qw(
139
139
  Adobe AFCP APP0 APP1 APP2 APP3 APP4 APP5 APP6 APP7 APP8 APP9 APP10 APP11
140
140
  APP12 APP13 APP14 APP15 CanonVRD CIFF Ducky EXIF ExifIFD File FlashPix
141
141
  FotoStation GlobParamIFD GPS ICC_Profile IFD0 IFD1 Insta360 InteropIFD IPTC
142
- ItemList JFIF Jpeg2000 Keys MakerNotes Meta MetaIFD Microsoft MIE MPF
142
+ ItemList JFIF Jpeg2000 JUMBF Keys MakerNotes Meta MetaIFD Microsoft MIE MPF
143
143
  NikonApp NikonCapture PDF PDF-update PhotoMechanic Photoshop PNG PNG-pHYs
144
144
  PrintIM QuickTime RMETA RSRC SubIFD Trailer UserData XML XML-* XMP XMP-*
145
145
  );
@@ -1028,7 +1028,7 @@ TAG: foreach $tagInfo (@matchingTags) {
1028
1028
  foreach (@vals) {
1029
1029
  if (ref $_ eq 'HASH') {
1030
1030
  require 'Image/ExifTool/XMPStruct.pl';
1031
- $_ = Image::ExifTool::XMP::SerializeStruct($_);
1031
+ $_ = Image::ExifTool::XMP::SerializeStruct($self, $_);
1032
1032
  }
1033
1033
  print $out "$$self{INDENT2}$verb $wgrp1:$tag$fromList if value is '${_}'\n";
1034
1034
  }
@@ -1293,6 +1293,7 @@ sub SetNewValuesFromFile($$;@)
1293
1293
  HexTagIDs => $$options{HexTagIDs},
1294
1294
  IgnoreMinorErrors=>$$options{IgnoreMinorErrors},
1295
1295
  IgnoreTags => $$options{IgnoreTags},
1296
+ ImageHashType => $$options{ImageHashType},
1296
1297
  Lang => $$options{Lang},
1297
1298
  LargeFileSupport=> $$options{LargeFileSupport},
1298
1299
  List => 1,
@@ -1313,11 +1314,13 @@ sub SetNewValuesFromFile($$;@)
1313
1314
  ScanForXMP => $$options{ScanForXMP},
1314
1315
  StrictDate => defined $$options{StrictDate} ? $$options{StrictDate} : 1,
1315
1316
  Struct => $structOpt,
1317
+ StructFormat => $$options{StructFormat},
1316
1318
  SystemTags => $$options{SystemTags},
1317
1319
  TimeZone => $$options{TimeZone},
1318
1320
  Unknown => $$options{Unknown},
1319
1321
  UserParam => $$options{UserParam},
1320
1322
  Validate => $$options{Validate},
1323
+ WindowsWideFile => $$options{WindowsWideFile},
1321
1324
  XAttrTags => $$options{XAttrTags},
1322
1325
  XMPAutoConv => $$options{XMPAutoConv},
1323
1326
  );
@@ -1763,7 +1766,14 @@ GNV_TagInfo: foreach $tagInfo (@tagInfoList) {
1763
1766
  }
1764
1767
  }
1765
1768
  # return our value(s)
1766
- return @$vals if wantarray;
1769
+ if (wantarray) {
1770
+ # remove duplicates if requested
1771
+ if (@$vals > 1 and $self->Options('NoDups')) {
1772
+ my %seen;
1773
+ @$vals = grep { !$seen{$_}++ } @$vals;
1774
+ }
1775
+ return @$vals;
1776
+ }
1767
1777
  return $$vals[0];
1768
1778
  }
1769
1779
 
@@ -2016,7 +2026,7 @@ sub SetFileName($$;$$$)
2016
2026
  # protect against empty file name
2017
2027
  length $newName or $self->Warn('New file name is empty'), return -1;
2018
2028
  # don't replace existing file
2019
- if ($self->Exists($newName) and (not defined $usedFlag or $usedFlag)) {
2029
+ if ($self->Exists($newName, 1) and (not defined $usedFlag or $usedFlag)) {
2020
2030
  if ($file ne $newName or $opt =~ /Link$/) {
2021
2031
  # allow for case-insensitive filesystem
2022
2032
  if ($opt =~ /Link$/ or not $self->IsSameFile($file, $newName)) {
@@ -2395,7 +2405,7 @@ sub WriteInfo($$;$$)
2395
2405
  $outBuff = '';
2396
2406
  $outRef = \$outBuff;
2397
2407
  $outPos = 0;
2398
- } elsif ($self->Exists($outfile)) {
2408
+ } elsif ($self->Exists($outfile, 1)) {
2399
2409
  $self->Error("File already exists: $outfile");
2400
2410
  } elsif ($self->Open(\*EXIFTOOL_OUTFILE, $outfile, '>')) {
2401
2411
  $outRef = \*EXIFTOOL_OUTFILE;
@@ -3283,7 +3293,7 @@ sub InsertTagValues($$$;$$$)
3283
3293
  }
3284
3294
  } elsif (ref $val eq 'HASH') {
3285
3295
  require 'Image/ExifTool/XMPStruct.pl';
3286
- $val = Image::ExifTool::XMP::SerializeStruct($val);
3296
+ $val = Image::ExifTool::XMP::SerializeStruct($self, $val);
3287
3297
  } elsif (not defined $val) {
3288
3298
  $val = $$self{OPTIONS}{MissingTagValue} if $asList;
3289
3299
  }
@@ -3767,6 +3777,8 @@ sub GetNewValueHash($$;$$$$)
3767
3777
  # this is a bit tricky: we want to add to a protected nvHash only if we
3768
3778
  # are adding a conditional delete ($_[5] true or DelValue with no Shift)
3769
3779
  # or accumulating List items (NoReplace true)
3780
+ # (NOTE: this should be looked into --> lists may be accumulated instead of being replaced
3781
+ # as expected when copying to the same list from different dynamic -tagsFromFile source files)
3770
3782
  if ($protect and not ($opts{create} and ($$nvHash{NoReplace} or $_[5] or
3771
3783
  ($$nvHash{DelValue} and not defined $$nvHash{Shift}))))
3772
3784
  {
@@ -4908,6 +4920,11 @@ sub InverseDateTime($$;$$)
4908
4920
  my $fs = ($fmt =~ s/%f$// and $val =~ s/(\.\d+)\s*$//) ? $1 : '';
4909
4921
  my ($lib, $wrn, @a);
4910
4922
  TryLib: for ($lib=$strptimeLib; ; $lib='') {
4923
+ # handle %s format ourself (not supported in Fedora, see forum15032)
4924
+ if ($fmt eq '%s') {
4925
+ $val = ConvertUnixTime($val, 1);
4926
+ last;
4927
+ }
4911
4928
  if (not $lib) {
4912
4929
  last unless $$self{OPTIONS}{StrictDate};
4913
4930
  warn $wrn || "Install POSIX::strptime or Time::Piece for inverse date/time conversions\n";
@@ -5598,6 +5615,8 @@ sub WriteJPEG($$)
5598
5615
  $s =~ /^(Meta|META|Exif)\0\0/ and $dirName = 'Meta';
5599
5616
  } elsif ($marker == 0xe5) {
5600
5617
  $s =~ /^RMETA\0/ and $dirName = 'RMETA';
5618
+ } elsif ($marker == 0xeb) {
5619
+ $s =~ /^JP/ and $dirName = 'JUMBF';
5601
5620
  } elsif ($marker == 0xec) {
5602
5621
  $s =~ /^Ducky/ and $dirName = 'Ducky';
5603
5622
  } elsif ($marker == 0xed) {
@@ -6463,6 +6482,11 @@ sub WriteJPEG($$)
6463
6482
  $segType = 'Ricoh RMETA';
6464
6483
  $$delGroup{RMETA} and $del = 1;
6465
6484
  }
6485
+ } elsif ($marker == 0xeb) { # APP10 (JUMBF)
6486
+ if ($$segDataPt =~ /^JP/) {
6487
+ $segType = 'JUMBF';
6488
+ $$delGroup{JUMBF} and $del = 1;
6489
+ }
6466
6490
  } elsif ($marker == 0xec) { # APP12 (Ducky)
6467
6491
  if ($$segDataPt =~ /^Ducky/) {
6468
6492
  $segType = 'Ducky';
@@ -6880,14 +6904,14 @@ sub SetFileTime($$;$$$$)
6880
6904
  }
6881
6905
 
6882
6906
  #------------------------------------------------------------------------------
6883
- # Add data to MD5 checksum
6907
+ # Add data to hash checksum
6884
6908
  # Inputs: 0) ExifTool ref, 1) RAF ref, 2) data size (or undef to read to end of file),
6885
6909
  # 3) data name (or undef for no warnings or messages), 4) flag for no verbose message
6886
- # Returns: number of bytes read and MD5'd
6887
- sub ImageDataMD5($$$;$$)
6910
+ # Returns: number of bytes read and hashed
6911
+ sub ImageDataHash($$$;$$)
6888
6912
  {
6889
6913
  my ($self, $raf, $size, $type, $noMsg) = @_;
6890
- my $md5 = $$self{ImageDataMD5} or return;
6914
+ my $hash = $$self{ImageDataHash} or return;
6891
6915
  my ($bytesRead, $n) = (0, 65536);
6892
6916
  my $buff;
6893
6917
  for (;;) {
@@ -6900,11 +6924,11 @@ sub ImageDataMD5($$$;$$)
6900
6924
  $self->Warn("Error reading $type data") if $type and defined $size;
6901
6925
  last;
6902
6926
  }
6903
- $md5->add($buff);
6927
+ $hash->add($buff);
6904
6928
  $bytesRead += length $buff;
6905
6929
  }
6906
6930
  if ($$self{OPTIONS}{Verbose} and $bytesRead and $type and not $noMsg) {
6907
- $self->VPrint(0, "$$self{INDENT}(ImageDataMD5: $bytesRead bytes of $type data)\n");
6931
+ $self->VPrint(0, "$$self{INDENT}(ImageDataHash: $bytesRead bytes of $type data)\n");
6908
6932
  }
6909
6933
  return $bytesRead;
6910
6934
  }
@@ -50,7 +50,7 @@ use Image::ExifTool::Exif;
50
50
  use Image::ExifTool::GPS;
51
51
  require Exporter;
52
52
 
53
- $VERSION = '3.59';
53
+ $VERSION = '3.60';
54
54
  @ISA = qw(Exporter);
55
55
  @EXPORT_OK = qw(EscapeXML UnescapeXML);
56
56
 
@@ -144,6 +144,7 @@ my %xmpNS = (
144
144
  xmpTPg => 'http://ns.adobe.com/xap/1.0/t/pg/',
145
145
  xmpidq => 'http://ns.adobe.com/xmp/Identifier/qual/1.0/',
146
146
  xmpPLUS => 'http://ns.adobe.com/xap/1.0/PLUS/',
147
+ panorama => 'http://ns.adobe.com/photoshop/1.0/panorama-profile',
147
148
  dex => 'http://ns.optimasc.com/dex/1.0/',
148
149
  mediapro => 'http://ns.iview-multimedia.com/mediapro/1.0/',
149
150
  expressionmedia => 'http://ns.microsoft.com/expressionmedia/1.0/',
@@ -199,6 +200,7 @@ my %xmpNS = (
199
200
  ast => 'http://ns.nikon.com/asteroid/1.0/',
200
201
  nine => 'http://ns.nikon.com/nine/1.0/',
201
202
  hdr_metadata => 'http://ns.adobe.com/hdr-metadata/1.0/',
203
+ hdrgm => 'http://ns.adobe.com/hdr-gain-map/1.0/',
202
204
  );
203
205
 
204
206
  # build reverse namespace lookup
@@ -709,6 +711,10 @@ my %sRangeMask = (
709
711
  Name => 'xmpPLUS',
710
712
  SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpPLUS' },
711
713
  },
714
+ panorama => {
715
+ Name => 'panorama',
716
+ SubDirectory => { TagTable => 'Image::ExifTool::XMP::panorama' },
717
+ },
712
718
  plus => {
713
719
  Name => 'plus',
714
720
  SubDirectory => { TagTable => 'Image::ExifTool::PLUS::XMP' },
@@ -909,6 +915,10 @@ my %sRangeMask = (
909
915
  Name => 'hdr',
910
916
  SubDirectory => { TagTable => 'Image::ExifTool::XMP::hdr' },
911
917
  },
918
+ hdrgm => {
919
+ Name => 'hdrgm',
920
+ SubDirectory => { TagTable => 'Image::ExifTool::XMP::hdrgm' },
921
+ },
912
922
  );
913
923
 
914
924
  # hack to allow XML containing Dublin Core metadata to be handled like XMP (eg. EPUB - see ZIP.pm)
@@ -2566,7 +2576,9 @@ my %sPantryItem = (
2566
2576
  %xmpTableDefaults,
2567
2577
  GROUPS => { 1 => 'XMP-et', 2 => 'Image' },
2568
2578
  NAMESPACE => 'et',
2569
- OriginalImageMD5 => { Notes => 'used to store ExifTool ImageDataMD5 digest' },
2579
+ OriginalImageHash => { Notes => 'used to store ExifTool ImageDataHash digest' },
2580
+ OriginalImageHashType => { Notes => "ImageHashType API setting, default 'MD5'" },
2581
+ OriginalImageMD5 => { Notes => 'deprecated' },
2570
2582
  );
2571
2583
 
2572
2584
  # table to add tags in other namespaces
@@ -1063,7 +1063,7 @@ my %prismPublicationDate = (
1063
1063
  NOTES => q{
1064
1064
  PRISM Rights Language 2.1 namespace tags. These tags have been deprecated
1065
1065
  since the release of the PRISM Usage Rights 3.0. (see
1066
- L<http://www.prismstandard.org/>)
1066
+ L<https://www.w3.org/submissions/2020/SUBM-prism-20200910/prism-image.html>)
1067
1067
  },
1068
1068
  geography => { List => 'Bag' },
1069
1069
  industry => { List => 'Bag' },
@@ -1388,6 +1388,17 @@ my %sSubVersion = (
1388
1388
  ReuseAllowed => { Writable => 'boolean' },
1389
1389
  );
1390
1390
 
1391
+ %Image::ExifTool::XMP::panorama = (
1392
+ %xmpTableDefaults,
1393
+ GROUPS => { 1 => 'XMP-panorama', 2 => 'Image' },
1394
+ NAMESPACE => 'panorama',
1395
+ NOTES => 'Adobe Photoshop Panorama-profile tags.',
1396
+ Transformation => { },
1397
+ VirtualFocalLength => { Writable => 'real' },
1398
+ VirtualImageXCenter => { Writable => 'real' },
1399
+ VirtualImageYCenter => { Writable => 'real' },
1400
+ );
1401
+
1391
1402
  # Creative Commons namespace properties (cc) (ref 5)
1392
1403
  %Image::ExifTool::XMP::cc = (
1393
1404
  %xmpTableDefaults,
@@ -2094,6 +2105,27 @@ my %sSubVersion = (
2094
2105
  scene_referred => { Name => 'SceneReferred', Writable => 'boolean' },
2095
2106
  );
2096
2107
 
2108
+ # HDR Gain Map metadata namespace
2109
+ %Image::ExifTool::XMP::hdrgm = (
2110
+ %xmpTableDefaults,
2111
+ GROUPS => { 1 => 'XMP-hdrgm', 2 => 'Image' },
2112
+ NAMESPACE => 'hdrgm',
2113
+ TABLE_DESC => 'XMP HDR Gain Map Metadata',
2114
+ NOTES => 'Tags used in Adobe gain map images.',
2115
+ Version => { Avoid => 1 },
2116
+ BaseRenditionIsHDR => { Writable => 'boolean' },
2117
+ # this is a pain in the ass: List items below may or may not be lists
2118
+ # according to the Adobe specification -- I don't know how to handle tags
2119
+ # with a variable format like this, so just make them lists here for now
2120
+ OffsetSDR => { Writable => 'real', List => 'Seq' },
2121
+ OffsetHDR => { Writable => 'real', List => 'Seq' },
2122
+ HDRCapacityMin => { Writable => 'real' },
2123
+ HDRCapacityMax => { Writable => 'real' },
2124
+ GainMapMin => { Writable => 'real', List => 'Seq' },
2125
+ GainMapMax => { Writable => 'real', List => 'Seq' },
2126
+ Gamma => { Writable => 'real', List => 'Seq', Avoid => 1 },
2127
+ );
2128
+
2097
2129
  # SVG namespace properties (ref 9)
2098
2130
  %Image::ExifTool::XMP::SVG = (
2099
2131
  GROUPS => { 0 => 'SVG', 1 => 'SVG', 2 => 'Image' },
@@ -14,42 +14,55 @@ use vars qw(%specialStruct %stdXlatNS);
14
14
  use Image::ExifTool qw(:Utils);
15
15
  use Image::ExifTool::XMP;
16
16
 
17
- sub SerializeStruct($;$);
18
- sub InflateStruct($;$);
17
+ sub SerializeStruct($$;$);
18
+ sub InflateStruct($$;$);
19
19
  sub DumpStruct($;$);
20
20
  sub CheckStruct($$$);
21
21
  sub AddNewStruct($$$$$$);
22
22
  sub ConvertStruct($$$$;$);
23
+ sub EscapeJSON($;$);
24
+
25
+ # lookups for JSON characters that we escape specially
26
+ my %jsonChar = ( '"'=>'"', '\\'=>'\\', "\b"=>'b', "\f"=>'f', "\n"=>'n', "\r"=>'r', "\t"=>'t' );
27
+ my %jsonEsc = ( '"'=>'"', '\\'=>'\\', 'b'=>"\b", 'f'=>"\f", 'n'=>"\n", 'r'=>"\r", 't'=>"\t" );
23
28
 
24
29
  #------------------------------------------------------------------------------
25
30
  # Serialize a structure (or other object) into a simple string
26
- # Inputs: 0) HASH ref, ARRAY ref, or SCALAR, 1) closing bracket (or undef)
27
- # Returns: serialized structure string
31
+ # Inputs: 0) ExifTool ref, 1) HASH ref, ARRAY ref, or SCALAR, 2) closing bracket (or undef)
32
+ # Returns: serialized structure string (in format specified by StructFormat option)
28
33
  # eg) "{field=text with {braces|}|, and a comma, field2=val2,field3={field4=[a,b]}}"
29
- sub SerializeStruct($;$)
34
+ sub SerializeStruct($$;$)
30
35
  {
31
- my ($obj, $ket) = @_;
36
+ my ($et, $obj, $ket) = @_;
32
37
  my ($key, $val, @vals, $rtnVal);
38
+ my $sfmt = $et->Options('StructFormat');
33
39
 
34
40
  if (ref $obj eq 'HASH') {
35
41
  # support hashes with ordered keys
36
42
  my @keys = $$obj{_ordered_keys_} ? @{$$obj{_ordered_keys_}} : sort keys %$obj;
37
43
  foreach $key (@keys) {
38
- push @vals, $key . '=' . SerializeStruct($$obj{$key}, '}');
44
+ my $hdr = $sfmt ? EscapeJSON($key) . ':' : $key . '=';
45
+ push @vals, $hdr . SerializeStruct($et, $$obj{$key}, '}');
39
46
  }
40
47
  $rtnVal = '{' . join(',', @vals) . '}';
41
48
  } elsif (ref $obj eq 'ARRAY') {
42
49
  foreach $val (@$obj) {
43
- push @vals, SerializeStruct($val, ']');
50
+ push @vals, SerializeStruct($et, $val, ']');
44
51
  }
45
52
  $rtnVal = '[' . join(',', @vals) . ']';
46
53
  } elsif (defined $obj) {
47
54
  $obj = $$obj if ref $obj eq 'SCALAR';
48
55
  # escape necessary characters in string (closing bracket plus "," and "|")
49
- my $pat = $ket ? "\\$ket|,|\\|" : ',|\\|';
50
- ($rtnVal = $obj) =~ s/($pat)/|$1/g;
51
- # also must escape opening bracket or whitespace at start of string
52
- $rtnVal =~ s/^([\s\[\{])/|$1/;
56
+ if ($sfmt) {
57
+ $rtnVal = EscapeJSON($obj, $sfmt eq 'JSONQ');
58
+ } else {
59
+ my $pat = $ket ? "\\$ket|,|\\|" : ',|\\|';
60
+ ($rtnVal = $obj) =~ s/($pat)/|$1/g;
61
+ # also must escape opening bracket or whitespace at start of string
62
+ $rtnVal =~ s/^([\s\[\{])/|$1/;
63
+ }
64
+ } elsif ($sfmt) {
65
+ $rtnVal = 'null';
53
66
  } else {
54
67
  $rtnVal = ''; # allow undefined list items
55
68
  }
@@ -58,21 +71,25 @@ sub SerializeStruct($;$)
58
71
 
59
72
  #------------------------------------------------------------------------------
60
73
  # Inflate structure (or other object) from a serialized string
61
- # Inputs: 0) reference to object in string form (serialized using the '|' escape)
62
- # 1) extra delimiter for scalar values delimiters
74
+ # Inputs: 0) ExifTool ref, 1) reference to object in string form
75
+ # (serialized using the '|' escape, or JSON)
76
+ # 2) extra delimiter for scalar values delimiters
63
77
  # Returns: 0) object as a SCALAR, HASH ref, or ARRAY ref (or undef on error),
64
78
  # 1) warning string (or undef)
65
79
  # Notes: modifies input string to remove parsed objects
66
- sub InflateStruct($;$)
80
+ sub InflateStruct($$;$)
67
81
  {
68
- my ($obj, $delim) = @_;
82
+ my ($et, $obj, $delim) = @_;
69
83
  my ($val, $warn, $part);
84
+ my $sfmt = $et->Options('StructFormat');
70
85
 
71
86
  if ($$obj =~ s/^\s*\{//) {
72
87
  my %struct;
73
- while ($$obj =~ s/^\s*([-\w:]+#?)\s*=//s) {
88
+ for (;;) {
89
+ last unless $sfmt ? $$obj =~ s/^\s*"(.*?)"\s*://s :
90
+ $$obj =~ s/^\s*([-\w:]+#?)\s*=//s;
74
91
  my $tag = $1;
75
- my ($v, $w) = InflateStruct($obj, '}');
92
+ my ($v, $w) = InflateStruct($et, $obj, '}');
76
93
  $warn = $w if $w and not $warn;
77
94
  return(undef, $warn) unless defined $v;
78
95
  $struct{$tag} = $v;
@@ -94,7 +111,7 @@ sub InflateStruct($;$)
94
111
  } elsif ($$obj =~ s/^\s*\[//) {
95
112
  my @list;
96
113
  for (;;) {
97
- my ($v, $w) = InflateStruct($obj, ']');
114
+ my ($v, $w) = InflateStruct($et, $obj, ']');
98
115
  $warn = $w if $w and not $warn;
99
116
  return(undef, $warn) unless defined $v;
100
117
  push @list, $v;
@@ -105,20 +122,71 @@ sub InflateStruct($;$)
105
122
  $val = \@list;
106
123
  } else {
107
124
  $$obj =~ s/^\s+//s; # remove leading whitespace
108
- # read scalar up to specified delimiter (or "," if not defined)
109
- $val = '';
110
- $delim = $delim ? "\\$delim|,|\\||\$" : ',|\\||$';
111
- for (;;) {
112
- $$obj =~ s/^(.*?)($delim)//s or last;
113
- $val .= $1;
114
- last unless $2;
115
- $2 eq '|' or $$obj = $2 . $$obj, last;
116
- $$obj =~ s/^(.)//s and $val .= $1; # add escaped character
125
+ if ($sfmt) {
126
+ if ($$obj =~ s/^"//) {
127
+ $val = '';
128
+ while ($$obj =~ s/(.*?)"//) {
129
+ $val .= $1;
130
+ last unless $val =~ /([\\]+)$/ and length($1) & 0x01;
131
+ substr($val, -1, 1) = '"'; # (was an escaped quote)
132
+ }
133
+ if ($val =~ s/^base64://) {
134
+ $val = DecodeBase64($val);
135
+ } else {
136
+ # un-escape characters in JSON string
137
+ $val =~ s/\\(.)/$jsonEsc{$1}||'\\'.$1/egs;
138
+ }
139
+ } elsif ($$obj =~ s/^(true|false)\b//) {
140
+ $val = '"' . ucfirst($1) . '"';
141
+ } elsif ($$obj =~ s/^([+-]?(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?)//) {
142
+ $val = $1;
143
+ } else {
144
+ $warn or $warn = 'Unknown JSON object';
145
+ $val = '""';
146
+ }
147
+ } else {
148
+ # read scalar up to specified delimiter (or "," if not defined)
149
+ $delim = $delim ? "\\$delim|,|\\||\$" : ',|\\||$';
150
+ $val = '';
151
+ for (;;) {
152
+ $$obj =~ s/^(.*?)($delim)//s or last;
153
+ $val .= $1;
154
+ last unless $2;
155
+ $2 eq '|' or $$obj = $2 . $$obj, last;
156
+ $$obj =~ s/^(.)//s and $val .= $1; # add escaped character
157
+ }
117
158
  }
118
159
  }
119
160
  return($val, $warn);
120
161
  }
121
162
 
163
+ #------------------------------------------------------------------------------
164
+ # Escape string for JSON
165
+ # Inputs: 0) string, 1) flag to force numbers to be quoted too
166
+ # Returns: Escaped string (quoted if necessary)
167
+ sub EscapeJSON($;$)
168
+ {
169
+ my ($str, $quote) = @_;
170
+ unless ($quote) {
171
+ return 'null' unless defined $str;
172
+ # JSON boolean (true or false)
173
+ return lc($str) if $str =~ /^(true|false)$/i;
174
+ # JSON number (see json.org for numerical format)
175
+ # return $str if $str =~ /^-?(\d|[1-9]\d+)(\.\d+)?(e[-+]?\d+)?$/i;
176
+ # (these big numbers caused problems for some JSON parsers, so be more conservative)
177
+ return $str if $str =~ /^-?(\d|[1-9]\d{1,14})(\.\d{1,16})?(e[-+]?\d{1,3})?$/i;
178
+ }
179
+ return '""' unless defined $str;
180
+ # encode JSON string in base64 if necessary
181
+ return '"base64:' . EncodeBase64($str, 1) . '"' if Image::ExifTool::IsUTF8(\$str) < 0;
182
+ # escape special characters
183
+ $str =~ s/(["\t\n\r\\])/\\$jsonChar{$1}/sg;
184
+ $str =~ tr/\0//d; # remove all nulls
185
+ # escape other control characters with \u
186
+ $str =~ s/([\0-\x1f])/sprintf("\\u%.4X",ord $1)/sge;
187
+ return '"' . $str . '"'; # return the quoted string
188
+ }
189
+
122
190
  #------------------------------------------------------------------------------
123
191
  # Get XMP language code from tag name string
124
192
  # Inputs: 0) tag name string