exiftool_vendored 12.98.0 → 12.99.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1a9880dae5035dc08ddc167d771f69b84d15bc6e5f8d4bcb2eb410c22dd531f
4
- data.tar.gz: 6e3b103009afb397df4213f33c9f695a961a153b647264dadfbf3917aba063fc
3
+ metadata.gz: 857ae11921e86ed35a6ca857a912acf876501c139db5e5646b08ebab6a30d02c
4
+ data.tar.gz: 27bb568a8097d27f5519718fc010df5acddc3976d465bb872492f4bb29f03e7f
5
5
  SHA512:
6
- metadata.gz: a09fd4ca266dd56cac58e246d5a899f34d90f0a93749e9b522129bb3d009e3d36b8ecef7187e54caf6c0a775ab7a9a234e32608b45ae08b778e4de9d3747872b
7
- data.tar.gz: 4119e87dd640a1c9ee73041b5fb915b5cbfbee7c21689ebdefa826f933a35e29e40d7ec5cd952c68b62ab90c5cc544292346e7a45581f355b92ba7a49ca76ec1
6
+ metadata.gz: a60c7457c7f88cff4e5f7c78c95463f9f04961ccb67e318518c0b5d86fb9782f043dc422155e33e7309f6e1db3567545d5aab6a597c764f40ec642dde0973c10
7
+ data.tar.gz: e97a2b5852aa0fd3b8debe38ff11abe65c3850a681526cb40aa65e03dafabcdc0560371b544218918a32221a314b08c5fd0041184a5dcfe7b8f76ad26f9ea537
data/bin/Changes CHANGED
@@ -7,6 +7,25 @@ RSS feed: https://exiftool.org/rss.xml
7
7
  Note: The most recent production release is Version 12.76. (Other versions are
8
8
  considered development releases, and are not uploaded to MetaCPAN.)
9
9
 
10
+ Oct. 18, 2024 - Version 12.99
11
+
12
+ - Added -diff option to compare the metadata in two files
13
+ - Added a new Canon lens (thanks Norbert Wasser)
14
+ - Decode GPS from 70mai A810 dashcam videos
15
+ - Decode a new QuickTime tag
16
+ - Patched to recognize C2PA APP11 JUMBF header with incorrect byte order
17
+ written by buggy Microsoft software
18
+ - Patched to maintain order of entries in a JSON object when reading
19
+ - Patched to maintain order of CSV columns when setting tags from a CSV file
20
+ - Patched to maintain order of XMP lang-alt entries when writing/copying
21
+ - Fixed typo in an APP12 tag name
22
+ - API Changes:
23
+ - Structured values returned as HASH references with the Struct option may
24
+ contain a new "_ordered_keys_" entry used to preserve the order of the
25
+ entries
26
+ - Added the OrderedKeys method to
27
+ return the ordered or sorted keys from a returned structure value
28
+
10
29
  Oct. 8, 2024 - Version 12.98
11
30
 
12
31
  - Added write support for PDF files with huge offsets
data/bin/META.json CHANGED
@@ -50,5 +50,5 @@
50
50
  }
51
51
  },
52
52
  "release_status" : "stable",
53
- "version" : "12.98"
53
+ "version" : "12.99"
54
54
  }
data/bin/META.yml CHANGED
@@ -31,4 +31,4 @@ recommends:
31
31
  Time::HiRes: '0'
32
32
  requires:
33
33
  perl: '5.004'
34
- version: '12.98'
34
+ version: '12.99'
data/bin/README CHANGED
@@ -109,8 +109,8 @@ your home directory, then you would type the following commands in a
109
109
  terminal window to extract and run ExifTool:
110
110
 
111
111
  cd ~/Desktop
112
- gzip -dc Image-ExifTool-12.98.tar.gz | tar -xf -
113
- cd Image-ExifTool-12.98
112
+ gzip -dc Image-ExifTool-12.99.tar.gz | tar -xf -
113
+ cd Image-ExifTool-12.99
114
114
  ./exiftool t/images/ExifTool.jpg
115
115
 
116
116
  Note: These commands extract meta information from one of the test images.
@@ -82,7 +82,8 @@
82
82
  },
83
83
  # add more user-defined EXIF tags here...
84
84
  },
