Changeset 10862 in josm


Ignore:
Timestamp:
2016-08-20T20:58:03+02:00 (3 years ago)
Author:
Don-vip
Message:

update to metadata-extractor 2.9.1

Location:
trunk/src/com/drew
Files:
5 added
2 deleted
106 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/com/drew/imaging/ImageProcessingException.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/PhotographicConversions.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/jpeg/JpegMetadataReader.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3939import com.drew.metadata.iptc.IptcReader;
    4040//import com.drew.metadata.jfif.JfifReader;
     41//import com.drew.metadata.jfxx.JfxxReader;
    4142import com.drew.metadata.jpeg.JpegCommentReader;
    4243import com.drew.metadata.jpeg.JpegReader;
     44//import com.drew.metadata.photoshop.DuckyReader;
    4345//import com.drew.metadata.photoshop.PhotoshopReader;
    4446//import com.drew.metadata.xmp.XmpReader;
     
    5557            new JpegCommentReader(),
    5658            //new JfifReader(),
     59            //new JfxxReader(),
    5760            new ExifReader(),
    5861            //new XmpReader(),
    5962            //new IccReader(),
    6063            //new PhotoshopReader(),
     64            //new DuckyReader(),
    6165            new IptcReader()//,
    6266            //new AdobeJpegReader()
  • trunk/src/com/drew/imaging/jpeg/JpegProcessingException.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentData.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    5252     * @param segmentBytes the byte array holding data for the segment being added
    5353     */
    54     @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
    5554    public void addSegment(byte segmentType, @NotNull byte[] segmentBytes)
    5655    {
     
    207206     * @param occurrence  the zero-based index of the segment occurrence to remove.
    208207     */
    209     @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
    210208    public void removeSegmentOccurrence(@NotNull JpegSegmentType segmentType, int occurrence)
    211209    {
     
    220218     * @param occurrence  the zero-based index of the segment occurrence to remove.
    221219     */
    222     @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
    223220    public void removeSegmentOccurrence(byte segmentType, int occurrence)
    224221    {
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    4343public class JpegSegmentReader
    4444{
     45    /**
     46     * The 0xFF byte that signals the start of a segment.
     47     */
     48    private static final byte SEGMENT_IDENTIFIER = (byte) 0xFF;
     49
    4550    /**
    4651     * Private, because this segment crashes my algorithm, and searching for it doesn't work (yet).
     
    112117            // by a 0xFF and then a byte not equal to 0x00 or 0xFF.
    113118
    114             final short segmentIdentifier = reader.getUInt8();
     119            byte segmentIdentifier = reader.getInt8();
     120            byte segmentType = reader.getInt8();
    115121
    116             // We must have at least one 0xFF byte
    117             if (segmentIdentifier != 0xFF)
    118                 throw new JpegProcessingException("Expected JPEG segment start identifier 0xFF, not 0x" + Integer.toHexString(segmentIdentifier).toUpperCase());
    119 
    120             // Read until we have a non-0xFF byte. This identifies the segment type.
    121             byte segmentType = reader.getInt8();
    122             while (segmentType == (byte)0xFF)
    123                 segmentType = reader.getInt8();
    124 
    125             if (segmentType == 0)
    126                 throw new JpegProcessingException("Expected non-zero byte as part of JPEG marker identifier");
     122            // Read until we have a 0xFF byte followed by a byte that is not 0xFF or 0x00
     123            while (segmentIdentifier != SEGMENT_IDENTIFIER || segmentType == SEGMENT_IDENTIFIER || segmentType == 0) {
     124                segmentIdentifier = segmentType;
     125                segmentType = reader.getInt8();
     126            }
    127127
    128128            if (segmentType == SEGMENT_SOS) {
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentType.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3030 * An enumeration of the known segment types found in JPEG files.
    3131 *
     32 * <ul>
     33 *     <li>http://www.ozhiker.com/electronics/pjmt/jpeg_info/app_segments.html</li>
     34 *     <li>http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html</li>
     35 * </ul>
     36 *
    3237 * @author Drew Noakes https://drewnoakes.com
    3338 */
    3439public enum JpegSegmentType
    3540{
    36     /** APP0 JPEG segment identifier -- JFIF data (also JFXX apparently). */
     41    /** APP0 JPEG segment identifier. Commonly contains JFIF, JFXX. */
    3742    APP0((byte)0xE0, true),
    3843
    39     /** APP1 JPEG segment identifier -- where Exif data is kept. XMP data is also kept in here, though usually in a second instance. */
     44    /** APP1 JPEG segment identifier. Commonly contains Exif. XMP data is also kept in here, though usually in a second instance. */
    4045    APP1((byte)0xE1, true),
    4146
    42     /** APP2 JPEG segment identifier. */
     47    /** APP2 JPEG segment identifier. Commonly contains ICC. */
    4348    APP2((byte)0xE2, true),
    4449
     
    6469    APP9((byte)0xE9, true),
    6570
    66     /** APPA (App10) JPEG segment identifier -- can hold Unicode comments. */
     71    /** APPA (App10) JPEG segment identifier. Can contain Unicode comments, though {@link JpegSegmentType#COM} is more commonly used for comments. */
    6772    APPA((byte)0xEA, true),
    6873
     
    7378    APPC((byte)0xEC, true),
    7479
    75     /** APPD (App13) JPEG segment identifier -- IPTC data in here. */
     80    /** APPD (App13) JPEG segment identifier. Commonly contains IPTC, Photoshop data. */
    7681    APPD((byte)0xED, true),
    7782
    78     /** APPE (App14) JPEG segment identifier. */
     83    /** APPE (App14) JPEG segment identifier. Commonly contains Adobe data. */
    7984    APPE((byte)0xEE, true),
    8085
  • trunk/src/com/drew/imaging/jpeg/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/tiff/TiffDataFormat.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/tiff/TiffHandler.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2424import com.drew.lang.Rational;
    2525import com.drew.lang.annotations.NotNull;
     26import com.drew.lang.annotations.Nullable;
    2627
    2728import java.io.IOException;
     
    4647    void setTiffMarker(int marker) throws TiffProcessingException;
    4748
    48     boolean isTagIfdPointer(int tagType);
     49    boolean tryEnterSubIfd(int tagId);
    4950    boolean hasFollowerIfd();
    5051
     
    5253
    5354    void completed(@NotNull final RandomAccessReader reader, final int tiffHeaderOffset);
     55
     56    @Nullable
     57    Long tryCustomProcessFormat(int tagId, int formatCode, long componentCount);
    5458
    5559    boolean customProcessTag(int tagOffset,
  • trunk/src/com/drew/imaging/tiff/TiffProcessingException.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/imaging/tiff/TiffReader.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    110110                                  final int tiffHeaderOffset) throws IOException
    111111    {
     112        Boolean resetByteOrder = null;
    112113        try {
    113114            // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist
     
    126127            // First two bytes in the IFD are the number of tags in this directory
    127128            int dirTagCount = reader.getUInt16(ifdOffset);
     129
     130            // Some software modifies the byte order of the file, but misses some IFDs (such as makernotes).
     131            // The entire test image repository doesn't contain a single IFD with more than 255 entries.
     132            // Here we detect switched bytes that suggest this problem, and temporarily swap the byte order.
     133            // This was discussed in GitHub issue #136.
     134            if (dirTagCount > 0xFF && (dirTagCount & 0xFF) == 0) {
     135                resetByteOrder = reader.isMotorolaByteOrder();
     136                dirTagCount >>= 8;
     137                reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder());
     138            }
    128139
    129140            int dirLength = (2 + (12 * dirTagCount) + 4);
     
    147158                final TiffDataFormat format = TiffDataFormat.fromTiffFormatCode(formatCode);
    148159
     160                // 4 bytes dictate the number of components in this tag's data
     161                final long componentCount = reader.getUInt32(tagOffset + 4);
     162
     163                final long byteCount;
    149164                if (format == null) {
    150                     // This error suggests that we are processing at an incorrect index and will generate
    151                     // rubbish until we go out of bounds (which may be a while).  Exit now.
    152                     handler.error("Invalid TIFF tag format code: " + formatCode);
    153                     // TODO specify threshold as a parameter, or provide some other external control over this behaviour
    154                     if (++invalidTiffFormatCodeCount > 5) {
    155                         handler.error("Stopping processing as too many errors seen in TIFF IFD");
    156                         return;
     165                    Long byteCountOverride = handler.tryCustomProcessFormat(tagId, formatCode, componentCount);
     166                    if (byteCountOverride == null) {
     167                        // This error suggests that we are processing at an incorrect index and will generate
     168                        // rubbish until we go out of bounds (which may be a while).  Exit now.
     169                        handler.error(String.format("Invalid TIFF tag format code %d for tag 0x%04X", formatCode, tagId));
     170                        // TODO specify threshold as a parameter, or provide some other external control over this behaviour
     171                        if (++invalidTiffFormatCodeCount > 5) {
     172                            handler.error("Stopping processing as too many errors seen in TIFF IFD");
     173                            return;
     174                        }
     175                        continue;
    157176                    }
    158                     continue;
    159                 }
    160 
    161                 // 4 bytes dictate the number of components in this tag's data
    162                 final int componentCount = reader.getInt32(tagOffset + 4);
    163                 if (componentCount < 0) {
    164                     handler.error("Negative TIFF tag component count");
    165                     continue;
    166                 }
    167 
    168                 final int byteCount = componentCount * format.getComponentSizeBytes();
    169 
    170                 final int tagValueOffset;
     177                    byteCount = byteCountOverride;
     178                } else {
     179                    byteCount = componentCount * format.getComponentSizeBytes();
     180                }
     181
     182                final long tagValueOffset;
    171183                if (byteCount > 4) {
    172184                    // If it's bigger than 4 bytes, the dir entry contains an offset.
    173                     final int offsetVal = reader.getInt32(tagOffset + 8);
     185                    final long offsetVal = reader.getUInt32(tagOffset + 8);
    174186                    if (offsetVal + byteCount > reader.getLength()) {
    175187                        // Bogus pointer offset and / or byteCount value
     
    195207                }
    196208
    197                 //
    198                 // Special handling for tags that point to other IFDs
    199                 //
    200                 if (byteCount == 4 && handler.isTagIfdPointer(tagId)) {
    201                     final int subDirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
    202                     processIfd(handler, reader, processedIfdOffsets, subDirOffset, tiffHeaderOffset);
    203                 } else {
    204                     if (!handler.customProcessTag(tagValueOffset, processedIfdOffsets, tiffHeaderOffset, reader, tagId, byteCount)) {
    205                         processTag(handler, tagId, tagValueOffset, componentCount, formatCode, reader);
     209                // Some tags point to one or more additional IFDs to process
     210                boolean isIfdPointer = false;
     211                if (byteCount == 4 * componentCount) {
     212                    for (int i = 0; i < componentCount; i++) {
     213                        if (handler.tryEnterSubIfd(tagId)) {
     214                            isIfdPointer = true;
     215                            int subDirOffset = tiffHeaderOffset + reader.getInt32((int) (tagValueOffset + i * 4));
     216                            processIfd(handler, reader, processedIfdOffsets, subDirOffset, tiffHeaderOffset);
     217                        }
    206218                    }
     219                }
     220
     221                // If it wasn't an IFD pointer, allow custom tag processing to occur
     222                if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedIfdOffsets, tiffHeaderOffset, reader, tagId, (int) byteCount)) {
     223                    // If no custom processing occurred, process the tag in the standard fashion
     224                    processTag(handler, tagId, (int) tagValueOffset, (int) componentCount, formatCode, reader);
    207225                }
    208226            }
     
    229247        } finally {
    230248            handler.endingIFD();
     249            if (resetByteOrder != null)
     250                reader.setMotorolaByteOrder(resetByteOrder);
    231251        }
    232252    }
     
    350370                break;
    351371            default:
    352                 handler.error(String.format("Unknown format code %d for tag %d", formatCode, tagId));
     372                handler.error(String.format("Invalid TIFF tag format code %d for tag 0x%04X", formatCode, tagId));
    353373        }
    354374    }
  • trunk/src/com/drew/imaging/tiff/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/BufferBoundsException.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/ByteArrayReader.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2222package com.drew.lang;
    2323
     24import java.io.IOException;
     25
    2426import com.drew.lang.annotations.NotNull;
    25 
    26 import java.io.IOException;
    2727
    2828/**
     
    4040    private final byte[] _buffer;
    4141
    42     @SuppressWarnings({ "ConstantConditions" })
    43     @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent")
    4442    public ByteArrayReader(@NotNull byte[] buffer)
    4543    {
     
    7472        return bytesRequested >= 0
    7573            && index >= 0
    76             && (long)index + (long)bytesRequested - 1L < (long)_buffer.length;
     74            && (long)index + (long)bytesRequested - 1L < _buffer.length;
    7775    }
    7876
  • trunk/src/com/drew/lang/CompoundException.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/GeoLocation.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    8484        double[] dms = decimalToDegreesMinutesSeconds(decimal);
    8585        DecimalFormat format = new DecimalFormat("0.##");
    86         return String.format("%s° %s' %s\"", format.format(dms[0]), format.format(dms[1]), format.format(dms[2]));
     86        return String.format("%s\u00B0 %s' %s\"", format.format(dms[0]), format.format(dms[1]), format.format(dms[2]));
    8787    }
    8888
  • trunk/src/com/drew/lang/RandomAccessReader.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    5353     * @param index The index from which to read the byte
    5454     * @return The read byte value
    55      * @throws IllegalArgumentException <code>index</code> or <code>count</code> are negative
     55     * @throws IllegalArgumentException <code>index</code> is negative
    5656     * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source
    5757     * @throws IOException if the byte is unable to be read
  • trunk/src/com/drew/lang/Rational.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/SequentialByteArrayReader.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    4242    }
    4343
    44     @SuppressWarnings("ConstantConditions")
    4544    public SequentialByteArrayReader(@NotNull byte[] bytes, int baseIndex)
    4645    {
  • trunk/src/com/drew/lang/SequentialReader.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/StreamReader.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3737    private final InputStream _stream;
    3838
    39     @SuppressWarnings("ConstantConditions")
    4039    public StreamReader(@NotNull InputStream stream)
    4140    {
  • trunk/src/com/drew/lang/StringUtil.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/annotations/NotNull.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/annotations/Nullable.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/annotations/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/lang/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/Age.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/Directory.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2424import com.drew.lang.annotations.NotNull;
    2525import com.drew.lang.annotations.Nullable;
    26 import com.drew.lang.annotations.SuppressWarnings;
    2726
    2827import java.io.UnsupportedEncodingException;
    2928import java.lang.reflect.Array;
    3029import java.text.DateFormat;
     30import java.text.DecimalFormat;
    3131import java.text.ParseException;
    3232import java.text.SimpleDateFormat;
    3333import java.util.*;
     34import java.util.regex.Matcher;
     35import java.util.regex.Pattern;
    3436
    3537/**
     
    4143public abstract class Directory
    4244{
     45    private static final DecimalFormat _floatFormat = new DecimalFormat("0.###");
     46
    4347    /** Map of values hashed by type identifiers. */
    4448    @NotNull
     
    5963    protected TagDescriptor _descriptor;
    6064
     65    @Nullable
     66    private Directory _parent;
     67
    6168// ABSTRACT METHODS
    6269
     
    171178    {
    172179        return _errorList.size();
     180    }
     181
     182    @Nullable
     183    public Directory getParent()
     184    {
     185        return _parent;
     186    }
     187
     188    public void setParent(@NotNull Directory parent)
     189    {
     190        _parent = parent;
    173191    }
    174192
     
    701719    /** Returns the specified tag's value as a boolean.  If the tag is not set or cannot be converted, <code>null</code> is returned. */
    702720    @Nullable
    703     @SuppressWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "keep API interface consistent")
    704721    public Boolean getBooleanObject(int tagType)
    705722    {
     
    725742     * <p>
    726743     * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
    727      * the current {@link TimeZone}.  If the {@link TimeZone} is known, call the overload that accepts one as an argument.
     744     * the GMT {@link TimeZone}.  If the {@link TimeZone} is known, call the overload that accepts one as an argument.
    728745     */
    729746    @Nullable
    730747    public java.util.Date getDate(int tagType)
    731748    {
    732         return getDate(tagType, null);
     749        return getDate(tagType, null, null);
    733750    }
    734751
     
    738755     * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
    739756     * the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null).  Note that this parameter
    740      * is only considered if the underlying value is a string and parsing occurs, otherwise it has no effect.
     757     * is only considered if the underlying value is a string and it has no time zone information, otherwise it has no effect.
    741758     */
    742759    @Nullable
    743760    public java.util.Date getDate(int tagType, @Nullable TimeZone timeZone)
    744761    {
    745         Object o = getObject(tagType);
    746 
    747         if (o == null)
    748             return null;
     762        return getDate(tagType, null, timeZone);
     763    }
     764
     765    /**
     766     * Returns the specified tag's value as a java.util.Date.  If the value is unset or cannot be converted, <code>null</code> is returned.
     767     * <p>
     768     * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
     769     * the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null).  Note that this parameter
     770     * is only considered if the underlying value is a string and it has no time zone information, otherwise it has no effect.
     771     * In addition, the {@code subsecond} parameter, which specifies the number of digits after the decimal point in the seconds,
     772     * is set to the returned Date. This parameter is only considered if the underlying value is a string and is has
     773     * no subsecond information, otherwise it has no effect.
     774     *
     775     * @param tagType the tag identifier
     776     * @param subsecond the subsecond value for the Date
     777     * @param timeZone the time zone to use
     778     * @return a Date representing the time value
     779     */
     780    @Nullable
     781    public java.util.Date getDate(int tagType, @Nullable String subsecond, @Nullable TimeZone timeZone)
     782    {
     783        Object o = getObject(tagType);
    749784
    750785        if (o instanceof java.util.Date)
    751786            return (java.util.Date)o;
    752787
     788        java.util.Date date = null;
     789
    753790        if (o instanceof String) {
    754             // This seems to cover all known Exif date strings
     791            // This seems to cover all known Exif and Xmp date strings
    755792            // Note that "    :  :     :  :  " is a valid date string according to the Exif spec (which means 'unknown date'): http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/datetimeoriginal.html
    756793            String datePatterns[] = {
     
    760797                    "yyyy-MM-dd HH:mm",
    761798                    "yyyy.MM.dd HH:mm:ss",
    762                     "yyyy.MM.dd HH:mm" };
     799                    "yyyy.MM.dd HH:mm",
     800                    "yyyy-MM-dd'T'HH:mm:ss",
     801                    "yyyy-MM-dd'T'HH:mm",
     802                    "yyyy-MM-dd",
     803                    "yyyy-MM",
     804                    "yyyy" };
    763805            String dateString = (String)o;
     806
     807            // if the date string has subsecond information, it supersedes the subsecond parameter
     808            Pattern subsecondPattern = Pattern.compile("(\\d\\d:\\d\\d:\\d\\d)(\\.\\d+)");
     809            Matcher subsecondMatcher = subsecondPattern.matcher(dateString);
     810            if (subsecondMatcher.find()) {
     811                subsecond = subsecondMatcher.group(2).substring(1);
     812                dateString = subsecondMatcher.replaceAll("$1");
     813            }
     814
     815            // if the date string has time zone information, it supersedes the timeZone parameter
     816            Pattern timeZonePattern = Pattern.compile("(Z|[+-]\\d\\d:\\d\\d)$");
     817            Matcher timeZoneMatcher = timeZonePattern.matcher(dateString);
     818            if (timeZoneMatcher.find()) {
     819                timeZone = TimeZone.getTimeZone("GMT" + timeZoneMatcher.group().replaceAll("Z", ""));
     820                dateString = timeZoneMatcher.replaceAll("");
     821            }
     822
    764823            for (String datePattern : datePatterns) {
    765824                try {
     
    770829                        parser.setTimeZone(TimeZone.getTimeZone("GMT")); // don't interpret zone time
    771830
    772                     return parser.parse(dateString);
     831                    date = parser.parse(dateString);
     832                    break;
    773833                } catch (ParseException ex) {
    774834                    // simply try the next pattern
     
    776836            }
    777837        }
    778         return null;
     838
     839        if (date == null)
     840            return null;
     841
     842        if (subsecond == null)
     843            return date;
     844
     845        try {
     846            int millisecond = (int) (Double.parseDouble("." + subsecond) * 1000);
     847            if (millisecond >= 0 && millisecond < 1000) {
     848                Calendar calendar = Calendar.getInstance();
     849                calendar.setTime(date);
     850                calendar.set(Calendar.MILLISECOND, millisecond);
     851                return calendar.getTime();
     852            }
     853            return date;
     854        } catch (NumberFormatException e) {
     855            return date;
     856        }
    779857    }
    780858
     
    835913            int arrayLength = Array.getLength(o);
    836914            final Class<?> componentType = o.getClass().getComponentType();
    837             boolean isObjectArray = Object.class.isAssignableFrom(componentType);
    838             boolean isFloatArray = componentType.getName().equals("float");
    839             boolean isDoubleArray = componentType.getName().equals("double");
    840             boolean isIntArray = componentType.getName().equals("int");
    841             boolean isLongArray = componentType.getName().equals("long");
    842             boolean isByteArray = componentType.getName().equals("byte");
    843             boolean isShortArray = componentType.getName().equals("short");
     915
    844916            StringBuilder string = new StringBuilder();
    845             for (int i = 0; i < arrayLength; i++) {
    846                 if (i != 0)
    847                     string.append(' ');
    848                 if (isObjectArray)
     917
     918            if (Object.class.isAssignableFrom(componentType)) {
     919                // object array
     920                for (int i = 0; i < arrayLength; i++) {
     921                    if (i != 0)
     922                        string.append(' ');
    849923                    string.append(Array.get(o, i).toString());
    850                 else if (isIntArray)
     924                }
     925            } else if (componentType.getName().equals("int")) {
     926                for (int i = 0; i < arrayLength; i++) {
     927                    if (i != 0)
     928                        string.append(' ');
    851929                    string.append(Array.getInt(o, i));
    852                 else if (isShortArray)
     930                }
     931            } else if (componentType.getName().equals("short")) {
     932                for (int i = 0; i < arrayLength; i++) {
     933                    if (i != 0)
     934                        string.append(' ');
    853935                    string.append(Array.getShort(o, i));
    854                 else if (isLongArray)
     936                }
     937            } else if (componentType.getName().equals("long")) {
     938                for (int i = 0; i < arrayLength; i++) {
     939                    if (i != 0)
     940                        string.append(' ');
    855941                    string.append(Array.getLong(o, i));
    856                 else if (isFloatArray)
    857                     string.append(Array.getFloat(o, i));
    858                 else if (isDoubleArray)
    859                     string.append(Array.getDouble(o, i));
    860                 else if (isByteArray)
    861                     string.append(Array.getByte(o, i));
    862                 else
    863                     addError("Unexpected array component type: " + componentType.getName());
    864             }
     942                }
     943            } else if (componentType.getName().equals("float")) {
     944                for (int i = 0; i < arrayLength; i++) {
     945                    if (i != 0)
     946                        string.append(' ');
     947                    string.append(_floatFormat.format(Array.getFloat(o, i)));
     948                }
     949            } else if (componentType.getName().equals("double")) {
     950                for (int i = 0; i < arrayLength; i++) {
     951                    if (i != 0)
     952                        string.append(' ');
     953                    string.append(_floatFormat.format(Array.getDouble(o, i)));
     954                }
     955            } else if (componentType.getName().equals("byte")) {
     956                for (int i = 0; i < arrayLength; i++) {
     957                    if (i != 0)
     958                        string.append(' ');
     959                    string.append(Array.getByte(o, i) & 0xff);
     960                }
     961            } else {
     962                addError("Unexpected array component type: " + componentType.getName());
     963            }
     964
    865965            return string.toString();
    866966        }
     967
     968        if (o instanceof Double)
     969            return _floatFormat.format(((Double)o).doubleValue());
     970
     971        if (o instanceof Float)
     972            return _floatFormat.format(((Float)o).floatValue());
    867973
    868974        // Note that several cameras leave trailing spaces (Olympus, Nikon) but this library is intended to show
  • trunk/src/com/drew/metadata/Face.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/Metadata.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3636public final class Metadata
    3737{
     38    /**
     39     * The list of {@link Directory} instances in this container, in the order they were added.
     40     */
    3841    @NotNull
    39     private final Map<Class<? extends Directory>,Collection<Directory>> _directoryListByClass = new HashMap<Class<? extends Directory>, Collection<Directory>>();
     42    private final List<Directory> _directories = new ArrayList<>();
    4043
    4144    /**
     
    4750    public Iterable<Directory> getDirectories()
    4851    {
    49         return new DirectoryIterable(_directoryListByClass);
     52        return _directories;
    5053    }
    5154
    5255    @Nullable
     56    @SuppressWarnings("unchecked")
    5357    public <T extends Directory> Collection<T> getDirectoriesOfType(Class<T> type)
    5458    {
    55         return (Collection<T>)_directoryListByClass.get(type);
     59        List<T> directories = new ArrayList<>();
     60        for (Directory dir : _directories) {
     61            if (type.isAssignableFrom(dir.getClass())) {
     62                directories.add((T)dir);
     63            }
     64        }
     65        return directories;
    5666    }
    5767
     
    6373    public int getDirectoryCount()
    6474    {
    65         int count = 0;
    66         for (Map.Entry<Class<? extends Directory>,Collection<Directory>> pair : _directoryListByClass.entrySet())
    67             count += pair.getValue().size();
    68         return count;
     75        return _directories.size();
    6976    }
    7077
     
    7683    public <T extends Directory> void addDirectory(@NotNull T directory)
    7784    {
    78         getOrCreateDirectoryList(directory.getClass()).add(directory);
     85        _directories.add(directory);
    7986    }
    8087
     
    9198    public <T extends Directory> T getFirstDirectoryOfType(@NotNull Class<T> type)
    9299    {
    93         // We suppress the warning here as the code asserts a map signature of Class<T>,T.
    94         // So after get(Class<T>) it is for sure the result is from type T.
    95 
    96         Collection<Directory> list = getDirectoryList(type);
    97 
    98         if (list == null || list.isEmpty())
    99             return null;
    100 
    101         return (T)list.iterator().next();
     100        for (Directory dir : _directories) {
     101            if (type.isAssignableFrom(dir.getClass()))
     102                return (T)dir;
     103        }
     104        return null;
    102105    }
    103106
     
    110113    public boolean containsDirectoryOfType(Class<? extends Directory> type)
    111114    {
    112         Collection<Directory> list = getDirectoryList(type);
    113         return list != null && !list.isEmpty();
     115        for (Directory dir : _directories) {
     116            if (type.isAssignableFrom(dir.getClass()))
     117                return true;
     118        }
     119        return false;
    114120    }
    115121
     
    139145                : "directories");
    140146    }
    141 
    142     @Nullable
    143     private <T extends Directory> Collection<Directory> getDirectoryList(@NotNull Class<T> type)
    144     {
    145         return _directoryListByClass.get(type);
    146     }
    147 
    148     @NotNull
    149     private <T extends Directory> Collection<Directory> getOrCreateDirectoryList(@NotNull Class<T> type)
    150     {
    151         Collection<Directory> collection = getDirectoryList(type);
    152         if (collection != null)
    153             return collection;
    154         collection = new ArrayList<Directory>();
    155         _directoryListByClass.put(type, collection);
    156         return collection;
    157     }
    158 
    159     private static class DirectoryIterable implements Iterable<Directory>
    160     {
    161         private final Map<Class<? extends Directory>, Collection<Directory>> _map;
    162 
    163         public DirectoryIterable(Map<Class<? extends Directory>, Collection<Directory>> map)
    164         {
    165             _map = map;
    166         }
    167 
    168         public Iterator<Directory> iterator()
    169         {
    170             return new DirectoryIterator(_map);
    171         }
    172 
    173         private static class DirectoryIterator implements Iterator<Directory>
    174         {
    175             @NotNull
    176             private final Iterator<Map.Entry<Class<? extends Directory>, Collection<Directory>>> _mapIterator;
    177             @Nullable
    178             private Iterator<Directory> _listIterator;
    179 
    180             public DirectoryIterator(Map<Class<? extends Directory>, Collection<Directory>> map)
    181             {
    182                 _mapIterator = map.entrySet().iterator();
    183 
    184                 if (_mapIterator.hasNext())
    185                     _listIterator = _mapIterator.next().getValue().iterator();
    186             }
    187 
    188             public boolean hasNext()
    189             {
    190                 return _listIterator != null && (_listIterator.hasNext() || _mapIterator.hasNext());
    191             }
    192 
    193             public Directory next()
    194             {
    195                 if (_listIterator == null || (!_listIterator.hasNext() && !_mapIterator.hasNext()))
    196                     throw new NoSuchElementException();
    197 
    198                 while (!_listIterator.hasNext())
    199                     _listIterator = _mapIterator.next().getValue().iterator();
    200 
    201                 return _listIterator.next();
    202             }
    203 
    204             public void remove()
    205             {
    206                 throw new UnsupportedOperationException();
    207             }
    208         }
    209     }
    210147}
  • trunk/src/com/drew/metadata/MetadataException.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/MetadataReader.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/Tag.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    5454    /**
    5555     * Gets the tag type in hex notation as a String with padded leading
    56      * zeroes if necessary (i.e. <code>0x100E</code>).
     56     * zeroes if necessary (i.e. <code>0x100e</code>).
    5757     *
    5858     * @return the tag type as a string in hexadecimal notation
     
    6161    public String getTagTypeHex()
    6262    {
    63         String hex = Integer.toHexString(_tagType);
    64         while (hex.length() < 4) hex = "0" + hex;
    65         return "0x" + hex;
     63        return String.format("0x%04x", _tagType);
    6664    }
    6765
     
    117115
    118116    /**
    119      * A basic representation of the tag's type and value.  EG: <code>[FNumber] F2.8</code>.
     117     * A basic representation of the tag's type and value.  EG: <code>[Exif IFD0] FNumber - f/2.8</code>.
    120118     *
    121119     * @return the tag's type and value
  • trunk/src/com/drew/metadata/TagDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2828import java.io.UnsupportedEncodingException;
    2929import java.lang.reflect.Array;
     30import java.math.RoundingMode;
     31import java.text.DecimalFormat;
     32import java.text.SimpleDateFormat;
    3033import java.util.ArrayList;
    3134import java.util.Date;
     
    7174            final int length = Array.getLength(object);
    7275            if (length > 16) {
    73                 final String componentTypeName = object.getClass().getComponentType().getName();
    74                 return String.format("[%d %s%s]", length, componentTypeName, length == 1 ? "" : "s");
     76                return String.format("[%d %s]", length, length == 1 ? "value" : "values");
    7577            }
     78        }
     79
     80        if (object instanceof Date)
     81        {
     82            // Produce a date string having a format that includes the offset in form "+00:00"
     83            return new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy")
     84                .format((Date) object)
     85                .replaceAll("([0-9]{2} [^ ]+)$", ":$1");
    7686        }
    7787
     
    272282        }
    273283    }
     284
     285    @Nullable
     286    protected String getRationalOrDoubleString(int tagType)
     287    {
     288        Rational rational = _directory.getRational(tagType);
     289        if (rational != null)
     290            return rational.toSimpleString(true);
     291
     292        Double d = _directory.getDoubleObject(tagType);
     293        if (d != null)
     294        {
     295            DecimalFormat format = new DecimalFormat("0.###");
     296            return format.format(d);
     297        }
     298
     299        return null;
     300    }
     301
     302    @Nullable
     303    protected static String getFStopDescription(double fStop)
     304    {
     305        DecimalFormat format = new DecimalFormat("0.0");
     306        format.setRoundingMode(RoundingMode.HALF_UP);
     307        return "f/" + format.format(fStop);
     308    }
     309
     310    @Nullable
     311    protected static String getFocalLengthDescription(double mm)
     312    {
     313        DecimalFormat format = new DecimalFormat("0.#");
     314        format.setRoundingMode(RoundingMode.HALF_UP);
     315        return format.format(mm) + " mm";
     316    }
     317
     318    @Nullable
     319    protected String getLensSpecificationDescription(int tag)
     320    {
     321        Rational[] values = _directory.getRationalArray(tag);
     322
     323        if (values == null || values.length != 4 || (values[0].doubleValue() == 0 && values[2].doubleValue() == 0))
     324            return null;
     325
     326        StringBuilder sb = new StringBuilder();
     327
     328        if (values[0].equals(values[1]))
     329            sb.append(values[0].toSimpleString(true)).append("mm");
     330        else
     331            sb.append(values[0].toSimpleString(true)).append('-').append(values[1].toSimpleString(true)).append("mm");
     332
     333        if (values[2].doubleValue() != 0) {
     334            sb.append(' ');
     335
     336            DecimalFormat format = new DecimalFormat("0.0");
     337            format.setRoundingMode(RoundingMode.HALF_UP);
     338
     339            if (values[2].equals(values[3]))
     340                sb.append(getFStopDescription(values[2].doubleValue()));
     341            else
     342                sb.append("f/").append(format.format(values[2].doubleValue())).append('-').append(format.format(values[3].doubleValue()));
     343        }
     344
     345        return sb.toString();
     346    }
    274347}
  • trunk/src/com/drew/metadata/exif/ExifDescriptorBase.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    3030
    3131import java.io.UnsupportedEncodingException;
     32import java.math.RoundingMode;
    3233import java.text.DecimalFormat;
    3334import java.util.HashMap;
     
    5152    @NotNull
    5253    private static final java.text.DecimalFormat SimpleDecimalFormatter = new DecimalFormat("0.#");
    53     @NotNull
    54     private static final java.text.DecimalFormat SimpleDecimalFormatterWithPrecision = new DecimalFormat("0.0");
    5554
    5655    // Note for the potential addition of brightness presentation in eV:
     
    206205            case TAG_JPEG_PROC:
    207206                return getJpegProcDescription();
     207            case TAG_LENS_SPECIFICATION:
     208                return getLensSpecificationDescription();
    208209            default:
    209210                return super.getDescription(tagType);
     
    509510
    510511    @Nullable
     512    public String getLensSpecificationDescription()
     513    {
     514        return getLensSpecificationDescription(TAG_LENS_SPECIFICATION);
     515    }
     516
     517    @Nullable
    511518    public String getSharpnessDescription()
    512519    {
     
    568575            ? null
    569576            : value == 0
    570             ? "Unknown"
    571             : SimpleDecimalFormatter.format(value) + "mm";
     577                ? "Unknown"
     578                : getFocalLengthDescription(value);
    572579    }
    573580
     
    579586            ? null
    580587            : value.getNumerator() == 0
    581             ? "Digital zoom not used."
    582             : SimpleDecimalFormatter.format(value.doubleValue());
     588                ? "Digital zoom not used"
     589                : SimpleDecimalFormatter.format(value.doubleValue());
    583590    }
    584591
     
    711718            return null;
    712719        double fStop = PhotographicConversions.apertureToFStop(aperture);
    713         return "f/" + SimpleDecimalFormatterWithPrecision.format(fStop);
     720        return getFStopDescription(fStop);
    714721    }
    715722
     
    721728            return null;
    722729        double fStop = PhotographicConversions.apertureToFStop(aperture);
    723         return "f/" + SimpleDecimalFormatterWithPrecision.format(fStop);
     730        return getFStopDescription(fStop);
    724731    }
    725732
     
    807814    {
    808815        Rational value = _directory.getRational(TAG_FOCAL_LENGTH);
    809         if (value == null)
    810             return null;
    811         java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
    812         return formatter.format(value.doubleValue()) + " mm";
     816        return value == null ? null : getFocalLengthDescription(value.doubleValue());
    813817    }
    814818
     
    859863    public String getWhiteBalanceDescription()
    860864    {
    861         // '0' means unknown, '1' daylight, '2' fluorescent, '3' tungsten, '4' flash,
    862         // '17' standard light A, '18' standard light B, '19' standard light C, '20' D55,
    863         // '21' D65, '22' D75, '255' other.
    864         // see http://web.archive.org/web/20131018091152/http://exif.org/Exif2-2.PDF page 35
     865        // See http://web.archive.org/web/20131018091152/http://exif.org/Exif2-2.PDF page 35
    865866        final Integer value = _directory.getInteger(TAG_WHITE_BALANCE);
    866867        if (value == null)
     
    875876            case 10: return "Cloudy";
    876877            case 11: return "Shade";
    877             case 12: return "Daylight Flourescent";
    878             case 13: return "Day White Flourescent";
    879             case 14: return "Cool White Flourescent";
    880             case 15: return "White Flourescent";
    881             case 16: return "Warm White Flourescent";
     878            case 12: return "Daylight Fluorescent";
     879            case 13: return "Day White Fluorescent";
     880            case 14: return "Cool White Fluorescent";
     881            case 15: return "White Fluorescent";
     882            case 16: return "Warm White Fluorescent";
    882883            case 17: return "Standard light";
    883884            case 18: return "Standard light (B)";
     
    975976        if (value == null)
    976977            return null;
    977         java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
     978        DecimalFormat formatter = new DecimalFormat("0.0##");
    978979        return formatter.format(value.doubleValue()) + " metres";
    979980    }
     
    10181019            long apexPower10 = Math.round((double)apexPower * 10.0);
    10191020            float fApexPower = (float)apexPower10 / 10.0f;
    1020             return fApexPower + " sec";
     1021            DecimalFormat format = new DecimalFormat("0.##");
     1022            format.setRoundingMode(RoundingMode.HALF_UP);
     1023            return format.format(fApexPower) + " sec";
    10211024        } else {
    10221025            int apexPower = (int)((Math.exp(apexValue * Math.log(2))));
     
    10511054        if (value == null)
    10521055            return null;
    1053         return "f/" + SimpleDecimalFormatterWithPrecision.format(value.doubleValue());
     1056        return getFStopDescription(value.doubleValue());
    10541057    }
    10551058
  • trunk/src/com/drew/metadata/exif/ExifDirectoryBase.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    140140    public static final int TAG_TILE_BYTE_COUNTS                  = 0x0145;
    141141
     142    /**
     143     * Tag is a pointer to one or more sub-IFDs.
     144     + Seems to be used exclusively by raw formats, referencing one or two IFDs.
     145     */
    142146    public static final int TAG_SUB_IFD_OFFSET                    = 0x014a;
    143147
     
    150154    public static final int TAG_YCBCR_POSITIONING                 = 0x0213;
    151155    public static final int TAG_REFERENCE_BLACK_WHITE             = 0x0214;
     156    public static final int TAG_STRIP_ROW_COUNTS                  = 0x022f;
     157    public static final int TAG_APPLICATION_NOTES                 = 0x02bc;
    152158
    153159    public static final int TAG_RELATED_IMAGE_FILE_FORMAT         = 0x1000;
     
    260266    public static final int TAG_METERING_MODE                     = 0x9207;
    261267
    262     public static final int TAG_LIGHT_SOURCE                      = 0x9208; // TODO duplicate tag
    263268    /**
    264269     * White balance (aka light source). '0' means unknown, '1' daylight,
     
    267272     * '22' D75, '255' other.
    268273     */
    269     public static final int TAG_WHITE_BALANCE                     = 0x9208; // TODO duplicate tag
     274    public static final int TAG_WHITE_BALANCE                     = 0x9208;
    270275    /**
    271276     * 0x0  = 0000000 = No Flash
     
    600605        map.put(TAG_ROWS_PER_STRIP, "Rows Per Strip");
    601606        map.put(TAG_STRIP_BYTE_COUNTS, "Strip Byte Counts");
    602         map.put(TAG_MIN_SAMPLE_VALUE, "Minimum sample value");
    603         map.put(TAG_MAX_SAMPLE_VALUE, "Maximum sample value");
     607        map.put(TAG_MIN_SAMPLE_VALUE, "Minimum Sample Value");
     608        map.put(TAG_MAX_SAMPLE_VALUE, "Maximum Sample Value");
    604609        map.put(TAG_X_RESOLUTION, "X Resolution");
    605610        map.put(TAG_Y_RESOLUTION, "Y Resolution");
     
    627632        map.put(TAG_YCBCR_POSITIONING, "YCbCr Positioning");
    628633        map.put(TAG_REFERENCE_BLACK_WHITE, "Reference Black/White");
     634        map.put(TAG_STRIP_ROW_COUNTS, "Strip Row Counts");
     635        map.put(TAG_APPLICATION_NOTES, "Application Notes");
    629636        map.put(TAG_RELATED_IMAGE_FILE_FORMAT, "Related Image File Format");
    630637        map.put(TAG_RELATED_IMAGE_WIDTH, "Related Image Width");
     
    663670        map.put(TAG_SUBJECT_DISTANCE, "Subject Distance");
    664671        map.put(TAG_METERING_MODE, "Metering Mode");
    665         map.put(TAG_LIGHT_SOURCE, "Light Source");
    666672        map.put(TAG_WHITE_BALANCE, "White Balance");
    667673        map.put(TAG_FLASH, "Flash");
  • trunk/src/com/drew/metadata/exif/ExifIFD0Descriptor.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifIFD0Directory.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2222package com.drew.metadata.exif;
    2323
     24import java.util.HashMap;
     25
    2426import com.drew.lang.annotations.NotNull;
    25 
    26 import java.util.HashMap;
    2727
    2828/**
  • trunk/src/com/drew/metadata/exif/ExifInteropDescriptor.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifInteropDirectory.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifReader.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2828import com.drew.lang.RandomAccessReader;
    2929import com.drew.lang.annotations.NotNull;
     30import com.drew.lang.annotations.Nullable;
     31import com.drew.metadata.Directory;
    3032import com.drew.metadata.Metadata;
    3133
    3234import java.io.IOException;
    33 import java.util.Arrays;
     35import java.util.Collections;
    3436
    3537/**
     
    6062    public Iterable<JpegSegmentType> getSegmentTypes()
    6163    {
    62         return Arrays.asList(JpegSegmentType.APP1);
     64        return Collections.singletonList(JpegSegmentType.APP1);
    6365    }
    6466
     
    8486    public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int readerOffset)
    8587    {
     88        extract(reader, metadata, readerOffset, null);
     89    }
     90
     91    /** Reads TIFF formatted Exif data a specified offset within a {@link RandomAccessReader}. */
     92    public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int readerOffset, @Nullable Directory parentDirectory)
     93    {
    8694        try {
    8795            // Read the TIFF-formatted Exif data
    8896            new TiffReader().processTiff(
    8997                reader,
    90                 new ExifTiffHandler(metadata, _storeThumbnailBytes),
     98                new ExifTiffHandler(metadata, _storeThumbnailBytes, parentDirectory),
    9199                readerOffset
    92100            );
  • trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/ExifSubIFDDirectory.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2121package com.drew.metadata.exif;
    2222
     23import java.util.Date;
     24import java.util.HashMap;
     25import java.util.TimeZone;
     26
    2327import com.drew.lang.annotations.NotNull;
    24 
    25 import java.util.HashMap;
     28import com.drew.lang.annotations.Nullable;
    2629
    2730/**
     
    6164        return _tagNameMap;
    6265    }
     66
     67    /**
     68     * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
     69     * representing the date and time when this image was captured.  Attempts will be made to parse the
     70     * values as though it is in the GMT {@link TimeZone}.
     71     *
     72     * @return A Date object representing when this image was captured, if possible, otherwise null
     73     */
     74    @Nullable
     75    public Date getDateOriginal()
     76    {
     77        return getDateOriginal(null);
     78    }
     79
     80    /**
     81     * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
     82     * representing the date and time when this image was captured.  Attempts will be made to parse the
     83     * values as though it is in the {@link TimeZone} represented by the {@code timeZone} parameter
     84     * (if it is non-null).
     85     *
     86     * @param timeZone the time zone to use
     87     * @return A Date object representing when this image was captured, if possible, otherwise null
     88     */
     89    @Nullable
     90    public Date getDateOriginal(TimeZone timeZone)
     91    {
     92        return getDate(TAG_DATETIME_ORIGINAL, getString(TAG_SUBSECOND_TIME_ORIGINAL), timeZone);
     93    }
     94
     95    /**
     96     * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
     97     * representing the date and time when this image was digitized.  Attempts will be made to parse the
     98     * values as though it is in the GMT {@link TimeZone}.
     99     *
     100     * @return A Date object representing when this image was digitized, if possible, otherwise null
     101     */
     102    @Nullable
     103    public Date getDateDigitized()
     104    {
     105        return getDateDigitized(null);
     106    }
     107
     108    /**
     109     * Parses the date/time tag and the subsecond tag to obtain a single Date object with milliseconds
     110     * representing the date and time when this image was digitized.  Attempts will be made to parse the
     111     * values as though it is in the {@link TimeZone} represented by the {@code timeZone} parameter
     112     * (if it is non-null).
     113     *
     114     * @param timeZone the time zone to use
     115     * @return A Date object representing when this image was digitized, if possible, otherwise null
     116     */
     117    @Nullable
     118    public Date getDateDigitized(TimeZone timeZone)
     119    {
     120        return getDate(TAG_DATETIME_DIGITIZED, getString(TAG_SUBSECOND_TIME_DIGITIZED), timeZone);
     121    }
    63122}
  • trunk/src/com/drew/metadata/exif/ExifThumbnailDescriptor.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2222package com.drew.metadata.exif;
    2323
     24import static com.drew.metadata.exif.ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH;
     25import static com.drew.metadata.exif.ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET;
     26
    2427import com.drew.lang.annotations.NotNull;
    2528import com.drew.lang.annotations.Nullable;
    26 
    27 import static com.drew.metadata.exif.ExifThumbnailDirectory.*;
    2829
    2930/**
     
    4849            case TAG_THUMBNAIL_LENGTH:
    4950                return getThumbnailLengthDescription();
    50             case TAG_THUMBNAIL_COMPRESSION:
    51                 return getCompressionDescription();
    5251            default:
    5352                return super.getDescription(tagType);
    54         }
    55     }
    56 
    57     @Nullable
    58     public String getCompressionDescription()
    59     {
    60         Integer value = _directory.getInteger(TAG_THUMBNAIL_COMPRESSION);
    61         if (value == null)
    62             return null;
    63         switch (value) {
    64             case 1: return "Uncompressed";
    65             case 2: return "CCITT 1D";
    66             case 3: return "T4/Group 3 Fax";
    67             case 4: return "T6/Group 4 Fax";
    68             case 5: return "LZW";
    69             case 6: return "JPEG (old-style)";
    70             case 7: return "JPEG";
    71             case 8: return "Adobe Deflate";
    72             case 9: return "JBIG B&W";
    73             case 10: return "JBIG Color";
    74             case 32766: return "Next";
    75             case 32771: return "CCIRLEW";
    76             case 32773: return "PackBits";
    77             case 32809: return "Thunderscan";
    78             case 32895: return "IT8CTPAD";
    79             case 32896: return "IT8LW";
    80             case 32897: return "IT8MP";
    81             case 32898: return "IT8BL";
    82             case 32908: return "PixarFilm";
    83             case 32909: return "PixarLog";
    84             case 32946: return "Deflate";
    85             case 32947: return "DCS";
    86             case 32661: return "JBIG";
    87             case 32676: return "SGILog";
    88             case 32677: return "SGILog24";
    89             case 32712: return "JPEG 2000";
    90             case 32713: return "Nikon NEF Compressed";
    91             default:
    92                 return "Unknown compression";
    9353        }
    9454    }
  • trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2222package com.drew.metadata.exif;
    2323
     24import java.io.FileOutputStream;
     25import java.io.IOException;
     26import java.util.HashMap;
     27
    2428import com.drew.lang.annotations.NotNull;
    2529import com.drew.lang.annotations.Nullable;
    2630import com.drew.metadata.MetadataException;
    27 
    28 import java.io.FileOutputStream;
    29 import java.io.IOException;
    30 import java.util.HashMap;
    3131
    3232/**
     
    4646    public static final int TAG_THUMBNAIL_LENGTH = 0x0202;
    4747
    48     /**
    49      * Shows compression method for Thumbnail.
    50      * 1 = Uncompressed
    51      * 2 = CCITT 1D
    52      * 3 = T4/Group 3 Fax
    53      * 4 = T6/Group 4 Fax
    54      * 5 = LZW
    55      * 6 = JPEG (old-style)
    56      * 7 = JPEG
    57      * 8 = Adobe Deflate
    58      * 9 = JBIG B&amp;W
    59      * 10 = JBIG Color
    60      * 32766 = Next
    61      * 32771 = CCIRLEW
    62      * 32773 = PackBits
    63      * 32809 = Thunderscan
    64      * 32895 = IT8CTPAD
    65      * 32896 = IT8LW
    66      * 32897 = IT8MP
    67      * 32898 = IT8BL
    68      * 32908 = PixarFilm
    69      * 32909 = PixarLog
    70      * 32946 = Deflate
    71      * 32947 = DCS
    72      * 34661 = JBIG
    73      * 34676 = SGILog
    74      * 34677 = SGILog24
    75      * 34712 = JPEG 2000
    76      * 34713 = Nikon NEF Compressed
    77      */
    78     public static final int TAG_THUMBNAIL_COMPRESSION = 0x0103;
    79 
    8048    @NotNull
    8149    protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
     
    8553        addExifTagNames(_tagNameMap);
    8654
    87         _tagNameMap.put(TAG_THUMBNAIL_COMPRESSION, "Thumbnail Compression");
    8855        _tagNameMap.put(TAG_THUMBNAIL_OFFSET, "Thumbnail Offset");
    8956        _tagNameMap.put(TAG_THUMBNAIL_LENGTH, "Thumbnail Length");
  • trunk/src/com/drew/metadata/exif/ExifTiffHandler.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2626import com.drew.lang.SequentialByteArrayReader;
    2727import com.drew.lang.annotations.NotNull;
     28import com.drew.lang.annotations.Nullable;
    2829import com.drew.metadata.Directory;
    2930import com.drew.metadata.Metadata;
     
    4748    private final boolean _storeThumbnailBytes;
    4849
    49     public ExifTiffHandler(@NotNull Metadata metadata, boolean storeThumbnailBytes)
     50    public ExifTiffHandler(@NotNull Metadata metadata, boolean storeThumbnailBytes, @Nullable Directory parentDirectory)
    5051    {
    5152        super(metadata, ExifIFD0Directory.class);
    5253        _storeThumbnailBytes = storeThumbnailBytes;
     54
     55        if (parentDirectory != null)
     56            _currentDirectory.setParent(parentDirectory);
    5357    }
    5458
     
    6569    }
    6670
    67     public boolean isTagIfdPointer(int tagType)
    68     {
    69         if (tagType == ExifIFD0Directory.TAG_EXIF_SUB_IFD_OFFSET && _currentDirectory instanceof ExifIFD0Directory) {
     71    public boolean tryEnterSubIfd(int tagId)
     72    {
     73        if (tagId == ExifDirectoryBase.TAG_SUB_IFD_OFFSET) {
    7074            pushDirectory(ExifSubIFDDirectory.class);
    7175            return true;
    72         } else if (tagType == ExifIFD0Directory.TAG_GPS_INFO_OFFSET && _currentDirectory instanceof ExifIFD0Directory) {
    73             pushDirectory(GpsDirectory.class);
    74             return true;
    75         } else if (tagType == ExifSubIFDDirectory.TAG_INTEROP_OFFSET && _currentDirectory instanceof ExifSubIFDDirectory) {
    76             pushDirectory(ExifInteropDirectory.class);
    77             return true;
     76        }
     77
     78        if (_currentDirectory instanceof ExifIFD0Directory) {
     79            if (tagId == ExifIFD0Directory.TAG_EXIF_SUB_IFD_OFFSET) {
     80                pushDirectory(ExifSubIFDDirectory.class);
     81                return true;
     82            }
     83
     84            if (tagId == ExifIFD0Directory.TAG_GPS_INFO_OFFSET) {
     85                pushDirectory(GpsDirectory.class);
     86                return true;
     87            }
     88        }
     89
     90        if (_currentDirectory instanceof ExifSubIFDDirectory) {
     91            if (tagId == ExifSubIFDDirectory.TAG_INTEROP_OFFSET) {
     92                pushDirectory(ExifInteropDirectory.class);
     93                return true;
     94            }
     95        }
     96
     97        if (_currentDirectory instanceof OlympusMakernoteDirectory) {
     98            if (tagId == OlympusMakernoteDirectory.TAG_EQUIPMENT) {
     99                pushDirectory(OlympusEquipmentMakernoteDirectory.class);
     100                return true;
     101            }
     102
     103            if (tagId == OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS) {
     104                pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
     105                return true;
     106            }
    78107        }
    79108
     
    96125        // NOTE have seen the CanonMakernoteDirectory IFD have a follower pointer, but it points to invalid data.
    97126        return false;
     127    }
     128
     129    @Nullable
     130    public Long tryCustomProcessFormat(final int tagId, final int formatCode, final long componentCount)
     131    {
     132        if (formatCode == 13)
     133            return componentCount * 4;
     134
     135        return null;
    98136    }
    99137
     
    115153            if (reader.getInt8(tagOffset) == 0x1c) {
    116154                final byte[] iptcBytes = reader.getBytes(tagOffset, byteCount);
    117                 new IptcReader().extract(new SequentialByteArrayReader(iptcBytes), _metadata, iptcBytes.length);
     155                new IptcReader().extract(new SequentialByteArrayReader(iptcBytes), _metadata, iptcBytes.length, _currentDirectory);
    118156                return true;
    119157            }
     
    129167            // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
    130168            ExifThumbnailDirectory thumbnailDirectory = _metadata.getFirstDirectoryOfType(ExifThumbnailDirectory.class);
    131             if (thumbnailDirectory != null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION)) {
     169            if (thumbnailDirectory != null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_COMPRESSION)) {
    132170                Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
    133171                Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
     
    164202        final String firstSevenChars = reader.getString(makernoteOffset, 7);
    165203        final String firstEightChars = reader.getString(makernoteOffset, 8);
     204        final String firstTenChars = reader.getString(makernoteOffset, 10);
    166205        final String firstTwelveChars = reader.getString(makernoteOffset, 12);
    167206
    168207        boolean byteOrderBefore = reader.isMotorolaByteOrder();
    169208
    170         if ("OLYMP".equals(firstFiveChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {
     209        if ("OLYMP\0".equals(firstSixChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {
    171210            // Olympus Makernote
    172211            // Epson and Agfa use Olympus makernote standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/
    173212            pushDirectory(OlympusMakernoteDirectory.class);
    174213            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
     214        } else if ("OLYMPUS\0II".equals(firstTenChars)) {
     215            // Olympus Makernote (alternate)
     216            // Note that data is relative to the beginning of the makernote
     217            // http://exiv2.org/makernote.html
     218            pushDirectory(OlympusMakernoteDirectory.class);
     219            TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, makernoteOffset);
    175220        } else if (cameraMake != null && cameraMake.toUpperCase().startsWith("MINOLTA")) {
    176221            // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote
  • trunk/src/com/drew/metadata/exif/GpsDescriptor.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    110110        // time in hour, min, sec
    111111        Rational[] timeComponents = _directory.getRationalArray(TAG_TIME_STAMP);
    112         DecimalFormat df = new DecimalFormat("00.00");
     112        DecimalFormat df = new DecimalFormat("00.000");
    113113        return timeComponents == null
    114114            ? null
  • trunk/src/com/drew/metadata/exif/GpsDirectory.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2626import com.drew.lang.annotations.Nullable;
    2727
     28import java.text.DateFormat;
     29import java.text.ParseException;
     30import java.text.SimpleDateFormat;
     31import java.util.Date;
    2832import java.util.HashMap;
     33import java.util.Locale;
    2934
    3035/**
     
    164169    public GeoLocation getGeoLocation()
    165170    {
    166         Rational[] latitudes = getRationalArray(GpsDirectory.TAG_LATITUDE);
    167         Rational[] longitudes = getRationalArray(GpsDirectory.TAG_LONGITUDE);
    168         String latitudeRef = getString(GpsDirectory.TAG_LATITUDE_REF);
    169         String longitudeRef = getString(GpsDirectory.TAG_LONGITUDE_REF);
     171        Rational[] latitudes = getRationalArray(TAG_LATITUDE);
     172        Rational[] longitudes = getRationalArray(TAG_LONGITUDE);
     173        String latitudeRef = getString(TAG_LATITUDE_REF);
     174        String longitudeRef = getString(TAG_LONGITUDE_REF);
    170175
    171176        // Make sure we have the required values
     
    186191        return new GeoLocation(lat, lon);
    187192    }
     193
     194    /**
     195     * Parses the date stamp tag and the time stamp tag to obtain a single Date object representing the
     196     * date and time when this image was captured.
     197     *
     198     * @return A Date object representing when this image was captured, if possible, otherwise null
     199     */
     200    @Nullable
     201    public Date getGpsDate()
     202    {
     203        String date = getString(TAG_DATE_STAMP);
     204        Rational[] timeComponents = getRationalArray(TAG_TIME_STAMP);
     205
     206        // Make sure we have the required values
     207        if (date == null)
     208            return null;
     209        if (timeComponents == null || timeComponents.length != 3)
     210            return null;
     211
     212        String dateTime = String.format(Locale.US, "%s %02d:%02d:%02.3f UTC",
     213            date, timeComponents[0].intValue(), timeComponents[1].intValue(), timeComponents[2].doubleValue());
     214        try {
     215            DateFormat parser = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss.S z");
     216            return parser.parse(dateTime);
     217        } catch (ParseException e) {
     218            return null;
     219        }
     220    }
    188221}
  • trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDescriptor.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2424import com.drew.lang.annotations.Nullable;
    2525import com.drew.metadata.TagDescriptor;
     26
     27import java.text.DecimalFormat;
    2628
    2729import static com.drew.metadata.exif.makernotes.CanonMakernoteDirectory.*;
     
    138140    public String getSerialNumberDescription()
    139141    {
     142        // http://www.ozhiker.com/electronics/pjmt/jpeg_info/canon_mn.html
    140143        Integer value = _directory.getInteger(TAG_CANON_SERIAL_NUMBER);
    141144        if (value == null)
     
    673676            return "Self timer not used";
    674677        } else {
    675             // TODO find an image that tests this calculation
    676             return Double.toString((double)value * 0.1d) + " sec";
     678            DecimalFormat format = new DecimalFormat("0.##");
     679            return format.format((double)value * 0.1d) + " sec";
    677680        }
    678681    }
  • trunk/src/com/drew/metadata/exif/makernotes/CanonMakernoteDirectory.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    155155    {
    156156        Integer value = _directory.getInteger(TAG_OBJECT_DISTANCE);
    157 
    158         if (value == null)
    159             return null;
    160 
    161         return value + " mm";
     157        return value == null ? null : getFocalLengthDescription(value);
    162158    }
    163159
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType1MakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    240240    {
    241241        Double value = _directory.getDoubleObject(TAG_FOCAL_LENGTH);
    242         if (value == null)
    243             return null;
    244         return Double.toString(value / 10d) + " mm";
     242        return value == null ? null : getFocalLengthDescription(value / 10d);
    245243    }
    246244
  • trunk/src/com/drew/metadata/exif/makernotes/CasioType2MakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/FujifilmMakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/KodakMakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/KyoceraMakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/LeicaMakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType1MakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    306306    {
    307307        int[] values = _directory.getIntArray(tagType);
    308         if (values == null)
     308        if (values == null || values.length < 2)
    309309            return null;
    310310        if (values.length < 3 || values[2] == 0)
     
    329329    public String getLensDescription()
    330330    {
    331         Rational[] values = _directory.getRationalArray(TAG_LENS);
    332 
    333         return values == null
    334             ? null
    335             : values.length < 4
    336                 ? _directory.getString(TAG_LENS)
    337                 : String.format("%d-%dmm f/%.1f-%.1f", values[0].intValue(), values[1].intValue(), values[2].floatValue(), values[3].floatValue());
    338 
     331        return getLensSpecificationDescription(TAG_LENS);
    339332    }
    340333
  • trunk/src/com/drew/metadata/exif/makernotes/NikonType2MakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2121package com.drew.metadata.exif.makernotes;
    2222
     23import com.drew.lang.DateUtil;
    2324import com.drew.lang.annotations.NotNull;
    2425import com.drew.lang.annotations.Nullable;
    2526import com.drew.metadata.TagDescriptor;
    2627
    27 import java.util.GregorianCalendar;
     28import java.math.RoundingMode;
     29import java.text.DecimalFormat;
    2830
    2931import static com.drew.metadata.exif.makernotes.OlympusMakernoteDirectory.*;
     
    115117                return getFocusDistanceDescription();
    116118            case CameraSettings.TAG_FLASH_FIRED:
    117                 return getFlastFiredDescription();
     119                return getFlashFiredDescription();
    118120            case CameraSettings.TAG_DATE:
    119121                return getDateDescription();
     
    142144                return getSubjectProgramDescription();
    143145            case CameraSettings.TAG_FLASH_COMPENSATION:
    144                 return getFlastCompensationDescription();
     146                return getFlashCompensationDescription();
    145147            case CameraSettings.TAG_ISO_SETTING:
    146148                return getIsoSettingDescription();
     
    258260
    259261        double iso = Math.pow((value / 8d) - 1, 2) * 3.125;
    260         return Double.toString(iso);
     262        DecimalFormat format = new DecimalFormat("0.##");
     263        format.setRoundingMode(RoundingMode.HALF_UP);
     264        return format.format(iso);
    261265    }
    262266
     
    274278
    275279        double shutterSpeed = Math.pow((49-value) / 8d, 2);
    276         return Double.toString(shutterSpeed) + " sec";
     280        DecimalFormat format = new DecimalFormat("0.###");
     281        format.setRoundingMode(RoundingMode.HALF_UP);
     282        return format.format(shutterSpeed) + " sec";
    277283    }
    278284
     
    289295
    290296        double fStop = Math.pow((value/16d) - 0.5, 2);
    291         return "F" + Double.toString(fStop);
     297        return getFStopDescription(fStop);
    292298    }
    293299
     
    308314    {
    309315        Long value = _directory.getLongObject(CameraSettings.TAG_EXPOSURE_COMPENSATION);
    310         return value == null ? null : ((value / 3d) - 2) + " EV";
     316        DecimalFormat format = new DecimalFormat("0.##");
     317        return value == null ? null : format.format((value / 3d) - 2) + " EV";
    311318    }
    312319
     
    341348    {
    342349        Long value = _directory.getLongObject(CameraSettings.TAG_FOCAL_LENGTH);
    343         return value == null ? null : Double.toString(value/256d) + " mm";
     350        return value == null ? null : getFocalLengthDescription(value/256d);
    344351    }
    345352
     
    356363
    357364    @Nullable
    358     public String getFlastFiredDescription()
     365    public String getFlashFiredDescription()
    359366    {
    360367        return getIndexedDescription(CameraSettings.TAG_FLASH_FIRED, "No", "Yes");
     
    370377        if (value == null)
    371378            return null;
    372         long day = value & 0xFF;
    373         long month = (value >> 16) & 0xFF;
    374         long year = (value >> 8) & 0xFF;
    375         return new GregorianCalendar((int)year + 1970, (int)month, (int)day).getTime().toString();
     379
     380        int day = (int) (value & 0xFF);
     381        int month = (int) ((value >> 16) & 0xFF);
     382        int year = (int) ((value >> 8) & 0xFF) + 1970;
     383
     384        if (!DateUtil.isValidDate(year, month, day))
     385            return "Invalid date";
     386
     387        return String.format("%04d-%02d-%02d", year, month + 1, day);
    376388    }
    377389
     
    385397        if (value == null)
    386398            return null;
    387         long hours = (value >> 8) & 0xFF;
    388         long minutes = (value >> 16) & 0xFF;
    389         long seconds = value & 0xFF;
     399
     400        int hours = (int) ((value >> 8) & 0xFF);
     401        int minutes = (int) ((value >> 16) & 0xFF);
     402        int seconds = (int) (value & 0xFF);
     403
     404        if (!DateUtil.isValidTime(hours, minutes, seconds))
     405            return "Invalid time";
    390406
    391407        return String.format("%02d:%02d:%02d", hours, minutes, seconds);
     
    400416            return null;
    401417        double fStop = Math.pow((value/16d) - 0.5, 2);
    402         return "F" + fStop;
     418        return getFStopDescription(fStop);
    403419    }
    404420
     
    424440    {
    425441        Long value = _directory.getLongObject(CameraSettings.TAG_WHITE_BALANCE_RED);
    426         return value == null ? null : Double.toString(value/256d);
     442        DecimalFormat format = new DecimalFormat("0.##");
     443        return value == null ? null : format.format(value/256d);
    427444    }
    428445
     
    431448    {
    432449        Long value = _directory.getLongObject(CameraSettings.TAG_WHITE_BALANCE_GREEN);
    433         return value == null ? null : Double.toString(value/256d);
     450        DecimalFormat format = new DecimalFormat("0.##");
     451        return value == null ? null : format.format(value/256d);
    434452    }
    435453
     
    438456    {
    439457        Long value = _directory.getLongObject(CameraSettings.TAG_WHITE_BALANCE_BLUE);
    440         return value == null ? null : Double.toString(value/256d);
     458        DecimalFormat format = new DecimalFormat("0.##");
     459        return value == null ? null : format.format(value / 256d);
    441460    }
    442461
     
    468487
    469488    @Nullable
    470     public String getFlastCompensationDescription()
     489    public String getFlashCompensationDescription()
    471490    {
    472491        Long value = _directory.getLongObject(CameraSettings.TAG_FLASH_COMPENSATION);
    473         return value == null ? null : ((value-6)/3d) + " EV";
     492        DecimalFormat format = new DecimalFormat("0.##");
     493        return value == null ? null : format.format((value-6)/3d) + " EV";
    474494    }
    475495
     
    535555    {
    536556        Long value = _directory.getLongObject(CameraSettings.TAG_APEX_BRIGHTNESS_VALUE);
    537         return value == null ? null : Double.toString((value/8d)-6);
     557        DecimalFormat format = new DecimalFormat("0.##");
     558        return value == null ? null : format.format((value/8d)-6);
    538559    }
    539560
  • trunk/src/com/drew/metadata/exif/makernotes/OlympusMakernoteDirectory.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/PanasonicMakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/PentaxMakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/PentaxMakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/RicohMakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/RicohMakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SanyoMakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SanyoMakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SigmaMakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SigmaMakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SonyType1MakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SonyType1MakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SonyType6MakernoteDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/SonyType6MakernoteDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/makernotes/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/exif/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/file/FileMetadataDescriptor.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2525import com.drew.metadata.TagDescriptor;
    2626
     27import static com.drew.metadata.file.FileMetadataDirectory.*;
     28
    2729/**
    2830 * @author Drew Noakes https://drewnoakes.com
     
    4042    {
    4143        switch (tagType) {
    42             case FileMetadataDirectory.TAG_FILE_SIZE:
     44            case TAG_FILE_SIZE:
    4345                return getFileSizeDescription();
    4446            default:
     
    5052    private String getFileSizeDescription()
    5153    {
    52         Long size = _directory.getLongObject(FileMetadataDirectory.TAG_FILE_SIZE);
     54        Long size = _directory.getLongObject(TAG_FILE_SIZE);
    5355
    5456        if (size == null)
  • trunk/src/com/drew/metadata/file/FileMetadataDirectory.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/file/FileMetadataReader.java

    r8243 r10862  
     1/*
     2 * Copyright 2002-2016 Drew Noakes
     3 *
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
     7 *
     8 *        http://www.apache.org/licenses/LICENSE-2.0
     9 *
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
     20 */
    121package com.drew.metadata.file;
    222
  • trunk/src/com/drew/metadata/file/package.html

    r8243 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/iptc/IptcDescriptor.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2626import com.drew.metadata.TagDescriptor;
    2727
     28import static com.drew.metadata.iptc.IptcDirectory.*;
     29
    2830/**
    2931 * Provides human-readable string representations of tag values stored in a {@link IptcDirectory}.
     
    4547    {
    4648        switch (tagType) {
    47             case IptcDirectory.TAG_FILE_FORMAT:
     49            case TAG_DATE_CREATED:
     50                return getDateCreatedDescription();
     51            case TAG_DIGITAL_DATE_CREATED:
     52                return getDigitalDateCreatedDescription();
     53            case TAG_DATE_SENT:
     54                return getDateSentDescription();
     55            case TAG_EXPIRATION_DATE:
     56                return getExpirationDateDescription();
     57            case TAG_EXPIRATION_TIME:
     58                return getExpirationTimeDescription();
     59            case TAG_FILE_FORMAT:
    4860                return getFileFormatDescription();
    49             case IptcDirectory.TAG_KEYWORDS:
     61            case TAG_KEYWORDS:
    5062                return getKeywordsDescription();
    51             case IptcDirectory.TAG_TIME_CREATED:
     63            case TAG_REFERENCE_DATE:
     64                return getReferenceDateDescription();
     65            case TAG_RELEASE_DATE:
     66                return getReleaseDateDescription();
     67            case TAG_RELEASE_TIME:
     68                return getReleaseTimeDescription();
     69            case TAG_TIME_CREATED:
    5270                return getTimeCreatedDescription();
    53             case IptcDirectory.TAG_DIGITAL_TIME_CREATED:
     71            case TAG_DIGITAL_TIME_CREATED:
    5472                return getDigitalTimeCreatedDescription();
     73            case TAG_TIME_SENT:
     74                return getTimeSentDescription();
    5575            default:
    5676                return super.getDescription(tagType);
     
    5979
    6080    @Nullable
     81    public String getDateDescription(int tagType)
     82    {
     83        String s = _directory.getString(tagType);
     84        if (s == null)
     85            return null;
     86        if (s.length() == 8)
     87            return s.substring(0, 4) + ':' + s.substring(4, 6) + ':' + s.substring(6);
     88        return s;
     89    }
     90
     91    @Nullable
     92    public String getTimeDescription(int tagType)
     93    {
     94        String s = _directory.getString(tagType);
     95        if (s == null)
     96            return null;
     97        if (s.length() == 6 || s.length() == 11)
     98            return s.substring(0, 2) + ':' + s.substring(2, 4) + ':' + s.substring(4);
     99        return s;
     100    }
     101
     102    @Nullable
    61103    public String getFileFormatDescription()
    62104    {
    63         Integer value = _directory.getInteger(IptcDirectory.TAG_FILE_FORMAT);
     105        Integer value = _directory.getInteger(TAG_FILE_FORMAT);
    64106        if (value == null)
    65107            return null;
     
    102144    public String getByLineDescription()
    103145    {
    104         return _directory.getString(IptcDirectory.TAG_BY_LINE);
     146        return _directory.getString(TAG_BY_LINE);
    105147    }
    106148
     
    108150    public String getByLineTitleDescription()
    109151    {
    110         return _directory.getString(IptcDirectory.TAG_BY_LINE_TITLE);
     152        return _directory.getString(TAG_BY_LINE_TITLE);
    111153    }
    112154
     
    114156    public String getCaptionDescription()
    115157    {
    116         return _directory.getString(IptcDirectory.TAG_CAPTION);
     158        return _directory.getString(TAG_CAPTION);
    117159    }
    118160
     
    120162    public String getCategoryDescription()
    121163    {
    122         return _directory.getString(IptcDirectory.TAG_CATEGORY);
     164        return _directory.getString(TAG_CATEGORY);
    123165    }
    124166
     
    126168    public String getCityDescription()
    127169    {
    128         return _directory.getString(IptcDirectory.TAG_CITY);
     170        return _directory.getString(TAG_CITY);
    129171    }
    130172
     
    132174    public String getCopyrightNoticeDescription()
    133175    {
    134         return _directory.getString(IptcDirectory.TAG_COPYRIGHT_NOTICE);
     176        return _directory.getString(TAG_COPYRIGHT_NOTICE);
    135177    }
    136178
     
    138180    public String getCountryOrPrimaryLocationDescription()
    139181    {
    140         return _directory.getString(IptcDirectory.TAG_COUNTRY_OR_PRIMARY_LOCATION_NAME);
     182        return _directory.getString(TAG_COUNTRY_OR_PRIMARY_LOCATION_NAME);
    141183    }
    142184
     
    144186    public String getCreditDescription()
    145187    {
    146         return _directory.getString(IptcDirectory.TAG_CREDIT);
     188        return _directory.getString(TAG_CREDIT);
    147189    }
    148190
     
    150192    public String getDateCreatedDescription()
    151193    {
    152         return _directory.getString(IptcDirectory.TAG_DATE_CREATED);
     194        return getDateDescription(TAG_DATE_CREATED);
     195    }
     196
     197    @Nullable
     198    public String getDigitalDateCreatedDescription()
     199    {
     200        return getDateDescription(TAG_DIGITAL_DATE_CREATED);
     201    }
     202
     203    @Nullable
     204    public String getDateSentDescription()
     205    {
     206        return getDateDescription(TAG_DATE_SENT);
     207    }
     208
     209    @Nullable
     210    public String getExpirationDateDescription()
     211    {
     212        return getDateDescription(TAG_EXPIRATION_DATE);
     213    }
     214
     215    @Nullable
     216    public String getExpirationTimeDescription()
     217    {
     218        return getTimeDescription(TAG_EXPIRATION_TIME);
    153219    }
    154220
     
    156222    public String getHeadlineDescription()
    157223    {
    158         return _directory.getString(IptcDirectory.TAG_HEADLINE);
     224        return _directory.getString(TAG_HEADLINE);
    159225    }
    160226
     
    162228    public String getKeywordsDescription()
    163229    {
    164         final String[] keywords = _directory.getStringArray(IptcDirectory.TAG_KEYWORDS);
     230        final String[] keywords = _directory.getStringArray(TAG_KEYWORDS);
    165231        if (keywords==null)
    166232            return null;
     
    171237    public String getObjectNameDescription()
    172238    {
    173         return _directory.getString(IptcDirectory.TAG_OBJECT_NAME);
     239        return _directory.getString(TAG_OBJECT_NAME);
    174240    }
    175241
     
    177243    public String getOriginalTransmissionReferenceDescription()
    178244    {
    179         return _directory.getString(IptcDirectory.TAG_ORIGINAL_TRANSMISSION_REFERENCE);
     245        return _directory.getString(TAG_ORIGINAL_TRANSMISSION_REFERENCE);
    180246    }
    181247
     
    183249    public String getOriginatingProgramDescription()
    184250    {
    185         return _directory.getString(IptcDirectory.TAG_ORIGINATING_PROGRAM);
     251        return _directory.getString(TAG_ORIGINATING_PROGRAM);
    186252    }
    187253
     
    189255    public String getProvinceOrStateDescription()
    190256    {
    191         return _directory.getString(IptcDirectory.TAG_PROVINCE_OR_STATE);
     257        return _directory.getString(TAG_PROVINCE_OR_STATE);
    192258    }
    193259
     
    195261    public String getRecordVersionDescription()
    196262    {
    197         return _directory.getString(IptcDirectory.TAG_APPLICATION_RECORD_VERSION);
     263        return _directory.getString(TAG_APPLICATION_RECORD_VERSION);
     264    }
     265
     266    @Nullable
     267    public String getReferenceDateDescription()
     268    {
     269        return getDateDescription(TAG_REFERENCE_DATE);
    198270    }
    199271
     
    201273    public String getReleaseDateDescription()
    202274    {
    203         return _directory.getString(IptcDirectory.TAG_RELEASE_DATE);
     275        return getDateDescription(TAG_RELEASE_DATE);
    204276    }
    205277
     
    207279    public String getReleaseTimeDescription()
    208280    {
    209         return _directory.getString(IptcDirectory.TAG_RELEASE_TIME);
     281        return getTimeDescription(TAG_RELEASE_TIME);
    210282    }
    211283
     
    213285    public String getSourceDescription()
    214286    {
    215         return _directory.getString(IptcDirectory.TAG_SOURCE);
     287        return _directory.getString(TAG_SOURCE);
    216288    }
    217289
     
    219291    public String getSpecialInstructionsDescription()
    220292    {
    221         return _directory.getString(IptcDirectory.TAG_SPECIAL_INSTRUCTIONS);
     293        return _directory.getString(TAG_SPECIAL_INSTRUCTIONS);
    222294    }
    223295
     
    225297    public String getSupplementalCategoriesDescription()
    226298    {
    227         return _directory.getString(IptcDirectory.TAG_SUPPLEMENTAL_CATEGORIES);
     299        return _directory.getString(TAG_SUPPLEMENTAL_CATEGORIES);
    228300    }
    229301
     
    231303    public String getTimeCreatedDescription()
    232304    {
    233         String s = _directory.getString(IptcDirectory.TAG_TIME_CREATED);
    234         if (s == null)
    235             return null;
    236         if (s.length() == 6 || s.length() == 11)
    237             return s.substring(0, 2) + ':' + s.substring(2, 4) + ':' + s.substring(4);
    238         return s;
     305        return getTimeDescription(TAG_TIME_CREATED);
    239306    }
    240307
     
    242309    public String getDigitalTimeCreatedDescription()
    243310    {
    244         String s = _directory.getString(IptcDirectory.TAG_DIGITAL_TIME_CREATED);
    245         if (s == null)
    246             return null;
    247         if (s.length() == 6 || s.length() == 11)
    248             return s.substring(0, 2) + ':' + s.substring(2, 4) + ':' + s.substring(4);
    249         return s;
     311        return getTimeDescription(TAG_DIGITAL_TIME_CREATED);
     312    }
     313
     314    @Nullable
     315    public String getTimeSentDescription()
     316    {
     317        return getTimeDescription(TAG_TIME_SENT);
    250318    }
    251319
     
    253321    public String getUrgencyDescription()
    254322    {
    255         return _directory.getString(IptcDirectory.TAG_URGENCY);
     323        return _directory.getString(TAG_URGENCY);
    256324    }
    257325
     
    259327    public String getWriterDescription()
    260328    {
    261         return _directory.getString(IptcDirectory.TAG_CAPTION_WRITER);
     329        return _directory.getString(TAG_CAPTION_WRITER);
    262330    }
    263331}
  • trunk/src/com/drew/metadata/iptc/IptcDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2525import com.drew.metadata.Directory;
    2626
     27import java.text.DateFormat;
     28import java.text.ParseException;
     29import java.text.SimpleDateFormat;
    2730import java.util.Arrays;
     31import java.util.Date;
    2832import java.util.HashMap;
    2933import java.util.List;
     
    231235    public List<String> getKeywords()
    232236    {
    233         final String[] array = getStringArray(IptcDirectory.TAG_KEYWORDS);
     237        final String[] array = getStringArray(TAG_KEYWORDS);
    234238        if (array==null)
    235239            return null;
    236240        return Arrays.asList(array);
    237241    }
     242
     243    /**
     244     * Parses the Date Sent tag and the Time Sent tag to obtain a single Date object representing the
     245     * date and time when the service sent this image.
     246     * @return A Date object representing when the service sent this image, if possible, otherwise null
     247     */
     248    @Nullable
     249    public Date getDateSent()
     250    {
     251        return getDate(TAG_DATE_SENT, TAG_TIME_SENT);
     252    }
     253
     254    /**
     255     * Parses the Release Date tag and the Release Time tag to obtain a single Date object representing the
     256     * date and time when this image was released.
     257     * @return A Date object representing when this image was released, if possible, otherwise null
     258     */
     259    @Nullable
     260    public Date getReleaseDate()
     261    {
     262        return getDate(TAG_RELEASE_DATE, TAG_RELEASE_TIME);
     263    }
     264
     265    /**
     266     * Parses the Expiration Date tag and the Expiration Time tag to obtain a single Date object representing
     267     * that this image should not used after this date and time.
     268     * @return A Date object representing when this image was released, if possible, otherwise null
     269     */
     270    @Nullable
     271    public Date getExpirationDate()
     272    {
     273        return getDate(TAG_EXPIRATION_DATE, TAG_EXPIRATION_TIME);
     274    }
     275
     276    /**
     277     * Parses the Date Created tag and the Time Created tag to obtain a single Date object representing the
     278     * date and time when this image was captured.
     279     * @return A Date object representing when this image was captured, if possible, otherwise null
     280     */
     281    @Nullable
     282    public Date getDateCreated()
     283    {
     284        return getDate(TAG_DATE_CREATED, TAG_TIME_CREATED);
     285    }
     286
     287    /**
     288     * Parses the Digital Date Created tag and the Digital Time Created tag to obtain a single Date object
     289     * representing the date and time when the digital representation of this image was created.
     290     * @return A Date object representing when the digital representation of this image was created,
     291     * if possible, otherwise null
     292     */
     293    @Nullable
     294    public Date getDigitalDateCreated()
     295    {
     296        return getDate(TAG_DIGITAL_DATE_CREATED, TAG_DIGITAL_TIME_CREATED);
     297    }
     298
     299    @Nullable
     300    private Date getDate(int dateTagType, int timeTagType)
     301    {
     302        String date = getString(dateTagType);
     303        String time = getString(timeTagType);
     304
     305        if (date == null)
     306            return null;
     307        if (time == null)
     308            return null;
     309
     310        try {
     311            DateFormat parser = new SimpleDateFormat("yyyyMMddHHmmssZ");
     312            return parser.parse(date + time);
     313        } catch (ParseException e) {
     314            return null;
     315        }
     316    }
    238317}
  • trunk/src/com/drew/metadata/iptc/IptcReader.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2626import com.drew.lang.SequentialReader;
    2727import com.drew.lang.annotations.NotNull;
     28import com.drew.lang.annotations.Nullable;
    2829import com.drew.metadata.Directory;
    2930import com.drew.metadata.Metadata;
    3031
    3132import java.io.IOException;
    32 import java.util.Arrays;
    33 import java.util.Date;
     33import java.util.Collections;
    3434
    3535/**
     
    6060    public Iterable<JpegSegmentType> getSegmentTypes()
    6161    {
    62         return Arrays.asList(JpegSegmentType.APPD);
     62        return Collections.singletonList(JpegSegmentType.APPD);
    6363    }
    6464
     
    7878    public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata, long length)
    7979    {
     80        extract(reader, metadata, length, null);
     81    }
     82
     83    /**
     84     * Performs the IPTC data extraction, adding found values to the specified instance of {@link Metadata}.
     85     */
     86    public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata, long length, @Nullable Directory parentDirectory)
     87    {
    8088        IptcDirectory directory = new IptcDirectory();
    8189        metadata.addDirectory(directory);
     90
     91        if (parentDirectory != null)
     92            directory.setParent(parentDirectory);
    8293
    8394        int offset = 0;
     
    105116
    106117            // we need at least five bytes left to read a tag
    107             if (offset + 5 >= length) {
     118            if (offset + 5 > length) {
    108119                directory.addError("Too few bytes remain for a valid IPTC tag");
    109120                return;
     
    184195                reader.skip(tagByteCount - 1);
    185196                return;
    186             case IptcDirectory.TAG_RELEASE_DATE:
    187             case IptcDirectory.TAG_DATE_CREATED:
    188                 // Date object
    189                 if (tagByteCount >= 8) {
    190                     string = reader.getString(tagByteCount);
    191                     try {
    192                         int year = Integer.parseInt(string.substring(0, 4));
    193                         int month = Integer.parseInt(string.substring(4, 6)) - 1;
    194                         int day = Integer.parseInt(string.substring(6, 8));
    195                         Date date = new java.util.GregorianCalendar(year, month, day).getTime();
    196                         directory.setDate(tagIdentifier, date);
    197                         return;
    198                     } catch (NumberFormatException e) {
    199                         // fall through and we'll process the 'string' value below
    200                     }
    201                 } else {
    202                     reader.skip(tagByteCount);
    203                 }
    204             case IptcDirectory.TAG_RELEASE_TIME:
    205             case IptcDirectory.TAG_TIME_CREATED:
    206                 // time...
    207197            default:
    208198                // fall through
     
    227217            String[] newStrings;
    228218            if (oldStrings == null) {
     219                // TODO hitting this block means any prior value(s) are discarded
    229220                newStrings = new String[1];
    230221            } else {
  • trunk/src/com/drew/metadata/iptc/Iso2022Converter.java

    r8132 r10862  
     1/*
     2 * Copyright 2002-2016 Drew Noakes
     3 *
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
     7 *
     8 *        http://www.apache.org/licenses/LICENSE-2.0
     9 *
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
     20 */
    121package com.drew.metadata.iptc;
    222
     
    4060    /**
    4161     * Attempts to guess the encoding of a string provided as a byte array.
    42      * <p/>
     62     * <p>
    4363     * Encodings trialled are, in order:
    4464     * <ul>
     
    4767     *     <li>ISO-8859-1</li>
    4868     * </ul>
    49      * <p/>
     69     * <p>
    5070     * Its only purpose is to guess the encoding if and only if iptc tag coded character set is not set. If the
    5171     * encoding is not UTF-8, the tag should be set. Otherwise it is bad practice. This method tries to
    5272     * workaround this issue since some metadata manipulating tools do not prevent such bad practice.
    53      * <p/>
     73     * <p>
    5474     * About the reliability of this method: The check if some bytes are UTF-8 or not has a very high reliability.
    5575     * The two other checks are less reliable.
  • trunk/src/com/drew/metadata/iptc/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegCommentDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegCommentDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegCommentReader.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2626import com.drew.metadata.Metadata;
    2727
    28 import java.util.Arrays;
     28import java.util.Collections;
    2929
    3030/**
     
    3939    public Iterable<JpegSegmentType> getSegmentTypes()
    4040    {
    41         return Arrays.asList(JpegSegmentType.COM);
    42     }
    43 
    44     public boolean canProcess(@NotNull byte[] segmentBytes, @NotNull JpegSegmentType segmentType)
    45     {
    46         // The entire contents of the byte[] is the comment. There's nothing here to discriminate upon.
    47         return true;
     41        return Collections.singletonList(JpegSegmentType.COM);
    4842    }
    4943
  • trunk/src/com/drew/metadata/jpeg/JpegComponent.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2121package com.drew.metadata.jpeg;
    2222
    23 import com.drew.lang.annotations.Nullable;
     23import com.drew.lang.annotations.NotNull;
    2424
    2525import java.io.Serializable;
     
    5555     * @return the component name
    5656     */
    57     @Nullable
     57    @NotNull
    5858    public String getComponentName()
    5959    {
     
    7070            case 5:
    7171                return "Q";
     72            default:
     73                return String.format("Unknown (%s)", _componentId);
    7274        }
    73         return null;
    7475    }
    7576
     
    8182    public int getHorizontalSamplingFactor()
    8283    {
    83         return _samplingFactorByte & 0x0F;
     84        return (_samplingFactorByte>>4) & 0x0F;
    8485    }
    8586
    8687    public int getVerticalSamplingFactor()
    8788    {
    88         return (_samplingFactorByte>>4) & 0x0F;
     89        return _samplingFactorByte & 0x0F;
    8990    }
    9091}
  • trunk/src/com/drew/metadata/jpeg/JpegDescriptor.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    2525import com.drew.metadata.TagDescriptor;
    2626
     27import static com.drew.metadata.jpeg.JpegDirectory.*;
     28
    2729/**
    2830 * Provides human-readable string versions of the tags stored in a JpegDirectory.
     
    4446        switch (tagType)
    4547        {
    46             case JpegDirectory.TAG_COMPRESSION_TYPE:
     48            case TAG_COMPRESSION_TYPE:
    4749                return getImageCompressionTypeDescription();
    48             case JpegDirectory.TAG_COMPONENT_DATA_1:
     50            case TAG_COMPONENT_DATA_1:
    4951                return getComponentDataDescription(0);
    50             case JpegDirectory.TAG_COMPONENT_DATA_2:
     52            case TAG_COMPONENT_DATA_2:
    5153                return getComponentDataDescription(1);
    52             case JpegDirectory.TAG_COMPONENT_DATA_3:
     54            case TAG_COMPONENT_DATA_3:
    5355                return getComponentDataDescription(2);
    54             case JpegDirectory.TAG_COMPONENT_DATA_4:
     56            case TAG_COMPONENT_DATA_4:
    5557                return getComponentDataDescription(3);
    56             case JpegDirectory.TAG_DATA_PRECISION:
     58            case TAG_DATA_PRECISION:
    5759                return getDataPrecisionDescription();
    58             case JpegDirectory.TAG_IMAGE_HEIGHT:
     60            case TAG_IMAGE_HEIGHT:
    5961                return getImageHeightDescription();
    60             case JpegDirectory.TAG_IMAGE_WIDTH:
     62            case TAG_IMAGE_WIDTH:
    6163                return getImageWidthDescription();
    6264            default:
     
    6870    public String getImageCompressionTypeDescription()
    6971    {
    70         Integer value = _directory.getInteger(JpegDirectory.TAG_COMPRESSION_TYPE);
    71         if (value==null)
    72             return null;
    73         // Note there is no 2 or 12
    74         switch (value) {
    75             case 0: return "Baseline";
    76             case 1: return "Extended sequential, Huffman";
    77             case 2: return "Progressive, Huffman";
    78             case 3: return "Lossless, Huffman";
    79             case 5: return "Differential sequential, Huffman";
    80             case 6: return "Differential progressive, Huffman";
    81             case 7: return "Differential lossless, Huffman";
    82             case 8: return "Reserved for JPEG extensions";
    83             case 9: return "Extended sequential, arithmetic";
    84             case 10: return "Progressive, arithmetic";
    85             case 11: return "Lossless, arithmetic";
    86             case 13: return "Differential sequential, arithmetic";
    87             case 14: return "Differential progressive, arithmetic";
    88             case 15: return "Differential lossless, arithmetic";
    89             default:
    90                 return "Unknown type: "+ value;
    91         }
     72        return getIndexedDescription(TAG_COMPRESSION_TYPE,
     73            "Baseline",
     74            "Extended sequential, Huffman",
     75            "Progressive, Huffman",
     76            "Lossless, Huffman",
     77            null, // no 4
     78            "Differential sequential, Huffman",
     79            "Differential progressive, Huffman",
     80            "Differential lossless, Huffman",
     81            "Reserved for JPEG extensions",
     82            "Extended sequential, arithmetic",
     83            "Progressive, arithmetic",
     84            "Lossless, arithmetic",
     85            null, // no 12
     86            "Differential sequential, arithmetic",
     87            "Differential progressive, arithmetic",
     88            "Differential lossless, arithmetic");
    9289    }
     90
    9391    @Nullable
    9492    public String getImageWidthDescription()
    9593    {
    96         final String value = _directory.getString(JpegDirectory.TAG_IMAGE_WIDTH);
     94        final String value = _directory.getString(TAG_IMAGE_WIDTH);
    9795        if (value==null)
    9896            return null;
     
    103101    public String getImageHeightDescription()
    104102    {
    105         final String value = _directory.getString(JpegDirectory.TAG_IMAGE_HEIGHT);
     103        final String value = _directory.getString(TAG_IMAGE_HEIGHT);
    106104        if (value==null)
    107105            return null;
     
    112110    public String getDataPrecisionDescription()
    113111    {
    114         final String value = _directory.getString(JpegDirectory.TAG_DATA_PRECISION);
     112        final String value = _directory.getString(TAG_DATA_PRECISION);
    115113        if (value==null)
    116114            return null;
  • trunk/src/com/drew/metadata/jpeg/JpegDirectory.java

    r8132 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/JpegReader.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/jpeg/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
  • trunk/src/com/drew/metadata/tiff/DirectoryTiffHandler.java

    r8243 r10862  
    11/*
    2  * Copyright 2002-2015 Drew Noakes
     2 * Copyright 2002-2016 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    6363        _directoryStack.push(_currentDirectory);
    6464        try {
    65             _currentDirectory = directoryClass.newInstance();
     65            Directory newDirectory = directoryClass.newInstance();
     66            newDirectory.setParent(_currentDirectory);
     67            _currentDirectory = newDirectory;
    6668        } catch (InstantiationException e) {
    6769            throw new RuntimeException(e);
  • trunk/src/com/drew/metadata/tiff/package.html

    r8132 r10862  
    11<!--
    2   ~ Copyright 2002-2015 Drew Noakes
     2  ~ Copyright 2002-2016 Drew Noakes
    33  ~
    44  ~    Licensed under the Apache License, Version 2.0 (the "License");
Note: See TracChangeset for help on using the changeset viewer.