<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#------------------------------------------------------------------------------
# File:         FujiFilm.pm
#
# Description:  Read/write FujiFilm maker notes and RAF images
#
# Revisions:    11/25/2003 - P. Harvey Created
#               11/14/2007 - PH Added abilty to write RAF images
#
# References:   1) http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html
#               2) http://homepage3.nifty.com/kamisaka/makernote/makernote_fuji.htm (2007/09/11)
#               3) Michael Meissner private communication
#               4) Paul Samuelson private communication (S5)
#               5) http://www.cybercom.net/~dcoffin/dcraw/
#               6) http://forums.dpreview.com/forums/readflat.asp?forum=1012&amp;thread=31350384
#                  and http://forum.photome.de/viewtopic.php?f=2&amp;t=353&amp;p=742#p740
#               JD) Jens Duttke private communication
#------------------------------------------------------------------------------

package Image::ExifTool::FujiFilm;

use strict;
use vars qw($VERSION);
use Image::ExifTool qw(:DataAccess :Utils);
use Image::ExifTool::Exif;

$VERSION = '1.31';

sub ProcessFujiDir($$$);
sub ProcessFaceRec($$$);

# the following RAF version numbers have been tested for writing:
my %testedRAF = (
    '0100' =&gt; 'E550, E900, S5600, S6000fd, S6500fd, HS10/HS11, S200EXR, X100 (all Ver1.00)',
    '0102' =&gt; 'S100FS Ver1.02',
    '0104' =&gt; 'S5Pro Ver1.04',
    '0106' =&gt; 'S5Pro Ver1.06',
    '0111' =&gt; 'S5Pro Ver1.11',
    '0114' =&gt; 'S9600 Ver1.00',
    '0159' =&gt; 'S2Pro Ver1.00',
    '0212' =&gt; 'S3Pro Ver2.12',
    '0216' =&gt; 'S3Pro Ver2.16', # (NC)
    '0218' =&gt; 'S3Pro Ver2.18',
    '0264' =&gt; 'F700  Ver2.00',
    '0266' =&gt; 'S9500 Ver1.01',
    '0269' =&gt; 'S9500 Ver1.02',
    '0271' =&gt; 'S3Pro Ver2.71', # UV/IR model?
    '0712' =&gt; 'S5000 Ver3.00',
    '0716' =&gt; 'S5000 Ver3.00', # (yes, 2 RAF versions with the same firmware version)
);

my %faceCategories = (
    Format =&gt; 'int8u',
    PrintConv =&gt; { BITMASK =&gt; {
        1 =&gt; 'Partner',
        2 =&gt; 'Family',
        3 =&gt; 'Friend',
    }},
);

