exiftool_vendored 12.18.0 → 12.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +236 -4
  3. data/bin/MANIFEST +23 -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 +2 -9
  12. data/bin/exiftool +152 -97
  13. data/bin/fmt_files/gpx.fmt +2 -2
  14. data/bin/fmt_files/gpx_wpt.fmt +2 -2
  15. data/bin/fmt_files/kml.fmt +1 -1
  16. data/bin/fmt_files/kml_track.fmt +1 -1
  17. data/bin/lib/Image/ExifTool/Apple.pm +3 -2
  18. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +31 -13
  19. data/bin/lib/Image/ExifTool/CBOR.pm +331 -0
  20. data/bin/lib/Image/ExifTool/Canon.pm +44 -19
  21. data/bin/lib/Image/ExifTool/DJI.pm +6 -6
  22. data/bin/lib/Image/ExifTool/DPX.pm +13 -2
  23. data/bin/lib/Image/ExifTool/DjVu.pm +6 -5
  24. data/bin/lib/Image/ExifTool/Exif.pm +124 -13
  25. data/bin/lib/Image/ExifTool/FITS.pm +13 -2
  26. data/bin/lib/Image/ExifTool/FlashPix.pm +35 -10
  27. data/bin/lib/Image/ExifTool/FujiFilm.pm +19 -8
  28. data/bin/lib/Image/ExifTool/GPS.pm +22 -11
  29. data/bin/lib/Image/ExifTool/Geotag.pm +13 -2
  30. data/bin/lib/Image/ExifTool/GoPro.pm +16 -1
  31. data/bin/lib/Image/ExifTool/ICC_Profile.pm +2 -2
  32. data/bin/lib/Image/ExifTool/ID3.pm +15 -3
  33. data/bin/lib/Image/ExifTool/JPEG.pm +74 -4
  34. data/bin/lib/Image/ExifTool/JSON.pm +30 -5
  35. data/bin/lib/Image/ExifTool/Jpeg2000.pm +395 -16
  36. data/bin/lib/Image/ExifTool/LIF.pm +153 -0
  37. data/bin/lib/Image/ExifTool/Lang/nl.pm +60 -59
  38. data/bin/lib/Image/ExifTool/M2TS.pm +137 -5
  39. data/bin/lib/Image/ExifTool/MIE.pm +4 -3
  40. data/bin/lib/Image/ExifTool/MRC.pm +341 -0
  41. data/bin/lib/Image/ExifTool/MWG.pm +3 -3
  42. data/bin/lib/Image/ExifTool/MXF.pm +1 -1
  43. data/bin/lib/Image/ExifTool/MacOS.pm +3 -3
  44. data/bin/lib/Image/ExifTool/Microsoft.pm +298 -82
  45. data/bin/lib/Image/ExifTool/Nikon.pm +18 -5
  46. data/bin/lib/Image/ExifTool/NikonSettings.pm +19 -2
  47. data/bin/lib/Image/ExifTool/Olympus.pm +10 -3
  48. data/bin/lib/Image/ExifTool/Other.pm +93 -0
  49. data/bin/lib/Image/ExifTool/PDF.pm +9 -12
  50. data/bin/lib/Image/ExifTool/PNG.pm +8 -7
  51. data/bin/lib/Image/ExifTool/Panasonic.pm +28 -3
  52. data/bin/lib/Image/ExifTool/Pentax.pm +28 -5
  53. data/bin/lib/Image/ExifTool/PhaseOne.pm +4 -3
  54. data/bin/lib/Image/ExifTool/Photoshop.pm +6 -0
  55. data/bin/lib/Image/ExifTool/QuickTime.pm +234 -75
  56. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +283 -141
  57. data/bin/lib/Image/ExifTool/README +5 -2
  58. data/bin/lib/Image/ExifTool/RIFF.pm +89 -12
  59. data/bin/lib/Image/ExifTool/Samsung.pm +48 -10
  60. data/bin/lib/Image/ExifTool/Shortcuts.pm +9 -0
  61. data/bin/lib/Image/ExifTool/Sony.pm +230 -69
  62. data/bin/lib/Image/ExifTool/TagInfoXML.pm +1 -0
  63. data/bin/lib/Image/ExifTool/TagLookup.pm +4145 -4029
  64. data/bin/lib/Image/ExifTool/TagNames.pod +671 -287
  65. data/bin/lib/Image/ExifTool/Torrent.pm +18 -11
  66. data/bin/lib/Image/ExifTool/WriteExif.pl +1 -1
  67. data/bin/lib/Image/ExifTool/WriteIPTC.pl +1 -1
  68. data/bin/lib/Image/ExifTool/WritePDF.pl +1 -0
  69. data/bin/lib/Image/ExifTool/WritePNG.pl +2 -0
  70. data/bin/lib/Image/ExifTool/WritePostScript.pl +1 -0
  71. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +55 -21
  72. data/bin/lib/Image/ExifTool/WriteXMP.pl +7 -3
  73. data/bin/lib/Image/ExifTool/Writer.pl +47 -10
  74. data/bin/lib/Image/ExifTool/XMP.pm +45 -15
  75. data/bin/lib/Image/ExifTool/XMP2.pl +3 -1
  76. data/bin/lib/Image/ExifTool/XMPStruct.pl +3 -1
  77. data/bin/lib/Image/ExifTool/ZISRAW.pm +121 -2
  78. data/bin/lib/Image/ExifTool.pm +233 -81
  79. data/bin/lib/Image/ExifTool.pod +114 -93
  80. data/bin/perl-Image-ExifTool.spec +43 -42
  81. data/lib/exiftool_vendored/version.rb +1 -1
  82. metadata +28 -13
