exiftool-vendored.pl 12.62.0 → 12.65.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 (54) hide show
  1. package/bin/Changes +76 -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 +108 -75
  9. package/bin/lib/Image/ExifTool/7Z.pm +793 -0
  10. package/bin/lib/Image/ExifTool/Apple.pm +14 -7
  11. package/bin/lib/Image/ExifTool/BigTIFF.pm +8 -1
  12. package/bin/lib/Image/ExifTool/Canon.pm +4 -1
  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 +3 -3
  18. package/bin/lib/Image/ExifTool/GPS.pm +5 -2
  19. package/bin/lib/Image/ExifTool/Geotag.pm +4 -1
  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 +939 -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/PNG.pm +6 -6
  30. package/bin/lib/Image/ExifTool/PhaseOne.pm +5 -5
  31. package/bin/lib/Image/ExifTool/QuickTime.pm +91 -30
  32. package/bin/lib/Image/ExifTool/QuickTimeStream.pl +20 -19
  33. package/bin/lib/Image/ExifTool/README +2 -2
  34. package/bin/lib/Image/ExifTool/RIFF.pm +11 -9
  35. package/bin/lib/Image/ExifTool/Samsung.pm +227 -227
  36. package/bin/lib/Image/ExifTool/Shortcuts.pm +2 -1
  37. package/bin/lib/Image/ExifTool/SigmaRaw.pm +4 -4
  38. package/bin/lib/Image/ExifTool/Sony.pm +229 -30
  39. package/bin/lib/Image/ExifTool/TagLookup.pm +4757 -4633
  40. package/bin/lib/Image/ExifTool/TagNames.pod +706 -18
  41. package/bin/lib/Image/ExifTool/Validate.pm +17 -1
  42. package/bin/lib/Image/ExifTool/WriteExif.pl +9 -7
  43. package/bin/lib/Image/ExifTool/WriteQuickTime.pl +21 -9
  44. package/bin/lib/Image/ExifTool/WriteXMP.pl +2 -2
  45. package/bin/lib/Image/ExifTool/Writer.pl +35 -12
  46. package/bin/lib/Image/ExifTool/XMP.pm +14 -2
  47. package/bin/lib/Image/ExifTool/XMP2.pl +32 -0
  48. package/bin/lib/Image/ExifTool/XMPStruct.pl +96 -28
  49. package/bin/lib/Image/ExifTool/ZIP.pm +5 -5
  50. package/bin/lib/Image/ExifTool.pm +176 -128
  51. package/bin/lib/Image/ExifTool.pod +109 -53
  52. package/bin/perl-Image-ExifTool.spec +44 -44
  53. package/bin/pp_build_exe.args +7 -4
  54. 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,6 +1314,7 @@ 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},
@@ -1763,7 +1765,14 @@ GNV_TagInfo: foreach $tagInfo (@tagInfoList) {
1763
1765
  }
1764
1766
  }
1765
1767
  # return our value(s)
1766
- return @$vals if wantarray;
1768
+ if (wantarray) {
1769
+ # remove duplicates if requested
1770
+ if (@$vals > 1 and $self->Options('NoDups')) {
1771
+ my %seen;
1772
+ @$vals = grep { !$seen{$_}++ } @$vals;
1773
+ }
1774
+ return @$vals;
1775
+ }
1767
1776
  return $$vals[0];
1768
1777
  }
1769
1778
 
@@ -2016,7 +2025,7 @@ sub SetFileName($$;$$$)
2016
2025
  # protect against empty file name
2017
2026
  length $newName or $self->Warn('New file name is empty'), return -1;
2018
2027
  # don't replace existing file