# FujiFilm MakerNotes tags
%Image::ExifTool::FujiFilm::Main = (
    WRITE_PROC =&gt; \&amp;Image::ExifTool::Exif::WriteExif,
    CHECK_PROC =&gt; \&amp;Image::ExifTool::Exif::CheckExif,
    WRITABLE =&gt; 1,
    GROUPS =&gt; { 0 =&gt; 'MakerNotes', 2 =&gt; 'Camera' },
    0x0 =&gt; {
        Name =&gt; 'Version',
        Writable =&gt; 'undef',
    },
    0x0010 =&gt; { #PH (how does this compare to actual serial number?)
        Name =&gt; 'InternalSerialNumber',
        Writable =&gt; 'string',
        Notes =&gt; q{
            this number is unique, and contains the date of manufacture, but doesn't
            necessarily correspond to the camera body number -- this needs to be checked
        },
        # ie)  "FPX20017035 592D31313034060427796060110384"
        # "FPX 20495643     592D313335310701318AD010110047" (F40fd)
        #                               yymmdd
        PrintConv =&gt; q{
            return $val unless $val=~/^(.*)(\d{2})(\d{2})(\d{2})(.{12})$/;
            my $yr = $2 + ($2 &lt; 70 ? 2000 : 1900);
            return "$1 $yr:$3:$4 $5";
        },
        PrintConvInv =&gt; '$_=$val; s/ (19|20)(\d{2}):(\d{2}):(\d{2}) /$2$3$4/; $_',
    },
    0x1000 =&gt; {
        Name =&gt; 'Quality',
        Writable =&gt; 'string',
    },
    0x1001 =&gt; {
        Name =&gt; 'Sharpness',
        Flags =&gt; 'PrintHex',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0x01 =&gt; 'Soft',
            0x02 =&gt; 'Soft2',
            0x03 =&gt; 'Normal',
            0x04 =&gt; 'Hard',
            0x05 =&gt; 'Hard2',
            0x82 =&gt; 'Medium Soft', #2
            0x84 =&gt; 'Medium Hard', #2
            0x8000 =&gt; 'Film Simulation', #2
            0xffff =&gt; 'n/a', #2
        },
    },
    0x1002 =&gt; {
        Name =&gt; 'WhiteBalance',
        Flags =&gt; 'PrintHex',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0x0   =&gt; 'Auto',
            0x100 =&gt; 'Daylight',
            0x200 =&gt; 'Cloudy',
            0x300 =&gt; 'Daylight Fluorescent',
            0x301 =&gt; 'Day White Fluorescent',
            0x302 =&gt; 'White Fluorescent',
            0x303 =&gt; 'Warm White Fluorescent', #2/PH (S5)
            0x304 =&gt; 'Living Room Warm White Fluorescent', #2/PH (S5)
            0x400 =&gt; 'Incandescent',
            0x500 =&gt; 'Flash', #4
            0xf00 =&gt; 'Custom',
            0xf01 =&gt; 'Custom2', #2
            0xf02 =&gt; 'Custom3', #2
            0xf03 =&gt; 'Custom4', #2
            0xf04 =&gt; 'Custom5', #2
            # 0xfe0 =&gt; 'Gray Point?', #2
            0xff0 =&gt; 'Kelvin', #4
        },
    },
    0x1003 =&gt; {
        Name =&gt; 'Saturation',
        Flags =&gt; 'PrintHex',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0x0   =&gt; 'Normal',
            0x080 =&gt; 'Medium High', #2
            0x100 =&gt; 'High',
            0x180 =&gt; 'Medium Low', #2
            0x200 =&gt; 'Low',
            0x300 =&gt; 'None (B&amp;W)', #2
            0x301 =&gt; 'B&amp;W Green Filter', #PH (X100)
            0x302 =&gt; 'B&amp;W Yellow Filter', #PH (X100)
            0x303 =&gt; 'B&amp;W Blue Filter', #PH (X100)
            0x310 =&gt; 'B&amp;W Sepia', #PH (X100)
            0x8000 =&gt; 'Film Simulation', #2
        },
    },
    0x1004 =&gt; {
        Name =&gt; 'Contrast',
        Flags =&gt; 'PrintHex',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0x0   =&gt; 'Normal',
            0x080 =&gt; 'Medium High', #2
            0x100 =&gt; 'High',
            0x180 =&gt; 'Medium Low', #2
            0x200 =&gt; 'Low',
            0x8000 =&gt; 'Film Simulation', #2
        },
    },
    0x1005 =&gt; { #4
        Name =&gt; 'ColorTemperature',
        Writable =&gt; 'int16u',
    },
    0x1006 =&gt; { #JD
        Name =&gt; 'Contrast',
        Flags =&gt; 'PrintHex',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0x0   =&gt; 'Normal',
            0x100 =&gt; 'High',
            0x300 =&gt; 'Low',
        },
    },
    0x100a =&gt; { #2
        Name =&gt; 'WhiteBalanceFineTune',
        Writable =&gt; 'int32s',
        Count =&gt; 2,
        PrintConv =&gt; 'sprintf("Red %+d, Blue %+d", split(" ", $val))',
        PrintConvInv =&gt; 'my @v=($val=~/-?\d+/g);"@v"',
    },
    0x100b =&gt; { #2
        Name =&gt; 'NoiseReduction',
        Flags =&gt; 'PrintHex',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0x40 =&gt; 'Low',
            0x80 =&gt; 'Normal',
            0x100 =&gt; 'n/a', #PH (NC) (all X100 samples)
        },
    },
    0x100e =&gt; { #PH (X100)
        Name =&gt; 'HighISONoiseReduction',
        Flags =&gt; 'PrintHex',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0x000 =&gt; 'Normal',
            0x100 =&gt; 'Strong',
            0x200 =&gt; 'Weak',
        },
    },
    0x1010 =&gt; {
        Name =&gt; 'FujiFlashMode',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0 =&gt; 'Auto',
            1 =&gt; 'On',
            2 =&gt; 'Off',
            3 =&gt; 'Red-eye reduction',
            4 =&gt; 'External', #JD
        },
    },
    0x1011 =&gt; {
        Name =&gt; 'FlashExposureComp', #JD
        Writable =&gt; 'rational64s',
    },
    0x1020 =&gt; {
        Name =&gt; 'Macro',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0 =&gt; 'Off',
            1 =&gt; 'On',
        },
    },
    0x1021 =&gt; {
        Name =&gt; 'FocusMode',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0 =&gt; 'Auto',
            1 =&gt; 'Manual',
        },
    },
    0x1023 =&gt; { #2
        Name =&gt; 'FocusPixel',
        Writable =&gt; 'int16u',
        Count =&gt; 2,
    },
    0x1030 =&gt; {
        Name =&gt; 'SlowSync',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0 =&gt; 'Off',
            1 =&gt; 'On',
        },
    },
    0x1031 =&gt; {
        Name =&gt; 'PictureMode',
        Flags =&gt; 'PrintHex',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0x0 =&gt; 'Auto',
            0x1 =&gt; 'Portrait',
            0x2 =&gt; 'Landscape',
            0x3 =&gt; 'Macro', #JD
            0x4 =&gt; 'Sports',
            0x5 =&gt; 'Night Scene',
            0x6 =&gt; 'Program AE',
            0x7 =&gt; 'Natural Light', #3
            0x8 =&gt; 'Anti-blur', #3
            0x9 =&gt; 'Beach &amp; Snow', #JD
            0xa =&gt; 'Sunset', #3
            0xb =&gt; 'Museum', #3
            0xc =&gt; 'Party', #3
            0xd =&gt; 'Flower', #3
            0xe =&gt; 'Text', #3
            0xf =&gt; 'Natural Light &amp; Flash', #3
            0x10 =&gt; 'Beach', #3
            0x11 =&gt; 'Snow', #3
            0x12 =&gt; 'Fireworks', #3
            0x13 =&gt; 'Underwater', #3
            0x16 =&gt; 'Panorama', #PH (X100)
            0x100 =&gt; 'Aperture-priority AE',
            0x200 =&gt; 'Shutter speed priority AE',
            0x300 =&gt; 'Manual',
        },
    },
