exiftool-vendored.exe 13.17.0 → 13.26.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 (49) hide show
  1. package/bin/README.txt +14 -14
  2. package/bin/exiftool.exe +0 -0
  3. package/bin/exiftool_files/exiftool.pl +117 -46
  4. package/bin/exiftool_files/lib/Image/ExifTool/Apple.pm +12 -2
  5. package/bin/exiftool_files/lib/Image/ExifTool/BuildTagLookup.pm +2 -2
  6. package/bin/exiftool_files/lib/Image/ExifTool/Canon.pm +3 -2
  7. package/bin/exiftool_files/lib/Image/ExifTool/CanonRaw.pm +1 -1
  8. package/bin/exiftool_files/lib/Image/ExifTool/DJI.pm +190 -29
  9. package/bin/exiftool_files/lib/Image/ExifTool/DarwinCore.pm +22 -11
  10. package/bin/exiftool_files/lib/Image/ExifTool/EXE.pm +2 -9
  11. package/bin/exiftool_files/lib/Image/ExifTool/GM.pm +1 -1
  12. package/bin/exiftool_files/lib/Image/ExifTool/GPS.pm +3 -3
  13. package/bin/exiftool_files/lib/Image/ExifTool/Geolocation.dat +0 -0
  14. package/bin/exiftool_files/lib/Image/ExifTool/GoPro.pm +86 -48
  15. package/bin/exiftool_files/lib/Image/ExifTool/ICC_Profile.pm +1 -0
  16. package/bin/exiftool_files/lib/Image/ExifTool/ICO.pm +2 -2
  17. package/bin/exiftool_files/lib/Image/ExifTool/JPEG.pm +5 -1
  18. package/bin/exiftool_files/lib/Image/ExifTool/JSON.pm +5 -1
  19. package/bin/exiftool_files/lib/Image/ExifTool/Kodak.pm +3 -2
  20. package/bin/exiftool_files/lib/Image/ExifTool/Nikon.pm +944 -1237
  21. package/bin/exiftool_files/lib/Image/ExifTool/Olympus.pm +3 -1
  22. package/bin/exiftool_files/lib/Image/ExifTool/PCAP.pm +462 -0
  23. package/bin/exiftool_files/lib/Image/ExifTool/PDF.pm +10 -1
  24. package/bin/exiftool_files/lib/Image/ExifTool/PLIST.pm +92 -29
  25. package/bin/exiftool_files/lib/Image/ExifTool/PNG.pm +7 -1
  26. package/bin/exiftool_files/lib/Image/ExifTool/Pentax.pm +2 -1
  27. package/bin/exiftool_files/lib/Image/ExifTool/Photoshop.pm +2 -2
  28. package/bin/exiftool_files/lib/Image/ExifTool/Plot.pm +713 -0
  29. package/bin/exiftool_files/lib/Image/ExifTool/Protobuf.pm +24 -11
  30. package/bin/exiftool_files/lib/Image/ExifTool/Qualcomm.pm +78 -1
  31. package/bin/exiftool_files/lib/Image/ExifTool/QuickTime.pm +371 -324
  32. package/bin/exiftool_files/lib/Image/ExifTool/QuickTimeStream.pl +75 -27
  33. package/bin/exiftool_files/lib/Image/ExifTool/RIFF.pm +15 -6
  34. package/bin/exiftool_files/lib/Image/ExifTool/Sony.pm +33 -14
  35. package/bin/exiftool_files/lib/Image/ExifTool/TagLookup.pm +5067 -4967
  36. package/bin/exiftool_files/lib/Image/ExifTool/TagNames.pod +457 -336
  37. package/bin/exiftool_files/lib/Image/ExifTool/Trailer.pm +3 -3
  38. package/bin/exiftool_files/lib/Image/ExifTool/Validate.pm +4 -4
  39. package/bin/exiftool_files/lib/Image/ExifTool/WriteCanonRaw.pl +1 -1
  40. package/bin/exiftool_files/lib/Image/ExifTool/WriteExif.pl +9 -4
  41. package/bin/exiftool_files/lib/Image/ExifTool/WritePDF.pl +1 -1
  42. package/bin/exiftool_files/lib/Image/ExifTool/WriteQuickTime.pl +58 -5
  43. package/bin/exiftool_files/lib/Image/ExifTool/Writer.pl +14 -13
  44. package/bin/exiftool_files/lib/Image/ExifTool/XMP.pm +55 -20
  45. package/bin/exiftool_files/lib/Image/ExifTool/XMP2.pl +7 -1
  46. package/bin/exiftool_files/lib/Image/ExifTool.pm +63 -29
  47. package/bin/exiftool_files/lib/Image/ExifTool.pod +84 -86
  48. package/bin/exiftool_files/windows_exiftool.txt +96 -72
  49. package/package.json +4 -4