@@ -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'})) {
@@ -548,7 +548,7 @@ sub WriteExif($$$)
548
548
  my $mustRead;
549
549
  if ($dirStart < 0 or $dirStart > $dataLen-2) {
550
550
  $mustRead = 1;
551
- } elsif ($dirLen > 2) {
551
+ } elsif ($dirLen >= 2) {
552
552
  my $len = 2 + 12 * Get16u($dataPt, $dirStart);
553
553
  $mustRead = 1 if $dirStart + $len > $dataLen;
554
554
  }
@@ -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',
@@ -17,6 +17,7 @@ my %movMap = (
17
17
  Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location)
18
18
  Meta => 'UserData',
19
19
  XMP => 'UserData', # MOV-Movie-UserData-XMP
20
+ Microsoft => 'UserData', # MOV-Movie-UserData-Microsoft
20
21
  UserData => 'Movie', # MOV-Movie-UserData
21
22
  Movie => 'MOV',
22
23
  GSpherical => 'SphericalVideoXML', # MOV-Movie-Track-SphericalVideoXML
@@ -30,6 +31,7 @@ my %mp4Map = (
30
31
  Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location)
31
32
  Meta => 'UserData',
32
33
  UserData => 'Movie', # MOV-Movie-UserData
34
+ Microsoft => 'UserData', # MOV-Movie-UserData-Microsoft
33
35
  Movie => 'MOV',
34
36
  XMP => 'MOV', # MOV-XMP
35
37
  GSpherical => 'SphericalVideoXML', # MOV-Movie-Track-SphericalVideoXML
@@ -76,8 +78,8 @@ my %dirMap = (
76
78
  # convert ExifTool Format to QuickTime type
77
79
  my %qtFormat = (
78
80
  'undef' => 0x00, string => 0x01,
79
- int8s => 0x15, int16s => 0x15, int32s => 0x15,
80
- int8u => 0x16, int16u => 0x16, int32u => 0x16,
81
+ int8s => 0x15, int16s => 0x15, int32s => 0x15, int64s => 0x15,
82
+ int8u => 0x16, int16u => 0x16, int32u => 0x16, int64u => 0x16,
81
83
  float => 0x17, double => 0x18,
82
84
  );
83
85
  my $undLang = 0x55c4; # numeric code for default ('und') language
@@ -161,6 +163,9 @@ sub ConvInvISO6709($)
161
163
  # latitude must have 2 digits before the decimal, and longitude 3,
162
164
  # and all values must start with a "+" or "-", and Google Photos
163
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 )
164
169
  my @fmt = ('%s%02d.%s%s','%s%03d.%s%s','%s%d.%s%s');