# this usually has a value of 1
#    0x1032 =&gt; { #2
#        Name =&gt; 'ShutterCount',
#        Writable =&gt; 'int16u',
#    },
    0x1033 =&gt; { #6
        Name =&gt; 'EXRAuto',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0 =&gt; 'Auto',
            1 =&gt; 'Manual',
        },
    },
    0x1034 =&gt; { #6
        Name =&gt; 'EXRMode',
        Writable =&gt; 'int16u',
        PrintHex =&gt; 1,
        PrintConv =&gt; {
            0x100 =&gt; 'HR (High Resolution)',
            0x200 =&gt; 'SN (Signal to Noise priority)',
            0x300 =&gt; 'DR (Dynamic Range priority)',
        },
    },
    0x1100 =&gt; {
        Name =&gt; 'AutoBracketing',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0 =&gt; 'Off',
            1 =&gt; 'On',
            2 =&gt; 'No flash &amp; flash', #3
        },
    },
    0x1101 =&gt; {
        Name =&gt; 'SequenceNumber',
        Writable =&gt; 'int16u',
    },
    0x1210 =&gt; { #2
        Name =&gt; 'ColorMode',
        Writable =&gt; 'int16u',
        PrintHex =&gt; 1,
        PrintConv =&gt; {
            0x00 =&gt; 'Standard',
            0x10 =&gt; 'Chrome',
            0x30 =&gt; 'B &amp; W',
        },
    },
    0x1300 =&gt; {
        Name =&gt; 'BlurWarning',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0 =&gt; 'None',
            1 =&gt; 'Blur Warning',
        },
    },
    0x1301 =&gt; {
        Name =&gt; 'FocusWarning',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0 =&gt; 'Good',
            1 =&gt; 'Out of focus',
        },
    },
    0x1302 =&gt; {
        Name =&gt; 'ExposureWarning',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            0 =&gt; 'Good',
            1 =&gt; 'Bad exposure',
        },
    },
    0x1304 =&gt; { #PH
        Name =&gt; 'GEImageSize',
        Condition =&gt; '$$self{Make} =~ /^GENERAL IMAGING/',
        Format =&gt; 'string',
        Notes =&gt; 'GE models only',
    },
    0x1400 =&gt; { #2
        Name =&gt; 'DynamicRange',
        Writable =&gt; 'int16u',
        PrintConv =&gt; {
            1 =&gt; 'Standard',
            3 =&gt; 'Wide',
            # the S5Pro has 100%(STD),130%,170%,230%(W1),300%,400%(W2) - PH
        },
    },
    0x1401 =&gt; { #2 (this doesn't seem to work for the X100 - PH)
        Name =&gt; 'FilmMode',
        Writable =&gt; 'int16u',
        PrintHex =&gt; 1,
        PrintConv =&gt; {
            0x000 =&gt; 'F0/Standard',
            0x100 =&gt; 'F1/Studio Portrait',
            0x110 =&gt; 'F1a/Studio Portrait Enhanced Saturation',
            0x120 =&gt; 'F1b/Studio Portrait Smooth Skin Tone',
            0x130 =&gt; 'F1c/Studio Portrait Increased Sharpness',
            0x200 =&gt; 'F2/Fujichrome',
            0x300 =&gt; 'F3/Studio Portrait Ex',
            0x400 =&gt; 'F4/Velvia',
        },
    },
    0x1402 =&gt; { #2
        Name =&gt; 'DynamicRangeSetting',
        Writable =&gt; 'int16u',
        PrintHex =&gt; 1,
        PrintConv =&gt; {
            0x000 =&gt; 'Auto (100-400%)',
            0x001 =&gt; 'Manual', #(ref http://forum.photome.de/viewtopic.php?f=2&amp;t=353)
            0x100 =&gt; 'Standard (100%)',
            0x200 =&gt; 'Wide1 (230%)',
            0x201 =&gt; 'Wide2 (400%)',
            0x8000 =&gt; 'Film Simulation',
        },
    },
    0x1403 =&gt; { #2 (only valid for manual DR, ref 6)
        Name =&gt; 'DevelopmentDynamicRange',
        Writable =&gt; 'int16u',
    },
    0x1404 =&gt; { #2
        Name =&gt; 'MinFocalLength',
        Writable =&gt; 'rational64s',
    },
    0x1405 =&gt; { #2
        Name =&gt; 'MaxFocalLength',
        Writable =&gt; 'rational64s',
    },
    0x1406 =&gt; { #2
        Name =&gt; 'MaxApertureAtMinFocal',
        Writable =&gt; 'rational64s',
    },
    0x1407 =&gt; { #2
        Name =&gt; 'MaxApertureAtMaxFocal',
        Writable =&gt; 'rational64s',
    },
    # 0x1408 - values: '0100', 'S100', 'VQ10'
    # 0x1409 - values: same as 0x1408
    # 0x140a - values: 0, 1, 3, 5, 7
    0x140b =&gt; { #6
        Name =&gt; 'AutoDynamicRange',
        Writable =&gt; 'int16u',
        PrintConv =&gt; '"$val%"',
        PrintConvInv =&gt; '$val=~s/\s*\%$//; $val',
    },
    # 0x140b - DR value for AutoDR???? (ref 6) - values: 100
    # 0x3820 - int16u video frame rate? - PH (HS20EXR)
    # 0x3821 - int16u video frame width? - PH (HS20EXR)
    # 0x3822 - int16u video frame height? - PH (HS20EXR)
    0x4100 =&gt; { #PH
        Name =&gt; 'FacesDetected',
        Writable =&gt; 'int16u',
    },
    0x4103 =&gt; { #PH
        Name =&gt; 'FacePositions',
        Writable =&gt; 'int16u',
        Count =&gt; -1,
        Notes =&gt; q{
            left, top, right and bottom coordinates in full-sized image for each face
            detected
        },
    },
    # 0x4104 - also related to face detection (same number of entries as FacePositions)
    # 0x4203 - same as 0x4103
    # 0x4204 - same as 0x4104
    0x4282 =&gt; { #PH
        Name =&gt; 'FaceRecInfo',
        SubDirectory =&gt; { TagTable =&gt; 'Image::ExifTool::FujiFilm::FaceRecInfo' },
    },
    0x8000 =&gt; { #2
        Name =&gt; 'FileSource',
        Writable =&gt; 'string',
    },
    0x8002 =&gt; { #2
        Name =&gt; 'OrderNumber',
        Writable =&gt; 'int32u',
    },
    0x8003 =&gt; { #2
        Name =&gt; 'FrameNumber',
        Writable =&gt; 'int16u',
    },
    0xb211 =&gt; { #PH
        Name =&gt; 'Parallax',
        # (value set in camera is -0.5 times this value in MPImage2... why?)
        Writable =&gt; 'rational64s',
        Notes =&gt; 'only found in MPImage2 of .MPO images',
    },
    # 0xb212 - also found in MPIMage2 images - PH
);

