exiftool_vendored 12.17.1 → 12.32.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +225 -1
  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 +142 -87
  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 +30 -12
  19. data/bin/lib/Image/ExifTool/CBOR.pm +277 -0
  20. data/bin/lib/Image/ExifTool/Canon.pm +49 -18
  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 +28 -11
  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 +27 -4
  35. data/bin/lib/Image/ExifTool/Jpeg2000.pm +393 -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 +1 -1
  44. data/bin/lib/Image/ExifTool/Microsoft.pm +298 -82
  45. data/bin/lib/Image/ExifTool/Nikon.pm +19 -8
  46. data/bin/lib/Image/ExifTool/NikonSettings.pm +28 -11
  47. data/bin/lib/Image/ExifTool/Olympus.pm +6 -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 +247 -88
  56. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +283 -141
  57. data/bin/lib/Image/ExifTool/README +3 -0
  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 +237 -78
  62. data/bin/lib/Image/ExifTool/TagInfoXML.pm +1 -0
  63. data/bin/lib/Image/ExifTool/TagLookup.pm +4125 -4028
  64. data/bin/lib/Image/ExifTool/TagNames.pod +644 -286
  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 +39 -14
  75. data/bin/lib/Image/ExifTool/XMP2.pl +2 -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 +223 -72
  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.44';
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!
@@ -2010,6 +2017,11 @@ my %sPantryItem = (
2010
2017
  Groups => { 2 => 'Location' },
2011
2018
  Writable => 'integer',
2012
2019
  PrintConv => {
2020
+ OTHER => sub {
2021
+ my ($val, $inv) = @_;
2022
+ return undef unless $inv and $val =~ /^([-+0-9])/;
2023
+ return($1 eq '-' ? 1 : 0);
2024
+ },
2013
2025
  0 => 'Above Sea Level',
2014
2026
  1 => 'Below Sea Level',
2015
2027
  },
@@ -2280,7 +2292,7 @@ my %sPantryItem = (
2280
2292
  Priority => 0,
2281
2293
  # prevent this from getting set from a LensID that has been converted
2282
2294
  ValueConvInv => q{
2283
- warn "Expected one or more integer values" if $val =~ /[^\d ]/;
2295
+ warn "Expected one or more integer values" if $val =~ /[^-\d ]/;
2284
2296
  return $val;
2285
2297
  },
2286
2298
  },
@@ -3251,8 +3263,14 @@ NoLoop:
3251
3263
  }
3252
3264
  }
3253
3265
  # generate a default tagInfo hash if necessary
3254
- $tagInfo or $tagInfo = { Name => $name, IsDefault => 1, Priority => 0 };
3255
-
3266
+ unless ($tagInfo) {
3267
+ # shorten tag name if necessary
3268
+ if ($$et{ShortenXmpTags}) {
3269
+ my $shorten = $$et{ShortenXmpTags};
3270
+ $name = &$shorten($name);
3271
+ }
3272
+ $tagInfo = { Name => $name, IsDefault => 1, Priority => 0 };
3273
+ }
3256
3274
  # add tag Namespace entry for tags in variable-namespace tables
3257
3275
  $$tagInfo{Namespace} = $xns if $xns;
3258
3276
  if ($$et{curURI}{$ns} and $$et{curURI}{$ns} =~ m{^http://ns.exiftool.(?:ca|org)/(.*?)/(.*?)/}) {
@@ -3771,6 +3789,7 @@ sub ParseXMPElement($$$;$$$$)
3771
3789
  # (unless we already extracted shorthand values from this element)
3772
3790
  if (length $val or not $shorthand) {
3773
3791
  my $lastProp = $$propList[-1];
3792
+ $lastProp = '' unless defined $lastProp;
3774
3793
  if (defined $nodeID) {
3775
3794
  SaveBlankInfo($blankInfo, $propList, $val);
3776
3795
  } elsif ($lastProp eq 'rdf:type' and $wasEmpty) {
@@ -3844,6 +3863,7 @@ sub ProcessXMP($$;$)
3844
3863
  my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG);
3845
3864
  my $rtnVal = 0;
3846
3865
  my $bom = 0;
3866
+ my $path = $et->MetadataPath();
3847
3867
 
3848
3868
  # namespaces and prefixes currently in effect while parsing the file,
3849
3869
  # and lookup to translate brain-dead-Microsoft-Photo-software prefixes
@@ -3861,11 +3881,7 @@ sub ProcessXMP($$;$)
3861
3881
  (($$dirInfo{DirName} || '') eq 'XMP' or $$et{FILE_TYPE} eq 'XMP'))
3862
3882
  {
3863
3883
  $$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
- }
3884
+ my $nonStd = ($stdPath{$$et{FILE_TYPE}} and $path ne $stdPath{$$et{FILE_TYPE}});
3869
3885
  if ($nonStd and $Image::ExifTool::MWG::strict) {
3870
3886
  $et->Warn("Ignored non-standard XMP at $path");
3871
3887
  return 1;
@@ -4063,12 +4079,14 @@ sub ProcessXMP($$;$)
4063
4079
 
4064
4080
  # extract XMP/XML as a block if specified
4065
4081
  my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : 'XMP';
4082
+ my $blockExtract = $et->Options('BlockExtract');
4066
4083
  if (($$et{REQ_TAG_LOOKUP}{lc $blockName} or ($$et{TAGS_FROM_FILE} and
4067
- not $$et{EXCL_TAG_LOOKUP}{lc $blockName})) and
4084
+ not $$et{EXCL_TAG_LOOKUP}{lc $blockName}) or $blockExtract) and
4068
4085
  (($$et{FileType} eq 'XMP' and $blockName eq 'XMP') or
4069
4086
  ($$dirInfo{DirName} and $$dirInfo{DirName} eq $blockName)))
4070
4087
  {
4071
4088
  $et->FoundTag($$dirInfo{BlockInfo} || 'XMP', substr($$dataPt, $dirStart, $dirLen));
4089
+ return 1 if $blockExtract and $blockExtract > 1;
4072
4090
  }
4073
4091
 
4074
4092
  $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
@@ -4123,6 +4141,13 @@ sub ProcessXMP($$;$)
4123
4141
  }
4124
4142
  defined $fmt or $et->Warn('XMP character encoding error');
4125
4143
  }
4144
+ # warn if standard XMP is missing xpacket wrapper
4145
+ if ($$et{XMP_NO_XPACKET} and $$et{OPTIONS}{Validate} and
4146
+ $stdPath{$$et{FILE_TYPE}} and $path eq $stdPath{$$et{FILE_TYPE}} and
4147
+ not $$dirInfo{IsExtended} and not $$et{DOC_NUM})
4148
+ {
4149
+ $et->Warn('XMP is missing xpacket wrapper', 1);
4150
+ }
4126
4151
  if ($fmt) {
4127
4152
  # trim if necessary to avoid converting non-UTF data
4128
4153
  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,
@@ -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) . '}';