From 795065eec0911f18f1b176045b89b9df7173ebc7 Mon Sep 17 00:00:00 2001 From: morozgrafix Date: Mon, 28 Oct 2024 18:02:44 -0700 Subject: [PATCH] Upgrading to exiftool v12.99 --- bin/Changes | 19 ++ bin/META.json | 2 +- bin/META.yml | 2 +- bin/README | 4 +- bin/config_files/example.config | 3 +- bin/exiftool | 267 ++++++++++++++++++---- bin/lib/Image/ExifTool.pm | 26 ++- bin/lib/Image/ExifTool.pod | 39 +++- bin/lib/Image/ExifTool/APP12.pm | 5 +- bin/lib/Image/ExifTool/Canon.pm | 3 +- bin/lib/Image/ExifTool/Import.pm | 10 +- bin/lib/Image/ExifTool/JSON.pm | 7 +- bin/lib/Image/ExifTool/Lytro.pm | 4 +- bin/lib/Image/ExifTool/QuickTime.pm | 12 +- bin/lib/Image/ExifTool/QuickTimeStream.pl | 21 +- bin/lib/Image/ExifTool/TagLookup.pm | 4 +- bin/lib/Image/ExifTool/TagNames.pod | 8 +- bin/lib/Image/ExifTool/WriteXMP.pl | 20 +- bin/lib/Image/ExifTool/Writer.pl | 22 +- bin/lib/Image/ExifTool/XMPStruct.pl | 22 +- bin/perl-Image-ExifTool.spec | 2 +- lib/exiftool_vendored/version.rb | 2 +- 22 files changed, 412 insertions(+), 92 deletions(-) diff --git a/bin/Changes b/bin/Changes index 8d296fe..3010c4f 100644 --- a/bin/Changes +++ b/bin/Changes @@ -7,6 +7,25 @@ RSS feed: https://exiftool.org/rss.xml Note: The most recent production release is Version 12.76. (Other versions are considered development releases, and are not uploaded to MetaCPAN.) +Oct. 18, 2024 - Version 12.99 + + - Added -diff option to compare the metadata in two files + - Added a new Canon lens (thanks Norbert Wasser) + - Decode GPS from 70mai A810 dashcam videos + - Decode a new QuickTime tag + - Patched to recognize C2PA APP11 JUMBF header with incorrect byte order + written by buggy Microsoft software + - Patched to maintain order of entries in a JSON object when reading + - Patched to maintain order of CSV columns when setting tags from a CSV file + - Patched to maintain order of XMP lang-alt entries when writing/copying + - Fixed typo in an APP12 tag name + - API Changes: + - Structured values returned as HASH references with the Struct option may + contain a new "_ordered_keys_" entry used to preserve the order of the + entries + - Added the OrderedKeys method to + return the ordered or sorted keys from a returned structure value + Oct. 8, 2024 - Version 12.98 - Added write support for PDF files with huge offsets diff --git a/bin/META.json b/bin/META.json index 9ac3bb9..dc6acfe 100644 --- a/bin/META.json +++ b/bin/META.json @@ -50,5 +50,5 @@ } }, "release_status" : "stable", - "version" : "12.98" + "version" : "12.99" } diff --git a/bin/META.yml b/bin/META.yml index 1681ed0..df88b77 100644 --- a/bin/META.yml +++ b/bin/META.yml @@ -31,4 +31,4 @@ recommends: Time::HiRes: '0' requires: perl: '5.004' -version: '12.98' +version: '12.99' diff --git a/bin/README b/bin/README index b23ffdb..dc120a4 100644 --- a/bin/README +++ b/bin/README @@ -109,8 +109,8 @@ your home directory, then you would type the following commands in a terminal window to extract and run ExifTool: cd ~/Desktop - gzip -dc Image-ExifTool-12.98.tar.gz | tar -xf - - cd Image-ExifTool-12.98 + gzip -dc Image-ExifTool-12.99.tar.gz | tar -xf - + cd Image-ExifTool-12.99 ./exiftool t/images/ExifTool.jpg Note: These commands extract meta information from one of the test images. diff --git a/bin/config_files/example.config b/bin/config_files/example.config index 553ba04..0a7620a 100644 --- a/bin/config_files/example.config +++ b/bin/config_files/example.config @@ -82,7 +82,8 @@ }, # add more user-defined EXIF tags here... }, - # the Geotag feature writes these additional GPS tags if available: + # the Geotag feature writes GPSPitch and GPSRoll tags, but these + # aren't standard EXIF so we define custom tags here: 'Image::ExifTool::GPS::Main' => { # Example 2. GPS:GPSPitch 0xd000 => { diff --git a/bin/exiftool b/bin/exiftool index fc42e88..0bc110c 100755 --- a/bin/exiftool +++ b/bin/exiftool @@ -11,7 +11,7 @@ use strict; use warnings; require 5.004; -my $version = '12.98'; +my $version = '12.99'; # add our 'lib' directory to the include list BEFORE 'use Image::ExifTool' my $exePath; @@ -56,6 +56,8 @@ sub PrintCSV(); sub AddGroups($$$$); sub ConvertBinary($); sub IsEqual($$); +sub Printable($); +sub LengthUTF8($); sub Infile($;$); sub AddSetTagsFile($;$); sub Warning($$); @@ -150,6 +152,7 @@ my $csvAdd; # flag to add CSV information to existing lists my $csvDelim; # delimiter for CSV files my $csvSaveCount; # save counter for last CSV file loaded my $deleteOrig; # 0=restore original files, 1=delete originals, 2=delete w/o asking +my $diff; # file name for comparing differences my $disableOutput; # flag to disable normal output my $doSetFileName; # flag set if FileName may be written my $doUnzip; # flag to extract info from .gz and .bz2 files @@ -256,11 +259,13 @@ my %optArgs = ( '-csvdelim' => 1, '-d' => 1, '-dateformat' => 1, '-D' => 0, # necessary to avoid matching lower-case equivalent + '-diff' => 1, '-echo' => 1, '-echo#' => 1, '-efile' => 1, '-efile#' => 1, '-efile!' => 1, '-efile#!' => 1, '-ext' => 1, '--ext' => 1, '-ext+' => 1, '--ext+' => 1, '-extension' => 1, '--extension' => 1, '-extension+' => 1, '--extension+' => 1, '-fileorder' => 1, '-fileorder#' => 1, + '-file#' => 1, '-geotag' => 1, '-globaltimeshift' => 1, '-i' => 1, '-ignore' => 1, @@ -332,7 +337,7 @@ sub Exit { exit shift; } # my warning and error routines (NEVER say "die"!) -sub Warn { +sub Warn { if ($quiet < 2 or $_[0] =~ /^Error/) { my $oldWarn = $SIG{'__WARN__'}; delete $SIG{'__WARN__'}; @@ -472,6 +477,7 @@ undef $comma; undef $csv; undef $csvAdd; undef $deleteOrig; +undef $diff; undef $disableOutput; undef $doSetFileName; undef $doUnzip; @@ -943,6 +949,11 @@ for (;;) { next; } (/^D$/ or $a eq 'decimal') and $showTagID = 'D', next; + if (/^diff$/i) { + $diff = shift; + defined $diff or Error("Expecting file name for -$_ option\n"), $badCmd=1; + next; + } /^delete_original(!?)$/i and $deleteOrig = ($1 ? 2 : 1), next; /^list_dir$/i and $listDir = 1, next; (/^e$/ or $a eq '-composite') and $mt->Options(Composite => 0), next; @@ -1501,8 +1512,11 @@ if ($overwriteOrig > 1 and $outOpt) { next; } -if ($tagOut and ($csv or %printFmt or $tabFormat or $xml or ($verbose and $html))) { - Warn "Sorry, -W may not be combined with -csv, -htmlDump, -j, -p, -t or -X\n"; +if (($tagOut or defined $diff) and ($csv or $json or %printFmt or $tabFormat or $xml or + ($verbose and $html))) +{ + my $opt = $tagOut ? '-W' : '-diff'; + Warn "Sorry, $opt may not be combined with -csv, -htmlDump, -j, -p, -t or -X\n"; $rtnVal = 1; next; } @@ -1757,9 +1771,14 @@ if (@newValues) { $rtnVal = 1; next; } -if ($isWriting and @tags and not $outOpt) { - my ($tg, $s) = @tags > 1 ? ("$tags[0] ...", 's') : ($tags[0], ''); - Warn "Ignored superfluous tag name$s or invalid option$s: -$tg\n"; +if ($isWriting) { + if (defined $diff) { + Error "Can't use -diff option when writing tags\n"; + next; + } elsif (@tags and not $outOpt) { + my ($tg, $s) = @tags > 1 ? ("$tags[0] ...", 's') : ($tags[0], ''); + Warn "Ignored superfluous tag name$s or invalid option$s: -$tg\n"; + } } # save current state of new values if setting values from target file # or if we may be translating to a different format @@ -1804,8 +1823,7 @@ if ($outOpt) { $type = Image::ExifTool::GetFileExtension($outOpt); $type = uc($outOpt) unless defined $type; } - Warn "Can't write $type files\n"; - $rtnVal = 1; + Error "Can't write $type files\n"; next; } $scanWritable = $type unless CanCreate($type); @@ -1903,10 +1921,7 @@ if (@dbKeys) { # process all specified files ProcessFiles($mt); -if ($filtered and not $validFile) { - Warn "No file with specified extension\n"; - $rtnVal = 1; -} +Error "No file with specified extension\n" if $filtered and not $validFile; # print CSV information if necessary PrintCSV() if $csv and not $isWriting; @@ -2014,7 +2029,7 @@ Exit $rtnValApp; # all done sub GetImageInfo($$) { my ($et, $orig) = @_; - my (@foundTags, $info, $file, $ind, $g8); + my (@foundTags, @found2, $info, $info2, $et2, $file, $file2, $ind, $g8); # set window title for this file if necessary if (defined $windowTitle) { @@ -2151,7 +2166,7 @@ sub GetImageInfo($$) } # can't make use of $info if verbose because we must reprocess # the file anyway to generate the verbose output - undef $info if $verbose or defined $fastCondition; + undef $info if $verbose or defined $fastCondition or defined $diff; } elsif ($file =~ s/^(\@JSON:)(.*)/$1/) { # read JSON file from command line my $dat = $2; @@ -2183,7 +2198,7 @@ sub GetImageInfo($$) my $lineCount = 0; my ($fp, $outfile, $append); - if ($textOut and ($verbose or $et->Options('PrintCSV')) and not $tagOut) { + if ($textOut and ($verbose or $et->Options('PrintCSV')) and not ($tagOut or defined $diff)) { ($fp, $outfile, $append) = OpenOutputFile($orig); $fp or EFile($file), ++$countBad, return; # delete file if we exit prematurely (unless appending) @@ -2228,7 +2243,7 @@ sub GetImageInfo($$) require Image::ExifTool::HTML; my $f = Image::ExifTool::HTML::EscapeHTML($file); print "\n"; - } elsif (not ($json or $xml)) { + } elsif (not ($json or $xml or defined $diff)) { $o = \*STDOUT if ($multiFile and not $quiet) or $progress; } } @@ -2257,10 +2272,37 @@ sub GetImageInfo($$) } else { @foundTags = @tags; } + if (defined $diff) { + $file2 = FilenameSPrintf($diff, $orig); + if ($file eq $file2) { + Warn "Error: Diffing file with itself - $file2\n"; + EFile($file); + ++$countBad; + return; + } + if ($et->Exists($file2)) { + $showGroup = 1 unless defined $showGroup; + $allGroup = 1 unless defined $allGroup; + $et->Options(Duplicates => 1, Sort => "Group$showGroup", Verbose => 0); + $et2 = Image::ExifTool->new; + $et2->Options(%{$$et{OPTIONS}}); + @found2 = @foundTags; + $info2 = $et2->ImageInfo($file2, \@found2); + } else { + $info2 = { Error => "Diff file not found" }; + } + if ($$info2{Error}) { + Warn "Error: $$info2{Error} - $file2\n"; + EFile($file); + ++$countBad; + return; + } + } # extract the information $info = $et->ImageInfo(Infile($pipe), \@foundTags); $et->Options(Duplicates => $oldDups); } + # all done now if we already wrote output text file (eg. verbose option) if ($fp) { if (defined $outfile) { @@ -2274,7 +2316,7 @@ sub GetImageInfo($$) } } if ($info->{Error}) { - Warn "Error: $info->{Error} - $file\n"; + Warn "Error: $$info{Error} - $file\n"; EFile($file); ++$countBad; return; @@ -2298,6 +2340,105 @@ sub GetImageInfo($$) $tmpText = $outfile unless $append; } + # print differences if requested + if (defined $diff) { + my (%done2, $wasDiff, @diffs, @groupTags2); + my $v = $verbose || 0; + print $fp "======== diff < $file > $file2\n"; + my ($g2, $same) = (0, 0); # start with $g2 false, but not equal to '' to avoid infinite loop + for (;;) { + my $tag = shift @foundTags; + my ($g, $tag2); + if (defined $tag) { + $g = $et->GetGroup($tag, $showGroup); + } else { + for (;;) { + $tag2 = shift @found2; + defined $tag2 or $g = '', last; + $done2{$tag2} or $g = $et2->GetGroup($tag2, $showGroup), last; + } + } + if ($g ne $g2) { + my $t2; + # add any outstanding tags from diff file not yet handled in previous group ($g2) + foreach $t2 (@groupTags2) { + next if $done2{$t2}; + my $val2 = $et2->GetValue($t2); + next unless defined $val2; + my $name = $outFormat < 1 ? $et2->GetDescription($t2) : GetTagName($t2); + my $len = LengthUTF8($name); + my $pad = $outFormat < 2 ? ' ' x ($len < 32 ? 32 - $len : 0) : ''; + if ($allGroup) { + my $grp = "[$g2]"; + $grp .= ' ' x (15 - length($grp)) if length($grp) < 15 and $outFormat < 2; + push @diffs, sprintf "> %s %s%s: %s\n", $grp, $name, $pad, Printable($val2); + } else { + push @diffs, sprintf "> %s%s: %s\n", $name, $pad, Printable($val2); + } + $done2{$t2} = 1; + } + my $str = ''; + ($v > 1 or $same) and $str = " ($same same tag" . ($same==1 ? '' : 's') . ')'; + if (not $allGroup) { + print $fp "---- $g2 ----$str\n" if $g2 and ($str or @diffs); + } elsif ($str and $g2) { + printf $fp " %-13s%s\n", $g2, $str; + } + # print all differences for this group + @diffs and print($fp @diffs), $wasDiff = 1, @diffs = (); + last unless $g; + ($g2, $same) = ($g, 0); + # build list of all tags in the new group of the diff file + @groupTags2 = (); + foreach $t2 (@found2) { + $done2{$t2} or $g ne $et2->GetGroup($t2, $showGroup) or push @groupTags2, $t2; + } + } + next unless defined $tag; + my $val = $et->GetValue($tag); + next unless defined $val; # (just in case) + my $name = GetTagName($tag); + # get matching tag key from diff file + my @tags2 = grep /^$name( |$)/, @groupTags2; + $name = $et->GetDescription($tag) if $outFormat < 1; + my ($val2, $t2); + foreach $t2 (@tags2) { + next if $done2{$t2}; + $tag2 = $t2; + $val2 = $et2->GetValue($t2); + last; + } + if (defined $val2 and IsEqual($val, $val2)) { + ++$same; + } else { + my $len = LengthUTF8($name); + my $pad = $outFormat < 2 ? ' ' x ($len < 32 ? 32 - $len : 0) : ''; + if ($allGroup) { + my $grp = "[$g]"; + $grp .= ' ' x (15 - length($grp)) if length($grp) < 15 and $outFormat < 2; + push @diffs, sprintf "< %s %s%s: %s\n", $grp, $name, $pad, Printable($val); + if (defined $val2) { + $v < 3 and $grp = ' ' x length($grp), $name = ' ' x $len; + push @diffs, sprintf "> %s %s%s: %s\n", $grp, $name, $pad, Printable($val2); + } + } else { + push @diffs, sprintf "< %s%s: %s\n", $name, $pad, Printable($val); + $v < 3 and $name = ' ' x $len; + push @diffs, sprintf "> %s%s %s\n", $name, $pad, Printable($val2) if defined $val2; + } + } + $done2{$tag2} = 1 if defined $tag2; + } + print $fp "(no metadata differences)\n" unless $wasDiff; + undef $tmpText; + if (defined $outfile) { + ++$created{$outfile}; + close($fp); + undef $tmpText; + } + ++$count; + return; + } # restore state of comma flag for this file if appending $comma = $outComma{$outfile} if $append and ($textOverwrite & 0x02); @@ -2766,21 +2907,7 @@ TAG: foreach $tag (@foundTags) { # pad description to a constant length # (get actual character length when using alternate languages # because these descriptions may contain UTF8-encoded characters) - my $padLen = $wid; - if (not $fixLen) { - $padLen -= length $desc; - } elsif ($fixLen == 1) { - $padLen -= length Encode::decode_utf8($desc); - } else { - my $gcstr = eval { Unicode::GCString->new(Encode::decode_utf8($desc)) }; - if ($gcstr) { - $padLen -= $gcstr->columns; - } else { - $padLen -= length Encode::decode_utf8($desc); - Warning($et, 'Unicode::GCString problem. Columns may be misaligned'); - $fixLen = 1; - } - } + my $padLen = $wid - LengthUTF8($desc); $padLen = 0 if $padLen < 0; $buff .= $desc . (' ' x $padLen) . ": $val\n"; } elsif ($outFormat == 2) { @@ -3011,7 +3138,7 @@ sub SetImageInfo($$$) } $found = 1; $verbose and print $vout "Setting new values from $csv database\n"; - foreach $tag (sort keys %$csvInfo) { + foreach $tag (OrderedKeys($csvInfo)) { next if $tag =~ /\b(SourceFile|Directory|FileName)$/i; # don't write these my ($rtn, $wrn) = $et->SetNewValue($tag, $$csvInfo{$tag}, Protected => 1, AddValue => $csvAdd, @@ -3427,8 +3554,7 @@ sub FormatXML($$$) } elsif (ref $val eq 'HASH') { $gt = " rdf:parseType='Resource'>"; my $val2 = ''; - my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val; - foreach (@keys) { + foreach (OrderedKeys($val)) { # (some variable-namespace XML structure fields may have a different group) my ($ns, $tg) = ($grp, $_); if (/^(.*?):(.*)/) { @@ -3520,8 +3646,7 @@ sub FormatJSON($$$;$) } elsif (ref $val eq 'HASH') { my ($bra, $ket, $sep) = $json == 1 ? ('{','}',':') : ('Array(',')',' =>'); print $fp $bra; - my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val; - foreach (@keys) { + foreach (OrderedKeys($val)) { print $fp ',' if $comma; my $key = EscapeJSON($_, 1); print $fp qq(\n$ind $key$sep ); @@ -3678,6 +3803,52 @@ sub IsEqual($$) return 1; } +#------------------------------------------------------------------------------ +# Get the printable rendition of a value +# Inputs: 0) value (may be a reference) +# Returns: de-referenced value +sub Printable($) +{ + my $val = shift; + if (ref $val) { + if ($structOpt) { + require Image::ExifTool::XMP; + $val = Image::ExifTool::XMP::SerializeStruct($mt, $val); + } elsif (ref $val eq 'ARRAY') { + $val = join($listSep, @$val); + } elsif (ref $val eq 'SCALAR') { + $val = '(Binary data '.length($$val).' bytes)'; + } + } + $val =~ tr/\0-\x1f\x7f/./; # translate unprintable characters + return $val; +} + +#------------------------------------------------------------------------------ +# Get character length of a UTF-8 string +# Inputs: 0) string +# Returns: number of characters (not bytes) in the UTF-8 string +sub LengthUTF8($) +{ + my $str = shift; + my $len; + if (not $fixLen) { + $len = length $str; + } elsif ($fixLen == 1) { + $len = length Encode::decode_utf8($str); + } else { + my $gcstr = eval { Unicode::GCString->new(Encode::decode_utf8($str)) }; + if ($gcstr) { + $len = $gcstr->columns; + } else { + $len = length Encode::decode_utf8($str); + Warning($mt, 'Unicode::GCString problem. Columns may be misaligned'); + $fixLen = 1; + } + } + return $len; +} + #------------------------------------------------------------------------------ # Add tag list for copying tags from specified file # Inputs: 0) set tags file name (or FMT), 1) options for SetNewValuesFromFile() @@ -4867,6 +5038,7 @@ L L + -diff FILE2 Compare metadata with another file -geotag TRKFILE Geotag images from specified GPS log -globalTimeShift SHIFT Shift all formatted date/time values -use MODULE Add features from plug-in module @@ -5662,7 +5834,7 @@ with this command: produces output like this: - -- Generated by ExifTool 12.98 -- + -- Generated by ExifTool 12.99 -- File: a.jpg - 2003:10:31 15:44:19 (f/5.6, 1/60s, ISO 100) File: b.jpg - 2006:05:23 11:57:38 @@ -6497,6 +6669,23 @@ names, even if they begin with a dash (C<->). =over 5 +=item B<-diff> I + +Compare metadata in I with I. The I name may include +filename formatting codes (see the B<-w> option). All extracted tags from +the files are compared, but the extracted tags may be controlled by adding +B<-TAG> or B<--TAG> options. For example, below is a command to compare all +the same-named files in two different directories, ignoring the System tags: + + exiftool DIR1 -diff DIR2/%f.%e --system:all + +The B<-g> and B<-G> options may be used to organize the output by the +specified family of groups, with B<-G1> being the default. The B<-a> option +is implied. Adding B<-v> includes a count of the number of tags that are +the same in each group. The following text formatting options are valid +when B<-diff> is used: B<-c>, B<-charset>, B<-d>, B<-E>, B<-L>, B<-lang>, +B<-n>, B<-s>, B<-sep>, B<-struct> and B<-w>. + =item B<-geotag> I Geotag images from the specified GPS track log file. Using the B<-geotag> diff --git a/bin/lib/Image/ExifTool.pm b/bin/lib/Image/ExifTool.pm index 5a02017..3689d58 100644 --- a/bin/lib/Image/ExifTool.pm +++ b/bin/lib/Image/ExifTool.pm @@ -29,7 +29,7 @@ use vars qw($VERSION $RELEASE @ISA @EXPORT_OK %EXPORT_TAGS $AUTOLOAD @fileTypes %jpegMarker %specialTags %fileTypeLookup $testLen $exeDir %static_vars $advFmtSelf); -$VERSION = '12.98'; +$VERSION = '12.99'; $RELEASE = ''; @ISA = qw(Exporter); %EXPORT_TAGS = ( @@ -37,7 +37,7 @@ $RELEASE = ''; Public => [qw( ImageInfo AvailableOptions GetTagName GetShortcuts GetAllTags GetWritableTags GetAllGroups GetDeleteGroups GetFileType CanWrite - CanCreate AddUserDefinedTags + CanCreate AddUserDefinedTags OrderedKeys )], # exports not part of the public API, but used by ExifTool modules: DataAccess => [qw( @@ -2292,6 +2292,7 @@ sub new $$self{PATH} = [ ]; # (this too) $$self{DEL_GROUP} = { }; # lookup for groups to delete when writing $$self{SAVE_COUNT} = 0; # count calls to SaveNewValues() + $$self{NV_COUNT} = 0; # count of NEW_VALUE entries $$self{FILE_SEQUENCE} = 0; # sequence number for files when reading $$self{FILES_WRITTEN} = 0; # count of files successfully written $$self{INDENT2} = ''; # indentation of verbose messages from SetNewValue @@ -2517,6 +2518,8 @@ sub Options($$;@) # set Compact and XMPShorthand options, preserving backward compatibility my ($p, %compact); foreach $p ('Compact','XMPShorthand') { + # (allow setting from a HASH (undocumented) + ref $newVal eq 'HASH' and %compact = %{$newVal}, next; my $val = $param eq $p ? $newVal : $$options{Compact}{$p}; if (defined $val) { my @v = ($val =~ /\w+/g); @@ -4195,6 +4198,16 @@ sub CanCreate($) return 0; } +#------------------------------------------------------------------------------ +# Return list of ordered keys if available, otherwise just sort alphabetically +# Inputs: 0) hash ref +# Returns: List of ordered/sorted keys +sub OrderedKeys($) +{ + my $hash = shift; + return $$hash{_ordered_keys_} ? @{$$hash{_ordered_keys_}} : sort keys %$hash; +} + #============================================================================== # Functions below this are not part of the public API @@ -7904,8 +7917,17 @@ sub ProcessJPEG($$;$) my $seq = Get32u($segDataPt, 4); my $len = Get32u($segDataPt, 8); my $type = substr($$segDataPt, 12, 4); + # a Microsoft bug writes $len and $type incorrectly as little-endian + if ($type eq 'bmuj') { + $self->WarnOnce('Wrong byte order in C2PA APP11 JUMBF header'); + $type = 'jumb'; + $len = unpack('x8V', $$segDataPt); + # fix the header + substr($$segDataPt, 8, 8) = Set32u($len) . $type; + } my $hdrLen; if ($len == 1 and length($$segDataPt) >= 24) { + # (haven't seen this with the Microsoft bug) $len = Get64u($$segDataPt, 16); $hdrLen = 16; } else { diff --git a/bin/lib/Image/ExifTool.pod b/bin/lib/Image/ExifTool.pod index fdb6c53..f2cac03 100644 --- a/bin/lib/Image/ExifTool.pod +++ b/bin/lib/Image/ExifTool.pod @@ -332,13 +332,14 @@ L: Values of the returned hash are usually simple scalars, but a scalar reference is used to indicate binary data and an array reference may be used -to indicate a list. Also, a hash reference may be returned if the L -option is used. Lists of values are joined by commas into a single -string only if the PrintConv option is enabled and the ListJoin option is -enabled (which are the defaults). Note that binary values are not -necessarily extracted unless specifically requested, or the Binary option is -enabled and the tag is not specifically excluded. If not extracted the -value is a reference to a string of the form "Binary data ##### bytes". +to indicate a list. Also, a hash reference may be returned if the +L option is used (see the L option to obtain the hash +keys). Lists of values are joined by commas into a single string only if +the PrintConv option is enabled and the ListJoin option is enabled (which +are the defaults). Note that binary values are not necessarily extracted +unless specifically requested, or the Binary option is enabled and the tag +is not specifically excluded. If not extracted the value is a reference to +a string of the form "Binary data ##### bytes". The code below gives an example of how to handle these return values, as well as illustrating the use of other ExifTool functions: @@ -1089,7 +1090,9 @@ values in standard format). Flag to return XMP structures as hash references instead of flattening into individual tags. Has no effect when writing since both flattened and -structured tags may always be written. Possible values are: +structured tags may always be written. A special "_ordered_keys_" element +containing a list of ordered keys may exist if the structure elements are +ordered (see the L method). Possible values are: undef - (default) Same as 0 for reading, 2 for copying 0 - Read/copy flattened tags @@ -2749,6 +2752,26 @@ details on the elements of the tag information hash. =back +=head2 OrderedKeys [static] + +Return a list of ordered keys from a tag value that is a HASH reference +when the Struct option is used. + + use Image::ExifTool ':Public'; + my @keys = OrderedKeys($structRef); + +=over 4 + +=item Inputs: + +0) Structure HASH reference + +=item Return Value: + +List of ordered keys, or sorted alphabetically if not ordered. + +=back + =head1 CHARACTER ENCODINGS Certain meta information formats allow coded character sets other than plain diff --git a/bin/lib/Image/ExifTool/APP12.pm b/bin/lib/Image/ExifTool/APP12.pm index 60d4de4..8bbd181 100644 --- a/bin/lib/Image/ExifTool/APP12.pm +++ b/bin/lib/Image/ExifTool/APP12.pm @@ -14,7 +14,7 @@ use strict; use vars qw($VERSION); use Image::ExifTool qw(:DataAccess :Utils); -$VERSION = '1.13'; +$VERSION = '1.14'; sub ProcessAPP12($$$); sub ProcessDucky($$$); @@ -72,7 +72,7 @@ sub WriteDucky($$$); StrobeTime => { }, Resolution => { }, Protect => { }, - ConTake => { }, + ContTake => { }, ImageSize => { PrintConv => '$val=~tr/-/x/;$val' }, ColorMode => { }, Zoom => { }, @@ -278,6 +278,7 @@ sub ProcessAPP12($$$) $tagInfo = { Name => ucfirst $tag }; # put in Camera group if information in "Camera" section $$tagInfo{Groups} = { 2 => 'Camera' } if $section =~ /camera/i; + $et->VPrint(0, $$et{INDENT}, "[adding APP12:$$tagInfo{Name}]\n"); AddTagToTable($tagTablePtr, $tag, $tagInfo); } $et->FoundTag($tagInfo, $val); diff --git a/bin/lib/Image/ExifTool/Canon.pm b/bin/lib/Image/ExifTool/Canon.pm index e4471d0..5e82248 100644 --- a/bin/lib/Image/ExifTool/Canon.pm +++ b/bin/lib/Image/ExifTool/Canon.pm @@ -88,7 +88,7 @@ sub ProcessCTMD($$$); sub ProcessExifInfo($$$); sub SwapWords($); -$VERSION = '4.81'; +$VERSION = '4.82'; # Note: Removed 'USM' from 'L' lenses since it is redundant - PH # (or is it? Ref 32 shows 5 non-USM L-type lenses) @@ -6999,6 +6999,7 @@ my %ciMaxFocal = ( 314 => 'Canon RF 24-105mm F2.8 L IS USM Z', #42 315 => 'Canon RF-S 10-18mm F4.5-6.3 IS STM', #42 316 => 'Canon RF 35mm F1.4 L VCM', #42 + 317 => 'Canon RF-S 3.9mm F3.5 STM DUAL FISHEYE', #42 318 => 'Canon RF 28-70mm F2.8 IS STM', #42 # Note: add new RF lenses to %canonLensTypes with ID 61182 }, diff --git a/bin/lib/Image/ExifTool/Import.pm b/bin/lib/Image/ExifTool/Import.pm index 74a5184..3a2c31e 100644 --- a/bin/lib/Image/ExifTool/Import.pm +++ b/bin/lib/Image/ExifTool/Import.pm @@ -12,7 +12,7 @@ require Exporter; use vars qw($VERSION @ISA @EXPORT_OK); -$VERSION = '1.12'; +$VERSION = '1.13'; @ISA = qw(Exporter); @EXPORT_OK = qw(ReadCSV ReadJSON); @@ -87,6 +87,7 @@ sub ReadCSV($$;$$) $fileInfo{$tags[$i]} = (defined $missingValue and $vals[$i] eq $missingValue) ? undef : $vals[$i]; } + $fileInfo{_ordered_keys_} = \@tags; # figure out the file name to use if ($fileInfo{SourceFile}) { $$database{$fileInfo{SourceFile}} = \%fileInfo; @@ -173,7 +174,7 @@ Tok: for (;;) { } # see what type of object this is if ($tok eq '{') { # object (hash) - $rtnVal = { } unless defined $rtnVal; + $rtnVal = { _ordered_keys_ => [ ] } unless defined $rtnVal; for (;;) { # read "KEY":"VALUE" pairs unless (defined $key) { @@ -189,6 +190,7 @@ Tok: for (;;) { $pos = pos $$buffPt; return undef unless defined $val; $$rtnVal{$key} = $val; + push @{$$rtnVal{_ordered_keys_}}, $key; undef $key; } # scan to delimiting ',' or bounding '}' @@ -345,7 +347,9 @@ option for a list of valid character sets. These functions return an error string, or undef on success and populate the database hash with entries from the CSV or JSON file. Entries are keyed based on the SourceFile column of the CSV or JSON information, and are -stored as hash lookups of tag name/value for each SourceFile. +stored as hash lookups of tag name/value for each SourceFile. The order +of the keys (CSV column order or order in a JSON object) is stored as an +ARRAY reference in a special "_ordered_keys_" element of this hash. =back diff --git a/bin/lib/Image/ExifTool/JSON.pm b/bin/lib/Image/ExifTool/JSON.pm index c1e413a..eab5a49 100644 --- a/bin/lib/Image/ExifTool/JSON.pm +++ b/bin/lib/Image/ExifTool/JSON.pm @@ -14,7 +14,7 @@ use vars qw($VERSION); use Image::ExifTool qw(:DataAccess :Utils); use Image::ExifTool::Import; -$VERSION = '1.08'; +$VERSION = '1.09'; sub ProcessJSON($$); sub ProcessTag($$$$%); @@ -92,8 +92,7 @@ sub ProcessTag($$$$%) return unless $et->Options('Struct') > 1; } # support hashes with ordered keys - my @keys = $$val{_ordered_keys_} ? @{$$val{_ordered_keys_}} : sort keys %$val; - foreach (@keys) { + foreach (Image::ExifTool::OrderedKeys($val)) { my $tg = $tag . ((/^\d/ and $tag =~ /\d$/) ? '_' : '') . ucfirst; $tg =~ s/([^a-zA-Z])([a-z])/$1\U$2/g; ProcessTag($et, $tagTablePtr, $tg, $$val{$_}, %flags, Flat => 1); @@ -155,7 +154,7 @@ sub ProcessJSON($$) # extract tags from JSON database foreach $key (sort keys %database) { - foreach $tag (sort keys %{$database{$key}}) { + foreach $tag (Image::ExifTool::OrderedKeys($database{$key})) { my $val = $database{$key}{$tag}; # (ignore SourceFile if generated automatically by ReadJSON) next if $tag eq 'SourceFile' and defined $val and $val eq '*'; diff --git a/bin/lib/Image/ExifTool/Lytro.pm b/bin/lib/Image/ExifTool/Lytro.pm index 9d10e4e..076e0b2 100644 --- a/bin/lib/Image/ExifTool/Lytro.pm +++ b/bin/lib/Image/ExifTool/Lytro.pm @@ -15,7 +15,7 @@ use vars qw($VERSION); use Image::ExifTool qw(:DataAccess :Utils); use Image::ExifTool::Import; -$VERSION = '1.03'; +$VERSION = '1.04'; sub ExtractTags($$$); @@ -106,7 +106,7 @@ sub ExtractTags($$$) my ($et, $meta, $parent) = @_; ref $meta eq 'HASH' or $et->Warn('Invalid LFP metadata'), return; my ($key, $val, $name, $tagTablePtr); - foreach $key (sort keys %$meta) { + foreach $key (Image::ExifTool::OrderedKeys($meta)) { my $tag = $parent . ucfirst($key); foreach $val (ref $$meta{$key} eq 'ARRAY' ? @{$$meta{$key}} : $$meta{$key}) { ref $val eq 'HASH' and ExtractTags($et, $val, $tag), next; diff --git a/bin/lib/Image/ExifTool/QuickTime.pm b/bin/lib/Image/ExifTool/QuickTime.pm index 2561df2..64063cf 100644 --- a/bin/lib/Image/ExifTool/QuickTime.pm +++ b/bin/lib/Image/ExifTool/QuickTime.pm @@ -48,7 +48,7 @@ use Image::ExifTool qw(:DataAccess :Utils); use Image::ExifTool::Exif; use Image::ExifTool::GPS; -$VERSION = '3.03'; +$VERSION = '3.04'; sub ProcessMOV($$;$); sub ProcessKeys($$$); @@ -901,6 +901,7 @@ my %userDefined = ( Writable => 1, }, # '35AX'? - seen "AT" (Yada RoadCam Pro 4K dashcam) + cust => 'CustomInfo', # 70mai A810 ); # stuff seen in 'skip' atom (70mai Pro Plus+ MP4 videos) @@ -3351,6 +3352,7 @@ my %userDefined = ( PrintConv => '"Track $val"', }, # cdep (Structural Dependency QT tag?) + # fall - ? int32u, seen: 2 ); # track aperture mode dimensions atoms @@ -6744,6 +6746,13 @@ my %userDefined = ( Avoid => 1, %iso8601Date, }, + # (mdta)com.apple.quicktime.scene-illuminance + 'scene-illuminance' => { + Name => 'SceneIlluminance', + Notes => 'milli-lux', + ValueConv => 'unpack("N", $val)', + Writable => 0, # (don't make this writable because it is found in timed metadata) + }, # # seen in Apple ProRes RAW file # @@ -7393,6 +7402,7 @@ my %userDefined = ( # alac - 28 bytes # adrm - AAX DRM atom? 148 bytes # aabd - AAX unknown 17kB (contains 'aavd' strings) + # dapa - ? 203 bytes ); # AMR decode config box (ref 3) diff --git a/bin/lib/Image/ExifTool/QuickTimeStream.pl b/bin/lib/Image/ExifTool/QuickTimeStream.pl index 89f0b48..bb91939 100644 --- a/bin/lib/Image/ExifTool/QuickTimeStream.pl +++ b/bin/lib/Image/ExifTool/QuickTimeStream.pl @@ -109,7 +109,7 @@ package Image::ExifTool::QuickTime; The tags below are extracted from timed metadata in QuickTime and other formats of video files when the ExtractEmbedded option is used. Although most of these tags are combined into the single table below, ExifTool - currently reads 77 different formats of timed GPS metadata from video files. + currently reads 78 different formats of timed GPS metadata from video files. }, VARS => { NO_ID => 1 }, GPSLatitude => { PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', RawConv => '$$self{FoundGPSLatitude} = 1; $val' }, @@ -2163,9 +2163,26 @@ ($$$) push(@xtra, $1 => $2), next; } + } elsif ($$dataPt =~ m/^.{30}A.{20}VV/) { + + $debug and $et->FoundTag(GPSType => 17); + # 70mai A810 dashcam (note: no timestamps in the samples I have) + # 0000: 00 00 40 00 66 72 65 65 47 50 53 20 ed 01 00 00 [..@.freeGPS ....] + # 0010: 03 00 ed 01 00 00 00 0f 00 00 70 08 00 00 41 66 [..........p...Af] + # 0020: 13 7d 1e 3c 11 dc 03 5d 01 00 00 01 00 00 00 23 [.}.<...].......#] + # 0030: 00 00 00 56 56 00 00 00 00 00 00 00 00 00 00 00 [...VV...........] + # 0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................] + SetByteOrder('II'); + SetGPSDateTime($et, $tagTbl, $$dirInfo{SampleTime}); + $lat = Get32s($dataPt, 31) / 1e5; + $lon = Get32s($dataPt, 35) / 1e5; + $spd = Get32s($dataPt, 43); # (seems to be km/h but not confirmed) + # offset 475 - int16u=N string[N] - some sort of settings?: + # eg. "\x15\x00{pA:V,rA:V,sF:0,tF:2}" + } else { - $debug and $et->FoundTag(GPSType => 17); + $debug and $et->FoundTag(GPSType => 18); # (look for binary GPS as stored by Nextbase 512G, ref PH) # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 01 00 00 [....freeGPS x...] # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............] diff --git a/bin/lib/Image/ExifTool/TagLookup.pm b/bin/lib/Image/ExifTool/TagLookup.pm index b8ec050..a7bb271 100644 --- a/bin/lib/Image/ExifTool/TagLookup.pm +++ b/bin/lib/Image/ExifTool/TagLookup.pm @@ -8686,7 +8686,6 @@ my %tagExists = ( 'cont' => 1, 'contactnames' => 1, 'containerversion' => 1, - 'contake' => 1, 'contentbranding' => 1, 'contentdescribes' => 1, 'contentdescription' => 1, @@ -8709,6 +8708,7 @@ my %tagExists = ( 'contrastadjustment' => 1, 'contrastinfo' => 1, 'controller' => 1, + 'conttake' => 1, 'convergenceangle' => 1, 'convergencebaseimage' => 1, 'convergencedistance' => 1, @@ -8820,6 +8820,7 @@ my %tagExists = ( 'customfunctionsd30' => 1, 'customfunctionsd60' => 1, 'customfunctionsunknown' => 1, + 'custominfo' => 1, 'customsettingsd3' => 1, 'customsettingsd300' => 1, 'customsettingsd300s' => 1, @@ -12014,6 +12015,7 @@ my %tagExists = ( 'scenebalancealgorithmrevision' => 1, 'sceneclassification' => 1, 'scenecolorimetryestimates' => 1, + 'sceneilluminance' => 1, 'scheduleitemid' => 1, 'schemeinfo' => 1, 'schemetype' => 1, diff --git a/bin/lib/Image/ExifTool/TagNames.pod b/bin/lib/Image/ExifTool/TagNames.pod index 3eca97c..836df33 100644 --- a/bin/lib/Image/ExifTool/TagNames.pod +++ b/bin/lib/Image/ExifTool/TagNames.pod @@ -12,7 +12,7 @@ meta information extracted from or written to a file. =head1 TAG TABLES The tables listed below give the names of all tags recognized by ExifTool. -They contain a total of 28148 tags, with 17486 unique tag names. +They contain a total of 28150 tags, with 17488 unique tag names. B, B or B is given in the first column of each table. A B is the computer-readable equivalent of a tag name, and @@ -26163,7 +26163,7 @@ from any tags found in this segment. ------ -------- -------- 'Aperture' Aperture no 'ColorMode' ColorMode no - 'ConTake' ConTake no + 'ContTake' ContTake no 'ExpBias' ExposureCompensation no 'FNumber' FNumber no 'FWare' FirmwareVersion no @@ -29846,6 +29846,7 @@ for the official QuickTime specification. 'PICT' PreviewPICT no '_htc' HTCInfo QuickTime HTCInfo 'ardt' ARDroneFile no + 'cust' CustomInfo no 'frea' Kodak_frea Kodak frea 'free' KodakFree Kodak Free Pittasoft QuickTime Pittasoft @@ -29897,7 +29898,7 @@ for the official QuickTime specification. The tags below are extracted from timed metadata in QuickTime and other formats of video files when the ExtractEmbedded option is used. Although most of these tags are combined into the single table below, ExifTool -currently reads 77 different formats of timed GPS metadata from video files. +currently reads 78 different formats of timed GPS metadata from video files. Tag Name Writable -------- -------- @@ -30163,6 +30164,7 @@ changed via the config file. 'producer' Producer yes 'publisher' Publisher yes 'rating.user' UserRating yes + 'scene-illuminance' SceneIlluminance no 'software' Software yes 'still-image-time' StillImageTime no 'title' Title yes diff --git a/bin/lib/Image/ExifTool/WriteXMP.pl b/bin/lib/Image/ExifTool/WriteXMP.pl index b8298dd..8bd79d5 100644 --- a/bin/lib/Image/ExifTool/WriteXMP.pl +++ b/bin/lib/Image/ExifTool/WriteXMP.pl @@ -925,19 +925,31 @@ ($$;$) # get hash of all information we want to change # (sorted by tag name so alternate languages come last, but with structures # first so flattened tags may be used to override individual structure elements) - my (@tagInfoList, $delLangPath, %delLangPaths, %delAllLang, $firstNewPath); + my (@tagInfoList, @structList, $delLangPath, %delLangPaths, %delAllLang, $firstNewPath, @langTags); my $writeGroup = $$dirInfo{WriteGroup}; foreach $tagInfo (sort ByTagName $et->GetNewTagInfoList()) { next unless $et->GetGroup($tagInfo, 0) eq 'XMP'; next if $$tagInfo{Name} eq 'XMP'; # (ignore full XMP block if we didn't write it already) next if $writeGroup and $writeGroup ne $$et{NEW_VALUE}{$tagInfo}{WriteGroup}; - if ($$tagInfo{Struct}) { - unshift @tagInfoList, $tagInfo; + if ($$tagInfo{LangCode}) { + push @langTags, $tagInfo + } elsif ($$tagInfo{Struct}) { + push @structList, $tagInfo; } else { push @tagInfoList, $tagInfo; } } - foreach $tagInfo (@tagInfoList) { + if (@langTags) { + # keep original order in which lang-alt entries were added + foreach $tagInfo (sort { $$et{NEW_VALUE}{$a}{Order} <=> $$et{NEW_VALUE}{$b}{Order} } @langTags) { + if ($$tagInfo{Struct}) { + push @structList, $tagInfo; + } else { + push @tagInfoList, $tagInfo; + } + } + } + foreach $tagInfo (@structList, @tagInfoList) { my @delPaths; # list of deleted paths my $tag = $$tagInfo{TagID}; my $path = GetPropertyPath($tagInfo); diff --git a/bin/lib/Image/ExifTool/Writer.pl b/bin/lib/Image/ExifTool/Writer.pl index c69f98b..1112053 100644 --- a/bin/lib/Image/ExifTool/Writer.pl +++ b/bin/lib/Image/ExifTool/Writer.pl @@ -295,10 +295,11 @@ package Image::ExifTool; # CreateGroups - hash of all family 0 group names where tag may be created # WriteGroup - group name where information is being written (correct case) # WantGroup - group name as specified in call to function (case insensitive) -# Next - pointer to next new value hash (if more than one) +# Next - pointer to next new value hash (if more than one for this tag) # NoReplace - set if value was created with Replace=0 # AddBefore - number of list items added by a subsequent Replace=0 call -# IsNVH - Flag indicating this is a new value hash +# IsNVH - flag indicating this is a new value hash +# Order - counter to indicate the order that new value hashes were created # Shift - shift value # Save - counter used by SaveNewValues()/RestoreNewValues() # MAKER_NOTE_FIXUP - pointer to fixup if necessary for a maker note value @@ -317,7 +318,7 @@ ($;$$%) unless (defined $tag) { delete $$self{NEW_VALUE}; - $$self{SAVE_COUNT} = 0; + $$self{SAVE_COUNT} = $$self{NV_COUNT} = 0; $$self{DEL_GROUP} = { }; return 1; } @@ -1389,8 +1390,16 @@ ($$;@) return $info if $$info{Error} and $$info{Error} eq 'Error opening file'; delete $$srcExifTool{VALUE}{Error}; # delete so we can check this later - # sort tags in reverse order so we get priority tag last - my @tags = reverse sort keys %$info; + # sort tags in file order with priority tags last + my (@tags, @prio); + foreach (sort { $$srcExifTool{FILE_ORDER}{$a} <=> $$srcExifTool{FILE_ORDER}{$b} } keys %$info) { + if (/ /) { + push @tags, $_; + } else { + push @prio, $_; + } + } + push @tags, @prio; # # simply transfer all tags from source image if no tags specified # @@ -3896,6 +3905,7 @@ ($$;$$$$) TagInfo => $tagInfo, WriteGroup => $writeGroup, IsNVH => 1, # set flag so we can recognize a new value hash + Order => $$self{NV_COUNT}++, }; # add entry to our NEW_VALUE hash if ($$self{NEW_VALUE}{$tagInfo}) { @@ -4023,7 +4033,7 @@ ($$) #------------------------------------------------------------------------------ # Get list of tagInfo hashes for all new data # Inputs: 0) ExifTool object reference, 1) optional tag table pointer -# Returns: list of tagInfo hashes +# Returns: list of tagInfo hashes in no particular order sub GetNewTagInfoList($;$) { my ($self, $tagTablePtr) = @_; diff --git a/bin/lib/Image/ExifTool/XMPStruct.pl b/bin/lib/Image/ExifTool/XMPStruct.pl index b92222d..fd0e7b9 100644 --- a/bin/lib/Image/ExifTool/XMPStruct.pl +++ b/bin/lib/Image/ExifTool/XMPStruct.pl @@ -39,8 +39,7 @@ ($$;$) if (ref $obj eq 'HASH') { # support hashes with ordered keys - my @keys = $$obj{_ordered_keys_} ? @{$$obj{_ordered_keys_}} : sort keys %$obj; - foreach $key (@keys) { + foreach $key (Image::ExifTool::OrderedKeys($obj)) { my $hdr = $sfmt ? EscapeJSON($key) . ':' : $key . '='; push @vals, $hdr . SerializeStruct($et, $$obj{$key}, '}'); } @@ -218,7 +217,7 @@ ($;$) $indent or $indent = ''; if (ref $obj eq 'HASH') { print "{\n"; - foreach (sort keys %$obj) { + foreach (Image::ExifTool::OrderedKeys($obj)) { print "$indent $_ = "; DumpStruct($$obj{$_}, "$indent "); } @@ -253,8 +252,10 @@ ($$$) ref $struct eq 'HASH' or return wantarray ? (undef, "Expecting $strName structure") : undef; my ($key, $err, $warn, %copy, $rtnVal, $val); + # copy the ordered keys if they exist + $copy{_ordered_keys_} = [ ] if $$struct{_ordered_keys_}; Key: - foreach $key (keys %$struct) { + foreach $key (Image::ExifTool::OrderedKeys($struct)) { my $tag = $key; # allow trailing '#' to disable print conversion on a per-field basis my ($type, $fieldInfo); @@ -377,6 +378,7 @@ ($$$) $copy{$tag} = \@copy; } elsif ($$fieldInfo{Struct}) { $warn = "Improperly formed structure in $strName $tag"; + next; } else { $et->Sanitize(\$$struct{$key}); ($val,$err) = $et->ConvInv($$struct{$key},$fieldInfo,$tag,$strName,$type,''); @@ -387,6 +389,7 @@ ($$$) # turn this into a list if necessary $copy{$tag} = $$fieldInfo{List} ? [ $val ] : $val; } + push @{$copy{_ordered_keys_}}, $tag if $copy{_ordered_keys_}; # save ordered keys } if (%copy or not $warn) { $rtnVal = \%copy; @@ -562,7 +565,7 @@ ($$$$$$) # after all valid structure fields, which is necessary when serializing the XMP later) %$struct or $$struct{'~dummy~'} = ''; - foreach $tag (sort keys %$struct) { + foreach $tag (Image::ExifTool::OrderedKeys($struct)) { my $fieldInfo = $$strTable{$tag}; unless ($fieldInfo) { next unless $tag eq '~dummy~'; # check for dummy field @@ -652,7 +655,8 @@ ($$$$;$) my (%struct, $key); my $table = $$tagInfo{Table}; $parentID = $$tagInfo{TagID} unless $parentID; - foreach $key (keys %$value) { + $struct{_ordered_keys_} = [ ] if $$value{_ordered_keys_}; + foreach $key (Image::ExifTool::OrderedKeys($value)) { my $tagID = $parentID . ucfirst($key); my $flatInfo = $$table{$tagID}; unless ($flatInfo) { @@ -669,7 +673,11 @@ ($$$$;$) } else { $v = $et->GetValue($flatInfo, $type, $v); } - $struct{$key} = $v if defined $v; # save the converted value + if (defined $v) { + $struct{$key} = $v; # save the converted value + # maintain ordered keys if necessary + push @{$struct{_ordered_keys_}}, $key if $struct{_ordered_keys_}; + } } return \%struct; } elsif (ref $value eq 'ARRAY') { diff --git a/bin/perl-Image-ExifTool.spec b/bin/perl-Image-ExifTool.spec index 200d941..8c634b2 100644 --- a/bin/perl-Image-ExifTool.spec +++ b/bin/perl-Image-ExifTool.spec @@ -1,6 +1,6 @@ Summary: perl module for image data extraction Name: perl-Image-ExifTool -Version: 12.98 +Version: 12.99 Release: 1 License: Artistic/GPL Group: Development/Libraries/Perl diff --git a/lib/exiftool_vendored/version.rb b/lib/exiftool_vendored/version.rb index 71b8bfd..27c80da 100644 --- a/lib/exiftool_vendored/version.rb +++ b/lib/exiftool_vendored/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module ExiftoolVendored - VERSION = Gem::Version.new('12.98.0') + VERSION = Gem::Version.new('12.99.0') end