exiftool_vendored 12.22.0 → 12.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +201 -5
  3. data/bin/MANIFEST +22 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +45 -43
  7. data/bin/arg_files/xmp2exif.args +2 -1
  8. data/bin/config_files/acdsee.config +193 -6
  9. data/bin/config_files/convert_regions.config +25 -14
  10. data/bin/config_files/cuepointlist.config +70 -0
  11. data/bin/config_files/example.config +1 -1
  12. data/bin/exiftool +89 -70
  13. data/bin/fmt_files/gpx.fmt +1 -1
  14. data/bin/fmt_files/gpx_wpt.fmt +1 -1
  15. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +17 -4
  16. data/bin/lib/Image/ExifTool/CBOR.pm +331 -0
  17. data/bin/lib/Image/ExifTool/Canon.pm +53 -21
  18. data/bin/lib/Image/ExifTool/Charset.pm +2 -0
  19. data/bin/lib/Image/ExifTool/DPX.pm +13 -2
  20. data/bin/lib/Image/ExifTool/DjVu.pm +6 -5
  21. data/bin/lib/Image/ExifTool/Exif.pm +120 -12
  22. data/bin/lib/Image/ExifTool/FlashPix.pm +35 -10
  23. data/bin/lib/Image/ExifTool/FujiFilm.pm +19 -8
  24. data/bin/lib/Image/ExifTool/Geotag.pm +13 -2
  25. data/bin/lib/Image/ExifTool/GoPro.pm +16 -1
  26. data/bin/lib/Image/ExifTool/ICC_Profile.pm +96 -4
  27. data/bin/lib/Image/ExifTool/ID3.pm +15 -3
  28. data/bin/lib/Image/ExifTool/JPEG.pm +74 -4
  29. data/bin/lib/Image/ExifTool/JSON.pm +30 -5
  30. data/bin/lib/Image/ExifTool/Jpeg2000.pm +395 -16
  31. data/bin/lib/Image/ExifTool/LIF.pm +153 -0
  32. data/bin/lib/Image/ExifTool/Lang/nl.pm +60 -59
  33. data/bin/lib/Image/ExifTool/M2TS.pm +103 -7
  34. data/bin/lib/Image/ExifTool/MIE.pm +2 -1
  35. data/bin/lib/Image/ExifTool/MRC.pm +341 -0
  36. data/bin/lib/Image/ExifTool/MWG.pm +3 -3
  37. data/bin/lib/Image/ExifTool/MXF.pm +1 -1
  38. data/bin/lib/Image/ExifTool/MacOS.pm +3 -3
  39. data/bin/lib/Image/ExifTool/Microsoft.pm +5 -3
  40. data/bin/lib/Image/ExifTool/Nikon.pm +17 -5
  41. data/bin/lib/Image/ExifTool/NikonSettings.pm +19 -2
  42. data/bin/lib/Image/ExifTool/Olympus.pm +9 -2
  43. data/bin/lib/Image/ExifTool/Other.pm +93 -0
  44. data/bin/lib/Image/ExifTool/PDF.pm +11 -12
  45. data/bin/lib/Image/ExifTool/PNG.pm +8 -7
  46. data/bin/lib/Image/ExifTool/Panasonic.pm +28 -3
  47. data/bin/lib/Image/ExifTool/Pentax.pm +28 -5
  48. data/bin/lib/Image/ExifTool/PhaseOne.pm +4 -3
  49. data/bin/lib/Image/ExifTool/Photoshop.pm +6 -0
  50. data/bin/lib/Image/ExifTool/QuickTime.pm +210 -65
  51. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +280 -139
  52. data/bin/lib/Image/ExifTool/README +9 -2
  53. data/bin/lib/Image/ExifTool/RIFF.pm +89 -12
  54. data/bin/lib/Image/ExifTool/Samsung.pm +48 -10
  55. data/bin/lib/Image/ExifTool/Sony.pm +204 -61
  56. data/bin/lib/Image/ExifTool/TagLookup.pm +206 -19
  57. data/bin/lib/Image/ExifTool/TagNames.pod +634 -195
  58. data/bin/lib/Image/ExifTool/Torrent.pm +18 -11
  59. data/bin/lib/Image/ExifTool/WriteIPTC.pl +1 -1
  60. data/bin/lib/Image/ExifTool/WritePDF.pl +1 -0
  61. data/bin/lib/Image/ExifTool/WritePNG.pl +2 -0
  62. data/bin/lib/Image/ExifTool/WritePostScript.pl +1 -0
  63. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +58 -16
  64. data/bin/lib/Image/ExifTool/WriteXMP.pl +7 -3
  65. data/bin/lib/Image/ExifTool/Writer.pl +44 -0
  66. data/bin/lib/Image/ExifTool/XMP.pm +51 -18
  67. data/bin/lib/Image/ExifTool/XMP2.pl +4 -1
  68. data/bin/lib/Image/ExifTool/XMPStruct.pl +3 -1
  69. data/bin/lib/Image/ExifTool/ZISRAW.pm +121 -2
  70. data/bin/lib/Image/ExifTool.pm +188 -61
  71. data/bin/lib/Image/ExifTool.pod +89 -68
  72. data/bin/perl-Image-ExifTool.spec +43 -42
  73. data/lib/exiftool_vendored/version.rb +1 -1
  74. metadata +10 -9