2019
- if ($self->Exists($newName) and (not defined $usedFlag or $usedFlag)) {
2028
+ if ($self->Exists($newName, 1) and (not defined $usedFlag or $usedFlag)) {
2020
2029
  if ($file ne $newName or $opt =~ /Link$/) {
2021
2030
  # allow for case-insensitive filesystem
2022
2031
  if ($opt =~ /Link$/ or not $self->IsSameFile($file, $newName)) {
@@ -2395,7 +2404,7 @@ sub WriteInfo($$;$$)
2395
2404
  $outBuff = '';
2396
2405
  $outRef = \$outBuff;
2397
2406
  $outPos = 0;
2398
- } elsif ($self->Exists($outfile)) {
2407
+ } elsif ($self->Exists($outfile, 1)) {
2399
2408
  $self->Error("File already exists: $outfile");
2400
2409
  } elsif ($self->Open(\*EXIFTOOL_OUTFILE, $outfile, '>')) {
2401
2410
  $outRef = \*EXIFTOOL_OUTFILE;
@@ -3283,7 +3292,7 @@ sub InsertTagValues($$$;$$$)
3283
3292
  }
3284
3293
  } elsif (ref $val eq 'HASH') {
3285
3294
  require 'Image/ExifTool/XMPStruct.pl';
3286
- $val = Image::ExifTool::XMP::SerializeStruct($val);
3295
+ $val = Image::ExifTool::XMP::SerializeStruct($self, $val);
3287
3296
  } elsif (not defined $val) {
3288
3297
  $val = $$self{OPTIONS}{MissingTagValue} if $asList;
3289
3298
  }
@@ -3767,6 +3776,8 @@ sub GetNewValueHash($$;$$$$)
3767
3776
  # this is a bit tricky: we want to add to a protected nvHash only if we
3768
3777
  # are adding a conditional delete ($_[5] true or DelValue with no Shift)
3769
3778
  # or accumulating List items (NoReplace true)
3779
+ # (NOTE: this should be looked into --> lists may be accumulated instead of being replaced
3780
+ # as expected when copying to the same list from different dynamic -tagsFromFile source files)
3770
3781
  if ($protect and not ($opts{create} and ($$nvHash{NoReplace} or $_[5] or
3771
3782
  ($$nvHash{DelValue} and not defined $$nvHash{Shift}))))
3772
3783
  {
@@ -4908,6 +4919,11 @@ sub InverseDateTime($$;$$)
4908
4919
  my $fs = ($fmt =~ s/%f$// and $val =~ s/(\.\d+)\s*$//) ? $1 : '';
4909
4920
  my ($lib, $wrn, @a);
4910
4921
  TryLib: for ($lib=$strptimeLib; ; $lib='') {
4922
+ # handle %s format ourself (not supported in Fedora, see forum15032)
4923
+ if ($fmt eq '%s') {
4924
+ $val = ConvertUnixTime($val, 1);
4925
+ last;
4926
+ }
4911
4927
  if (not $lib) {
4912
4928
  last unless $$self{OPTIONS}{StrictDate};
4913
4929
  warn $wrn || "Install POSIX::strptime or Time::Piece for inverse date/time conversions\n";
@@ -5598,6 +5614,8 @@ sub WriteJPEG($$)
5598
5614
  $s =~ /^(Meta|META|Exif)\0\0/ and $dirName = 'Meta';
5599
5615
  } elsif ($marker == 0xe5) {
5600
5616
  $s =~ /^RMETA\0/ and $dirName = 'RMETA';
5617
+ } elsif ($marker == 0xeb) {
5618
+ $s =~ /^JP/ and $dirName = 'JUMBF';
5601
5619
  } elsif ($marker == 0xec) {
5602
5620
  $s =~ /^Ducky/ and $dirName = 'Ducky';
5603
5621
  } elsif ($marker == 0xed) {
@@ -6463,6 +6481,11 @@ sub WriteJPEG($$)
6463
6481
  $segType = 'Ricoh RMETA';
6464
6482
  $$delGroup{RMETA} and $del = 1;
6465
6483
  }
6484
+ } elsif ($marker == 0xeb) { # APP10 (JUMBF)
6485
+ if ($$segDataPt =~ /^JP/) {
6486
+ $segType = 'JUMBF';
6487
+ $$delGroup{JUMBF} and $del = 1;
6488
+ }
6466
6489
  } elsif ($marker == 0xec) { # APP12 (Ducky)
6467
6490
  if ($$segDataPt =~ /^Ducky/) {
6468
6491
  $segType = 'Ducky';
@@ -6880,14 +6903,14 @@ sub SetFileTime($$;$$$$)
6880
6903
  }
6881
6904
 
6882
6905
  #------------------------------------------------------------------------------
6883
- # Add data to MD5 checksum
6906
+ # Add data to hash checksum
6884
6907
  # Inputs: 0) ExifTool ref, 1) RAF ref, 2) data size (or undef to read to end of file),
6885
6908
  # 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($$$;$$)
6909
+ # Returns: number of bytes read and hashed
6910
+ sub ImageDataHash($$$;$$)
6888
6911
  {
6889
6912
  my ($self, $raf, $size, $type, $noMsg) = @_;
6890
- my $md5 = $$self{ImageDataMD5} or return;
6913
+ my $hash = $$self{ImageDataHash} or return;
6891
6914
  my ($bytesRead, $n) = (0, 65536);
6892
6915
  my $buff;
6893
6916
  for (;;) {
@@ -6900,11 +6923,11 @@ sub ImageDataMD5($$$;$$)
6900
6923
  $self->Warn("Error reading $type data") if $type and defined $size;
6901
6924
  last;
6902
6925
  }
6903
- $md5->add($buff);
6926
+ $hash->add($buff);
6904
6927
  $bytesRead += length $buff;
6905
6928
  }
6906
6929
  if ($$self{OPTIONS}{Verbose} and $bytesRead and $type and not $noMsg) {
6907
- $self->VPrint(0, "$$self{INDENT}(ImageDataMD5: $bytesRead bytes of $type data)\n");
6930
+ $self->VPrint(0, "$$self{INDENT}(ImageDataHash: $bytesRead bytes of $type data)\n");
6908
6931
  }
6909
6932
  return $bytesRead;
6910
6933
  }
@@ -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
@@ -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
@@ -20,7 +20,7 @@ use strict;
20
20
  use vars qw($VERSION $warnString);
21
21
  use Image::ExifTool qw(:DataAccess :Utils);
22
22
 
23
- $VERSION = '1.29';
23
+ $VERSION = '1.30';
24
24
 
25
25
  sub WarnProc($) { $warnString = $_[0]; }
26
26
 
@@ -259,8 +259,8 @@ my %iWorkType = (
259
259
  %Image::ExifTool::ZIP::RAR5 = (
260
260
  GROUPS => { 2 => 'Other' },
261
261
  VARS => { NO_ID => 1 },
262
- NOTES => 'These tags are extracted from RAR v5 archive files.',
263
- RARVersion => { },
262
+ NOTES => 'These tags are extracted from RAR v5 and 7z archive files.',
263
+ FileVersion => { },
264
264
  CompressedSize => { },
265
265
  ModifyDate => {
266
266
  Groups => { 2 => 'Time' },
@@ -310,7 +310,7 @@ sub ProcessRAR($$)
310
310
  $et->SetFileType();
311
311
  SetByteOrder('II');
312
312
  my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR5');
313
- $et->HandleTag($tagTablePtr, 'RARVersion', 4);
313
+ $et->HandleTag($tagTablePtr, 'FileVersion', 'RAR v4');
314
314
  $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR');
315
315
 
316
316
  for (;;) {
@@ -356,7 +356,7 @@ sub ProcessRAR($$)
356
356
  return 0 unless $raf->Read($buff, 1) and $buff eq "\0";
357
357
  $et->SetFileType();
358
358
  my $tagTablePtr = GetTagTable('Image::ExifTool::ZIP::RAR5');
359
- $et->HandleTag($tagTablePtr, 'RARVersion', 5);
359
+ $et->HandleTag($tagTablePtr, 'FileVersion', 'RAR v5');
360
360
  $$et{INDENT} .= '| ';
361
361
 
362
362
  # loop through header blocks