exiftool-vendored.exe 12.60.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 (55) hide show
  1. package/LICENSE +254 -254
  2. package/bin/exiftool_files/Changes +110 -0
  3. package/bin/exiftool_files/LICENSE +674 -0
  4. package/bin/exiftool_files/Makefile.PL +7 -1
  5. package/bin/exiftool_files/README +50 -45
  6. package/bin/exiftool_files/config_files/guano.config +161 -0
  7. package/bin/exiftool_files/exiftool.pl +162 -104
  8. package/bin/exiftool_files/lib/Image/ExifTool/7Z.pm +793 -0
  9. package/bin/exiftool_files/lib/Image/ExifTool/Apple.pm +14 -7
  10. package/bin/exiftool_files/lib/Image/ExifTool/BMP.pm +0 -1
  11. package/bin/exiftool_files/lib/Image/ExifTool/BigTIFF.pm +8 -1
  12. package/bin/exiftool_files/lib/Image/ExifTool/BuildTagLookup.pm +4 -4
  13. package/bin/exiftool_files/lib/Image/ExifTool/Canon.pm +4 -1
  14. package/bin/exiftool_files/lib/Image/ExifTool/CanonRaw.pm +4 -4
  15. package/bin/exiftool_files/lib/Image/ExifTool/CanonVRD.pm +4 -1
  16. package/bin/exiftool_files/lib/Image/ExifTool/Exif.pm +31 -14
  17. package/bin/exiftool_files/lib/Image/ExifTool/FlashPix.pm +9 -2
  18. package/bin/exiftool_files/lib/Image/ExifTool/FujiFilm.pm +3 -3
  19. package/bin/exiftool_files/lib/Image/ExifTool/GPS.pm +5 -2
  20. package/bin/exiftool_files/lib/Image/ExifTool/Geotag.pm +4 -1
  21. package/bin/exiftool_files/lib/Image/ExifTool/Jpeg2000.pm +243 -20
  22. package/bin/exiftool_files/lib/Image/ExifTool/Lang/fr.pm +1467 -202
  23. package/bin/exiftool_files/lib/Image/ExifTool/MPF.pm +2 -1
  24. package/bin/exiftool_files/lib/Image/ExifTool/Matroska.pm +16 -1
  25. package/bin/exiftool_files/lib/Image/ExifTool/MinoltaRaw.pm +2 -2
  26. package/bin/exiftool_files/lib/Image/ExifTool/Nikon.pm +941 -33
  27. package/bin/exiftool_files/lib/Image/ExifTool/NikonCustom.pm +874 -63
  28. package/bin/exiftool_files/lib/Image/ExifTool/PDF.pm +39 -12
  29. package/bin/exiftool_files/lib/Image/ExifTool/PLIST.pm +8 -1
  30. package/bin/exiftool_files/lib/Image/ExifTool/PNG.pm +6 -6
  31. package/bin/exiftool_files/lib/Image/ExifTool/PhaseOne.pm +5 -5
  32. package/bin/exiftool_files/lib/Image/ExifTool/QuickTime.pm +96 -32
  33. package/bin/exiftool_files/lib/Image/ExifTool/QuickTimeStream.pl +68 -37
  34. package/bin/exiftool_files/lib/Image/ExifTool/README +2 -2
  35. package/bin/exiftool_files/lib/Image/ExifTool/RIFF.pm +11 -9
  36. package/bin/exiftool_files/lib/Image/ExifTool/Samsung.pm +227 -227
  37. package/bin/exiftool_files/lib/Image/ExifTool/Shortcuts.pm +2 -1
  38. package/bin/exiftool_files/lib/Image/ExifTool/SigmaRaw.pm +4 -4
  39. package/bin/exiftool_files/lib/Image/ExifTool/Sony.pm +237 -32
  40. package/bin/exiftool_files/lib/Image/ExifTool/TagLookup.pm +4762 -4629
  41. package/bin/exiftool_files/lib/Image/ExifTool/TagNames.pod +737 -20
  42. package/bin/exiftool_files/lib/Image/ExifTool/Validate.pm +17 -1
  43. package/bin/exiftool_files/lib/Image/ExifTool/WPG.pm +296 -0
  44. package/bin/exiftool_files/lib/Image/ExifTool/WriteExif.pl +9 -7
  45. package/bin/exiftool_files/lib/Image/ExifTool/WritePDF.pl +7 -8
  46. package/bin/exiftool_files/lib/Image/ExifTool/WriteQuickTime.pl +21 -9
  47. package/bin/exiftool_files/lib/Image/ExifTool/WriteXMP.pl +2 -2
  48. package/bin/exiftool_files/lib/Image/ExifTool/Writer.pl +47 -16
  49. package/bin/exiftool_files/lib/Image/ExifTool/XMP.pm +30 -6
  50. package/bin/exiftool_files/lib/Image/ExifTool/XMP2.pl +32 -0
  51. package/bin/exiftool_files/lib/Image/ExifTool/XMPStruct.pl +96 -28
  52. package/bin/exiftool_files/lib/Image/ExifTool/ZIP.pm +159 -41
  53. package/bin/exiftool_files/lib/Image/ExifTool.pm +280 -164
  54. package/bin/exiftool_files/lib/Image/ExifTool.pod +117 -52
  55. package/package.json +3 -2