@@ -13,10 +13,11 @@ package Image::ExifTool::Torrent;
13
13
  use strict;
14
14
  use vars qw($VERSION);
15
15
  use Image::ExifTool qw(:DataAccess :Utils);
16
+ use Image::ExifTool::XMP;
16
17
 
17
- $VERSION = '1.04';
18
+ $VERSION = '1.05';
18
19
 
19
- sub ReadBencode($$);
20
+ sub ReadBencode($$$);
20
21
  sub ExtractTags($$$;$$@);
21
22
 
22
23
  # tags extracted from BitTorrent files
@@ -99,12 +100,12 @@ sub ReadMore($$)
99
100
 
100
101
  #------------------------------------------------------------------------------
101
102
  # Read bencoded value
102
- # Inputs: 0) input file, 1) buffer (pos must be set to current position)
103
+ # Inputs: 0) ExifTool ref, 1) input file, 2) buffer (pos must be set to current position)
103
104
  # Returns: HASH ref, ARRAY ref, SCALAR ref, SCALAR, or undef on error or end of data
104
105
  # Notes: Sets BencodeError element of RAF on any error
105
- sub ReadBencode($$)
106
+ sub ReadBencode($$$)
106
107
  {
107
- my ($raf, $dataPt) = @_;
108
+ my ($et, $raf, $dataPt) = @_;
108
109
 
109
110
  # read more if necessary (keep a minimum of 64 bytes in the buffer)
110
111
  my $pos = pos($$dataPt);
@@ -123,21 +124,21 @@ sub ReadBencode($$)
123
124
  } elsif ($tok eq 'd') { # dictionary
124
125
  $val = { };
125
126
  for (;;) {
126
- my $k = ReadBencode($raf, $dataPt);
127
+ my $k = ReadBencode($et, $raf, $dataPt);
127
128
  last unless defined $k;
128
129
  # the key must be a byte string
129
130
  if (ref $k) {
130
131
  ref $k ne 'SCALAR' and $$raf{BencodeError} = 'Bad dictionary key', last;
131
132
  $k = $$k;
132
133
  }
133
- my $v = ReadBencode($raf, $dataPt);
134
+ my $v = ReadBencode($et, $raf, $dataPt);
134
135
  last unless defined $v;
135
136
  $$val{$k} = $v;
136
137
  }
137
138
  } elsif ($tok eq 'l') { # list
138
139
  $val = [ ];
139
140
  for (;;) {
140
- my $v = ReadBencode($raf, $dataPt);
141
+ my $v = ReadBencode($et, $raf, $dataPt);
141
142
  last unless defined $v;
142
143
  push @$val, $v;
143
144
  }
@@ -165,8 +166,14 @@ sub ReadBencode($$)
165
166
  }
