exiftool_vendored 12.16.0 → 12.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +137 -1
  3. data/bin/MANIFEST +12 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +44 -43
  7. data/bin/config_files/acdsee.config +193 -6
  8. data/bin/config_files/cuepointlist.config +70 -0
  9. data/bin/config_files/example.config +1 -8
  10. data/bin/exiftool +139 -98
  11. data/bin/fmt_files/gpx.fmt +1 -1
  12. data/bin/fmt_files/gpx_wpt.fmt +1 -1
  13. data/bin/fmt_files/kml.fmt +1 -1
  14. data/bin/fmt_files/kml_track.fmt +1 -1
  15. data/bin/lib/Image/ExifTool.pm +158 -49
  16. data/bin/lib/Image/ExifTool.pod +94 -75
  17. data/bin/lib/Image/ExifTool/Apple.pm +3 -2
  18. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +25 -14
  19. data/bin/lib/Image/ExifTool/Canon.pm +28 -3
  20. data/bin/lib/Image/ExifTool/CanonCustom.pm +19 -1
  21. data/bin/lib/Image/ExifTool/DJI.pm +6 -6
  22. data/bin/lib/Image/ExifTool/DjVu.pm +6 -5
  23. data/bin/lib/Image/ExifTool/Exif.pm +50 -22
  24. data/bin/lib/Image/ExifTool/FITS.pm +13 -2
  25. data/bin/lib/Image/ExifTool/FujiFilm.pm +19 -8
  26. data/bin/lib/Image/ExifTool/GPS.pm +24 -13
  27. data/bin/lib/Image/ExifTool/H264.pm +20 -5
  28. data/bin/lib/Image/ExifTool/ICC_Profile.pm +2 -2
  29. data/bin/lib/Image/ExifTool/JPEG.pm +6 -2
  30. data/bin/lib/Image/ExifTool/JSON.pm +24 -3
  31. data/bin/lib/Image/ExifTool/Jpeg2000.pm +361 -16
  32. data/bin/lib/Image/ExifTool/M2TS.pm +40 -4
  33. data/bin/lib/Image/ExifTool/MIE.pm +2 -2
  34. data/bin/lib/Image/ExifTool/MRC.pm +341 -0
  35. data/bin/lib/Image/ExifTool/MWG.pm +3 -3
  36. data/bin/lib/Image/ExifTool/MXF.pm +1 -1
  37. data/bin/lib/Image/ExifTool/MacOS.pm +1 -1
  38. data/bin/lib/Image/ExifTool/Microsoft.pm +298 -82
  39. data/bin/lib/Image/ExifTool/Nikon.pm +5 -5
  40. data/bin/lib/Image/ExifTool/NikonSettings.pm +25 -16
  41. data/bin/lib/Image/ExifTool/Olympus.pm +2 -2
  42. data/bin/lib/Image/ExifTool/PNG.pm +2 -2
  43. data/bin/lib/Image/ExifTool/Panasonic.pm +14 -1
  44. data/bin/lib/Image/ExifTool/PhaseOne.pm +4 -3
  45. data/bin/lib/Image/ExifTool/QuickTime.pm +148 -68
  46. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +94 -34
  47. data/bin/lib/Image/ExifTool/README +5 -4
  48. data/bin/lib/Image/ExifTool/RIFF.pm +84 -12
  49. data/bin/lib/Image/ExifTool/Samsung.pm +2 -1
  50. data/bin/lib/Image/ExifTool/Shortcuts.pm +9 -0
  51. data/bin/lib/Image/ExifTool/Sony.pm +157 -49
  52. data/bin/lib/Image/ExifTool/TagInfoXML.pm +1 -0
  53. data/bin/lib/Image/ExifTool/TagLookup.pm +4079 -3987
  54. data/bin/lib/Image/ExifTool/TagNames.pod +642 -273
  55. data/bin/lib/Image/ExifTool/WriteExif.pl +1 -1
  56. data/bin/lib/Image/ExifTool/WritePostScript.pl +1 -0
  57. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +44 -17
  58. data/bin/lib/Image/ExifTool/WriteXMP.pl +15 -8
  59. data/bin/lib/Image/ExifTool/Writer.pl +50 -14
  60. data/bin/lib/Image/ExifTool/XMP.pm +50 -11
  61. data/bin/perl-Image-ExifTool.spec +42 -42
  62. data/lib/exiftool_vendored/version.rb +1 -1
  63. metadata +52 -12
