exiftool_vendored 12.63.0 → 12.64.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,7 +12,7 @@ meta information extracted from or written to a file.
12
12
  =head1 TAG TABLES
13
13
 
14
14
  The tables listed below give the names of all tags recognized by ExifTool.
15
- They contain a total of 26443 tags, with 16891 unique tag names.
15
+ They contain a total of 26591 tags, with 16966 unique tag names.
16
16
 
17
17
  B<Tag ID>, B<Index#> or B<Sequence> is given in the first column of each
18
18
  table. A B<Tag ID> is the computer-readable equivalent of a tag name, and
@@ -7199,6 +7199,7 @@ Tags extracted from the maker notes of iPhone images.
7199
7199
  0x0025 SceneFlags? int32s
7200
7200
  0x0026 SignalToNoiseRatioType? int32s
7201
7201
  0x0027 SignalToNoiseRatio rational64s
7202
+ 0x002b PhotoIdentifier string
7202
7203
  0x002f FocusPosition int32s
7203
7204
  0x0030 HDRGain rational64s
7204
7205
  0x0038 AFMeasuredDepth int32s
@@ -14715,6 +14716,7 @@ These tags are extracted from encrypted data in images from the Z9.
14715
14716
  4 FirmwareVersion no
14716
14717
  48 SequenceOffset Nikon SeqInfoZ9
14717
14718
  88 Offset13 Nikon Offset13InfoZ9
14719
+ 128 AutoCaptureOffset Nikon AutoCaptureInfo
14718
14720
  132 OrientOffset Nikon OrientationInfo
14719
14721
  140 MenuOffset Nikon MenuInfoZ9
14720
14722
 
@@ -14734,6 +14736,21 @@ These tags are extracted from encrypted data in images from the Z9.
14734
14736
  3050 AFAreaInitialWidth no
14735
14737
  3051 AFAreaInitialHeight no
14736
14738
 
14739
+ =head3 Nikon AutoCaptureInfo Tags
14740
+
14741
+ Index1 Tag Name Writable
14742
+ ------ -------- --------
14743
+ 1 AutoCaptureCriteria int8u~
14744
+ 55 AutoCaptureRecordingTime int8u
14745
+ 56 AutoCaptureWaitTime int8u
14746
+ 74 AutoCaptureDistanceFar int8u~
14747
+ 78 AutoCaptureDistanceNear int8u~
14748
+ 95 AutoCaptureCriteriaMotionDirection int8u~
14749
+ 99 AutoCaptureCriteriaMotionSpeed int8u
14750
+ 100 AutoCaptureCriteriaMotionSize int8u
14751
+ 105 AutoCaptureCriteriaSubjectSize int8u
14752
+ 106 AutoCaptureCriteriaSubjectType int8u
14753
+
14737
14754
  =head3 Nikon MenuInfoZ9 Tags
14738
14755
 
14739
14756
  Index1 Tag Name Writable
@@ -14741,6 +14758,8 @@ These tags are extracted from encrypted data in images from the Z9.
14741
14758
  16 MenuSettingsOffsetZ9 Nikon MenuSettingsZ9
14742
14759
  MenuSettingsOffsetZ9v3 -
14743
14760
  Nikon MenuSettingsZ9v3
14761
+ -
14762
+ Nikon MenuSettingsZ9v4
14744
14763
 
14745
14764
  =head3 Nikon MenuSettingsZ9 Tags
14746
14765
 
@@ -14900,6 +14919,144 @@ These tags are used by the Z9 firmware 3.00.
14900
14919
  1810 PreReleaseBurstLength int8u
14901
14920
  1812 PostReleaseBurstLength int8u
14902
14921
 