166
167
  if (defined $value) {
167
168
  # return as binary data unless it is a reasonable-length ASCII string
168
- if (length($value) > 256 or $value =~ /[^\t\x20-\x7e]/) {
169
+ if (length($value) > 256) {
169
170
  $val = \$value;
171
+ } elsif ($value =~ /[^\t\x20-\x7e]/) {
172
+ if (Image::ExifTool::XMP::IsUTF8(\$value) >= 0) {
173
+ $val = $et->Decode($value, 'UTF8');
174
+ } else {
175
+ $val = \$value;
176
+ }
170
177
  } else {
171
178
  $val = $value;
172
179
  }
@@ -206,7 +213,7 @@ sub ExtractTags($$$;$$@)
206
213
  my $tagInfo = $et->GetTagInfo($tagTablePtr, $id) or next;
207
214
  if (ref $val eq 'ARRAY') {
208
215
  if ($$tagInfo{JoinPath}) {
209
- $val = join '/', @$val;
216
+ $val = join '/', map { ref $_ ? '(Binary data)' : $_ } @$val;
210
217
  } else {
211
218
  push @more, @$val;
212
219
  next if ref $more[0] eq 'ARRAY'; # continue expanding nested lists
@@ -273,7 +280,7 @@ sub ProcessTorrent($$)
273
280
  my $raf = $$dirInfo{RAF};
274
281
  my $buff = '';
275
282
  pos($buff) = 0;
276
- my $dict = ReadBencode($raf, \$buff);
283
+ my $dict = ReadBencode($et, $raf, \$buff);
277
284
  my $err = $$raf{BencodeError};
278
285
  $et->Warn("Bencode error: $err") if $err;
279
286
  if (ref $dict eq 'HASH' and ($$dict{announce} or $$dict{'created by'})) {
@@ -170,7 +170,7 @@ sub FormatIPTC($$$$$;$)
170
170
  } else {
171
171
  my $len = int(($1 || 0) / 8);
172
172
  if ($len == 1) { # 1 byte
173
- $$valPtr = chr($$valPtr);
173
+ $$valPtr = chr($$valPtr & 0xff);
174
174
  } elsif ($len == 2) { # 2-byte integer
175
175
  $$valPtr = pack('n', $$valPtr);
176
176
  } else { # 4-byte integer
@@ -293,6 +293,7 @@ sub WritePDF($$)
293
293
  my $newTool = new Image::ExifTool;
294
294
  $newTool->Options(List => 1);
295
295
  $newTool->Options(Password => $et->Options('Password'));
296
+ $newTool->Options(NoPDFList => $et->Options('NoPDFList'));
296
297
  $$newTool{PDF_CAPTURE} = \%capture;
297
298
  my $info = $newTool->ImageInfo($raf, 'XMP', 'PDF:*', 'Error', 'Warning');
298
299
  # not a valid PDF file unless we got a version number
@@ -179,6 +179,8 @@ sub BuildTextChunk($$$$$)
179
179
  $tag =~ s/-$lang$//; # remove language code from tagID
180
180
  } elsif ($$et{OPTIONS}{Charset} ne 'Latin' and $val =~ /[\x80-\xff]/) {
181
181
  $iTXt = 1; # write as iTXt if it contains non-Latin special characters
182
+ } elsif ($$tagInfo{iTXt}) {
183
+ $iTXt = 1; # write as iTXt if specified in user-defined tag
182
184
  }
183
185
  }
184
186
  if ($comp) {
@@ -59,6 +59,7 @@ my %psMap = (
59
59
  Photoshop => 'PostScript',
60
60
  IPTC => 'Photoshop',
61
61
  EXIFInfo => 'Photoshop',
62
+ EXIF => 'EXIFInfo',
62
63
  IFD0 => 'EXIFInfo',
63
64
  IFD1 => 'IFD0',
64
65
  ICC_Profile => 'PostScript',
@@ -163,6 +163,9 @@ sub ConvInvISO6709($)
163
163
  # latitude must have 2 digits before the decimal, and longitude 3,
164
164
  # and all values must start with a "+" or "-", and Google Photos
165
165
  # requires at least 3 digits after the decimal point
166
+ # (and as of Apr 2021, Google Photos doesn't accept coordinats
167
+ # with more than 5 digits after the decimal place:
168
+ # https://exiftool.org/forum/index.php?topic=11055.msg67171#msg67171 )
166
169
  my @fmt = ('%s%02d.%s%s','%s%03d.%s%s','%s%d.%s%s');
167
170
  foreach (@a) {
168
171
  return undef unless Image::ExifTool::IsFloat($_);
@@ -314,7 +317,7 @@ sub FormatQTValue($$;$$)
314
317
  if ($writable and $qtFormat{$writable}) {
315
318
  $flags = $qtFormat{$writable};
316
319
  } else {
317
- $flags = $qtFormat{$format} || 0;
320
+ $flags = $qtFormat{$format || 0} || 0;
318
321
  }
319
322
  } elsif ($$valPt =~ /^\xff\xd8\xff/) {
320
323
  $flags = 0x0d; # JPG
@@ -846,7 +849,7 @@ sub WriteQuickTime($$$)
846
849
  # --> hold this terminator to the end
847
850
  $term = $hdr;
848
851
  } elsif ($n != 0) {
849
- $et->Error('File format error');
852
+ $et->Error("Unknown $n bytes at end of file", 1);
850
853
  }
851
854
  last;
852
855
  }
@@ -1064,6 +1067,9 @@ sub WriteQuickTime($$$)
1064
1067
  # 3=optional base offset, 4=optional item ID)
1065
1068
  ChunkOffset => \@chunkOffset,
1066
1069
  );
1070
+ # set InPlace flag so XMP will be padded properly when
1071
+ # QuickTimePad is used if this is an XMP directory
1072
+ $subdirInfo{InPlace} = 2 if $et->Options('QuickTimePad');
1067
1073
  # pass the header pointer if necessary (for EXIF IFD's
1068
1074
  # where the Base offset is at the end of the header)
1069
1075
  if ($hdrLen and $hdrLen < $size) {
@@ -1086,7 +1092,9 @@ sub WriteQuickTime($$$)
1086
1092
  $$et{CHANGED} = $oldChanged if $$et{DemoteErrors} > 1;
1087
1093
  delete $$et{DemoteErrors};
1088
1094
  }
1089
- if (defined $newData and not length $newData and $$tagTablePtr{PERMANENT}) {
1095
+ if (defined $newData and not length $newData and ($$tagInfo{Permanent} or
1096
+ ($$tagTablePtr{PERMANENT} and not defined $$tagInfo{Permanent})))
1097
+ {
1090
1098
  # do nothing if trying to delete tag from a PERMANENT table
1091
1099
  $$et{CHANGED} = $oldChanged;
1092
1100
  undef $newData;
@@ -1094,7 +1102,9 @@ sub WriteQuickTime($$$)
1094
1102
  $$et{CUR_WRITE_GROUP} = $oldWriteGroup;
1095
1103
  SetByteOrder('MM');
1096
1104
  # add back header if necessary
1097
- if ($start and defined $newData and length $newData) {
1105
+ if ($start and defined $newData and (length $newData or
1106
+ (defined $$tagInfo{Permanent} and not $$tagInfo{Permanent})))
1107
+ {
1098
1108
  $newData = substr($buff,0,$start) . $newData;
1099
1109
  $$_[1] += $start foreach @chunkOffset;
1100
1110
  }
@@ -1232,10 +1242,14 @@ sub WriteQuickTime($$$)
1232
1242
  } elsif ($format) {
1233
1243
  $val = ReadValue(\$buff, 0, $format, undef, $size);
1234
1244
  } elsif (($tag =~ /^\xa9/ or $$tagInfo{IText}) and $size >= ($$tagInfo{IText} || 4)) {
1235
- if ($$tagInfo{IText} and $$tagInfo{IText} == 6) {
1236
- $lang = unpack('x4n', $buff);
1237
- $len = $size - 6;
1238
- $val = substr($buff, 6, $len);
1245
+ my $hdr;
1246
+ if ($$tagInfo{IText} and $$tagInfo{IText} >= 6) {
1247
+ my $iText = $$tagInfo{IText};
1248
+ my $pos = $iText - 2;
1249
+ $lang = unpack("x${pos}n", $buff);
1250
+ $hdr = substr($buff,4,$iText-6);
1251
+ $len = $size - $iText;
1252
+ $val = substr($buff, $iText, $len);
1239
1253
  } else {
1240
1254
  ($len, $lang) = unpack('nn', $buff);
1241
1255
  $len -= 4 if 4 + $len > $size; # (see QuickTime.pm for explanation)
@@ -1243,14 +1257,18 @@ sub WriteQuickTime($$$)
1243
1257
  $val = substr($buff, 4, $len);
1244
1258
  }
1245
1259
  $lang or $lang = $undLang; # treat both 0 and 'und' as 'und'
1260
+ my $enc;
1246
1261
  if ($lang < 0x400 and $val !~ /^\xfe\xff/) {
1247
1262
  $charsetQuickTime = $et->Options('CharsetQuickTime');
1248
- $val = $et->Decode($val, $charsetQuickTime);
1263
+ $enc = $charsetQuickTime;
1249
1264
  } else {
1250
- my $enc = $val=~s/^\xfe\xff// ? 'UTF16' : 'UTF8';
1265
+ $enc = $val=~s/^\xfe\xff// ? 'UTF16' : 'UTF8';
1266
+ }
1267
+ unless ($$tagInfo{NoDecode}) {
1251
1268
  $val = $et->Decode($val, $enc);
1269
+ $val =~ s/\0+$//; # remove trailing nulls if they exist
1252
1270
  }
1253
- $val =~ s/\0+$//; # remove trailing nulls if they exist
1271
+ $val = $hdr . $val if defined $hdr;
1254
1272
  my $langCode = UnpackLang($lang, 1);
1255
1273
  $langInfo = GetLangInfo($tagInfo, $langCode);
1256
1274
  $nvHash = $et->GetNewValueHash($langInfo);
@@ -1267,6 +1285,9 @@ sub WriteQuickTime($$$)
1267
1285
  }
1268
1286
  } else {
1269
1287
  $val = $buff;
1288
+ if ($tag =~ /^\xa9/ or $$tagInfo{IText}) {
1289
+ $et->Warn("Corrupted $$tagInfo{Name} value");
1290
+ }
1270
1291
  }
1271
1292
  if ($nvHash and defined $val) {
1272
1293
  if ($et->IsOverwriting($nvHash, $val)) {
@@ -1279,12 +1300,23 @@ sub WriteQuickTime($$$)
1279
1300
  $et->VerboseValue("+ $grp:$$langInfo{Name}", $newData);
1280
1301
  # add back necessary header and encode as necessary
1281
1302
  if (defined $lang) {
1282
- $newData = $et->Encode($newData, $lang < 0x400 ? $charsetQuickTime : 'UTF8');
1303
+ my $iText = $$tagInfo{IText} || 0;
1304
+ my $hdr;
1305
+ if ($iText > 6) {
1306
+ $newData .= ' 'x($iText-6) if length($newData) < $iText-6;
1307
+ $hdr = substr($newData, 0, $iText-6);
1308
+ $newData = substr($newData, $iText-6);
1309
+ }
1310
+ unless ($$tagInfo{NoDecode}) {
1311
+ $newData = $et->Encode($newData, $lang < 0x400 ? $charsetQuickTime : 'UTF8');
1312
+ }
1283
1313
  my $wLang = $lang eq $undLang ? 0 : $lang;
1284
- if ($$tagInfo{IText} and $$tagInfo{IText} == 6) {
1314
+ if ($iText < 6) {
1315
+ $newData = pack('nn', length($newData), $wLang) . $newData;
1316
+ } elsif ($iText == 6) {
1285
1317
  $newData = pack('Nn', 0, $wLang) . $newData . "\0";
1286
1318
  } else {
1287
- $newData = pack('nn', length($newData), $wLang) . $newData;
1319
+ $newData = "\0\0\0\0" . $hdr . pack('n', $wLang) . $newData . "\0";
1288
1320
  }
1289
1321
  } elsif (not $format or $format =~ /^string/ and
1290
1322
  not $$tagInfo{Binary} and not $$tagInfo{ValueConv})
@@ -1302,6 +1334,13 @@ sub WriteQuickTime($$$)
1302
1334
  }
1303
1335
  # write the new atom if it was modified
1304
1336
  if (defined $newData) {
1337
+ my $sizeDiff = length($buff) - length($newData);
1338
+ if ($sizeDiff > 0 and $$tagInfo{PreservePadding} and $et->Options('QuickTimePad')) {
1339
+ $newData .= "\0" x $sizeDiff;
1340
+ $et->VPrint(1, " ($$tagInfo{Name} padded to original size)");
1341
+ } elsif ($sizeDiff) {
1342
+ $et->VPrint(1, " ($$tagInfo{Name} changed size)");
1343
+ }
1305
1344
  my $len = length($newData) + 8;
1306
1345
  $len > 0x7fffffff and $et->Error("$$tagInfo{Name} to large to write"), last;
1307
1346
  # update size in ChunkOffset list for modified 'uuid' atom
@@ -1443,9 +1482,12 @@ sub WriteQuickTime($$$)
1443
1482
  my $grp = $et->GetGroup($tagInfo,1);
1444
1483
  $et->Warn("Can't use country code for $grp:$$tagInfo{Name}");
1445
1484
  next;
1446
- } elsif ($$tagInfo{IText} and $$tagInfo{IText} == 6) {
1485
+ } elsif ($$tagInfo{IText} and $$tagInfo{IText} >= 6) {
1447
1486
  # add 6-byte langText header and trailing null
1448
- $newVal = pack('Nn',0,$lang) . $newVal . "\0";
1487
+ # (with extra junk before language code if IText > 6)
1488
+ my $n = $$tagInfo{IText} - 6;
1489
+ $newVal .= ' ' x $n if length($newVal) < $n;
1490
+ $newVal = "\0\0\0\0" . substr($newVal,0,$n) . pack('n',0,$lang) . substr($newVal,$n) . "\0";
1449
1491
  } else {
1450
1492
  # add IText header
1451
1493
  $newVal = pack('nn',length($newVal),$lang) . $newVal;
@@ -506,7 +506,7 @@ sub ConformPathToNamespace($$)
506
506
  my $prop;
507
507
  foreach $prop (@propList) {
508
508
  my ($ns, $tag) = $prop =~ /(.+?):(.*)/;
509
- next if $$nsUsed{$ns};
509
+ next if not defined $ns or $$nsUsed{$ns};
510
510
  my $uri = $nsURI{$ns};
511
511
  unless ($uri) {
512
512
  warn "No URI for namespace prefix $ns!\n";
@@ -1417,7 +1417,11 @@ sub WriteXMP($$;$)
1417
1417
  my $uri = $nsUsed{$1};
1418
1418
  unless ($uri) {
1419
1419
  $uri = $nsURI{$1}; # we must have added a namespace
1420
- $uri or $xmpErr = "Undefined XMP namespace: $1", next;
1420
+ unless ($uri) {
1421
+ # (namespace may be empty if trying to write empty XMP structure, forum12384)
1422
+ $xmpErr = "Undefined XMP namespace: $1" if length $uri;
1423
+ next;
1424
+ }
1421
1425
  }
1422
1426
  $nsNew{$1} = $uri;
1423
1427
  # need a new description if any new namespaces
@@ -1465,7 +1469,7 @@ sub WriteXMP($$;$)
1465
1469
  $long[-2] .= "$nl$sp<$prop rdf:about='${about}'";
1466
1470
  # generate et:toolkit attribute if this is an exiftool RDF/XML output file
1467
1471
  if (@ns and $nsCur{$ns[0]} =~ m{^http://ns.exiftool.(?:ca|org)/}) {
1468
- $long[-2] .= "\n$sp${sp}xmlns:et='http://ns.exiftool.ca/1.0/'" .
1472
+ $long[-2] .= "\n$sp${sp}xmlns:et='http://ns.exiftool.org/1.0/'" .
1469
1473
  " et:toolkit='Image::ExifTool $Image::ExifTool::VERSION'";
1470
1474
  }
1471
1475
  $long[-2] .= "\n$sp${sp}xmlns:$_='$nsCur{$_}'" foreach @ns;
@@ -105,6 +105,7 @@ my %writableType = (
105
105
  ICC => [ 'ICC_Profile', 'WriteICC' ],
106
106
  IND => 'InDesign',
107
107
  JP2 => 'Jpeg2000',
108
+ JXL => 'Jpeg2000',
108
109
  MIE => undef,
109
110
  MOV => [ 'QuickTime', 'WriteMOV' ],
110
111
  MRW => 'MinoltaRaw',
@@ -573,6 +574,9 @@ sub SetNewValue($;$$%)
573
574
  my $pre = $wantGroup ? $wantGroup . ':' : '';
574
575
  $err = "Tag '$pre${origTag}' is not defined";
575
576
  $err .= ' or has a bad language code' if $origTag =~ /-/;
577
+ if (not $pre and uc($origTag) eq 'TAG') {
578
+ $err .= " (specify a writable tag name, not '${origTag}' literally)"
579
+ }
576
580
  } else {
577
581
  $err = "Invalid tag name '${tag}'";
578
582
  $err .= " (remove the leading '\$')" if $tag =~ /^\$/;
@@ -2070,6 +2074,46 @@ sub SetSystemTags($$)
2070
2074
  last;
2071
2075
  }
2072
2076
  }
2077
+ # delete Windows Zone.Identifier if specified
2078
+ my $zhash = $self->GetNewValueHash($Image::ExifTool::Extra{ZoneIdentifier});
2079
+ if ($zhash) {
2080
+ my $res = -1;
2081
+ if ($^O ne 'MSWin32') {
2082
+ $self->Warn('ZoneIdentifer is a Windows-only tag');
2083
+ } elsif (ref $file) {
2084
+ $self->Warn('Writing ZoneIdentifer requires a file name');
2085
+ } elsif (defined $self->GetNewValue('ZoneIdentifier', \$zhash)) {
2086
+ $self->Warn('ZoneIndentifier may only be delted');
2087
+ } elsif (not eval { require Win32API::File }) {
2088
+ $self->Warn('Install Win32API::File to write ZoneIdentifier');
2089
+ } else {
2090
+ my ($wattr, $wide);
2091
+ my $zfile = "${file}:Zone.Identifier";
2092
+ if ($self->EncodeFileName($zfile)) {
2093
+ $wide = 1;
2094
+ $wattr = eval { Win32API::File::GetFileAttributesW($zfile) };
2095
+ } else {
2096
+ $wattr = eval { Win32API::File::GetFileAttributes($zfile) };
2097
+ }
2098
+ if ($wattr == Win32API::File::INVALID_FILE_ATTRIBUTES()) {
2099
+ $res = 0; # file doesn't exist, nothing to do
2100
+ } elsif ($wattr & Win32API::File::FILE_ATTRIBUTE_READONLY()) {
2101
+ $self->Warn('Zone.Identifier stream is read-only');
2102
+ } else {
2103
+ if ($wide) {
2104
+ $res = 1 if eval { Win32API::File::DeleteFileW($zfile) };
2105
+ } else {
2106
+ $res = 1 if eval { Win32API::File::DeleteFile($zfile) };
2107
+ }
2108
+ if ($res > 0) {
2109
+ $self->VPrint(0, " Deleting Zone.Identifier stream\n");
2110
+ } else {
2111
+ $self->Warn('Error deleting Zone.Identifier stream');
2112
+ }
2113
+ }
2114
+ }
2115
+ $result = $res if $res == 1 or not $result;
2116
+ }
2073
2117
  return $result;
2074
2118
  }
2075
2119
 
@@ -50,7 +50,7 @@ use Image::ExifTool::Exif;
50
50
  use Image::ExifTool::GPS;
51
51
  require Exporter;
52
52
 
53
- $VERSION = '3.39';
53
+ $VERSION = '3.47';
54
54
  @ISA = qw(Exporter);
55
55
  @EXPORT_OK = qw(EscapeXML UnescapeXML);
56
56
 
@@ -71,6 +71,13 @@ sub ConvertRational($);
71
71
  sub ConvertRationalList($);
72
72
  sub WriteGSpherical($$$);
73
73
 
74
+ # standard path locations for XMP in major file types
75
+ my %stdPath = (
76
+ JPEG => 'JPEG-APP1-XMP',
77
+ TIFF => 'TIFF-IFD0-XMP',
78
+ PSD => 'PSD-XMP',
79
+ );
80
+
74
81
  # lookup for translating to ExifTool namespaces (and family 1 group names)
75
82
  %stdXlatNS = (
76
83
  # shorten ugly namespace prefixes
@@ -148,7 +155,7 @@ my %xmpNS = (
148
155
  DICOM => 'http://ns.adobe.com/DICOM/',
149
156
  'drone-dji'=> 'http://www.dji.com/drone-dji/1.0/',
150
157
  svg => 'http://www.w3.org/2000/svg',
151
- et => 'http://ns.exiftool.ca/1.0/',
158
+ et => 'http://ns.exiftool.org/1.0/',
152
159
  #
153
160
  # namespaces defined in XMP2.pl:
154
161
  #
@@ -188,7 +195,7 @@ my %xmpNS = (
188
195
  );
189
196
 
190
197
  # build reverse namespace lookup
191
- my %uri2ns = ( 'http://ns.exiftool.org/1.0/' => 'et' ); # (allow exiftool.org as well as exiftool.ca)
198
+ my %uri2ns = ( 'http://ns.exiftool.ca/1.0/' => 'et' ); # (allow exiftool.ca as well as exiftool.org)
192
199
  {
193
200
  my $ns;
194
201
  foreach $ns (keys %nsURI) {
@@ -1526,6 +1533,9 @@ my %sPantryItem = (
1526
1533
  CameraProfile => { },
1527
1534
  LookTable => { },
1528
1535
  ToneCurvePV2012 => { List => 'Seq' },
1536
+ ToneCurvePV2012Red => { List => 'Seq' },
1537
+ ToneCurvePV2012Green => { List => 'Seq' },
1538
+ ToneCurvePV2012Blue => { List => 'Seq' },
1529
1539
  },
1530
1540
  },
1531
1541
  }
@@ -2010,6 +2020,11 @@ my %sPantryItem = (
2010
2020
  Groups => { 2 => 'Location' },
2011
2021
  Writable => 'integer',
2012
2022
  PrintConv => {
2023
+ OTHER => sub {
2024
+ my ($val, $inv) = @_;
2025
+ return undef unless $inv and $val =~ /^([-+0-9])/;
2026
+ return($1 eq '-' ? 1 : 0);
2027
+ },
2013
2028
  0 => 'Above Sea Level',
2014
2029
  1 => 'Below Sea Level',
2015
2030
  },
@@ -2127,8 +2142,8 @@ my %sPantryItem = (
2127
2142
  NAMESPACE => 'exifEX',
2128
2143
  PRIORITY => 0, # not as reliable as actual EXIF tags
2129
2144
  NOTES => q{
2130
- EXIF tags added by the EXIF 2.31 for XMP specification (see
2131
- L<http://www.cipa.jp/std/documents/e/DC-X010-2017.pdf>).
2145
+ EXIF tags added by the EXIF 2.32 for XMP specification (see
2146
+ L<https://cipa.jp/std/documents/download_e.html?DC-010-2020_E>).
2132
2147
  },
2133
2148
  Gamma => { Writable => 'rational' },
2134
2149
  PhotographicSensitivity => { Writable => 'integer' },
@@ -2334,6 +2349,9 @@ my %sPantryItem = (
2334
2349
  Scene => { Groups => { 2 => 'Other' }, List => 'Bag' },
2335
2350
  SubjectCode => { Groups => { 2 => 'Other' }, List => 'Bag' },
2336
2351
  # Copyright - have seen this in a sample (Jan 2021), but I think it is non-standard
2352
+ # new IPTC Core 1.3 properties
2353
+ AltTextAccessibility => { Groups => { 2 => 'Other' }, Writable => 'lang-alt' },
2354
+ ExtDescrAccessibility => { Groups => { 2 => 'Other' }, Writable => 'lang-alt' },
2337
2355
  );
2338
2356
 
2339
2357
  # Adobe Lightroom namespace properties (lr) (ref PH)
@@ -3251,8 +3269,14 @@ NoLoop:
3251
3269
  }
3252
3270
  }
3253
3271
  # generate a default tagInfo hash if necessary
3254
- $tagInfo or $tagInfo = { Name => $name, IsDefault => 1, Priority => 0 };
3255
-
3272
+ unless ($tagInfo) {
3273
+ # shorten tag name if necessary
3274
+ if ($$et{ShortenXmpTags}) {
3275
+ my $shorten = $$et{ShortenXmpTags};
3276
+ $name = &$shorten($name);
3277
+ }
3278
+ $tagInfo = { Name => $name, IsDefault => 1, Priority => 0 };
3279
+ }
3256
3280
  # add tag Namespace entry for tags in variable-namespace tables
3257
3281
  $$tagInfo{Namespace} = $xns if $xns;
3258
3282
  if ($$et{curURI}{$ns} and $$et{curURI}{$ns} =~ m{^http://ns.exiftool.(?:ca|org)/(.*?)/(.*?)/}) {
@@ -3771,6 +3795,7 @@ sub ParseXMPElement($$$;$$$$)
3771
3795
  # (unless we already extracted shorthand values from this element)
3772
3796
  if (length $val or not $shorthand) {
3773
3797
  my $lastProp = $$propList[-1];
3798
+ $lastProp = '' unless defined $lastProp;
3774
3799
  if (defined $nodeID) {
3775
3800
  SaveBlankInfo($blankInfo, $propList, $val);
3776
3801
  } elsif ($lastProp eq 'rdf:type' and $wasEmpty) {
@@ -3844,6 +3869,7 @@ sub ProcessXMP($$;$)
3844
3869
  my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG);
3845
3870
  my $rtnVal = 0;
3846
3871
  my $bom = 0;
3872
+ my $path = $et->MetadataPath();
3847
3873
 
3848
3874
  # namespaces and prefixes currently in effect while parsing the file,
3849
3875
  # and lookup to translate brain-dead-Microsoft-Photo-software prefixes
@@ -3861,11 +3887,7 @@ sub ProcessXMP($$;$)
3861
3887
  (($$dirInfo{DirName} || '') eq 'XMP' or $$et{FILE_TYPE} eq 'XMP'))
3862
3888
  {
3863
3889
  $$et{XmpValidate} = { } if $$et{OPTIONS}{Validate};
3864
- my $path = $et->MetadataPath();
3865
- my $nonStd;
3866
- if ($$et{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/ and $path !~ /^(JPEG-APP1-XMP|TIFF-IFD0-XMP|PSD-XMP)$/) {
3867
- $nonStd = 1;
3868
- }
3890
+ my $nonStd = ($stdPath{$$et{FILE_TYPE}} and $path ne $stdPath{$$et{FILE_TYPE}});
3869
3891
  if ($nonStd and $Image::ExifTool::MWG::strict) {
3870
3892
  $et->Warn("Ignored non-standard XMP at $path");
3871
3893
  return 1;
@@ -3944,7 +3966,7 @@ sub ProcessXMP($$;$)
3944
3966
  } elsif ($1 eq 'REDXIF') {
3945
3967
  $type = 'RMD';
3946
3968
  $mime = 'application/xml';
3947
- } else {
3969
+ } elsif ($1 ne 'fcpxml') { # Final Cut Pro XML
3948
3970
  return 0;
3949
3971
  }
3950
3972
  } elsif ($buf2 =~ /<svg[\s>]/) {
@@ -3954,14 +3976,16 @@ sub ProcessXMP($$;$)
3954
3976
  } elsif ($buf2 =~ /<plist[\s>]/) {
3955
3977
  $type = 'PLIST';
3956
3978
  }
3957
- if ($isSVG and $$et{XMP_CAPTURE}) {
3958
- $et->Error("ExifTool does not yet support writing of SVG images");
3959
- return 0;
3960
- }
3961
3979
  }
3962
3980
  $isXML = 1;
3963
3981
  } elsif ($2 eq '<rdf:RDF') {
3964
3982
  $isRDF = 1; # recognize XMP without x:xmpmeta element
3983
+ } elsif ($2 eq '<svg') {
3984
+ $isSVG = $isXML = 1;
3985
+ }
3986
+ if ($isSVG and $$et{XMP_CAPTURE}) {
3987
+ $et->Error("ExifTool does not yet support writing of SVG images");
3988
+ return 0;
3965
3989
  }
3966
3990
  if ($buff =~ /^\0\0/) {
3967
3991
  $fmt = 'N'; # UTF-32 MM with or without BOM
@@ -4063,12 +4087,14 @@ sub ProcessXMP($$;$)
4063
4087
 
4064
4088
  # extract XMP/XML as a block if specified
4065
4089
  my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : 'XMP';
4090
+ my $blockExtract = $et->Options('BlockExtract');
4066
4091
  if (($$et{REQ_TAG_LOOKUP}{lc $blockName} or ($$et{TAGS_FROM_FILE} and
4067
- not $$et{EXCL_TAG_LOOKUP}{lc $blockName})) and
4092
+ not $$et{EXCL_TAG_LOOKUP}{lc $blockName}) or $blockExtract) and
4068
4093
  (($$et{FileType} eq 'XMP' and $blockName eq 'XMP') or
4069
4094
  ($$dirInfo{DirName} and $$dirInfo{DirName} eq $blockName)))
4070
4095
  {
4071
4096
  $et->FoundTag($$dirInfo{BlockInfo} || 'XMP', substr($$dataPt, $dirStart, $dirLen));
4097
+ return 1 if $blockExtract and $blockExtract > 1;
4072
4098
  }
4073
4099
 
4074
4100
  $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
@@ -4123,6 +4149,13 @@ sub ProcessXMP($$;$)
4123
4149
  }
4124
4150
  defined $fmt or $et->Warn('XMP character encoding error');
4125
4151
  }
4152
+ # warn if standard XMP is missing xpacket wrapper
4153
+ if ($$et{XMP_NO_XPACKET} and $$et{OPTIONS}{Validate} and
4154
+ $stdPath{$$et{FILE_TYPE}} and $path eq $stdPath{$$et{FILE_TYPE}} and
4155
+ not $$dirInfo{IsExtended} and not $$et{DOC_NUM})
4156
+ {
4157
+ $et->Warn('XMP is missing xpacket wrapper', 1);
4158
+ }
4126
4159
  if ($fmt) {
4127
4160
  # trim if necessary to avoid converting non-UTF data
4128
4161
  if ($dirStart or $dirEnd != length($$dataPt)) {
@@ -539,7 +539,8 @@ my %sImageRegion = ( # new in 1.5
539
539
  NOTES => q{
540
540
  This table contains tags defined by the IPTC Extension schema version 1.5.
541
541
  The actual namespace prefix is "Iptc4xmpExt", but ExifTool shortens this for
542
- the family 1 group name. (see L<http://www.iptc.org/IPTC4XMP/>)
542
+ the family 1 group name. (see
543
+ L<http://www.iptc.org/standards/photo-metadata/iptc-standard/>)
543
544
  },
544
545
  AboutCvTerm => {
545
546
  Struct => \%sCVTermDetails,
@@ -796,6 +797,8 @@ my %sImageRegion = ( # new in 1.5
796
797
  audioBitsPerSample => { Groups => { 2 => 'Audio' }, Writable => 'integer' },
797
798
  # new IPTC Extension schema 1.5 property
798
799
  ImageRegion => { Groups => { 2 => 'Image' }, List => 'Bag', Struct => \%sImageRegion },
800
+ # new Extension 1.6 property
801
+ EventId => { Name => 'EventID', List => 'Bag' },
799
802
  );
800
803
 
801
804
  #------------------------------------------------------------------------------
@@ -32,7 +32,9 @@ sub SerializeStruct($;$)
32
32
  my ($key, $val, @vals, $rtnVal);
33
33
 
34
34
  if (ref $obj eq 'HASH') {
35
- foreach $key (sort keys %$obj) {
35
+ # support hashes with ordered keys
36
+ my @keys = $$obj{_ordered_keys_} ? @{$$obj{_ordered_keys_}} : sort keys %$obj;
37
+ foreach $key (@keys) {
36
38
  push @vals, $key . '=' . SerializeStruct($$obj{$key}, '}');
37
39
  }
38
40
  $rtnVal = '{' . join(',', @vals) . '}';