exiftool_vendored 12.98.0 → 13.02.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +73 -1
  3. data/bin/META.json +1 -1
  4. data/bin/META.yml +1 -1
  5. data/bin/README +2 -2
  6. data/bin/arg_files/exif2xmp.args +4 -0
  7. data/bin/arg_files/xmp2exif.args +4 -0
  8. data/bin/config_files/example.config +2 -1
  9. data/bin/exiftool +297 -66
  10. data/bin/lib/Image/ExifTool/APP12.pm +3 -2
  11. data/bin/lib/Image/ExifTool/CBOR.pm +4 -1
  12. data/bin/lib/Image/ExifTool/Canon.pm +36 -26
  13. data/bin/lib/Image/ExifTool/Exif.pm +8 -9
  14. data/bin/lib/Image/ExifTool/FlashPix.pm +5 -9
  15. data/bin/lib/Image/ExifTool/Geolocation.dat +0 -0
  16. data/bin/lib/Image/ExifTool/Geotag.pm +6 -5
  17. data/bin/lib/Image/ExifTool/GoPro.pm +2 -2
  18. data/bin/lib/Image/ExifTool/Import.pm +7 -3
  19. data/bin/lib/Image/ExifTool/JSON.pm +3 -4
  20. data/bin/lib/Image/ExifTool/Jpeg2000.pm +2 -2
  21. data/bin/lib/Image/ExifTool/LNK.pm +1 -1
  22. data/bin/lib/Image/ExifTool/Lytro.pm +2 -2
  23. data/bin/lib/Image/ExifTool/M2TS.pm +2 -2
  24. data/bin/lib/Image/ExifTool/MIE.pm +9 -3
  25. data/bin/lib/Image/ExifTool/MacOS.pm +2 -1
  26. data/bin/lib/Image/ExifTool/Nikon.pm +5 -2
  27. data/bin/lib/Image/ExifTool/PDF.pm +7 -3
  28. data/bin/lib/Image/ExifTool/PhaseOne.pm +2 -1
  29. data/bin/lib/Image/ExifTool/QuickTime.pm +11 -1
  30. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +88 -9
  31. data/bin/lib/Image/ExifTool/TagLookup.pm +14 -9
  32. data/bin/lib/Image/ExifTool/TagNames.pod +37 -20
  33. data/bin/lib/Image/ExifTool/Text.pm +3 -2
  34. data/bin/lib/Image/ExifTool/Validate.pm +2 -2
  35. data/bin/lib/Image/ExifTool/WriteXMP.pl +16 -4
  36. data/bin/lib/Image/ExifTool/Writer.pl +42 -61
  37. data/bin/lib/Image/ExifTool/XMP.pm +13 -3
  38. data/bin/lib/Image/ExifTool/XMPStruct.pl +16 -9
  39. data/bin/lib/Image/ExifTool.pm +190 -77
  40. data/bin/lib/Image/ExifTool.pod +73 -36
  41. data/bin/perl-Image-ExifTool.spec +1 -1
  42. data/lib/exiftool_vendored/version.rb +1 -1
  43. metadata +2 -2
data/bin/exiftool CHANGED
@@ -11,7 +11,7 @@ use strict;
11
11
  use warnings;
12
12
  require 5.004;
13
13
 
14
- my $version = '12.98';
14
+ my $version = '13.02';
15
15
 
16
16
  # add our 'lib' directory to the include list BEFORE 'use Image::ExifTool'
17
17
  my $exePath;
@@ -56,11 +56,14 @@ sub PrintCSV();
56
56
  sub AddGroups($$$$);
57
57
  sub ConvertBinary($);
58
58
  sub IsEqual($$);
59
+ sub Printable($);
60
+ sub LengthUTF8($);
59
61
  sub Infile($;$);
60
62
  sub AddSetTagsFile($;$);
61
63
  sub Warning($$);
62
64
  sub DoSetFromFile($$$);
63
65
  sub CleanFilename($);
66
+ sub HasWildcards($);
64
67
  sub SetWindowTitle($);
65
68
  sub ProcessFiles($;$);
66
69
  sub ScanDir($$;$);
@@ -150,6 +153,7 @@ my $csvAdd; # flag to add CSV information to existing lists
150
153
  my $csvDelim; # delimiter for CSV files
151
154
  my $csvSaveCount; # save counter for last CSV file loaded
152
155
  my $deleteOrig; # 0=restore original files, 1=delete originals, 2=delete w/o asking
156
+ my $diff; # file name for comparing differences
153
157
  my $disableOutput; # flag to disable normal output
154
158
  my $doSetFileName; # flag set if FileName may be written
155
159
  my $doUnzip; # flag to extract info from .gz and .bz2 files