# Face recognition information from FinePix F550EXR (ref PH)
%Image::ExifTool::FujiFilm::FaceRecInfo = (
    PROCESS_PROC =&gt; \&amp;ProcessFaceRec,
    GROUPS =&gt; { 0 =&gt; 'MakerNotes', 2 =&gt; 'Image' },
    VARS =&gt; { NO_ID =&gt; 1 },
    NOTES =&gt; 'Face recognition information.',
    Face1Name =&gt; { },
    Face2Name =&gt; { },
    Face3Name =&gt; { },
    Face4Name =&gt; { },
    Face5Name =&gt; { },
    Face6Name =&gt; { },
    Face7Name =&gt; { },
    Face8Name =&gt; { },
    Face1Category =&gt; { %faceCategories },
    Face2Category =&gt; { %faceCategories },
    Face3Category =&gt; { %faceCategories },
    Face4Category =&gt; { %faceCategories },
    Face5Category =&gt; { %faceCategories },
    Face6Category =&gt; { %faceCategories },
    Face7Category =&gt; { %faceCategories },
    Face8Category =&gt; { %faceCategories },
    Face1Birthday =&gt; { },
    Face2Birthday =&gt; { },
    Face3Birthday =&gt; { },
    Face4Birthday =&gt; { },
    Face5Birthday =&gt; { },
    Face6Birthday =&gt; { },
    Face7Birthday =&gt; { },
    Face8Birthday =&gt; { },
);