14922
+ =head3 Nikon MenuSettingsZ9v4 Tags
14923
+
14924
+ These tags are used by the Z9 firmware 3.00.
14925
+
14926
+ Index1 Tag Name Writable
14927
+ --------------- --------
14928
+ 72 HighFrameRate int8u
14929
+ 154 MultipleExposureMode int8u
14930
+ 156 MultiExposureShots int8u
14931
+ 204 Intervals int32u
14932
+ 208 ShotsPerInterval int32u
14933
+ 248 FocusShiftNumberShots int8u
14934
+ 252 FocusShiftStepWidth int8u
14935
+ 256 FocusShiftInterval int8u~
14936
+ 260 FocusShiftExposureLock? int8u
14937
+ 290 PhotoShootingMenuBank int8u
14938
+ 292 ExtendedMenuBanks int8u
14939
+ 328 PhotoShootingMenuBankImageArea int8u
14940
+ 342 AutoISO int8u
14941
+ 344 ISOAutoHiLimit? int16u
14942
+ 346 ISOAutoFlashLimit? int16u
14943
+ 354 ISOAutoShutterTime no
14944
+ 436 MovieVignetteControl? int8u
14945
+ 438 DiffractionCompensation int8u
14946
+ 440 FlickerReductionShooting int8u
14947
+ 444 FlashControlMode int8u
14948
+ 446 FlashMasterCompensation? int8s
14949
+ 450 FlashGNDistance? no
14950
+ 454 FlashOutput? int8u
14951
+ 548 AFAreaMode int8u
14952
+ 550 VRMode int8u
14953
+ 554 BracketSet int8u
14954
+ 556 BracketProgram int8u
14955
+ 558 BracketIncrement int8u
14956
+ 576 SecondarySlotFunction int8u
14957
+ 586 Slot2JpgSize? int8u
14958
+ 592 DXCropAlert int8u
14959
+ 594 SubjectDetection int8u
14960
+ 596 DynamicAFAreaSize int8u
14961
+ 636 HighFrequencyFlickerReductionShooting? int8u
14962
+ 646 MovieImageArea? int8u & 0x01
14963
+ 656 MovieType? int8u
14964
+ 658 MovieISOAutoHiLimit? int16u
14965
+ 660 MovieISOAutoControlManualMode? int8u
14966
+ 662 MovieISOAutoManualMode? int16u
14967
+ 736 MovieActiveD-Lighting? int8u
14968
+ 738 MovieHighISONoiseReduction? int8u
14969
+ 744 MovieFlickerReduction int8u
14970
+ 746 MovieMeteringMode? int8u
14971
+ 748 MovieFocusMode? int8u
14972
+ 750 MovieAFAreaMode int8u
14973
+ 752 MovieVRMode? int8u
14974
+ 756 MovieElectronicVR? int8u
14975
+ 758 MovieSoundRecording? int8u
14976
+ 760 MicrophoneSensitivity? int8u
14977
+ 762 MicrophoneAttenuator? int8u
14978
+ 764 MicrophoneFrequencyResponse? int8u
14979
+ 766 WindNoiseReduction? int8u
14980
+ 788 MovieToneMap? int8u
14981
+ 794 MovieFrameSize? int8u
14982
+ 796 MovieFrameRate? int8u
14983
+ 802 MicrophoneJackPower? int8u
14984
+ 803 MovieDXCropAlert? int8u
14985
+ 804 MovieSubjectDetection? int8u
14986
+ 812 MovieHighResZoom? int8u
14987
+ 847 CustomSettingsZ9 NikonCustom SettingsZ9
14988
+ 1474 Language? int8u
14989
+ 1476 TimeZone int8u
14990
+ 1482 MonitorBrightness? int8u
14991
+ 1504 AFFineTune? int8u
14992
+ 1532 NonCPULens1FocalLength? int16s~
14993
+ 1536 NonCPULens2FocalLength? int16s~
14994
+ 1540 NonCPULens3FocalLength? int16s~
14995
+ 1544 NonCPULens4FocalLength? int16s~
14996
+ 1548 NonCPULens5FocalLength? int16s~
14997
+ 1552 NonCPULens6FocalLength? int16s~
14998
+ 1556 NonCPULens7FocalLength? int16s~
14999
+ 1560 NonCPULens8FocalLength? int16s~
15000
+ 1564 NonCPULens9FocalLength? int16s~
15001
+ 1568 NonCPULens10FocalLength? int16s~
15002
+ 1572 NonCPULens11FocalLength? int16s~
15003
+ 1576 NonCPULens21FocalLength? int16s~
15004
+ 1580 NonCPULens13FocalLength? int16s~
15005
+ 1584 NonCPULens14FocalLength? int16s~
15006
+ 1588 NonCPULens15FocalLength? int16s~
15007
+ 1592 NonCPULens16FocalLength? int16s~
15008
+ 1596 NonCPULens17FocalLength? int16s~
15009
+ 1600 NonCPULens18FocalLength? int16s~
15010
+ 1604 NonCPULens19FocalLength? int16s~
15011
+ 1608 NonCPULens20FocalLength? int16s~
15012
+ 1612 NonCPULens1MaxAperture? int16s~
15013
+ 1616 NonCPULens2MaxAperture? int16s~
15014
+ 1620 NonCPULens3MaxAperture? int16s~
15015
+ 1624 NonCPULens4MaxAperture? int16s~
15016
+ 1628 NonCPULens5MaxAperture? int16s~
15017
+ 1632 NonCPULens6MaxAperture? int16s~
15018
+ 1636 NonCPULens7MaxAperture? int16s~
15019
+ 1640 NonCPULens8MaxAperture? int16s~
15020
+ 1644 NonCPULens9MaxAperture? int16s~
15021
+ 1648 NonCPULens10MaxAperture? int16s~
15022
+ 1652 NonCPULens11MaxAperture? int16s~
15023
+ 1656 NonCPULens12MaxAperture? int16s~
15024
+ 1660 NonCPULens13MaxAperture? int16s~
15025
+ 1664 NonCPULens14MaxAperture? int16s~
15026
+ 1668 NonCPULens15MaxAperture? int16s~
15027
+ 1672 NonCPULens16MaxAperture? int16s~
15028
+ 1676 NonCPULens17MaxAperture? int16s~
15029
+ 1680 NonCPULens18MaxAperture? int16s~
15030
+ 1684 NonCPULens19MaxAperture? int16s~
15031
+ 1688 NonCPULens20MaxAperture? int16s~
15032
+ 1704 HDMIOutputResolution int8u
15033
+ 1717 SetClockFromLocationData? int8u
15034
+ 1724 AirplaneMode? int8u
15035
+ 1725 EmptySlotRelease? int8u
15036
+ 1760 EnergySavingMode? int8u
15037
+ 1784 RecordLocationData? int8u
15038
+ 1788 USBPowerDelivery? int8u
15039
+ 1797 SensorShield? int8u
15040
+ 1862 AutoCapturePreset int8u
15041
+ 1864 FocusShiftAutoReset? int8u
15042
+ 1922 PreReleaseBurstLength int8u
15043
+ 1924 PostReleaseBurstLength int8u
15044
+ 1938 VerticalISOButton int8u
15045
+ 1940 ExposureCompensationButton int8u
15046
+ 1942 ISOButton int8u
15047
+ 2002 ViewModeShowEffectsOfSettings? int8u
15048
+ 2004 DispButton int8u
15049
+ 2048 ExposureDelay fixed32u~
15050
+ 2056 PlaybackButton int8u
15051
+ 2058 WBButton int8u
15052
+ 2060 BracketButton int8u
15053
+ 2062 FlashModeButton int8u
15054
+ 2064 LensFunc1ButtonPlaybackMode int8u
15055
+ 2066 LensFunc2ButtonPlaybackMode int8u
15056
+ 2068 PlaybackButtonPlaybackMode int8u
15057
+ 2070 BracketButtonPlaybackMode int8u
15058
+ 2072 FlashModeButtonPlaybackMode int8u
15059
+
14903
15060
  =head3 Nikon ShotInfo Tags