@@ -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,
@@ -1302,6 +1303,7 @@ sub SetNewValuesFromFile($$;@)
1302
1303
  MDItemTags => $$options{MDItemTags},
1303
1304
  MissingTagValue => $$options{MissingTagValue},
1304
1305
  NoPDFList => $$options{NoPDFList},
1306
+ NoWarning => $$options{NoWarning},
1305
1307
  Password => $$options{Password},
1306
1308
  PrintConv => $$options{PrintConv},
1307
1309
  QuickTimeUTC => $$options{QuickTimeUTC},
@@ -1312,6 +1314,7 @@ sub SetNewValuesFromFile($$;@)
1312
1314
  ScanForXMP => $$options{ScanForXMP},
1313
1315
  StrictDate => defined $$options{StrictDate} ? $$options{StrictDate} : 1,
1314
1316
  Struct => $structOpt,
1317
+ StructFormat => $$options{StructFormat},
1315
1318
  SystemTags => $$options{SystemTags},
1316
1319
  TimeZone => $$options{TimeZone},
1317
1320
  Unknown => $$options{Unknown},
@@ -1569,10 +1572,17 @@ SET: foreach $set (@setList) {
1569
1572
  # handle expressions
1570
1573
  if ($$opts{EXPR}) {
1571
1574
  my $val = $srcExifTool->InsertTagValues(\@tags, $$set[1], 'Error');
1572
- if ($$srcExifTool{VALUE}{Error}) {
1573
- # pass on any error as a warning
1574
- $tag = NextFreeTagKey(\%rtnInfo, 'Warning');
1575
- $rtnInfo{$tag} = $$srcExifTool{VALUE}{Error};
1575
+ my $err = $$srcExifTool{VALUE}{Error};
1576
+ if ($err) {
1577
+ # pass on any error as a warning unless it is suppressed
1578
+ my $noWarn = $$srcExifTool{OPTIONS}{NoWarning};
1579
+ unless ($noWarn and (eval { $err =~ /$noWarn/ } or
1580
+ # (also apply expression to warning without "[minor] " prefix)
1581
+ ($err =~ s/^\[minor\] //i and eval { $err =~ /$noWarn/ })))
1582
+ {
1583
+ $tag = NextFreeTagKey(\%rtnInfo, 'Warning');
1584
+ $rtnInfo{$tag} = $$srcExifTool{VALUE}{Error};
1585
+ }
1576
1586
  delete $$srcExifTool{VALUE}{Error};
1577
1587
  next unless defined $val;
1578
1588
  }
@@ -1755,7 +1765,14 @@ GNV_TagInfo: foreach $tagInfo (@tagInfoList) {
1755
1765
  }
1756
1766
  }
1757
1767
  # return our value(s)
1758
- 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
+ }
1759
1776
  return $$vals[0];
1760
1777
  }
1761
1778
 
@@ -2008,7 +2025,7 @@ sub SetFileName($$;$$$)
2008
2025
  # protect against empty file name
2009
2026
  length $newName or $self->Warn('New file name is empty'), return -1;
2010
2027
  # don't replace existing file
2011
- if ($self->Exists($newName) and (not defined $usedFlag or $usedFlag)) {
2028
+ if ($self->Exists($newName, 1) and (not defined $usedFlag or $usedFlag)) {
2012
2029
  if ($file ne $newName or $opt =~ /Link$/) {
2013
2030
  # allow for case-insensitive filesystem
2014
2031
  if ($opt =~ /Link$/ or not $self->IsSameFile($file, $newName)) {
@@ -2387,7 +2404,7 @@ sub WriteInfo($$;$$)
2387
2404
  $outBuff = '';
2388
2405
  $outRef = \$outBuff;
2389
2406
  $outPos = 0;
2390
- } elsif ($self->Exists($outfile)) {
2407
+ } elsif ($self->Exists($outfile, 1)) {
2391
2408
  $self->Error("File already exists: $outfile");
2392
2409
  } elsif ($self->Open(\*EXIFTOOL_OUTFILE, $outfile, '>')) {
2393
2410
  $outRef = \*EXIFTOOL_OUTFILE;
@@ -3275,7 +3292,7 @@ sub InsertTagValues($$$;$$$)
3275
3292
  }
3276
3293
  } elsif (ref $val eq 'HASH') {
3277
3294
  require 'Image/ExifTool/XMPStruct.pl';
3278
- $val = Image::ExifTool::XMP::SerializeStruct($val);
3295
+ $val = Image::ExifTool::XMP::SerializeStruct($self, $val);
3279
3296
  } elsif (not defined $val) {
3280
3297
  $val = $$self{OPTIONS}{MissingTagValue} if $asList;
3281
3298
  }
@@ -3759,6 +3776,8 @@ sub GetNewValueHash($$;$$$$)
3759
3776
  # this is a bit tricky: we want to add to a protected nvHash only if we
3760
3777
  # are adding a conditional delete ($_[5] true or DelValue with no Shift)
3761
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)
3762
3781
  if ($protect and not ($opts{create} and ($$nvHash{NoReplace} or $_[5] or
3763
3782
  ($$nvHash{DelValue} and not defined $$nvHash{Shift}))))
3764
3783
  {
@@ -4900,6 +4919,11 @@ sub InverseDateTime($$;$$)
4900
4919
  my $fs = ($fmt =~ s/%f$// and $val =~ s/(\.\d+)\s*$//) ? $1 : '';
4901
4920
  my ($lib, $wrn, @a);
4902
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
+ }
4903
4927
  if (not $lib) {
4904
4928
  last unless $$self{OPTIONS}{StrictDate};
4905
4929
  warn $wrn || "Install POSIX::strptime or Time::Piece for inverse date/time conversions\n";
@@ -5590,6 +5614,8 @@ sub WriteJPEG($$)
5590
5614
  $s =~ /^(Meta|META|Exif)\0\0/ and $dirName = 'Meta';
5591
5615
  } elsif ($marker == 0xe5) {
5592
5616
  $s =~ /^RMETA\0/ and $dirName = 'RMETA';
5617
+ } elsif ($marker == 0xeb) {
5618
+ $s =~ /^JP/ and $dirName = 'JUMBF';
5593
5619
  } elsif ($marker == 0xec) {
5594
5620
  $s =~ /^Ducky/ and $dirName = 'Ducky';
5595
5621
  } elsif ($marker == 0xed) {
@@ -6455,6 +6481,11 @@ sub WriteJPEG($$)
6455
6481
  $segType = 'Ricoh RMETA';
6456
6482
  $$delGroup{RMETA} and $del = 1;
6457
6483
  }
6484
+ } elsif ($marker == 0xeb) { # APP10 (JUMBF)
6485
+ if ($$segDataPt =~ /^JP/) {
6486
+ $segType = 'JUMBF';
6487
+ $$delGroup{JUMBF} and $del = 1;
6488
+ }
6458
6489
  } elsif ($marker == 0xec) { # APP12 (Ducky)
6459
6490
  if ($$segDataPt =~ /^Ducky/) {
6460
6491
  $segType = 'Ducky';
@@ -6872,14 +6903,14 @@ sub SetFileTime($$;$$$$)
6872
6903
  }
6873
6904
 
6874
6905
  #------------------------------------------------------------------------------
6875
- # Add data to MD5 checksum
6906
+ # Add data to hash checksum
6876
6907
  # Inputs: 0) ExifTool ref, 1) RAF ref, 2) data size (or undef to read to end of file),
6877
6908
  # 3) data name (or undef for no warnings or messages), 4) flag for no verbose message
6878
- # Returns: number of bytes read and MD5'd
6879
- sub ImageDataMD5($$$;$$)
6909
+ # Returns: number of bytes read and hashed
6910
+ sub ImageDataHash($$$;$$)
6880
6911
  {
6881
6912
  my ($self, $raf, $size, $type, $noMsg) = @_;
6882
- my $md5 = $$self{ImageDataMD5} or return;
6913
+ my $hash = $$self{ImageDataHash} or return;
6883
6914
  my ($bytesRead, $n) = (0, 65536);
6884
6915
  my $buff;
6885
6916
  for (;;) {
@@ -6892,11 +6923,11 @@ sub ImageDataMD5($$$;$$)
6892
6923
  $self->Warn("Error reading $type data") if $type and defined $size;
6893
6924
  last;
6894
6925
  }
6895
- $md5->add($buff);
6926
+ $hash->add($buff);
6896
6927
  $bytesRead += length $buff;
6897
6928
  }
6898
6929
  if ($$self{OPTIONS}{Verbose} and $bytesRead and $type and not $noMsg) {
6899
- $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");
6900
6931
  }
6901
6932
  return $bytesRead;
6902
6933
  }
@@ -50,7 +50,7 @@ use Image::ExifTool::Exif;
50
50
  use Image::ExifTool::GPS;
51
51
  require Exporter;
52
52
 
53
- $VERSION = '3.58';
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
@@ -248,7 +250,11 @@ my %boolConv = (
248
250
 
249
251
  # XMP namespaces which we don't want to contribute to generated EXIF tag names
250
252
  # (Note: namespaces with non-standard prefixes aren't currently ignored)
251
- my %ignoreNamespace = ( 'x'=>1, rdf=>1, xmlns=>1, xml=>1, svg=>1, et=>1, office=>1 );
253
+ my %ignoreNamespace = ( 'x'=>1, rdf=>1, xmlns=>1, xml=>1, svg=>1, office=>1 );
254
+
255
+ # ExifTool properties that don't generate tag names (et:tagid is historic)
256
+ my %ignoreEtProp = ( 'et:desc'=>1, 'et:prt'=>1, 'et:val'=>1 , 'et:id'=>1, 'et:tagid'=>1,
257
+ 'et:toolkit'=>1, 'et:table'=>1, 'et:index'=>1 );
252
258
 
253
259
  # XMP properties to ignore (set dynamically via dirInfo IgnoreProp)
254
260
  my %ignoreProp;
@@ -705,6 +711,10 @@ my %sRangeMask = (
705
711
  Name => 'xmpPLUS',
706
712
  SubDirectory => { TagTable => 'Image::ExifTool::XMP::xmpPLUS' },
707
713
  },
714
+ panorama => {
715
+ Name => 'panorama',
716
+ SubDirectory => { TagTable => 'Image::ExifTool::XMP::panorama' },
717
+ },
708
718
  plus => {
709
719
  Name => 'plus',
710
720
  SubDirectory => { TagTable => 'Image::ExifTool::PLUS::XMP' },
@@ -905,6 +915,10 @@ my %sRangeMask = (
905
915
  Name => 'hdr',
906
916
  SubDirectory => { TagTable => 'Image::ExifTool::XMP::hdr' },
907
917
  },
918
+ hdrgm => {
919
+ Name => 'hdrgm',
920
+ SubDirectory => { TagTable => 'Image::ExifTool::XMP::hdrgm' },
921
+ },
908
922
  );
909
923
 
910
924
  # hack to allow XML containing Dublin Core metadata to be handled like XMP (eg. EPUB - see ZIP.pm)
@@ -2485,6 +2499,9 @@ my %sPantryItem = (
2485
2499
  EnhanceSuperResolutionAlreadyApplied => { Writable => 'boolean' },
2486
2500
  EnhanceSuperResolutionVersion => { }, # integer?
2487
2501
  EnhanceSuperResolutionScale => { Writable => 'rational' },
2502
+ EnhanceDenoiseAlreadyApplied => { Writable => 'boolean' }, #forum14760
2503
+ EnhanceDenoiseVersion => { }, #forum14760 integer?
2504
+ EnhanceDenoiseLumaAmount => { }, #forum14760 integer?
2488
2505
  );
2489
2506
 
2490
2507
  # IPTC Core namespace properties (Iptc4xmpCore) (ref 4)
@@ -2559,7 +2576,9 @@ my %sPantryItem = (
2559
2576
  %xmpTableDefaults,
2560
2577
  GROUPS => { 1 => 'XMP-et', 2 => 'Image' },
2561
2578
  NAMESPACE => 'et',
2562
- 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' },
2563
2582
  );
2564
2583
 
2565
2584
  # table to add tags in other namespaces
@@ -2850,7 +2869,7 @@ sub GetXMPTagID($;$$)
2850
2869
  # split name into namespace and property name
2851
2870
  # (Note: namespace can be '' for property qualifiers)
2852
2871
  my ($ns, $nm) = ($prop =~ /(.*?):(.*)/) ? ($1, $2) : ('', $prop);
2853
- if ($ignoreNamespace{$ns} or $ignoreProp{$prop}) {
2872
+ if ($ignoreNamespace{$ns} or $ignoreProp{$prop} or $ignoreEtProp{$prop}) {
2854
2873
  # special case: don't ignore rdf numbered items
2855
2874
  # (not technically allowed in XMP, but used in RDF/XML)
2856
2875
  unless ($prop =~ /^rdf:(_\d+)$/) {
@@ -3420,7 +3439,10 @@ NoLoop:
3420
3439
  my %grps = ( 0 => $1, 1 => $2 );
3421
3440
  # apply a little magic to recover original group names
3422
3441
  # from this exiftool-written RDF/XML file
3423
- if ($grps{1} =~ /^\d/) {
3442
+ if ($grps{1} eq 'System') {
3443
+ $grps{1} = 'XML-System';
3444
+ $grps{0} = 'XML';
3445
+ } elsif ($grps{1} =~ /^\d/) {
3424
3446
  # URI's with only family 0 are internal tags from the source file,
3425
3447
  # so change the group name to avoid confusion with tags from this file
3426
3448
  $grps{1} = "XML-$grps{0}";
@@ -3888,7 +3910,9 @@ sub ParseXMPElement($$$;$$$$)
3888
3910
  }
3889
3911
  }
3890
3912
  my $shortVal = $attrs{$shortName};
3891
- if ($ignoreNamespace{$ns} or $ignoreProp{$prop}) {
3913
+ # Note: $prop is the containing property in this loop (not the shorthand property)
3914
+ # so $ignoreProp ignores all attributes of the ignored property
3915
+ if ($ignoreNamespace{$ns} or $ignoreProp{$prop} or $ignoreEtProp{$propName}) {
3892
3916
  $ignored = $propName;
3893
3917
  # handle special attributes (extract as tags only once if not empty)
3894
3918
  if (ref $recognizedAttrs{$propName} and $shortVal) {
@@ -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