# tags in RAF images (ref 5)
%Image::ExifTool::FujiFilm::RAF = (
    PROCESS_PROC =&gt; \&amp;ProcessFujiDir,
    GROUPS =&gt; { 0 =&gt; 'RAF', 1 =&gt; 'RAF', 2 =&gt; 'Image' },
    PRIORITY =&gt; 0, # so the first RAF directory takes precedence
    NOTES =&gt; q{
        FujiFilm RAF images contain meta information stored in a proprietary
        FujiFilm RAF format, as well as EXIF information stored inside an embedded
        JPEG preview image.  The table below lists tags currently decoded from the
        RAF-format information.
    },
    0x100 =&gt; {
        Name =&gt; 'RawImageFullSize',
        Format =&gt; 'int16u',
        Groups =&gt; { 1 =&gt; 'RAF2' }, # (so RAF2 shows up in family 1 list)
        Count =&gt; 2,
        Notes =&gt; 'including borders',
        ValueConv =&gt; 'my @v=reverse split(" ",$val);"@v"',
        PrintConv =&gt; '$val=~tr/ /x/; $val',
    },
    0x121 =&gt; [
        {
            Name =&gt; 'RawImageSize',
            Condition =&gt; '$$self{Model} eq "FinePixS2Pro"',
            Format =&gt; 'int16u',
            Count =&gt; 2,
            ValueConv =&gt; q{
                my @v=split(" ",$val);
                $v[0]*=2, $v[1]/=2;
                return "@v";
            },
            PrintConv =&gt; '$val=~tr/ /x/; $val',
        },
        {
            Name =&gt; 'RawImageSize',
            Format =&gt; 'int16u',
            Count =&gt; 2,
            # values are height then width, adjusted for the layout
            ValueConv =&gt; q{
                my @v=reverse split(" ",$val);
                $$self{FujiLayout} and $v[0]/=2, $v[1]*=2;
                return "@v";
            },
            PrintConv =&gt; '$val=~tr/ /x/; $val',
        },
    ],
    0x130 =&gt; {
        Name =&gt; 'FujiLayout',
        Format =&gt; 'int8u',
        RawConv =&gt; q{
            my ($v) = split ' ', $val;
            $$self{FujiLayout} = $v &amp; 0x80 ? 1 : 0;
            return $val;
        },
    },
    0x2ff0 =&gt; {
        Name =&gt; 'WB_GRGBLevels',
        Format =&gt; 'int16u',
        Count =&gt; 4,
    },
);

