Index: trunk/src/com/drew/metadata/exif/ExifDescriptorBase.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifDescriptorBase.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifDescriptorBase.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,4 +30,5 @@
 
 import java.io.UnsupportedEncodingException;
+import java.math.RoundingMode;
 import java.text.DecimalFormat;
 import java.util.HashMap;
@@ -51,6 +52,4 @@
     @NotNull
     private static final java.text.DecimalFormat SimpleDecimalFormatter = new DecimalFormat("0.#");
-    @NotNull
-    private static final java.text.DecimalFormat SimpleDecimalFormatterWithPrecision = new DecimalFormat("0.0");
 
     // Note for the potential addition of brightness presentation in eV:
@@ -206,4 +205,6 @@
             case TAG_JPEG_PROC:
                 return getJpegProcDescription();
+            case TAG_LENS_SPECIFICATION:
+                return getLensSpecificationDescription();
             default:
                 return super.getDescription(tagType);
@@ -509,4 +510,10 @@
 
     @Nullable
+    public String getLensSpecificationDescription()
+    {
+        return getLensSpecificationDescription(TAG_LENS_SPECIFICATION);
+    }
+
+    @Nullable
     public String getSharpnessDescription()
     {
@@ -568,6 +575,6 @@
             ? null
             : value == 0
-            ? "Unknown"
-            : SimpleDecimalFormatter.format(value) + "mm";
+                ? "Unknown"
+                : getFocalLengthDescription(value);
     }
 
@@ -579,6 +586,6 @@
             ? null
             : value.getNumerator() == 0
-            ? "Digital zoom not used."
-            : SimpleDecimalFormatter.format(value.doubleValue());
+                ? "Digital zoom not used"
+                : SimpleDecimalFormatter.format(value.doubleValue());
     }
 
@@ -711,5 +718,5 @@
             return null;
         double fStop = PhotographicConversions.apertureToFStop(aperture);
-        return "f/" + SimpleDecimalFormatterWithPrecision.format(fStop);
+        return getFStopDescription(fStop);
     }
 
@@ -721,5 +728,5 @@
             return null;
         double fStop = PhotographicConversions.apertureToFStop(aperture);
-        return "f/" + SimpleDecimalFormatterWithPrecision.format(fStop);
+        return getFStopDescription(fStop);
     }
 
@@ -807,8 +814,5 @@
     {
         Rational value = _directory.getRational(TAG_FOCAL_LENGTH);
-        if (value == null)
-            return null;
-        java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
-        return formatter.format(value.doubleValue()) + " mm";
+        return value == null ? null : getFocalLengthDescription(value.doubleValue());
     }
 
@@ -859,8 +863,5 @@
     public String getWhiteBalanceDescription()
     {
-        // '0' means unknown, '1' daylight, '2' fluorescent, '3' tungsten, '4' flash,
-        // '17' standard light A, '18' standard light B, '19' standard light C, '20' D55,
-        // '21' D65, '22' D75, '255' other.
-        // see http://web.archive.org/web/20131018091152/http://exif.org/Exif2-2.PDF page 35
+        // See http://web.archive.org/web/20131018091152/http://exif.org/Exif2-2.PDF page 35
         final Integer value = _directory.getInteger(TAG_WHITE_BALANCE);
         if (value == null)
@@ -875,9 +876,9 @@
             case 10: return "Cloudy";
             case 11: return "Shade";
-            case 12: return "Daylight Flourescent";
-            case 13: return "Day White Flourescent";
-            case 14: return "Cool White Flourescent";
-            case 15: return "White Flourescent";
-            case 16: return "Warm White Flourescent";
+            case 12: return "Daylight Fluorescent";
+            case 13: return "Day White Fluorescent";
+            case 14: return "Cool White Fluorescent";
+            case 15: return "White Fluorescent";
+            case 16: return "Warm White Fluorescent";
             case 17: return "Standard light";
             case 18: return "Standard light (B)";
@@ -975,5 +976,5 @@
         if (value == null)
             return null;
-        java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
+        DecimalFormat formatter = new DecimalFormat("0.0##");
         return formatter.format(value.doubleValue()) + " metres";
     }
@@ -1018,5 +1019,7 @@
             long apexPower10 = Math.round((double)apexPower * 10.0);
             float fApexPower = (float)apexPower10 / 10.0f;
-            return fApexPower + " sec";
+            DecimalFormat format = new DecimalFormat("0.##");
+            format.setRoundingMode(RoundingMode.HALF_UP);
+            return format.format(fApexPower) + " sec";
         } else {
             int apexPower = (int)((Math.exp(apexValue * Math.log(2))));
@@ -1051,5 +1054,5 @@
         if (value == null)
             return null;
-        return "f/" + SimpleDecimalFormatterWithPrecision.format(value.doubleValue());
+        return getFStopDescription(value.doubleValue());
     }
 
Index: trunk/src/com/drew/metadata/exif/ExifDirectoryBase.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifDirectoryBase.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifDirectoryBase.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -140,4 +140,8 @@
     public static final int TAG_TILE_BYTE_COUNTS                  = 0x0145;
 
+    /**
+     * Tag is a pointer to one or more sub-IFDs.
+     + Seems to be used exclusively by raw formats, referencing one or two IFDs.
+     */
     public static final int TAG_SUB_IFD_OFFSET                    = 0x014a;
 
@@ -150,4 +154,6 @@
     public static final int TAG_YCBCR_POSITIONING                 = 0x0213;
     public static final int TAG_REFERENCE_BLACK_WHITE             = 0x0214;
+    public static final int TAG_STRIP_ROW_COUNTS                  = 0x022f;
+    public static final int TAG_APPLICATION_NOTES                 = 0x02bc;
 
     public static final int TAG_RELATED_IMAGE_FILE_FORMAT         = 0x1000;
@@ -260,5 +266,4 @@
     public static final int TAG_METERING_MODE                     = 0x9207;
 
-    public static final int TAG_LIGHT_SOURCE                      = 0x9208; // TODO duplicate tag
     /**
      * White balance (aka light source). '0' means unknown, '1' daylight,
@@ -267,5 +272,5 @@
      * '22' D75, '255' other.
      */
-    public static final int TAG_WHITE_BALANCE                     = 0x9208; // TODO duplicate tag
+    public static final int TAG_WHITE_BALANCE                     = 0x9208;
     /**
      * 0x0  = 0000000 = No Flash
@@ -600,6 +605,6 @@
         map.put(TAG_ROWS_PER_STRIP, "Rows Per Strip");
         map.put(TAG_STRIP_BYTE_COUNTS, "Strip Byte Counts");
-        map.put(TAG_MIN_SAMPLE_VALUE, "Minimum sample value");
-        map.put(TAG_MAX_SAMPLE_VALUE, "Maximum sample value");
+        map.put(TAG_MIN_SAMPLE_VALUE, "Minimum Sample Value");
+        map.put(TAG_MAX_SAMPLE_VALUE, "Maximum Sample Value");
         map.put(TAG_X_RESOLUTION, "X Resolution");
         map.put(TAG_Y_RESOLUTION, "Y Resolution");
@@ -627,4 +632,6 @@
         map.put(TAG_YCBCR_POSITIONING, "YCbCr Positioning");
         map.put(TAG_REFERENCE_BLACK_WHITE, "Reference Black/White");
+        map.put(TAG_STRIP_ROW_COUNTS, "Strip Row Counts");
+        map.put(TAG_APPLICATION_NOTES, "Application Notes");
         map.put(TAG_RELATED_IMAGE_FILE_FORMAT, "Related Image File Format");
         map.put(TAG_RELATED_IMAGE_WIDTH, "Related Image Width");
@@ -663,5 +670,4 @@
         map.put(TAG_SUBJECT_DISTANCE, "Subject Distance");
         map.put(TAG_METERING_MODE, "Metering Mode");
-        map.put(TAG_LIGHT_SOURCE, "Light Source");
         map.put(TAG_WHITE_BALANCE, "White Balance");
         map.put(TAG_FLASH, "Flash");
Index: trunk/src/com/drew/metadata/exif/ExifIFD0Descriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifIFD0Descriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifIFD0Descriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/ExifIFD0Directory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifIFD0Directory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifIFD0Directory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,7 +22,7 @@
 package com.drew.metadata.exif;
 
+import java.util.HashMap;
+
 import com.drew.lang.annotations.NotNull;
-
-import java.util.HashMap;
 
 /**
Index: trunk/src/com/drew/metadata/exif/ExifInteropDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifInteropDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifInteropDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/ExifInteropDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifInteropDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifInteropDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/ExifReader.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifReader.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifReader.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,8 +28,10 @@
 import com.drew.lang.RandomAccessReader;
 import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
+import com.drew.metadata.Directory;
 import com.drew.metadata.Metadata;
 
 import java.io.IOException;
-import java.util.Arrays;
+import java.util.Collections;
 
 /**
@@ -60,5 +62,5 @@
     public Iterable<JpegSegmentType> getSegmentTypes()
     {
-        return Arrays.asList(JpegSegmentType.APP1);
+        return Collections.singletonList(JpegSegmentType.APP1);
     }
 
@@ -84,9 +86,15 @@
     public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int readerOffset)
     {
+        extract(reader, metadata, readerOffset, null);
+    }
+
+    /** Reads TIFF formatted Exif data a specified offset within a {@link RandomAccessReader}. */
+    public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int readerOffset, @Nullable Directory parentDirectory)
+    {
         try {
             // Read the TIFF-formatted Exif data
             new TiffReader().processTiff(
                 reader,
-                new ExifTiffHandler(metadata, _storeThumbnailBytes),
+                new ExifTiffHandler(metadata, _storeThumbnailBytes, parentDirectory),
                 readerOffset
             );
Index: trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/ExifSubIFDDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifSubIFDDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifSubIFDDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +21,10 @@
 package com.drew.metadata.exif;
 
+import java.util.Date;
+import java.util.HashMap;
+import java.util.TimeZone;
+
 import com.drew.lang.annotations.NotNull;
-
-import java.util.HashMap;
+import com.drew.lang.annotations.Nullable;
 
 /**
@@ -61,3 +64,59 @@
         return _tagNameMap;
     }
+
+    /**
+     * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
+     * representing the date and time when this image was captured.  Attempts will be made to parse the
+     * values as though it is in the GMT {@link TimeZone}.
+     *
+     * @return A Date object representing when this image was captured, if possible, otherwise null
+     */
+    @Nullable
+    public Date getDateOriginal()
+    {
+        return getDateOriginal(null);
+    }
+
+    /**
+     * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
+     * representing the date and time when this image was captured.  Attempts will be made to parse the
+     * values as though it is in the {@link TimeZone} represented by the {@code timeZone} parameter
+     * (if it is non-null).
+     *
+     * @param timeZone the time zone to use
+     * @return A Date object representing when this image was captured, if possible, otherwise null
+     */
+    @Nullable
+    public Date getDateOriginal(TimeZone timeZone)
+    {
+        return getDate(TAG_DATETIME_ORIGINAL, getString(TAG_SUBSECOND_TIME_ORIGINAL), timeZone);
+    }
+
+    /**
+     * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
+     * representing the date and time when this image was digitized.  Attempts will be made to parse the
+     * values as though it is in the GMT {@link TimeZone}.
+     *
+     * @return A Date object representing when this image was digitized, if possible, otherwise null
+     */
+    @Nullable
+    public Date getDateDigitized()
+    {
+        return getDateDigitized(null);
+    }
+
+    /**
+     * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
+     * representing the date and time when this image was digitized.  Attempts will be made to parse the
+     * values as though it is in the {@link TimeZone} represented by the {@code timeZone} parameter
+     * (if it is non-null).
+     *
+     * @param timeZone the time zone to use
+     * @return A Date object representing when this image was digitized, if possible, otherwise null
+     */
+    @Nullable
+    public Date getDateDigitized(TimeZone timeZone)
+    {
+        return getDate(TAG_DATETIME_DIGITIZED, getString(TAG_SUBSECOND_TIME_DIGITIZED), timeZone);
+    }
 }
Index: trunk/src/com/drew/metadata/exif/ExifThumbnailDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifThumbnailDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifThumbnailDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,8 +22,9 @@
 package com.drew.metadata.exif;
 
+import static com.drew.metadata.exif.ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH;
+import static com.drew.metadata.exif.ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET;
+
 import com.drew.lang.annotations.NotNull;
 import com.drew.lang.annotations.Nullable;
-
-import static com.drew.metadata.exif.ExifThumbnailDirectory.*;
 
 /**
@@ -48,47 +49,6 @@
             case TAG_THUMBNAIL_LENGTH:
                 return getThumbnailLengthDescription();
-            case TAG_THUMBNAIL_COMPRESSION:
-                return getCompressionDescription();
             default:
                 return super.getDescription(tagType);
-        }
-    }
-
-    @Nullable
-    public String getCompressionDescription()
-    {
-        Integer value = _directory.getInteger(TAG_THUMBNAIL_COMPRESSION);
-        if (value == null)
-            return null;
-        switch (value) {
-            case 1: return "Uncompressed";
-            case 2: return "CCITT 1D";
-            case 3: return "T4/Group 3 Fax";
-            case 4: return "T6/Group 4 Fax";
-            case 5: return "LZW";
-            case 6: return "JPEG (old-style)";
-            case 7: return "JPEG";
-            case 8: return "Adobe Deflate";
-            case 9: return "JBIG B&W";
-            case 10: return "JBIG Color";
-            case 32766: return "Next";
-            case 32771: return "CCIRLEW";
-            case 32773: return "PackBits";
-            case 32809: return "Thunderscan";
-            case 32895: return "IT8CTPAD";
-            case 32896: return "IT8LW";
-            case 32897: return "IT8MP";
-            case 32898: return "IT8BL";
-            case 32908: return "PixarFilm";
-            case 32909: return "PixarLog";
-            case 32946: return "Deflate";
-            case 32947: return "DCS";
-            case 32661: return "JBIG";
-            case 32676: return "SGILog";
-            case 32677: return "SGILog24";
-            case 32712: return "JPEG 2000";
-            case 32713: return "Nikon NEF Compressed";
-            default:
-                return "Unknown compression";
         }
     }
Index: trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -22,11 +22,11 @@
 package com.drew.metadata.exif;
 
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+
 import com.drew.lang.annotations.NotNull;
 import com.drew.lang.annotations.Nullable;
 import com.drew.metadata.MetadataException;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.HashMap;
 
 /**
@@ -46,36 +46,4 @@
     public static final int TAG_THUMBNAIL_LENGTH = 0x0202;
 
-    /**
-     * Shows compression method for Thumbnail.
-     * 1 = Uncompressed
-     * 2 = CCITT 1D
-     * 3 = T4/Group 3 Fax
-     * 4 = T6/Group 4 Fax
-     * 5 = LZW
-     * 6 = JPEG (old-style)
-     * 7 = JPEG
-     * 8 = Adobe Deflate
-     * 9 = JBIG B&amp;W
-     * 10 = JBIG Color
-     * 32766 = Next
-     * 32771 = CCIRLEW
-     * 32773 = PackBits
-     * 32809 = Thunderscan
-     * 32895 = IT8CTPAD
-     * 32896 = IT8LW
-     * 32897 = IT8MP
-     * 32898 = IT8BL
-     * 32908 = PixarFilm
-     * 32909 = PixarLog
-     * 32946 = Deflate
-     * 32947 = DCS
-     * 34661 = JBIG
-     * 34676 = SGILog
-     * 34677 = SGILog24
-     * 34712 = JPEG 2000
-     * 34713 = Nikon NEF Compressed
-     */
-    public static final int TAG_THUMBNAIL_COMPRESSION = 0x0103;
-
     @NotNull
     protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
@@ -85,5 +53,4 @@
         addExifTagNames(_tagNameMap);
 
-        _tagNameMap.put(TAG_THUMBNAIL_COMPRESSION, "Thumbnail Compression");
         _tagNameMap.put(TAG_THUMBNAIL_OFFSET, "Thumbnail Offset");
         _tagNameMap.put(TAG_THUMBNAIL_LENGTH, "Thumbnail Length");
Index: trunk/src/com/drew/metadata/exif/ExifTiffHandler.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifTiffHandler.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/ExifTiffHandler.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,4 +26,5 @@
 import com.drew.lang.SequentialByteArrayReader;
 import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
 import com.drew.metadata.Directory;
 import com.drew.metadata.Metadata;
@@ -47,8 +48,11 @@
     private final boolean _storeThumbnailBytes;
 
-    public ExifTiffHandler(@NotNull Metadata metadata, boolean storeThumbnailBytes)
+    public ExifTiffHandler(@NotNull Metadata metadata, boolean storeThumbnailBytes, @Nullable Directory parentDirectory)
     {
         super(metadata, ExifIFD0Directory.class);
         _storeThumbnailBytes = storeThumbnailBytes;
+
+        if (parentDirectory != null)
+            _currentDirectory.setParent(parentDirectory);
     }
 
@@ -65,15 +69,40 @@
     }
 