85
- # the Geotag feature writes these additional GPS tags if available:
85
+ # the Geotag feature writes GPSPitch and GPSRoll tags, but these
86
+ # aren't standard EXIF so we define custom tags here:
86
87
  'Image::ExifTool::GPS::Main' => {
87
88
  # Example 2. GPS:GPSPitch
88
89
  0xd000 => {
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 = '12.99';
15
15
 
16
16
  # add our 'lib' directory to the include list BEFORE 'use Image::ExifTool'
17
17
  my $exePath;
@@ -56,6 +56,8 @@ 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($$);
@@ -150,6 +152,7 @@ my $csvAdd; # flag to add CSV information to existing lists
150
152
  my $csvDelim; # delimiter for CSV files
151
153
  my $csvSaveCount; # save counter for last CSV file loaded
152
154
  my $deleteOrig; # 0=restore original files, 1=delete originals, 2=delete w/o asking
155
+ my $diff; # file name for comparing differences
153
156
  my $disableOutput; # flag to disable normal output
154
157
  my $doSetFileName; # flag set if FileName may be written
155
158
  my $doUnzip; # flag to extract info from .gz and .bz2 files
@@ -256,11 +259,13 @@ my %optArgs = (
256
259
  '-csvdelim' => 1,
257
260
  '-d' => 1, '-dateformat' => 1,
258
261
  '-D' => 0, # necessary to avoid matching lower-case equivalent
262
+ '-diff' => 1,
259
263
  '-echo' => 1, '-echo#' => 1,
260
264
  '-efile' => 1, '-efile#' => 1, '-efile!' => 1, '-efile#!' => 1,
261
265
  '-ext' => 1, '--ext' => 1, '-ext+' => 1, '--ext+' => 1,
262
266
  '-extension' => 1, '--extension' => 1, '-extension+' => 1, '--extension+' => 1,
263
267
  '-fileorder' => 1, '-fileorder#' => 1,
268
+ '-file#' => 1,
264
269
  '-geotag' => 1,
265
270
  '-globaltimeshift' => 1,
266
271
  '-i' => 1, '-ignore' => 1,
@@ -332,7 +337,7 @@ sub Exit {
332
337
  exit shift;
333
338
  }
334
339
  # my warning and error routines (NEVER say "die"!)
335
- sub Warn {
340
+ sub Warn {
336
341
  if ($quiet < 2 or $_[0] =~ /^Error/) {
337
342
  my $oldWarn = $SIG{'__WARN__'};
338
343
  delete $SIG{'__WARN__'};
@@ -472,6 +477,7 @@ undef $comma;
472
477
  undef $csv;
473
478
  undef $csvAdd;
474
479
  undef $deleteOrig;
480
+ undef $diff;
475
481
  undef $disableOutput;
476
482
  undef $doSetFileName;
477
483
  undef $doUnzip;
@@ -943,6 +949,11 @@ for (;;) {
943
949
  next;
944
950
  }
945
951
  (/^D$/ or $a eq 'decimal') and $showTagID = 'D', next;
952
+ if (/^diff$/i) {
953
+ $diff = shift;
954
+ defined $diff or Error("Expecting file name for -$_ option\n"), $badCmd=1;
955
+ next;
956
+ }
946
957
  /^delete_original(!?)$/i and $deleteOrig = ($1 ? 2 : 1), next;
947
958
  /^list_dir$/i and $listDir = 1, next;
948
959
  (/^e$/ or $a eq '-composite') and $mt->Options(Composite => 0), next;
@@ -1501,8 +1512,11 @@ if ($overwriteOrig > 1 and $outOpt) {
1501
1512
  next;
1502
1513
  }
1503
1514
 
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";
1515
+ if (($tagOut or defined $diff) and ($csv or $json or %printFmt or $tabFormat or $xml or
1516
+ ($verbose and $html)))
1517
+ {
1518
+ my $opt = $tagOut ? '-W' : '-diff';
1519
+ Warn "Sorry, $opt may not be combined with -csv, -htmlDump, -j, -p, -t or -X\n";
1506
1520
  $rtnVal = 1;
1507
1521
  next;
1508
1522
  }
@@ -1757,9 +1771,14 @@ if (@newValues) {
1757
1771
  $rtnVal = 1;
1758
1772
  next;
1759
1773
  }
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";
1774
+ if ($isWriting) {
1775
+ if (defined $diff) {
1776
+ Error "Can't use -diff option when writing tags\n";
1777
+ next;
1778
+ } elsif (@tags and not $outOpt) {
1779
+ my ($tg, $s) = @tags > 1 ? ("$tags[0] ...", 's') : ($tags[0], '');
1780
+ Warn "Ignored superfluous tag name$s or invalid option$s: -$tg\n";
1781
+ }
1763
1782
  }
1764
1783
  # save current state of new values if setting values from target file
1765
1784
  # or if we may be translating to a different format
@@ -1804,8 +1823,7 @@ if ($outOpt) {
1804
1823
  $type = Image::ExifTool::GetFileExtension($outOpt);
1805
1824
  $type = uc($outOpt) unless defined $type;
1806
1825
  }
1807
- Warn "Can't write $type files\n";
1808
- $rtnVal = 1;
1826
+ Error "Can't write $type files\n";
1809
1827
  next;
1810
1828
  }
1811
1829
  $scanWritable = $type unless CanCreate($type);
@@ -1903,10 +1921,7 @@ if (@dbKeys) {
1903
1921
  # process all specified files
1904
1922
  ProcessFiles($mt);
1905
1923
 
1906
- if ($filtered and not $validFile) {
1907
- Warn "No file with specified extension\n";
1908
- $rtnVal = 1;
1909
- }
1924
+ Error "No file with specified extension\n" if $filtered and not $validFile;
1910
1925
 
1911
1926
  # print CSV information if necessary
1912
1927
  PrintCSV() if $csv and not $isWriting;
@@ -2014,7 +2029,7 @@ Exit $rtnValApp; # all done
2014
2029
  sub GetImageInfo($$)
2015
2030
  {
2016
2031
  my ($et, $orig) = @_;
2017
- my (@foundTags, $info, $file, $ind, $g8);
2032
+ my (@foundTags, @found2, $info, $info2, $et2, $file, $file2, $ind, $g8);
2018
2033
 
2019
2034
  # set window title for this file if necessary
2020
2035
  if (defined $windowTitle) {
@@ -2151,7 +2166,7 @@ sub GetImageInfo($$)
2151
2166
  }
2152
2167
  # can't make use of $info if verbose because we must reprocess
2153
2168
  # the file anyway to generate the verbose output
2154
- undef $info if $verbose or defined $fastCondition;
2169
+ undef $info if $verbose or defined $fastCondition or defined $diff;
2155
2170
  } elsif ($file =~ s/^(\@JSON:)(.*)/$1/) {
2156
2171
  # read JSON file from command line
2157
2172
  my $dat = $2;
@@ -2183,7 +2198,7 @@ sub GetImageInfo($$)
2183
2198
 
2184
2199
  my $lineCount = 0;
2185
2200
  my ($fp, $outfile, $append);
2186
- if ($textOut and ($verbose or $et->Options('PrintCSV')) and not $tagOut) {
2201
+ if ($textOut and ($verbose or $et->Options('PrintCSV')) and not ($tagOut or defined $diff)) {
2187
2202
  ($fp, $outfile, $append) = OpenOutputFile($orig);
2188
2203
  $fp or EFile($file), ++$countBad, return;
2189
2204
  # delete file if we exit prematurely (unless appending)
@@ -2228,7 +2243,7 @@ sub GetImageInfo($$)
2228
2243
  require Image::ExifTool::HTML;
2229
2244
  my $f = Image::ExifTool::HTML::EscapeHTML($file);
2230
2245
  print "<!-- $f -->\n";
2231
- } elsif (not ($json or $xml)) {
2246
+ } elsif (not ($json or $xml or defined $diff)) {
2232
2247
  $o = \*STDOUT if ($multiFile and not $quiet) or $progress;
2233
2248
  }
2234
2249
  }
@@ -2257,10 +2272,37 @@ sub GetImageInfo($$)
2257
2272
  } else {
2258
2273
  @foundTags = @tags;
2259
2274
  }
2275
+ if (defined $diff) {
2276
+ $file2 = FilenameSPrintf($diff, $orig);
2277
+ if ($file eq $file2) {
2278
+ Warn "Error: Diffing file with itself - $file2\n";
2279
+ EFile($file);
2280
+ ++$countBad;
2281
+ return;
2282
+ }
2283
+ if ($et->Exists($file2)) {
2284
+ $showGroup = 1 unless defined $showGroup;
2285
+ $allGroup = 1 unless defined $allGroup;
2286
+ $et->Options(Duplicates => 1, Sort => "Group$showGroup", Verbose => 0);
2287
+ $et2 = Image::ExifTool->new;
2288
+ $et2->Options(%{$$et{OPTIONS}});
2289
+ @found2 = @foundTags;
2290
+ $info2 = $et2->ImageInfo($file2, \@found2);
2291
+ } else {
2292
+ $info2 = { Error => "Diff file not found" };
2293
+ }
2294
+ if ($$info2{Error}) {
2295
+ Warn "Error: $$info2{Error} - $file2\n";
2296
+ EFile($file);
2297
+ ++$countBad;
2298
+ return;
2299
+ }
2300
+ }
2260
2301
  # extract the information
2261
2302
  $info = $et->ImageInfo(Infile($pipe), \@foundTags);
2262
2303
  $et->Options(Duplicates => $oldDups);
2263
2304
  }
2305
+
2264
2306
  # all done now if we already wrote output text file (eg. verbose option)
2265
2307
  if ($fp) {
2266
2308
  if (defined $outfile) {
@@ -2274,7 +2316,7 @@ sub GetImageInfo($$)
2274
2316
  }
2275
2317
  }
2276
2318
  if ($info->{Error}) {
2277
- Warn "Error: $info->{Error} - $file\n";
2319
+ Warn "Error: $$info{Error} - $file\n";
2278
2320
  EFile($file);
2279
2321
  ++$countBad;
2280
2322
  return;
@@ -2298,6 +2340,105 @@ sub GetImageInfo($$)
2298
2340
  $tmpText = $outfile unless $append;
2299
2341
  }
2300
2342
 
2343
+ # print differences if requested
2344
+ if (defined $diff) {
2345
+ my (%done2, $wasDiff, @diffs, @groupTags2);
2346
+ my $v = $verbose || 0;
2347
+ print $fp "======== diff < $file > $file2\n";
2348
+ my ($g2, $same) = (0, 0); # start with $g2 false, but not equal to '' to avoid infinite loop
2349
+ for (;;) {
2350
+ my $tag = shift @foundTags;
2351
+ my ($g, $tag2);
2352
+ if (defined $tag) {
2353
+ $g = $et->GetGroup($tag, $showGroup);
2354
+ } else {
2355
+ for (;;) {
2356
+ $tag2 = shift @found2;
2357
+ defined $tag2 or $g = '', last;
2358
+ $done2{$tag2} or $g = $et2->GetGroup($tag2, $showGroup), last;
2359
+ }
2360
+ }
2361
+ if ($g ne $g2) {
2362
+ my $t2;
2363
+ # add any outstanding tags from diff file not yet handled in previous group ($g2)
2364
+ foreach $t2 (@groupTags2) {
2365
+ next if $done2{$t2};
2366
+ my $val2 = $et2->GetValue($t2);
2367
+ next unless defined $val2;
2368
+ my $name = $outFormat < 1 ? $et2->GetDescription($t2) : GetTagName($t2);
2369
+ my $len = LengthUTF8($name);
2370
+ my $pad = $outFormat < 2 ? ' ' x ($len < 32 ? 32 - $len : 0) : '';
2371
+ if ($allGroup) {
2372
+ my $grp = "[$g2]";
2373
+ $grp .= ' ' x (15 - length($grp)) if length($grp) < 15 and $outFormat < 2;
2374
+ push @diffs, sprintf "> %s %s%s: %s\n", $grp, $name, $pad, Printable($val2);
2375
+ } else {
2376
+ push @diffs, sprintf "> %s%s: %s\n", $name, $pad, Printable($val2);
2377
+ }
2378
+ $done2{$t2} = 1;
2379
+ }
2380
+ my $str = '';
2381
+ ($v > 1 or $same) and $str = " ($same same tag" . ($same==1 ? '' : 's') . ')';
2382
+ if (not $allGroup) {
2383
+ print $fp "---- $g2 ----$str\n" if $g2 and ($str or @diffs);
2384
+ } elsif ($str and $g2) {
2385
+ printf $fp " %-13s%s\n", $g2, $str;
2386
+ }
2387
+ # print all differences for this group
2388
+ @diffs and print($fp @diffs), $wasDiff = 1, @diffs = ();
2389
+ last unless $g;
2390
+ ($g2, $same) = ($g, 0);
2391
+ # build list of all tags in the new group of the diff file
2392
+ @groupTags2 = ();
2393
+ foreach $t2 (@found2) {
2394
+ $done2{$t2} or $g ne $et2->GetGroup($t2, $showGroup) or push @groupTags2, $t2;
2395
+ }
2396
+ }
2397
+ next unless defined $tag;
2398
+ my $val = $et->GetValue($tag);
2399
+ next unless defined $val; # (just in case)
2400
+ my $name = GetTagName($tag);
2401
+ # get matching tag key from diff file
2402
+ my @tags2 = grep /^$name( |$)/, @groupTags2;
2403
+ $name = $et->GetDescription($tag) if $outFormat < 1;
2404
+ my ($val2, $t2);
2405
+ foreach $t2 (@tags2) {
2406
+ next if $done2{$t2};
2407
+ $tag2 = $t2;
2408
+ $val2 = $et2->GetValue($t2);
2409
+ last;
2410
+ }
2411
+ if (defined $val2 and IsEqual($val, $val2)) {
2412
+ ++$same;
2413
+ } else {
2414
+ my $len = LengthUTF8($name);
2415
+ my $pad = $outFormat < 2 ? ' ' x ($len < 32 ? 32 - $len : 0) : '';
2416
+ if ($allGroup) {
2417
+ my $grp = "[$g]";
2418
+ $grp .= ' ' x (15 - length($grp)) if length($grp) < 15 and $outFormat < 2;
2419
+ push @diffs, sprintf "< %s %s%s: %s\n", $grp, $name, $pad, Printable($val);
2420
+ if (defined $val2) {
2421
+ $v < 3 and $grp = ' ' x length($grp), $name = ' ' x $len;
2422
+ push @diffs, sprintf "> %s %s%s: %s\n", $grp, $name, $pad, Printable($val2);
2423
+ }
2424
+ } else {
2425
+ push @diffs, sprintf "< %s%s: %s\n", $name, $pad, Printable($val);
2426
+ $v < 3 and $name = ' ' x $len;
2427
+ push @diffs, sprintf "> %s%s %s\n", $name, $pad, Printable($val2) if defined $val2;
2428
+ }
2429
+ }
2430
+ $done2{$tag2} = 1 if defined $tag2;
2431
+ }
2432
+ print $fp "(no metadata differences)\n" unless $wasDiff;
2433
+ undef $tmpText;
2434
+ if (defined $outfile) {
2435
+ ++$created{$outfile};
2436
+ close($fp);
2437
+ undef $tmpText;
2438
+ }
2439
+ ++$count;
2440
+ return;
2441
+ }
2301
2442
  # restore state of comma flag for this file if appending
2302
2443
  $comma = $outComma{$outfile} if $append and ($textOverwrite & 0x02);
2303
2444
 
@@ -2766,21 +2907,7 @@ TAG: foreach $tag (@foundTags) {
2766
2907
  # pad description to a constant length
2767
2908
  # (get actual character length when using alternate languages
2768
2909
  # 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
- }
2910
+ my $padLen = $wid - LengthUTF8($desc);
2784
2911
  $padLen = 0 if $padLen < 0;
2785
2912
  $buff .= $desc . (' ' x $padLen) . ": $val\n";
2786
2913
  } elsif ($outFormat == 2) {
@@ -3011,7 +3138,7 @@ sub SetImageInfo($$$)
3011
3138
  }
3012
3139
  $found = 1;
3013
3140
  $verbose and print $vout "Setting new values from $csv database\n";
3014
- foreach $tag (sort keys %$csvInfo) {
3141
+ foreach $tag (OrderedKeys($csvInfo)) {
3015
3142
  next if $tag =~ /\b(SourceFile|Directory|FileName)$/i; # don't write these
3016
3143
  my ($rtn, $wrn) = $et->SetNewValue($tag, $$csvInfo{$tag},
3017
3144
  Protected => 1, AddValue => $csvAdd,
@@ -3427,8 +3554,7 @@ sub FormatXML($$$)
3427
3554
  } elsif (ref $val eq 'HASH') {
3428
3555
  $gt = " rdf:parseType='Resource'>";
3429
3556
  my $val2 = '';
3430
- my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val;
3431
- foreach (@keys) {
3557
+ foreach (OrderedKeys($val)) {
3432
3558
  # (some variable-namespace XML structure fields may have a different group)
3433
3559
  my ($ns, $tg) = ($grp, $_);
3434
3560
  if (/^(.*?):(.*)/) {
@@ -3520,8 +3646,7 @@ sub FormatJSON($$$;$)
3520
3646
  } elsif (ref $val eq 'HASH') {
3521
3647
  my ($bra, $ket, $sep) = $json == 1 ? ('{','}',':') : ('Array(',')',' =>');
3522
3648
  print $fp $bra;
3523
- my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val;
3524
- foreach (@keys) {
3649
+ foreach (OrderedKeys($val)) {
3525
3650
  print $fp ',' if $comma;
3526
3651
  my $key = EscapeJSON($_, 1);
3527
3652
  print $fp qq(\n$ind $key$sep );
@@ -3678,6 +3803,52 @@ sub IsEqual($$)
3678
3803
  return 1;
3679
3804
  }
3680
3805
 
3806
+ #------------------------------------------------------------------------------
3807
+ # Get the printable rendition of a value
3808
+ # Inputs: 0) value (may be a reference)
3809
+ # Returns: de-referenced value
3810
+ sub Printable($)
3811
+ {
3812
+ my $val = shift;
3813
+ if (ref $val) {
3814
+ if ($structOpt) {
3815
+ require Image::ExifTool::XMP;
3816
+ $val = Image::ExifTool::XMP::SerializeStruct($mt, $val);
3817
+ } elsif (ref $val eq 'ARRAY') {
3818
+ $val = join($listSep, @$val);
3819
+ } elsif (ref $val eq 'SCALAR') {
3820
+ $val = '(Binary data '.length($$val).' bytes)';
3821
+ }
3822
+ }
3823
+ $val =~ tr/\0-\x1f\x7f/./; # translate unprintable characters
3824
+ return $val;
3825
+ }
3826
+
3827
+ #------------------------------------------------------------------------------
3828
+ # Get character length of a UTF-8 string
3829
+ # Inputs: 0) string
3830
+ # Returns: number of characters (not bytes) in the UTF-8 string
3831
+ sub LengthUTF8($)
3832
+ {
3833
+ my $str = shift;
3834
+ my $len;
3835
+ if (not $fixLen) {
3836
+ $len = length $str;
3837
+ } elsif ($fixLen == 1) {
3838
+ $len = length Encode::decode_utf8($str);
3839
+ } else {
3840
+ my $gcstr = eval { Unicode::GCString->new(Encode::decode_utf8($str)) };
3841
+ if ($gcstr) {
3842
+ $len = $gcstr->columns;
3843
+ } else {
3844
+ $len = length Encode::decode_utf8($str);
3845
+ Warning($mt, 'Unicode::GCString problem. Columns may be misaligned');
3846
+ $fixLen = 1;
3847
+ }
3848
+ }
3849
+ return $len;
3850
+ }
3851
+
3681
3852
  #------------------------------------------------------------------------------
3682
3853
  # Add tag list for copying tags from specified file
3683
3854
  # Inputs: 0) set tags file name (or FMT), 1) options for SetNewValuesFromFile()
@@ -4867,6 +5038,7 @@ L<Other options|/Other options>
4867
5038
 
4868
5039
  L<Special features|/Special features>
4869
5040
 
5041
+ -diff FILE2 Compare metadata with another file
4870
5042
  -geotag TRKFILE Geotag images from specified GPS log
4871
5043
  -globalTimeShift SHIFT Shift all formatted date/time values
4872
5044
  -use MODULE Add features from plug-in module
@@ -5662,7 +5834,7 @@ with this command:
5662
5834
 
5663
5835
  produces output like this:
5664
5836
 
5665
- -- Generated by ExifTool 12.98 --
5837
+ -- Generated by ExifTool 12.99 --
5666
5838
  File: a.jpg - 2003:10:31 15:44:19
5667
5839
  (f/5.6, 1/60s, ISO 100)
5668
5840
  File: b.jpg - 2006:05:23 11:57:38
@@ -6497,6 +6669,23 @@ names, even if they begin with a dash (C<->).
6497
6669
 
6498
6670
  =over 5
6499
6671
 
6672
+ =item B<-diff> I<FILE2>
6673
+
6674
+ Compare metadata in I<FILE> with I<FILE2>. The I<FILE2> name may include
6675
+ filename formatting codes (see the B<-w> option). All extracted tags from
6676
+ the files are compared, but the extracted tags may be controlled by adding
6677
+ B<-TAG> or B<--TAG> options. For example, below is a command to compare all
6678
+ the same-named files in two different directories, ignoring the System tags:
6679
+
6680
+ exiftool DIR1 -diff DIR2/%f.%e --system:all
6681
+
6682
+ The B<-g> and B<-G> options may be used to organize the output by the
6683
+ specified family of groups, with B<-G1> being the default. The B<-a> option
6684
+ is implied. Adding B<-v> includes a count of the number of tags that are
6685
+ the same in each group. The following text formatting options are valid
6686
+ when B<-diff> is used: B<-c>, B<-charset>, B<-d>, B<-E>, B<-L>, B<-lang>,
6687
+ B<-n>, B<-s>, B<-sep>, B<-struct> and B<-w>.
6688
+
6500
6689
  =item B<-geotag> I<TRKFILE>
6501
6690
 
6502
6691
  Geotag images from the specified GPS track log file. Using the B<-geotag>
@@ -14,7 +14,7 @@ use strict;
14
14
  use vars qw($VERSION);
15
15
  use Image::ExifTool qw(:DataAccess :Utils);
16
16
 
17
- $VERSION = '1.13';
17
+ $VERSION = '1.14';
18
18
 
19
19
  sub ProcessAPP12($$$);
20
20
  sub ProcessDucky($$$);
@@ -72,7 +72,7 @@ sub WriteDucky($$$);
72
72
  StrobeTime => { },
73
73
  Resolution => { },
74
74
  Protect => { },
75
- ConTake => { },
75
+ ContTake => { },
76
76
  ImageSize => { PrintConv => '$val=~tr/-/x/;$val' },
77
77
  ColorMode => { },
78
78
  Zoom => { },
@@ -278,6 +278,7 @@ sub ProcessAPP12($$$)
278
278
  $tagInfo = { Name => ucfirst $tag };
279
279
  # put in Camera group if information in "Camera" section
280
280
  $$tagInfo{Groups} = { 2 => 'Camera' } if $section =~ /camera/i;
281
+ $et->VPrint(0, $$et{INDENT}, "[adding APP12:$$tagInfo{Name}]\n");
281
282
  AddTagToTable($tagTablePtr, $tag, $tagInfo);
282
283
  }
283
284
  $et->FoundTag($tagInfo, $val);
@@ -88,7 +88,7 @@ sub ProcessCTMD($$$);
88
88
  sub ProcessExifInfo($$$);
89
89
  sub SwapWords($);
90
90
 
91
- $VERSION = '4.81';
91
+ $VERSION = '4.82';
92
92
 
93
93
  # Note: Removed 'USM' from 'L' lenses since it is redundant - PH
94
94
  # (or is it? Ref 32 shows 5 non-USM L-type lenses)
@@ -6999,6 +6999,7 @@ my %ciMaxFocal = (
6999
6999
  314 => 'Canon RF 24-105mm F2.8 L IS USM Z', #42
7000
7000
  315 => 'Canon RF-S 10-18mm F4.5-6.3 IS STM', #42
7001
7001
  316 => 'Canon RF 35mm F1.4 L VCM', #42
7002
+ 317 => 'Canon RF-S 3.9mm F3.5 STM DUAL FISHEYE', #42
7002
7003
  318 => 'Canon RF 28-70mm F2.8 IS STM', #42
7003
7004
  # Note: add new RF lenses to %canonLensTypes with ID 61182
7004
7005
  },
@@ -12,7 +12,7 @@ require Exporter;
12
12
 
13
13
  use vars qw($VERSION @ISA @EXPORT_OK);
14
14
 
15
- $VERSION = '1.12';
15
+ $VERSION = '1.13';
16
16
  @ISA = qw(Exporter);
17
17
  @EXPORT_OK = qw(ReadCSV ReadJSON);
18
18
 
@@ -87,6 +87,7 @@ sub ReadCSV($$;$$)
87
87
  $fileInfo{$tags[$i]} =
88
88
  (defined $missingValue and $vals[$i] eq $missingValue) ? undef : $vals[$i];
89
89
  }
90
+ $fileInfo{_ordered_keys_} = \@tags;
90
91
  # figure out the file name to use
91
92
  if ($fileInfo{SourceFile}) {
92
93
  $$database{$fileInfo{SourceFile}} = \%fileInfo;
@@ -173,7 +174,7 @@ Tok: for (;;) {
173
174
  }
174
175
  # see what type of object this is
175
176
  if ($tok eq '{') { # object (hash)
176
- $rtnVal = { } unless defined $rtnVal;
177
+ $rtnVal = { _ordered_keys_ => [ ] } unless defined $rtnVal;
177
178
  for (;;) {
178
179
  # read "KEY":"VALUE" pairs
179
180
  unless (defined $key) {
@@ -189,6 +190,7 @@ Tok: for (;;) {
189
190
  $pos = pos $$buffPt;
190
191
  return undef unless defined $val;
191
192
  $$rtnVal{$key} = $val;
193
+ push @{$$rtnVal{_ordered_keys_}}, $key;
192
194
  undef $key;
193
195
  }
194
196
  # scan to delimiting ',' or bounding '}'
@@ -345,7 +347,9 @@ option for a list of valid character sets.
345
347
  These functions return an error string, or undef on success and populate the
346
348
  database hash with entries from the CSV or JSON file. Entries are keyed
347
349
  based on the SourceFile column of the CSV or JSON information, and are
348
- stored as hash lookups of tag name/value for each SourceFile.
350
+ stored as hash lookups of tag name/value for each SourceFile. The order
351
+ of the keys (CSV column order or order in a JSON object) is stored as an
352
+ ARRAY reference in a special "_ordered_keys_" element of this hash.
349
353
 
350
354
  =back
351
355
 
@@ -14,7 +14,7 @@ use vars qw($VERSION);
14
14
  use Image::ExifTool qw(:DataAccess :Utils);
15
15
  use Image::ExifTool::Import;
16
16
 
17
- $VERSION = '1.08';
17
+ $VERSION = '1.09';
18
18
 
19
19
  sub ProcessJSON($$);
20
20
  sub ProcessTag($$$$%);
@@ -92,8 +92,7 @@ sub ProcessTag($$$$%)
92
92
  return unless $et->Options('Struct') > 1;
93
93
  }
94
94
  # support hashes with ordered keys
95
- my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val;
96
- foreach (@keys) {
95
+ foreach (Image::ExifTool::OrderedKeys($val)) {
97
96
  my $tg = $tag . ((/^\d/ and $tag =~ /\d$/) ? '_' : '') . ucfirst;
98
97
  $tg =~ s/([^a-zA-Z])([a-z])/$1\U$2/g;
99
98
  ProcessTag($et, $tagTablePtr, $tg, $$val{$_}, %flags, Flat => 1);
@@ -155,7 +154,7 @@ sub ProcessJSON($$)
155
154
 
156
155
  # extract tags from JSON database
157
156
  foreach $key (sort keys %database) {
158
- foreach $tag (sort keys %{$database{$key}}) {
157
+ foreach $tag (Image::ExifTool::OrderedKeys($database{$key})) {
159
158
  my $val = $database{$key}{$tag};
160
159
  # (ignore SourceFile if generated automatically by ReadJSON)
161
160
  next if $tag eq 'SourceFile' and defined $val and $val eq '*';
@@ -15,7 +15,7 @@ use vars qw($VERSION);
15
15
  use Image::ExifTool qw(:DataAccess :Utils);
16
16
  use Image::ExifTool::Import;
17
17
 
18
- $VERSION = '1.03';
18
+ $VERSION = '1.04';
19
19
 
20
20
  sub ExtractTags($$$);
21
21
 
@@ -106,7 +106,7 @@ sub ExtractTags($$$)
106
106
  my ($et, $meta, $parent) = @_;
107
107
  ref $meta eq 'HASH' or $et->Warn('Invalid LFP metadata'), return;
108
108
  my ($key, $val, $name, $tagTablePtr);
109
- foreach $key (sort keys %$meta) {
109
+ foreach $key (Image::ExifTool::OrderedKeys($meta)) {
110
110
  my $tag = $parent . ucfirst($key);
111
111
  foreach $val (ref $$meta{$key} eq 'ARRAY' ? @{$$meta{$key}} : $$meta{$key}) {
112
112
  ref $val eq 'HASH' and ExtractTags($et, $val, $tag), next;
@@ -48,7 +48,7 @@ use Image::ExifTool qw(:DataAccess :Utils);
48
48
  use Image::ExifTool::Exif;
49
49
  use Image::ExifTool::GPS;
50
50
 
51
- $VERSION = '3.03';
51
+ $VERSION = '3.04';
52
52
 
53
53
  sub ProcessMOV($$;$);
54
54
  sub ProcessKeys($$$);
@@ -901,6 +901,7 @@ my %userDefined = (
901
901
  Writable => 1,
902
902
  },
903
903
  # '35AX'? - seen "AT" (Yada RoadCam Pro 4K dashcam)
904
+ cust => 'CustomInfo', # 70mai A810
904
905
  );
905
906
 
906
907
  # stuff seen in 'skip' atom (70mai Pro Plus+ MP4 videos)
@@ -3351,6 +3352,7 @@ my %userDefined = (
3351
3352
  PrintConv => '"Track $val"',
3352
3353
  },
3353
3354
  # cdep (Structural Dependency QT tag?)
3355
+ # fall - ? int32u, seen: 2
3354
3356
  );
3355
3357
 
3356
3358
  # track aperture mode dimensions atoms
@@ -6744,6 +6746,13 @@ my %userDefined = (
6744
6746
  Avoid => 1,
6745
6747
  %iso8601Date,
6746
6748
  },
6749
+ # (mdta)com.apple.quicktime.scene-illuminance
6750
+ 'scene-illuminance' => {
6751
+ Name => 'SceneIlluminance',
6752
+ Notes => 'milli-lux',
6753
+ ValueConv => 'unpack("N", $val)',
6754
+ Writable => 0, # (don't make this writable because it is found in timed metadata)
6755
+ },
6747
6756
  #
6748
6757
  # seen in Apple ProRes RAW file
6749
6758
  #
@@ -7393,6 +7402,7 @@ my %userDefined = (
7393
7402
  # alac - 28 bytes
7394
7403
  # adrm - AAX DRM atom? 148 bytes
7395
7404
  # aabd - AAX unknown 17kB (contains 'aavd' strings)
7405
+ # dapa - ? 203 bytes
7396
7406
  );
7397
7407
 
7398
7408
  # AMR decode config box (ref 3)
@@ -109,7 +109,7 @@ my %insvLimit = (
109
109
  The tags below are extracted from timed metadata in QuickTime and other
110
110
  formats of video files when the ExtractEmbedded option is used. Although
111
111
  most of these tags are combined into the single table below, ExifTool
112
- currently reads 77 different formats of timed GPS metadata from video files.
112
+ currently reads 78 different formats of timed GPS metadata from video files.
113
113
  },
114
114
  VARS => { NO_ID => 1 },
115
115
  GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' },
@@ -2163,9 +2163,26 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $dirLen; $recPos += 52) {
2163
2163
  push(@xtra, $1 => $2), next;
2164
2164
  }
2165
2165
 
2166
+ } elsif ($$dataPt =~ m/^.{30}A.{20}VV/) {
2167
+
2168
+ $debug and $et->FoundTag(GPSType => 17);
2169
+ # 70mai A810 dashcam (note: no timestamps in the samples I have)
2170
+ # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 ed 01 00 00 [..@.freeGPS ....]
2171
+ # 0010: 03 00 ed 01 00 00 00 0f 00 00 70 08 00 00 41 66 [..........p...Af]
2172
+ # 0020: 13 7d 1e 3c 11 dc 03 5d 01 00 00 01 00 00 00 23 [.}.<...].......#]
2173
+ # 0030: 00 00 00 56 56 00 00 00 00 00 00 00 00 00 00 00 [...VV...........]
2174
+ # 0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
2175
+ SetByteOrder('II');
2176
+ SetGPSDateTime($et, $tagTbl, $$dirInfo{SampleTime});
2177
+ $lat = Get32s($dataPt, 31) / 1e5;
2178
+ $lon = Get32s($dataPt, 35) / 1e5;
2179
+ $spd = Get32s($dataPt, 43); # (seems to be km/h but not confirmed)
2180
+ # offset 475 - int16u=N string[N] - some sort of settings?:
2181
+ # eg. "\x15\x00{pA:V,rA:V,sF:0,tF:2}"
2182
+
2166
2183
  } else {
2167
2184
 
2168
- $debug and $et->FoundTag(GPSType => 17);
2185
+ $debug and $et->FoundTag(GPSType => 18);
2169
2186
  # (look for binary GPS as stored by Nextbase 512G, ref PH)
2170
2187
  # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 01 00 00 [....freeGPS x...]
2171
2188
  # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
@@ -8686,7 +8686,6 @@ my %tagExists = (
8686
8686
  'cont' => 1,
8687
8687
  'contactnames' => 1,
8688
8688
  'containerversion' => 1,
8689
- 'contake' => 1,
8690
8689
  'contentbranding' => 1,
8691
8690
  'contentdescribes' => 1,
8692
8691
  'contentdescription' => 1,
@@ -8709,6 +8708,7 @@ my %tagExists = (
8709
8708
  'contrastadjustment' => 1,
8710
8709
  'contrastinfo' => 1,
8711
8710
  'controller' => 1,
8711
+ 'conttake' => 1,
8712
8712
  'convergenceangle' => 1,
8713
8713
  'convergencebaseimage' => 1,
8714
8714
  'convergencedistance' => 1,
@@ -8820,6 +8820,7 @@ my %tagExists = (
8820
8820
  'customfunctionsd30' => 1,
8821
8821
  'customfunctionsd60' => 1,
8822
8822
  'customfunctionsunknown' => 1,
8823
+ 'custominfo' => 1,
8823
8824
  'customsettingsd3' => 1,
8824
8825
  'customsettingsd300' => 1,
8825
8826
  'customsettingsd300s' => 1,
@@ -12014,6 +12015,7 @@ my %tagExists = (
12014
12015
  'scenebalancealgorithmrevision' => 1,
12015
12016
  'sceneclassification' => 1,
12016
12017
  'scenecolorimetryestimates' => 1,
12018
+ 'sceneilluminance' => 1,
12017
12019
  'scheduleitemid' => 1,
12018
12020
  'schemeinfo' => 1,
12019
12021
  'schemetype' => 1,
@@ -12,7 +12,7 @@ meta information extracted from or written to a file.
12
12
  =head1 TAG TABLES
13
13
 
14
14
  The tables listed below give the names of all tags recognized by ExifTool.
15
- They contain a total of 28148 tags, with 17486 unique tag names.
15
+ They contain a total of 28150 tags, with 17488 unique tag names.
16
16
 
17
17
  B<Tag ID>, B<Index#> or B<Sequence> is given in the first column of each
18
18
  table. A B<Tag ID> is the computer-readable equivalent of a tag name, and
@@ -26163,7 +26163,7 @@ from any tags found in this segment.
26163
26163
  ------ -------- --------
26164
26164
  'Aperture' Aperture no
26165
26165
  'ColorMode' ColorMode no
26166
- 'ConTake' ConTake no
26166
+ 'ContTake' ContTake no
26167
26167
  'ExpBias' ExposureCompensation no
26168
26168
  'FNumber' FNumber no
26169
26169
  'FWare' FirmwareVersion no
@@ -29846,6 +29846,7 @@ for the official QuickTime specification.
29846
29846
  'PICT' PreviewPICT no
29847
29847
  '_htc' HTCInfo QuickTime HTCInfo
29848
29848
  'ardt' ARDroneFile no
29849
+ 'cust' CustomInfo no
29849
29850
  'frea' Kodak_frea Kodak frea
29850
29851
  'free' KodakFree Kodak Free
29851
29852
  Pittasoft QuickTime Pittasoft
@@ -29897,7 +29898,7 @@ for the official QuickTime specification.
29897
29898
  The tags below are extracted from timed metadata in QuickTime and other
29898
29899
  formats of video files when the ExtractEmbedded option is used. Although
29899
29900
  most of these tags are combined into the single table below, ExifTool
29900
- currently reads 77 different formats of timed GPS metadata from video files.
29901
+ currently reads 78 different formats of timed GPS metadata from video files.
29901
29902
 
29902
29903
  Tag Name Writable
29903
29904
  -------- --------
@@ -30163,6 +30164,7 @@ changed via the config file.
30163
30164
  'producer' Producer yes
30164
30165
  'publisher' Publisher yes
30165
30166
  'rating.user' UserRating yes
30167
+ 'scene-illuminance' SceneIlluminance no
30166
30168
  'software' Software yes
30167
30169
  'still-image-time' StillImageTime no
30168
30170
  'title' Title yes
@@ -925,19 +925,31 @@ sub WriteXMP($$;$)
925
925
  # get hash of all information we want to change
926
926
  # (sorted by tag name so alternate languages come last, but with structures
927
927
  # first so flattened tags may be used to override individual structure elements)
928
- my (@tagInfoList, $delLangPath, %delLangPaths, %delAllLang, $firstNewPath);
928
+ my (@tagInfoList, @structList, $delLangPath, %delLangPaths, %delAllLang, $firstNewPath, @langTags);
929
929
  my $writeGroup = $$dirInfo{WriteGroup};
930
930
  foreach $tagInfo (sort ByTagName $et->GetNewTagInfoList()) {
931
931
  next unless $et->GetGroup($tagInfo, 0) eq 'XMP';
932
932
  next if $$tagInfo{Name} eq 'XMP'; # (ignore full XMP block if we didn't write it already)
933
933
  next if $writeGroup and $writeGroup ne $$et{NEW_VALUE}{$tagInfo}{WriteGroup};
934
- if ($$tagInfo{Struct}) {
935
- unshift @tagInfoList, $tagInfo;
934
+ if ($$tagInfo{LangCode}) {
935
+ push @langTags, $tagInfo
936
+ } elsif ($$tagInfo{Struct}) {
937
+ push @structList, $tagInfo;
936
938
  } else {
937
939
  push @tagInfoList, $tagInfo;
938
940
  }
939
941
  }
940
- foreach $tagInfo (@tagInfoList) {
942
+ if (@langTags) {
943
+ # keep original order in which lang-alt entries were added
944
+ foreach $tagInfo (sort { $$et{NEW_VALUE}{$a}{Order} <=> $$et{NEW_VALUE}{$b}{Order} } @langTags) {
945
+ if ($$tagInfo{Struct}) {
946
+ push @structList, $tagInfo;
947
+ } else {
948
+ push @tagInfoList, $tagInfo;
949
+ }
950
+ }
951
+ }
952
+ foreach $tagInfo (@structList, @tagInfoList) {
941
953
  my @delPaths; # list of deleted paths
942
954
  my $tag = $$tagInfo{TagID};
943
955
  my $path = GetPropertyPath($tagInfo);
@@ -295,10 +295,11 @@ my %ignorePrintConv = map { $_ => 1 } qw(OTHER BITMASK Notes);
295
295
  # CreateGroups - hash of all family 0 group names where tag may be created
296
296
  # WriteGroup - group name where information is being written (correct case)
297
297
  # WantGroup - group name as specified in call to function (case insensitive)
298
- # Next - pointer to next new value hash (if more than one)
298
+ # Next - pointer to next new value hash (if more than one for this tag)
299
299
  # NoReplace - set if value was created with Replace=0
300
300
  # AddBefore - number of list items added by a subsequent Replace=0 call
301
- # IsNVH - Flag indicating this is a new value hash
301
+ # IsNVH - flag indicating this is a new value hash
302
+ # Order - counter to indicate the order that new value hashes were created
302
303
  # Shift - shift value
303
304
  # Save - counter used by SaveNewValues()/RestoreNewValues()
304
305
  # MAKER_NOTE_FIXUP - pointer to fixup if necessary for a maker note value
@@ -317,7 +318,7 @@ sub SetNewValue($;$$%)
317
318
 
318
319
  unless (defined $tag) {
319
320
  delete $$self{NEW_VALUE};
320
- $$self{SAVE_COUNT} = 0;
321
+ $$self{SAVE_COUNT} = $$self{NV_COUNT} = 0;
321
322
  $$self{DEL_GROUP} = { };
322
323
  return 1;
323
324
  }
@@ -1389,8 +1390,16 @@ sub SetNewValuesFromFile($$;@)
1389
1390
  return $info if $$info{Error} and $$info{Error} eq 'Error opening file';
1390
1391
  delete $$srcExifTool{VALUE}{Error}; # delete so we can check this later
1391
1392
 
1392
- # sort tags in reverse order so we get priority tag last
1393
- my @tags = reverse sort keys %$info;
1393
+ # sort tags in file order with priority tags last
1394
+ my (@tags, @prio);
1395
+ foreach (sort { $$srcExifTool{FILE_ORDER}{$a} <=> $$srcExifTool{FILE_ORDER}{$b} } keys %$info) {
1396
+ if (/ /) {
1397
+ push @tags, $_;
1398
+ } else {
1399
+ push @prio, $_;
1400
+ }
1401
+ }
1402
+ push @tags, @prio;
1394
1403
  #
1395
1404
  # simply transfer all tags from source image if no tags specified
1396
1405
  #
@@ -3896,6 +3905,7 @@ sub GetNewValueHash($$;$$$$)
3896
3905
  TagInfo => $tagInfo,
3897
3906
  WriteGroup => $writeGroup,
3898
3907
  IsNVH => 1, # set flag so we can recognize a new value hash
3908
+ Order => $$self{NV_COUNT}++,
3899
3909
  };
3900
3910
  # add entry to our NEW_VALUE hash
3901
3911
  if ($$self{NEW_VALUE}{$tagInfo}) {
@@ -4023,7 +4033,7 @@ sub RemoveNewValuesForGroup($$)
4023
4033
  #------------------------------------------------------------------------------
4024
4034
  # Get list of tagInfo hashes for all new data
4025
4035
  # Inputs: 0) ExifTool object reference, 1) optional tag table pointer
4026
- # Returns: list of tagInfo hashes
4036
+ # Returns: list of tagInfo hashes in no particular order
4027
4037
  sub GetNewTagInfoList($;$)
4028
4038
  {
4029
4039
  my ($self, $tagTablePtr) = @_;
@@ -39,8 +39,7 @@ sub SerializeStruct($$;$)
39
39
 
40
40
  if (ref $obj eq 'HASH') {
41
41
  # support hashes with ordered keys
42
- my @keys = $$obj{_ordered_keys_} ? @{$$obj{_ordered_keys_}} : sort keys %$obj;
43
- foreach $key (@keys) {
42
+ foreach $key (Image::ExifTool::OrderedKeys($obj)) {
44
43
  my $hdr = $sfmt ? EscapeJSON($key) . ':' : $key . '=';
45
44
  push @vals, $hdr . SerializeStruct($et, $$obj{$key}, '}');
46
45
  }
@@ -218,7 +217,7 @@ sub DumpStruct($;$)
218
217
  $indent or $indent = '';
219
218
  if (ref $obj eq 'HASH') {
220
219
  print "{\n";
221
- foreach (sort keys %$obj) {
220
+ foreach (Image::ExifTool::OrderedKeys($obj)) {
222
221
  print "$indent $_ = ";
223
222
  DumpStruct($$obj{$_}, "$indent ");
224
223
  }
@@ -253,8 +252,10 @@ sub CheckStruct($$$)
253
252
  ref $struct eq 'HASH' or return wantarray ? (undef, "Expecting $strName structure") : undef;
254
253
 
255
254
  my ($key, $err, $warn, %copy, $rtnVal, $val);
255
+ # copy the ordered keys if they exist
256
+ $copy{_ordered_keys_} = [ ] if $$struct{_ordered_keys_};
256
257
  Key:
257
- foreach $key (keys %$struct) {
258
+ foreach $key (Image::ExifTool::OrderedKeys($struct)) {
258
259
  my $tag = $key;
259
260
  # allow trailing '#' to disable print conversion on a per-field basis
260
261
  my ($type, $fieldInfo);
@@ -377,6 +378,7 @@ Key:
377
378
  $copy{$tag} = \@copy;
378
379
  } elsif ($$fieldInfo{Struct}) {
379
380
  $warn = "Improperly formed structure in $strName $tag";
381
+ next;
380
382
  } else {
381
383
  $et->Sanitize(\$$struct{$key});
382
384
  ($val,$err) = $et->ConvInv($$struct{$key},$fieldInfo,$tag,$strName,$type,'');
@@ -387,6 +389,7 @@ Key:
387
389
  # turn this into a list if necessary
388
390
  $copy{$tag} = $$fieldInfo{List} ? [ $val ] : $val;
389
391
  }
392
+ push @{$copy{_ordered_keys_}}, $tag if $copy{_ordered_keys_}; # save ordered keys
390
393
  }
391
394
  if (%copy or not $warn) {
392
395
  $rtnVal = \%copy;
@@ -562,7 +565,7 @@ sub AddNewStruct($$$$$$)
562
565
  # after all valid structure fields, which is necessary when serializing the XMP later)
563
566
  %$struct or $$struct{'~dummy~'} = '';
564
567
 
565
- foreach $tag (sort keys %$struct) {
568
+ foreach $tag (Image::ExifTool::OrderedKeys($struct)) {
566
569
  my $fieldInfo = $$strTable{$tag};
567
570
  unless ($fieldInfo) {
568
571
  next unless $tag eq '~dummy~'; # check for dummy field
@@ -652,7 +655,8 @@ sub ConvertStruct($$$$;$)
652
655
  my (%struct, $key);
653
656
  my $table = $$tagInfo{Table};
654
657
  $parentID = $$tagInfo{TagID} unless $parentID;
655
- foreach $key (keys %$value) {
658
+ $struct{_ordered_keys_} = [ ] if $$value{_ordered_keys_};
659
+ foreach $key (Image::ExifTool::OrderedKeys($value)) {
656
660
  my $tagID = $parentID . ucfirst($key);
657
661
  my $flatInfo = $$table{$tagID};
658
662
  unless ($flatInfo) {
@@ -669,7 +673,11 @@ sub ConvertStruct($$$$;$)
669
673
  } else {
670
674
  $v = $et->GetValue($flatInfo, $type, $v);
671
675
  }
672
- $struct{$key} = $v if defined $v; # save the converted value
676
+ if (defined $v) {
677
+ $struct{$key} = $v; # save the converted value
678
+ # maintain ordered keys if necessary
679
+ push @{$struct{_ordered_keys_}}, $key if $struct{_ordered_keys_};
680
+ }
673
681
  }
674
682
  return \%struct;
675
683
  } elsif (ref $value eq 'ARRAY') {
@@ -29,7 +29,7 @@ use vars qw($VERSION $RELEASE @ISA @EXPORT_OK %EXPORT_TAGS $AUTOLOAD @fileTypes
29
29
  %jpegMarker %specialTags %fileTypeLookup $testLen $exeDir
30
30
  %static_vars $advFmtSelf);
31
31
 
32
- $VERSION = '12.98';
32
+ $VERSION = '12.99';
33
33
  $RELEASE = '';
34
34
  @ISA = qw(Exporter);
35
35
  %EXPORT_TAGS = (
@@ -37,7 +37,7 @@ $RELEASE = '';
37
37
  Public => [qw(
38
38
  ImageInfo AvailableOptions GetTagName GetShortcuts GetAllTags
39
39
  GetWritableTags GetAllGroups GetDeleteGroups GetFileType CanWrite
40
- CanCreate AddUserDefinedTags
40
+ CanCreate AddUserDefinedTags OrderedKeys
41
41
  )],
42
42
  # exports not part of the public API, but used by ExifTool modules:
43
43
  DataAccess => [qw(
@@ -2292,6 +2292,7 @@ sub new
2292
2292
  $$self{PATH} = [ ]; # (this too)
2293
2293
  $$self{DEL_GROUP} = { }; # lookup for groups to delete when writing
2294
2294
  $$self{SAVE_COUNT} = 0; # count calls to SaveNewValues()
2295
+ $$self{NV_COUNT} = 0; # count of NEW_VALUE entries
2295
2296
  $$self{FILE_SEQUENCE} = 0; # sequence number for files when reading
2296
2297
  $$self{FILES_WRITTEN} = 0; # count of files successfully written
2297
2298
  $$self{INDENT2} = ''; # indentation of verbose messages from SetNewValue
@@ -2517,6 +2518,8 @@ sub Options($$;@)
2517
2518
  # set Compact and XMPShorthand options, preserving backward compatibility
2518
2519
  my ($p, %compact);
2519
2520
  foreach $p ('Compact','XMPShorthand') {
2521
+ # (allow setting from a HASH (undocumented)
2522
+ ref $newVal eq 'HASH' and %compact = %{$newVal}, next;
2520
2523
  my $val = $param eq $p ? $newVal : $$options{Compact}{$p};
2521
2524
  if (defined $val) {
2522
2525
  my @v = ($val =~ /\w+/g);
@@ -4195,6 +4198,16 @@ sub CanCreate($)
4195
4198
  return 0;
4196
4199
  }
4197
4200
 
4201
+ #------------------------------------------------------------------------------
4202
+ # Return list of ordered keys if available, otherwise just sort alphabetically
4203
+ # Inputs: 0) hash ref
4204
+ # Returns: List of ordered/sorted keys
4205
+ sub OrderedKeys($)
4206
+ {
4207
+ my $hash = shift;
4208
+ return $$hash{_ordered_keys_} ? @{$$hash{_ordered_keys_}} : sort keys %$hash;
4209
+ }
4210
+
4198
4211
  #==============================================================================
4199
4212
  # Functions below this are not part of the public API
4200
4213
 
@@ -7904,8 +7917,17 @@ sub ProcessJPEG($$;$)
7904
7917
  my $seq = Get32u($segDataPt, 4);
7905
7918
  my $len = Get32u($segDataPt, 8);
7906
7919
  my $type = substr($$segDataPt, 12, 4);
7920
+ # a Microsoft bug writes $len and $type incorrectly as little-endian
7921
+ if ($type eq 'bmuj') {
7922
+ $self->WarnOnce('Wrong byte order in C2PA APP11 JUMBF header');
7923
+ $type = 'jumb';
7924
+ $len = unpack('x8V', $$segDataPt);
7925
+ # fix the header
7926
+ substr($$segDataPt, 8, 8) = Set32u($len) . $type;
7927
+ }
7907
7928
  my $hdrLen;
7908
7929
  if ($len == 1 and length($$segDataPt) >= 24) {
7930
+ # (haven't seen this with the Microsoft bug)
7909
7931
  $len = Get64u($$segDataPt, 16);
7910
7932
  $hdrLen = 16;
7911
7933
  } else {
@@ -332,13 +332,14 @@ L</ImageInfo>:
332
332
 
333
333
  Values of the returned hash are usually simple scalars, but a scalar
334
334
  reference is used to indicate binary data and an array reference may be used
335
- to indicate a list. Also, a hash reference may be returned if the L</Struct>
336
- option is used. Lists of values are joined by commas into a single
337
- string only if the PrintConv option is enabled and the ListJoin option is
338
- enabled (which are the defaults). Note that binary values are not
339
- necessarily extracted unless specifically requested, or the Binary option is
340
- enabled and the tag is not specifically excluded. If not extracted the
341
- value is a reference to a string of the form "Binary data ##### bytes".
335
+ to indicate a list. Also, a hash reference may be returned if the
336
+ L</Struct> option is used (see the L</OrderedKeys> option to obtain the hash
337
+ keys). Lists of values are joined by commas into a single string only if
338
+ the PrintConv option is enabled and the ListJoin option is enabled (which
339
+ are the defaults). Note that binary values are not necessarily extracted
340
+ unless specifically requested, or the Binary option is enabled and the tag
341
+ is not specifically excluded. If not extracted the value is a reference to
342
+ a string of the form "Binary data ##### bytes".
342
343
 
343
344
  The code below gives an example of how to handle these return values, as
344
345
  well as illustrating the use of other ExifTool functions:
@@ -1089,7 +1090,9 @@ values in standard format).
1089
1090
 
1090
1091
  Flag to return XMP structures as hash references instead of flattening into
1091
1092
  individual tags. Has no effect when writing since both flattened and
1092
- structured tags may always be written. Possible values are:
1093
+ structured tags may always be written. A special "_ordered_keys_" element
1094
+ containing a list of ordered keys may exist if the structure elements are
1095
+ ordered (see the L<OrderedKeys> method). Possible values are:
1093
1096
 
1094
1097
  undef - (default) Same as 0 for reading, 2 for copying
1095
1098
  0 - Read/copy flattened tags
@@ -2749,6 +2752,26 @@ details on the elements of the tag information hash.
2749
2752
 
2750
2753
  =back
2751
2754
 
2755
+ =head2 OrderedKeys [static]
2756
+
2757
+ Return a list of ordered keys from a tag value that is a HASH reference
2758
+ when the Struct option is used.
2759
+
2760
+ use Image::ExifTool ':Public';
2761
+ my @keys = OrderedKeys($structRef);
2762
+
2763
+ =over 4
2764
+
2765
+ =item Inputs:
2766
+
2767
+ 0) Structure HASH reference
2768
+
2769
+ =item Return Value:
2770
+
2771
+ List of ordered keys, or sorted alphabetically if not ordered.
2772
+
2773
+ =back
2774
+
2752
2775
  =head1 CHARACTER ENCODINGS
2753
2776
 
2754
2777
  Certain meta information formats allow coded character sets other than plain
@@ -1,6 +1,6 @@
1
1
  Summary: perl module for image data extraction
2
2
  Name: perl-Image-ExifTool
3
- Version: 12.98
3
+ Version: 12.99
4
4
  Release: 1
5
5
  License: Artistic/GPL
6
6
  Group: Development/Libraries/Perl
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExiftoolVendored
4
- VERSION = Gem::Version.new('12.98.0')
4
+ VERSION = Gem::Version.new('12.99.0')
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exiftool_vendored
3
3
  version: !ruby/object:Gem::Version
4
- version: 12.98.0
4
+ version: 12.99.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew McEachen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-10-09 00:00:00.000000000 Z
12
+ date: 2024-10-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: exiftool