# information found in FFMV atom of MOV videos
%Image::ExifTool::FujiFilm::FFMV = (
    PROCESS_PROC =&gt; \&amp;Image::ExifTool::ProcessBinaryData,
    GROUPS =&gt; { 0 =&gt; 'MakerNotes', 2 =&gt; 'Camera' },
    FIRST_ENTRY =&gt; 0,
    NOTES =&gt; 'Information found in the FFMV atom of MOV videos.',
    0 =&gt; {
        Name =&gt; 'MovieStreamName',
        Format =&gt; 'string[34]',
    },
);

#------------------------------------------------------------------------------
# decode information from FujiFilm face recognition information
# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
# Returns: 1
sub ProcessFaceRec($$$)
{
    my ($exifTool, $dirInfo, $tagTablePtr) = @_;
    my $dataPt = $$dirInfo{DataPt};
    my $dataPos = $$dirInfo{DataPos} + ($$dirInfo{Base} || 0);
    my $dirStart = $$dirInfo{DirStart};
    my $dirLen = $$dirInfo{DirLen};
    my $pos = $dirStart;
    my $end = $dirStart + $dirLen;
    my ($i, $n, $p, $val);
    $exifTool-&gt;VerboseDir('FaceRecInfo');
    for ($i=1; ; ++$i) {
        last if $pos + 8 &gt; $end;
        my $off = Get32u($dataPt, $pos) + $dirStart;
        my $len = Get32u($dataPt, $pos + 4);
        last if $len==0 or $off&gt;$end or $off+$len&gt;$end or $len &lt; 62;
        # values observed for each offset (always zero if not listed):
        # 0=5; 3=1; 4=4; 6=1; 10-13=numbers(constant for a given registered face)
        # 15=16; 16=3; 18=1; 22=nameLen; 26=1; 27=16; 28=7; 30-33=nameLen(int32u)
        # 34-37=nameOffset(int32u); 38=32; 39=16; 40=4; 42=1; 46=0,2,4,8(category)
        # 50=33; 51=16; 52=7; 54-57=dateLen(int32u); 58-61=dateOffset(int32u)
        $n = Get32u($dataPt, $off + 30);
        $p = Get32u($dataPt, $off + 34) + $dirStart;
        last if $p &lt; $dirStart or $p + $n &gt; $end;
        $val = substr($$dataPt, $p, $n);
        $exifTool-&gt;HandleTag($tagTablePtr, "Face${i}Name", $val,
            DataPt  =&gt; $dataPt,
            DataPos =&gt; $dataPos,
            Start   =&gt; $p,
            Size    =&gt; $n,
        );
        $n = Get32u($dataPt, $off + 54);
        $p = Get32u($dataPt, $off + 58) + $dirStart;
        last if $p &lt; $dirStart or $p + $n &gt; $end;
        $val = substr($$dataPt, $p, $n);
        $val =~ s/(\d{4})(\d{2})(\d{2})/$1:$2:$2/;
        $exifTool-&gt;HandleTag($tagTablePtr, "Face${i}Birthday", $val,
            DataPt  =&gt; $dataPt,
            DataPos =&gt; $dataPos,
            Start   =&gt; $p,
            Size    =&gt; $n,
        );
        $exifTool-&gt;HandleTag($tagTablePtr, "Face${i}Category", undef,
            DataPt  =&gt; $dataPt,
            DataPos =&gt; $dataPos,
            Start   =&gt; $off + 46,
            Size    =&gt; 1,
        );
        $pos += 8;
    }
    return 1;
}