-    public boolean isTagIfdPointer(int tagType)
-    {
-        if (tagType == ExifIFD0Directory.TAG_EXIF_SUB_IFD_OFFSET && _currentDirectory instanceof ExifIFD0Directory) {
+    public boolean tryEnterSubIfd(int tagId)
+    {
+        if (tagId == ExifDirectoryBase.TAG_SUB_IFD_OFFSET) {
             pushDirectory(ExifSubIFDDirectory.class);
             return true;
-        } else if (tagType == ExifIFD0Directory.TAG_GPS_INFO_OFFSET && _currentDirectory instanceof ExifIFD0Directory) {
-            pushDirectory(GpsDirectory.class);
-            return true;
-        } else if (tagType == ExifSubIFDDirectory.TAG_INTEROP_OFFSET && _currentDirectory instanceof ExifSubIFDDirectory) {
-            pushDirectory(ExifInteropDirectory.class);
-            return true;
+        }
+
+        if (_currentDirectory instanceof ExifIFD0Directory) {
+            if (tagId == ExifIFD0Directory.TAG_EXIF_SUB_IFD_OFFSET) {
+                pushDirectory(ExifSubIFDDirectory.class);
+                return true;
+            }
+
+            if (tagId == ExifIFD0Directory.TAG_GPS_INFO_OFFSET) {
+                pushDirectory(GpsDirectory.class);
+                return true;
+            }
+        }
+
+        if (_currentDirectory instanceof ExifSubIFDDirectory) {
+            if (tagId == ExifSubIFDDirectory.TAG_INTEROP_OFFSET) {
+                pushDirectory(ExifInteropDirectory.class);
+                return true;
+            }
+        }
+
+        if (_currentDirectory instanceof OlympusMakernoteDirectory) {
+            if (tagId == OlympusMakernoteDirectory.TAG_EQUIPMENT) {
+                pushDirectory(OlympusEquipmentMakernoteDirectory.class);
+                return true;
+            }
+
+            if (tagId == OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS) {
+                pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
+                return true;
+            }
         }
 
@@ -96,4 +125,13 @@
         // NOTE have seen the CanonMakernoteDirectory IFD have a follower pointer, but it points to invalid data.
         return false;
+    }
+
+    @Nullable
+    public Long tryCustomProcessFormat(final int tagId, final int formatCode, final long componentCount)
+    {
+        if (formatCode == 13)
+            return componentCount * 4;
+
+        return null;
     }
 
@@ -115,5 +153,5 @@
             if (reader.getInt8(tagOffset) == 0x1c) {
                 final byte[] iptcBytes = reader.getBytes(tagOffset, byteCount);
-                new IptcReader().extract(new SequentialByteArrayReader(iptcBytes), _metadata, iptcBytes.length);
+                new IptcReader().extract(new SequentialByteArrayReader(iptcBytes), _metadata, iptcBytes.length, _currentDirectory);
                 return true;
             }
@@ -129,5 +167,5 @@
             // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
             ExifThumbnailDirectory thumbnailDirectory = _metadata.getFirstDirectoryOfType(ExifThumbnailDirectory.class);
-            if (thumbnailDirectory != null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION)) {
+            if (thumbnailDirectory != null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_COMPRESSION)) {
                 Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
                 Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
@@ -164,13 +202,20 @@
         final String firstSevenChars = reader.getString(makernoteOffset, 7);
         final String firstEightChars = reader.getString(makernoteOffset, 8);
+        final String firstTenChars = reader.getString(makernoteOffset, 10);
         final String firstTwelveChars = reader.getString(makernoteOffset, 12);
 
         boolean byteOrderBefore = reader.isMotorolaByteOrder();
 
-        if ("OLYMP".equals(firstFiveChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {
+        if ("OLYMP\0".equals(firstSixChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {
             // Olympus Makernote
             // Epson and Agfa use Olympus makernote standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/
             pushDirectory(OlympusMakernoteDirectory.class);
             TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
+        } else if ("OLYMPUS\0II".equals(firstTenChars)) {
+            // Olympus Makernote (alternate)
+            // Note that data is relative to the beginning of the makernote
+            // http://exiv2.org/makernote.html
+            pushDirectory(OlympusMakernoteDirectory.class);
+            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, makernoteOffset);
         } else if (cameraMake != null && cameraMake.toUpperCase().startsWith("MINOLTA")) {
             // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote
Index: trunk/src/com/drew/metadata/exif/GpsDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/GpsDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/GpsDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -110,5 +110,5 @@
         // time in hour, min, sec
         Rational[] timeComponents = _directory.getRationalArray(TAG_TIME_STAMP);
-        DecimalFormat df = new DecimalFormat("00.00");
+        DecimalFormat df = new DecimalFormat("00.000");
         return timeComponents == null
             ? null
Index: trunk/src/com/drew/metadata/exif/GpsDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/GpsDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/GpsDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,5 +26,10 @@
 import com.drew.lang.annotations.Nullable;
 
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.HashMap;
+import java.util.Locale;
 
 /**
@@ -164,8 +169,8 @@
     public GeoLocation getGeoLocation()
     {
-        Rational[] latitudes = getRationalArray(GpsDirectory.TAG_LATITUDE);
-        Rational[] longitudes = getRationalArray(GpsDirectory.TAG_LONGITUDE);
-        String latitudeRef = getString(GpsDirectory.TAG_LATITUDE_REF);
-        String longitudeRef = getString(GpsDirectory.TAG_LONGITUDE_REF);
+        Rational[] latitudes = getRationalArray(TAG_LATITUDE);
+        Rational[] longitudes = getRationalArray(TAG_LONGITUDE);
+        String latitudeRef = getString(TAG_LATITUDE_REF);
+        String longitudeRef = getString(TAG_LONGITUDE_REF);
 
         // Make sure we have the required values
@@ -186,3 +191,31 @@
         return new GeoLocation(lat, lon);
     }
+
+    /**
+     * Parses the date stamp tag and the time stamp tag to obtain a single Date object representing the
+     * date and time when this image was captured.
+     *
+     * @return A Date object representing when this image was captured, if possible, otherwise null
+     */
+    @Nullable
+    public Date getGpsDate()
+    {
+        String date = getString(TAG_DATE_STAMP);
+        Rational[] timeComponents = getRationalArray(TAG_TIME_STAMP);
+
+        // Make sure we have the required values
+        if (date == null)
+            return null;
+        if (timeComponents == null || timeComponents.length != 3)
+            return null;
+
+        String dateTime = String.format(Locale.US, "%s %02d:%02d:%02.3f UTC",
+            date, timeComponents[0].intValue(), timeComponents[1].intValue(), timeComponents[2].doubleValue());
+        try {
+            DateFormat parser = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss.S z");
+            return parser.parse(dateTime);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
 }
Index: trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,4 +24,6 @@
 import com.drew.lang.annotations.Nullable;
 import com.drew.metadata.TagDescriptor;
+
+import java.text.DecimalFormat;
 
 import static com.drew.metadata.exif.makernotes.CanonMakernoteDirectory.*;
@@ -138,4 +140,5 @@
     public String getSerialNumberDescription()
     {
+        // http://www.ozhiker.com/electronics/pjmt/jpeg_info/canon_mn.html
         Integer value = _directory.getInteger(TAG_CANON_SERIAL_NUMBER);
         if (value == null)
@@ -673,6 +676,6 @@
             return "Self timer not used";
         } else {
-            // TODO find an image that tests this calculation
-            return Double.toString((double)value * 0.1d) + " sec";
+            DecimalFormat format = new DecimalFormat("0.##");
+            return format.format((double)value * 0.1d) + " sec";
         }
     }
Index: trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -155,9 +155,5 @@
     {
         Integer value = _directory.getInteger(TAG_OBJECT_DISTANCE);
-
-        if (value == null)
-            return null;
-
-        return value + " mm";
+        return value == null ? null : getFocalLengthDescription(value);
     }
 
Index: trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -240,7 +240,5 @@
     {
         Double value = _directory.getDoubleObject(TAG_FOCAL_LENGTH);
-        if (value == null)
-            return null;
-        return Double.toString(value / 10d) + " mm";
+        return value == null ? null : getFocalLengthDescription(value / 10d);
     }
 
Index: trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -306,5 +306,5 @@
     {
         int[] values = _directory.getIntArray(tagType);
-        if (values == null)
+        if (values == null || values.length < 2)
             return null;
         if (values.length < 3 || values[2] == 0)
@@ -329,12 +329,5 @@
     public String getLensDescription()
     {
-        Rational[] values = _directory.getRationalArray(TAG_LENS);
-
-        return values == null
-            ? null
-            : values.length < 4
-                ? _directory.getString(TAG_LENS)
-                : String.format("%d-%dmm f/%.1f-%.1f", values[0].intValue(), values[1].intValue(), values[2].floatValue(), values[3].floatValue());
-
+        return getLensSpecificationDescription(TAG_LENS);
     }
 
Index: trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.java	(revision 10862)
+++ trunk/src/com/drew/metadata/exif/makernotes/OlympusCameraSettingsMakernoteDescriptor.java	(revision 10862)
@@ -0,0 +1,1357 @@
+/*
+ * Copyright 2002-2015 Drew Noakes
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ *    https://drewnoakes.com/code/exif/
+ *    https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.metadata.exif.makernotes;
+
+import com.drew.lang.Rational;
+import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
+import com.drew.metadata.TagDescriptor;
+
+import java.text.DecimalFormat;
+import java.util.HashMap;
+
+import static com.drew.metadata.exif.makernotes.OlympusCameraSettingsMakernoteDirectory.*;
+
+/**
+ * Provides human-readable String representations of tag values stored in a {@link OlympusCameraSettingsMakernoteDirectory}.
+ * <p>
+ * Some Description functions and the Extender and Lens types lists converted from Exiftool version 10.10 created by Phil Harvey
+ * http://www.sno.phy.queensu.ca/~phil/exiftool/
+ * lib\Image\ExifTool\Olympus.pm
+ *
+ * @author Kevin Mott https://github.com/kwhopper
+ * @author Drew Noakes https://drewnoakes.com
+ */
+public class OlympusCameraSettingsMakernoteDescriptor extends TagDescriptor<OlympusCameraSettingsMakernoteDirectory>
+{
+    public OlympusCameraSettingsMakernoteDescriptor(@NotNull OlympusCameraSettingsMakernoteDirectory directory)
+    {
+        super(directory);
+    }
+
+    @Override
+    @Nullable
+    public String getDescription(int tagType)
+    {
+        switch (tagType) {
+            case TagCameraSettingsVersion:
+                return getCameraSettingsVersionDescription();
+            case TagPreviewImageValid:
+                return getPreviewImageValidDescription();
+
+            case TagExposureMode:
+                return getExposureModeDescription();
+            case TagAeLock:
+                return getAeLockDescription();
+            case TagMeteringMode:
+                return getMeteringModeDescription();
+            case TagExposureShift:
+                return getExposureShiftDescription();
+            case TagNdFilter:
+                return getNdFilterDescription();
+
+            case TagMacroMode:
+                return getMacroModeDescription();
+            case TagFocusMode:
+                return getFocusModeDescription();
+            case TagFocusProcess:
+                return getFocusProcessDescription();
+            case TagAfSearch:
+                return getAfSearchDescription();
+            case TagAfAreas:
+                return getAfAreasDescription();
+            case TagAfPointSelected:
+                return getAfPointSelectedDescription();
+            case TagAfFineTune:
+                return getAfFineTuneDescription();
+
+            case TagFlashMode:
+                return getFlashModeDescription();
+            case TagFlashRemoteControl:
+                return getFlashRemoteControlDescription();
+            case TagFlashControlMode:
+                return getFlashControlModeDescription();
+            case TagFlashIntensity:
+                return getFlashIntensityDescription();
+            case TagManualFlashStrength:
+                return getManualFlashStrengthDescription();
+
+            case TagWhiteBalance2:
+                return getWhiteBalance2Description();
+            case TagWhiteBalanceTemperature:
+                return getWhiteBalanceTemperatureDescription();
+            case TagCustomSaturation:
+                return getCustomSaturationDescription();
+            case TagModifiedSaturation:
+                return getModifiedSaturationDescription();
+            case TagContrastSetting:
+                return getContrastSettingDescription();
+            case TagSharpnessSetting:
+                return getSharpnessSettingDescription();
+            case TagColorSpace:
+                return getColorSpaceDescription();
+            case TagSceneMode:
+                return getSceneModeDescription();
+            case TagNoiseReduction:
+                return getNoiseReductionDescription();
+            case TagDistortionCorrection:
+                return getDistortionCorrectionDescription();
+            case TagShadingCompensation:
+                return getShadingCompensationDescription();
+            case TagGradation:
+                return getGradationDescription();
+            case TagPictureMode:
+                return getPictureModeDescription();
+            case TagPictureModeSaturation:
+                return getPictureModeSaturationDescription();
+            case TagPictureModeContrast:
+                return getPictureModeContrastDescription();
+            case TagPictureModeSharpness:
+                return getPictureModeSharpnessDescription();
+            case TagPictureModeBWFilter:
+                return getPictureModeBWFilterDescription();
+            case TagPictureModeTone:
+                return getPictureModeToneDescription();
+            case TagNoiseFilter:
+                return getNoiseFilterDescription();
+            case TagArtFilter:
+                return getArtFilterDescription();
+            case TagMagicFilter:
+                return getMagicFilterDescription();
+            case TagPictureModeEffect:
+                return getPictureModeEffectDescription();
+            case TagToneLevel:
+                return getToneLevelDescription();
+            case TagArtFilterEffect:
+                return getArtFilterEffectDescription();
+
+            case TagDriveMode:
+                return getDriveModeDescription();
+            case TagPanoramaMode:
+                return getPanoramaModeDescription();
+            case TagImageQuality2:
+                return getImageQuality2Description();
+            case TagImageStabilization:
+                return getImageStabilizationDescription();
+
+            case TagStackedImage:
+                return getStackedImageDescription();
+
+            case TagManometerPressure:
+                return getManometerPressureDescription();
+            case TagManometerReading:
+                return getManometerReadingDescription();
+            case TagExtendedWBDetect:
+                return getExtendedWBDetectDescription();
+            case TagRollAngle:
+                return getRollAngleDescription();
+            case TagPitchAngle:
+                return getPitchAngleDescription();
+            case TagDateTimeUtc:
+                return getDateTimeUTCDescription();
+
+            default:
+                return super.getDescription(tagType);
+        }
+    }
+
+    @Nullable
+    public String getCameraSettingsVersionDescription()
+    {
+        return getVersionBytesDescription(TagCameraSettingsVersion, 4);
+    }
+
+    @Nullable
+    public String getPreviewImageValidDescription()
+    {
+        return getIndexedDescription(TagPreviewImageValid,
+            "No", "Yes");
+    }
+
+    @Nullable
+    public String getExposureModeDescription()
+    {
+        return getIndexedDescription(TagExposureMode, 1,
+            "Manual", "Program", "Aperture-priority AE", "Shutter speed priority", "Program-shift");
+    }
+
+    @Nullable
+    public String getAeLockDescription()
+    {
+        return getIndexedDescription(TagAeLock,
+            "Off", "On");
+    }
+
+    @Nullable
+    public String getMeteringModeDescription()
+    {
+        Integer value = _directory.getInteger(TagMeteringMode);
+        if (value == null)
+            return null;
+
+        switch (value) {
+            case 2:
+                return "Center-weighted average";
+            case 3:
+                return "Spot";
+            case 5:
+                return "ESP";
+            case 261:
+                return "Pattern+AF";
+            case 515:
+                return "Spot+Highlight control";
+            case 1027:
+                return "Spot+Shadow control";
+            default:
+                return "Unknown (" + value + ")";
+        }
+    }
+
+    @Nullable
+    public String getExposureShiftDescription()
+    {
+        return getRationalOrDoubleString(TagExposureShift);
+    }
+
+    @Nullable
+    public String getNdFilterDescription()
+    {
+        return getIndexedDescription(TagNdFilter, "Off", "On");
+    }
+
+    @Nullable
+    public String getMacroModeDescription()
+    {
+        return getIndexedDescription(TagMacroMode, "Off", "On", "Super Macro");
+    }
+
+    @Nullable
+    public String getFocusModeDescription()
+    {
+        int[] values = _directory.getIntArray(TagFocusMode);
+        if (values == null) {
+            // check if it's only one value long also
+            Integer value = _directory.getInteger(TagFocusMode);
+            if (value == null)
+                return null;
+
+            values = new int[]{value};
+        }
+
+        if (values.length == 0)
+            return null;
+
+        StringBuilder sb = new StringBuilder();
+        switch (values[0]) {
+            case 0:
+                sb.append("Single AF");
+                break;
+            case 1:
+                sb.append("Sequential shooting AF");
+                break;
+            case 2:
+                sb.append("Continuous AF");
+                break;
+            case 3:
+                sb.append("Multi AF");
+                break;
+            case 4:
+                sb.append("Face detect");
+                break;
+            case 10:
+                sb.append("MF");
+                break;
+            default:
+                sb.append("Unknown (" + values[0] + ")");
+                break;
+        }
+
+        if (values.length > 1) {
+            sb.append("; ");
+            int value1 = values[1];
+
+            if (value1 == 0) {
+                sb.append("(none)");
+            } else {
+                if (( value1       & 1) > 0) sb.append("S-AF, ");
+                if (((value1 >> 2) & 1) > 0) sb.append("C-AF, ");
+                if (((value1 >> 4) & 1) > 0) sb.append("MF, ");
+                if (((value1 >> 5) & 1) > 0) sb.append("Face detect, ");
+                if (((value1 >> 6) & 1) > 0) sb.append("Imager AF, ");
+                if (((value1 >> 7) & 1) > 0) sb.append("Live View Magnification Frame, ");
+                if (((value1 >> 8) & 1) > 0) sb.append("AF sensor, ");
+
+                sb.setLength(sb.length() - 2);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    @Nullable
+    public String getFocusProcessDescription()
+    {
+        int[] values = _directory.getIntArray(TagFocusProcess);
+        if (values == null) {
+            // check if it's only one value long also
+            Integer value = _directory.getInteger(TagFocusProcess);
+            if (value == null)
+                return null;
+
+            values = new int[]{value};
+        }
+
+        if (values.length == 0)
+            return null;
+
+        StringBuilder sb = new StringBuilder();
+
+        switch (values[0]) {
+            case 0:
+                sb.append("AF not used");
+                break;
+            case 1:
+                sb.append("AF used");
+                break;
+            default:
+                sb.append("Unknown (" + values[0] + ")");
+                break;
+        }
+
+        if (values.length > 1)
+            sb.append("; " + values[1]);
+
+        return sb.toString();
+    }
+
+    @Nullable
+    public String getAfSearchDescription()
+    {
+        return getIndexedDescription(TagAfSearch, "Not Ready", "Ready");
+    }
+
+    /// <summary>
+    /// coordinates range from 0 to 255
+    /// </summary>
+    /// <returns></returns>
+    @Nullable
+    public String getAfAreasDescription()
+    {
+        Object obj = _directory.getObject(TagAfAreas);
+        if (obj == null || !(obj instanceof long[]))
+            return null;
+
+        StringBuilder sb = new StringBuilder();
+        for (long point : (long[]) obj) {
+            if (point == 0L)
+                continue;
+            if (sb.length() != 0)
+                sb.append(", ");
+
+            if (point == 0x36794285L)
+                sb.append("Left ");
+            else if (point == 0x79798585L)
+                sb.append("Center ");
+            else if (point == 0xBD79C985L)
+                sb.append("Right ");
+
+            sb.append(String.format("(%d/255,%d/255)-(%d/255,%d/255)",
+                (point >> 24) & 0xFF,
+                (point >> 16) & 0xFF,
+                (point >> 8) & 0xFF,
+                point & 0xFF));
+        }
+
+        return sb.length() == 0 ? null : sb.toString();
+    }
+
+    /// <summary>
+    /// coordinates expressed as a percent
+    /// </summary>
+    /// <returns></returns>
+    @Nullable
+    public String getAfPointSelectedDescription()
+    {
+        Rational[] values = _directory.getRationalArray(TagAfPointSelected);
+        if (values == null)
+            return "n/a";
+
+        if (values.length < 4)
+            return null;
+
+        int index = 0;
+        if (values.length == 5 && values[0].longValue() == 0)
+            index = 1;
+
+        int p1 = (int)(values[index].doubleValue() * 100);
+        int p2 = (int)(values[index + 1].doubleValue() * 100);
+        int p3 = (int)(values[index + 2].doubleValue() * 100);
+        int p4 = (int)(values[index + 3].doubleValue() * 100);
+
+        return String.format("(%d%%,%d%%) (%d%%,%d%%)", p1, p2, p3, p4);
+
+    }
+
+    @Nullable
+    public String getAfFineTuneDescription()
+    {
+        return getIndexedDescription(TagAfFineTune, "Off", "On");
+    }
+
+    @Nullable
+    public String getFlashModeDescription()
+    {
+        Integer value = _directory.getInteger(TagFlashMode);
+        if (value == null)
+            return null;
+
+        if (value == 0)
+            return "Off";
+
+        StringBuilder sb = new StringBuilder();
+        int v = value;
+
+        if (( v       & 1) != 0) sb.append("On, ");
+        if (((v >> 1) & 1) != 0) sb.append("Fill-in, ");
+        if (((v >> 2) & 1) != 0) sb.append("Red-eye, ");
+        if (((v >> 3) & 1) != 0) sb.append("Slow-sync, ");
+        if (((v >> 4) & 1) != 0) sb.append("Forced On, ");
+        if (((v >> 5) & 1) != 0) sb.append("2nd Curtain, ");
+
+        return sb.substring(0, sb.length() - 2);
+    }
+
+    @Nullable
+    public String getFlashRemoteControlDescription()
+    {
+        Integer value = _directory.getInteger(TagFlashRemoteControl);
+        if (value == null)
+            return null;
+
+        switch (value) {
+            case 0:
+                return "Off";
+            case 0x01:
+                return "Channel 1, Low";
+            case 0x02:
+                return "Channel 2, Low";
+            case 0x03:
+                return "Channel 3, Low";
+            case 0x04:
+                return "Channel 4, Low";
+            case 0x09:
+                return "Channel 1, Mid";
+            case 0x0a:
+                return "Channel 2, Mid";
+            case 0x0b:
+                return "Channel 3, Mid";
+            case 0x0c:
+                return "Channel 4, Mid";
+            case 0x11:
+                return "Channel 1, High";
+            case 0x12:
+                return "Channel 2, High";
+            case 0x13:
+                return "Channel 3, High";
+            case 0x14:
+                return "Channel 4, High";
+
+            default:
+                return "Unknown (" + value + ")";
+        }
+    }
+
+    /// <summary>
+    /// 3 or 4 values
+    /// </summary>
+    /// <returns></returns>
+    @Nullable
+    public String getFlashControlModeDescription()
+    {
+        int[] values = _directory.getIntArray(TagFlashControlMode);
+        if (values == null)
+            return null;
+
+        if (values.length == 0)
+            return null;
+
+        StringBuilder sb = new StringBuilder();
+
+        switch (values[0]) {
+            case 0:
+                sb.append("Off");
+                break;
+            case 3:
+                sb.append("TTL");
+                break;
+            case 4:
+                sb.append("Auto");
+                break;
+            case 5:
+                sb.append("Manual");
+                break;
+            default:
+                sb.append("Unknown (").append(values[0]).append(")");
+                break;
+        }
+
+        for (int i = 1; i < values.length; i++)
+            sb.append("; ").append(values[i]);
+
+        return sb.toString();
+    }
+
+    /// <summary>
+    /// 3 or 4 values
+    /// </summary>
+    /// <returns></returns>
+    @Nullable
+    public String getFlashIntensityDescription()
+    {
+        Rational[] values = _directory.getRationalArray(TagFlashIntensity);
+        if (values == null || values.length == 0)
+            return null;
+
+        if (values.length == 3) {
+            if (values[0].getDenominator() == 0 && values[1].getDenominator() == 0 && values[2].getDenominator() == 0)
+                return "n/a";
+        } else if (values.length == 4) {
+            if (values[0].getDenominator() == 0 && values[1].getDenominator() == 0 && values[2].getDenominator() == 0 && values[3].getDenominator() == 0)
+                return "n/a (x4)";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (Rational t : values)
+            sb.append(t).append(", ");
+
+        return sb.substring(0, sb.length() - 2);
+    }
+
+    @Nullable
+    public String getManualFlashStrengthDescription()
+    {
+        Rational[] values = _directory.getRationalArray(TagManualFlashStrength);
+        if (values == null || values.length == 0)
+            return "n/a";
+
+        if (values.length == 3) {
+            if (values[0].getDenominator() == 0 && values[1].getDenominator() == 0 && values[2].getDenominator() == 0)
+                return "n/a";
+        } else if (values.length == 4) {
+            if (values[0].getDenominator() == 0 && values[1].getDenominator() == 0 && values[2].getDenominator() == 0 && values[3].getDenominator() == 0)
+                return "n/a (x4)";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (Rational t : values)
+            sb.append(t).append(", ");
+
+        return sb.substring(0, sb.length() - 2);
+    }
+
+    @Nullable
+    public String getWhiteBalance2Description()
+    {
+        Integer value = _directory.getInteger(TagWhiteBalance2);
+        if (value == null)
+            return null;
+
+        switch (value) {
+            case 0:
+                return "Auto";
+            case 1:
+                return "Auto (Keep Warm Color Off)";
+            case 16:
+                return "7500K (Fine Weather with Shade)";
+            case 17:
+                return "6000K (Cloudy)";
+            case 18:
+                return "5300K (Fine Weather)";
+            case 20:
+                return "3000K (Tungsten light)";
+            case 21:
+                return "3600K (Tungsten light-like)";
+            case 22:
+                return "Auto Setup";
+            case 23:
+                return "5500K (Flash)";
+            case 33:
+                return "6600K (Daylight fluorescent)";
+            case 34:
+                return "4500K (Neutral white fluorescent)";
+            case 35:
+                return "4000K (Cool white fluorescent)";
+            case 36:
+                return "White Fluorescent";
+            case 48:
+                return "3600K (Tungsten light-like)";
+            case 67:
+                return "Underwater";
+            case 256:
+                return "One Touch WB 1";
+            case 257:
+                return "One Touch WB 2";
+            case 258:
+                return "One Touch WB 3";
+            case 259:
+                return "One Touch WB 4";
+            case 512:
+                return "Custom WB 1";
+            case 513:
+                return "Custom WB 2";
+            case 514:
+                return "Custom WB 3";
+            case 515:
+                return "Custom WB 4";
+            default:
+                return "Unknown (" + value + ")";
+        }
+    }
+
+    @Nullable
+    public String getWhiteBalanceTemperatureDescription()
+    {
+        Integer value = _directory.getInteger(TagWhiteBalanceTemperature);
+        if (value == null)
+            return null;
+        if (value == 0)
+            return "Auto";
+        return value.toString();
+    }
+
+    @Nullable
+    public String getCustomSaturationDescription()
+    {
+        // TODO: if model is /^E-1\b/  then
+        // $a-=$b; $c-=$b;
+        // return "CS$a (min CS0, max CS$c)"
+        return getValueMinMaxDescription(TagCustomSaturation);
+    }
+
+    @Nullable
+    public String getModifiedSaturationDescription()
+    {
+        return getIndexedDescription(TagModifiedSaturation,
+            "Off", "CM1 (Red Enhance)", "CM2 (Green Enhance)", "CM3 (Blue Enhance)", "CM4 (Skin Tones)");
+    }
+
+    @Nullable
+    public String getContrastSettingDescription()
+    {
+        return getValueMinMaxDescription(TagContrastSetting);
+    }
+
+    @Nullable
+    public String getSharpnessSettingDescription()
+    {
+        return getValueMinMaxDescription(TagSharpnessSetting);
+    }
+
+    @Nullable
+    public String getColorSpaceDescription()
+    {
+        return getIndexedDescription(TagColorSpace,
+            "sRGB", "Adobe RGB", "Pro Photo RGB");
+    }
+
+    @Nullable
+    public String getSceneModeDescription()
+    {
+        Integer value = _directory.getInteger(TagSceneMode);
+        if (value == null)
+            return null;
+
+        switch (value) {
+            case 0:
+                return "Standard";
+            case 6:
+                return "Auto";
+            case 7:
+                return "Sport";
+            case 8:
+                return "Portrait";
+            case 9:
+                return "Landscape+Portrait";
+            case 10:
+                return "Landscape";
+            case 11:
+                return "Night Scene";
+            case 12:
+                return "Self Portrait";
+            case 13:
+                return "Panorama";
+            case 14:
+                return "2 in 1";
+            case 15:
+                return "Movie";
+            case 16:
+                return "Landscape+Portrait";
+            case 17:
+                return "Night+Portrait";
+            case 18:
+                return "Indoor";
+            case 19:
+                return "Fireworks";
+            case 20:
+                return "Sunset";
+            case 21:
+                return "Beauty Skin";
+            case 22:
+                return "Macro";
+            case 23:
+                return "Super Macro";
+            case 24:
+                return "Food";
+            case 25:
+                return "Documents";
+            case 26:
+                return "Museum";
+            case 27:
+                return "Shoot & Select";
+            case 28:
+                return "Beach & Snow";
+            case 29:
+                return "Self Portrait+Timer";
+            case 30:
+                return "Candle";
+            case 31:
+                return "Available Light";
+            case 32:
+                return "Behind Glass";
+            case 33:
+                return "My Mode";
+            case 34:
+                return "Pet";
+            case 35:
+                return "Underwater Wide1";
+            case 36:
+                return "Underwater Macro";
+            case 37:
+                return "Shoot & Select1";
+            case 38:
+                return "Shoot & Select2";
+            case 39:
+                return "High Key";
+            case 40:
+                return "Digital Image Stabilization";
+            case 41:
+                return "Auction";
+            case 42:
+                return "Beach";
+            case 43:
+                return "Snow";
+            case 44:
+                return "Underwater Wide2";
+            case 45:
+                return "Low Key";
+            case 46:
+                return "Children";
+            case 47:
+                return "Vivid";
+            case 48:
+                return "Nature Macro";
+            case 49:
+                return "Underwater Snapshot";
+            case 50:
+                return "Shooting Guide";
+            case 54:
+                return "Face Portrait";
+            case 57:
+                return "Bulb";
+            case 59:
+                return "Smile Shot";
+            case 60:
+                return "Quick Shutter";
+            case 63:
+                return "Slow Shutter";
+            case 64:
+                return "Bird Watching";
+            case 65:
+                return "Multiple Exposure";
+            case 66:
+                return "e-Portrait";
+            case 67:
+                return "Soft Background Shot";
+            case 142:
+                return "Hand-held Starlight";
+            case 154:
+                return "HDR";
+            default:
+                return "Unknown (" + value + ")";
+        }
+    }
+
+    @Nullable
+    public String getNoiseReductionDescription()
+    {
+        Integer value = _directory.getInteger(TagNoiseReduction);
+        if (value == null)
+            return null;
+
+        if (value == 0)
+            return "(none)";
+
+        StringBuilder sb = new StringBuilder();
+        int v = value;
+
+        if ((v & 1) != 0) sb.append("Noise Reduction, ");
+        if (((v >> 1) & 1) != 0) sb.append("Noise Filter, ");
+        if (((v >> 2) & 1) != 0) sb.append("Noise Filter (ISO Boost), ");
+        if (((v >> 3) & 1) != 0) sb.append("Auto, ");
+
+        return sb.length() != 0
+            ? sb.substring(0, sb.length() - 2)
+            : "(none)";
+    }
+
+    @Nullable
+    public String getDistortionCorrectionDescription()
+    {
+        return getIndexedDescription(TagDistortionCorrection, "Off", "On");
+    }
+
+    @Nullable
+    public String getShadingCompensationDescription()
+    {
+        return getIndexedDescription(TagShadingCompensation, "Off", "On");
+    }
+
+    /// <summary>
+    /// 3 or 4 values
+    /// </summary>
+    /// <returns></returns>
+    @Nullable
+    public String getGradationDescription()
+    {
+        int[] values = _directory.getIntArray(TagGradation);
+        if (values == null || values.length < 3)
+            return null;
+
+        String join = String.format("%d %d %d", values[0], values[1], values[2]);
+
+        String ret;
+        if (join.equals("0 0 0")) {
+            ret = "n/a";
+        } else if (join.equals("-1 -1 1")) {
+            ret = "Low Key";
+        } else if (join.equals("0 -1 1")) {
+            ret = "Normal";
+        } else if (join.equals("1 -1 1")) {
+            ret = "High Key";
+        } else {
+            ret = "Unknown (" + join + ")";
+        }
+
+        if (values.length > 3) {
+            if (values[3] == 0)
+                ret += "; User-Selected";
+            else if (values[3] == 1)
+                ret += "; Auto-Override";
+        }
+
+        return ret;
+    }
+
+    /// <summary>
+    /// 1 or 2 values
+    /// </summary>
+    /// <returns></returns>
+    @Nullable
+    public String getPictureModeDescription()
+    {
+        int[] values = _directory.getIntArray(TagPictureMode);
+        if (values == null) {
+            // check if it's only one value long also
+            Integer value = _directory.getInteger(TagNoiseReduction);
+            if (value == null)
+                return null;
+
+            values = new int[]{value};
+        }
+
+        if (values.length == 0)
+            return null;
+
+        StringBuilder sb = new StringBuilder();
+        switch (values[0]) {
+            case 1:
+                sb.append("Vivid");
+                break;
+            case 2:
+                sb.append("Natural");
+                break;
+            case 3:
+                sb.append("Muted");
+                break;
+            case 4:
+                sb.append("Portrait");
+                break;
+            case 5:
+                sb.append("i-Enhance");
+                break;
+            case 256:
+                sb.append("Monotone");
+                break;
+            case 512:
+                sb.append("Sepia");
+                break;
+            default:
+                sb.append("Unknown (").append(values[0]).append(")");
+                break;
+        }
+
+        if (values.length > 1)
+            sb.append("; ").append(values[1]);
+
+        return sb.toString();
+    }
+
+    @Nullable
+    public String getPictureModeSaturationDescription()
+    {
+        return getValueMinMaxDescription(TagPictureModeSaturation);
+    }
+
+    @Nullable
+    public String getPictureModeContrastDescription()
+    {
+        return getValueMinMaxDescription(TagPictureModeContrast);
+    }
+
+    @Nullable
+    public String getPictureModeSharpnessDescription()
+    {
+        return getValueMinMaxDescription(TagPictureModeSharpness);
+    }
+
+    @Nullable
+    public String getPictureModeBWFilterDescription()
+    {
+        return getIndexedDescription(TagPictureModeBWFilter,
+            "n/a", "Neutral", "Yellow", "Orange", "Red", "Green");
+    }
+
+    @Nullable
+    public String getPictureModeToneDescription()
+    {
+        return getIndexedDescription(TagPictureModeTone,
+            "n/a", "Neutral", "Sepia", "Blue", "Purple", "Green");
+    }
+
+    @Nullable
+    public String getNoiseFilterDescription()
+    {
+        int[] values = _directory.getIntArray(TagNoiseFilter);
+        if (values == null)
+            return null;
+
+        String join = String.format("%d %d %d", values[0], values[1], values[2]);
+
+        if (join.equals("0 0 0"))
+            return "n/a";
+        if (join.equals("-2 -2 1"))
+            return "Off";
+        if (join.equals("-1 -2 1"))
+            return "Low";
+        if (join.equals("0 -2 1"))
+            return "Standard";
+        if (join.equals("1 -2 1"))
+            return "High";
+        return "Unknown (" + join + ")";
+    }
+
+    @Nullable
+    public String getArtFilterDescription()
+    {
+        return getFiltersDescription(TagArtFilter);
+    }
+
+    @Nullable
+    public String getMagicFilterDescription()
+    {
+        return getFiltersDescription(TagMagicFilter);
+    }
+
+    @Nullable
+    public String getPictureModeEffectDescription()
+    {
+        int[] values = _directory.getIntArray(TagPictureModeEffect);
+        if (values == null)
+            return null;
+
+        String key = String.format("%d %d %d", values[0], values[1], values[2]);
+        if (key.equals("0 0 0"))
+            return "n/a";
+        if (key.equals("-1 -1 1"))
+            return "Low";
+        if (key.equals("0 -1 1"))
+            return "Standard";
+        if (key.equals("1 -1 1"))
+            return "High";
+        return "Unknown (" + key + ")";
+    }
+
+    @Nullable
+    public String getToneLevelDescription()
+    {
+        int[] values = _directory.getIntArray(TagToneLevel);
+        if (values == null || values.length == 0)
+            return null;
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < values.length; i++) {
+            if (i == 1)
+                sb.append("Highlights ");
+            else if (i == 5)
+                sb.append("Shadows ");
+
+            sb.append(values[i]).append("; ");
+        }
+
+        return sb.substring(0, sb.length() - 2);
+    }
+
+    @Nullable
+    public String getArtFilterEffectDescription()
+    {
+        int[] values = _directory.getIntArray(TagArtFilterEffect);
+        if (values == null || values.length == 0)
+            return null;
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < values.length; i++) {
+            if (i == 0) {
+                sb.append((_filters.containsKey(values[i]) ? _filters.get(values[i]) : "[unknown]"));
+            } else if (i == 4) {
+                switch (values[i]) {
+                    case 0x0000:
+                        sb.append("No Effect");
+                        break;
+                    case 0x8010:
+                        sb.append("Star Light");
+                        break;
+                    case 0x8020:
+                        sb.append("Pin Hole");
+                        break;
+                    case 0x8030:
+                        sb.append("Frame");
+                        break;
+                    case 0x8040:
+                        sb.append("Soft Focus");
+                        break;
+                    case 0x8050:
+                        sb.append("White Edge");
+                        break;
+                    case 0x8060:
+                        sb.append("B&W");
+                        break;
+                    default:
+                        sb.append("Unknown (").append(values[i]).append(")");
+                        break;
+                }
+            } else {
+                sb.append(values[i]);
+            }
+            sb.append("; ");
+        }
+
+        return sb.substring(0, sb.length() - 2);
+    }
+
+    /// <summary>
+    /// 2 or 3 numbers: 1. Mode, 2. Shot number, 3. Mode bits
+    /// </summary>
+    /// <returns></returns>
+    @Nullable
+    public String getDriveModeDescription()
+    {
+        int[] values = _directory.getIntArray(TagDriveMode);
+        if (values == null)
+            return null;
+
+        if (values.length == 0 || values[0] == 0)
+            return "Single Shot";
+
+        StringBuilder a = new StringBuilder();
+
+        if (values[0] == 5 && values.length >= 3) {
+            int c = values[2];
+            if (( c       & 1) > 0) a.append("AE");
+            if (((c >> 1) & 1) > 0) a.append("WB");
+            if (((c >> 2) & 1) > 0) a.append("FL");
+            if (((c >> 3) & 1) > 0) a.append("MF");
+            if (((c >> 6) & 1) > 0) a.append("Focus");
+
+            a.append(" Bracketing");
+        } else {
+            switch (values[0]) {
+                case 1:
+                    a.append("Continuous Shooting");
+                    break;
+                case 2:
+                    a.append("Exposure Bracketing");
+                    break;
+                case 3:
+                    a.append("White Balance Bracketing");
+                    break;
+                case 4:
+                    a.append("Exposure+WB Bracketing");
+                    break;
+                default:
+                    a.append("Unknown (").append(values[0]).append(")");
+                    break;
+            }
+        }
+
+        a.append(", Shot ").append(values[1]);
+
+        return a.toString();
+    }
+
+    /// <summary>
+    /// 2 numbers: 1. Mode, 2. Shot number
+    /// </summary>
+    /// <returns></returns>
+    @Nullable
+    public String getPanoramaModeDescription()
+    {
+        int[] values = _directory.getIntArray(TagPanoramaMode);
+        if (values == null)
+            return null;
+
+        if (values.length == 0 || values[0] == 0)
+            return "Off";
+
+        String a;
+        switch (values[0]) {
+            case 1:
+                a = "Left to Right";
+                break;
+            case 2:
+                a = "Right to Left";
+                break;
+            case 3:
+                a = "Bottom to Top";
+                break;
+            case 4:
+                a = "Top to Bottom";
+                break;
+            default:
+                a = "Unknown (" + values[0] + ")";
+                break;
+        }
+
+        return String.format("%s, Shot %d", a, values[1]);
+    }
+
+    @Nullable
+    public String getImageQuality2Description()
+    {
+        return getIndexedDescription(TagImageQuality2, 1,
+            "SQ", "HQ", "SHQ", "RAW", "SQ (5)");
+    }
+
+    @Nullable
+    public String getImageStabilizationDescription()
+    {
+        return getIndexedDescription(TagImageStabilization,
+            "Off", "On, Mode 1", "On, Mode 2", "On, Mode 3", "On, Mode 4");
+    }
+
+    @Nullable
+    public String getStackedImageDescription()
+    {
+        int[] values = _directory.getIntArray(TagStackedImage);
+        if (values == null || values.length < 2)
+            return null;
+
+        int v1 = values[0];
+        int v2 = values[1];
+
+        if (v1 == 0 && v2 == 0)
+            return "No";
+        if (v1 == 9 && v2 == 8)
+            return "Focus-stacked (8 images)";
+
+        return String.format("Unknown (%d %d)", v1, v2);
+    }
+
+    /// <remarks>
+    /// TODO: need better image examples to test this function
+    /// </remarks>
+    /// <returns></returns>
+    @Nullable
+    public String getManometerPressureDescription()
+    {
+        Integer value = _directory.getInteger(TagManometerPressure);
+        if (value == null)
+            return null;
+
+        return String.format("%s kPa", new DecimalFormat("#.##").format(value / 10.0));
+    }
+
+    /// <remarks>
+    /// TODO: need better image examples to test this function
+    /// </remarks>
+    /// <returns></returns>
+    @Nullable
+    public String getManometerReadingDescription()
+    {
+        int[] values = _directory.getIntArray(TagManometerReading);
+        if (values == null || values.length < 2)
+            return null;
+
+        DecimalFormat format = new DecimalFormat("#.##");
+        return String.format("%s m, %s ft",
+            format.format(values[0] / 10.0),
+            format.format(values[1] / 10.0));
+    }
+
+    @Nullable
+    public String getExtendedWBDetectDescription()
+    {
+        return getIndexedDescription(TagExtendedWBDetect, "Off", "On");
+    }
+
+    /// <summary>
+    /// converted to degrees of clockwise camera rotation
+    /// </summary>
+    /// <remarks>
+    /// TODO: need better image examples to test this function
+    /// </remarks>
+    /// <returns></returns>
+    @Nullable
+    public String getRollAngleDescription()
+    {
+        int[] values = _directory.getIntArray(TagRollAngle);
+        if (values == null || values.length < 2)
+            return null;
+
+        String ret = values[0] != 0
+            ? Double.toString(-values[0] / 10.0)
+            : "n/a";
+
+        return String.format("%s %d", ret, values[1]);
+    }
+
+    /// <summary>
+    /// converted to degrees of upward camera tilt
+    /// </summary>
+    /// <remarks>
+    /// TODO: need better image examples to test this function
+    /// </remarks>
+    /// <returns></returns>
+    @Nullable
+    public String getPitchAngleDescription()
+    {
+        int[] values = _directory.getIntArray(TagPitchAngle);
+        if (values == null || values.length < 2)
+            return null;
+
+        // (second value is 0 if level gauge is off)
+        String ret = values[0] != 0
+            ? Double.toString(values[0] / 10.0)
+            : "n/a";
+
+        return String.format("%s %d", ret, values[1]);
+    }
+
+    @Nullable
+    public String getDateTimeUTCDescription()
+    {
+        Object value = _directory.getObject(TagDateTimeUtc);
+        if (value == null)
+            return null;
+        return value.toString();
+    }
+
+    @Nullable
+    private String getValueMinMaxDescription(int tagId)
+    {
+        int[] values = _directory.getIntArray(tagId);
+        if (values == null || values.length < 3)
+            return null;
+
+        return String.format("%d (min %d, max %d)", values[0], values[1], values[2]);
+    }
+
+    private String getFiltersDescription(int tagId)
+    {
+        int[] values = _directory.getIntArray(tagId);
+        if (values == null || values.length == 0)
+            return null;
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < values.length; i++) {
+            if (i == 0)
+                sb.append(_filters.containsKey(values[i]) ? _filters.get(values[i]) : "[unknown]");
+            else
+                sb.append(values[i]);
+            sb.append("; ");
+        }
+
+        return sb.substring(0, sb.length() - 2);
+    }
+
+    // ArtFilter, ArtFilterEffect and MagicFilter values
+    private static final HashMap<Integer, String> _filters = new HashMap<Integer, String>();
+
+    static {
+        _filters.put(0, "Off");
+        _filters.put(1, "Soft Focus");
+        _filters.put(2, "Pop Art");
+        _filters.put(3, "Pale & Light Color");
+        _filters.put(4, "Light Tone");
+        _filters.put(5, "Pin Hole");
+        _filters.put(6, "Grainy Film");
+        _filters.put(9, "Diorama");
+        _filters.put(10, "Cross Process");
+        _filters.put(12, "Fish Eye");
+        _filters.put(13, "Drawing");
+        _filters.put(14, "Gentle Sepia");
+        _filters.put(15, "Pale & Light Color II");
+        _filters.put(16, "Pop Art II");
+        _filters.put(17, "Pin Hole II");
+        _filters.put(18, "Pin Hole III");
+        _filters.put(19, "Grainy Film II");
+        _filters.put(20, "Dramatic Tone");
+        _filters.put(21, "Punk");
+        _filters.put(22, "Soft Focus 2");
+        _filters.put(23, "Sparkle");
+        _filters.put(24, "Watercolor");
+        _filters.put(25, "Key Line");
+        _filters.put(26, "Key Line II");
+        _filters.put(27, "Miniature");
+        _filters.put(28, "Reflection");
+        _filters.put(29, "Fragmented");
+        _filters.put(31, "Cross Process II");
+        _filters.put(32, "Dramatic Tone II");
+        _filters.put(33, "Watercolor I");
+        _filters.put(34, "Watercolor II");
+        _filters.put(35, "Diorama II");
+        _filters.put(36, "Vintage");
+        _filters.put(37, "Vintage II");
+        _filters.put(38, "Vintage III");
+        _filters.put(39, "Partial Color");
+        _filters.put(40, "Partial Color II");
+        _filters.put(41, "Partial Color III");
+    }
+}
Index: trunk/src/com/drew/metadata/exif/makernotes/OlympusCameraSettingsMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/OlympusCameraSettingsMakernoteDirectory.java	(revision 10862)
+++ trunk/src/com/drew/metadata/exif/makernotes/OlympusCameraSettingsMakernoteDirectory.java	(revision 10862)
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2002-2015 Drew Noakes
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ *    https://drewnoakes.com/code/exif/
+ *    https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.metadata.exif.makernotes;
+
+import com.drew.lang.annotations.NotNull;
+import com.drew.metadata.Directory;
+
+import java.util.HashMap;
+
+/**
+ * The Olympus camera settings makernote is used by many manufacturers (Epson, Konica, Minolta and Agfa...), and as such contains some tags
+ * that appear specific to those manufacturers.
+ *
+ * @author Kevin Mott https://github.com/kwhopper
+ * @author Drew Noakes https://drewnoakes.com
+ */
+public class OlympusCameraSettingsMakernoteDirectory extends Directory
+{
+    public static final int TagCameraSettingsVersion = 0x0000;
+    public static final int TagPreviewImageValid = 0x0100;
+    public static final int TagPreviewImageStart = 0x0101;
+    public static final int TagPreviewImageLength = 0x0102;
+
+    public static final int TagExposureMode = 0x0200;
+    public static final int TagAeLock = 0x0201;
+    public static final int TagMeteringMode = 0x0202;
+    public static final int TagExposureShift = 0x0203;
+    public static final int TagNdFilter = 0x0204;
+
+    public static final int TagMacroMode = 0x0300;
+    public static final int TagFocusMode = 0x0301;
+    public static final int TagFocusProcess = 0x0302;
+    public static final int TagAfSearch = 0x0303;
+    public static final int TagAfAreas = 0x0304;
+    public static final int TagAfPointSelected = 0x0305;
+    public static final int TagAfFineTune = 0x0306;
+    public static final int TagAfFineTuneAdj = 0x0307;
+
+    public static final int TagFlashMode = 0x400;
+    public static final int TagFlashExposureComp = 0x401;
+    public static final int TagFlashRemoteControl = 0x403;
+    public static final int TagFlashControlMode = 0x404;
+    public static final int TagFlashIntensity = 0x405;
+    public static final int TagManualFlashStrength = 0x406;
+
+    public static final int TagWhiteBalance2 = 0x500;
+    public static final int TagWhiteBalanceTemperature = 0x501;
+    public static final int TagWhiteBalanceBracket = 0x502;
+    public static final int TagCustomSaturation = 0x503;
+    public static final int TagModifiedSaturation = 0x504;
+    public static final int TagContrastSetting = 0x505;
+    public static final int TagSharpnessSetting = 0x506;
+    public static final int TagColorSpace = 0x507;
+    public static final int TagSceneMode = 0x509;
+    public static final int TagNoiseReduction = 0x50a;
+    public static final int TagDistortionCorrection = 0x50b;
+    public static final int TagShadingCompensation = 0x50c;
+    public static final int TagCompressionFactor = 0x50d;
+    public static final int TagGradation = 0x50f;
+    public static final int TagPictureMode = 0x520;
+    public static final int TagPictureModeSaturation = 0x521;
+    public static final int TagPictureModeHue = 0x522;
+    public static final int TagPictureModeContrast = 0x523;
+    public static final int TagPictureModeSharpness = 0x524;
+    public static final int TagPictureModeBWFilter = 0x525;
+    public static final int TagPictureModeTone = 0x526;
+    public static final int TagNoiseFilter = 0x527;
+    public static final int TagArtFilter = 0x529;
+    public static final int TagMagicFilter = 0x52c;
+    public static final int TagPictureModeEffect = 0x52d;
+    public static final int TagToneLevel = 0x52e;
+    public static final int TagArtFilterEffect = 0x52f;
+
+    public static final int TagDriveMode = 0x600;
+    public static final int TagPanoramaMode = 0x601;
+    public static final int TagImageQuality2 = 0x603;
+    public static final int TagImageStabilization = 0x604;
+
+    public static final int TagStackedImage = 0x804;
+
+    public static final int TagManometerPressure = 0x900;
+    public static final int TagManometerReading = 0x901;
+    public static final int TagExtendedWBDetect = 0x902;
+    public static final int TagRollAngle = 0x903;
+    public static final int TagPitchAngle = 0x904;
+    public static final int TagDateTimeUtc = 0x908;
+
+    @NotNull
+    protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
+
+    static {
+        _tagNameMap.put(TagCameraSettingsVersion, "Camera Settings Version");
+        _tagNameMap.put(TagPreviewImageValid, "Preview Image Valid");
+        _tagNameMap.put(TagPreviewImageStart, "Preview Image Start");
+        _tagNameMap.put(TagPreviewImageLength, "Preview Image Length");
+
+        _tagNameMap.put(TagExposureMode, "Exposure Mode");
+        _tagNameMap.put(TagAeLock, "AE Lock");
+        _tagNameMap.put(TagMeteringMode, "Metering Mode");
+        _tagNameMap.put(TagExposureShift, "Exposure Shift");
+        _tagNameMap.put(TagNdFilter, "ND Filter");
+
+        _tagNameMap.put(TagMacroMode, "Macro Mode");
+        _tagNameMap.put(TagFocusMode, "Focus Mode");
+        _tagNameMap.put(TagFocusProcess, "Focus Process");
+        _tagNameMap.put(TagAfSearch, "AF Search");
+        _tagNameMap.put(TagAfAreas, "AF Areas");
+        _tagNameMap.put(TagAfPointSelected, "AF Point Selected");
+        _tagNameMap.put(TagAfFineTune, "AF Fine Tune");
+        _tagNameMap.put(TagAfFineTuneAdj, "AF Fine Tune Adj");
+
+        _tagNameMap.put(TagFlashMode, "Flash Mode");
+        _tagNameMap.put(TagFlashExposureComp, "Flash Exposure Comp");
+        _tagNameMap.put(TagFlashRemoteControl, "Flash Remote Control");
+        _tagNameMap.put(TagFlashControlMode, "Flash Control Mode");
+        _tagNameMap.put(TagFlashIntensity, "Flash Intensity");
+        _tagNameMap.put(TagManualFlashStrength, "Manual Flash Strength");
+
+        _tagNameMap.put(TagWhiteBalance2, "White Balance 2");
+        _tagNameMap.put(TagWhiteBalanceTemperature, "White Balance Temperature");
+        _tagNameMap.put(TagWhiteBalanceBracket, "White Balance Bracket");
+        _tagNameMap.put(TagCustomSaturation, "Custom Saturation");
+        _tagNameMap.put(TagModifiedSaturation, "Modified Saturation");
+        _tagNameMap.put(TagContrastSetting, "Contrast Setting");
+        _tagNameMap.put(TagSharpnessSetting, "Sharpness Setting");
+        _tagNameMap.put(TagColorSpace, "Color Space");
+        _tagNameMap.put(TagSceneMode, "Scene Mode");
+        _tagNameMap.put(TagNoiseReduction, "Noise Reduction");
+        _tagNameMap.put(TagDistortionCorrection, "Distortion Correction");
+        _tagNameMap.put(TagShadingCompensation, "Shading Compensation");
+        _tagNameMap.put(TagCompressionFactor, "Compression Factor");
+        _tagNameMap.put(TagGradation, "Gradation");
+        _tagNameMap.put(TagPictureMode, "Picture Mode");
+        _tagNameMap.put(TagPictureModeSaturation, "Picture Mode Saturation");
+        _tagNameMap.put(TagPictureModeHue, "Picture Mode Hue");
+        _tagNameMap.put(TagPictureModeContrast, "Picture Mode Contrast");
+        _tagNameMap.put(TagPictureModeSharpness, "Picture Mode Sharpness");
+        _tagNameMap.put(TagPictureModeBWFilter, "Picture Mode BW Filter");
+        _tagNameMap.put(TagPictureModeTone, "Picture Mode Tone");
+        _tagNameMap.put(TagNoiseFilter, "Noise Filter");
+        _tagNameMap.put(TagArtFilter, "Art Filter");
+        _tagNameMap.put(TagMagicFilter, "Magic Filter");
+        _tagNameMap.put(TagPictureModeEffect, "Picture Mode Effect");
+        _tagNameMap.put(TagToneLevel, "Tone Level");
+        _tagNameMap.put(TagArtFilterEffect, "Art Filter Effect");
+
+        _tagNameMap.put(TagDriveMode, "Drive Mode");
+        _tagNameMap.put(TagPanoramaMode, "Panorama Mode");
+        _tagNameMap.put(TagImageQuality2, "Image Quality 2");
+        _tagNameMap.put(TagImageStabilization, "Image Stabilization");
+
+        _tagNameMap.put(TagStackedImage, "Stacked Image");
+
+        _tagNameMap.put(TagManometerPressure, "Manometer Pressure");
+        _tagNameMap.put(TagManometerReading, "Manometer Reading");
+        _tagNameMap.put(TagExtendedWBDetect, "Extended WB Detect");
+        _tagNameMap.put(TagRollAngle, "Roll Angle");
+        _tagNameMap.put(TagPitchAngle, "Pitch Angle");
+        _tagNameMap.put(TagDateTimeUtc, "Date Time UTC");
+    }
+
+    public OlympusCameraSettingsMakernoteDirectory()
+    {
+        this.setDescriptor(new OlympusCameraSettingsMakernoteDescriptor(this));
+    }
+
+    @Override
+    @NotNull
+    public String getName()
+    {
+        return "Olympus Camera Settings";
+    }
+
+    @Override
+    @NotNull
+    protected HashMap<Integer, String> getTagNameMap()
+    {
+        return _tagNameMap;
+    }
+}
Index: trunk/src/com/drew/metadata/exif/makernotes/OlympusEquipmentMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/OlympusEquipmentMakernoteDescriptor.java	(revision 10862)
+++ trunk/src/com/drew/metadata/exif/makernotes/OlympusEquipmentMakernoteDescriptor.java	(revision 10862)
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2002-2015 Drew Noakes
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ *    https://drewnoakes.com/code/exif/
+ *    https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.metadata.exif.makernotes;
+
+import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
+import com.drew.metadata.TagDescriptor;
+
+import java.text.DecimalFormat;
+import java.util.HashMap;
+
+import static com.drew.metadata.exif.makernotes.OlympusEquipmentMakernoteDirectory.*;
+
+/**
+ * Provides human-readable String representations of tag values stored in a {@link OlympusEquipmentMakernoteDirectory}.
+ * <p>
+ * Some Description functions and the Extender and Lens types lists converted from Exiftool version 10.10 created by Phil Harvey
+ * http://www.sno.phy.queensu.ca/~phil/exiftool/
+ * lib\Image\ExifTool\Olympus.pm
+ *
+ * @author Kevin Mott https://github.com/kwhopper
+ * @author Drew Noakes https://drewnoakes.com
+ */
+public class OlympusEquipmentMakernoteDescriptor extends TagDescriptor<OlympusEquipmentMakernoteDirectory>
+{
+    public OlympusEquipmentMakernoteDescriptor(@NotNull OlympusEquipmentMakernoteDirectory directory)
+    {
+        super(directory);
+    }
+
+    @Override
+    @Nullable
+    public String getDescription(int tagType)
+    {
+        switch (tagType) {
+            case TAG_EQUIPMENT_VERSION:
+                return GetEquipmentVersionDescription();
+            case TAG_FOCAL_PLANE_DIAGONAL:
+                return GetFocalPlaneDiagonalDescription();
+            case TAG_BODY_FIRMWARE_VERSION:
+                return GetBodyFirmwareVersionDescription();
+            case TAG_LENS_TYPE:
+                return GetLensTypeDescription();
+            case TAG_LENS_FIRMWARE_VERSION:
+                return GetLensFirmwareVersionDescription();
+            case TAG_MAX_APERTURE_AT_MIN_FOCAL:
+                return GetMaxApertureAtMinFocalDescription();
+            case TAG_MAX_APERTURE_AT_MAX_FOCAL:
+                return GetMaxApertureAtMaxFocalDescription();
+            case TAG_MAX_APERTURE:
+                return GetMaxApertureDescription();
+            case TAG_LENS_PROPERTIES:
+                return GetLensPropertiesDescription();
+            case TAG_EXTENDER:
+                return GetExtenderDescription();
+            case TAG_FLASH_TYPE:
+                return GetFlashTypeDescription();
+            case TAG_FLASH_MODEL:
+                return GetFlashModelDescription();
+            default:
+                return super.getDescription(tagType);
+        }
+    }
+
+    @Nullable
+    public String GetEquipmentVersionDescription()
+    {
+        return getVersionBytesDescription(TAG_EQUIPMENT_VERSION, 4);
+    }
+
+    @Nullable
+    public String GetFocalPlaneDiagonalDescription()
+    {
+        return _directory.getString(TAG_FOCAL_PLANE_DIAGONAL) + " mm";
+    }
+
+    @Nullable
+    public String GetBodyFirmwareVersionDescription()
+    {
+        Integer value = _directory.getInteger(TAG_BODY_FIRMWARE_VERSION);
+        if (value == null)
+            return null;
+
+        String hex = String.format("%04X", value);
+        return String.format("%s.%s",
+            hex.substring(0, hex.length() - 3),
+            hex.substring(hex.length() - 3));
+    }
+
+    @Nullable
+    public String GetLensTypeDescription()
+    {
+        String str = _directory.getString(TAG_LENS_TYPE);
+
+        if (str == null)
+            return null;
+
+        // The String contains six numbers:
+        //
+        // - Make
+        // - Unknown
+        // - Model
+        // - Sub-model
+        // - Unknown
+        // - Unknown
+        //
+        // Only the Make, Model and Sub-model are used to identify the lens type
+        String[] values = str.split(" ");
+
+        if (values.length < 6)
+            return null;
+
+        try {
+            int num1 = Integer.parseInt(values[0]);
+            int num2 = Integer.parseInt(values[2]);
+            int num3 = Integer.parseInt(values[3]);
+            return _olympusLensTypes.get(String.format("%X %02X %02X", num1, num2, num3));
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    @Nullable
+    public String GetLensFirmwareVersionDescription()
+    {
+        Integer value = _directory.getInteger(TAG_LENS_FIRMWARE_VERSION);
+        if (value == null)
+            return null;
+
+        String hex = String.format("%04X", value);
+        return String.format("%s.%s",
+            hex.substring(0, hex.length() - 3),
+            hex.substring(hex.length() - 3));
+    }
+
+    @Nullable
+    public String GetMaxApertureAtMinFocalDescription()
+    {
+        Integer value = _directory.getInteger(TAG_MAX_APERTURE_AT_MIN_FOCAL);
+        if (value == null)
+            return null;
+
+        DecimalFormat format = new DecimalFormat("0.#");
+        return format.format(CalcMaxAperture(value));
+    }
+
+    @Nullable
+    public String GetMaxApertureAtMaxFocalDescription()
+    {
+        Integer value = _directory.getInteger(TAG_MAX_APERTURE_AT_MAX_FOCAL);
+        if (value == null)
+            return null;
+
+        DecimalFormat format = new DecimalFormat("0.#");
+        return format.format(CalcMaxAperture(value));
+    }
+
+    @Nullable
+    public String GetMaxApertureDescription()
+    {
+        Integer value = _directory.getInteger(TAG_MAX_APERTURE);
+        if (value == null)
+            return null;
+
+        DecimalFormat format = new DecimalFormat("0.#");
+        return format.format(CalcMaxAperture(value));
+    }
+
+    private static double CalcMaxAperture(int value)
+    {
+        return Math.pow(Math.sqrt(2.00), value / 256.0);
+    }
+
+    @Nullable
+    public String GetLensPropertiesDescription()
+    {
+        Integer value = _directory.getInteger(TAG_LENS_PROPERTIES);
+        if (value == null)
+            return null;
+
+        return String.format("0x%04X", value);
+    }
+
+    @Nullable
+    public String GetExtenderDescription()
+    {
+        String str = _directory.getString(TAG_EXTENDER);
+
+        if (str == null)
+            return null;
+
+        // The String contains six numbers:
+        //
+        // - Make
+        // - Unknown
+        // - Model
+        // - Sub-model
+        // - Unknown
+        // - Unknown
+        //
+        // Only the Make and Model are used to identify the extender
+        String[] values = str.split(" ");
+
+        if (values.length < 6)
+            return null;
+
+        try {
+            int num1 = Integer.parseInt(values[0]);
+            int num2 = Integer.parseInt(values[2]);
+            String extenderType = String.format("%X %02X", num1, num2);
+            return _olympusExtenderTypes.get(extenderType);
+        } catch (NumberFormatException e) {
+            return null;
+        }
+    }
+
+    @Nullable
+    public String GetFlashTypeDescription()
+    {
+        return getIndexedDescription(TAG_FLASH_TYPE,
+            "None", null, "Simple E-System", "E-System");
+    }
+
+    @Nullable
+    public String GetFlashModelDescription()
+    {
+        return getIndexedDescription(TAG_FLASH_MODEL,
+            "None", "FL-20", "FL-50", "RF-11", "TF-22", "FL-36", "FL-50R", "FL-36R");
+    }
+
+    private static final HashMap<String, String> _olympusLensTypes = new HashMap<String, String>();
+    private static final HashMap<String, String> _olympusExtenderTypes = new HashMap<String, String>();
+
+    static {
+        _olympusLensTypes.put("0 00 00", "None");
+        // Olympus lenses (also Kenko Tokina)
+        _olympusLensTypes.put("0 01 00", "Olympus Zuiko Digital ED 50mm F2.0 Macro");
+        _olympusLensTypes.put("0 01 01", "Olympus Zuiko Digital 40-150mm F3.5-4.5"); //8
+        _olympusLensTypes.put("0 01 10", "Olympus M.Zuiko Digital ED 14-42mm F3.5-5.6"); //PH (E-P1 pre-production)
+        _olympusLensTypes.put("0 02 00", "Olympus Zuiko Digital ED 150mm F2.0");
+        _olympusLensTypes.put("0 02 10", "Olympus M.Zuiko Digital 17mm F2.8 Pancake"); //PH (E-P1 pre-production)
+        _olympusLensTypes.put("0 03 00", "Olympus Zuiko Digital ED 300mm F2.8");
+        _olympusLensTypes.put("0 03 10", "Olympus M.Zuiko Digital ED 14-150mm F4.0-5.6 [II]"); //11 (The second version of this lens seems to have the same lens ID number as the first version #20)
+        _olympusLensTypes.put("0 04 10", "Olympus M.Zuiko Digital ED 9-18mm F4.0-5.6"); //11
+        _olympusLensTypes.put("0 05 00", "Olympus Zuiko Digital 14-54mm F2.8-3.5");
+        _olympusLensTypes.put("0 05 01", "Olympus Zuiko Digital Pro ED 90-250mm F2.8"); //9
+        _olympusLensTypes.put("0 05 10", "Olympus M.Zuiko Digital ED 14-42mm F3.5-5.6 L"); //11 (E-PL1)
+        _olympusLensTypes.put("0 06 00", "Olympus Zuiko Digital ED 50-200mm F2.8-3.5");
+        _olympusLensTypes.put("0 06 01", "Olympus Zuiko Digital ED 8mm F3.5 Fisheye"); //9
+        _olympusLensTypes.put("0 06 10", "Olympus M.Zuiko Digital ED 40-150mm F4.0-5.6"); //PH
+        _olympusLensTypes.put("0 07 00", "Olympus Zuiko Digital 11-22mm F2.8-3.5");
+        _olympusLensTypes.put("0 07 01", "Olympus Zuiko Digital 18-180mm F3.5-6.3"); //6
+        _olympusLensTypes.put("0 07 10", "Olympus M.Zuiko Digital ED 12mm F2.0"); //PH
+        _olympusLensTypes.put("0 08 01", "Olympus Zuiko Digital 70-300mm F4.0-5.6"); //7 (seen as release 1 - PH)
+        _olympusLensTypes.put("0 08 10", "Olympus M.Zuiko Digital ED 75-300mm F4.8-6.7"); //PH
+        _olympusLensTypes.put("0 09 10", "Olympus M.Zuiko Digital 14-42mm F3.5-5.6 II"); //PH (E-PL2)
+        _olympusLensTypes.put("0 10 01", "Kenko Tokina Reflex 300mm F6.3 MF Macro"); //20
+        _olympusLensTypes.put("0 10 10", "Olympus M.Zuiko Digital ED 12-50mm F3.5-6.3 EZ"); //PH
+        _olympusLensTypes.put("0 11 10", "Olympus M.Zuiko Digital 45mm F1.8"); //17
+        _olympusLensTypes.put("0 12 10", "Olympus M.Zuiko Digital ED 60mm F2.8 Macro"); //20
+        _olympusLensTypes.put("0 13 10", "Olympus M.Zuiko Digital 14-42mm F3.5-5.6 II R"); //PH/20
+        _olympusLensTypes.put("0 14 10", "Olympus M.Zuiko Digital ED 40-150mm F4.0-5.6 R"); //19
+        // '0 14 10.1", "Olympus M.Zuiko Digital ED 14-150mm F4.0-5.6 II"); //11 (questionable & unconfirmed -- all samples I can find are '0 3 10' - PH)
+        _olympusLensTypes.put("0 15 00", "Olympus Zuiko Digital ED 7-14mm F4.0");
+        _olympusLensTypes.put("0 15 10", "Olympus M.Zuiko Digital ED 75mm F1.8"); //PH
+        _olympusLensTypes.put("0 16 10", "Olympus M.Zuiko Digital 17mm F1.8"); //20
+        _olympusLensTypes.put("0 17 00", "Olympus Zuiko Digital Pro ED 35-100mm F2.0"); //7
+        _olympusLensTypes.put("0 18 00", "Olympus Zuiko Digital 14-45mm F3.5-5.6");
+        _olympusLensTypes.put("0 18 10", "Olympus M.Zuiko Digital ED 75-300mm F4.8-6.7 II"); //20
+        _olympusLensTypes.put("0 19 10", "Olympus M.Zuiko Digital ED 12-40mm F2.8 Pro"); //PH
+        _olympusLensTypes.put("0 20 00", "Olympus Zuiko Digital 35mm F3.5 Macro"); //9
+        _olympusLensTypes.put("0 20 10", "Olympus M.Zuiko Digital ED 40-150mm F2.8 Pro"); //20
+        _olympusLensTypes.put("0 21 10", "Olympus M.Zuiko Digital ED 14-42mm F3.5-5.6 EZ"); //20
+        _olympusLensTypes.put("0 22 00", "Olympus Zuiko Digital 17.5-45mm F3.5-5.6"); //9
+        _olympusLensTypes.put("0 22 10", "Olympus M.Zuiko Digital 25mm F1.8"); //20
+        _olympusLensTypes.put("0 23 00", "Olympus Zuiko Digital ED 14-42mm F3.5-5.6"); //PH
+        _olympusLensTypes.put("0 23 10", "Olympus M.Zuiko Digital ED 7-14mm F2.8 Pro"); //20
+        _olympusLensTypes.put("0 24 00", "Olympus Zuiko Digital ED 40-150mm F4.0-5.6"); //PH
+        _olympusLensTypes.put("0 24 10", "Olympus M.Zuiko Digital ED 300mm F4.0 IS Pro"); //20
+        _olympusLensTypes.put("0 25 10", "Olympus M.Zuiko Digital ED 8mm F1.8 Fisheye Pro"); //20
+        _olympusLensTypes.put("0 30 00", "Olympus Zuiko Digital ED 50-200mm F2.8-3.5 SWD"); //7
+        _olympusLensTypes.put("0 31 00", "Olympus Zuiko Digital ED 12-60mm F2.8-4.0 SWD"); //7
+        _olympusLensTypes.put("0 32 00", "Olympus Zuiko Digital ED 14-35mm F2.0 SWD"); //PH
+        _olympusLensTypes.put("0 33 00", "Olympus Zuiko Digital 25mm F2.8"); //PH
+        _olympusLensTypes.put("0 34 00", "Olympus Zuiko Digital ED 9-18mm F4.0-5.6"); //7
+        _olympusLensTypes.put("0 35 00", "Olympus Zuiko Digital 14-54mm F2.8-3.5 II"); //PH
+        // Sigma lenses
+        _olympusLensTypes.put("1 01 00", "Sigma 18-50mm F3.5-5.6 DC"); //8
+        _olympusLensTypes.put("1 01 10", "Sigma 30mm F2.8 EX DN"); //20
+        _olympusLensTypes.put("1 02 00", "Sigma 55-200mm F4.0-5.6 DC");
+        _olympusLensTypes.put("1 02 10", "Sigma 19mm F2.8 EX DN"); //20
+        _olympusLensTypes.put("1 03 00", "Sigma 18-125mm F3.5-5.6 DC");
+        _olympusLensTypes.put("1 03 10", "Sigma 30mm F2.8 DN | A"); //20
+        _olympusLensTypes.put("1 04 00", "Sigma 18-125mm F3.5-5.6 DC"); //7
+        _olympusLensTypes.put("1 04 10", "Sigma 19mm F2.8 DN | A"); //20
+        _olympusLensTypes.put("1 05 00", "Sigma 30mm F1.4 EX DC HSM"); //10
+        _olympusLensTypes.put("1 05 10", "Sigma 60mm F2.8 DN | A"); //20
+        _olympusLensTypes.put("1 06 00", "Sigma APO 50-500mm F4.0-6.3 EX DG HSM"); //6
+        _olympusLensTypes.put("1 07 00", "Sigma Macro 105mm F2.8 EX DG"); //PH
+        _olympusLensTypes.put("1 08 00", "Sigma APO Macro 150mm F2.8 EX DG HSM"); //PH
+        _olympusLensTypes.put("1 09 00", "Sigma 18-50mm F2.8 EX DC Macro"); //20
+        _olympusLensTypes.put("1 10 00", "Sigma 24mm F1.8 EX DG Aspherical Macro"); //PH
+        _olympusLensTypes.put("1 11 00", "Sigma APO 135-400mm F4.5-5.6 DG"); //11
+        _olympusLensTypes.put("1 12 00", "Sigma APO 300-800mm F5.6 EX DG HSM"); //11
+        _olympusLensTypes.put("1 13 00", "Sigma 30mm F1.4 EX DC HSM"); //11
+        _olympusLensTypes.put("1 14 00", "Sigma APO 50-500mm F4.0-6.3 EX DG HSM"); //11
+        _olympusLensTypes.put("1 15 00", "Sigma 10-20mm F4.0-5.6 EX DC HSM"); //11
+        _olympusLensTypes.put("1 16 00", "Sigma APO 70-200mm F2.8 II EX DG Macro HSM"); //11
+        _olympusLensTypes.put("1 17 00", "Sigma 50mm F1.4 EX DG HSM"); //11
+        // Panasonic/Leica lenses
+        _olympusLensTypes.put("2 01 00", "Leica D Vario Elmarit 14-50mm F2.8-3.5 Asph."); //11
+        _olympusLensTypes.put("2 01 10", "Lumix G Vario 14-45mm F3.5-5.6 Asph. Mega OIS"); //16
+        _olympusLensTypes.put("2 02 00", "Leica D Summilux 25mm F1.4 Asph."); //11
+        _olympusLensTypes.put("2 02 10", "Lumix G Vario 45-200mm F4.0-5.6 Mega OIS"); //16
+        _olympusLensTypes.put("2 03 00", "Leica D Vario Elmar 14-50mm F3.8-5.6 Asph. Mega OIS"); //11
+        _olympusLensTypes.put("2 03 01", "Leica D Vario Elmar 14-50mm F3.8-5.6 Asph."); //14 (L10 kit)
+        _olympusLensTypes.put("2 03 10", "Lumix G Vario HD 14-140mm F4.0-5.8 Asph. Mega OIS"); //16
+        _olympusLensTypes.put("2 04 00", "Leica D Vario Elmar 14-150mm F3.5-5.6"); //13
+        _olympusLensTypes.put("2 04 10", "Lumix G Vario 7-14mm F4.0 Asph."); //PH (E-P1 pre-production)
+        _olympusLensTypes.put("2 05 10", "Lumix G 20mm F1.7 Asph."); //16
+        _olympusLensTypes.put("2 06 10", "Leica DG Macro-Elmarit 45mm F2.8 Asph. Mega OIS"); //PH
+        _olympusLensTypes.put("2 07 10", "Lumix G Vario 14-42mm F3.5-5.6 Asph. Mega OIS"); //20
+        _olympusLensTypes.put("2 08 10", "Lumix G Fisheye 8mm F3.5"); //PH
+        _olympusLensTypes.put("2 09 10", "Lumix G Vario 100-300mm F4.0-5.6 Mega OIS"); //11
+        _olympusLensTypes.put("2 10 10", "Lumix G 14mm F2.5 Asph."); //17
+        _olympusLensTypes.put("2 11 10", "Lumix G 12.5mm F12 3D"); //20 (H-FT012)
+        _olympusLensTypes.put("2 12 10", "Leica DG Summilux 25mm F1.4 Asph."); //20
+        _olympusLensTypes.put("2 13 10", "Lumix G X Vario PZ 45-175mm F4.0-5.6 Asph. Power OIS"); //20
+        _olympusLensTypes.put("2 14 10", "Lumix G X Vario PZ 14-42mm F3.5-5.6 Asph. Power OIS"); //20
+        _olympusLensTypes.put("2 15 10", "Lumix G X Vario 12-35mm F2.8 Asph. Power OIS"); //PH
+        _olympusLensTypes.put("2 16 10", "Lumix G Vario 45-150mm F4.0-5.6 Asph. Mega OIS"); //20
+        _olympusLensTypes.put("2 17 10", "Lumix G X Vario 35-100mm F2.8 Power OIS"); //PH
+        _olympusLensTypes.put("2 18 10", "Lumix G Vario 14-42mm F3.5-5.6 II Asph. Mega OIS"); //20
+        _olympusLensTypes.put("2 19 10", "Lumix G Vario 14-140mm F3.5-5.6 Asph. Power OIS"); //20
+        _olympusLensTypes.put("2 20 10", "Lumix G Vario 12-32mm F3.5-5.6 Asph. Mega OIS"); //20
+        _olympusLensTypes.put("2 21 10", "Leica DG Nocticron 42.5mm F1.2 Asph. Power OIS"); //20
+        _olympusLensTypes.put("2 22 10", "Leica DG Summilux 15mm F1.7 Asph."); //20
+        // '2 23 10", "Lumix G Vario 35-100mm F4.0-5.6 Asph. Mega OIS"); //20 (guess)
+        _olympusLensTypes.put("2 24 10", "Lumix G Macro 30mm F2.8 Asph. Mega OIS"); //20
+        _olympusLensTypes.put("2 25 10", "Lumix G 42.5mm F1.7 Asph. Power OIS"); //20
+        _olympusLensTypes.put("3 01 00", "Leica D Vario Elmarit 14-50mm F2.8-3.5 Asph."); //11
+        _olympusLensTypes.put("3 02 00", "Leica D Summilux 25mm F1.4 Asph."); //11
+        // Tamron lenses
+        _olympusLensTypes.put("5 01 10", "Tamron 14-150mm F3.5-5.8 Di III"); //20 (model C001)
+
+
+        _olympusExtenderTypes.put("0 00", "None");
+        _olympusExtenderTypes.put("0 04", "Olympus Zuiko Digital EC-14 1.4x Teleconverter");
+        _olympusExtenderTypes.put("0 08", "Olympus EX-25 Extension Tube");
+        _olympusExtenderTypes.put("0 10", "Olympus Zuiko Digital EC-20 2.0x Teleconverter");
+    }
+}
Index: trunk/src/com/drew/metadata/exif/makernotes/OlympusEquipmentMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/OlympusEquipmentMakernoteDirectory.java	(revision 10862)
+++ trunk/src/com/drew/metadata/exif/makernotes/OlympusEquipmentMakernoteDirectory.java	(revision 10862)
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2002-2015 Drew Noakes
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ *    https://drewnoakes.com/code/exif/
+ *    https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.metadata.exif.makernotes;
+
+import com.drew.lang.annotations.NotNull;
+import com.drew.metadata.Directory;
+
+import java.util.HashMap;
+
+/**
+ * The Olympus equipment makernote is used by many manufacturers (Epson, Konica, Minolta and Agfa...), and as such contains some tags
+ * that appear specific to those manufacturers.
+ *
+ * @author Kevin Mott https://github.com/kwhopper
+ * @author Drew Noakes https://drewnoakes.com
+ */
+public class OlympusEquipmentMakernoteDirectory extends Directory
+{
+    public static final int TAG_EQUIPMENT_VERSION = 0x0000;
+    public static final int TAG_CAMERA_TYPE_2 = 0x0100;
+    public static final int TAG_SERIAL_NUMBER = 0x0101;
+
+    public static final int TAG_INTERNAL_SERIAL_NUMBER = 0x0102;
+    public static final int TAG_FOCAL_PLANE_DIAGONAL = 0x0103;
+    public static final int TAG_BODY_FIRMWARE_VERSION = 0x0104;
+
+    public static final int TAG_LENS_TYPE = 0x0201;
+    public static final int TAG_LENS_SERIAL_NUMBER = 0x0202;
+    public static final int TAG_LENS_MODEL = 0x0203;
+    public static final int TAG_LENS_FIRMWARE_VERSION = 0x0204;
+    public static final int TAG_MAX_APERTURE_AT_MIN_FOCAL = 0x0205;
+    public static final int TAG_MAX_APERTURE_AT_MAX_FOCAL = 0x0206;
+    public static final int TAG_MIN_FOCAL_LENGTH = 0x0207;
+    public static final int TAG_MAX_FOCAL_LENGTH = 0x0208;
+    public static final int TAG_MAX_APERTURE = 0x020A;
+    public static final int TAG_LENS_PROPERTIES = 0x020B;
+
+    public static final int TAG_EXTENDER = 0x0301;
+    public static final int TAG_EXTENDER_SERIAL_NUMBER = 0x0302;
+    public static final int TAG_EXTENDER_MODEL = 0x0303;
+    public static final int TAG_EXTENDER_FIRMWARE_VERSION = 0x0304;
+
+    public static final int TAG_CONVERSION_LENS = 0x0403;
+
+    public static final int TAG_FLASH_TYPE = 0x1000;
+    public static final int TAG_FLASH_MODEL = 0x1001;
+    public static final int TAG_FLASH_FIRMWARE_VERSION = 0x1002;
+    public static final int TAG_FLASH_SERIAL_NUMBER = 0x1003;
+
+    @NotNull
+    protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
+
+    static {
+        _tagNameMap.put(TAG_EQUIPMENT_VERSION, "Equipment Version");
+        _tagNameMap.put(TAG_CAMERA_TYPE_2, "Camera Type 2");
+        _tagNameMap.put(TAG_SERIAL_NUMBER, "Serial Number");
+        _tagNameMap.put(TAG_INTERNAL_SERIAL_NUMBER, "Internal Serial Number");
+        _tagNameMap.put(TAG_FOCAL_PLANE_DIAGONAL, "Focal Plane Diagonal");
+        _tagNameMap.put(TAG_BODY_FIRMWARE_VERSION, "Body Firmware Version");
+        _tagNameMap.put(TAG_LENS_TYPE, "Lens Type");
+        _tagNameMap.put(TAG_LENS_SERIAL_NUMBER, "Lens Serial Number");
+        _tagNameMap.put(TAG_LENS_MODEL, "Lens Model");
+        _tagNameMap.put(TAG_LENS_FIRMWARE_VERSION, "Lens Firmware Version");
+        _tagNameMap.put(TAG_MAX_APERTURE_AT_MIN_FOCAL, "Max Aperture At Min Focal");
+        _tagNameMap.put(TAG_MAX_APERTURE_AT_MAX_FOCAL, "Max Aperture At Max Focal");
+        _tagNameMap.put(TAG_MIN_FOCAL_LENGTH, "Min Focal Length");
+        _tagNameMap.put(TAG_MAX_FOCAL_LENGTH, "Max Focal Length");
+        _tagNameMap.put(TAG_MAX_APERTURE, "Max Aperture");
+        _tagNameMap.put(TAG_LENS_PROPERTIES, "Lens Properties");
+        _tagNameMap.put(TAG_EXTENDER, "Extender");
+        _tagNameMap.put(TAG_EXTENDER_SERIAL_NUMBER, "Extender Serial Number");
+        _tagNameMap.put(TAG_EXTENDER_MODEL, "Extender Model");
+        _tagNameMap.put(TAG_EXTENDER_FIRMWARE_VERSION, "Extender Firmware Version");
+        _tagNameMap.put(TAG_CONVERSION_LENS, "Conversion Lens");
+        _tagNameMap.put(TAG_FLASH_TYPE, "Flash Type");
+        _tagNameMap.put(TAG_FLASH_MODEL, "Flash Model");
+        _tagNameMap.put(TAG_FLASH_FIRMWARE_VERSION, "Flash Firmware Version");
+        _tagNameMap.put(TAG_FLASH_SERIAL_NUMBER, "Flash Serial Number");
+    }
+
+    public OlympusEquipmentMakernoteDirectory()
+    {
+        this.setDescriptor(new OlympusEquipmentMakernoteDescriptor(this));
+    }
+
+    @Override
+    @NotNull
+    public String getName()
+    {
+        return "Olympus Equipment";
+    }
+
+    @Override
+    @NotNull
+    protected HashMap<Integer, String> getTagNameMap()
+    {
+        return _tagNameMap;
+    }
+}
Index: trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,9 +21,11 @@
 package com.drew.metadata.exif.makernotes;
 
+import com.drew.lang.DateUtil;
 import com.drew.lang.annotations.NotNull;
 import com.drew.lang.annotations.Nullable;
 import com.drew.metadata.TagDescriptor;
 
-import java.util.GregorianCalendar;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
 
 import static com.drew.metadata.exif.makernotes.OlympusMakernoteDirectory.*;
@@ -115,5 +117,5 @@
                 return getFocusDistanceDescription();
             case CameraSettings.TAG_FLASH_FIRED:
-                return getFlastFiredDescription();
+                return getFlashFiredDescription();
             case CameraSettings.TAG_DATE:
                 return getDateDescription();
@@ -142,5 +144,5 @@
                 return getSubjectProgramDescription();
             case CameraSettings.TAG_FLASH_COMPENSATION:
-                return getFlastCompensationDescription();
+                return getFlashCompensationDescription();
             case CameraSettings.TAG_ISO_SETTING:
                 return getIsoSettingDescription();
@@ -258,5 +260,7 @@
 
         double iso = Math.pow((value / 8d) - 1, 2) * 3.125;
-        return Double.toString(iso);
+        DecimalFormat format = new DecimalFormat("0.##");
+        format.setRoundingMode(RoundingMode.HALF_UP);
+        return format.format(iso);
     }
 
@@ -274,5 +278,7 @@
 
         double shutterSpeed = Math.pow((49-value) / 8d, 2);
-        return Double.toString(shutterSpeed) + " sec";
+        DecimalFormat format = new DecimalFormat("0.###");
+        format.setRoundingMode(RoundingMode.HALF_UP);
+        return format.format(shutterSpeed) + " sec";
     }
 
@@ -289,5 +295,5 @@
 
         double fStop = Math.pow((value/16d) - 0.5, 2);
-        return "F" + Double.toString(fStop);
+        return getFStopDescription(fStop);
     }
 
@@ -308,5 +314,6 @@
     {
         Long value = _directory.getLongObject(CameraSettings.TAG_EXPOSURE_COMPENSATION);
-        return value == null ? null : ((value / 3d) - 2) + " EV";
+        DecimalFormat format = new DecimalFormat("0.##");
+        return value == null ? null : format.format((value / 3d) - 2) + " EV";
     }
 
@@ -341,5 +348,5 @@
     {
         Long value = _directory.getLongObject(CameraSettings.TAG_FOCAL_LENGTH);
-        return value == null ? null : Double.toString(value/256d) + " mm";
+        return value == null ? null : getFocalLengthDescription(value/256d);
     }
 
@@ -356,5 +363,5 @@
 
     @Nullable
-    public String getFlastFiredDescription()
+    public String getFlashFiredDescription()
     {
         return getIndexedDescription(CameraSettings.TAG_FLASH_FIRED, "No", "Yes");
@@ -370,8 +377,13 @@
         if (value == null)
             return null;
-        long day = value & 0xFF;
-        long month = (value >> 16) & 0xFF;
-        long year = (value >> 8) & 0xFF;
-        return new GregorianCalendar((int)year + 1970, (int)month, (int)day).getTime().toString();
+
+        int day = (int) (value & 0xFF);
+        int month = (int) ((value >> 16) & 0xFF);
+        int year = (int) ((value >> 8) & 0xFF) + 1970;
+
+        if (!DateUtil.isValidDate(year, month, day))
+            return "Invalid date";
+
+        return String.format("%04d-%02d-%02d", year, month + 1, day);
     }
 
@@ -385,7 +397,11 @@
         if (value == null)
             return null;
-        long hours = (value >> 8) & 0xFF;
-        long minutes = (value >> 16) & 0xFF;
-        long seconds = value & 0xFF;
+
+        int hours = (int) ((value >> 8) & 0xFF);
+        int minutes = (int) ((value >> 16) & 0xFF);
+        int seconds = (int) (value & 0xFF);
+
+        if (!DateUtil.isValidTime(hours, minutes, seconds))
+            return "Invalid time";
 
         return String.format("%02d:%02d:%02d", hours, minutes, seconds);
@@ -400,5 +416,5 @@
             return null;
         double fStop = Math.pow((value/16d) - 0.5, 2);
-        return "F" + fStop;
+        return getFStopDescription(fStop);
     }
 
@@ -424,5 +440,6 @@
     {
         Long value = _directory.getLongObject(CameraSettings.TAG_WHITE_BALANCE_RED);
-        return value == null ? null : Double.toString(value/256d);
+        DecimalFormat format = new DecimalFormat("0.##");
+        return value == null ? null : format.format(value/256d);
     }
 
@@ -431,5 +448,6 @@
     {
         Long value = _directory.getLongObject(CameraSettings.TAG_WHITE_BALANCE_GREEN);
-        return value == null ? null : Double.toString(value/256d);
+        DecimalFormat format = new DecimalFormat("0.##");
+        return value == null ? null : format.format(value/256d);
     }
 
@@ -438,5 +456,6 @@
     {
         Long value = _directory.getLongObject(CameraSettings.TAG_WHITE_BALANCE_BLUE);
-        return value == null ? null : Double.toString(value/256d);
+        DecimalFormat format = new DecimalFormat("0.##");
+        return value == null ? null : format.format(value / 256d);
     }
 
@@ -468,8 +487,9 @@
 
     @Nullable
-    public String getFlastCompensationDescription()
+    public String getFlashCompensationDescription()
     {
         Long value = _directory.getLongObject(CameraSettings.TAG_FLASH_COMPENSATION);
-        return value == null ? null : ((value-6)/3d) + " EV";
+        DecimalFormat format = new DecimalFormat("0.##");
+        return value == null ? null : format.format((value-6)/3d) + " EV";
     }
 
@@ -535,5 +555,6 @@
     {
         Long value = _directory.getLongObject(CameraSettings.TAG_APEX_BRIGHTNESS_VALUE);
-        return value == null ? null : Double.toString((value/8d)-6);
+        DecimalFormat format = new DecimalFormat("0.##");
+        return value == null ? null : format.format((value/8d)-6);
     }
 
Index: trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/PanasonicMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/PanasonicMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/PanasonicMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/PentaxMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/PentaxMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/PentaxMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/PentaxMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/PentaxMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/PentaxMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/RicohMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/RicohMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/RicohMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/RicohMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/RicohMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/RicohMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/SanyoMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/SanyoMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/SanyoMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/SanyoMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/SanyoMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/SanyoMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/SigmaMakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/SigmaMakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/SigmaMakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/SigmaMakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/SigmaMakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/SigmaMakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/SonyType1MakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/SonyType1MakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/SonyType1MakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/SonyType1MakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/SonyType1MakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/SonyType1MakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/SonyType6MakernoteDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/SonyType6MakernoteDescriptor.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/SonyType6MakernoteDescriptor.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/SonyType6MakernoteDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/SonyType6MakernoteDirectory.java	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/SonyType6MakernoteDirectory.java	(revision 10862)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2015 Drew Noakes
+ * Copyright 2002-2016 Drew Noakes
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/makernotes/package.html
===================================================================
--- trunk/src/com/drew/metadata/exif/makernotes/package.html	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/makernotes/package.html	(revision 10862)
@@ -1,4 +1,4 @@
 <!--
-  ~ Copyright 2002-2015 Drew Noakes
+  ~ Copyright 2002-2016 Drew Noakes
   ~
   ~    Licensed under the Apache License, Version 2.0 (the "License");
Index: trunk/src/com/drew/metadata/exif/package.html
===================================================================
--- trunk/src/com/drew/metadata/exif/package.html	(revision 8243)
+++ trunk/src/com/drew/metadata/exif/package.html	(revision 10862)
@@ -1,4 +1,4 @@
 <!--
-  ~ Copyright 2002-2015 Drew Noakes
+  ~ Copyright 2002-2016 Drew Noakes
   ~
   ~    Licensed under the Apache License, Version 2.0 (the "License");