@@ -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
  }
@@ -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
@@ -294,7 +296,7 @@ sub GetLangInfo($$)
294
296
  sub CheckQTValue($$$)
295
297
  {
296
298
  my ($et, $tagInfo, $valPtr) = @_;
297
- my $format = $$tagInfo{Format} || $$tagInfo{Table}{FORMAT};
299
+ my $format = $$tagInfo{Format} || $$tagInfo{Writable} || $$tagInfo{Table}{FORMAT};
298
300
  return undef unless $format;
299
301
  return Image::ExifTool::CheckValue($valPtr, $format, $$tagInfo{Count});
300
302
  }
@@ -307,8 +309,8 @@ sub FormatQTValue($$;$$)
307
309
  {
308
310
  my ($et, $valPt, $format, $writable) = @_;
309
311
  my $flags;
310
- if ($format and $format ne 'string') {
311
- $$valPt = WriteValue($$valPt, $format);
312
+ if ($format and $format ne 'string' or not $format and $writable and $writable ne 'string') {
313
+ $$valPt = WriteValue($$valPt, $format || $writable);
312
314
  if ($writable and $qtFormat{$writable}) {
313
315
  $flags = $qtFormat{$writable};
314
316
  } else {
@@ -1230,10 +1232,14 @@ sub WriteQuickTime($$$)
1230
1232
  } elsif ($format) {
1231
1233
  $val = ReadValue(\$buff, 0, $format, undef, $size);
1232
1234
  } 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);
1235
+ my $hdr;
1236
+ if ($$tagInfo{IText} and $$tagInfo{IText} >= 6) {
1237
+ my $iText = $$tagInfo{IText};
1238
+ my $pos = $iText - 2;
1239
+ $lang = unpack("x${pos}n", $buff);
1240
+ $hdr = substr($buff,4,$iText-6);
1241
+ $len = $size - $iText;
1242
+ $val = substr($buff, $iText, $len);
1237
1243
  } else {
1238
1244
  ($len, $lang) = unpack('nn', $buff);
1239
1245
  $len -= 4 if 4 + $len > $size; # (see QuickTime.pm for explanation)
@@ -1241,14 +1247,18 @@ sub WriteQuickTime($$$)
1241
1247
  $val = substr($buff, 4, $len);
1242
1248
  }
1243
1249
  $lang or $lang = $undLang; # treat both 0 and 'und' as 'und'
1250
+ my $enc;
1244
1251
  if ($lang < 0x400 and $val !~ /^\xfe\xff/) {
1245
1252
  $charsetQuickTime = $et->Options('CharsetQuickTime');
1246
- $val = $et->Decode($val, $charsetQuickTime);
1253
+ $enc = $charsetQuickTime;
1247
1254
  } else {
1248
- my $enc = $val=~s/^\xfe\xff// ? 'UTF16' : 'UTF8';
1255
+ $enc = $val=~s/^\xfe\xff// ? 'UTF16' : 'UTF8';
1256
+ }
1257
+ unless ($$tagInfo{NoDecode}) {
1249
1258
  $val = $et->Decode($val, $enc);
1259
+ $val =~ s/\0+$//; # remove trailing nulls if they exist
1250
1260
  }
1251
- $val =~ s/\0+$//; # remove trailing nulls if they exist
1261
+ $val = $hdr . $val if defined $hdr;
1252
1262
  my $langCode = UnpackLang($lang, 1);
1253
1263
  $langInfo = GetLangInfo($tagInfo, $langCode);
1254
1264
  $nvHash = $et->GetNewValueHash($langInfo);
@@ -1265,6 +1275,9 @@ sub WriteQuickTime($$$)
1265
1275
  }
1266
1276
  } else {
1267
1277
  $val = $buff;
1278
+ if ($tag =~ /^\xa9/ or $$tagInfo{IText}) {
1279
+ $et->Warn("Corrupted $$tagInfo{Name} value");
1280
+ }
1268
1281
  }