@@ -12,7 +12,7 @@ use strict;
12
12
  use vars qw($VERSION);
13
13
  use Image::ExifTool qw(:DataAccess :Utils);
14
14
 
15
- $VERSION = '1.00';
15
+ $VERSION = '1.01';
16
16
 
17
17
  %Image::ExifTool::Trailer::Vivo = (
18
18
  GROUPS => { 0 => 'Trailer', 1 => 'Vivo', 2 => 'Image' },
@@ -214,7 +214,7 @@ sub ProcessGoogle($$)
214
214
 
215
215
  delete $$et{ProcessGoogleTrailer}; # reset flag to process the Google trailer
216
216
 
217
- return -1 if $$dirInfo{OutFile};
217
+ return -1 if $$dirInfo{OutFile}; # let caller handle the writing
218
218
 
219
219
  # sometimes DirectoryItemLength is missing the Primary entry
220
220
  $len = [ $len ] unless ref $len eq 'ARRAY';
@@ -271,7 +271,7 @@ sub ProcessGoogle($$)
271
271
  DataPos => $start + $pos,
272
272
  DirLen => $$len[$i],
273
273
  });
274
- $et->HandleTag($tagTable, $$tag[$i], \$buff, { DataPos => $start + $pos, DataPt => \$buff });
274
+ $et->HandleTag($tagTable, $$tag[$i], \$buff, DataPos => $start + $pos, DataPt => \$buff);
275
275
  # (haven't seen non-zero padding, but I assume this is how it works
276
276
  $pos += $$len[$i] + (($pad and $$pad[$i]) ? $$pad[$i] : 0);
277
277
  }