#------------------------------------------------------------------------------
# get information from FujiFilm RAF directory
# Inputs: 0) ExifTool object reference, 1) dirInfo reference, 2) tag table ref
# Returns: 1 if this was a valid FujiFilm directory
sub ProcessFujiDir($$$)
{
    my ($exifTool, $dirInfo, $tagTablePtr) = @_;
    my $raf = $$dirInfo{RAF};
    my $offset = $$dirInfo{DirStart};
    $raf-&gt;Seek($offset, 0) or return 0;
    my ($buff, $index);
    $raf-&gt;Read($buff, 4) or return 0;
    my $entries = unpack 'N', $buff;
    $entries &lt; 256 or return 0;
    $exifTool-&gt;Options('Verbose') and $exifTool-&gt;VerboseDir('Fuji', $entries);
    SetByteOrder('MM');
    my $pos = $offset + 4;
    for ($index=0; $index&lt;$entries; ++$index) {
        $raf-&gt;Read($buff,4) or return 0;
        $pos += 4;
        my ($tag, $len) = unpack 'nn', $buff;
        my ($val, $vbuf);
        $raf-&gt;Read($vbuf, $len) or return 0;
        my $tagInfo = $exifTool-&gt;GetTagInfo($tagTablePtr, $tag);
        if ($tagInfo and $$tagInfo{Format}) {
            $val = ReadValue(\$vbuf, 0, $$tagInfo{Format}, $$tagInfo{Count}, $len);
            next unless defined $val;
        } elsif ($len == 4) {
            # interpret unknown 4-byte values as int32u
            $val = Get32u(\$vbuf, 0);
        } else {
            # treat other unknown values as binary data
            $val = \$vbuf;
        }
        $exifTool-&gt;HandleTag($tagTablePtr, $tag, $val,
            Index   =&gt; $index,
            DataPt  =&gt; \$vbuf,
            DataPos =&gt; $pos,
            Size    =&gt; $len,
            TagInfo =&gt; $tagInfo,
        );
        $pos += $len;
    }
    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
sub WriteRAF($$)
{
    my ($exifTool, $dirInfo) = @_;
    my $raf = $$dirInfo{RAF};
    my ($hdr, $jpeg, $outJpeg, $offset, $err, $buff);

    $raf-&gt;Read($hdr,0x94) == 0x94  or return 0;
    $hdr =~ /^FUJIFILM/            or return 0;
    my $ver = substr($hdr, 0x3c, 4);
    $ver =~ /^\d{4}$/              or return 0;

    # 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 &gt; 0x94 or $jpos &lt; 0x68 or $jpos &amp; 0x03) {
        $exifTool-&gt;Error("Unsupported or corrupted RAF image (version $ver)");
        return 1;
    }
    # check to make sure this version of RAF has been tested
    unless ($testedRAF{$ver}) {
        $exifTool-&gt;Error("RAF version $ver not yet tested", 1) and return 1;
    }
    # read the embedded JPEG
    unless ($raf-&gt;Seek($jpos, 0) and $raf-&gt;Read($jpeg, $jlen) == $jlen) {
        $exifTool-&gt;Error('Error reading RAF meta information');
        return 1;
    }
    # use same write directories as JPEG
    $exifTool-&gt;InitWriteDirs('JPEG');
    # rewrite the embedded JPEG in memory
    my %jpegInfo = (
        Parent  =&gt; 'RAF',
        RAF     =&gt; new File::RandomAccess(\$jpeg),
        OutFile =&gt; \$outJpeg,
    );
    $$exifTool{FILE_TYPE} = 'JPEG';
    my $success = $exifTool-&gt;WriteJPEG(\%jpegInfo);
    $$exifTool{FILE_TYPE} = 'RAF';
    unless ($success and $outJpeg) {
        $exifTool-&gt;Error("Invalid RAF format");
        return 1;
    }
    return -1 if $success &lt; 0;

    # rewrite the RAF image
    SetByteOrder('MM');
    my $jpegLen = length $outJpeg;
    # pad JPEG to an even 4 bytes (ALWAYS use padding as Fuji does)
    my $pad = "\0" x (4 - ($jpegLen % 4));
    # update JPEG size in header (size without padding)
    Set32u(length($outJpeg), \$hdr, 0x58);
    # get pointer to start of the next RAF block
    my $nextPtr = Get32u(\$hdr, 0x5c);
    # determine the length of padding at the end of the original JPEG
    my $oldPadLen = $nextPtr - ($jpos + $jlen);
    if ($oldPadLen) {
        if ($oldPadLen &gt; 1000000 or $oldPadLen &lt; 0 or
            not $raf-&gt;Seek($jpos+$jlen, 0) or
            $raf-&gt;Read($buff, $oldPadLen) != $oldPadLen)
        {
            $exifTool-&gt;Error('Bad RAF pointer at 0x5c');
            return 1;
        }
        # make sure padding is only zero bytes (can be &gt;100k for HS10)
        if ($buff =~ /[^\0]/) {
            $exifTool-&gt;Error('Non-null bytes found in padding');
            return 1;
        }
    }
    # 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) {
        last if $offset &gt;= $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);
    }
    # write the new header
    my $outfile = $$dirInfo{OutFile};
    Write($outfile, substr($hdr, 0, $jpos)) or $err = 1;
    # write the updated JPEG plus padding
    Write($outfile, $outJpeg, $pad) or $err = 1;
    # copy over the rest of the RAF image
    unless ($raf-&gt;Seek($nextPtr, 0)) {
        $exifTool-&gt;Error('Error reading RAF image');
        return 1;
    }
    while ($raf-&gt;Read($buff, 65536)) {
        Write($outfile, $buff) or $err = 1, last;
    }
    return $err ? -1 : 1;
}