1269
1282
  if ($nvHash and defined $val) {
1270
1283
  if ($et->IsOverwriting($nvHash, $val)) {
@@ -1277,12 +1290,23 @@ sub WriteQuickTime($$$)
1277
1290
  $et->VerboseValue("+ $grp:$$langInfo{Name}", $newData);
1278
1291
  # add back necessary header and encode as necessary
1279
1292
  if (defined $lang) {
1280
- $newData = $et->Encode($newData, $lang < 0x400 ? $charsetQuickTime : 'UTF8');
1293
+ my $iText = $$tagInfo{IText} || 0;
1294
+ my $hdr;
1295
+ if ($iText > 6) {
1296
+ $newData .= ' 'x($iText-6) if length($newData) < $iText-6;
1297
+ $hdr = substr($newData, 0, $iText-6);
1298
+ $newData = substr($newData, $iText-6);
1299
+ }
1300
+ unless ($$tagInfo{NoDecode}) {
1301
+ $newData = $et->Encode($newData, $lang < 0x400 ? $charsetQuickTime : 'UTF8');
1302
+ }
1281
1303
  my $wLang = $lang eq $undLang ? 0 : $lang;
1282
- if ($$tagInfo{IText} and $$tagInfo{IText} == 6) {
1304
+ if ($iText < 6) {
1305
+ $newData = pack('nn', length($newData), $wLang) . $newData;
1306
+ } elsif ($iText == 6) {
1283
1307
  $newData = pack('Nn', 0, $wLang) . $newData . "\0";
1284
1308
  } else {
1285
- $newData = pack('nn', length($newData), $wLang) . $newData;
1309
+ $newData = "\0\0\0\0" . $hdr . pack('n', $wLang) . $newData . "\0";
1286
1310
  }
1287
1311
  } elsif (not $format or $format =~ /^string/ and
1288
1312
  not $$tagInfo{Binary} and not $$tagInfo{ValueConv})
@@ -1441,9 +1465,12 @@ sub WriteQuickTime($$$)
1441
1465
  my $grp = $et->GetGroup($tagInfo,1);
1442
1466
  $et->Warn("Can't use country code for $grp:$$tagInfo{Name}");
1443
1467
  next;
1444
- } elsif ($$tagInfo{IText} and $$tagInfo{IText} == 6) {
1468
+ } elsif ($$tagInfo{IText} and $$tagInfo{IText} >= 6) {
1445
1469
  # add 6-byte langText header and trailing null
1446
- $newVal = pack('Nn',0,$lang) . $newVal . "\0";
1470
+ # (with extra junk before language code if IText > 6)
1471
+ my $n = $$tagInfo{IText} - 6;
1472
+ $newVal .= ' ' x $n if length($newVal) < $n;
1473
+ $newVal = "\0\0\0\0" . substr($newVal,0,$n) . pack('n',0,$lang) . substr($newVal,$n) . "\0";
1447
1474
  } else {
1448
1475
  # add IText header
1449
1476
  $newVal = pack('nn',length($newVal),$lang) . $newVal;
@@ -12,7 +12,7 @@ use vars qw(%specialStruct %dateTimeInfo %stdXlatNS);
12
12
 
13
13
  use Image::ExifTool qw(:DataAccess :Utils);
14
14
 
15
- sub CheckXMP($$$);
15
+ sub CheckXMP($$$;$);
16
16
  sub CaptureXMP($$$;$);
17
17
  sub SetPropertyPath($$;$$$$);
18
18
 
@@ -165,12 +165,12 @@ sub FormatXMPDate($)
165
165
 
166
166
  #------------------------------------------------------------------------------
167
167
  # Check XMP values for validity and format accordingly
168
- # Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref
168
+ # Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref, 3) conversion type
169
169
  # Returns: error string or undef (and may change value) on success
170
170
  # Note: copies structured information to avoid conflicts with calling code
171
- sub CheckXMP($$$)
171
+ sub CheckXMP($$$;$)
172
172
  {
173
- my ($et, $tagInfo, $valPtr) = @_;
173
+ my ($et, $tagInfo, $valPtr, $convType) = @_;
174
174
 
175
175
  if ($$tagInfo{Struct}) {
176
176
  require 'Image/ExifTool/XMPStruct.pl';
@@ -250,9 +250,12 @@ sub CheckXMP($$$)
250
250
  return "Invalid date/time (use YYYY:mm:dd HH:MM:SS[.ss][+/-HH:MM|Z])" unless $newDate;
251
251
  $$valPtr = $newDate;
252
252
  } elsif ($format eq 'boolean') {
253
+ # (allow lower-case 'true' and 'false' if not setting PrintConv value)
253
254
  if (not $$valPtr or $$valPtr =~ /false/i or $$valPtr =~ /^no$/i) {
254
- $$valPtr = 'False';
255
- } else {
255
+ if (not $$valPtr or $$valPtr ne 'false' or not $convType or $convType eq 'PrintConv') {
256
+ $$valPtr = 'False';
257
+ }
258
+ } elsif ($$valPtr ne 'true' or not $convType or $convType eq 'PrintConv') {
256
259
  $$valPtr = 'True';
257
260
  }
258
261
  } elsif ($format eq '1') {
@@ -503,7 +506,7 @@ sub ConformPathToNamespace($$)
503
506
  my $prop;
504
507
  foreach $prop (@propList) {
505
508
  my ($ns, $tag) = $prop =~ /(.+?):(.*)/;
506
- next if $$nsUsed{$ns};
509
+ next if not defined $ns or $$nsUsed{$ns};
507
510
  my $uri = $nsURI{$ns};
508
511
  unless ($uri) {
509
512
  warn "No URI for namespace prefix $ns!\n";
@@ -1414,7 +1417,11 @@ sub WriteXMP($$;$)
1414
1417
  my $uri = $nsUsed{$1};
1415
1418
  unless ($uri) {
1416
1419
  $uri = $nsURI{$1}; # we must have added a namespace
1417
- $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
+ }
1418
1425
  }
1419
1426
  $nsNew{$1} = $uri;
1420
1427
  # need a new description if any new namespaces
@@ -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(
@@ -669,7 +670,7 @@ TAG: foreach $tagInfo (@matchingTags) {
669
670
  next TAG unless $lcWant eq lc $grp[1];
670
671
  }
671
672
  }
672
- $writeGroup or $writeGroup = ($$tagInfo{WriteGroup} || $grp[0]);
673
+ $writeGroup or $writeGroup = ($$tagInfo{WriteGroup} || $$tagInfo{Table}{WRITE_GROUP} || $grp[0]);
673
674
  $priority = $hiPri; # highest priority since group was specified
674
675
  }
675
676
  ++$foundMatch;
@@ -2639,12 +2640,14 @@ GWTInfo: foreach $tagInfo (@infoArray) {
2639
2640
 
2640
2641
  #------------------------------------------------------------------------------
2641
2642
  # Get list of all group names
2642
- # Inputs: 0) Group family number
2643
+ # Inputs: 0) [optional] ExifTool ref, 1) Group family number
2643
2644
  # Returns: List of group names (sorted alphabetically)
2644
- sub GetAllGroups($)
2645
+ sub GetAllGroups($;$)
2645
2646
  {
2646
2647
  local $_;
2647
2648
  my $family = shift || 0;
2649
+ my $self;
2650
+ ref $family and $self = $family, $family = shift || 0;
2648
2651
 
2649
2652
  $family == 3 and return('Doc#', 'Main');
2650
2653
  $family == 4 and return('Copy#');
@@ -2663,9 +2666,23 @@ sub GetAllGroups($)
2663
2666
  $allGroups{$grp} = 1 if ($grps = $$table{GROUPS}) and ($grp = $$grps{$family});
2664
2667
  foreach $tag (TagTableKeys($table)) {
2665
2668
  my @infoArray = GetTagInfoList($table, $tag);
2666
- foreach $tagInfo (@infoArray) {
2667
- next unless ($grps = $$tagInfo{Groups}) and ($grp = $$grps{$family});
2668
- $allGroups{$grp} = 1;
2669
+ if ($family == 7) {
2670
+ foreach $tagInfo (@infoArray) {
2671
+ my $id = $$tagInfo{TagID};
2672
+ if (not defined $id) {
2673
+ $id = ''; # (just to be safe)
2674
+ } elsif ($id =~ /^\d+$/) {
2675
+ $id = sprintf('0x%x', $id) if $self and $$self{OPTIONS}{HexTagIDs};
2676
+ } else {
2677
+ $id =~ s/([^-_A-Za-z0-9])/sprintf('%.2x',ord $1)/ge;
2678
+ }
2679
+ $allGroups{'ID-' . $id} = 1;
2680
+ }
2681
+ } else {
2682
+ foreach $tagInfo (@infoArray) {
2683
+ next unless ($grps = $$tagInfo{Groups}) and ($grp = $$grps{$family});
2684
+ $allGroups{$grp} = 1;
2685
+ }
2669
2686
  }
2670
2687
  }
2671
2688
  }
@@ -2766,6 +2783,8 @@ sub ConvInv($$$$$;$$)
2766
2783
  my ($self, $val, $tagInfo, $tag, $wgrp1, $convType, $wantGroup) = @_;
2767
2784
  my ($err, $type);
2768
2785
 
2786
+ $convType or $convType = $$self{ConvType} || 'PrintConv';
2787
+
2769
2788
  Conv: for (;;) {
2770
2789
  if (not defined $type) {
2771
2790
  # split value into list if necessary
@@ -2779,7 +2798,7 @@ Conv: for (;;) {
2779
2798
  $val = @splitVal > 1 ? \@splitVal : @splitVal ? $splitVal[0] : '';
2780
2799
  }
2781
2800
  }
2782
- $type = $convType || $$self{ConvType} || 'PrintConv';
2801
+ $type = $convType;
2783
2802
  } elsif ($type eq 'PrintConv') {
2784
2803
  $type = 'ValueConv';
2785
2804
  } else {
@@ -2802,11 +2821,11 @@ Conv: for (;;) {
2802
2821
  if (ref $val eq 'ARRAY') {
2803
2822
  # loop through array values
2804
2823
  foreach $v (@$val) {
2805
- $err2 = &$checkProc($self, $tagInfo, \$v);
2824
+ $err2 = &$checkProc($self, $tagInfo, \$v, $convType);
2806
2825
  last if $err2;
2807
2826
  }
2808
2827
  } else {
2809
- $err2 = &$checkProc($self, $tagInfo, \$val);
2828
+ $err2 = &$checkProc($self, $tagInfo, \$val, $convType);
2810
2829
  }
2811
2830
  }
2812
2831
  }
@@ -4933,6 +4952,12 @@ sub Set64u(@)
4933
4952
  $_[1] and substr(${$_[1]}, $_[2], length($val)) = $val;
4934
4953
  return $val;
4935
4954
  }
4955
+ sub Set64s(@)
4956
+ {
4957
+ my $val = shift;
4958
+ $val < 0 and $val += 4294967296 * 4294967296; # (temporary hack won't really work due to round-off errors)
4959
+ return Set64u($val, @_);
4960
+ }
4936
4961
  sub SetRational64u(@) {
4937
4962
  my ($numer,$denom) = Rationalize($_[0],0xffffffff);
4938
4963
  my $val = Set32u($numer) . Set32u($denom);
@@ -4994,6 +5019,7 @@ my %writeValueProc = (
4994
5019
  int16uRev => \&Set16uRev,
4995
5020
  int32s => \&Set32s,
4996
5021
  int32u => \&Set32u,
5022
+ int64s => \&Set64s,
4997
5023
  int64u => \&Set64u,
4998
5024
  rational32s => \&SetRational32s,
4999
5025
  rational32u => \&SetRational32u,
@@ -6630,7 +6656,17 @@ sub SetFileTime($$;$$$$)
6630
6656
  # open file by name if necessary
6631
6657
  unless (ref $file) {
6632
6658
  # (file will be automatically closed when *FH goes out of scope)
6633
- $self->Open(\*FH, $file, '+<') or $self->Warn('Error opening file for update'), return 0;
6659
+ unless ($self->Open(\*FH, $file, '+<')) {
6660
+ my $success;
6661
+ if (defined $atime or defined $mtime) {
6662
+ my ($a, $m, $c) = $self->GetFileTime($file);
6663
+ $atime = $a unless defined $atime;
6664
+ $mtime = $m unless defined $mtime;
6665
+ $success = eval { utime($atime, $mtime, $file) } if defined $atime and defined $mtime;
6666
+ }
6667
+ $self->Warn('Error opening file for update') unless $success;
6668
+ return $success;
6669
+ }
6634
6670
  $saveFile = $file;
6635
6671
  $file = \*FH;
6636
6672
  }
@@ -50,13 +50,13 @@ use Image::ExifTool::Exif;
50
50
  use Image::ExifTool::GPS;
51
51
  require Exporter;
52
52
 
53
- $VERSION = '3.37';
53
+ $VERSION = '3.41';
54
54
  @ISA = qw(Exporter);
55
55
  @EXPORT_OK = qw(EscapeXML UnescapeXML);
56
56
 
57
57
  sub ProcessXMP($$;$);
58
58
  sub WriteXMP($$;$);
59
- sub CheckXMP($$$);
59
+ sub CheckXMP($$$;$);
60
60
  sub ParseXMPElement($$$;$$$$);
61
61
  sub DecodeBase64($);
62
62
  sub EncodeBase64($;$);
@@ -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
@@ -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!
@@ -467,6 +474,19 @@ my %sCorrectionMask = (
467
474
  CenterValue => { Writable => 'real', List => 0 },
468
475
  PerimeterValue=>{ Writable => 'real', List => 0 },
469
476
  );
477
+ my %sCorrectionRangeMask = (
478
+ STRUCT_NAME => 'CorrectionRangeMask',
479
+ NAMESPACE => 'crs',
480
+ Version => { },
481
+ Type => { },
482
+ ColorAmount => { Writable => 'real' },
483
+ LumMin => { Writable => 'real' },
484
+ LumMax => { Writable => 'real' },
485
+ LumFeather => { Writable => 'real' },
486
+ DepthMin => { Writable => 'real' },
487
+ DepthMax => { Writable => 'real' },
488
+ DepthFeather=> { Writable => 'real' },
489
+ );
470
490
  my %sCorrection = (
471
491
  STRUCT_NAME => 'Correction',
472
492
  NAMESPACE => 'crs',
@@ -491,6 +511,15 @@ my %sCorrection = (
491
511
  LocalDefringe => { FlatName => 'Defringe', Writable => 'real', List => 0 },
492
512
  LocalTemperature => { FlatName => 'Temperature',Writable => 'real', List => 0 },
493
513
  LocalTint => { FlatName => 'Tint', Writable => 'real', List => 0 },
514
+ LocalHue => { FlatName => 'Hue', Writable => 'real', List => 0 },
515
+ LocalWhites2012 => { FlatName => 'Whites2012', Writable => 'real', List => 0 },
516
+ LocalBlacks2012 => { FlatName => 'Blacks2012', Writable => 'real', List => 0 },
517
+ LocalDehaze => { FlatName => 'Dehaze', Writable => 'real', List => 0 },
518
+ LocalTexture => { FlatName => 'Texture', Writable => 'real', List => 0 },
519
+ CorrectionRangeMask => {
520
+ FlatName => 'RangeMask',
521
+ Struct => \%sCorrectionRangeMask,
522
+ },
494
523
  CorrectionMasks => {
495
524
  FlatName => 'Mask',
496
525
  Struct => \%sCorrectionMask,
@@ -2258,7 +2287,7 @@ my %sPantryItem = (
2258
2287
  Priority => 0,
2259
2288
  # prevent this from getting set from a LensID that has been converted
2260
2289
  ValueConvInv => q{
2261
- warn "Expected one or more integer values" if $val =~ /[^\d ]/;
2290
+ warn "Expected one or more integer values" if $val =~ /[^-\d ]/;
2262
2291
  return $val;
2263
2292
  },
2264
2293
  },
@@ -2311,6 +2340,7 @@ my %sPantryItem = (
2311
2340
  Location => { Groups => { 2 => 'Location' } },
2312
2341
  Scene => { Groups => { 2 => 'Other' }, List => 'Bag' },
2313
2342
  SubjectCode => { Groups => { 2 => 'Other' }, List => 'Bag' },
2343
+ # Copyright - have seen this in a sample (Jan 2021), but I think it is non-standard
2314
2344
  );
2315
2345
 
2316
2346
  # Adobe Lightroom namespace properties (lr) (ref PH)
@@ -3306,6 +3336,9 @@ NoLoop:
3306
3336
  } else {
3307
3337
  $val = ConvertXMPDate($val, $new) if $new or $fmt eq 'date';
3308
3338
  }
3339
+ if ($$et{XmpValidate} and $fmt and $fmt eq 'boolean') {
3340
+ $et->WarnOnce("Boolean value for XMP-$ns:$$tagInfo{Name} should be capitalized",1);
3341
+ }
3309
3342
  # protect against large binary data in unknown tags
3310
3343
  $$tagInfo{Binary} = 1 if $new and length($val) > 65536;
3311
3344
  }
@@ -3818,6 +3851,7 @@ sub ProcessXMP($$;$)
3818
3851
  my ($buff, $fmt, $hasXMP, $isXML, $isRDF, $isSVG);
3819
3852
  my $rtnVal = 0;
3820
3853
  my $bom = 0;
3854
+ my $path = $et->MetadataPath();
3821
3855
 
3822
3856
  # namespaces and prefixes currently in effect while parsing the file,
3823
3857
  # and lookup to translate brain-dead-Microsoft-Photo-software prefixes
@@ -3835,11 +3869,7 @@ sub ProcessXMP($$;$)
3835
3869
  (($$dirInfo{DirName} || '') eq 'XMP' or $$et{FILE_TYPE} eq 'XMP'))
3836
3870
  {
3837
3871
  $$et{XmpValidate} = { } if $$et{OPTIONS}{Validate};
3838
- my $path = $et->MetadataPath();
3839
- my $nonStd;
3840
- if ($$et{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/ and $path !~ /^(JPEG-APP1-XMP|TIFF-IFD0-XMP|PSD-XMP)$/) {
3841
- $nonStd = 1;
3842
- }
3872
+ my $nonStd = ($stdPath{$$et{FILE_TYPE}} and $path ne $stdPath{$$et{FILE_TYPE}});
3843
3873
  if ($nonStd and $Image::ExifTool::MWG::strict) {
3844
3874
  $et->Warn("Ignored non-standard XMP at $path");
3845
3875
  return 1;
@@ -4037,12 +4067,14 @@ sub ProcessXMP($$;$)
4037
4067
 
4038
4068
  # extract XMP/XML as a block if specified
4039
4069
  my $blockName = $$dirInfo{BlockInfo} ? $$dirInfo{BlockInfo}{Name} : 'XMP';
4070
+ my $blockExtract = $et->Options('BlockExtract');
4040
4071
  if (($$et{REQ_TAG_LOOKUP}{lc $blockName} or ($$et{TAGS_FROM_FILE} and
4041
- not $$et{EXCL_TAG_LOOKUP}{lc $blockName})) and
4072
+ not $$et{EXCL_TAG_LOOKUP}{lc $blockName}) or $blockExtract) and
4042
4073
  (($$et{FileType} eq 'XMP' and $blockName eq 'XMP') or
4043
4074
  ($$dirInfo{DirName} and $$dirInfo{DirName} eq $blockName)))
4044
4075
  {
4045
4076
  $et->FoundTag($$dirInfo{BlockInfo} || 'XMP', substr($$dataPt, $dirStart, $dirLen));
4077
+ return 1 if $blockExtract and $blockExtract > 1;
4046
4078
  }
4047
4079
 
4048
4080
  $tagTablePtr or $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
@@ -4097,6 +4129,13 @@ sub ProcessXMP($$;$)
4097
4129
  }
4098
4130
  defined $fmt or $et->Warn('XMP character encoding error');
4099
4131
  }
4132
+ # warn if standard XMP is missing xpacket wrapper
4133
+ if ($$et{XMP_NO_XPACKET} and $$et{OPTIONS}{Validate} and
4134
+ $stdPath{$$et{FILE_TYPE}} and $path eq $stdPath{$$et{FILE_TYPE}} and
4135
+ not $$dirInfo{IsExtended} and not $$et{DOC_NUM})
4136
+ {
4137
+ $et->Warn('XMP is missing xpacket wrapper', 1);
4138
+ }
4100
4139
  if ($fmt) {
4101
4140
  # trim if necessary to avoid converting non-UTF data
4102
4141
  if ($dirStart or $dirEnd != length($$dataPt)) {