@@ -188,10 +188,10 @@ my %validValue = (
188
188
  0x115 => undef, # SamplesPerPixel
189
189
  0x116 => undef, # RowsPerStrip
190
190
  0x117 => undef, # StripByteCounts
191
- 0x11a => 'defined $val', # XResolution
192
- 0x11b => 'defined $val', # YResolution
191
+ # (optional as of 3.0) 0x11a => 'defined $val', # XResolution
192
+ # (optional as of 3.0) 0x11b => 'defined $val', # YResolution
193
193
  0x11c => undef, # PlanarConfiguration
194
- 0x128 => '$val =~ /^[123]$/', # ResolutionUnit
194
+ # (optional as of 3.0) 0x128 => '$val =~ /^[123]$/', # ResolutionUnit
195
195
  0x201 => undef, # JPEGInterchangeFormat
196
196
  0x202 => undef, # JPEGInterchangeFormatLength
197
197
  0x212 => undef, # YCbCrSubSampling
@@ -218,7 +218,7 @@ my %validValue = (
218
218
  ExifIFD => {
219
219
  0x9000 => 'defined $val and $val =~ /^\d{4}$/', # ExifVersion
220
220
  0x9101 => 'defined $val', # ComponentsConfiguration
221
- 0xa000 => 'defined $val', # FlashpixVersion
221
+ # (optional as of 3.0) 0xa000 => 'defined $val', # FlashpixVersion
222
222
  0xa001 => '$val == 1 or $val == 0xffff', # ColorSpace
223
223
  0xa002 => 'defined $val', # PixelXDimension
224
224
  0xa003 => 'defined $val', # PixelYDimension
@@ -566,7 +566,7 @@ sub WriteCRW($$)
566
566
  my $trailPt;
567
567
  while ($success) {
568
568
  # check to see if trailer(s) exist(s)
569
- my $trailInfo = Image::ExifTool::IdentifyTrailer($raf) or last;
569
+ my $trailInfo = $et->IdentifyTrailer($raf) or last;
570
570
  # rewrite the trailer(s)
571
571
  $buff = '';
572
572
  $$trailInfo{OutFile} = \$buff;
@@ -25,9 +25,9 @@ my %crossDelete = (
25
25
  # mandatory tag default values
26
26
  my %mandatory = (
27
27
  IFD0 => {
28
- 0x011a => 72, # XResolution
29
- 0x011b => 72, # YResolution
30
- 0x0128 => 2, # ResolutionUnit (inches)
28
+ # (optional as of 3.0) 0x011a => 72, # XResolution
29
+ # (optional as of 3.0) 0x011b => 72, # YResolution
30
+ # (optional as of 3.0) 0x0128 => 2, # ResolutionUnit (inches)
31
31
  0x0213 => 1, # YCbCrPositioning (centered)
32
32
  # 0x8769 => ????, # ExifOffset
33
33
  },
@@ -40,7 +40,7 @@ my %mandatory = (
40
40
  ExifIFD => {
41
41
  0x9000 => '0232', # ExifVersion
42
42
  0x9101 => "1 2 3 0",# ComponentsConfiguration
43
- 0xa000 => '0100', # FlashpixVersion
43
+ # (optional as of 3.0) 0xa000 => '0100', # FlashpixVersion
44
44
  0xa001 => 0xffff, # ColorSpace (uncalibrated)
45
45
  # 0xa002 => ????, # ExifImageWidth
46
46
  # 0xa003 => ????, # ExifImageHeight
@@ -2269,6 +2269,11 @@ NoOverwrite: next if $isNew > 0;
2269
2269
  # build list of offsets to process
2270
2270
  my @offsetList;
2271
2271
  if ($ifd >= 0) {
2272
+ $dirName = $$dirInfo{DirName} || 'unknown';
2273
+ if ($ifd) {
2274
+ $dirName =~ s/\d+$//;
2275
+ $dirName .= $ifd;
2276
+ }
2272
2277
  my $offsetInfo = $offsetInfo[$ifd] or next;
2273
2278
  if ($$offsetInfo{0x111} and $$offsetInfo{0x144}) {
2274
2279
  # SubIFD may contain double-referenced data as both strips and tiles
@@ -546,7 +546,7 @@ sub WritePDF($$)
546
546
  }
547
547
  if ($metaChanged) {
548
548
  if ($newXMP) {
549
- unless ($metaRef) {
549
+ unless (ref $metaRef) {
550
550
  # allocate new PDF object
551
551
  $metaRef = \ "$nextObject 0 R";
552
552
  ++$nextObject;
@@ -891,7 +891,7 @@ sub WriteQuickTime($$$)
891
891
  $et or return 1; # allow dummy access to autoload this package
892
892
  my ($mdat, @mdat, @mdatEdit, $edit, $track, $outBuff, $co, $term, $delCount);
893
893
  my (%langTags, $canCreate, $delGrp, %boxPos, %didDir, $writeLast, $err, $atomCount);
894
- my ($tag, $lastTag, $lastPos, $errStr, $trailer, $buf2, $keysGrp, $keysPath);
894
+ my ($tag, $lastTag, $lastPos, $errStr, $trailer, $buf2, $keysGrp, $keysPath, $itemIndex);
895
895
  my $outfile = $$dirInfo{OutFile} || return 0;
896
896
  my $raf = $$dirInfo{RAF}; # (will be null for lower-level atoms)
897
897
  my $dataPt = $$dirInfo{DataPt}; # (will be null for top-level atoms)
@@ -982,8 +982,25 @@ sub WriteQuickTime($$$)
982
982
  $atomCount = $$tagTablePtr{VARS}{ATOM_COUNT} if $$tagTablePtr{VARS};
983
983
 
984
984
  $tag = $lastTag = '';
985
-
985
+ $itemIndex = 0 if $dirName eq 'ItemPropertyContainer';
986
+
987
+ # read ahead to parse item property associations if this is 'iprp' ItemProperties
988
+ # (necessary to determine which properties belong to primary item in HEIC file)
989
+ if ($dirName eq 'ItemProperties') {
990
+ my $pos = $raf->Tell();
991
+ for (;;) {
992
+ $raf->Read($buf2, 8) == 8 or last;
993
+ my $size = Get32u(\$buf2, 0) - 8; # (atom size without 8-byte header)
994
+ $tag = substr($buf2, 4, 4);
995
+ last if $size < 0;
996
+ $tag eq 'ipma' or $raf->Seek($size, 1), next;
997
+ ParseItemPropAssoc($buf2,$et) if $raf->Read($buf2,$size) == $size;
998
+ last;
999
+ }
1000
+ $raf->Seek($pos);
1001
+ }
986
1002
  for (;;) { # loop through all atoms at this level
1003
+ ++$itemIndex if defined $itemIndex;
987
1004
  $lastPos = $raf->Tell();
988
1005
  # stop processing if we reached a known trailer
989
1006
  if ($trailer and $lastPos >= $$trailer[1]) {
@@ -1186,6 +1203,36 @@ sub WriteQuickTime($$$)
1186
1203
  }
1187
1204
  undef $tagInfo if $tagInfo and $$tagInfo{AddedUnknown};
1188
1205
 
1206
+ # don't write this tag unless associated with the primary item
1207
+ # (Note: This relies on iinf and dimg coming before iprp)
1208
+ if (defined $itemIndex and $$et{ItemInfo}) {
1209
+ my $items = $$et{ItemInfo};
1210
+ my ($id, $prop, $isPrimary);
1211
+ my $primary = $$et{PrimaryItem};
1212
+ unless (defined $primary) {
1213
+ ($primary) = sort { $a <=> $b } keys %{$$et{ItemInfo}} if $$et{ItemInfo};
1214
+ $primary = 0 unless defined $primary;
1215
+ }
1216
+ my $pitem = $$items{$primary} || { };
1217
+ $$pitem{RefersTo} or $$pitem{RefersTo} = { };
1218
+ ItemID2: foreach $id (reverse sort { $a <=> $b } keys %$items) {
1219
+ next unless $$items{$id}{Association};
1220
+ my $item = $$items{$id};
1221
+ foreach $prop (@{$$item{Association}}) {
1222
+ next unless $prop == $itemIndex;
1223
+ my $dont = $dontInherit{$tag} || 0;
1224
+ last unless $id == $primary or (not $dont and
1225
+ ($$item{RefersTo} and $$item{RefersTo}{$primary})) or
1226
+ # special case to assume this property belongs to the primary
1227
+ # image if it belongs to an item referred to by the primary
1228
+ ($dont != 1 and $$pitem{RefersTo}{$id});
1229
+ $isPrimary = 1;
1230
+ last ItemID2;
1231
+ }
1232
+ }
1233
+ undef $tagInfo unless $isPrimary;
1234
+ }
1235
+
1189
1236
  if ($tagInfo and (not defined $$tagInfo{Writable} or $$tagInfo{Writable})) {
1190
1237
  my $subdir = $$tagInfo{SubDirectory};
1191
1238
  my ($newData, @chunkOffset);
@@ -1225,6 +1272,7 @@ sub WriteQuickTime($$$)
1225
1272
  OutFile => $outfile,
1226
1273
  NoRefTest=> 1, # don't check directory references
1227
1274
  WriteGroup => $$tagInfo{WriteGroup},
1275
+ Permanent => $$tagInfo{Permanent},
1228
1276
  # initialize array to hold details about chunk offset table
1229
1277
  # (each entry has 3-5 items: 0=atom type, 1=table offset, 2=table size,
1230
1278
  # 3=optional base offset, 4=optional item ID)
@@ -1468,7 +1516,10 @@ sub WriteQuickTime($$$)
1468
1516
  ++$$et{CHANGED};
1469
1517
  my $grp = $et->GetGroup($langInfo, 1);
1470
1518
  $et->VerboseValue("- $grp:$$langInfo{Name}", $val);
1471
- next unless defined $newData and not $$didTag{$nvHash};
1519
+ unless (defined $newData and not $$didTag{$nvHash}) {
1520
+ # must not delete items from iprp because it will mess up the ordering
1521
+ next unless defined $itemIndex;
1522
+ }
1472
1523
  $et->VerboseValue("+ $grp:$$langInfo{Name}", $newData);
1473
1524
  # add back necessary header and encode as necessary
1474
1525
  if (defined $lang) {
@@ -1497,7 +1548,7 @@ sub WriteQuickTime($$$)
1497
1548
  $newData = $et->Encode($newData, 'UTF8');
1498
1549
  } elsif ($format and not $$tagInfo{Binary}) {
1499
1550
  # format new value for writing
1500
- $newData = WriteValue($newData, $format);
1551
+ $newData = WriteValue($newData, $format, $$tagInfo{Count});
1501
1552
  }
1502
1553
  }
1503
1554
  $$didTag{$nvHash} = 1; # set flag so we don't add this tag again
@@ -1564,7 +1615,9 @@ sub WriteQuickTime($$$)
1564
1615
  }
1565
1616
  if ($msg) {
1566
1617
  # (allow empty sample description for non-audio/video handler types, eg. 'url ', 'meta')
1567
- if ($$et{MediaType}) {
1618
+ # (also, incorrectly written 'mett' SampleEntry by Google phones,
1619
+ # see https://exiftool.org/forum/index.php?msg=91158)
1620
+ if ($avType{$$et{MediaType}}) {
1568
1621
  my $grp = $$et{CUR_WRITE_GROUP} || $parent;
1569
1622
  $et->Error("$msg for $grp");
1570
1623
  return $rtnErr;
@@ -1296,15 +1296,15 @@ sub SetNewValuesFromFile($$;@)
1296
1296
  # ! option to decide how it is handled here. !
1297
1297
  # +------------------------------------------+
1298
1298
  foreach (qw(ByteUnit Charset CharsetEXIF CharsetFileName CharsetID3 CharsetIPTC
1299
- CharsetPhotoshop Composite DateFormat Debug EncodeHangs Escape ExtendedXMP
1300
- ExtractEmbedded FastScan Filter FixBase Geolocation GeolocAltNames
1301
- GeolocFeature GeolocMinPop GeolocMaxDist GlobalTimeShift GPSQuadrant
1302
- HexTagIDs IgnoreGroups IgnoreMinorErrors IgnoreTags ImageHashType Lang
1303
- LargeFileSupport LigoGPSScale ListItem ListSep MDItemTags
1304
- MissingTagValue NoPDFList NoWarning Password PrintConv QuickTimeUTC
1305
- RequestTags SaveFormat SavePath ScanForXMP StructFormat SystemTags
1306
- TimeZone Unknown UserParam Validate WindowsLongPath WindowsWideFile
1307
- XAttrTags XMPAutoConv))
1299
+ CharsetPhotoshop Composite DateFormat Debug EncodeHangs Escape
1300
+ ExtendedXMP ExtractEmbedded FastScan Filter FixBase Geolocation
1301
+ GeolocAltNames GeolocFeature GeolocMinPop GeolocMaxDist
1302
+ GlobalTimeShift GPSQuadrant HexTagIDs IgnoreGroups IgnoreMinorErrors
1303
+ IgnoreTags ImageHashType KeepUTCTime Lang LargeFileSupport
1304
+ LigoGPSScale ListItem ListSep MDItemTags MissingTagValue NoPDFList
1305
+ NoWarning Password PrintConv QuickTimeUTC RequestTags SaveFormat
1306
+ SavePath ScanForXMP StructFormat SystemTags TimeZone Unknown UserParam
1307
+ Validate WindowsLongPath WindowsWideFile XAttrTags XMPAutoConv))
1308
1308
  {
1309
1309
  $srcExifTool->Options($_ => $$options{$_});
1310
1310
  }
@@ -4262,9 +4262,11 @@ sub WriteDirectory($$$;$)
4262
4262
  if ($permanentDir{$grp0} and not ($$dirInfo{TagInfo} and $$dirInfo{TagInfo}{Deletable})) {
4263
4263
  undef $delFlag;
4264
4264
  }
4265
- # (never delete an entire QuickTime group)
4266
4265
  if ($delFlag) {
4267
- if (($grp0 =~ /^(MakerNotes)$/ or $grp1 =~ /^(IFD0|ExifIFD|MakerNotes)$/) and
4266
+ if ($$dirInfo{Permanent}) {
4267
+ $self->Warn("Not deleting permanent $dirName directory");
4268
+ undef $grp1;
4269
+ } elsif (($grp0 =~ /^(MakerNotes)$/ or $grp1 =~ /^(IFD0|ExifIFD|MakerNotes)$/) and
4268
4270
  $self->IsRawType() and
4269
4271
  # allow non-permanent MakerNote directories to be deleted (ie. NikonCapture)
4270
4272
  (not $$dirInfo{TagInfo} or not defined $$dirInfo{TagInfo}{Permanent} or
@@ -5044,7 +5046,6 @@ TryLib: for ($lib=$strptimeLib; ; $lib='') {
5044
5046
  last;
5045
5047
  }
5046
5048
  if (not $lib) {
5047
- last unless $$self{OPTIONS}{StrictDate};
5048
5049
  warn $wrn || "Install POSIX::strptime or Time::Piece for inverse date/time conversions\n";
5049
5050
  return undef;
5050
5051
  } elsif ($lib eq 'POSIX::strptime') {
@@ -6027,7 +6028,7 @@ sub WriteJPEG($$)
6027
6028
  Write($outfile, $hdr, $s, $segData) or $err = 1;
6028
6029
  my ($buff, $endPos, $trailInfo);
6029
6030
  my $delPreview = $$self{DEL_PREVIEW};
6030
- $trailInfo = IdentifyTrailer($raf) unless $$delGroup{Trailer};
6031
+ $trailInfo = $self->IdentifyTrailer($raf) unless $$delGroup{Trailer};
6031
6032
  my $nvTrail = $self->GetNewValueHash($Image::ExifTool::Extra{Trailer});
6032
6033
  unless ($oldOutfile or $delPreview or $trailInfo or $$delGroup{Trailer} or $nvTrail or
6033
6034
  $$self{HiddenData})
@@ -50,7 +50,7 @@ use Image::ExifTool::Exif;
50
50
  use Image::ExifTool::GPS;
51
51
  require Exporter;
52
52
 
53
- $VERSION = '3.70';
53
+ $VERSION = '3.73';
54
54
  @ISA = qw(Exporter);
55
55
  @EXPORT_OK = qw(EscapeXML UnescapeXML);
56
56
 
@@ -165,7 +165,7 @@ my %xmpNS = (
165
165
  #
166
166
  plus => 'http://ns.useplus.org/ldf/xmp/1.0/',
167
167
  # (prism recommendations from http://www.prismstandard.org/specifications/3.0/Image_Guide_3.0.htm)
168
- prism => 'http://prismstandard.org/namespaces/basic/2.0/',
168
+ prism => 'http://prismstandard.org/namespaces/basic/2.0/', # (maybe left at 2.0 to avoid compatibility issues -- think hard before changing this)
169
169
  prl => 'http://prismstandard.org/namespaces/prl/2.1/',
170
170
  pur => 'http://prismstandard.org/namespaces/prismusagerights/2.1/',
171
171
  pmi => 'http://prismstandard.org/namespaces/pmi/2.2/',
@@ -1247,8 +1247,22 @@ my %sPantryItem = (
1247
1247
  NAMESPACE => 'pdfx',
1248
1248
  NOTES => q{
1249
1249
  PDF extension tags. This namespace is used to store application-defined PDF
1250
- information, so there are no pre-defined tags. User-defined tags must be
1251
- created to enable writing of XMP-pdfx information.
1250
+ information, so there are few pre-defined tags. User-defined tags must be
1251
+ created to enable writing of other XMP-pdfx information.
1252
+ },
1253
+ SourceModified => {
1254
+ Name => 'SourceModified',
1255
+ Groups => { 2 => 'Time' },
1256
+ Shift => 'Time',
1257
+ ValueConv => 'require Image::ExifTool::PDF; $val = Image::ExifTool::PDF::ConvertPDFDate($val)',
1258
+ ValueConvInv => q{
1259
+ require Image::ExifTool::PDF;
1260
+ $val = Image::ExifTool::PDF::WritePDFValue($self,$val,"date");
1261
+ $val =~ tr/()//d;
1262
+ return $val;
1263
+ },
1264
+ PrintConv => '$self->ConvertDateTime($val)',
1265
+ PrintConvInv => '$self->InverseDateTime($val)',
1252
1266
  },
1253
1267
  );
1254
1268
 
@@ -3525,14 +3539,19 @@ NoLoop:
3525
3539
  }
3526
3540
  last unless $sti;
3527
3541
  }
3528
- # generate new tagInfo hash based on existing top-level tag
3529
- $tagInfo = { %$sti, Name => $flat . $$sti{Name} };
3530
- # be careful not to copy elements we shouldn't...
3531
- delete $$tagInfo{Description}; # Description will be different
3532
- # can't copy group hash because group 1 will be different and
3533
- # we need to check this when writing tag to a specific group
3534
- delete $$tagInfo{Groups};
3535
- $$tagInfo{Groups}{2} = $$sti{Groups}{2} if $$sti{Groups};
3542
+ # use existing definition if we already added this tag
3543
+ if ($$tagTablePtr{$tagID}) {
3544
+ $tagInfo = $$tagTablePtr{$tagID};
3545
+ } else {
3546
+ # generate new tagInfo hash based on existing top-level tag
3547
+ $tagInfo = { %$sti, Name => $flat . $$sti{Name} };
3548
+ # be careful not to copy elements we shouldn't...
3549
+ delete $$tagInfo{Description}; # Description will be different
3550
+ # can't copy group hash because group 1 will be different and
3551
+ # we need to check this when writing tag to a specific group
3552
+ delete $$tagInfo{Groups};
3553
+ $$tagInfo{Groups}{2} = $$sti{Groups}{2} if $$sti{Groups};
3554
+ }
3536
3555
  last;
3537
3556
  }
3538
3557
  }
@@ -3578,13 +3597,15 @@ NoLoop:
3578
3597
  #} elsif (grep / /, @$props) {
3579
3598
  # $$tagInfo{List} = 1;
3580
3599
  }
3581
- # save property list for verbose "adding" message unless this tag already exists
3582
- $added = \@tagList unless $$tagTablePtr{$tagID};
3583
- # if this is an empty structure, we must add a Struct field
3584
- if (not length $val and $$attrs{'rdf:parseType'} and $$attrs{'rdf:parseType'} eq 'Resource') {
3585
- $$tagInfo{Struct} = { STRUCT_NAME => 'XMP Unknown' };
3600
+ unless ($$tagTablePtr{$tagID} and $$tagTablePtr{$tagID} eq $tagInfo) {
3601
+ # save property list for verbose "adding" message unless this tag already exists
3602
+ $added = \@tagList unless $$tagTablePtr{$tagID};
3603
+ # if this is an empty structure, we must add a Struct field
3604
+ if (not length $val and $$attrs{'rdf:parseType'} and $$attrs{'rdf:parseType'} eq 'Resource') {
3605
+ $$tagInfo{Struct} = { STRUCT_NAME => 'XMP Unknown' } unless $$tagInfo{Struct};
3606
+ }
3607
+ AddTagToTable($tagTablePtr, $tagID, $tagInfo);
3586
3608
  }
3587
- AddTagToTable($tagTablePtr, $tagID, $tagInfo);
3588
3609
  last;
3589
3610
  }
3590
3611
  # decode value if necessary (et:encoding was used before exiftool 7.71)
@@ -3804,8 +3825,22 @@ sub ParseXMPElement($$$;$$$$)
3804
3825
  my ($parseResource, %attrs, @attrs);
3805
3826
  # this hangs Perl (v5.18.4) for a specific capture string [patched in ExifTool 12.98]
3806
3827
  # while ($attrs =~ m/(\S+?)\s*=\s*(['"])(.*?)\2/sg) {
3807
- while ($attrs =~ /(\S+?)\s*=\s*(['"])/g) {
3808
- my ($attr, $quote) = ($1, $2);
3828
+ # this may hang Perl v5.26.3 (but not v5.18.4) if there is lots of garbage in the XMP [patched in 13.23]
3829
+ # while ($attrs =~ /(\S+?)\s*=\s*(['"])/g) {
3830
+ for (;;) {
3831
+ my ($attr, $quote);
3832
+ if (length($attrs) < 2000) { # (do it the easy way if attributes aren't stupid long)
3833
+ last unless $attrs =~ /(\S+)\s*=\s*(['"])/g;
3834
+ ($attr, $quote) = ($1, $2);
3835
+ } else {
3836
+ # 13.23 patch to avoid capturing tons of garbage if XMP is corrupted
3837
+ last unless $attrs =~ /=\s*(['"])/g;
3838
+ $quote = $1;
3839
+ my $p = pos($attrs) > 1000 ? pos($attrs) - 1000 : 0;
3840
+ my $tmp = substr($attrs, $p, pos($attrs)-$p);
3841
+ last unless $tmp =~ /(\S+)\s*=\s*$quote$/;
3842
+ $attr = $1;
3843
+ }
3809
3844
  my $p0 = pos($attrs);
3810
3845
  last unless $attrs =~ /$quote/g;
3811
3846
  my $val = substr($attrs, $p0, pos($attrs)-$p0-1);
@@ -170,7 +170,11 @@ my %sAppInfo = (
170
170
  %xmpTableDefaults,
171
171
  GROUPS => { 1 => 'XMP-xmpDM', 2 => 'Image' },
172
172
  NAMESPACE => 'xmpDM',
173
- NOTES => 'XMP Dynamic Media namespace tags.',
173
+ NOTES => q{
174
+ XMP Dynamic Media namespace tags. See
175
+ L<https://developer.adobe.com/xmp/docs/XMPNamespaces/xmpDM/> for the
176
+ specification.
177
+ },
174
178
  absPeakAudioFilePath=> { },
175
179
  album => { },
176
180
  altTapeName => { },
@@ -402,6 +406,7 @@ my %sLocationDetails = (
402
406
  STRUCT_NAME => 'LocationDetails',
403
407
  NAMESPACE => 'Iptc4xmpExt',
404
408
  GROUPS => { 2 => 'Location' },
409
+ NOTES => 'Note that the GPS elements of this structure are in the "exif" namespace.',
405
410
  Identifier => { List => 'Bag', Namespace => 'xmp' },
406
411
  City => { },
407
412
  CountryCode => { },
@@ -420,6 +425,7 @@ my %sLocationDetails = (
420
425
  PrintConvInv => '$val=~s/\s*m$//;$val',
421
426
  },
422
427
  GPSAltitudeRef => {
428
+ Namespace => 'exif',
423
429
  Writable => 'integer',
424
430
  PrintConv => {
425
431
  OTHER => sub {