14904
15061
 
14905
15062
  This information is encrypted for ShotInfoVersion 02xx, and some tags are
@@ -28914,12 +29071,27 @@ atoms.
28914
29071
  24 AudioChannels no
28915
29072
  26 AudioBitsPerSample no
28916
29073
  32 AudioSampleRate no
29074
+ 'SA3D' SpatialAudio QuickTime SpatialAudio
28917
29075
  'chan' AudioChannelLayout QuickTime ChannelLayout
28918
29076
  'damr' DecodeConfig QuickTime DecodeConfig
28919
29077
  'pinf' PurchaseInfo QuickTime ProtectionInfo
28920
29078
  'sinf' ProtectionInfo QuickTime ProtectionInfo
28921
29079
  'wave' Wave QuickTime Wave
28922
29080
 
29081
+ =head3 QuickTime SpatialAudio Tags
29082
+
29083
+ Spatial Audio tags.
29084
+
29085
+ Index1 Tag Name Writable
29086
+ ------ -------- --------
29087
+ 0 SpatialAudioVersion no
29088
+ 1 AmbisonicType no
29089
+ 2 AmbisonicOrder no
29090
+ 6 AmbisonicChannelOrdering no
29091
+ 7 AmbisonicNormalization no
29092
+ 8 AmbisonicChannels no
29093
+ 12 AmbisonicChannelMap no
29094
+
28923
29095
  =head3 QuickTime ChannelLayout Tags