165
170
  foreach (@a) {
166
171
  return undef unless Image::ExifTool::IsFloat($_);
@@ -294,7 +299,7 @@ sub GetLangInfo($$)
294
299
  sub CheckQTValue($$$)
295
300
  {
296
301
  my ($et, $tagInfo, $valPtr) = @_;
297
- my $format = $$tagInfo{Format} || $$tagInfo{Table}{FORMAT};
302
+ my $format = $$tagInfo{Format} || $$tagInfo{Writable} || $$tagInfo{Table}{FORMAT};
298
303
  return undef unless $format;
299
304
  return Image::ExifTool::CheckValue($valPtr, $format, $$tagInfo{Count});
300
305
  }
@@ -307,12 +312,12 @@ sub FormatQTValue($$;$$)
307
312
  {
308
313
  my ($et, $valPt, $format, $writable) = @_;
309
314
  my $flags;
310
- if ($format and $format ne 'string') {
311
- $$valPt = WriteValue($$valPt, $format);
315
+ if ($format and $format ne 'string' or not $format and $writable and $writable ne 'string') {
316
+ $$valPt = WriteValue($$valPt, $format || $writable);
312
317
  if ($writable and $qtFormat{$writable}) {
313
318
  $flags = $qtFormat{$writable};
314
319
  } else {
315
- $flags = $qtFormat{$format} || 0;
320
+ $flags = $qtFormat{$format || 0} || 0;
316
321
  }
317
322
  } elsif ($$valPt =~ /^\xff\xd8\xff/) {
318
323
  $flags = 0x0d; # JPG
@@ -844,7 +849,7 @@ sub WriteQuickTime($$$)
844
849
  # --> hold this terminator to the end
845
850
  $term = $hdr;
846
851
  } elsif ($n != 0) {
847
- $et->Error('File format error');
852
+ $et->Error("Unknown $n bytes at end of file", 1);
848
853
  }
849
854
  last;
850
855
  }
@@ -1084,7 +1089,9 @@ sub WriteQuickTime($$$)
1084
1089
  $$et{CHANGED} = $oldChanged if $$et{DemoteErrors} > 1;
1085
1090
  delete $$et{DemoteErrors};
1086
1091
  }
1087
- if (defined $newData and not length $newData and $$tagTablePtr{PERMANENT}) {
1092
+ if (defined $newData and not length $newData and ($$tagInfo{Permanent} or
1093
+ ($$tagTablePtr{PERMANENT} and not defined $$tagInfo{Permanent})))
1094
+ {
1088
1095
  # do nothing if trying to delete tag from a PERMANENT table
1089
1096
  $$et{CHANGED} = $oldChanged;
1090
1097
  undef $newData;
@@ -1092,7 +1099,9 @@ sub WriteQuickTime($$$)
1092
1099
  $$et{CUR_WRITE_GROUP} = $oldWriteGroup;
1093
1100
  SetByteOrder('MM');
1094
1101
  # add back header if necessary
1095
- if ($start and defined $newData and length $newData) {
1102
+ if ($start and defined $newData and (length $newData or
1103
+ (defined $$tagInfo{Permanent} and not $$tagInfo{Permanent})))
1104
+ {
1096
1105
  $newData = substr($buff,0,$start) . $newData;
1097
1106
  $$_[1] += $start foreach @chunkOffset;
1098
1107
  }
@@ -1230,10 +1239,14 @@ sub WriteQuickTime($$$)
1230
1239
  } elsif ($format) {
1231
1240
  $val = ReadValue(\$buff, 0, $format, undef, $size);
1232
1241
  } elsif (($tag =~ /^\xa9/ or $$tagInfo{IText}) and $size >= ($$tagInfo{IText} || 4)) {
1233
- if ($$tagInfo{IText} and $$tagInfo{IText} == 6) {
1234
- $lang = unpack('x4n', $buff);
1235
- $len = $size - 6;
1236
- $val = substr($buff, 6, $len);
1242
+ my $hdr;
1243
+ if ($$tagInfo{IText} and $$tagInfo{IText} >= 6) {
1244
+ my $iText = $$tagInfo{IText};
1245
+ my $pos = $iText - 2;
1246
+ $lang = unpack("x${pos}n", $buff);
1247
+ $hdr = substr($buff,4,$iText-6);
1248
+ $len = $size - $iText;
1249
+ $val = substr($buff, $iText, $len);
1237
1250
  } else {
1238
1251
  ($len, $lang) = unpack('nn', $buff);
1239
1252
  $len -= 4 if 4 + $len > $size; # (see QuickTime.pm for explanation)
@@ -1241,14 +1254,18 @@ sub WriteQuickTime($$$)
1241
1254
  $val = substr($buff, 4, $len);
1242
1255
  }
1243
1256
  $lang or $lang = $undLang; # treat both 0 and 'und' as 'und'
1257
+ my $enc;
1244
1258
  if ($lang < 0x400 and $val !~ /^\xfe\xff/) {
1245
1259
  $charsetQuickTime = $et->Options('CharsetQuickTime');
1246
- $val = $et->Decode($val, $charsetQuickTime);
1260
+ $enc = $charsetQuickTime;
1247
1261
  } else {
1248
- my $enc = $val=~s/^\xfe\xff// ? 'UTF16' : 'UTF8';
1262
+ $enc = $val=~s/^\xfe\xff// ? 'UTF16' : 'UTF8';
1263
+ }
1264
+ unless ($$tagInfo{NoDecode}) {
1249
1265
  $val = $et->Decode($val, $enc);
1266
+ $val =~ s/\0+$//; # remove trailing nulls if they exist
1250
1267
  }
1251
- $val =~ s/\0+$//; # remove trailing nulls if they exist
1268
+ $val = $hdr . $val if defined $hdr;
1252
1269
  my $langCode = UnpackLang($lang, 1);
1253
1270
  $langInfo = GetLangInfo($tagInfo, $langCode);
1254
1271
  $nvHash = $et->GetNewValueHash($langInfo);
@@ -1265,6 +1282,9 @@ sub WriteQuickTime($$$)
1265
1282
  }
1266
1283
  } else {
1267
1284
  $val = $buff;
1285
+ if ($tag =~ /^\xa9/ or $$tagInfo{IText}) {
1286
+ $et->Warn("Corrupted $$tagInfo{Name} value");
1287
+ }
1268
1288
  }
1269
1289
  if ($nvHash and defined $val) {
1270
1290
  if ($et->IsOverwriting($nvHash, $val)) {
@@ -1277,12 +1297,23 @@ sub WriteQuickTime($$$)
1277
1297
  $et->VerboseValue("+ $grp:$$langInfo{Name}", $newData);
1278
1298
  # add back necessary header and encode as necessary
1279
1299
  if (defined $lang) {
1280
- $newData = $et->Encode($newData, $lang < 0x400 ? $charsetQuickTime : 'UTF8');
1300
+ my $iText = $$tagInfo{IText} || 0;
1301
+ my $hdr;
1302
+ if ($iText > 6) {
1303
+ $newData .= ' 'x($iText-6) if length($newData) < $iText-6;
1304
+ $hdr = substr($newData, 0, $iText-6);
1305
+ $newData = substr($newData, $iText-6);
1306
+ }
1307
+ unless ($$tagInfo{NoDecode}) {
1308
+ $newData = $et->Encode($newData, $lang < 0x400 ? $charsetQuickTime : 'UTF8');
1309
+ }
1281
1310
  my $wLang = $lang eq $undLang ? 0 : $lang;
1282
- if ($$tagInfo{IText} and $$tagInfo{IText} == 6) {
1311
+ if ($iText < 6) {
1312
+ $newData = pack('nn', length($newData), $wLang) . $newData;
1313
+ } elsif ($iText == 6) {
1283
1314
  $newData = pack('Nn', 0, $wLang) . $newData . "\0";
1284
1315
  } else {
1285
- $newData = pack('nn', length($newData), $wLang) . $newData;
1316
+ $newData = "\0\0\0\0" . $hdr . pack('n', $wLang) . $newData . "\0";
1286
1317
  }
1287
1318
  } elsif (not $format or $format =~ /^string/ and
1288
1319
  not $$tagInfo{Binary} and not $$tagInfo{ValueConv})
@@ -1441,9 +1472,12 @@ sub WriteQuickTime($$$)
1441
1472
  my $grp = $et->GetGroup($tagInfo,1);
1442
1473
  $et->Warn("Can't use country code for $grp:$$tagInfo{Name}");
1443
1474
  next;
1444
- } elsif ($$tagInfo{IText} and $$tagInfo{IText} == 6) {
1475
+ } elsif ($$tagInfo{IText} and $$tagInfo{IText} >= 6) {
1445
1476
  # add 6-byte langText header and trailing null
1446
- $newVal = pack('Nn',0,$lang) . $newVal . "\0";
1477
+ # (with extra junk before language code if IText > 6)
1478
+ my $n = $$tagInfo{IText} - 6;
1479
+ $newVal .= ' ' x $n if length($newVal) < $n;
1480
+ $newVal = "\0\0\0\0" . substr($newVal,0,$n) . pack('n',0,$lang) . substr($newVal,$n) . "\0";
1447
1481
  } else {
1448
1482
  # add IText header
1449
1483
  $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',
@@ -135,10 +136,10 @@ my %rawType = (
135
136
  my @delGroups = qw(
136
137
  Adobe AFCP APP0 APP1 APP2 APP3 APP4 APP5 APP6 APP7 APP8 APP9 APP10 APP11
137
138
  APP12 APP13 APP14 APP15 CanonVRD CIFF Ducky EXIF ExifIFD File FlashPix
138
- FotoStation GlobParamIFD GPS ICC_Profile IFD0 IFD1 Insta360 InteropIFD IPTC
139
- ItemList JFIF Jpeg2000 Keys MakerNotes Meta MetaIFD MIE MPF NikonCapture PDF
140
- PDF-update PhotoMechanic Photoshop PNG PNG-pHYs PrintIM QuickTime RMETA RSRC
141
- SubIFD Trailer UserData XML XML-* XMP XMP-*
139
+ FotoStation GlobParamIFD GPS ICC_Profile IFD0 IFD1 Insta360 InteropIFD
140
+ IPTC ItemList JFIF Jpeg2000 Keys MakerNotes Meta MetaIFD Microsoft MIE
141
+ MPF NikonCapture PDF PDF-update PhotoMechanic Photoshop PNG PNG-pHYs
142
+ PrintIM QuickTime RMETA RSRC SubIFD Trailer UserData XML XML-* XMP XMP-*
142
143
  );
143
144
  # family 2 group names that we can delete
144
145
  my @delGroup2 = qw(
@@ -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 =~ /^\$/;
@@ -2639,12 +2643,14 @@ GWTInfo: foreach $tagInfo (@infoArray) {
2639
2643
 
2640
2644
  #------------------------------------------------------------------------------
2641
2645
  # Get list of all group names
2642
- # Inputs: 0) Group family number
2646
+ # Inputs: 0) [optional] ExifTool ref, 1) Group family number
2643
2647
  # Returns: List of group names (sorted alphabetically)
2644
- sub GetAllGroups($)
2648
+ sub GetAllGroups($;$)
2645
2649
  {
2646
2650
  local $_;
2647
2651
  my $family = shift || 0;
2652
+ my $self;
2653
+ ref $family and $self = $family, $family = shift || 0;
2648
2654
 
2649
2655
  $family == 3 and return('Doc#', 'Main');
2650
2656
  $family == 4 and return('Copy#');
@@ -2663,9 +2669,23 @@ sub GetAllGroups($)
2663
2669
  $allGroups{$grp} = 1 if ($grps = $$table{GROUPS}) and ($grp = $$grps{$family});
2664
2670
  foreach $tag (TagTableKeys($table)) {
2665
2671
  my @infoArray = GetTagInfoList($table, $tag);
2666
- foreach $tagInfo (@infoArray) {
2667
- next unless ($grps = $$tagInfo{Groups}) and ($grp = $$grps{$family});
2668
- $allGroups{$grp} = 1;
2672
+ if ($family == 7) {
2673
+ foreach $tagInfo (@infoArray) {
2674
+ my $id = $$tagInfo{TagID};
2675
+ if (not defined $id) {
2676
+ $id = ''; # (just to be safe)
2677
+ } elsif ($id =~ /^\d+$/) {
2678
+ $id = sprintf('0x%x', $id) if $self and $$self{OPTIONS}{HexTagIDs};
2679
+ } else {
2680
+ $id =~ s/([^-_A-Za-z0-9])/sprintf('%.2x',ord $1)/ge;
2681
+ }
2682
+ $allGroups{'ID-' . $id} = 1;
2683
+ }
2684
+ } else {
2685
+ foreach $tagInfo (@infoArray) {
2686
+ next unless ($grps = $$tagInfo{Groups}) and ($grp = $$grps{$family});
2687
+ $allGroups{$grp} = 1;
2688
+ }
2669
2689
  }
2670
2690
  }
2671
2691
  }
@@ -4935,6 +4955,12 @@ sub Set64u(@)
4935
4955
  $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val;
4936
4956
  return $val;
4937
4957
  }
4958
+ sub Set64s(@)
4959
+ {
4960
+ my $val = shift;
4961
+ $val < 0 and $val += 4294967296 * 4294967296; # (temporary hack won't really work due to round-off errors)
4962
+ return Set64u($val, @_);
4963
+ }
4938
4964
  sub SetRational64u(@) {
4939
4965
  my ($numer,$denom) = Rationalize($_[0],0xffffffff);
4940
4966
  my $val = Set32u($numer) . Set32u($denom);
@@ -4996,6 +5022,7 @@ my %writeValueProc = (
4996
5022
  int16uRev => \&Set16uRev,
4997
5023
  int32s => \&Set32s,
4998
5024
  int32u => \&Set32u,
5025
+ int64s => \&Set64s,
4999
5026
  int64u => \&Set64u,
5000
5027
  rational32s => \&SetRational32s,
5001
5028
  rational32u => \&SetRational32u,
@@ -6632,7 +6659,17 @@ sub SetFileTime($$;$$$$)
6632
6659
  # open file by name if necessary
6633
6660
  unless (ref $file) {
6634
6661
  # (file will be automatically closed when *FH goes out of scope)
6635
- $self->Open(\*FH, $file, '+<') or $self->Warn('Error opening file for update'), return 0;
6662
+ unless ($self->Open(\*FH, $file, '+<')) {
6663
+ my $success;
6664
+ if (defined $atime or defined $mtime) {
6665
+ my ($a, $m, $c) = $self->GetFileTime($file);
6666
+ $atime = $a unless defined $atime;
6667
+ $mtime = $m unless defined $mtime;
6668
+ $success = eval { utime($atime, $mtime, $file) } if defined $atime and defined $mtime;
6669
+ }
6670
+ $self->Warn('Error opening file for update') unless $success;
6671
+ return $success;
6672
+ }
6636
6673
  $saveFile = $file;
6637
6674
  $file = \*FH;
6638
6675
  }
@@ -50,7 +50,7 @@ use Image::ExifTool::Exif;
50
50
  use Image::ExifTool::GPS;
51
51
  require Exporter;
52
52
 
53
- $VERSION = '3.38';
53
+ $VERSION = '3.46';
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) {
@@ -201,13 +208,13 @@ my %uri2ns = ( 'http://ns.exiftool.org/1.0/' => 'et' ); # (allow exiftool.org as
201
208
  ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
202
209
  ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 2, "N")',
203
210
  PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
204
- PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
211
+ PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")',
205
212
  );
206
213
  %longConv = (
207
214
  ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
208
215
  ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 2, "E")',
209
216
  PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
210
- PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
217
+ PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")',
211
218
  );
212
219
  %dateTimeInfo = (
213
220
  # NOTE: Do NOT put "Groups" here because Groups hash must not be common!
@@ -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
  },
@@ -2280,7 +2295,7 @@ my %sPantryItem = (
2280
2295
  Priority => 0,
2281
2296
  # prevent this from getting set from a LensID that has been converted
2282
2297
  ValueConvInv => q{
2283
- warn "Expected one or more integer values" if $val =~ /[^\d ]/;
2298
+ warn "Expected one or more integer values" if $val =~ /[^-\d ]/;
2284
2299
  return $val;
2285
2300
  },
2286
2301
  },
@@ -2334,6 +2349,8 @@ 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
+ AltTextAccessibility =>{ Groups => { 2 => 'Other' }, Writable => 'lang-alt' }, # added 2021-10-13
2353
+ ExtDescrAccessibility=>{ Groups => { 2 => 'Other' }, Writable => 'lang-alt' }, # added 2021-10-13
2337
2354
  );
2338
2355
 
2339
2356
  # Adobe Lightroom namespace properties (lr) (ref PH)
@@ -3251,8 +3268,14 @@ NoLoop:
3251
3268
  }
3252
3269
  }
3253
3270
  # generate a default tagInfo hash if necessary
3254
- $tagInfo or $tagInfo = { Name => $name, IsDefault => 1, Priority => 0 };
3255
-
3271
+ unless ($tagInfo) {
3272
+ # shorten tag name if necessary
3273
+ if ($$et{ShortenXmpTags}) {
3274
+ my $shorten = $$et{ShortenXmpTags};
3275
+ $name = &$shorten($name);
3276
+ }
3277
+ $tagInfo = { Name => $name, IsDefault => 1, Priority => 0 };
3278
+ }
3256
3279
  # add tag Namespace entry for tags in variable-namespace tables
3257
3280
  $$tagInfo{Namespace} = $xns if $xns;
3258
3281
  if ($$et{curURI}{$ns} and $$et{curURI}{$ns} =~ m{^http://ns.exiftool.(?:ca|org)/(.*?)/(.*?)/}) {
@@ -3771,6 +3794,7 @@ sub ParseXMPElement($$$;$$$$)
3771
3794
  # (unless we already extracted shorthand values from this element)
3772
3795
  if (length $val or not $shorthand) {
3773
3796
  my $lastProp = $$propList[-1];
3797
+ $lastProp = '' unless defined $lastProp;
3774
3798
  if (defined $nodeID) {
3775
3799
  SaveBlankInfo($blankInfo, $propList, $val);
3776
3800
  } elsif ($lastProp eq 'rdf:type' and $wasEmpty) {
@@ -3844,6 +3868,7 @@ sub ProcessXMP($$;$)
3844
3868
  my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG);
3845
3869
  my $rtnVal = 0;
3846
3870
  my $bom = 0;
3871
+ my $path = $et->MetadataPath();
3847
3872
 
3848
3873
  # namespaces and prefixes currently in effect while parsing the file,
3849
3874
  # and lookup to translate brain-dead-Microsoft-Photo-software prefixes
@@ -3861,11 +3886,7 @@ sub ProcessXMP($$;$)
3861
3886
  (($$dirInfo{DirName} || '') eq 'XMP' or $$et{FILE_TYPE} eq 'XMP'))
3862
3887
  {
3863
3888
  $$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
- }
3889
+ my $nonStd = ($stdPath{$$et{FILE_TYPE}} and $path ne $stdPath{$$et{FILE_TYPE}});
3869
3890
  if ($nonStd and $Image::ExifTool::MWG::strict) {
3870
3891
  $et->Warn("Ignored non-standard XMP at $path");
3871
3892
  return 1;
@@ -3944,7 +3965,7 @@ sub ProcessXMP($$;$)
3944
3965
  } elsif ($1 eq 'REDXIF') {
3945
3966
  $type = 'RMD';
3946
3967
  $mime = 'application/xml';
3947
- } else {
3968
+ } elsif ($1 ne 'fcpxml') { # Final Cut Pro XML
3948
3969
  return 0;
3949
3970
  }
3950
3971
  } elsif ($buf2 =~ /<svg[\s>]/) {
@@ -4063,12 +4084,14 @@ sub ProcessXMP($$;$)
4063
4084
 
4064
4085
  # extract XMP/XML as a block if specified
4065
4086
  my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : 'XMP';
4087
+ my $blockExtract = $et->Options('BlockExtract');
4066
4088
  if (($$et{REQ_TAG_LOOKUP}{lc $blockName} or ($$et{TAGS_FROM_FILE} and
4067
- not $$et{EXCL_TAG_LOOKUP}{lc $blockName})) and
4089
+ not $$et{EXCL_TAG_LOOKUP}{lc $blockName}) or $blockExtract) and
4068
4090
  (($$et{FileType} eq 'XMP' and $blockName eq 'XMP') or
4069
4091
  ($$dirInfo{DirName} and $$dirInfo{DirName} eq $blockName)))
4070
4092
  {
4071
4093
  $et->FoundTag($$dirInfo{BlockInfo} || 'XMP', substr($$dataPt, $dirStart, $dirLen));
4094
+ return 1 if $blockExtract and $blockExtract > 1;
4072
4095
  }
4073
4096
 
4074
4097
  $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
@@ -4123,6 +4146,13 @@ sub ProcessXMP($$;$)
4123
4146
  }
4124
4147
  defined $fmt or $et->Warn('XMP character encoding error');
4125
4148
  }
4149
+ # warn if standard XMP is missing xpacket wrapper
4150
+ if ($$et{XMP_NO_XPACKET} and $$et{OPTIONS}{Validate} and
4151
+ $stdPath{$$et{FILE_TYPE}} and $path eq $stdPath{$$et{FILE_TYPE}} and
4152
+ not $$dirInfo{IsExtended} and not $$et{DOC_NUM})
4153
+ {
4154
+ $et->Warn('XMP is missing xpacket wrapper', 1);
4155
+ }
4126
4156
  if ($fmt) {
4127
4157
  # trim if necessary to avoid converting non-UTF data
4128
4158
  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,7 @@ 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
+ EventID => { List => 'Bag' }, # added 2021-10-13
799
801
  );
800
802
 
801
803
  #------------------------------------------------------------------------------