Ignore:
Timestamp:
2017-10-30T22:46:09+01:00 (6 years ago)
Author:
Don-vip
Message:

fix #15505 - update to metadata-extractor 2.10.1

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/com/drew/metadata/exif/ExifTiffHandler.java

    r10862 r13061  
    11/*
    2  * Copyright 2002-2016 Drew Noakes
     2 * Copyright 2002-2017 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2121package com.drew.metadata.exif;
    2222
     23import java.io.ByteArrayInputStream;
     24import java.io.IOException;
     25import java.util.Arrays;
     26import java.util.Set;
     27
     28import com.drew.imaging.jpeg.JpegMetadataReader;
     29import com.drew.imaging.jpeg.JpegProcessingException;
    2330import com.drew.imaging.tiff.TiffProcessingException;
    2431import com.drew.imaging.tiff.TiffReader;
     32import com.drew.lang.Charsets;
    2533import com.drew.lang.RandomAccessReader;
    2634import com.drew.lang.SequentialByteArrayReader;
     
    2937import com.drew.metadata.Directory;
    3038import com.drew.metadata.Metadata;
     39import com.drew.metadata.StringValue;
    3140import com.drew.metadata.exif.makernotes.*;
    3241import com.drew.metadata.iptc.IptcReader;
    3342import com.drew.metadata.tiff.DirectoryTiffHandler;
    34 
    35 import java.io.IOException;
    36 import java.util.Set;
    3743
    3844/**
     
    4652public class ExifTiffHandler extends DirectoryTiffHandler
    4753{
    48     private final boolean _storeThumbnailBytes;
    49 
    50     public ExifTiffHandler(@NotNull Metadata metadata, boolean storeThumbnailBytes, @Nullable Directory parentDirectory)
    51     {
    52         super(metadata, ExifIFD0Directory.class);
    53         _storeThumbnailBytes = storeThumbnailBytes;
     54    public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDirectory)
     55    {
     56        super(metadata);
    5457
    5558        if (parentDirectory != null)
     
    6467        final int panasonicRawTiffMarker = 0x0055; // for RW2 files
    6568
    66         if (marker != standardTiffMarker && marker != olympusRawTiffMarker && marker != olympusRawTiffMarker2 && marker != panasonicRawTiffMarker) {
    67             throw new TiffProcessingException("Unexpected TIFF marker: 0x" + Integer.toHexString(marker));
     69        switch (marker)
     70        {
     71            case standardTiffMarker:
     72            case olympusRawTiffMarker:      // Todo: implement an IFD0, if there is one
     73            case olympusRawTiffMarker2:     // Todo: implement an IFD0, if there is one
     74                pushDirectory(ExifIFD0Directory.class);
     75                break;
     76            case panasonicRawTiffMarker:
     77                pushDirectory(PanasonicRawIFD0Directory.class);
     78                break;
     79            default:
     80                throw new TiffProcessingException(String.format("Unexpected TIFF marker: 0x%X", marker));
    6881        }
    6982    }
     
    7689        }
    7790
    78         if (_currentDirectory instanceof ExifIFD0Directory) {
     91        if (_currentDirectory instanceof ExifIFD0Directory || _currentDirectory instanceof PanasonicRawIFD0Directory) {
    7992            if (tagId == ExifIFD0Directory.TAG_EXIF_SUB_IFD_OFFSET) {
    8093                pushDirectory(ExifSubIFDDirectory.class);
     
    96109
    97110        if (_currentDirectory instanceof OlympusMakernoteDirectory) {
    98             if (tagId == OlympusMakernoteDirectory.TAG_EQUIPMENT) {
    99                 pushDirectory(OlympusEquipmentMakernoteDirectory.class);
    100                 return true;
    101             }
    102 
    103             if (tagId == OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS) {
    104                 pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
    105                 return true;
     111            // Note: these also appear in customProcessTag because some are IFD pointers while others begin immediately
     112            // for the same directories
     113            switch(tagId) {
     114                case OlympusMakernoteDirectory.TAG_EQUIPMENT:
     115                    pushDirectory(OlympusEquipmentMakernoteDirectory.class);
     116                    return true;
     117                case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS:
     118                    pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
     119                    return true;
     120                case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT:
     121                    pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class);
     122                    return true;
     123                case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2:
     124                    pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class);
     125                    return true;
     126                case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING:
     127                    pushDirectory(OlympusImageProcessingMakernoteDirectory.class);
     128                    return true;
     129                case OlympusMakernoteDirectory.TAG_FOCUS_INFO:
     130                    pushDirectory(OlympusFocusInfoMakernoteDirectory.class);
     131                    return true;
     132                case OlympusMakernoteDirectory.TAG_RAW_INFO:
     133                    pushDirectory(OlympusRawInfoMakernoteDirectory.class);
     134                    return true;
     135                case OlympusMakernoteDirectory.TAG_MAIN_INFO:
     136                    pushDirectory(OlympusMakernoteDirectory.class);
     137                    return true;
    106138            }
    107139        }
     
    113145    {
    114146        // In Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case.
    115         if (_currentDirectory instanceof ExifIFD0Directory) {
    116             pushDirectory(ExifThumbnailDirectory.class);
     147        // UPDATE: In multipage TIFFs, the 'follower' IFD points to the next image in the set
     148        if (_currentDirectory instanceof ExifIFD0Directory || _currentDirectory instanceof ExifImageDirectory) {
     149            // If the PageNumber tag is defined, assume this is a multipage TIFF or similar
     150            // TODO: Find better ways to know which follower Directory should be used
     151            if (_currentDirectory.containsTag(ExifDirectoryBase.TAG_PAGE_NUMBER))
     152                pushDirectory(ExifImageDirectory.class);
     153            else
     154                pushDirectory(ExifThumbnailDirectory.class);
    117155            return true;
    118156        }
     
    132170        if (formatCode == 13)
    133171            return componentCount * 4;
     172
     173        // an unknown (0) formatCode needs to be potentially handled later as a highly custom directory tag
     174        if(formatCode == 0)
     175            return 0L;
    134176
    135177        return null;
     
    143185                                    final int byteCount) throws IOException
    144186    {
     187        // Some 0x0000 tags have a 0 byteCount. Determine whether it's bad.
     188        if (tagId == 0)
     189        {
     190            if (_currentDirectory.containsTag(tagId))
     191            {
     192                // Let it go through for now. Some directories handle it, some don't
     193                return false;
     194            }
     195
     196            // Skip over 0x0000 tags that don't have any associated bytes. No idea what it contains in this case, if anything.
     197            if (byteCount == 0)
     198                return true;
     199        }
     200
    145201        // Custom processing for the Makernote tag
    146202        if (tagId == ExifSubIFDDirectory.TAG_MAKERNOTE && _currentDirectory instanceof ExifSubIFDDirectory) {
     
    159215        }
    160216
     217        if (HandlePrintIM(_currentDirectory, tagId))
     218        {
     219            PrintIMDirectory printIMDirectory = new PrintIMDirectory();
     220            printIMDirectory.setParent(_currentDirectory);
     221            _metadata.addDirectory(printIMDirectory);
     222            ProcessPrintIM(printIMDirectory, tagOffset, reader, byteCount);
     223            return true;
     224        }
     225
     226        // Note: these also appear in tryEnterSubIfd because some are IFD pointers while others begin immediately
     227        // for the same directories
     228        if(_currentDirectory instanceof OlympusMakernoteDirectory)
     229        {
     230            switch (tagId)
     231            {
     232                case OlympusMakernoteDirectory.TAG_EQUIPMENT:
     233                    pushDirectory(OlympusEquipmentMakernoteDirectory.class);
     234                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     235                    return true;
     236                case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS:
     237                    pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
     238                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     239                    return true;
     240                case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT:
     241                    pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class);
     242                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     243                    return true;
     244                case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2:
     245                    pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class);
     246                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     247                    return true;
     248                case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING:
     249                    pushDirectory(OlympusImageProcessingMakernoteDirectory.class);
     250                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     251                    return true;
     252                case OlympusMakernoteDirectory.TAG_FOCUS_INFO:
     253                    pushDirectory(OlympusFocusInfoMakernoteDirectory.class);
     254                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     255                    return true;
     256                case OlympusMakernoteDirectory.TAG_RAW_INFO:
     257                    pushDirectory(OlympusRawInfoMakernoteDirectory.class);
     258                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     259                    return true;
     260                case OlympusMakernoteDirectory.TAG_MAIN_INFO:
     261                    pushDirectory(OlympusMakernoteDirectory.class);
     262                    TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
     263                    return true;
     264            }
     265        }
     266
     267        if (_currentDirectory instanceof PanasonicRawIFD0Directory)
     268        {
     269            // these contain binary data with specific offsets, and can't be processed as regular ifd's.
     270            // The binary data is broken into 'fake' tags and there is a pattern.
     271            switch (tagId)
     272            {
     273                case PanasonicRawIFD0Directory.TagWbInfo:
     274                    PanasonicRawWbInfoDirectory dirWbInfo = new PanasonicRawWbInfoDirectory();
     275                    dirWbInfo.setParent(_currentDirectory);
     276                    _metadata.addDirectory(dirWbInfo);
     277                    ProcessBinary(dirWbInfo, tagOffset, reader, byteCount, false, 2);
     278                    return true;
     279                case PanasonicRawIFD0Directory.TagWbInfo2:
     280                    PanasonicRawWbInfo2Directory dirWbInfo2 = new PanasonicRawWbInfo2Directory();
     281                    dirWbInfo2.setParent(_currentDirectory);
     282                    _metadata.addDirectory(dirWbInfo2);
     283                    ProcessBinary(dirWbInfo2, tagOffset, reader, byteCount, false, 3);
     284                    return true;
     285                case PanasonicRawIFD0Directory.TagDistortionInfo:
     286                    PanasonicRawDistortionDirectory dirDistort = new PanasonicRawDistortionDirectory();
     287                    dirDistort.setParent(_currentDirectory);
     288                    _metadata.addDirectory(dirDistort);
     289                    ProcessBinary(dirDistort, tagOffset, reader, byteCount, true, 1);
     290                    return true;
     291            }
     292        }
     293
     294        // Panasonic RAW sometimes contains an embedded version of the data as a JPG file.
     295        if (tagId == PanasonicRawIFD0Directory.TagJpgFromRaw && _currentDirectory instanceof PanasonicRawIFD0Directory)
     296        {
     297            byte[] jpegrawbytes = reader.getBytes(tagOffset, byteCount);
     298
     299            // Extract information from embedded image since it is metadata-rich
     300            ByteArrayInputStream jpegmem = new ByteArrayInputStream(jpegrawbytes);
     301            try {
     302                Metadata jpegDirectory = JpegMetadataReader.readMetadata(jpegmem);
     303                for (Directory directory : jpegDirectory.getDirectories()) {
     304                    directory.setParent(_currentDirectory);
     305                    _metadata.addDirectory(directory);
     306                }
     307                return true;
     308            } catch (JpegProcessingException e) {
     309                _currentDirectory.addError("Error processing JpgFromRaw: " + e.getMessage());
     310            } catch (IOException e) {
     311                _currentDirectory.addError("Error reading JpgFromRaw: " + e.getMessage());
     312            }
     313        }
     314
    161315        return false;
    162316    }
    163317
    164     public void completed(@NotNull final RandomAccessReader reader, final int tiffHeaderOffset)
    165     {
    166         if (_storeThumbnailBytes) {
    167             // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
    168             ExifThumbnailDirectory thumbnailDirectory = _metadata.getFirstDirectoryOfType(ExifThumbnailDirectory.class);
    169             if (thumbnailDirectory != null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_COMPRESSION)) {
    170                 Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
    171                 Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
    172                 if (offset != null && length != null) {
    173                     try {
    174                         byte[] thumbnailData = reader.getBytes(tiffHeaderOffset + offset, length);
    175                         thumbnailDirectory.setThumbnailData(thumbnailData);
    176                     } catch (IOException ex) {
    177                         thumbnailDirectory.addError("Invalid thumbnail data specification: " + ex.getMessage());
     318    private static void ProcessBinary(@NotNull final Directory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount, final Boolean issigned, final int arrayLength) throws IOException
     319    {
     320        // expects signed/unsigned int16 (for now)
     321        //int byteSize = issigned ? sizeof(short) : sizeof(ushort);
     322        int byteSize = 2;
     323
     324        // 'directory' is assumed to contain tags that correspond to the byte position unless it's a set of bytes
     325        for (int i = 0; i < byteCount; i++)
     326        {
     327            if (directory.hasTagName(i))
     328            {
     329                // only process this tag if the 'next' integral tag exists. Otherwise, it's a set of bytes
     330                if (i < byteCount - 1 && directory.hasTagName(i + 1))
     331                {
     332                    if(issigned)
     333                        directory.setObject(i, reader.getInt16(tagValueOffset + (i* byteSize)));
     334                    else
     335                        directory.setObject(i, reader.getUInt16(tagValueOffset + (i* byteSize)));
     336                }
     337                else
     338                {
     339                    // the next arrayLength bytes are a multi-byte value
     340                    if (issigned)
     341                    {
     342                        short[] val = new short[arrayLength];
     343                        for (int j = 0; j<val.length; j++)
     344                            val[j] = reader.getInt16(tagValueOffset + ((i + j) * byteSize));
     345                        directory.setObjectArray(i, val);
    178346                    }
     347                    else
     348                    {
     349                        int[] val = new int[arrayLength];
     350                        for (int j = 0; j<val.length; j++)
     351                            val[j] = reader.getUInt16(tagValueOffset + ((i + j) * byteSize));
     352                        directory.setObjectArray(i, val);
     353                    }
     354
     355                    i += arrayLength - 1;
    179356                }
    180357            }
     
    190367        Directory ifd0Directory = _metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
    191368
    192         if (ifd0Directory == null)
    193             return false;
    194 
    195         String cameraMake = ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
    196 
    197         final String firstTwoChars = reader.getString(makernoteOffset, 2);
    198         final String firstThreeChars = reader.getString(makernoteOffset, 3);
    199         final String firstFourChars = reader.getString(makernoteOffset, 4);
    200         final String firstFiveChars = reader.getString(makernoteOffset, 5);
    201         final String firstSixChars = reader.getString(makernoteOffset, 6);
    202         final String firstSevenChars = reader.getString(makernoteOffset, 7);
    203         final String firstEightChars = reader.getString(makernoteOffset, 8);
    204         final String firstTenChars = reader.getString(makernoteOffset, 10);
    205         final String firstTwelveChars = reader.getString(makernoteOffset, 12);
     369        String cameraMake = ifd0Directory == null ? null : ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
     370
     371        final String firstTwoChars    = reader.getString(makernoteOffset, 2, Charsets.UTF_8);
     372        final String firstThreeChars  = reader.getString(makernoteOffset, 3, Charsets.UTF_8);
     373        final String firstFourChars   = reader.getString(makernoteOffset, 4, Charsets.UTF_8);
     374        final String firstFiveChars   = reader.getString(makernoteOffset, 5, Charsets.UTF_8);
     375        final String firstSixChars    = reader.getString(makernoteOffset, 6, Charsets.UTF_8);
     376        final String firstSevenChars  = reader.getString(makernoteOffset, 7, Charsets.UTF_8);
     377        final String firstEightChars  = reader.getString(makernoteOffset, 8, Charsets.UTF_8);
     378        final String firstNineChars   = reader.getString(makernoteOffset, 9, Charsets.UTF_8);
     379        final String firstTenChars    = reader.getString(makernoteOffset, 10, Charsets.UTF_8);
     380        final String firstTwelveChars = reader.getString(makernoteOffset, 12, Charsets.UTF_8);
    206381
    207382        boolean byteOrderBefore = reader.isMotorolaByteOrder();
     
    243418                        break;
    244419                    default:
    245                         ifd0Directory.addError("Unsupported Nikon makernote data ignored.");
     420                        _currentDirectory.addError("Unsupported Nikon makernote data ignored.");
    246421                        break;
    247422                }
     
    254429            pushDirectory(SonyType1MakernoteDirectory.class);
    255430            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset);
     431        // Do this check LAST after most other Sony checks
     432        } else if (cameraMake != null && cameraMake.startsWith("SONY") &&
     433                !Arrays.equals(reader.getBytes(makernoteOffset, 2), new byte[]{ 0x01, 0x00 }) ) {
     434            // The IFD begins with the first Makernote byte (no ASCII name). Used in SR2 and ARW images
     435            pushDirectory(SonyType1MakernoteDirectory.class);
     436            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
    256437        } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) {
    257438            // force MM for this directory
     
    294475        } else if ("LEICA".equals(firstFiveChars)) {
    295476            reader.setMotorolaByteOrder(false);
    296             if ("Leica Camera AG".equals(cameraMake)) {
     477
     478            // used by the X1/X2/X VARIO/T
     479            // (X1 starts with "LEICA\0\x01\0", Make is "LEICA CAMERA AG")
     480            // (X2 starts with "LEICA\0\x05\0", Make is "LEICA CAMERA AG")
     481            // (X VARIO starts with "LEICA\0\x04\0", Make is "LEICA CAMERA AG")
     482            // (T (Typ 701) starts with "LEICA\0\0x6", Make is "LEICA CAMERA AG")
     483            // (X (Typ 113) starts with "LEICA\0\0x7", Make is "LEICA CAMERA AG")
     484
     485            if ("LEICA\0\u0001\0".equals(firstEightChars) ||
     486                "LEICA\0\u0004\0".equals(firstEightChars) ||
     487                "LEICA\0\u0005\0".equals(firstEightChars) ||
     488                "LEICA\0\u0006\0".equals(firstEightChars) ||
     489                "LEICA\0\u0007\0".equals(firstEightChars))
     490            {
     491                pushDirectory(LeicaType5MakernoteDirectory.class);
     492                TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset);
     493            } else if ("Leica Camera AG".equals(cameraMake)) {
    297494                pushDirectory(LeicaMakernoteDirectory.class);
    298495                TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
     
    304501                return false;
    305502            }
    306         } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(makernoteOffset, 12))) {
     503        } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(makernoteOffset, 12, Charsets.UTF_8))) {
    307504            // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD
    308505            // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
     
    349546                TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset);
    350547            }
     548        } else if (firstTenChars.equals("Apple iOS\0")) {
     549            // Always in Motorola byte order
     550            boolean orderBefore = reader.isMotorolaByteOrder();
     551            reader.setMotorolaByteOrder(true);
     552            pushDirectory(AppleMakernoteDirectory.class);
     553            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 14, makernoteOffset);
     554            reader.setMotorolaByteOrder(orderBefore);
     555        } else if (reader.getUInt16(makernoteOffset) == ReconyxHyperFireMakernoteDirectory.MAKERNOTE_VERSION) {
     556            ReconyxHyperFireMakernoteDirectory directory = new ReconyxHyperFireMakernoteDirectory();
     557            _metadata.addDirectory(directory);
     558            processReconyxHyperFireMakernote(directory, makernoteOffset, reader);
     559        } else if (firstNineChars.equalsIgnoreCase("RECONYXUF")) {
     560            ReconyxUltraFireMakernoteDirectory directory = new ReconyxUltraFireMakernoteDirectory();
     561            _metadata.addDirectory(directory);
     562            processReconyxUltraFireMakernote(directory, makernoteOffset, reader);
     563        } else if ("SAMSUNG".equals(cameraMake)) {
     564            // Only handles Type2 notes correctly. Others aren't implemented, and it's complex to determine which ones to use
     565            pushDirectory(SamsungType2MakernoteDirectory.class);
     566            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
    351567        } else {
    352568            // The makernote is not comprehended by this library.
     
    359575    }
    360576
     577    private static Boolean HandlePrintIM(@NotNull final Directory directory, final int tagId)
     578    {
     579        if (tagId == ExifDirectoryBase.TAG_PRINT_IMAGE_MATCHING_INFO)
     580            return true;
     581
     582        if (tagId == 0x0E00)
     583        {
     584            // Tempting to say every tagid of 0x0E00 is a PIM tag, but can't be 100% sure
     585            if (directory instanceof CasioType2MakernoteDirectory ||
     586                directory instanceof KyoceraMakernoteDirectory ||
     587                directory instanceof NikonType2MakernoteDirectory ||
     588                directory instanceof OlympusMakernoteDirectory ||
     589                directory instanceof PanasonicMakernoteDirectory ||
     590                directory instanceof PentaxMakernoteDirectory ||
     591                directory instanceof RicohMakernoteDirectory ||
     592                directory instanceof SanyoMakernoteDirectory ||
     593                directory instanceof SonyType1MakernoteDirectory)
     594                return true;
     595        }
     596
     597        return false;
     598    }
     599
     600    /// <summary>
     601    /// Process PrintIM IFD
     602    /// </summary>
     603    /// <remarks>
     604    /// Converted from Exiftool version 10.33 created by Phil Harvey
     605    /// http://www.sno.phy.queensu.ca/~phil/exiftool/
     606    /// lib\Image\ExifTool\PrintIM.pm
     607    /// </remarks>
     608    private static void ProcessPrintIM(@NotNull final PrintIMDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount) throws IOException
     609    {
     610        Boolean resetByteOrder = null;
     611
     612        if (byteCount == 0)
     613        {
     614            directory.addError("Empty PrintIM data");
     615            return;
     616        }
     617
     618        if (byteCount <= 15)
     619        {
     620            directory.addError("Bad PrintIM data");
     621            return;
     622        }
     623
     624        String header = reader.getString(tagValueOffset, 12, Charsets.UTF_8);
     625
     626        if (!header.startsWith("PrintIM")) //, StringComparison.Ordinal))
     627        {
     628            directory.addError("Invalid PrintIM header");
     629            return;
     630        }
     631
     632        // check size of PrintIM block
     633        int num = reader.getUInt16(tagValueOffset + 14);
     634        if (byteCount < 16 + num * 6)
     635        {
     636            // size is too big, maybe byte ordering is wrong
     637            resetByteOrder = reader.isMotorolaByteOrder();
     638            reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder());
     639            num = reader.getUInt16(tagValueOffset + 14);
     640            if (byteCount < 16 + num * 6)
     641            {
     642                directory.addError("Bad PrintIM size");
     643                return;
     644            }
     645        }
     646
     647        directory.setObject(PrintIMDirectory.TagPrintImVersion, header.substring(8, 12));
     648
     649        for (int n = 0; n < num; n++)
     650        {
     651            int pos = tagValueOffset + 16 + n * 6;
     652            int tag = reader.getUInt16(pos);
     653            long val = reader.getUInt32(pos + 2);
     654
     655            directory.setObject(tag, val);
     656        }
     657
     658        if (resetByteOrder != null)
     659            reader.setMotorolaByteOrder(resetByteOrder);
     660    }
     661
    361662    private static void processKodakMakernote(@NotNull final KodakMakernoteDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader)
    362663    {
     
    364665        int dataOffset = tagValueOffset + 8;
    365666        try {
    366             directory.setString(KodakMakernoteDirectory.TAG_KODAK_MODEL, reader.getString(dataOffset, 8));
     667            directory.setStringValue(KodakMakernoteDirectory.TAG_KODAK_MODEL, reader.getStringValue(dataOffset, 8, Charsets.UTF_8));
    367668            directory.setInt(KodakMakernoteDirectory.TAG_QUALITY, reader.getUInt8(dataOffset + 9));
    368669            directory.setInt(KodakMakernoteDirectory.TAG_BURST_MODE, reader.getUInt8(dataOffset + 10));
     
    394695        }
    395696    }
     697
     698    private static void processReconyxHyperFireMakernote(@NotNull final ReconyxHyperFireMakernoteDirectory directory, final int makernoteOffset, @NotNull final RandomAccessReader reader) throws IOException
     699    {
     700        directory.setObject(ReconyxHyperFireMakernoteDirectory.TAG_MAKERNOTE_VERSION, reader.getUInt16(makernoteOffset));
     701
     702        int major = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION);
     703        int minor = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 2);
     704        int revision = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 4);
     705        String buildYear = String.format("%04X", reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 6));
     706        String buildDate = String.format("%04X", reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 8));
     707        String buildYearAndDate = buildYear + buildDate;
     708        Integer build;
     709        try {
     710            build = Integer.parseInt(buildYearAndDate);
     711        } catch (NumberFormatException e) {
     712            build = null;
     713        }
     714        if (build != null)
     715        {
     716            directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION, String.format("%d.%d.%d.%s", major, minor, revision, build));
     717        }
     718        else
     719        {
     720            directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION, String.format("%d.%d.%d", major, minor, revision));
     721            directory.addError("Error processing Reconyx HyperFire makernote data: build '" + buildYearAndDate + "' is not in the expected format and will be omitted from Firmware Version.");
     722        }
     723
     724        directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_TRIGGER_MODE, String.valueOf((char)reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_TRIGGER_MODE)));
     725        directory.setIntArray(ReconyxHyperFireMakernoteDirectory.TAG_SEQUENCE,
     726                      new int[]
     727                      {
     728                          reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SEQUENCE),
     729                          reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SEQUENCE + 2)
     730                      });
     731
     732        int eventNumberHigh = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_EVENT_NUMBER);
     733        int eventNumberLow = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_EVENT_NUMBER + 2);
     734        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_EVENT_NUMBER, (eventNumberHigh << 16) + eventNumberLow);
     735
     736        int seconds = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL);
     737        int minutes = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 2);
     738        int hour = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 4);
     739        int month = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 6);
     740        int day = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 8);
     741        int year = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 10);
     742
     743        if ((seconds >= 0 && seconds < 60) &&
     744            (minutes >= 0 && minutes < 60) &&
     745            (hour >= 0 && hour < 24) &&
     746            (month >= 1 && month < 13) &&
     747            (day >= 1 && day < 32) &&
     748            (year >= 1 && year <= 9999))
     749        {
     750            directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL,
     751                    String.format("%4d:%2d:%2d %2d:%2d:%2d", year, month, day, hour, minutes, seconds));
     752        }
     753        else
     754        {
     755            directory.addError("Error processing Reconyx HyperFire makernote data: Date/Time Original " + year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + " is not a valid date/time.");
     756        }
     757
     758        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_MOON_PHASE, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_MOON_PHASE));
     759        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE_FAHRENHEIT, reader.getInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE_FAHRENHEIT));
     760        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE, reader.getInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE));
     761        //directory.setByteArray(ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, reader.getBytes(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, 28));
     762        directory.setStringValue(ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, new StringValue(reader.getBytes(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, 28), Charsets.UTF_16LE));
     763        // two unread bytes: the serial number's terminating null
     764
     765        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_CONTRAST, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_CONTRAST));
     766        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_BRIGHTNESS, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_BRIGHTNESS));
     767        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_SHARPNESS, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SHARPNESS));
     768        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_SATURATION, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SATURATION));
     769        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_INFRARED_ILLUMINATOR, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_INFRARED_ILLUMINATOR));
     770        directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_MOTION_SENSITIVITY, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_MOTION_SENSITIVITY));
     771        directory.setDouble(ReconyxHyperFireMakernoteDirectory.TAG_BATTERY_VOLTAGE, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_BATTERY_VOLTAGE) / 1000.0);
     772        directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_USER_LABEL, reader.getNullTerminatedString(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_USER_LABEL, 44, Charsets.UTF_8));
     773    }
     774
     775    private static void processReconyxUltraFireMakernote(@NotNull final ReconyxUltraFireMakernoteDirectory directory, final int makernoteOffset, @NotNull final RandomAccessReader reader) throws IOException
     776    {
     777        directory.setString(ReconyxUltraFireMakernoteDirectory.TAG_LABEL, reader.getString(makernoteOffset, 9, Charsets.UTF_8));
     778        /*uint makernoteID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernoteID));
     779        directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernoteID, makernoteID);
     780        if (makernoteID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_ID)
     781        {
     782            directory.addError("Error processing Reconyx UltraFire makernote data: unknown Makernote ID 0x" + makernoteID.ToString("x8"));
     783            return;
     784        }
     785        directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernoteSize, ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernoteSize)));
     786        uint makernotePublicID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernotePublicID));
     787        directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernotePublicID, makernotePublicID);
     788        if (makernotePublicID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_PUBLIC_ID)
     789        {
     790            directory.addError("Error processing Reconyx UltraFire makernote data: unknown Makernote Public ID 0x" + makernotePublicID.ToString("x8"));
     791            return;
     792        }*/
     793        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernotePublicSize, ByteConvert.FromBigEndianToNative(reader.GetUInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernotePublicSize)));
     794
     795        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagCameraVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagCameraVersion, reader));
     796        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagUibVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagUibVersion, reader));
     797        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagBtlVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagBtlVersion, reader));
     798        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagPexVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagPexVersion, reader));
     799
     800        directory.setString(ReconyxUltraFireMakernoteDirectory.TAG_EVENT_TYPE, reader.getString(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_EVENT_TYPE, 1, Charsets.UTF_8));
     801        directory.setIntArray(ReconyxUltraFireMakernoteDirectory.TAG_SEQUENCE,
     802                      new int[]
     803                      {
     804                          reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_SEQUENCE),
     805                          reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_SEQUENCE + 1)
     806                      });
     807        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagEventNumber, ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagEventNumber)));
     808
     809        byte seconds = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL);
     810        byte minutes = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 1);
     811        byte hour = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 2);
     812        byte day = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 3);
     813        byte month = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 4);
     814        /*ushort year = ByteConvert.FromBigEndianToNative(reader.GetUInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagDateTimeOriginal + 5));
     815        if ((seconds >= 0 && seconds < 60) &&
     816            (minutes >= 0 && minutes < 60) &&
     817            (hour >= 0 && hour < 24) &&
     818            (month >= 1 && month < 13) &&
     819            (day >= 1 && day < 32) &&
     820            (year >= 1 && year <= 9999))
     821        {
     822            directory.Set(ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL, new DateTime(year, month, day, hour, minutes, seconds, DateTimeKind.Unspecified));
     823        }
     824        else
     825        {
     826            directory.addError("Error processing Reconyx UltraFire makernote data: Date/Time Original " + year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + " is not a valid date/time.");
     827        }*/
     828        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagDayOfWeek, reader.GetByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagDayOfWeek));
     829
     830        directory.setInt(ReconyxUltraFireMakernoteDirectory.TAG_MOON_PHASE, reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_MOON_PHASE));
     831        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagAmbientTemperatureFahrenheit, ByteConvert.FromBigEndianToNative(reader.GetInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagAmbientTemperatureFahrenheit)));
     832        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagAmbientTemperature, ByteConvert.FromBigEndianToNative(reader.GetInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagAmbientTemperature)));
     833
     834        directory.setInt(ReconyxUltraFireMakernoteDirectory.TAG_FLASH, reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_FLASH));
     835        //directory.Set(ReconyxUltraFireMakernoteDirectory.TagBatteryVoltage, ByteConvert.FromBigEndianToNative(reader.GetUInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagBatteryVoltage)) / 1000.0);
     836        directory.setStringValue(ReconyxUltraFireMakernoteDirectory.TAG_SERIAL_NUMBER, new StringValue(reader.getBytes(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_SERIAL_NUMBER, 14), Charsets.UTF_8));
     837        // unread byte: the serial number's terminating null
     838        directory.setString(ReconyxUltraFireMakernoteDirectory.TAG_USER_LABEL, reader.getNullTerminatedString(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_USER_LABEL, 20, Charsets.UTF_8));
     839    }
    396840}
    397841
Note: See TracChangeset for help on using the changeset viewer.