diff options
Diffstat (limited to 'lib/Image/ExifTool/FujiFilm.pm')
-rw-r--r-- | lib/Image/ExifTool/FujiFilm.pm | 162 |
1 files changed, 142 insertions, 20 deletions
diff --git a/lib/Image/ExifTool/FujiFilm.pm b/lib/Image/ExifTool/FujiFilm.pm index be878e91..fdfb2df7 100644 --- a/lib/Image/ExifTool/FujiFilm.pm +++ b/lib/Image/ExifTool/FujiFilm.pm @@ -31,10 +31,11 @@ use vars qw($VERSION); use Image::ExifTool qw(:DataAccess :Utils); use Image::ExifTool::Exif; -$VERSION = '1.90'; +$VERSION = '1.91'; sub ProcessFujiDir($$$); sub ProcessFaceRec($$$); +sub ProcessMRAW($$$); # the following RAF version numbers have been tested for writing: # (as of ExifTool 11.70, this lookup is no longer used if the version number is numerical) @@ -42,11 +43,12 @@ my %testedRAF = ( '0100' => 'E550, E900, F770, S5600, S6000fd, S6500fd, HS10/HS11, HS30, S200EXR, X100, XF1, X-Pro1, X-S1, XQ2 Ver1.00, X-T100, GFX 50R, XF10', '0101' => 'X-E1, X20 Ver1.01, X-T3', '0102' => 'S100FS, X10 Ver1.02', - '0103' => 'IS Pro Ver1.03', + '0103' => 'IS Pro and X-T5 Ver1.03', '0104' => 'S5Pro Ver1.04', '0106' => 'S5Pro Ver1.06', '0111' => 'S5Pro Ver1.11', '0114' => 'S9600 Ver1.00', + '0120' => 'X-T4 Ver1.20', '0132' => 'X-T2 Ver1.32', '0144' => 'X100T Ver1.44', '0159' => 'S2Pro Ver1.00', @@ -1454,6 +1456,27 @@ my %faceCategories = ( }, ); +# tags in RAF M-RAW header (ref PH) +%Image::ExifTool::FujiFilm::MRAW = ( + PROCESS_PROC => \&ProcessMRAW, + GROUPS => { 0 => 'RAF', 1 => 'M-RAW', 2 => 'Image' }, + FORMAT => 'int32u', + TAG_PREFIX => 'MRAW', + NOTES => q{ + Tags extracted from the M-RAW header of multi-image RAF files. The family 1 + group name for these tags is "M-RAW". + }, + 1 => { Name => 'RawImageNumber', Format => 'int32u' }, + # 3 - seen "0 100", "-300 100" and "300 100" for a sequence of 3 images + 3 => { Name => 'MRAW_0x0003', Format => 'rational32s', Unknown => 1, Hidden => 1, PrintConv => 'sprintf("%+.2f",$val)' }, + # 4 - (same value as 3 in all my samples) + 4 => { Name => 'MRAW_0x0004', Format => 'rational32s', Unknown => 1, Hidden => 1, PrintConv => 'sprintf("%+.2f",$val)' }, + # 5 - seen "10 1600", "10 6800", "10 200", "10 35000" etc + # 6 - seen "450 100", "400 100" (all images in RAF have same value) + # 7 - seen 200, 125, 250, 2000 + # 8 - seen 0 +); + #------------------------------------------------------------------------------ # decode information from FujiFilm face recognition information # Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref @@ -1525,7 +1548,7 @@ sub ProcessFujiDir($$$) $raf->Read($buff, 4) or return 0; my $entries = unpack 'N', $buff; $entries < 256 or return 0; - $et->Options('Verbose') and $et->VerboseDir('Fuji', $entries); + $et->VerboseDir('Fuji', $entries); SetByteOrder('MM'); my $pos = $offset + 4; for ($index=0; $index<$entries; ++$index) { @@ -1558,6 +1581,51 @@ sub ProcessFujiDir($$$) } #------------------------------------------------------------------------------ +# get information from FujiFilm M-RAW header +# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref +# Returns: 1 if this was a valid M-RAW headerx +sub ProcessMRAW($$$) +{ + my ($et, $dirInfo, $tagTablePtr) = @_; + my $dataPt = $$dirInfo{DataPt}; + my $dataPos = $$dirInfo{DataPos}; + my $dataLen = length $$dataPt; + $dataLen < 44 and $et->Warn('Short M-RAW header'), return 0; + $$dataPt =~ /^FUJIFILMM-RAW / or $et->Warn('Bad M-RAW header'), return 0; + my $ver = substr($$dataPt, 16, 4); + $et->VerboseDir("M-RAW $ver", undef, $dataLen); + SetByteOrder('MM'); + my $size = Get16u($dataPt, 40); # (these are just a guess - PH) + my $num = Get16u($dataPt, 42); + my $pos = 44; + my ($i, $n); + for ($n=0; ; ++$n) { + $pos += 16; # skip offset/size fields + my $end = $pos + $size; + last if $end > $dataLen; + $$et{DOC_NUM} = ++$$et{DOC_COUNT} if $pos > 60; + $et->VPrint(0, "$$et{INDENT}(Raw image $n parameters: $size bytes, $num entries)\n"); + for ($i=0; $i<$num; ++$i) { + last if $pos + 4 > $end; + # (don't know what the byte at the current $pos is for, value = 0x20) + my $tag = Get8u($dataPt, $pos+1); + my $size = Get16u($dataPt, $pos+2); + $pos += 4; + last if $pos + $size > $end; + $et->HandleTag($tagTablePtr, $tag, undef, + DataPt => $dataPt, + DataPos => $dataPos, + Start => $pos, + Size => $size, + ); + $pos += $size; + } + } + delete $$et{DOC_NUM}; + return 1; +} + +#------------------------------------------------------------------------------ # write information to FujiFilm RAW file (RAF) # Inputs: 0) ExifTool object reference, 1) dirInfo reference # Returns: 1 on success, 0 if this wasn't a valid RAF file, or -1 on write error @@ -1572,10 +1640,12 @@ sub WriteRAF($$) my $ver = substr($hdr, 0x3c, 4); $ver =~ /^\d{4}$/ or $testedRAF{$ver} or return 0; + # get position and size of M-RAW header + my ($mpos, $mlen) = unpack('x72NN', $hdr); # get the position and size of embedded JPEG my ($jpos, $jlen) = unpack('x84NN', $hdr); # check to be sure the JPEG starts in the expected location - if ($jpos > 0x94 or $jpos < 0x68 or $jpos & 0x03) { + if (($mpos > 0x94 or $jpos > 0x94 + $mlen) or $jpos < 0x68 or $jpos & 0x03) { $et->Error("Unsupported or corrupted RAF image (version $ver)"); return 1; } @@ -1589,6 +1659,27 @@ sub WriteRAF($$) $et->Error('Error reading RAF meta information'); return 1; } + if ($mpos) { + if ($mlen != 0x11c) { + $et->Error('Unsupported M-RAW header (please submit sample for testing)'); + return 1; + } + # read M-RAW header and add to file header + my $mraw; + unless ($raf->Seek($mpos, 0) and $raf->Read($mraw, $mlen) == $mlen) { + $et->Error('Error reading M-RAW header'); + return 1; + } + $hdr .= $mraw; + # verify that the 1st raw image offset is zero, and that the 1st raw image + # length is the same as the 2nd raw image offset + unless (substr($hdr, 0xc0, 8) eq "\0\0\0\0\0\0\0\0" and + substr($hdr, 0xc8, 8) eq substr($hdr, 0x110, 8)) + { + $et->Error('Unexpected layout of M-RAW header'); + return 1; + } + } # use same write directories as JPEG $et->InitWriteDirs('JPEG'); # rewrite the embedded JPEG in memory @@ -1633,12 +1724,28 @@ sub WriteRAF($$) } # calculate offset difference due to change in JPEG size my $ptrDiff = length($outJpeg) + length($pad) - ($jlen + $oldPadLen); - # update necessary pointers in header - foreach $offset (0x5c, 0x64, 0x78, 0x80) { + # update necessary pointers in header (0xcc and higher in M-RAW header) + foreach $offset (0x5c, 0x64, 0x78, 0x80, 0xcc, 0x114, 0x164) { last if $offset >= $jpos; # some versions have a short header my $oldPtr = Get32u(\$hdr, $offset); next unless $oldPtr; # don't update if pointer is zero - Set32u($oldPtr + $ptrDiff, \$hdr, $offset); + my $newPtr = $oldPtr + $ptrDiff; + if ($newPtr < 0 or $newPtr > 0xffffffff) { + $offset < 0xcc and $et->Error('Invalid offset in RAF header'), return 1; + # assume values at 0xcc and greater are 8-byte integers (NC) + # and adjust high word if necessary + my $high = Get32u(\$hdr, $offset-4); + if ($newPtr < 0) { + $high -= 1; + $newPtr += 0xffffffff + 1; + $high < 0 and $et->Error('RAF header offset error'), return 1; + } else { + $high += 1; + $newPtr -= 0xffffffff + 1; + } + Set32u($high, \$hdr, $offset-4); + } + Set32u($newPtr, \$hdr, $offset); } # write the new header my $outfile = $$dirInfo{OutFile}; @@ -1668,11 +1775,14 @@ sub ProcessRAF($$) my $raf = $$dirInfo{RAF}; $raf->Read($buff,0x5c) == 0x5c or return 0; $buff =~ /^FUJIFILM/ or return 0; + # get position and size of M-RAW header and jpeg preview + my ($mpos, $mlen) = unpack('x72NN', $buff); my ($jpos, $jlen) = unpack('x84NN', $buff); $jpos & 0x8000 and return 0; - $raf->Seek($jpos, 0) or return 0; - $raf->Read($jpeg, $jlen) == $jlen or return 0; - + if ($jpos) { + $raf->Seek($jpos, 0) or return 0; + $raf->Read($jpeg, $jlen) == $jlen or return 0; + } $et->SetFileType(); $et->FoundTag('RAFVersion', substr($buff, 0x3c, 4)); @@ -1681,15 +1791,16 @@ sub ProcessRAF($$) Parent => 'RAF', RAF => new File::RandomAccess(\$jpeg), ); - $$et{BASE} += $jpos; - my $rtnVal = $et->ProcessJPEG(\%dirInfo); - $$et{BASE} -= $jpos; - $et->FoundTag('PreviewImage', \$jpeg) if $rtnVal; - + if ($jpos) { + $$et{BASE} += $jpos; + my $ok = $et->ProcessJPEG(\%dirInfo); + $$et{BASE} -= $jpos; + $et->FoundTag('PreviewImage', \$jpeg) if $ok; + } # extract information from Fuji RAF and TIFF directories my ($rafNum, $ifdNum) = ('',''); - foreach $offset (0x5c, 0x64, 0x78, 0x80) { - last if $offset >= $jpos; + foreach $offset (0x48, 0x5c, 0x64, 0x78, 0x80) { + last if $jpos and $offset >= $jpos; unless ($raf->Seek($offset, 0) and $raf->Read($buff, 8)) { $warn = 1; last; @@ -1711,6 +1822,14 @@ sub ProcessRAF($$) } delete $$et{SET_GROUP1}; $ifdNum = ($ifdNum || 1) + 1; + } elsif ($offset == 0x48) { + $$et{VALUE}{FileType} .= ' (M-RAW)'; + if ($raf->Seek($start, 0) and $raf->Read($buff, $mlen) == $mlen) { + my $tbl = GetTagTable('Image::ExifTool::FujiFilm::MRAW'); + $et->ProcessDirectory({ DataPt => \$buff, DataPos => $start, DirName => 'M-RAW' }, $tbl); + } else { + $et->Warn('Error reading M-RAW header'); + } } else { # parse RAF directory %dirInfo = ( @@ -1719,14 +1838,17 @@ sub ProcessRAF($$) ); $$et{SET_GROUP1} = "RAF$rafNum"; my $tagTablePtr = GetTagTable('Image::ExifTool::FujiFilm::RAF'); - $et->ProcessDirectory(\%dirInfo, $tagTablePtr) or $warn = 1; + if ($et->ProcessDirectory(\%dirInfo, $tagTablePtr)) { + $rafNum = ($rafNum || 1) + 1; + } else { + $warn = 1; + } delete $$et{SET_GROUP1}; - $rafNum = ($rafNum || 1) + 1; } } $warn and $et->Warn('Possibly corrupt RAF information'); - return $rtnVal; + return 1; } 1; # end |