@@ -256,11 +260,13 @@ my %optArgs = (
256
260
  '-csvdelim' => 1,
257
261
  '-d' => 1, '-dateformat' => 1,
258
262
  '-D' => 0, # necessary to avoid matching lower-case equivalent
263
+ '-diff' => 1,
259
264
  '-echo' => 1, '-echo#' => 1,
260
265
  '-efile' => 1, '-efile#' => 1, '-efile!' => 1, '-efile#!' => 1,
261
266
  '-ext' => 1, '--ext' => 1, '-ext+' => 1, '--ext+' => 1,
262
267
  '-extension' => 1, '--extension' => 1, '-extension+' => 1, '--extension+' => 1,
263
268
  '-fileorder' => 1, '-fileorder#' => 1,
269
+ '-file#' => 1,
264
270
  '-geotag' => 1,
265
271
  '-globaltimeshift' => 1,
266
272
  '-i' => 1, '-ignore' => 1,
@@ -332,7 +338,7 @@ sub Exit {
332
338
  exit shift;
333
339
  }
334
340
  # my warning and error routines (NEVER say "die"!)
335
- sub Warn {
341
+ sub Warn {
336
342
  if ($quiet < 2 or $_[0] =~ /^Error/) {
337
343
  my $oldWarn = $SIG{'__WARN__'};
338
344
  delete $SIG{'__WARN__'};
@@ -472,6 +478,7 @@ undef $comma;
472
478
  undef $csv;
473
479
  undef $csvAdd;
474
480
  undef $deleteOrig;
481
+ undef $diff;
475
482
  undef $disableOutput;
476
483
  undef $doSetFileName;
477
484
  undef $doUnzip;
@@ -850,7 +857,7 @@ for (;;) {
850
857
  } else {
851
858
  print "Available API Options:\n";
852
859
  my $availableOptions = Image::ExifTool::AvailableOptions();
853
- printf(" %-17s - %s\n", $$_[0], $$_[2]) foreach @$availableOptions;
860
+ $$_[3] or printf(" %-17s - %s\n", $$_[0], $$_[2]) foreach @$availableOptions;
854
861
  $helped = 1;
855
862
  }
856
863
  next;
@@ -943,6 +950,11 @@ for (;;) {
943
950
  next;
944
951
  }
945
952
  (/^D$/ or $a eq 'decimal') and $showTagID = 'D', next;
953
+ if (/^diff$/i) {
954
+ $diff = shift;
955
+ defined $diff or Error("Expecting file name for -$_ option\n"), $badCmd=1;
956
+ next;
957
+ }
946
958
  /^delete_original(!?)$/i and $deleteOrig = ($1 ? 2 : 1), next;
947
959
  /^list_dir$/i and $listDir = 1, next;
948
960
  (/^e$/ or $a eq '-composite') and $mt->Options(Composite => 0), next;
@@ -1028,7 +1040,7 @@ for (;;) {
1028
1040
  }
1029
1041
  $trkfile or Error("Expecting file name for -geotag option\n"), $badCmd=1, next;
1030
1042
  # allow wildcards in filename
1031
- if ($trkfile =~ /[*?]/) {
1043
+ if (HasWildcards($trkfile)) {
1032
1044
  # CORE::glob() splits on white space, so use File::Glob if possible
1033
1045
  my @trks;
1034
1046
  if ($^O eq 'MSWin32' and eval { require Win32::FindFile }) {
@@ -1414,7 +1426,7 @@ for (;;) {
1414
1426
  push @nextPass, $_;
1415
1427
  next;
1416
1428
  }
1417
- if ($doGlob and /[*?]/) {
1429
+ if ($doGlob and HasWildcards($_)) {
1418
1430
  if ($^O eq 'MSWin32' and eval { require Win32::FindFile }) {
1419
1431
  push @files, FindFileWindows($mt, $_);
1420
1432
  } else {
@@ -1501,13 +1513,17 @@ if ($overwriteOrig > 1 and $outOpt) {
1501
1513
  next;
1502
1514
  }
1503
1515
 
1504
- if ($tagOut and ($csv or %printFmt or $tabFormat or $xml or ($verbose and $html))) {
1505
- Warn "Sorry, -W may not be combined with -csv, -htmlDump, -j, -p, -t or -X\n";
1516
+ if (($tagOut or defined $diff) and ($csv or $json or %printFmt or $tabFormat or $xml or
1517
+ ($verbose and $html)))
1518
+ {
1519
+ my $opt = $tagOut ? '-W' : '-diff';
1520
+ Warn "Sorry, $opt may not be combined with -csv, -htmlDump, -j, -p, -t or -X\n";
1506
1521
  $rtnVal = 1;
1507
1522
  next;
1508
1523
  }
1509
1524
 
1510
1525
  if ($csv and $csv eq 'CSV' and not $isWriting) {
1526
+ undef $json; # (not compatible)
1511
1527
  if ($textOut) {
1512
1528
  Warn "Sorry, -w may not be combined with -csv\n";
1513
1529
  $rtnVal = 1;
@@ -1757,9 +1773,14 @@ if (@newValues) {
1757
1773
  $rtnVal = 1;
1758
1774
  next;
1759
1775
  }
1760
- if ($isWriting and @tags and not $outOpt) {
1761
- my ($tg, $s) = @tags > 1 ? ("$tags[0] ...", 's') : ($tags[0], '');
1762
- Warn "Ignored superfluous tag name$s or invalid option$s: -$tg\n";
1776
+ if ($isWriting) {
1777
+ if (defined $diff) {
1778
+ Error "Can't use -diff option when writing tags\n";
1779
+ next;
1780
+ } elsif (@tags and not $outOpt) {
1781
+ my ($tg, $s) = @tags > 1 ? ("$tags[0] ...", 's') : ($tags[0], '');
1782
+ Warn "Ignored superfluous tag name$s or invalid option$s: -$tg\n";
1783
+ }
1763
1784
  }
1764
1785
  # save current state of new values if setting values from target file
1765
1786
  # or if we may be translating to a different format
@@ -1783,7 +1804,7 @@ if ($binaryOutput) {
1783
1804
  }
1784
1805
 
1785
1806
  # sort by groups to look nicer depending on options
1786
- if (defined $showGroup and not (@tags and $allGroup) and ($sortOpt or not defined $sortOpt)) {
1807
+ if (defined $showGroup and not (@tags and ($allGroup or $csv)) and ($sortOpt or not defined $sortOpt)) {
1787
1808
  $mt->Options(Sort => "Group$showGroup");
1788
1809
  }
1789
1810
 
@@ -1804,8 +1825,7 @@ if ($outOpt) {
1804
1825
  $type = Image::ExifTool::GetFileExtension($outOpt);
1805
1826
  $type = uc($outOpt) unless defined $type;
1806
1827
  }
1807
- Warn "Can't write $type files\n";
1808
- $rtnVal = 1;
1828
+ Error "Can't write $type files\n";
1809
1829
  next;
1810
1830
  }
1811
1831
  $scanWritable = $type unless CanCreate($type);
@@ -1822,7 +1842,7 @@ $altEnc = $mt->Options('Charset');
1822
1842
  undef $altEnc if $altEnc eq 'UTF8';
1823
1843
 
1824
1844
  # set flag to fix description lengths if necessary
1825
- if (not $altEnc and $mt->Options('Lang') ne 'en' and eval { require Encode }) {
1845
+ if (not $altEnc and $mt->Options('Lang') ne 'en') {
1826
1846
  # (note that Unicode::GCString is part of the Unicode::LineBreak package)
1827
1847
  $fixLen = eval { require Unicode::GCString } ? 2 : 1;
1828
1848
  }
@@ -1903,10 +1923,7 @@ if (@dbKeys) {
1903
1923
  # process all specified files
1904
1924
  ProcessFiles($mt);
1905
1925
 
1906
- if ($filtered and not $validFile) {
1907
- Warn "No file with specified extension\n";
1908
- $rtnVal = 1;
1909
- }
1926
+ Error "No file with specified extension\n" if $filtered and not $validFile;
1910
1927
 
1911
1928
  # print CSV information if necessary
1912
1929
  PrintCSV() if $csv and not $isWriting;
@@ -2014,7 +2031,7 @@ Exit $rtnValApp; # all done
2014
2031
  sub GetImageInfo($$)
2015
2032
  {
2016
2033
  my ($et, $orig) = @_;
2017
- my (@foundTags, $info, $file, $ind, $g8);
2034
+ my (@foundTags, @found2, $info, $info2, $et2, $file, $file2, $ind, $g8);
2018
2035
 
2019
2036
  # set window title for this file if necessary
2020
2037
  if (defined $windowTitle) {
@@ -2151,7 +2168,7 @@ sub GetImageInfo($$)
2151
2168
  }
2152
2169
  # can't make use of $info if verbose because we must reprocess
2153
2170
  # the file anyway to generate the verbose output
2154
- undef $info if $verbose or defined $fastCondition;
2171
+ undef $info if $verbose or defined $fastCondition or defined $diff;
2155
2172
  } elsif ($file =~ s/^(\@JSON:)(.*)/$1/) {
2156
2173
  # read JSON file from command line
2157
2174
  my $dat = $2;
@@ -2183,7 +2200,7 @@ sub GetImageInfo($$)
2183
2200
 
2184
2201
  my $lineCount = 0;
2185
2202
  my ($fp, $outfile, $append);
2186
- if ($textOut and ($verbose or $et->Options('PrintCSV')) and not $tagOut) {
2203
+ if ($textOut and ($verbose or $et->Options('PrintCSV')) and not ($tagOut or defined $diff)) {
2187
2204
  ($fp, $outfile, $append) = OpenOutputFile($orig);
2188
2205
  $fp or EFile($file), ++$countBad, return;
2189
2206
  # delete file if we exit prematurely (unless appending)
@@ -2228,7 +2245,7 @@ sub GetImageInfo($$)
2228
2245
  require Image::ExifTool::HTML;
2229
2246
  my $f = Image::ExifTool::HTML::EscapeHTML($file);
2230
2247
  print "<!-- $f -->\n";
2231
- } elsif (not ($json or $xml)) {
2248
+ } elsif (not ($json or $xml or defined $diff)) {
2232
2249
  $o = \*STDOUT if ($multiFile and not $quiet) or $progress;
2233
2250
  }
2234
2251
  }
@@ -2257,10 +2274,37 @@ sub GetImageInfo($$)
2257
2274
  } else {
2258
2275
  @foundTags = @tags;
2259
2276
  }
2277
+ if (defined $diff) {
2278
+ $file2 = FilenameSPrintf($diff, $orig);
2279
+ if ($file eq $file2) {
2280
+ Warn "Error: Diffing file with itself - $file2\n";
2281
+ EFile($file);
2282
+ ++$countBad;
2283
+ return;
2284
+ }
2285
+ if ($et->Exists($file2)) {
2286
+ $showGroup = 1 unless defined $showGroup;
2287
+ $allGroup = 1 unless defined $allGroup;
2288
+ $et->Options(Duplicates => 1, Sort => "Group$showGroup", Verbose => 0);
2289
+ $et2 = Image::ExifTool->new;
2290
+ $et2->Options(%{$$et{OPTIONS}});
2291
+ @found2 = @foundTags;
2292
+ $info2 = $et2->ImageInfo($file2, \@found2);
2293
+ } else {
2294
+ $info2 = { Error => "Diff file not found" };
2295
+ }
2296
+ if ($$info2{Error}) {
2297
+ Warn "Error: $$info2{Error} - $file2\n";
2298
+ EFile($file);
2299
+ ++$countBad;
2300
+ return;
2301
+ }
2302
+ }
2260
2303
  # extract the information
2261
2304
  $info = $et->ImageInfo(Infile($pipe), \@foundTags);
2262
2305
  $et->Options(Duplicates => $oldDups);
2263
2306
  }
2307
+
2264
2308
  # all done now if we already wrote output text file (eg. verbose option)
2265
2309
  if ($fp) {
2266
2310
  if (defined $outfile) {
@@ -2274,7 +2318,7 @@ sub GetImageInfo($$)
2274
2318
  }
2275
2319
  }
2276
2320
  if ($info->{Error}) {
2277
- Warn "Error: $info->{Error} - $file\n";
2321
+ Warn "Error: $$info{Error} - $file\n";
2278
2322
  EFile($file);
2279
2323
  ++$countBad;
2280
2324
  return;
@@ -2298,6 +2342,105 @@ sub GetImageInfo($$)
2298
2342
  $tmpText = $outfile unless $append;
2299
2343
  }
2300
2344
 
2345
+ # print differences if requested
2346
+ if (defined $diff) {
2347
+ my (%done2, $wasDiff, @diffs, @groupTags2);
2348
+ my $v = $verbose || 0;
2349
+ print $fp "======== diff < $file > $file2\n";
2350
+ my ($g2, $same) = (0, 0); # start with $g2 false, but not equal to '' to avoid infinite loop
2351
+ for (;;) {
2352
+ my $tag = shift @foundTags;
2353
+ my ($g, $tag2);
2354
+ if (defined $tag) {
2355
+ $g = $et->GetGroup($tag, $showGroup);
2356
+ } else {
2357
+ for (;;) {
2358
+ $tag2 = shift @found2;
2359
+ defined $tag2 or $g = '', last;
2360
+ $done2{$tag2} or $g = $et2->GetGroup($tag2, $showGroup), last;
2361
+ }
2362
+ }
2363
+ if ($g ne $g2) {
2364
+ my $t2;
2365
+ # add any outstanding tags from diff file not yet handled in previous group ($g2)
2366
+ foreach $t2 (@groupTags2) {
2367
+ next if $done2{$t2};
2368
+ my $val2 = $et2->GetValue($t2);
2369
+ next unless defined $val2;
2370
+ my $name = $outFormat < 1 ? $et2->GetDescription($t2) : GetTagName($t2);
2371
+ my $len = LengthUTF8($name);
2372
+ my $pad = $outFormat < 2 ? ' ' x ($len < 32 ? 32 - $len : 0) : '';
2373
+ if ($allGroup) {
2374
+ my $grp = "[$g2]";
2375
+ $grp .= ' ' x (15 - length($grp)) if length($grp) < 15 and $outFormat < 2;
2376
+ push @diffs, sprintf "> %s %s%s: %s\n", $grp, $name, $pad, Printable($val2);
2377
+ } else {
2378
+ push @diffs, sprintf "> %s%s: %s\n", $name, $pad, Printable($val2);
2379
+ }
2380
+ $done2{$t2} = 1;
2381
+ }
2382
+ my $str = '';
2383
+ $v and ($same or $v > 1) and $str = " ($same same tag" . ($same==1 ? '' : 's') . ')';
2384
+ if (not $allGroup) {
2385
+ print $fp "---- $g2 ----$str\n" if $g2 and ($str or @diffs);
2386
+ } elsif ($str and $g2) {
2387
+ printf $fp " %-13s%s\n", $g2, $str;
2388
+ }
2389
+ # print all differences for this group
2390
+ @diffs and print($fp @diffs), $wasDiff = 1, @diffs = ();
2391
+ last unless $g;
2392
+ ($g2, $same) = ($g, 0);
2393
+ # build list of all tags in the new group of the diff file
2394
+ @groupTags2 = ();
2395
+ foreach $t2 (@found2) {
2396
+ $done2{$t2} or $g ne $et2->GetGroup($t2, $showGroup) or push @groupTags2, $t2;
2397
+ }
2398
+ }
2399
+ next unless defined $tag;
2400
+ my $val = $et->GetValue($tag);
2401
+ next unless defined $val; # (just in case)
2402
+ my $name = GetTagName($tag);
2403
+ # get matching tag key from diff file
2404
+ my @tags2 = grep /^$name( |$)/, @groupTags2;
2405
+ $name = $et->GetDescription($tag) if $outFormat < 1;
2406
+ my ($val2, $t2);
2407
+ foreach $t2 (@tags2) {
2408
+ next if $done2{$t2};
2409
+ $tag2 = $t2;
2410
+ $val2 = $et2->GetValue($t2);
2411
+ last;
2412
+ }
2413
+ if (defined $val2 and IsEqual($val, $val2)) {
2414
+ ++$same;
2415
+ } else {
2416
+ my $len = LengthUTF8($name);
2417
+ my $pad = $outFormat < 2 ? ' ' x ($len < 32 ? 32 - $len : 0) : '';
2418
+ if ($allGroup) {
2419
+ my $grp = "[$g]";
2420
+ $grp .= ' ' x (15 - length($grp)) if length($grp) < 15 and $outFormat < 2;
2421
+ push @diffs, sprintf "< %s %s%s: %s\n", $grp, $name, $pad, Printable($val);
2422
+ if (defined $val2) {
2423
+ $grp = ' ' x length($grp), $name = ' ' x $len if $v < 3;
2424
+ push @diffs, sprintf "> %s %s%s: %s\n", $grp, $name, $pad, Printable($val2);
2425
+ }
2426
+ } else {
2427
+ push @diffs, sprintf "< %s%s: %s\n", $name, $pad, Printable($val);
2428
+ $name = ' ' x $len if $v < 3;
2429
+ push @diffs, sprintf "> %s%s %s\n", $name, $pad, Printable($val2) if defined $val2;
2430
+ }
2431
+ }
2432
+ $done2{$tag2} = 1 if defined $tag2;
2433
+ }
2434
+ print $fp "(no metadata differences)\n" unless $wasDiff;
2435
+ undef $tmpText;
2436
+ if (defined $outfile) {
2437
+ ++$created{$outfile};
2438
+ close($fp);
2439
+ undef $tmpText;
2440
+ }
2441
+ ++$count;
2442
+ return;
2443
+ }
2301
2444
  # restore state of comma flag for this file if appending
2302
2445
  $comma = $outComma{$outfile} if $append and ($textOverwrite & 0x02);
2303
2446
 
@@ -2480,7 +2623,13 @@ TAG: foreach $tag (@foundTags) {
2480
2623
  # (note that the tag key may look like "TAG #(1)" when the "#" feature is used)
2481
2624
  next if $noDups and $tag =~ /^(.*?) ?\(/ and defined $$info{$1} and
2482
2625
  $group eq $et->GetGroup($1, $showGroup);
2483
- $group = 'Unknown' if not $group and ($xml or $json or $csv);
2626
+ if (not $group and ($xml or $json or $csv)) {
2627
+ if ($showGroup !~ /\b4\b/) {
2628
+ $group = 'Unknown';
2629
+ } elsif ($json and not $allGroup) {
2630
+ $group = 'Copy0';
2631
+ }
2632
+ }
2484
2633
  if ($fp and not ($allGroup or $csv)) {
2485
2634
  if ($lastGroup ne $group) {
2486
2635
  if ($html) {
@@ -2702,6 +2851,9 @@ TAG: foreach $tag (@foundTags) {
2702
2851
  my $num = $et->GetValue($tag, 'ValueConv');
2703
2852
  $$val{num} = $num if defined $num and not IsEqual($num, $$val{val});
2704
2853
  }
2854
+ my $ex = $$et{TAG_EXTRA}{$tag};
2855
+ $$val{'hex'} = join ' ', unpack '(H2)*', $$ex{BinVal} if defined $$ex{BinVal};
2856
+ $$val{'fmt'} = $$ex{G6} if defined $$ex{G6};
2705
2857
  }
2706
2858
  }
2707
2859
  FormatJSON($fp, $val, $ind, $quote);
@@ -2766,21 +2918,7 @@ TAG: foreach $tag (@foundTags) {
2766
2918
  # pad description to a constant length
2767
2919
  # (get actual character length when using alternate languages
2768
2920
  # because these descriptions may contain UTF8-encoded characters)
2769
- my $padLen = $wid;
2770
- if (not $fixLen) {
2771
- $padLen -= length $desc;
2772
- } elsif ($fixLen == 1) {
2773
- $padLen -= length Encode::decode_utf8($desc);
2774
- } else {
2775
- my $gcstr = eval { Unicode::GCString->new(Encode::decode_utf8($desc)) };
2776
- if ($gcstr) {
2777
- $padLen -= $gcstr->columns;
2778
- } else {
2779
- $padLen -= length Encode::decode_utf8($desc);
2780
- Warning($et, 'Unicode::GCString problem. Columns may be misaligned');
2781
- $fixLen = 1;
2782
- }
2783
- }
2921
+ my $padLen = $wid - LengthUTF8($desc);
2784
2922
  $padLen = 0 if $padLen < 0;
2785
2923
  $buff .= $desc . (' ' x $padLen) . ": $val\n";
2786
2924
  } elsif ($outFormat == 2) {
@@ -3011,7 +3149,7 @@ sub SetImageInfo($$$)
3011
3149
  }
3012
3150
  $found = 1;
3013
3151
  $verbose and print $vout "Setting new values from $csv database\n";
3014
- foreach $tag (sort keys %$csvInfo) {
3152
+ foreach $tag (OrderedKeys($csvInfo)) {
3015
3153
  next if $tag =~ /\b(SourceFile|Directory|FileName)$/i; # don't write these
3016
3154
  my ($rtn, $wrn) = $et->SetNewValue($tag, $$csvInfo{$tag},
3017
3155
  Protected => 1, AddValue => $csvAdd,
@@ -3427,8 +3565,7 @@ sub FormatXML($$$)
3427
3565
  } elsif (ref $val eq 'HASH') {
3428
3566
  $gt = " rdf:parseType='Resource'>";
3429
3567
  my $val2 = '';
3430
- my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val;
3431
- foreach (@keys) {
3568
+ foreach (OrderedKeys($val)) {
3432
3569
  # (some variable-namespace XML structure fields may have a different group)
3433
3570
  my ($ns, $tg) = ($grp, $_);
3434
3571
  if (/^(.*?):(.*)/) {
@@ -3512,7 +3649,7 @@ sub FormatJSON($$$;$)
3512
3649
  print $fp $bra;
3513
3650
  foreach (@$val) {
3514
3651
  print $fp ',' if $comma;
3515
- FormatJSON($fp, $_, $ind);
3652
+ FormatJSON($fp, $_, $ind, $quote);
3516
3653
  $comma = 1,
3517
3654
  }
3518
3655
  print $fp $ket,
@@ -3520,8 +3657,7 @@ sub FormatJSON($$$;$)
3520
3657
  } elsif (ref $val eq 'HASH') {
3521
3658
  my ($bra, $ket, $sep) = $json == 1 ? ('{','}',':') : ('Array(',')',' =>');
3522
3659
  print $fp $bra;
3523
- my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val;
3524
- foreach (@keys) {
3660
+ foreach (OrderedKeys($val)) {
3525
3661
  print $fp ',' if $comma;
3526
3662
  my $key = EscapeJSON($_, 1);
3527
3663
  print $fp qq(\n$ind $key$sep );
@@ -3529,7 +3665,7 @@ sub FormatJSON($$$;$)
3529
3665
  if ($showTagID and $_ eq 'id' and $showTagID eq 'H' and $$val{$_} =~ /^\d+\.\d+$/) {
3530
3666
  print $fp qq{"$$val{$_}"};
3531
3667
  } else {
3532
- FormatJSON($fp, $$val{$_}, "$ind ");
3668
+ FormatJSON($fp, $$val{$_}, "$ind ", $quote);
3533
3669
  }
3534
3670
  $comma = 1,
3535
3671
  }
@@ -3678,6 +3814,65 @@ sub IsEqual($$)
3678
3814
  return 1;
3679
3815
  }
3680
3816
 
3817
+ #------------------------------------------------------------------------------
3818
+ # Get the printable rendition of a value
3819
+ # Inputs: 0) value (may be a reference)
3820
+ # Returns: de-referenced value
3821
+ sub Printable($)
3822
+ {
3823
+ my $val = shift;
3824
+ if (ref $val) {
3825
+ if ($structOpt) {
3826
+ require Image::ExifTool::XMP;
3827
+ $val = Image::ExifTool::XMP::SerializeStruct($mt, $val);
3828
+ } elsif (ref $val eq 'ARRAY') {
3829
+ $val = join($listSep, @$val);
3830
+ } elsif (ref $val eq 'SCALAR') {
3831
+ $val = '(Binary data '.length($$val).' bytes)';
3832
+ }
3833
+ }
3834
+ if ($escapeC) {
3835
+ $val =~ s/([\0-\x1f\\\x7f])/$escC{$1} || sprintf('\x%.2x', ord $1)/eg;
3836
+ } else {
3837
+ # translate unprintable chars in value and remove trailing spaces
3838
+ $val =~ tr/\x01-\x1f\x7f/./;
3839
+ $val =~ s/\x00//g;
3840
+ $val =~ s/\s+$//;
3841
+ }
3842
+ return $val;
3843
+ }
3844
+
3845
+ #------------------------------------------------------------------------------
3846
+ # Get character length of a UTF-8 string
3847
+ # Inputs: 0) string
3848
+ # Returns: number of characters (not bytes) in the UTF-8 string
3849
+ sub LengthUTF8($)
3850
+ {
3851
+ my $str = shift;
3852
+ return length $str unless $fixLen;
3853
+ local $SIG{'__WARN__'} = sub { };
3854
+ if (not $$mt{OPTIONS}{EncodeHangs} and eval { require Encode }) {
3855
+ $str = Encode::decode_utf8($str);
3856
+ } else {
3857
+ $str = pack('U0C*', unpack 'C*', $str);
3858
+ }
3859
+ my $len;
3860
+ if ($fixLen == 1) {
3861
+ $len = length $str;
3862
+ } else {
3863
+ my $gcstr = eval { Unicode::GCString->new($str) };
3864
+ if ($gcstr) {
3865
+ $len = $gcstr->columns;
3866
+ } else {
3867
+ $len = length $str;
3868
+ delete $SIG{'__WARN__'};
3869
+ Warning($mt, 'Unicode::GCString problem. Columns may be misaligned');
3870
+ $fixLen = 1;
3871
+ }
3872
+ }
3873
+ return $len;
3874
+ }
3875
+
3681
3876
  #------------------------------------------------------------------------------
3682
3877
  # Add tag list for copying tags from specified file
3683
3878
  # Inputs: 0) set tags file name (or FMT), 1) options for SetNewValuesFromFile()
@@ -3773,6 +3968,19 @@ sub CleanFilename($)
3773
3968
  $_[0] =~ tr/\\/\// if Image::ExifTool::IsPC();
3774
3969
  }
3775
3970
 
3971
+ #------------------------------------------------------------------------------
3972
+ # Does path name contain wildcards
3973
+ # Inputs: 0) path name
3974
+ # Returns: true if path contains wildcards
3975
+ sub HasWildcards($)
3976
+ {
3977
+ my $path = shift;
3978
+
3979
+ # if this is a Windows path with the long path prefix, then wildcards are not supported
3980
+ return 0 if $^O eq 'MSWin32' and $path =~ m{^[\\/]{2}\?[\\/]};
3981
+ return $path =~ /[*?]/;
3982
+ }
3983
+
3776
3984
  #------------------------------------------------------------------------------
3777
3985
  # Check for valid UTF-8 of a file name
3778
3986
  # Inputs: 0) string, 1) original encoding
@@ -3893,7 +4101,7 @@ sub ScanDir($$;$)
3893
4101
  return if $ignore{$dir};
3894
4102
  # use Win32::FindFile on Windows if available
3895
4103
  # (ReadDir will croak if there is a wildcard, so check for this)
3896
- if ($^O eq 'MSWin32' and $dir !~ /[*?]/) {
4104
+ if ($^O eq 'MSWin32' and not HasWildcards($dir)) {
3897
4105
  undef $evalWarning;
3898
4106
  local $SIG{'__WARN__'} = sub { $evalWarning = $_[0] };;
3899
4107
  if (CheckUTF8($dir, $enc) >= 0) {
@@ -4019,7 +4227,7 @@ sub FindFileWindows($$)
4019
4227
  $wildfile = $et->Decode($wildfile, $enc, undef, 'UTF8') if $enc and $enc ne 'UTF8';
4020
4228
  $wildfile =~ tr/\\/\//; # use forward slashes
4021
4229
  my ($dir, $wildname) = ($wildfile =~ m{(.*[:/])(.*)}) ? ($1, $2) : ('', $wildfile);
4022
- if ($dir =~ /[*?]/) {
4230
+ if (HasWildcards($dir)) {
4023
4231
  Warn "Wildcards don't work in the directory specification\n";
4024
4232
  return ();
4025
4233
  }
@@ -4867,6 +5075,7 @@ L<Other options|/Other options>
4867
5075
 
4868
5076
  L<Special features|/Special features>
4869
5077
 
5078
+ -diff FILE2 Compare metadata with another file
4870
5079
  -geotag TRKFILE Geotag images from specified GPS log
4871
5080
  -globalTimeShift SHIFT Shift all formatted date/time values
4872
5081
  -use MODULE Add features from plug-in module
@@ -4958,13 +5167,13 @@ may be used to to indicate any XMP namespace (eg. C<--xmp-all:dabs>).
4958
5167
  Write a new value for the specified tag (eg. C<-comment=wow>), or delete the
4959
5168
  tag if no I<VALUE> is given (eg. C<-comment=>). C<+=> and C<-=> are used to
4960
5169
  add or remove existing entries from a list, or to shift date/time values
4961
- (see L<Image::ExifTool::Shift.pl|Image::ExifTool::Shift.pl> and note 6 below
4962
- for more details). C<+=> may also be used to increment numerical values (or
4963
- decrement if I<VALUE> is negative), and C<-=> may be used to conditionally
4964
- delete or replace a tag (see L</WRITING EXAMPLES> for examples). C<^=> is
4965
- used to write an empty string instead of deleting the tag when no I<VALUE>
4966
- is given, but otherwise it is equivalent to C<=>. (Note that the caret must
4967
- be quoted on the Windows command line.)
5170
+ (see L<Image::ExifTool::Shift.pl|Image::ExifTool::Shift.pl> and notes 6 and
5171
+ 7 below for more details). C<+=> may also be used to increment numerical
5172
+ values (or decrement if I<VALUE> is negative), and C<-=> may be used to
5173
+ conditionally delete or replace a tag (see L</WRITING EXAMPLES> for
5174
+ examples). C<^=> is used to write an empty string instead of deleting the
5175
+ tag when no I<VALUE> is given, but otherwise it is equivalent to C<=>.
5176
+ (Note that the caret must be quoted on the Windows command line.)
4968
5177
 
4969
5178
  I<TAG> may contain one or more leading family 0, 1, 2 or 7 group names,
4970
5179
  prefixed by optional family numbers, and separated colons. If no group name
@@ -5039,6 +5248,10 @@ tag, overriding any other values previously assigned to the tag on the same
5039
5248
  command line. To shift a date/time value and copy it to another tag in the
5040
5249
  same operation, use the B<-globalTimeShift> option.
5041
5250
 
5251
+ 7) The C<+=> operator may not be used to shift a List-type date/time tag
5252
+ (eg. XMP-dc:Date) because C<+=> is used to add elements to the list.
5253
+ Instead, the B<-globalTimeShift> option should be used.
5254
+
5042
5255
  Special feature: Integer values may be specified in hexadecimal with a
5043
5256
  leading C<0x>, and simple rational values may be specified as fractions.
5044
5257
 
@@ -5499,16 +5712,17 @@ output as simple strings). The B<-a> option is implied when B<-json> is
5499
5712
  used, but entries with identical JSON names are suppressed in the output.
5500
5713
  (B<-G4> may be used to ensure that all tags have unique JSON names.) Adding
5501
5714
  the B<-D> or B<-H> option changes tag values to JSON objects with "val" and
5502
- "id" fields, and adding B<-l> adds a "desc" field, and a "num" field if the
5503
- numerical value is different from the converted "val". The B<-b> option may
5504
- be added to output binary data, encoded in base64 if necessary (indicated by
5505
- ASCII "base64:" as the first 7 bytes of the value), and B<-t> may be added
5506
- to include tag table information (see B<-t> for details). The JSON output
5507
- is UTF-8 regardless of any B<-L> or B<-charset> option setting, but the
5508
- UTF-8 validation is disabled if a character set other than UTF-8 is
5509
- specified. Note that ExifTool quotes JSON values only if they don't look
5510
- like numbers (regardless of the original storage format or the relevant
5511
- metadata specification).
5715
+ "id" fields. Adding B<-l> adds a "desc" field, and a "num" field if the
5716
+ numerical value is different from the converted "val", and "fmt" and "hex"
5717
+ fields for EXIF metadata if the API SaveFormat and SaveBin options are set
5718
+ respectively. The B<-b> option may be added to output binary data, encoded
5719
+ in base64 if necessary (indicated by ASCII "base64:" as the first 7 bytes of
5720
+ the value), and B<-t> may be added to include tag table information (see
5721
+ B<-t> for details). The JSON output is UTF-8 regardless of any B<-L> or
5722
+ B<-charset> option setting, but the UTF-8 validation is disabled if a
5723
+ character set other than UTF-8 is specified. Note that ExifTool quotes JSON
5724
+ values only if they don't look like numbers (regardless of the original
5725
+ storage format or the relevant metadata specification).
5512
5726
 
5513
5727
  If I<JSONFILE> is specified, the file is imported and the tag definitions
5514
5728
  from the file are used to set tag values on a per-file basis. The special
@@ -5662,7 +5876,7 @@ with this command:
5662
5876
 
5663
5877
  produces output like this:
5664
5878
 
5665
- -- Generated by ExifTool 12.98 --
5879
+ -- Generated by ExifTool 13.02 --
5666
5880
  File: a.jpg - 2003:10:31 15:44:19
5667
5881
  (f/5.6, 1/60s, ISO 100)
5668
5882
  File: b.jpg - 2006:05:23 11:57:38
@@ -6497,6 +6711,23 @@ names, even if they begin with a dash (C<->).
6497
6711
 
6498
6712
  =over 5
6499
6713
 
6714
+ =item B<-diff> I<FILE2>
6715
+
6716
+ Compare metadata in I<FILE> with I<FILE2>. The I<FILE2> name may include
6717
+ filename formatting codes (see the B<-w> option). All extracted tags from
6718
+ the files are compared, but the extracted tags may be controlled by adding
6719
+ B<-TAG> or B<--TAG> options. For example, below is a command to compare all
6720
+ the same-named files in two different directories, ignoring the System tags:
6721
+
6722
+ exiftool DIR1 -diff DIR2/%f.%e --system:all
6723
+
6724
+ The B<-g> and B<-G> options may be used to organize the output by the
6725
+ specified family of groups, with B<-G1> being the default. The B<-a> option
6726
+ is implied. Adding B<-v> includes a count of the number of tags that are
6727
+ the same in each group. The following text formatting options are valid
6728
+ when B<-diff> is used: B<-c>, B<-charset>, B<-d>, B<-E>, B<-ec>, B<-ex>,
6729
+ B<-L>, B<-lang>, B<-n>, B<-s>, B<-sep>, B<-struct> and B<-w>.
6730
+
6500
6731
  =item B<-geotag> I<TRKFILE>
6501
6732
 
6502
6733
  Geotag images from the specified GPS track log file. Using the B<-geotag>