#------------------------------------------------------------------------------
# get information from FujiFilm RAW file (RAF)
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
# Returns: 1 if this was a valid RAF file
sub ProcessRAF($$)
{
    my ($exifTool, $dirInfo) = @_;
    my ($buff, $jpeg, $warn, $offset);

    my $raf = $$dirInfo{RAF};
    $raf-&gt;Read($buff,0x5c) == 0x5c    or return 0;
    $buff =~ /^FUJIFILM/              or return 0;
    my ($jpos, $jlen) = unpack('x84NN', $buff);
    $jpos &amp; 0x8000                   and return 0;
    $raf-&gt;Seek($jpos, 0)              or return 0;
    $raf-&gt;Read($jpeg, $jlen) == $jlen or return 0;

    $exifTool-&gt;FoundTag('RAFVersion', substr($buff, 0x3c, 4));

    # extract information from embedded JPEG
    my %dirInfo = (
        Parent =&gt; 'RAF',
        RAF    =&gt; new File::RandomAccess(\$jpeg),
    );
    $$exifTool{BASE} += $jpos;
    my $rtnVal = $exifTool-&gt;ProcessJPEG(\%dirInfo);
    $$exifTool{BASE} -= $jpos;
    $exifTool-&gt;FoundTag('PreviewImage', \$jpeg) if $rtnVal;

    # extract information from Fuji RAF directories
    my $num = '';
    foreach $offset (0x5c, 0x78) {
        last if $offset &gt;= $jpos;
        unless ($raf-&gt;Seek($offset, 0) and $raf-&gt;Read($buff, 4)) {
            $warn = 1;
            last;
        }
        my $start = unpack('N',$buff);
        next unless $start;

        %dirInfo = (
            RAF      =&gt; $raf,
            DirStart =&gt; $start,
        );
        $$exifTool{SET_GROUP1} = "RAF$num";
        my $tagTablePtr = GetTagTable('Image::ExifTool::FujiFilm::RAF');
        $exifTool-&gt;ProcessDirectory(\%dirInfo, $tagTablePtr) or $warn = 1;
        delete $$exifTool{SET_GROUP1};

        $num = ($num || 1) + 1;
    }
    $warn and $exifTool-&gt;Warn('Possibly corrupt RAF information');

    return $rtnVal;
}

1; # end

__END__

=head1 NAME

Image::ExifTool::FujiFilm - Read/write FujiFilm maker notes and RAF images

=head1 SYNOPSIS

This module is loaded automatically by Image::ExifTool when required.

=head1 DESCRIPTION

This module contains definitions required by Image::ExifTool to interpret
FujiFilm maker notes in EXIF information, and to read/write FujiFilm RAW
(RAF) images.

=head1 AUTHOR

Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)

This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=head1 REFERENCES

=over 4

=item L&lt;http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html&gt;

=item L&lt;http://homepage3.nifty.com/kamisaka/makernote/makernote_fuji.htm&gt;

=item L&lt;http://www.cybercom.net/~dcoffin/dcraw/&gt;

=item (...plus testing with my own FinePix 2400 Zoom)

=back

=head1 ACKNOWLEDGEMENTS

Thanks to Michael Meissner, Paul Samuelson and Jens Duttke for help decoding
some FujiFilm information.

=head1 SEE ALSO

L&lt;Image::ExifTool::TagNames/FujiFilm Tags&gt;,
L&lt;Image::ExifTool(3pm)|Image::ExifTool&gt;

=cut
</pre></body></html>