28924
29096
 
28925
29097
  Audio channel layout.
@@ -31198,6 +31370,7 @@ L<https://www.matroska.org/technical/tagging.html>).
31198
31370
  'SCREENPLAY_BY' ScreenplayBy no
31199
31371
  'SORT_WITH' SortWith no
31200
31372
  'SOUND_ENGINEER' SoundEngineer no
31373
+ 'SPHERICAL-VIDEO' SphericalVideoXML XMP
31201
31374
  'SUBJECT' Subject no
31202
31375
  'SUBTITLE' Subtitle no
31203
31376
  'SUMMARY' Summary no
@@ -31211,6 +31384,7 @@ L<https://www.matroska.org/technical/tagging.html>).
31211
31384
  'TVDB' TVDB no
31212
31385
  'URL' URL no
31213
31386
  'WRITTEN_BY' WrittenBy no
31387
+ 'spherical-video' SphericalVideoXML XMP
31214
31388
 
31215
31389
  =head2 MOI Tags
31216
31390
 
@@ -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
  }
@@ -1314,6 +1314,7 @@ sub SetNewValuesFromFile($$;@)
1314
1314
  ScanForXMP => $$options{ScanForXMP},
1315
1315
  StrictDate => defined $$options{StrictDate} ? $$options{StrictDate} : 1,
1316
1316
  Struct => $structOpt,
1317
+ StructFormat => $$options{StructFormat},
1317
1318
  SystemTags => $$options{SystemTags},
1318
1319
  TimeZone => $$options{TimeZone},
1319
1320
  Unknown => $$options{Unknown},
@@ -1764,7 +1765,14 @@ GNV_TagInfo: foreach $tagInfo (@tagInfoList) {
1764
1765
  }
1765
1766
  }
1766
1767
  # return our value(s)
1767
- 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
+ }
1768
1776
  return $$vals[0];
1769
1777
  }
1770
1778
 
@@ -3284,7 +3292,7 @@ sub InsertTagValues($$$;$$$)
3284
3292
  }
3285
3293
  } elsif (ref $val eq 'HASH') {
3286
3294
  require 'Image/ExifTool/XMPStruct.pl';
3287
- $val = Image::ExifTool::XMP::SerializeStruct($val);
3295
+ $val = Image::ExifTool::XMP::SerializeStruct($self, $val);
3288
3296
  } elsif (not defined $val) {
3289
3297
  $val = $$self{OPTIONS}{MissingTagValue} if $asList;
3290
3298
  }
@@ -3768,6 +3776,8 @@ sub GetNewValueHash($$;$$$$)
3768
3776
  # this is a bit tricky: we want to add to a protected nvHash only if we
3769
3777
  # are adding a conditional delete ($_[5] true or DelValue with no Shift)
3770
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)
3771
3781
  if ($protect and not ($opts{create} and ($$nvHash{NoReplace} or $_[5] or
3772
3782
  ($$nvHash{DelValue} and not defined $$nvHash{Shift}))))
3773
3783
  {
@@ -5599,6 +5609,8 @@ sub WriteJPEG($$)
5599
5609
  $s =~ /^(Meta|META|Exif)\0\0/ and $dirName = 'Meta';
5600
5610
  } elsif ($marker == 0xe5) {
5601
5611
  $s =~ /^RMETA\0/ and $dirName = 'RMETA';
5612
+ } elsif ($marker == 0xeb) {
5613
+ $s =~ /^JP/ and $dirName = 'JUMBF';
5602
5614
  } elsif ($marker == 0xec) {
5603
5615
  $s =~ /^Ducky/ and $dirName = 'Ducky';
5604
5616
  } elsif ($marker == 0xed) {
@@ -6464,6 +6476,11 @@ sub WriteJPEG($$)
6464
6476
  $segType = 'Ricoh RMETA';
6465
6477
  $$delGroup{RMETA} and $del = 1;
6466
6478
  }
6479
+ } elsif ($marker == 0xeb) { # APP10 (JUMBF)
6480
+ if ($$segDataPt =~ /^JP/) {
6481
+ $segType = 'JUMBF';
6482
+ $$delGroup{JUMBF} and $del = 1;
6483
+ }
6467
6484
  } elsif ($marker == 0xec) { # APP12 (Ducky)
6468
6485
  if ($$segDataPt =~ /^Ducky/) {
6469
6486
  $segType = 'Ducky';
@@ -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