summaryrefslogtreecommitdiff
path: root/lib/Image/ExifTool.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Image/ExifTool.pm')
-rw-r--r--lib/Image/ExifTool.pm95
1 files changed, 82 insertions, 13 deletions
diff --git a/lib/Image/ExifTool.pm b/lib/Image/ExifTool.pm
index e7b445dc..a3506d49 100644
--- a/lib/Image/ExifTool.pm
+++ b/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);
-$VERSION = '12.58';
+$VERSION = '12.59';
$RELEASE = '';
@ISA = qw(Exporter);
%EXPORT_TAGS = (
@@ -75,6 +75,7 @@ sub GetAllGroups($;$);
sub GetNewGroups($);
sub GetDeleteGroups();
sub AddUserDefinedTags($%);
+sub SetAlternateFile($$$);
# non-public routines below
sub InsertTagValues($$$;$$$);
sub IsWritable($);
@@ -1824,8 +1825,11 @@ my %systemTagsNotes = (
ImageDataMD5 => {
Notes => q{
MD5 of image data. Generated only if specifically requested for JPEG and
- TIFF-based images, except Panasonic raw for now. Includes image data,
- OtherImage and JpgFromRaw in the MD5, but not ThumbnailImage or PreviewImage
+ TIFF-based images, CR3, MRW and PNG images, MOV/MP4 videos, and RIFF-based
+ files. The MD5 includes the main image data, plus JpgFromRaw/OtherImage for
+ some formats, but does not include ThumbnailImage or PreviewImage. Includes
+ video and audio data for MOV/MP4. The L<XMP-et:OriginalImageMD5
+ tag|XMP.html#ExifTool> provides a place to store these values in the file.
},
},
);
@@ -2052,6 +2056,7 @@ sub new
$$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
+ $$self{ALT_EXIFTOOL} = { }; # alternate exiftool objects
# initialize our new groups for writing
$self->SetNewGroups(@defaultWriteGroups);
@@ -2496,6 +2501,23 @@ sub ExtractInfo($;@)
}
}
++$$self{FILE_SEQUENCE}; # count files read
+ # extract information from alternate files if necessary
+ my ($g8, $altExifTool);
+ foreach $g8 (keys %{$$self{ALT_EXIFTOOL}}) {
+ $altExifTool = $$self{ALT_EXIFTOOL}{$g8};
+ next if $$altExifTool{DID_EXTRACT}; # avoid extracting twice
+ $$altExifTool{OPTIONS} = $$self{OPTIONS};
+ $$altExifTool{GLOBAL_TIME_OFFSET} = $$self{GLOBAL_TIME_OFFSET};
+ $$altExifTool{REQ_TAG_LOOKUP} = $$self{REQ_TAG_LOOKUP};
+ $altExifTool->ExtractInfo($$altExifTool{ALT_FILE});
+ # set family 8 group name for all tags
+ foreach (keys %{$$altExifTool{VALUE}}) {
+ my $ex = $$altExifTool{TAG_EXTRA}{$_};
+ $ex or $ex = $$altExifTool{TAG_EXTRA}{$_} = { };
+ $$ex{G8} = $g8;
+ }
+ $$altExifTool{DID_EXTRACT} = 1;
+ }
}
my $filename = $$self{FILENAME}; # image file name ('' if already open)
@@ -3520,6 +3542,10 @@ sub GetGroup($$;$)
$groups[6] = $$ex{G6};
}
}
+ if ($$ex{G8}) {
+ $groups[7] = '';
+ $groups[8] = $$ex{G8};
+ }
# generate tag ID group names unless obviously not needed
unless ($noID) {
my $id = $$tagInfo{KeysID} || $$tagInfo{TagID};
@@ -4153,7 +4179,11 @@ sub SplitFileName($)
} else {
($name = $file) =~ tr/\\/\//;
# remove path
- $dir = length($1) ? $1 : '/' if $name =~ s/(.*)\///;
+ if ($name =~ s/(.*)\///) {
+ $dir = length($1) ? $1 : '/';
+ } else {
+ $dir = '.';
+ }
}
return ($dir, $name);
}
@@ -4594,11 +4624,18 @@ sub SetFoundTags($)
my $tagHash = $$self{VALUE};
my $reqTag;
foreach $reqTag (@$reqTags) {
- my (@matches, $group, $allGrp, $allTag, $byValue);
+ my (@matches, $group, $allGrp, $allTag, $byValue, $g8, $altOrder);
+ my $et = $self;
if ($reqTag =~ /^(.*):(.+)/) {
($group, $tag) = ($1, $2);
if ($group =~ /^(\*|all)$/i) {
$allGrp = 1;
+ } elsif ($reqTag =~ /\bfile(\d+):/i) {
+ $g8 = "File$1";
+ $altOrder = ($1 + 1) * 100000;
+ $et = $$self{ALT_EXIFTOOL}{$g8} || $self;
+ $fileOrder = $$et{FILE_ORDER};
+ $tagHash = $$et{VALUE};
} elsif ($group !~ /^[-\w:]*$/) {
$self->Warn("Invalid group name '${group}'");
$group = 'invalid';
@@ -4640,7 +4677,7 @@ sub SetFoundTags($)
}
if (defined $group and not $allGrp) {
# keep only specified group
- @matches = $self->GroupMatches($group, \@matches);
+ @matches = $et->GroupMatches($group, \@matches);
next unless @matches or not $allTag;
}
if (@matches > 1) {
@@ -4649,9 +4686,9 @@ sub SetFoundTags($)
# return only the highest priority tag unless duplicates wanted
unless ($doDups or $allTag or $allGrp) {
$tag = shift @matches;
- my $oldPriority = $$self{PRIORITY}{$tag} || 1;
+ my $oldPriority = $$et{PRIORITY}{$tag} || 1;
foreach (@matches) {
- my $priority = $$self{PRIORITY}{$_};
+ my $priority = $$et{PRIORITY}{$_};
$priority = 1 unless defined $priority;
next unless $priority >= $oldPriority;
$tag = $_;
@@ -4665,6 +4702,22 @@ sub SetFoundTags($)
# bogus file order entry to avoid warning if sorting in file order
$$self{FILE_ORDER}{$matches[0]} = 9999;
}
+ # copy over necessary information for tags from alternate files
+ if ($g8) {
+ my $tag;
+ foreach $tag (@matches) {
+ my $vtag = $tag;
+ $vtag =~ s/( |$)/ #[$g8]/;
+ $$self{VALUE}{$vtag} = $$et{VALUE}{$tag};
+ $$self{TAG_INFO}{$vtag} = $$et{TAG_INFO}{$tag};
+ $$self{TAG_EXTRA}{$vtag} = $$et{TAG_EXTRA}{$tag} || { };
+ $$self{FILE_ORDER}{$vtag} = ($$et{FILE_ORDER}{$tag} || 0) + $altOrder;
+ $tag = $vtag;
+ }
+ # restore variables to original values for main file
+ $fileOrder = $$self{FILE_ORDER};
+ $tagHash = $$self{VALUE};
+ }
# save indices of tags extracted by value
push @byValue, scalar(@$rtnTags) .. (scalar(@$rtnTags)+scalar(@matches)-1) if $byValue;
# save indices of wildcard tags
@@ -6347,7 +6400,6 @@ sub ProcessJPEG($$)
{
local $_;
my ($self, $dirInfo) = @_;
- my ($ch, $s, $length);
my $options = $$self{OPTIONS};
my $verbose = $$options{Verbose};
my $out = $$options{TextOut};
@@ -6356,12 +6408,16 @@ sub ProcessJPEG($$)
my $req = $$self{REQ_TAG_LOOKUP};
my $htmlDump = $$self{HTML_DUMP};
my %dumpParms = ( Out => $out );
+ my ($ch, $s, $length, $md5, $md5size);
my ($success, $wantTrailer, $trailInfo, $foundSOS, %jumbfChunk);
my (@iccChunk, $iccChunkCount, $iccChunksTotal, @flirChunk, $flirCount, $flirTotal);
- my ($preview, $scalado, @dqt, $subSampling, $dumpEnd, %extendedXMP, $md5);
+ my ($preview, $scalado, @dqt, $subSampling, $dumpEnd, %extendedXMP);
# get pointer to MD5 object if it exists and we are the top-level JPEG
- $md5 = $$self{ImageDataMD5} if $$self{FILE_TYPE} eq 'JPEG' and not $$self{DOC_NUM};
+ if ($$self{FILE_TYPE} eq 'JPEG' and not $$self{DOC_NUM}) {
+ $md5 = $$self{ImageDataMD5};
+ $md5size = 0;
+ }
# check to be sure this is a valid JPG (or J2C, or EXV) file
return 0 unless $raf->Read($s, 2) == 2 and $s =~ /^\xff[\xd8\x4f\x01]/;
@@ -6441,8 +6497,19 @@ sub ProcessJPEG($$)
$nextSegPos = $raf->Tell();
$len -= 4; # subtract size of length word
last unless $raf->Seek($len, 1);
- } elsif ($md5 and defined $marker and ($marker == 0x00 or $marker == 0xda)) {
- $md5->add($buff); # (note: this includes the terminating 0xff's)
+ } elsif ($md5 and defined $marker and ($marker == 0x00 or $marker == 0xda or
+ ($marker >= 0xd0 and $marker <= 0xd7)))
+ {
+ # calculate MD5 for image data (includes leading ff d9 but not trailing ff da)
+ $md5->add("\xff" . chr($marker));
+ my $n = $skipped - (length($buff) - 1); # number of extra 0xff's
+ if (not $n) {
+ $buff = substr($buff, 0, -1); # remove trailing 0xff
+ } elsif ($n > 1) {
+ $buff .= "\xff" x ($n - 1); # add back extra 0xff's
+ }
+ $md5->add($buff);
+ $md5size += $skipped + 2;
}
# read second segment too if this was the first
next unless defined $marker;
@@ -7457,6 +7524,8 @@ sub ProcessJPEG($$)
delete $extendedXMP{$guid};
}
}
+ # print verbose MD5 message if necessary
+ print $out "$$self{INDENT}(ImageDataMD5: $md5size bytes of JPEG image data)\n" if $md5size and $verbose;
# calculate JPEGDigest if requested
if (@dqt) {
require Image::ExifTool::JPEGDigest;