Changeset 8132 in josm for trunk/src/com/drew/metadata


Ignore:
Timestamp:
2015-03-10T01:17:39+01:00 (10 years ago)
Author:
Don-vip
Message:

fix #11162 - update to metadata-extractor 2.7.2

Location:
trunk/src/com/drew/metadata
Files:
45 added
30 deleted
30 edited

Legend:

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

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121
     
    2727/**
    2828 * Represents an age in years, months, days, hours, minutes and seconds.
    29  * <p/>
     29 * <p>
    3030 * Used by certain Panasonic cameras which have face recognition features.
    3131 *
    32  * @author Drew Noakes http://drewnoakes.com
     32 * @author Drew Noakes https://drewnoakes.com
    3333 */
    3434public class Age
    3535{
    36     private int _years;
    37     private int _months;
    38     private int _days;
    39     private int _hours;
    40     private int _minutes;
    41     private int _seconds;
     36    private final int _years;
     37    private final int _months;
     38    private final int _days;
     39    private final int _hours;
     40    private final int _minutes;
     41    private final int _seconds;
    4242
    4343    /**
    4444     * Parses an age object from the string format used by Panasonic cameras:
    4545     * <code>0031:07:15 00:00:00</code>
     46     *
    4647     * @param s The String in format <code>0031:07:15 00:00:00</code>.
    4748     * @return The parsed Age object, or null if the value could not be parsed
  • trunk/src/com/drew/metadata/DefaultTagDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata;
     
    2828 * and gives descriptions using the default string representation of the value.
    2929 *
    30  * @author Drew Noakes http://drewnoakes.com
     30 * @author Drew Noakes https://drewnoakes.com
    3131 */
    3232public class DefaultTagDescriptor extends TagDescriptor<Directory>
  • trunk/src/com/drew/metadata/Directory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata;
     
    3737 * data types.
    3838 *
    39  * @author Drew Noakes http://drewnoakes.com
     39 * @author Drew Noakes https://drewnoakes.com
    4040 */
    4141public abstract class Directory
    4242{
    43     // TODO get Array methods need to return cloned data, to maintain this directory's integrity
    44 
    4543    /** Map of values hashed by type identifiers. */
    4644    @NotNull
     
    104102    public Collection<Tag> getTags()
    105103    {
    106         return _definedTagList;
     104        return Collections.unmodifiableCollection(_definedTagList);
    107105    }
    108106
     
    158156    public Iterable<String> getErrors()
    159157    {
    160         return _errorList;
     158        return Collections.unmodifiableCollection(_errorList);
    161159    }
    162160
     
    414412            return null;
    415413
    416         if (o instanceof String) {
     414        if (o instanceof Number) {
     415            return ((Number)o).intValue();
     416        } else if (o instanceof String) {
    417417            try {
    418418                return Integer.parseInt((String)o);
     
    428428                return (int)val;
    429429            }
    430         } else if (o instanceof Number) {
    431             return ((Number)o).intValue();
    432430        } else if (o instanceof Rational[]) {
    433431            Rational[] rationals = (Rational[])o;
     
    498496        if (o == null)
    499497            return null;
     498        if (o instanceof int[])
     499            return (int[])o;
    500500        if (o instanceof Rational[]) {
    501501            Rational[] rationals = (Rational[])o;
     
    506506            return ints;
    507507        }
    508         if (o instanceof int[])
    509             return (int[])o;
     508        if (o instanceof short[]) {
     509            short[] shorts = (short[])o;
     510            int[] ints = new int[shorts.length];
     511            for (int i = 0; i < shorts.length; i++) {
     512                ints[i] = shorts[i];
     513            }
     514            return ints;
     515        }
    510516        if (o instanceof byte[]) {
    511517            byte[] bytes = (byte[])o;
    512518            int[] ints = new int[bytes.length];
    513519            for (int i = 0; i < bytes.length; i++) {
    514                 byte b = bytes[i];
    515                 ints[i] = b;
     520                ints[i] = bytes[i];
    516521            }
    517522            return ints;
     
    527532        if (o instanceof Integer)
    528533            return new int[] { (Integer)o };
    529        
     534
    530535        return null;
    531536    }
     
    560565            }
    561566            return bytes;
     567        } else if (o instanceof short[]) {
     568            short[] shorts = (short[])o;
     569            byte[] bytes = new byte[shorts.length];
     570            for (int i = 0; i < shorts.length; i++) {
     571                bytes[i] = (byte)shorts[i];
     572            }
     573            return bytes;
    562574        } else if (o instanceof CharSequence) {
    563575            CharSequence str = (CharSequence)o;
     
    703715    /**
    704716     * 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.
    705      * <p/>
     717     * <p>
    706718     * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
    707719     * the current {@link TimeZone}.  If the {@link TimeZone} is known, call the overload that accepts one as an argument.
     
    712724        return getDate(tagType, null);
    713725    }
    714    
     726
    715727    /**
    716728     * 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.
    717      * <p/>
     729     * <p>
    718730     * If the underlying value is a {@link String}, then attempts will be made to parse the string as though it is in
    719731     * the {@link TimeZone} represented by the {@code timeZone} parameter (if it is non-null).  Note that this parameter
     
    818830            boolean isLongArray = componentType.getName().equals("long");
    819831            boolean isByteArray = componentType.getName().equals("byte");
     832            boolean isShortArray = componentType.getName().equals("short");
    820833            StringBuilder string = new StringBuilder();
    821834            for (int i = 0; i < arrayLength; i++) {
     
    826839                else if (isIntArray)
    827840                    string.append(Array.getInt(o, i));
     841                else if (isShortArray)
     842                    string.append(Array.getShort(o, i));
    828843                else if (isLongArray)
    829844                    string.append(Array.getLong(o, i));
     
    896911
    897912    /**
     913     * Gets whether the specified tag is known by the directory and has a name.
     914     *
     915     * @param tagType the tag type identifier
     916     * @return whether this directory has a name for the specified tag
     917     */
     918    public boolean hasTagName(int tagType)
     919    {
     920        return getTagNameMap().containsKey(tagType);
     921    }
     922
     923    /**
    898924     * Provides a description of a tag's value using the descriptor set by
    899925     * <code>setDescriptor(Descriptor)</code>.
     
    908934        return _descriptor.getDescription(tagType);
    909935    }
     936
     937    @Override
     938    public String toString()
     939    {
     940        return String.format("%s Directory (%d %s)",
     941            getName(),
     942            _tagMap.size(),
     943            _tagMap.size() == 1
     944                ? "tag"
     945                : "tags");
     946    }
    910947}
  • trunk/src/com/drew/metadata/Face.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata;
     
    2626/**
    2727 * Class to hold information about a detected or recognized face in a photo.
    28  * <p/>
     28 * <p>
    2929 * When a face is <em>detected</em>, the camera believes that a face is present at a given location in
    3030 * the image, but is not sure whose face it is.  When a face is <em>recognised</em>, then the face is
     
    116116    }
    117117
     118    @Override
    118119    @NotNull
    119120    public String toString()
  • trunk/src/com/drew/metadata/Metadata.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata;
     
    2424import com.drew.lang.annotations.Nullable;
    2525
    26 import java.util.ArrayList;
    27 import java.util.Collection;
    28 import java.util.HashMap;
    29 import java.util.Map;
     26import java.util.*;
    3027
    3128/**
    32  * A top-level object to hold the various types of metadata (Exif/IPTC/etc) related to one entity (such as a file
    33  * or stream).
    34  * <p/>
    35  * Metadata objects may contain zero or more directories.  Each directory may contain zero or more tags with
    36  * corresponding values.
     29 * A top-level object that holds the metadata values extracted from an image.
     30 * <p>
     31 * Metadata objects may contain zero or more {@link Directory} objects.  Each directory may contain zero or more tags
     32 * with corresponding values.
    3733 *
    38  * @author Drew Noakes http://drewnoakes.com
     34 * @author Drew Noakes https://drewnoakes.com
    3935 */
    4036public final class Metadata
     
    4238    @NotNull
    4339    private final Map<Class<? extends Directory>,Directory> _directoryByClass = new HashMap<Class<? extends Directory>, Directory>();
    44    
     40
    4541    /**
    4642     * List of Directory objects set against this object.  Keeping a list handy makes
     
    5854    public Iterable<Directory> getDirectories()
    5955    {
    60         return _directoryList;
     56        return Collections.unmodifiableCollection(_directoryList);
    6157    }
    6258
     
    7268
    7369    /**
    74      * Returns a <code>Directory</code> of specified type.  If this <code>Metadata</code> object already contains
     70     * Returns a {@link Directory} of specified type.  If this {@link Metadata} object already contains
    7571     * such a directory, it is returned.  Otherwise a new instance of this directory will be created and stored within
    76      * this Metadata object.
     72     * this {@link Metadata} object.
    7773     *
    7874     * @param type the type of the Directory implementation required.
     
    104100
    105101    /**
    106      * If this <code>Metadata</code> object contains a <code>Directory</code> of the specified type, it is returned.
     102     * If this {@link Metadata} object contains a {@link Directory} of the specified type, it is returned.
    107103     * Otherwise <code>null</code> is returned.
    108104     *
    109105     * @param type the Directory type
    110106     * @param <T> the Directory type
    111      * @return a Directory of type T if it exists in this Metadata object, otherwise <code>null</code>.
     107     * @return a Directory of type T if it exists in this {@link Metadata} object, otherwise <code>null</code>.
    112108     */
    113109    @Nullable
     
    123119    /**
    124120     * Indicates whether a given directory type has been created in this metadata
    125      * repository.  Directories are created by calling <code>getOrCreateDirectory(Class)</code>.
     121     * repository.  Directories are created by calling {@link Metadata#getOrCreateDirectory(Class)}.
    126122     *
    127      * @param type the Directory type
    128      * @return true if the metadata directory has been created
     123     * @param type the {@link Directory} type
     124     * @return true if the {@link Directory} has been created
    129125     */
    130126    public boolean containsDirectory(Class<? extends Directory> type)
     
    135131    /**
    136132     * Indicates whether any errors were reported during the reading of metadata values.
    137      * This value will be true if Directory.hasErrors() is true for one of the contained Directory objects.
     133     * This value will be true if Directory.hasErrors() is true for one of the contained {@link Directory} objects.
    138134     *
    139135     * @return whether one of the contained directories has an error
     
    147143        return false;
    148144    }
     145
     146    @Override
     147    public String toString()
     148    {
     149        return String.format("Metadata (%d %s)",
     150            _directoryList.size(),
     151            _directoryList.size() == 1
     152                ? "directory"
     153                : "directories");
     154    }
    149155}
  • trunk/src/com/drew/metadata/MetadataException.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata;
     
    2727 * Base class for all metadata specific exceptions.
    2828 *
    29  * @author Drew Noakes http://drewnoakes.com
     29 * @author Drew Noakes https://drewnoakes.com
    3030 */
    3131public class MetadataException extends CompoundException
  • trunk/src/com/drew/metadata/MetadataReader.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata;
    2222
    23 import com.drew.lang.BufferReader;
     23import com.drew.lang.RandomAccessReader;
    2424import com.drew.lang.annotations.NotNull;
    2525
    2626/**
    27  * Interface through which all classes responsible for decoding a particular type of metadata may be called.
    28  * Note that the data source is not specified on this interface.  Instead it is suggested that implementations
    29  * take their data within a constructor.  Constructors might be overloaded to allow for different sources, such as
    30  * files, streams and byte arrays.  As such, instances of implementations of this interface would be single-use and
    31  * not thread-safe.
     27 * Defines an object capable of processing a particular type of metadata from a {@link RandomAccessReader}.
     28 * <p>
     29 * Instances of this interface must be thread-safe and reusable.
    3230 *
    33  * @author Drew Noakes http://drewnoakes.com
     31 * @author Drew Noakes https://drewnoakes.com
    3432 */
    3533public interface MetadataReader
    3634{
    3735    /**
    38      * Extract metadata from the source and merge it into an existing Metadata object.
     36     * Extracts metadata from <code>reader</code> and merges it into the specified {@link Metadata} object.
    3937     *
    40      * @param reader   The reader from which the metadata should be extracted.
    41      * @param metadata The Metadata object into which extracted values should be merged.
     38     * @param reader   The {@link RandomAccessReader} from which the metadata should be extracted.
     39     * @param metadata The {@link Metadata} object into which extracted values should be merged.
    4240     */
    43     public void extract(@NotNull final BufferReader reader, @NotNull final Metadata metadata);
     41    public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata);
    4442}
  • trunk/src/com/drew/metadata/Tag.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata;
     
    2525
    2626/**
    27  * Models a particular tag within a directory and provides methods for obtaining its value.  Note that a Tag instance is
    28  * specific to a particular metadata extraction and cannot be reused.
     27 * Models a particular tag within a {@link com.drew.metadata.Directory} and provides methods for obtaining its value.
     28 * Immutable.
    2929 *
    30  * @author Drew Noakes http://drewnoakes.com
     30 * @author Drew Noakes https://drewnoakes.com
    3131 */
    3232public class Tag
     
    7979
    8080    /**
     81     * Get whether this tag has a name.
     82     *
     83     * If <code>true</code>, it may be accessed via {@link #getTagName}.
     84     * If <code>false</code>, {@link #getTagName} will return a string resembling <code>"Unknown tag (0x1234)"</code>.
     85     *
     86     * @return whether this tag has a name
     87     */
     88    @NotNull
     89    public boolean hasTagName()
     90    {
     91        return _directory.hasTagName(_tagType);
     92    }
     93
     94    /**
    8195     * Get the name of the tag, such as <code>Aperture</code>, or
    8296     * <code>InteropVersion</code>.
     
    91105
    92106    /**
    93      * Get the name of the directory in which the tag exists, such as
     107     * Get the name of the {@link com.drew.metadata.Directory} in which the tag exists, such as
    94108     * <code>Exif</code>, <code>GPS</code> or <code>Interoperability</code>.
    95109     *
    96      * @return name of the directory in which this tag exists
     110     * @return name of the {@link com.drew.metadata.Directory} in which this tag exists
    97111     */
    98112    @NotNull
     
    107121     * @return the tag's type and value
    108122     */
     123    @Override
    109124    @NotNull
    110125    public String toString()
    111126    {
    112127        String description = getDescription();
    113         if (description==null)
     128        if (description == null)
    114129            description = _directory.getString(getTagType()) + " (unable to formulate description)";
    115130        return "[" + _directory.getName() + "] " + getTagName() + " - " + description;
  • trunk/src/com/drew/metadata/TagDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata;
    2222
     23import com.drew.lang.Rational;
     24import com.drew.lang.StringUtil;
    2325import com.drew.lang.annotations.NotNull;
    2426import com.drew.lang.annotations.Nullable;
    2527
     28import java.io.UnsupportedEncodingException;
    2629import java.lang.reflect.Array;
     30import java.util.ArrayList;
     31import java.util.Date;
     32import java.util.List;
    2733
    2834/**
    29  * Abstract base class for all tag descriptor classes.  Implementations are responsible for
     35 * Base class for all tag descriptor classes.  Implementations are responsible for
    3036 * providing the human-readable string representation of tag values stored in a directory.
    3137 * The directory is provided to the tag descriptor via its constructor.
    3238 *
    33  * @author Drew Noakes http://drewnoakes.com
     39 * @author Drew Noakes https://drewnoakes.com
    3440 */
    35 public abstract class TagDescriptor<T extends Directory>
     41public class TagDescriptor<T extends Directory>
    3642{
    3743    @NotNull
     
    4450
    4551    /**
    46      * Returns a descriptive value of the the specified tag for this image.
     52     * Returns a descriptive value of the specified tag for this image.
    4753     * Where possible, known values will be substituted here in place of the raw
    4854     * tokens actually kept in the metadata segment.  If no substitution is
    4955     * available, the value provided by <code>getString(tagType)</code> will be returned.
    50      * 
     56     *
    5157     * @param tagType the tag to find a description for
    5258     * @return a description of the image's value for the specified tag, or
     
    5864        Object object = _directory.getObject(tagType);
    5965
    60         if (object==null)
     66        if (object == null)
    6167            return null;
    6268
     
    6672            if (length > 16) {
    6773                final String componentTypeName = object.getClass().getComponentType().getName();
    68                 return String.format("[%d %s%s]", length, componentTypeName, length==1 ? "" : "s");
     74                return String.format("[%d %s%s]", length, componentTypeName, length == 1 ? "" : "s");
    6975            }
    7076        }
     
    7783     * Takes a series of 4 bytes from the specified offset, and converts these to a
    7884     * well-known version number, where possible.
    79      * <p/>
     85     * <p>
    8086     * Two different formats are processed:
    8187     * <ul>
    82      *     <li>[30 32 31 30] -&gt; 2.10</li>
    83      *     <li>[0 1 0 0] -&gt; 1.00</li>
     88     * <li>[30 32 31 30] -&gt; 2.10</li>
     89     * <li>[0 1 0 0] -&gt; 1.00</li>
    8490     * </ul>
    85      * @param components the four version values
    86      * @param majorDigits the number of components to be
     91     *
     92     * @param components  the four version values
     93     * @param majorDigits the number of components to be
    8794     * @return the version as a string of form "2.10" or null if the argument cannot be converted
    8895     */
     
    9097    public static String convertBytesToVersionString(@Nullable int[] components, final int majorDigits)
    9198    {
    92         if (components==null)
     99        if (components == null)
    93100            return null;
    94101        StringBuilder version = new StringBuilder();
     
    99106            if (c < '0')
    100107                c += '0';
    101             if (i == 0 && c=='0')
     108            if (i == 0 && c == '0')
    102109                continue;
    103110            version.append(c);
     
    105112        return version.toString();
    106113    }
     114
     115    @Nullable
     116    protected String getVersionBytesDescription(final int tagType, int majorDigits)
     117    {
     118        int[] values = _directory.getIntArray(tagType);
     119        return values == null ? null : convertBytesToVersionString(values, majorDigits);
     120    }
     121
     122    @Nullable
     123    protected String getIndexedDescription(final int tagType, @NotNull String... descriptions)
     124    {
     125        return getIndexedDescription(tagType, 0, descriptions);
     126    }
     127
     128    @Nullable
     129    protected String getIndexedDescription(final int tagType, final int baseIndex, @NotNull String... descriptions)
     130    {
     131        final Integer index = _directory.getInteger(tagType);
     132        if (index == null)
     133            return null;
     134        final int arrayIndex = index - baseIndex;
     135        if (arrayIndex >= 0 && arrayIndex < descriptions.length) {
     136            String description = descriptions[arrayIndex];
     137            if (description != null)
     138                return description;
     139        }
     140        return "Unknown (" + index + ")";
     141    }
     142
     143    @Nullable
     144    protected String getByteLengthDescription(final int tagType)
     145    {
     146        byte[] bytes = _directory.getByteArray(tagType);
     147        if (bytes == null)
     148            return null;
     149        return String.format("(%d byte%s)", bytes.length, bytes.length == 1 ? "" : "s");
     150    }
     151
     152    @Nullable
     153    protected String getSimpleRational(final int tagType)
     154    {
     155        Rational value = _directory.getRational(tagType);
     156        if (value == null)
     157            return null;
     158        return value.toSimpleString(true);
     159    }
     160
     161    @Nullable
     162    protected String getDecimalRational(final int tagType, final int decimalPlaces)
     163    {
     164        Rational value = _directory.getRational(tagType);
     165        if (value == null)
     166            return null;
     167        return String.format("%." + decimalPlaces + "f", value.doubleValue());
     168    }
     169
     170    @Nullable
     171    protected String getFormattedInt(final int tagType, @NotNull final String format)
     172    {
     173        Integer value = _directory.getInteger(tagType);
     174        if (value == null)
     175            return null;
     176        return String.format(format, value);
     177    }
     178
     179    @Nullable
     180    protected String getFormattedFloat(final int tagType, @NotNull final String format)
     181    {
     182        Float value = _directory.getFloatObject(tagType);
     183        if (value == null)
     184            return null;
     185        return String.format(format, value);
     186    }
     187
     188    @Nullable
     189    protected String getFormattedString(final int tagType, @NotNull final String format)
     190    {
     191        String value = _directory.getString(tagType);
     192        if (value == null)
     193            return null;
     194        return String.format(format, value);
     195    }
     196
     197    @Nullable
     198    protected String getEpochTimeDescription(final int tagType)
     199    {
     200        // TODO have observed a byte[8] here which is likely some kind of date (ticks as long?)
     201        Long value = _directory.getLongObject(tagType);
     202        if (value==null)
     203            return null;
     204        return new Date(value).toString();
     205    }
     206
     207    /**
     208     * LSB first. Labels may be null, a String, or a String[2] with (low label,high label) values.
     209     */
     210    @Nullable
     211    protected String getBitFlagDescription(final int tagType, @NotNull final Object... labels)
     212    {
     213        Integer value = _directory.getInteger(tagType);
     214
     215        if (value == null)
     216            return null;
     217
     218        List<String> parts = new ArrayList<String>();
     219
     220        int bitIndex = 0;
     221        while (labels.length > bitIndex) {
     222            Object labelObj = labels[bitIndex];
     223            if (labelObj != null) {
     224                boolean isBitSet = (value & 1) == 1;
     225                if (labelObj instanceof String[]) {
     226                    String[] labelPair = (String[])labelObj;
     227                    assert(labelPair.length == 2);
     228                    parts.add(labelPair[isBitSet ? 1 : 0]);
     229                } else if (isBitSet && labelObj instanceof String) {
     230                    parts.add((String)labelObj);
     231                }
     232            }
     233            value >>= 1;
     234            bitIndex++;
     235        }
     236
     237        return StringUtil.join(parts, ", ");
     238    }
     239
     240    @Nullable
     241    protected String get7BitStringFromBytes(final int tagType)
     242    {
     243        final byte[] bytes = _directory.getByteArray(tagType);
     244
     245        if (bytes == null)
     246            return null;
     247
     248        int length = bytes.length;
     249        for (int index = 0; index < bytes.length; index++) {
     250            int i = bytes[index] & 0xFF;
     251            if (i == 0 || i > 0x7F) {
     252                length = index;
     253                break;
     254            }
     255        }
     256
     257        return new String(bytes, 0, length);
     258    }
     259
     260    @Nullable
     261    protected String getAsciiStringFromBytes(int tag)
     262    {
     263        byte[] values = _directory.getByteArray(tag);
     264
     265        if (values == null)
     266            return null;
     267
     268        try {
     269            return new String(values, "ASCII").trim();
     270        } catch (UnsupportedEncodingException e) {
     271            return null;
     272        }
     273    }
    107274}
  • trunk/src/com/drew/metadata/exif/ExifIFD0Descriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121
     
    2929import java.io.UnsupportedEncodingException;
    3030
     31import static com.drew.metadata.exif.ExifIFD0Directory.*;
     32
    3133/**
    32  * Provides human-readable string representations of tag values stored in a <code>ExifIFD0Directory</code>.
    33  *
    34  * @author Drew Noakes http://drewnoakes.com
     34 * Provides human-readable string representations of tag values stored in a {@link ExifIFD0Directory}.
     35 *
     36 * @author Drew Noakes https://drewnoakes.com
    3537 */
    3638public class ExifIFD0Descriptor extends TagDescriptor<ExifIFD0Directory>
     
    5456
    5557    /**
    56      * Returns a descriptive value of the the specified tag for this image.
     58     * Returns a descriptive value of the specified tag for this image.
    5759     * Where possible, known values will be substituted here in place of the raw
    5860     * tokens actually kept in the Exif segment.  If no substitution is
     
    6264     *         <code>null</code> if the tag hasn't been defined.
    6365     */
     66    @Override
    6467    @Nullable
    6568    public String getDescription(int tagType)
    6669    {
    6770        switch (tagType) {
    68             case ExifIFD0Directory.TAG_RESOLUTION_UNIT:
     71            case TAG_RESOLUTION_UNIT:
    6972                return getResolutionDescription();
    70             case ExifIFD0Directory.TAG_YCBCR_POSITIONING:
     73            case TAG_YCBCR_POSITIONING:
    7174                return getYCbCrPositioningDescription();
    72             case ExifIFD0Directory.TAG_X_RESOLUTION:
     75            case TAG_X_RESOLUTION:
    7376                return getXResolutionDescription();
    74             case ExifIFD0Directory.TAG_Y_RESOLUTION:
     77            case TAG_Y_RESOLUTION:
    7578                return getYResolutionDescription();
    76             case ExifIFD0Directory.TAG_REFERENCE_BLACK_WHITE:
     79            case TAG_REFERENCE_BLACK_WHITE:
    7780                return getReferenceBlackWhiteDescription();
    78             case ExifIFD0Directory.TAG_ORIENTATION:
     81            case TAG_ORIENTATION:
    7982                return getOrientationDescription();
    8083
    81             case ExifIFD0Directory.TAG_WIN_AUTHOR:
     84            case TAG_WIN_AUTHOR:
    8285               return getWindowsAuthorDescription();
    83             case ExifIFD0Directory.TAG_WIN_COMMENT:
     86            case TAG_WIN_COMMENT:
    8487               return getWindowsCommentDescription();
    85             case ExifIFD0Directory.TAG_WIN_KEYWORDS:
     88            case TAG_WIN_KEYWORDS:
    8689               return getWindowsKeywordsDescription();
    87             case ExifIFD0Directory.TAG_WIN_SUBJECT:
     90            case TAG_WIN_SUBJECT:
    8891               return getWindowsSubjectDescription();
    89             case ExifIFD0Directory.TAG_WIN_TITLE:
     92            case TAG_WIN_TITLE:
    9093               return getWindowsTitleDescription();
    9194
     
    98101    public String getReferenceBlackWhiteDescription()
    99102    {
    100         int[] ints = _directory.getIntArray(ExifIFD0Directory.TAG_REFERENCE_BLACK_WHITE);
    101         if (ints==null)
     103        int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE);
     104        if (ints==null || ints.length < 6)
    102105            return null;
    103106        int blackR = ints[0];
     
    107110        int blackB = ints[4];
    108111        int whiteB = ints[5];
    109         return "[" + blackR + "," + blackG + "," + blackB + "] " +
    110                "[" + whiteR + "," + whiteG + "," + whiteB + "]";
     112        return String.format("[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB);
    111113    }
    112114
     
    114116    public String getYResolutionDescription()
    115117    {
    116         Rational value = _directory.getRational(ExifIFD0Directory.TAG_Y_RESOLUTION);
     118        Rational value = _directory.getRational(TAG_Y_RESOLUTION);
    117119        if (value==null)
    118120            return null;
    119121        final String unit = getResolutionDescription();
    120         return value.toSimpleString(_allowDecimalRepresentationOfRationals) +
    121                 " dots per " +
    122                 (unit==null ? "unit" : unit.toLowerCase());
     122        return String.format("%s dots per %s",
     123            value.toSimpleString(_allowDecimalRepresentationOfRationals),
     124            unit == null ? "unit" : unit.toLowerCase());
    123125    }
    124126
     
    126128    public String getXResolutionDescription()
    127129    {
    128         Rational value = _directory.getRational(ExifIFD0Directory.TAG_X_RESOLUTION);
     130        Rational value = _directory.getRational(TAG_X_RESOLUTION);
    129131        if (value==null)
    130132            return null;
    131133        final String unit = getResolutionDescription();
    132         return value.toSimpleString(_allowDecimalRepresentationOfRationals) +
    133                 " dots per " +
    134                 (unit==null ? "unit" : unit.toLowerCase());
     134        return String.format("%s dots per %s",
     135            value.toSimpleString(_allowDecimalRepresentationOfRationals),
     136            unit == null ? "unit" : unit.toLowerCase());
    135137    }
    136138
     
    138140    public String getYCbCrPositioningDescription()
    139141    {
    140         Integer value = _directory.getInteger(ExifIFD0Directory.TAG_YCBCR_POSITIONING);
    141         if (value==null)
    142             return null;
    143         switch (value) {
    144             case 1: return "Center of pixel array";
    145             case 2: return "Datum point";
    146             default:
    147                 return String.valueOf(value);
     142        return getIndexedDescription(TAG_YCBCR_POSITIONING, 1, "Center of pixel array", "Datum point");
     143    }
     144
     145    @Nullable
     146    public String getOrientationDescription()
     147    {
     148        return getIndexedDescription(TAG_ORIENTATION, 1,
     149            "Top, left side (Horizontal / normal)",
     150            "Top, right side (Mirror horizontal)",
     151            "Bottom, right side (Rotate 180)",
     152            "Bottom, left side (Mirror vertical)",
     153            "Left side, top (Mirror horizontal and rotate 270 CW)",
     154            "Right side, top (Rotate 90 CW)",
     155            "Right side, bottom (Mirror horizontal and rotate 90 CW)",
     156            "Left side, bottom (Rotate 270 CW)");
     157    }
     158
     159    @Nullable
     160    public String getResolutionDescription()
     161    {
     162        // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)
     163        return getIndexedDescription(TAG_RESOLUTION_UNIT, 1, "(No unit)", "Inch", "cm");
     164    }
     165
     166    /** The Windows specific tags uses plain Unicode. */
     167    @Nullable
     168    private String getUnicodeDescription(int tag)
     169    {
     170        byte[] bytes = _directory.getByteArray(tag);
     171        if (bytes == null)
     172            return null;
     173        try {
     174            // Decode the unicode string and trim the unicode zero "\0" from the end.
     175            return new String(bytes, "UTF-16LE").trim();
     176        } catch (UnsupportedEncodingException ex) {
     177            return null;
    148178        }
    149179    }
    150180
    151181    @Nullable
    152     public String getOrientationDescription()
    153     {
    154         Integer value = _directory.getInteger(ExifIFD0Directory.TAG_ORIENTATION);
    155         if (value==null)
    156             return null;
    157         switch (value) {
    158             case 1: return "Top, left side (Horizontal / normal)";
    159             case 2: return "Top, right side (Mirror horizontal)";
    160             case 3: return "Bottom, right side (Rotate 180)";
    161             case 4: return "Bottom, left side (Mirror vertical)";
    162             case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)";
    163             case 6: return "Right side, top (Rotate 90 CW)";
    164             case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)";
    165             case 8: return "Left side, bottom (Rotate 270 CW)";
    166             default:
    167                 return String.valueOf(value);
    168         }
    169     }
    170 
    171     @Nullable
    172     public String getResolutionDescription()
    173     {
    174         // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)
    175         Integer value = _directory.getInteger(ExifIFD0Directory.TAG_RESOLUTION_UNIT);
    176         if (value==null)
    177             return null;
    178         switch (value) {
    179             case 1: return "(No unit)";
    180             case 2: return "Inch";
    181             case 3: return "cm";
    182             default:
    183                 return "";
    184         }
    185     }
    186 
    187     /** The Windows specific tags uses plain Unicode. */
    188     @Nullable
    189     private String getUnicodeDescription(int tag)
    190     {
    191          byte[] commentBytes = _directory.getByteArray(tag);
    192         if (commentBytes==null)
    193             return null;
    194          try {
    195              // Decode the unicode string and trim the unicode zero "\0" from the end.
    196             return new String(commentBytes, "UTF-16LE").trim();
    197          }
    198          catch (UnsupportedEncodingException ex) {
    199             return null;
    200          }
    201     }
    202 
    203     @Nullable
    204182    public String getWindowsAuthorDescription()
    205183    {
    206        return getUnicodeDescription(ExifIFD0Directory.TAG_WIN_AUTHOR);
     184       return getUnicodeDescription(TAG_WIN_AUTHOR);
    207185    }
    208186
     
    210188    public String getWindowsCommentDescription()
    211189    {
    212        return getUnicodeDescription(ExifIFD0Directory.TAG_WIN_COMMENT);
     190       return getUnicodeDescription(TAG_WIN_COMMENT);
    213191    }
    214192
     
    216194    public String getWindowsKeywordsDescription()
    217195    {
    218        return getUnicodeDescription(ExifIFD0Directory.TAG_WIN_KEYWORDS);
     196       return getUnicodeDescription(TAG_WIN_KEYWORDS);
    219197    }
    220198
     
    222200    public String getWindowsTitleDescription()
    223201    {
    224        return getUnicodeDescription(ExifIFD0Directory.TAG_WIN_TITLE);
     202       return getUnicodeDescription(TAG_WIN_TITLE);
    225203    }
    226204
     
    228206    public String getWindowsSubjectDescription()
    229207    {
    230        return getUnicodeDescription(ExifIFD0Directory.TAG_WIN_SUBJECT);
     208       return getUnicodeDescription(TAG_WIN_SUBJECT);
    231209    }
    232210}
  • trunk/src/com/drew/metadata/exif/ExifIFD0Directory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121
     
    3030 * Describes Exif tags from the IFD0 directory.
    3131 *
    32  * @author Drew Noakes http://drewnoakes.com
     32 * @author Drew Noakes https://drewnoakes.com
    3333 */
    3434public class ExifIFD0Directory extends Directory
     
    4646    public static final int TAG_WHITE_POINT = 0x013E;
    4747    public static final int TAG_PRIMARY_CHROMATICITIES = 0x013F;
     48
    4849    public static final int TAG_YCBCR_COEFFICIENTS = 0x0211;
    4950    public static final int TAG_YCBCR_POSITIONING = 0x0213;
    5051    public static final int TAG_REFERENCE_BLACK_WHITE = 0x0214;
     52
     53
     54    /** This tag is a pointer to the Exif SubIFD. */
     55    public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769;
     56
     57    /** This tag is a pointer to the Exif GPS IFD. */
     58    public static final int TAG_GPS_INFO_OFFSET = 0x8825;
     59
    5160    public static final int TAG_COPYRIGHT = 0x8298;
     61
     62    /** Non-standard, but in use. */
     63    public static final int TAG_TIME_ZONE_OFFSET = 0x882a;
    5264
    5365    /** The image title, as used by Windows XP. */
     
    8294        _tagNameMap.put(TAG_YCBCR_POSITIONING, "YCbCr Positioning");
    8395        _tagNameMap.put(TAG_REFERENCE_BLACK_WHITE, "Reference Black/White");
     96
    8497        _tagNameMap.put(TAG_COPYRIGHT, "Copyright");
     98
     99        _tagNameMap.put(TAG_TIME_ZONE_OFFSET, "Time Zone Offset");
    85100
    86101        _tagNameMap.put(TAG_WIN_AUTHOR, "Windows XP Author");
     
    96111    }
    97112
     113    @Override
    98114    @NotNull
    99115    public String getName()
     
    102118    }
    103119
     120    @Override
    104121    @NotNull
    105122    protected HashMap<Integer, String> getTagNameMap()
  • trunk/src/com/drew/metadata/exif/ExifInteropDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    2525import com.drew.metadata.TagDescriptor;
    2626
     27import static com.drew.metadata.exif.ExifInteropDirectory.*;
     28
    2729/**
    28  * Provides human-readable string representations of tag values stored in a <code>ExifInteropDirectory</code>.
     30 * Provides human-readable string representations of tag values stored in a {@link ExifInteropDirectory}.
    2931 *
    30  * @author Drew Noakes http://drewnoakes.com
     32 * @author Drew Noakes https://drewnoakes.com
    3133 */
    3234public class ExifInteropDescriptor extends TagDescriptor<ExifInteropDirectory>
     
    3739    }
    3840
     41    @Override
    3942    @Nullable
    4043    public String getDescription(int tagType)
    4144    {
    4245        switch (tagType) {
    43             case ExifInteropDirectory.TAG_INTEROP_INDEX:
     46            case TAG_INTEROP_INDEX:
    4447                return getInteropIndexDescription();
    45             case ExifInteropDirectory.TAG_INTEROP_VERSION:
     48            case TAG_INTEROP_VERSION:
    4649                return getInteropVersionDescription();
    4750            default:
     
    5356    public String getInteropVersionDescription()
    5457    {
    55         int[] ints = _directory.getIntArray(ExifInteropDirectory.TAG_INTEROP_VERSION);
    56         return convertBytesToVersionString(ints, 2);
     58        return getVersionBytesDescription(TAG_INTEROP_VERSION, 2);
    5759    }
    5860
     
    6062    public String getInteropIndexDescription()
    6163    {
    62         String value = _directory.getString(ExifInteropDirectory.TAG_INTEROP_INDEX);
     64        String value = _directory.getString(TAG_INTEROP_INDEX);
    6365
    64         if (value==null)
     66        if (value == null)
    6567            return null;
    6668
  • trunk/src/com/drew/metadata/exif/ExifInteropDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    2929 * Describes Exif interoperability tags.
    3030 *
    31  * @author Drew Noakes http://drewnoakes.com
     31 * @author Drew Noakes https://drewnoakes.com
    3232 */
    3333public class ExifInteropDirectory extends Directory
     
    5656    }
    5757
     58    @Override
    5859    @NotNull
    5960    public String getName()
     
    6263    }
    6364
     65    @Override
    6466    @NotNull
    6567    protected HashMap<Integer, String> getTagNameMap()
  • trunk/src/com/drew/metadata/exif/ExifReader.java

    r6209 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
    2222
    23 import java.util.HashSet;
    24 import java.util.Set;
     23import com.drew.imaging.jpeg.JpegSegmentMetadataReader;
     24import com.drew.imaging.jpeg.JpegSegmentType;
     25import com.drew.imaging.tiff.TiffProcessingException;
     26import com.drew.imaging.tiff.TiffReader;
     27import com.drew.lang.ByteArrayReader;
     28import com.drew.lang.annotations.NotNull;
     29import com.drew.metadata.Metadata;
    2530
    26 import com.drew.lang.BufferBoundsException;
    27 import com.drew.lang.BufferReader;
    28 import com.drew.lang.Rational;
    29 import com.drew.lang.annotations.NotNull;
    30 import com.drew.metadata.Directory;
    31 import com.drew.metadata.Metadata;
    32 import com.drew.metadata.MetadataReader;
     31import java.io.IOException;
     32import java.util.Arrays;
    3333
    3434/**
    3535 * Decodes Exif binary data, populating a {@link Metadata} object with tag values in {@link ExifSubIFDDirectory},
    36  * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera makernote directories.
     36 * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera
     37 * makernote directories.
    3738 *
    38  * @author Drew Noakes http://drewnoakes.com
     39 * @author Drew Noakes https://drewnoakes.com
    3940 */
    40 public class ExifReader implements MetadataReader
     41public class ExifReader implements JpegSegmentMetadataReader
    4142{
    42     // TODO extract a reusable TiffReader from this class with hooks for special tag handling and subdir following
    43    
    44     /** The number of bytes used per format descriptor. */
     43    /**
     44     * The offset at which the TIFF data actually starts. This may be necessary when, for example, processing
     45     * JPEG Exif data from APP0 which has a 6-byte preamble before starting the TIFF data.
     46     */
     47    private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
     48
     49    private boolean _storeThumbnailBytes = true;
     50
     51    public boolean isStoreThumbnailBytes()
     52    {
     53        return _storeThumbnailBytes;
     54    }
     55
     56    public void setStoreThumbnailBytes(boolean storeThumbnailBytes)
     57    {
     58        _storeThumbnailBytes = storeThumbnailBytes;
     59    }
     60
    4561    @NotNull
    46     private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
     62    public Iterable<JpegSegmentType> getSegmentTypes()
     63    {
     64        return Arrays.asList(JpegSegmentType.APP1);
     65    }
    4766
    48     /** The number of formats known. */
    49     private static final int MAX_FORMAT_CODE = 12;
     67    public boolean canProcess(@NotNull final byte[] segmentBytes, @NotNull final JpegSegmentType segmentType)
     68    {
     69        return segmentBytes.length >= JPEG_EXIF_SEGMENT_PREAMBLE.length() && new String(segmentBytes, 0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equalsIgnoreCase(JPEG_EXIF_SEGMENT_PREAMBLE);
     70    }
    5071
    51     // Format types
    52     // TODO use an enum for these?
    53     /** An 8-bit unsigned integer. */
    54     private static final int FMT_BYTE = 1;
    55     /** A fixed-length character string. */
    56     private static final int FMT_STRING = 2;
    57     /** An unsigned 16-bit integer. */
    58     private static final int FMT_USHORT = 3;
    59     /** An unsigned 32-bit integer. */
    60     private static final int FMT_ULONG = 4;
    61     private static final int FMT_URATIONAL = 5;
    62     /** An 8-bit signed integer. */
    63     private static final int FMT_SBYTE = 6;
    64     private static final int FMT_UNDEFINED = 7;
    65     /** A signed 16-bit integer. */
    66     private static final int FMT_SSHORT = 8;
    67     /** A signed 32-bit integer. */
    68     private static final int FMT_SLONG = 9;
    69     private static final int FMT_SRATIONAL = 10;
    70     /** A 32-bit floating point number. */
    71     private static final int FMT_SINGLE = 11;
    72     /** A 64-bit floating point number. */
    73     private static final int FMT_DOUBLE = 12;
     72    public void extract(@NotNull final byte[] segmentBytes, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType)
     73    {
     74        if (segmentBytes == null)
     75            throw new NullPointerException("segmentBytes cannot be null");
     76        if (metadata == null)
     77            throw new NullPointerException("metadata cannot be null");
     78        if (segmentType == null)
     79            throw new NullPointerException("segmentType cannot be null");
    7480
    75     /** This tag is a pointer to the Exif SubIFD. */
    76     public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769;
    77     /** This tag is a pointer to the Exif Interop IFD. */
    78     public static final int TAG_INTEROP_OFFSET = 0xA005;
    79     /** This tag is a pointer to the Exif GPS IFD. */
    80     public static final int TAG_GPS_INFO_OFFSET = 0x8825;
    81     /** This tag is a pointer to the Exif Makernote IFD. */
    82     public static final int TAG_MAKER_NOTE_OFFSET = 0x927C;
     81        try {
     82            ByteArrayReader reader = new ByteArrayReader(segmentBytes);
    8383
    84     public static final int TIFF_HEADER_START_OFFSET = 6;
    85 
    86     /**
    87      * Performs the Exif data extraction, adding found values to the specified
    88      * instance of <code>Metadata</code>.
    89      *
    90      * @param reader   The buffer reader from which Exif data should be read.
    91      * @param metadata The Metadata object into which extracted values should be merged.
    92      */
    93     public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata)
    94     {
    95         final ExifSubIFDDirectory directory = metadata.getOrCreateDirectory(ExifSubIFDDirectory.class);
    96 
    97         // check for the header length
    98         if (reader.getLength() <= 14) {
    99             directory.addError("Exif data segment must contain at least 14 bytes");
    100             return;
    101         }
    102 
    103         // check for the header preamble
    104         try {
    105             if (!reader.getString(0, 6).equals("Exif\0\0")) {
    106                 directory.addError("Exif data segment doesn't begin with 'Exif'");
     84            //
     85            // Check for the header preamble
     86            //
     87            try {
     88                if (!reader.getString(0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equals(JPEG_EXIF_SEGMENT_PREAMBLE)) {
     89                    // TODO what do to with this error state?
     90                    System.err.println("Invalid JPEG Exif segment preamble");
     91                    return;
     92                }
     93            } catch (IOException e) {
     94                // TODO what do to with this error state?
     95                e.printStackTrace(System.err);
    10796                return;
    10897            }
    10998
    110             extractIFD(metadata, metadata.getOrCreateDirectory(ExifIFD0Directory.class), TIFF_HEADER_START_OFFSET, reader);
    111         } catch (BufferBoundsException e) {
    112             directory.addError("Exif data segment ended prematurely");
     99            //
     100            // Read the TIFF-formatted Exif data
     101            //
     102            new TiffReader().processTiff(
     103                reader,
     104                new ExifTiffHandler(metadata, _storeThumbnailBytes),
     105                JPEG_EXIF_SEGMENT_PREAMBLE.length()
     106            );
     107
     108        } catch (TiffProcessingException e) {
     109            // TODO what do to with this error state?
     110            e.printStackTrace(System.err);
     111        } catch (IOException e) {
     112            // TODO what do to with this error state?
     113            e.printStackTrace(System.err);
    113114        }
    114115    }
    115 
    116     /**
    117      * Performs the Exif data extraction on a TIFF/RAW, adding found values to the specified
    118      * instance of <code>Metadata</code>.
    119      *
    120      * @param reader   The BufferReader from which TIFF data should be read.
    121      * @param metadata The Metadata object into which extracted values should be merged.
    122      */
    123     public void extractTiff(@NotNull BufferReader reader, @NotNull Metadata metadata)
    124     {
    125         final ExifIFD0Directory directory = metadata.getOrCreateDirectory(ExifIFD0Directory.class);
    126 
    127         try {
    128             extractIFD(metadata, directory, 0, reader);
    129         } catch (BufferBoundsException e) {
    130             directory.addError("Exif data segment ended prematurely");
    131         }
    132     }
    133 
    134     private void extractIFD(@NotNull Metadata metadata, @NotNull final ExifIFD0Directory directory, int tiffHeaderOffset, @NotNull BufferReader reader) throws BufferBoundsException
    135     {
    136         // this should be either "MM" or "II"
    137         String byteOrderIdentifier = reader.getString(tiffHeaderOffset, 2);
    138 
    139         if ("MM".equals(byteOrderIdentifier)) {
    140             reader.setMotorolaByteOrder(true);
    141         } else if ("II".equals(byteOrderIdentifier)) {
    142             reader.setMotorolaByteOrder(false);
    143         } else {
    144             directory.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);
    145             return;
    146         }
    147 
    148         // Check the next two values for correctness.
    149         final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset);
    150 
    151         final int standardTiffMarker = 0x002A;
    152         final int olympusRawTiffMarker = 0x4F52; // for ORF files
    153         final int panasonicRawTiffMarker = 0x0055; // for RW2 files
    154 
    155         if (tiffMarker != standardTiffMarker && tiffMarker != olympusRawTiffMarker && tiffMarker != panasonicRawTiffMarker) {
    156             directory.addError("Unexpected TIFF marker after byte order identifier: 0x" + Integer.toHexString(tiffMarker));
    157             return;
    158         }
    159 
    160         int firstDirectoryOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;
    161 
    162         // David Ekholm sent a digital camera image that has this problem
    163         if (firstDirectoryOffset >= reader.getLength() - 1) {
    164             directory.addError("First exif directory offset is beyond end of Exif data segment");
    165             // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case
    166             firstDirectoryOffset = 14;
    167         }
    168 
    169         Set<Integer> processedDirectoryOffsets = new HashSet<Integer>();
    170 
    171         processDirectory(directory, processedDirectoryOffsets, firstDirectoryOffset, tiffHeaderOffset, metadata, reader);
    172 
    173         // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
    174         ExifThumbnailDirectory thumbnailDirectory = metadata.getDirectory(ExifThumbnailDirectory.class);
    175         if (thumbnailDirectory!=null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION)) {
    176             Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
    177             Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
    178             if (offset != null && length != null) {
    179                 try {
    180                     byte[] thumbnailData = reader.getBytes(tiffHeaderOffset + offset, length);
    181                     thumbnailDirectory.setThumbnailData(thumbnailData);
    182                 } catch (BufferBoundsException ex) {
    183                     directory.addError("Invalid thumbnail data specification: " + ex.getMessage());
    184                 }
    185             }
    186         }
    187     }
    188 
    189     /**
    190      * Process one of the nested Tiff IFD directories.
    191      * <p/>
    192      * Header
    193      * 2 bytes: number of tags
    194      * <p/>
    195      * Then for each tag
    196      * 2 bytes: tag type
    197      * 2 bytes: format code
    198      * 4 bytes: component count
    199      */
    200     private void processDirectory(@NotNull Directory directory, @NotNull Set<Integer> processedDirectoryOffsets, int dirStartOffset, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull final BufferReader reader) throws BufferBoundsException
    201     {
    202         // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist
    203         if (processedDirectoryOffsets.contains(Integer.valueOf(dirStartOffset)))
    204             return;
    205 
    206         // remember that we've visited this directory so that we don't visit it again later
    207         processedDirectoryOffsets.add(dirStartOffset);
    208 
    209         if (dirStartOffset >= reader.getLength() || dirStartOffset < 0) {
    210             directory.addError("Ignored directory marked to start outside data segment");
    211             return;
    212         }
    213 
    214         // First two bytes in the IFD are the number of tags in this directory
    215         int dirTagCount = reader.getUInt16(dirStartOffset);
    216 
    217         int dirLength = (2 + (12 * dirTagCount) + 4);
    218         if (dirLength + dirStartOffset > reader.getLength()) {
    219             directory.addError("Illegally sized directory");
    220             return;
    221         }
    222 
    223         // Handle each tag in this directory
    224         for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) {
    225             final int tagOffset = calculateTagOffset(dirStartOffset, tagNumber);
    226 
    227             // 2 bytes for the tag type
    228             final int tagType = reader.getUInt16(tagOffset);
    229 
    230             // 2 bytes for the format code
    231             final int formatCode = reader.getUInt16(tagOffset + 2);
    232             if (formatCode < 1 || formatCode > MAX_FORMAT_CODE) {
    233                 // This error suggests that we are processing at an incorrect index and will generate
    234                 // rubbish until we go out of bounds (which may be a while).  Exit now.
    235                 directory.addError("Invalid TIFF tag format code: " + formatCode);
    236                 continue; // JOSM patch to fix #9030
    237             }
    238 
    239             // 4 bytes dictate the number of components in this tag's data
    240             final int componentCount = reader.getInt32(tagOffset + 4);
    241             if (componentCount < 0) {
    242                 directory.addError("Negative TIFF tag component count");
    243                 continue;
    244             }
    245             // each component may have more than one byte... calculate the total number of bytes
    246             final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode];
    247             final int tagValueOffset;
    248             if (byteCount > 4) {
    249                 // If it's bigger than 4 bytes, the dir entry contains an offset.
    250                 // dirEntryOffset must be passed, as some makernote implementations (e.g. FujiFilm) incorrectly use an
    251                 // offset relative to the start of the makernote itself, not the TIFF segment.
    252                 final int offsetVal = reader.getInt32(tagOffset + 8);
    253                 if (offsetVal + byteCount > reader.getLength()) {
    254                     // Bogus pointer offset and / or byteCount value
    255                     directory.addError("Illegal TIFF tag pointer offset");
    256                     continue;
    257                 }
    258                 tagValueOffset = tiffHeaderOffset + offsetVal;
    259             } else {
    260                 // 4 bytes or less and value is in the dir entry itself
    261                 tagValueOffset = tagOffset + 8;
    262             }
    263 
    264             if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) {
    265                 directory.addError("Illegal TIFF tag pointer offset");
    266                 continue;
    267             }
    268 
    269             // Check that this tag isn't going to allocate outside the bounds of the data array.
    270             // This addresses an uncommon OutOfMemoryError.
    271             if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) {
    272                 directory.addError("Illegal number of bytes: " + byteCount);
    273                 continue;
    274             }
    275 
    276             switch (tagType) {
    277                 case TAG_EXIF_SUB_IFD_OFFSET: {
    278                     final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
    279                     processDirectory(metadata.getOrCreateDirectory(ExifSubIFDDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    280                     continue;
    281                 }
    282                 case TAG_INTEROP_OFFSET: {
    283                     final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
    284                     processDirectory(metadata.getOrCreateDirectory(ExifInteropDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    285                     continue;
    286                 }
    287                 case TAG_GPS_INFO_OFFSET: {
    288                     final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
    289                     processDirectory(metadata.getOrCreateDirectory(GpsDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    290                     continue;
    291                 }
    292                 case TAG_MAKER_NOTE_OFFSET: {
    293                     processMakerNote(tagValueOffset, processedDirectoryOffsets, tiffHeaderOffset, metadata, reader);
    294                     continue;
    295                 }
    296                 default: {
    297                     processTag(directory, tagType, tagValueOffset, componentCount, formatCode, reader);
    298                     break;
    299                 }
    300             }
    301         }
    302 
    303         // at the end of each IFD is an optional link to the next IFD
    304         final int finalTagOffset = calculateTagOffset(dirStartOffset, dirTagCount);
    305         int nextDirectoryOffset = reader.getInt32(finalTagOffset);
    306         if (nextDirectoryOffset != 0) {
    307             nextDirectoryOffset += tiffHeaderOffset;
    308             if (nextDirectoryOffset >= reader.getLength()) {
    309                 // Last 4 bytes of IFD reference another IFD with an address that is out of bounds
    310                 // Note this could have been caused by jhead 1.3 cropping too much
    311                 return;
    312             } else if (nextDirectoryOffset < dirStartOffset) {
    313                 // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory
    314                 return;
    315             }
    316             // TODO in Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case
    317             final ExifThumbnailDirectory nextDirectory = metadata.getOrCreateDirectory(ExifThumbnailDirectory.class);
    318             processDirectory(nextDirectory, processedDirectoryOffsets, nextDirectoryOffset, tiffHeaderOffset, metadata, reader);
    319         }
    320     }
    321 
    322     private void processMakerNote(int subdirOffset, @NotNull Set<Integer> processedDirectoryOffsets, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull BufferReader reader) throws BufferBoundsException
    323     {
    324         // Determine the camera model and makernote format
    325         Directory ifd0Directory = metadata.getDirectory(ExifIFD0Directory.class);
    326 
    327         if (ifd0Directory==null)
    328             return;
    329 
    330         String cameraModel = ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
    331 
    332         //final String firstTwoChars = reader.getString(subdirOffset, 2);
    333         final String firstThreeChars = reader.getString(subdirOffset, 3);
    334         final String firstFourChars = reader.getString(subdirOffset, 4);
    335         final String firstFiveChars = reader.getString(subdirOffset, 5);
    336         final String firstSixChars = reader.getString(subdirOffset, 6);
    337         final String firstSevenChars = reader.getString(subdirOffset, 7);
    338         final String firstEightChars = reader.getString(subdirOffset, 8);
    339         final String firstTwelveChars = reader.getString(subdirOffset, 12);
    340 
    341         if ("OLYMP".equals(firstFiveChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {
    342             // Olympus Makernote
    343             // Epson and Agfa use Olympus maker note standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/
    344             processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);
    345         } else if (cameraModel != null && cameraModel.trim().toUpperCase().startsWith("NIKON")) {
    346             if ("Nikon".equals(firstFiveChars)) {
    347                 /* There are two scenarios here:
    348                  * Type 1:                  **
    349                  * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon...........
    350                  * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................
    351                  * Type 3:                  **
    352                  * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*...
    353                  * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200
    354                  */
    355                 switch (reader.getUInt8(subdirOffset + 6)) {
    356                     case 1:
    357                         processDirectory(metadata.getOrCreateDirectory(NikonType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);
    358                         break;
    359                     case 2:
    360                         processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 18, subdirOffset + 10, metadata, reader);
    361                         break;
    362                     default:
    363                         ifd0Directory.addError("Unsupported Nikon makernote data ignored.");
    364                         break;
    365                 }
    366             } else {
    367                 // The IFD begins with the first MakerNote byte (no ASCII name).  This occurs with CoolPix 775, E990 and D1 models.
    368                 processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    369             }
    370         } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) {
    371             processDirectory(metadata.getOrCreateDirectory(SonyType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);
    372         } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) {
    373             processDirectory(metadata.getOrCreateDirectory(SigmaMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 10, tiffHeaderOffset, metadata, reader);
    374         } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) {
    375             // force MM for this directory
    376             boolean isMotorola = reader.isMotorolaByteOrder();
    377             reader.setMotorolaByteOrder(true);
    378             // skip 12 byte header + 2 for "MM" + 6
    379             processDirectory(metadata.getOrCreateDirectory(SonyType6MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);
    380             reader.setMotorolaByteOrder(isMotorola);
    381         } else if ("KDK".equals(firstThreeChars)) {
    382             processDirectory(metadata.getOrCreateDirectory(KodakMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);
    383         } else if ("Canon".equalsIgnoreCase(cameraModel)) {
    384             processDirectory(metadata.getOrCreateDirectory(CanonMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    385         } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("CASIO")) {
    386             if ("QVC\u0000\u0000\u0000".equals(firstSixChars))
    387                 processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, tiffHeaderOffset, metadata, reader);
    388             else
    389                 processDirectory(metadata.getOrCreateDirectory(CasioType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    390         } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraModel)) {
    391             boolean byteOrderBefore = reader.isMotorolaByteOrder();
    392             // bug in fujifilm makernote ifd means we temporarily use Intel byte ordering
    393             reader.setMotorolaByteOrder(false);
    394             // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote
    395             // IFD, though the offset is relative to the start of the makernote, not the TIFF
    396             // header (like everywhere else)
    397             int ifdStart = subdirOffset + reader.getInt32(subdirOffset + 8);
    398             processDirectory(metadata.getOrCreateDirectory(FujifilmMakernoteDirectory.class), processedDirectoryOffsets, ifdStart, tiffHeaderOffset, metadata, reader);
    399             reader.setMotorolaByteOrder(byteOrderBefore);
    400         } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("MINOLTA")) {
    401             // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote
    402             // area that commences immediately.
    403             processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    404         } else if ("KYOCERA".equals(firstSevenChars)) {
    405             // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html
    406             processDirectory(metadata.getOrCreateDirectory(KyoceraMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 22, tiffHeaderOffset, metadata, reader);
    407         } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(subdirOffset, 12))) {
    408             // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD
    409             // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
    410             // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html
    411             processDirectory(metadata.getOrCreateDirectory(PanasonicMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);
    412         } else if ("AOC\u0000".equals(firstFourChars)) {
    413             // NON-Standard TIFF IFD Data using Casio Type 2 Tags
    414             // IFD has no Next-IFD pointer at end of IFD, and
    415             // Offsets are relative to the start of the current IFD tag, not the TIFF header
    416             // Observed for:
    417             // - Pentax ist D
    418             processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, subdirOffset, metadata, reader);
    419         } else if (cameraModel != null && (cameraModel.toUpperCase().startsWith("PENTAX") || cameraModel.toUpperCase().startsWith("ASAHI"))) {
    420             // NON-Standard TIFF IFD Data using Pentax Tags
    421             // IFD has no Next-IFD pointer at end of IFD, and
    422             // Offsets are relative to the start of the current IFD tag, not the TIFF header
    423             // Observed for:
    424             // - PENTAX Optio 330
    425             // - PENTAX Optio 430
    426             processDirectory(metadata.getOrCreateDirectory(PentaxMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, subdirOffset, metadata, reader);
    427 //        } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) {
    428 //            // This Konica data is not understood.  Header identified in accordance with information at this site:
    429 //            // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html
    430 //            // TODO add support for minolta/konica cameras
    431 //            exifDirectory.addError("Unsupported Konica/Minolta data ignored.");
    432         } else {
    433             // TODO how to store makernote data when it's not from a supported camera model?
    434             // this is difficult as the starting offset is not known.  we could look for it...
    435         }
    436     }
    437 
    438     private void processTag(@NotNull Directory directory, int tagType, int tagValueOffset, int componentCount, int formatCode, @NotNull final BufferReader reader) throws BufferBoundsException
    439     {
    440         // Directory simply stores raw values
    441         // The display side uses a Descriptor class per directory to turn the raw values into 'pretty' descriptions
    442         switch (formatCode) {
    443             case FMT_UNDEFINED:
    444                 // this includes exif user comments
    445                 directory.setByteArray(tagType, reader.getBytes(tagValueOffset, componentCount));
    446                 break;
    447             case FMT_STRING:
    448                 String string = reader.getNullTerminatedString(tagValueOffset, componentCount);
    449                 directory.setString(tagType, string);
    450 /*
    451                 // special handling for certain known tags, proposed by Yuri Binev but left out for now,
    452                 // as it gives the false impression that the image was captured in the same timezone
    453                 // in which the string is parsed
    454                 if (tagType==ExifSubIFDDirectory.TAG_DATETIME ||
    455                     tagType==ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL ||
    456                     tagType==ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED) {
    457                     String[] datePatterns = {
    458                         "yyyy:MM:dd HH:mm:ss",
    459                         "yyyy:MM:dd HH:mm",
    460                         "yyyy-MM-dd HH:mm:ss",
    461                         "yyyy-MM-dd HH:mm"};
    462                     for (String datePattern : datePatterns) {
    463                         try {
    464                             DateFormat parser = new SimpleDateFormat(datePattern);
    465                             Date date = parser.parse(string);
    466                             directory.setDate(tagType, date);
    467                             break;
    468                         } catch (ParseException ex) {
    469                             // simply try the next pattern
    470                         }
    471                     }
    472                 }
    473 */
    474                 break;
    475             case FMT_SRATIONAL:
    476                 if (componentCount == 1) {
    477                     directory.setRational(tagType, new Rational(reader.getInt32(tagValueOffset), reader.getInt32(tagValueOffset + 4)));
    478                 } else if (componentCount > 1) {
    479                     Rational[] rationals = new Rational[componentCount];
    480                     for (int i = 0; i < componentCount; i++)
    481                         rationals[i] = new Rational(reader.getInt32(tagValueOffset + (8 * i)), reader.getInt32(tagValueOffset + 4 + (8 * i)));
    482                     directory.setRationalArray(tagType, rationals);
    483                 }
    484                 break;
    485             case FMT_URATIONAL:
    486                 if (componentCount == 1) {
    487                     directory.setRational(tagType, new Rational(reader.getUInt32(tagValueOffset), reader.getUInt32(tagValueOffset + 4)));
    488                 } else if (componentCount > 1) {
    489                     Rational[] rationals = new Rational[componentCount];
    490                     for (int i = 0; i < componentCount; i++)
    491                         rationals[i] = new Rational(reader.getUInt32(tagValueOffset + (8 * i)), reader.getUInt32(tagValueOffset + 4 + (8 * i)));
    492                     directory.setRationalArray(tagType, rationals);
    493                 }
    494                 break;
    495             case FMT_SINGLE:
    496                 if (componentCount == 1) {
    497                     directory.setFloat(tagType, reader.getFloat32(tagValueOffset));
    498                 } else {
    499                     float[] floats = new float[componentCount];
    500                     for (int i = 0; i < componentCount; i++)
    501                         floats[i] = reader.getFloat32(tagValueOffset + (i * 4));
    502                     directory.setFloatArray(tagType, floats);
    503                 }
    504                 break;
    505             case FMT_DOUBLE:
    506                 if (componentCount == 1) {
    507                     directory.setDouble(tagType, reader.getDouble64(tagValueOffset));
    508                 } else {
    509                     double[] doubles = new double[componentCount];
    510                     for (int i = 0; i < componentCount; i++)
    511                         doubles[i] = reader.getDouble64(tagValueOffset + (i * 4));
    512                     directory.setDoubleArray(tagType, doubles);
    513                 }
    514                 break;
    515 
    516             //
    517             // Note that all integral types are stored as int32 internally (the largest supported by TIFF)
    518             //
    519 
    520             case FMT_SBYTE:
    521                 if (componentCount == 1) {
    522                     directory.setInt(tagType, reader.getInt8(tagValueOffset));
    523                 } else {
    524                     int[] bytes = new int[componentCount];
    525                     for (int i = 0; i < componentCount; i++)
    526                         bytes[i] = reader.getInt8(tagValueOffset + i);
    527                     directory.setIntArray(tagType, bytes);
    528                 }
    529                 break;
    530             case FMT_BYTE:
    531                 if (componentCount == 1) {
    532                     directory.setInt(tagType, reader.getUInt8(tagValueOffset));
    533                 } else {
    534                     int[] bytes = new int[componentCount];
    535                     for (int i = 0; i < componentCount; i++)
    536                         bytes[i] = reader.getUInt8(tagValueOffset + i);
    537                     directory.setIntArray(tagType, bytes);
    538                 }
    539                 break;
    540             case FMT_USHORT:
    541                 if (componentCount == 1) {
    542                     int i = reader.getUInt16(tagValueOffset);
    543                     directory.setInt(tagType, i);
    544                 } else {
    545                     int[] ints = new int[componentCount];
    546                     for (int i = 0; i < componentCount; i++)
    547                         ints[i] = reader.getUInt16(tagValueOffset + (i * 2));
    548                     directory.setIntArray(tagType, ints);
    549                 }
    550                 break;
    551             case FMT_SSHORT:
    552                 if (componentCount == 1) {
    553                     int i = reader.getInt16(tagValueOffset);
    554                     directory.setInt(tagType, i);
    555                 } else {
    556                     int[] ints = new int[componentCount];
    557                     for (int i = 0; i < componentCount; i++)
    558                         ints[i] = reader.getInt16(tagValueOffset + (i * 2));
    559                     directory.setIntArray(tagType, ints);
    560                 }
    561                 break;
    562             case FMT_SLONG:
    563             case FMT_ULONG:
    564                 // NOTE 'long' in this case means 32 bit, not 64
    565                 if (componentCount == 1) {
    566                     int i = reader.getInt32(tagValueOffset);
    567                     directory.setInt(tagType, i);
    568                 } else {
    569                     int[] ints = new int[componentCount];
    570                     for (int i = 0; i < componentCount; i++)
    571                         ints[i] = reader.getInt32(tagValueOffset + (i * 4));
    572                     directory.setIntArray(tagType, ints);
    573                 }
    574                 break;
    575             default:
    576                 directory.addError("Unknown format code " + formatCode + " for tag " + tagType);
    577         }
    578     }
    579 
    580     /**
    581      * Determine the offset at which a given InteropArray entry begins within the specified IFD.
    582      *
    583      * @param dirStartOffset the offset at which the IFD starts
    584      * @param entryNumber    the zero-based entry number
    585      */
    586     private int calculateTagOffset(int dirStartOffset, int entryNumber)
    587     {
    588         // add 2 bytes for the tag count
    589         // each entry is 12 bytes, so we skip 12 * the number seen so far
    590         return dirStartOffset + 2 + (12 * entryNumber);
    591     }
    592116}
  • trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    3232import java.util.Map;
    3333
     34import static com.drew.metadata.exif.ExifSubIFDDirectory.*;
     35
    3436/**
    35  * Provides human-readable string representations of tag values stored in a <code>ExifSubIFDDirectory</code>.
     37 * Provides human-readable string representations of tag values stored in a {@link ExifSubIFDDirectory}.
    3638 *
    37  * @author Drew Noakes http://drewnoakes.com
     39 * @author Drew Noakes https://drewnoakes.com
    3840 */
    3941public class ExifSubIFDDescriptor extends TagDescriptor<ExifSubIFDDirectory>
     
    6062
    6163    /**
    62      * Returns a descriptive value of the the specified tag for this image.
     64     * Returns a descriptive value of the specified tag for this image.
    6365     * Where possible, known values will be substituted here in place of the raw
    6466     * tokens actually kept in the Exif segment.  If no substitution is
    6567     * available, the value provided by getString(int) will be returned.
     68     *
    6669     * @param tagType the tag to find a description for
    6770     * @return a description of the image's value for the specified tag, or
    6871     *         <code>null</code> if the tag hasn't been defined.
    6972     */
     73    @Override
    7074    @Nullable
    7175    public String getDescription(int tagType)
    7276    {
    7377        switch (tagType) {
    74             case ExifSubIFDDirectory.TAG_NEW_SUBFILE_TYPE:
     78            case TAG_NEW_SUBFILE_TYPE:
    7579                return getNewSubfileTypeDescription();
    76             case ExifSubIFDDirectory.TAG_SUBFILE_TYPE:
     80            case TAG_SUBFILE_TYPE:
    7781                return getSubfileTypeDescription();
    78             case ExifSubIFDDirectory.TAG_THRESHOLDING:
     82            case TAG_THRESHOLDING:
    7983                return getThresholdingDescription();
    80             case ExifSubIFDDirectory.TAG_FILL_ORDER:
     84            case TAG_FILL_ORDER:
    8185                return getFillOrderDescription();
    82             case ExifSubIFDDirectory.TAG_EXPOSURE_TIME:
     86            case TAG_EXPOSURE_TIME:
    8387                return getExposureTimeDescription();
    84             case ExifSubIFDDirectory.TAG_SHUTTER_SPEED:
     88            case TAG_SHUTTER_SPEED:
    8589                return getShutterSpeedDescription();
    86             case ExifSubIFDDirectory.TAG_FNUMBER:
     90            case TAG_FNUMBER:
    8791                return getFNumberDescription();
    88             case ExifSubIFDDirectory.TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL:
     92            case TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL:
    8993                return getCompressedAverageBitsPerPixelDescription();
    90             case ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE:
     94            case TAG_SUBJECT_DISTANCE:
    9195                return getSubjectDistanceDescription();
    92             case ExifSubIFDDirectory.TAG_METERING_MODE:
     96            case TAG_METERING_MODE:
    9397                return getMeteringModeDescription();
    94             case ExifSubIFDDirectory.TAG_WHITE_BALANCE:
     98            case TAG_WHITE_BALANCE:
    9599                return getWhiteBalanceDescription();
    96             case ExifSubIFDDirectory.TAG_FLASH:
     100            case TAG_FLASH:
    97101                return getFlashDescription();
    98             case ExifSubIFDDirectory.TAG_FOCAL_LENGTH:
     102            case TAG_FOCAL_LENGTH:
    99103                return getFocalLengthDescription();
    100             case ExifSubIFDDirectory.TAG_COLOR_SPACE:
     104            case TAG_COLOR_SPACE:
    101105                return getColorSpaceDescription();
    102             case ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH:
     106            case TAG_EXIF_IMAGE_WIDTH:
    103107                return getExifImageWidthDescription();
    104             case ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT:
     108            case TAG_EXIF_IMAGE_HEIGHT:
    105109                return getExifImageHeightDescription();
    106             case ExifSubIFDDirectory.TAG_FOCAL_PLANE_UNIT:
     110            case TAG_FOCAL_PLANE_RESOLUTION_UNIT:
    107111                return getFocalPlaneResolutionUnitDescription();
    108             case ExifSubIFDDirectory.TAG_FOCAL_PLANE_X_RES:
     112            case TAG_FOCAL_PLANE_X_RESOLUTION:
    109113                return getFocalPlaneXResolutionDescription();
    110             case ExifSubIFDDirectory.TAG_FOCAL_PLANE_Y_RES:
     114            case TAG_FOCAL_PLANE_Y_RESOLUTION:
    111115                return getFocalPlaneYResolutionDescription();
    112             case ExifSubIFDDirectory.TAG_BITS_PER_SAMPLE:
     116            case TAG_BITS_PER_SAMPLE:
    113117                return getBitsPerSampleDescription();
    114             case ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION:
     118            case TAG_PHOTOMETRIC_INTERPRETATION:
    115119                return getPhotometricInterpretationDescription();
    116             case ExifSubIFDDirectory.TAG_ROWS_PER_STRIP:
     120            case TAG_ROWS_PER_STRIP:
    117121                return getRowsPerStripDescription();
    118             case ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS:
     122            case TAG_STRIP_BYTE_COUNTS:
    119123                return getStripByteCountsDescription();
    120             case ExifSubIFDDirectory.TAG_SAMPLES_PER_PIXEL:
     124            case TAG_SAMPLES_PER_PIXEL:
    121125                return getSamplesPerPixelDescription();
    122             case ExifSubIFDDirectory.TAG_PLANAR_CONFIGURATION:
     126            case TAG_PLANAR_CONFIGURATION:
    123127                return getPlanarConfigurationDescription();
    124             case ExifSubIFDDirectory.TAG_YCBCR_SUBSAMPLING:
     128            case TAG_YCBCR_SUBSAMPLING:
    125129                return getYCbCrSubsamplingDescription();
    126             case ExifSubIFDDirectory.TAG_EXPOSURE_PROGRAM:
     130            case TAG_EXPOSURE_PROGRAM:
    127131                return getExposureProgramDescription();
    128             case ExifSubIFDDirectory.TAG_APERTURE:
     132            case TAG_APERTURE:
    129133                return getApertureValueDescription();
    130             case ExifSubIFDDirectory.TAG_MAX_APERTURE:
     134            case TAG_MAX_APERTURE:
    131135                return getMaxApertureValueDescription();
    132             case ExifSubIFDDirectory.TAG_SENSING_METHOD:
     136            case TAG_SENSING_METHOD:
    133137                return getSensingMethodDescription();
    134             case ExifSubIFDDirectory.TAG_EXPOSURE_BIAS:
     138            case TAG_EXPOSURE_BIAS:
    135139                return getExposureBiasDescription();
    136             case ExifSubIFDDirectory.TAG_FILE_SOURCE:
     140            case TAG_FILE_SOURCE:
    137141                return getFileSourceDescription();
    138             case ExifSubIFDDirectory.TAG_SCENE_TYPE:
     142            case TAG_SCENE_TYPE:
    139143                return getSceneTypeDescription();
    140             case ExifSubIFDDirectory.TAG_COMPONENTS_CONFIGURATION:
     144            case TAG_COMPONENTS_CONFIGURATION:
    141145                return getComponentConfigurationDescription();
    142             case ExifSubIFDDirectory.TAG_EXIF_VERSION:
     146            case TAG_EXIF_VERSION:
    143147                return getExifVersionDescription();
    144             case ExifSubIFDDirectory.TAG_FLASHPIX_VERSION:
     148            case TAG_FLASHPIX_VERSION:
    145149                return getFlashPixVersionDescription();
    146             case ExifSubIFDDirectory.TAG_ISO_EQUIVALENT:
     150            case TAG_ISO_EQUIVALENT:
    147151                return getIsoEquivalentDescription();
    148             case ExifSubIFDDirectory.TAG_USER_COMMENT:
     152            case TAG_USER_COMMENT:
    149153                return getUserCommentDescription();
    150             case ExifSubIFDDirectory.TAG_CUSTOM_RENDERED:
     154            case TAG_CUSTOM_RENDERED:
    151155                return getCustomRenderedDescription();
    152             case ExifSubIFDDirectory.TAG_EXPOSURE_MODE:
     156            case TAG_EXPOSURE_MODE:
    153157                return getExposureModeDescription();
    154             case ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE:
     158            case TAG_WHITE_BALANCE_MODE:
    155159                return getWhiteBalanceModeDescription();
    156             case ExifSubIFDDirectory.TAG_DIGITAL_ZOOM_RATIO:
     160            case TAG_DIGITAL_ZOOM_RATIO:
    157161                return getDigitalZoomRatioDescription();
    158             case ExifSubIFDDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH:
     162            case TAG_35MM_FILM_EQUIV_FOCAL_LENGTH:
    159163                return get35mmFilmEquivFocalLengthDescription();
    160             case ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE:
     164            case TAG_SCENE_CAPTURE_TYPE:
    161165                return getSceneCaptureTypeDescription();
    162             case ExifSubIFDDirectory.TAG_GAIN_CONTROL:
     166            case TAG_GAIN_CONTROL:
    163167                return getGainControlDescription();
    164             case ExifSubIFDDirectory.TAG_CONTRAST:
     168            case TAG_CONTRAST:
    165169                return getContrastDescription();
    166             case ExifSubIFDDirectory.TAG_SATURATION:
     170            case TAG_SATURATION:
    167171                return getSaturationDescription();
    168             case ExifSubIFDDirectory.TAG_SHARPNESS:
     172            case TAG_SHARPNESS:
    169173                return getSharpnessDescription();
    170             case ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE_RANGE:
     174            case TAG_SUBJECT_DISTANCE_RANGE:
    171175                return getSubjectDistanceRangeDescription();
    172176            default:
     
    178182    public String getNewSubfileTypeDescription()
    179183    {
    180         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_NEW_SUBFILE_TYPE);
    181         if (value==null)
    182             return null;
    183         switch (value) {
    184             case 1: return "Full-resolution image";
    185             case 2: return "Reduced-resolution image";
    186             case 3: return "Single page of multi-page reduced-resolution image";
    187             case 4: return "Transparency mask";
    188             case 5: return "Transparency mask of reduced-resolution image";
    189             case 6: return "Transparency mask of multi-page image";
    190             case 7: return "Transparency mask of reduced-resolution multi-page image";
    191             default:
    192                 return "Unknown (" + value + ")";
    193         }
     184        return getIndexedDescription(TAG_NEW_SUBFILE_TYPE, 1,
     185            "Full-resolution image",
     186            "Reduced-resolution image",
     187            "Single page of multi-page reduced-resolution image",
     188            "Transparency mask",
     189            "Transparency mask of reduced-resolution image",
     190            "Transparency mask of multi-page image",
     191            "Transparency mask of reduced-resolution multi-page image"
     192        );
    194193    }
    195194
     
    197196    public String getSubfileTypeDescription()
    198197    {
    199         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SUBFILE_TYPE);
    200         if (value==null)
    201             return null;
    202         switch (value) {
    203             case 1: return "Full-resolution image";
    204             case 2: return "Reduced-resolution image";
    205             case 3: return "Single page of multi-page image";
    206             default:
    207                 return "Unknown (" + value + ")";
    208         }
     198        return getIndexedDescription(TAG_SUBFILE_TYPE, 1,
     199            "Full-resolution image",
     200            "Reduced-resolution image",
     201            "Single page of multi-page image"
     202        );
    209203    }
    210204
     
    212206    public String getThresholdingDescription()
    213207    {
    214         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_THRESHOLDING);
    215         if (value==null)
    216             return null;
    217         switch (value) {
    218             case 1: return "No dithering or halftoning";
    219             case 2: return "Ordered dither or halftone";
    220             case 3: return "Randomized dither";
    221             default:
    222                 return "Unknown (" + value + ")";
    223         }
     208        return getIndexedDescription(TAG_THRESHOLDING, 1,
     209            "No dithering or halftoning",
     210            "Ordered dither or halftone",
     211            "Randomized dither"
     212        );
    224213    }
    225214
     
    227216    public String getFillOrderDescription()
    228217    {
    229         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FILL_ORDER);
    230         if (value==null)
    231             return null;
    232         switch (value) {
    233             case 1: return "Normal";
    234             case 2: return "Reversed";
    235             default:
    236                 return "Unknown (" + value + ")";
    237         }
     218        return getIndexedDescription(TAG_FILL_ORDER, 1,
     219            "Normal",
     220            "Reversed"
     221        );
    238222    }
    239223
     
    241225    public String getSubjectDistanceRangeDescription()
    242226    {
    243         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE_RANGE);
    244         if (value==null)
    245             return null;
    246         switch (value) {
    247             case 0: return "Unknown";
    248             case 1: return "Macro";
    249             case 2: return "Close view";
    250             case 3: return "Distant view";
    251             default:
    252                 return "Unknown (" + value + ")";
    253         }
     227        return getIndexedDescription(TAG_SUBJECT_DISTANCE_RANGE,
     228            "Unknown",
     229            "Macro",
     230            "Close view",
     231            "Distant view"
     232        );
    254233    }
    255234
     
    257236    public String getSharpnessDescription()
    258237    {
    259         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SHARPNESS);
    260         if (value==null)
    261             return null;
    262         switch (value) {
    263             case 0: return "None";
    264             case 1: return "Low";
    265             case 2: return "Hard";
    266             default:
    267                 return "Unknown (" + value + ")";
    268         }
     238        return getIndexedDescription(TAG_SHARPNESS,
     239            "None",
     240            "Low",
     241            "Hard"
     242        );
    269243    }
    270244
     
    272246    public String getSaturationDescription()
    273247    {
    274         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SATURATION);
    275         if (value==null)
    276             return null;
    277         switch (value) {
    278             case 0: return "None";
    279             case 1: return "Low saturation";
    280             case 2: return "High saturation";
    281             default:
    282                 return "Unknown (" + value + ")";
    283         }
     248        return getIndexedDescription(TAG_SATURATION,
     249            "None",
     250            "Low saturation",
     251            "High saturation"
     252        );
    284253    }
    285254
     
    287256    public String getContrastDescription()
    288257    {
    289         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_CONTRAST);
    290         if (value==null)
    291             return null;
    292         switch (value) {
    293             case 0: return "None";
    294             case 1: return "Soft";
    295             case 2: return "Hard";
    296             default:
    297                 return "Unknown (" + value + ")";
    298         }
     258        return getIndexedDescription(TAG_CONTRAST,
     259            "None",
     260            "Soft",
     261            "Hard"
     262        );
    299263    }
    300264
     
    302266    public String getGainControlDescription()
    303267    {
    304         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_GAIN_CONTROL);
    305         if (value==null)
    306             return null;
    307         switch (value) {
    308             case 0: return "None";
    309             case 1: return "Low gain up";
    310             case 2: return "Low gain down";
    311             case 3: return "High gain up";
    312             case 4: return "High gain down";
    313             default:
    314                 return "Unknown (" + value + ")";
    315         }
     268        return getIndexedDescription(TAG_GAIN_CONTROL,
     269            "None",
     270            "Low gain up",
     271            "Low gain down",
     272            "High gain up",
     273            "High gain down"
     274        );
    316275    }
    317276
     
    319278    public String getSceneCaptureTypeDescription()
    320279    {
    321         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE);
    322         if (value==null)
    323             return null;
    324         switch (value) {
    325             case 0: return "Standard";
    326             case 1: return "Landscape";
    327             case 2: return "Portrait";
    328             case 3: return "Night scene";
    329             default:
    330                 return "Unknown (" + value + ")";
    331         }
     280        return getIndexedDescription(TAG_SCENE_CAPTURE_TYPE,
     281            "Standard",
     282            "Landscape",
     283            "Portrait",
     284            "Night scene"
     285        );
    332286    }
    333287
     
    335289    public String get35mmFilmEquivFocalLengthDescription()
    336290    {
    337         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH);
    338         if (value==null)
    339             return null;
    340         if (value==0)
    341             return "Unknown";
    342         else
    343             return SimpleDecimalFormatter.format(value) + "mm";
     291        Integer value = _directory.getInteger(TAG_35MM_FILM_EQUIV_FOCAL_LENGTH);
     292        return value == null
     293            ? null
     294            : value == 0
     295            ? "Unknown"
     296            : SimpleDecimalFormatter.format(value) + "mm";
    344297    }
    345298
     
    347300    public String getDigitalZoomRatioDescription()
    348301    {
    349         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_DIGITAL_ZOOM_RATIO);
    350         if (value==null)
    351             return null;
    352         if (value.getNumerator()==0)
    353             return "Digital zoom not used.";
    354         return SimpleDecimalFormatter.format(value.doubleValue());
     302        Rational value = _directory.getRational(TAG_DIGITAL_ZOOM_RATIO);
     303        return value == null
     304            ? null
     305            : value.getNumerator() == 0
     306            ? "Digital zoom not used."
     307            : SimpleDecimalFormatter.format(value.doubleValue());
    355308    }
    356309
     
    358311    public String getWhiteBalanceModeDescription()
    359312    {
    360         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE);
    361         if (value==null)
    362             return null;
    363         switch (value) {
    364             case 0: return "Auto white balance";
    365             case 1: return "Manual white balance";
    366             default:
    367                 return "Unknown (" + value + ")";
    368         }
     313        return getIndexedDescription(TAG_WHITE_BALANCE_MODE,
     314            "Auto white balance",
     315            "Manual white balance"
     316        );
    369317    }
    370318
     
    372320    public String getExposureModeDescription()
    373321    {
    374         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXPOSURE_MODE);
    375         if (value==null)
    376             return null;
    377         switch (value) {
    378             case 0: return "Auto exposure";
    379             case 1: return "Manual exposure";
    380             case 2: return "Auto bracket";
    381             default:
    382                 return "Unknown (" + value + ")";
    383         }
     322        return getIndexedDescription(TAG_EXPOSURE_MODE,
     323            "Auto exposure",
     324            "Manual exposure",
     325            "Auto bracket"
     326        );
    384327    }
    385328
     
    387330    public String getCustomRenderedDescription()
    388331    {
    389         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_CUSTOM_RENDERED);
    390         if (value==null)
    391             return null;
    392         switch (value) {
    393             case 0: return "Normal process";
    394             case 1: return "Custom process";
    395             default:
    396                 return "Unknown (" + value + ")";
    397         }
     332        return getIndexedDescription(TAG_CUSTOM_RENDERED,
     333            "Normal process",
     334            "Custom process"
     335        );
    398336    }
    399337
     
    401339    public String getUserCommentDescription()
    402340    {
    403         byte[] commentBytes = _directory.getByteArray(ExifSubIFDDirectory.TAG_USER_COMMENT);
    404         if (commentBytes==null)
     341        byte[] commentBytes = _directory.getByteArray(TAG_USER_COMMENT);
     342        if (commentBytes == null)
    405343            return null;
    406344        if (commentBytes.length == 0)
     
    408346
    409347        final Map<String, String> encodingMap = new HashMap<String, String>();
    410         encodingMap.put("ASCII",    System.getProperty("file.encoding")); // Someone suggested "ISO-8859-1".
    411         encodingMap.put("UNICODE",  "UTF-16LE");
    412         encodingMap.put("JIS",      "Shift-JIS"); // We assume this charset for now.  Another suggestion is "JIS".
     348        encodingMap.put("ASCII", System.getProperty("file.encoding")); // Someone suggested "ISO-8859-1".
     349        encodingMap.put("UNICODE", "UTF-16LE");
     350        encodingMap.put("JIS", "Shift-JIS"); // We assume this charset for now.  Another suggestion is "JIS".
    413351
    414352        try {
     
    442380    {
    443381        // Have seen an exception here from files produced by ACDSEE that stored an int[] here with two values
    444         Integer isoEquiv = _directory.getInteger(ExifSubIFDDirectory.TAG_ISO_EQUIVALENT);
    445         if (isoEquiv==null)
    446             return null;
     382        Integer isoEquiv = _directory.getInteger(TAG_ISO_EQUIVALENT);
    447383        // There used to be a check here that multiplied ISO values < 50 by 200.
    448384        // Issue 36 shows a smart-phone image from a Samsung Galaxy S2 with ISO-40.
    449         return Integer.toString(isoEquiv);
     385        return isoEquiv != null
     386            ? Integer.toString(isoEquiv)
     387            : null;
    450388    }
    451389
     
    453391    public String getExifVersionDescription()
    454392    {
    455         int[] ints = _directory.getIntArray(ExifSubIFDDirectory.TAG_EXIF_VERSION);
    456         if (ints==null)
    457             return null;
    458         return ExifSubIFDDescriptor.convertBytesToVersionString(ints, 2);
     393        return getVersionBytesDescription(TAG_EXIF_VERSION, 2);
    459394    }
    460395
     
    462397    public String getFlashPixVersionDescription()
    463398    {
    464         int[] ints = _directory.getIntArray(ExifSubIFDDirectory.TAG_FLASHPIX_VERSION);
    465         if (ints==null)
    466             return null;
    467         return ExifSubIFDDescriptor.convertBytesToVersionString(ints, 2);
     399        return getVersionBytesDescription(TAG_FLASHPIX_VERSION, 2);
    468400    }
    469401
     
    471403    public String getSceneTypeDescription()
    472404    {
    473         Integer sceneType = _directory.getInteger(ExifSubIFDDirectory.TAG_SCENE_TYPE);
    474         if (sceneType==null)
    475             return null;
    476         return sceneType == 1
    477                 ? "Directly photographed image"
    478                 : "Unknown (" + sceneType + ")";
     405        return getIndexedDescription(TAG_SCENE_TYPE,
     406            1,
     407            "Directly photographed image"
     408        );
    479409    }
    480410
     
    482412    public String getFileSourceDescription()
    483413    {
    484         Integer fileSource = _directory.getInteger(ExifSubIFDDirectory.TAG_FILE_SOURCE);
    485         if (fileSource==null)
    486             return null;
    487         return fileSource == 3
    488                 ? "Digital Still Camera (DSC)"
    489                 : "Unknown (" + fileSource + ")";
     414        return getIndexedDescription(TAG_FILE_SOURCE,
     415            1,
     416            "Film Scanner",
     417            "Reflection Print Scanner",
     418            "Digital Still Camera (DSC)"
     419        );
    490420    }
    491421
     
    493423    public String getExposureBiasDescription()
    494424    {
    495         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_EXPOSURE_BIAS);
    496         if (value==null)
     425        Rational value = _directory.getRational(TAG_EXPOSURE_BIAS);
     426        if (value == null)
    497427            return null;
    498428        return value.toSimpleString(true) + " EV";
     
    502432    public String getMaxApertureValueDescription()
    503433    {
    504         Double aperture = _directory.getDoubleObject(ExifSubIFDDirectory.TAG_MAX_APERTURE);
    505         if (aperture==null)
     434        Double aperture = _directory.getDoubleObject(TAG_MAX_APERTURE);
     435        if (aperture == null)
    506436            return null;
    507437        double fStop = PhotographicConversions.apertureToFStop(aperture);
     
    512442    public String getApertureValueDescription()
    513443    {
    514         Double aperture = _directory.getDoubleObject(ExifSubIFDDirectory.TAG_APERTURE);
    515         if (aperture==null)
     444        Double aperture = _directory.getDoubleObject(TAG_APERTURE);
     445        if (aperture == null)
    516446            return null;
    517447        double fStop = PhotographicConversions.apertureToFStop(aperture);
     
    522452    public String getExposureProgramDescription()
    523453    {
    524         // '1' means manual control, '2' program normal, '3' aperture priority,
    525         // '4' shutter priority, '5' program creative (slow program),
    526         // '6' program action(high-speed program), '7' portrait mode, '8' landscape mode.
    527         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXPOSURE_PROGRAM);
    528         if (value==null)
    529             return null;
    530         switch (value) {
    531             case 1: return "Manual control";
    532             case 2: return "Program normal";
    533             case 3: return "Aperture priority";
    534             case 4: return "Shutter priority";
    535             case 5: return "Program creative (slow program)";
    536             case 6: return "Program action (high-speed program)";
    537             case 7: return "Portrait mode";
    538             case 8: return "Landscape mode";
    539             default:
    540                 return "Unknown program (" + value + ")";
    541         }
     454        return getIndexedDescription(TAG_EXPOSURE_PROGRAM,
     455            1,
     456            "Manual control",
     457            "Program normal",
     458            "Aperture priority",
     459            "Shutter priority",
     460            "Program creative (slow program)",
     461            "Program action (high-speed program)",
     462            "Portrait mode",
     463            "Landscape mode"
     464        );
    542465    }
    543466
     
    545468    public String getYCbCrSubsamplingDescription()
    546469    {
    547         int[] positions = _directory.getIntArray(ExifSubIFDDirectory.TAG_YCBCR_SUBSAMPLING);
    548         if (positions==null)
     470        int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING);
     471        if (positions == null)
    549472            return null;
    550473        if (positions[0] == 2 && positions[1] == 1) {
     
    564487        // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr
    565488        // plane format.
    566         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_PLANAR_CONFIGURATION);
    567         if (value==null)
    568             return null;
    569         switch (value) {
    570             case 1: return "Chunky (contiguous for each subsampling pixel)";
    571             case 2: return "Separate (Y-plane/Cb-plane/Cr-plane format)";
    572             default:
    573                 return "Unknown configuration";
    574         }
     489        return getIndexedDescription(TAG_PLANAR_CONFIGURATION,
     490            1,
     491            "Chunky (contiguous for each subsampling pixel)",
     492            "Separate (Y-plane/Cb-plane/Cr-plane format)"
     493        );
    575494    }
    576495
     
    578497    public String getSamplesPerPixelDescription()
    579498    {
    580         String value = _directory.getString(ExifSubIFDDirectory.TAG_SAMPLES_PER_PIXEL);
    581         return value==null ? null : value + " samples/pixel";
     499        String value = _directory.getString(TAG_SAMPLES_PER_PIXEL);
     500        return value == null ? null : value + " samples/pixel";
    582501    }
    583502
     
    585504    public String getRowsPerStripDescription()
    586505    {
    587         final String value = _directory.getString(ExifSubIFDDirectory.TAG_ROWS_PER_STRIP);
    588         return value==null ? null : value + " rows/strip";
     506        final String value = _directory.getString(TAG_ROWS_PER_STRIP);
     507        return value == null ? null : value + " rows/strip";
    589508    }
    590509
     
    592511    public String getStripByteCountsDescription()
    593512    {
    594         final String value = _directory.getString(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
    595         return value==null ? null : value + " bytes";
     513        final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS);
     514        return value == null ? null : value + " bytes";
    596515    }
    597516
     
    600519    {
    601520        // Shows the color space of the image data components
    602         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION);
    603         if (value==null)
     521        Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION);
     522        if (value == null)
    604523            return null;
    605524        switch (value) {
     
    626545    public String getBitsPerSampleDescription()
    627546    {
    628         String value = _directory.getString(ExifSubIFDDirectory.TAG_BITS_PER_SAMPLE);
    629         return value==null ? null : value + " bits/component/pixel";
     547        String value = _directory.getString(TAG_BITS_PER_SAMPLE);
     548        return value == null ? null : value + " bits/component/pixel";
    630549    }
    631550
     
    633552    public String getFocalPlaneXResolutionDescription()
    634553    {
    635         Rational rational = _directory.getRational(ExifSubIFDDirectory.TAG_FOCAL_PLANE_X_RES);
    636         if (rational==null)
     554        Rational rational = _directory.getRational(TAG_FOCAL_PLANE_X_RESOLUTION);
     555        if (rational == null)
    637556            return null;
    638557        final String unit = getFocalPlaneResolutionUnitDescription();
    639558        return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
    640             + (unit==null ? "" : " " + unit.toLowerCase());
     559            + (unit == null ? "" : " " + unit.toLowerCase());
    641560    }
    642561
     
    644563    public String getFocalPlaneYResolutionDescription()
    645564    {
    646         Rational rational = _directory.getRational(ExifSubIFDDirectory.TAG_FOCAL_PLANE_Y_RES);
    647         if (rational==null)
     565        Rational rational = _directory.getRational(TAG_FOCAL_PLANE_Y_RESOLUTION);
     566        if (rational == null)
    648567            return null;
    649568        final String unit = getFocalPlaneResolutionUnitDescription();
    650569        return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
    651             + (unit==null ? "" : " " + unit.toLowerCase());
     570            + (unit == null ? "" : " " + unit.toLowerCase());
    652571    }
    653572
     
    655574    public String getFocalPlaneResolutionUnitDescription()
    656575    {
    657         // Unit of FocalPlaneXResolution/FocalPlaneYResolution. '1' means no-unit,
    658         // '2' inch, '3' centimeter.
    659         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FOCAL_PLANE_UNIT);
    660         if (value==null)
    661             return null;
    662         switch (value) {
    663             case 1: return "(No unit)";
    664             case 2: return "Inches";
    665             case 3: return "cm";
    666             default:
    667                 return "";
    668         }
     576        // Unit of FocalPlaneXResolution/FocalPlaneYResolution.
     577        // '1' means no-unit, '2' inch, '3' centimeter.
     578        return getIndexedDescription(TAG_FOCAL_PLANE_RESOLUTION_UNIT,
     579            1,
     580            "(No unit)",
     581            "Inches",
     582            "cm"
     583        );
    669584    }
    670585
     
    672587    public String getExifImageWidthDescription()
    673588    {
    674         final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH);
    675         if (value==null)
    676             return null;
    677         return value + " pixels";
     589        final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_WIDTH);
     590        return value == null ? null : value + " pixels";
    678591    }
    679592
     
    681594    public String getExifImageHeightDescription()
    682595    {
    683         final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT);
    684         if (value==null)
    685             return null;
    686         return value + " pixels";
     596        final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_HEIGHT);
     597        return value == null ? null : value + " pixels";
    687598    }
    688599
     
    690601    public String getColorSpaceDescription()
    691602    {
    692         final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_COLOR_SPACE);
    693         if (value==null)
     603        final Integer value = _directory.getInteger(TAG_COLOR_SPACE);
     604        if (value == null)
    694605            return null;
    695606        if (value == 1)
     
    697608        if (value == 65535)
    698609            return "Undefined";
    699         return "Unknown";
     610        return "Unknown (" + value + ")";
    700611    }
    701612
     
    703614    public String getFocalLengthDescription()
    704615    {
    705         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_FOCAL_LENGTH);
    706         if (value==null)
     616        Rational value = _directory.getRational(TAG_FOCAL_LENGTH);
     617        if (value == null)
    707618            return null;
    708619        java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
     
    714625    {
    715626        /*
    716          * This is a bitmask.
     627         * This is a bit mask.
    717628         * 0 = flash fired
    718629         * 1 = return detected
     
    724635         */
    725636
    726         final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FLASH);
    727 
    728         if (value==null)
     637        final Integer value = _directory.getInteger(TAG_FLASH);
     638
     639        if (value == null)
    729640            return null;
    730641
    731642        StringBuilder sb = new StringBuilder();
    732643
    733         if ((value & 0x1)!=0)
     644        if ((value & 0x1) != 0)
    734645            sb.append("Flash fired");
    735646        else
     
    737648
    738649        // check if we're able to detect a return, before we mention it
    739         if ((value & 0x4)!=0)
    740         {
    741             if ((value & 0x2)!=0)
     650        if ((value & 0x4) != 0) {
     651            if ((value & 0x2) != 0)
    742652                sb.append(", return detected");
    743653            else
     
    745655        }
    746656
    747         if ((value & 0x10)!=0)
     657        if ((value & 0x10) != 0)
    748658            sb.append(", auto");
    749659
    750         if ((value & 0x40)!=0)
     660        if ((value & 0x40) != 0)
    751661            sb.append(", red-eye reduction");
    752662
     
    760670        // '17' standard light A, '18' standard light B, '19' standard light C, '20' D55,
    761671        // '21' D65, '22' D75, '255' other.
    762         final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_WHITE_BALANCE);
    763         if (value==null)
     672        final Integer value = _directory.getInteger(TAG_WHITE_BALANCE);
     673        if (value == null)
    764674            return null;
    765675        switch (value) {
     
    786696        // '0' means unknown, '1' average, '2' center weighted average, '3' spot
    787697        // '4' multi-spot, '5' multi-segment, '6' partial, '255' other
    788         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_METERING_MODE);
    789         if (value==null)
     698        Integer value = _directory.getInteger(TAG_METERING_MODE);
     699        if (value == null)
    790700            return null;
    791701        switch (value) {
     
    806716    public String getSubjectDistanceDescription()
    807717    {
    808         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE);
    809         if (value==null)
     718        Rational value = _directory.getRational(TAG_SUBJECT_DISTANCE);
     719        if (value == null)
    810720            return null;
    811721        java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
     
    816726    public String getCompressedAverageBitsPerPixelDescription()
    817727    {
    818         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL);
    819         if (value==null)
     728        Rational value = _directory.getRational(TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL);
     729        if (value == null)
    820730            return null;
    821731        String ratio = value.toSimpleString(_allowDecimalRepresentationOfRationals);
    822         if (value.isInteger() && value.intValue() == 1) {
    823             return ratio + " bit/pixel";
    824         } else {
    825             return ratio + " bits/pixel";
    826         }
     732        return value.isInteger() && value.intValue() == 1
     733            ? ratio + " bit/pixel"
     734            : ratio + " bits/pixel";
    827735    }
    828736
     
    830738    public String getExposureTimeDescription()
    831739    {
    832         String value = _directory.getString(ExifSubIFDDirectory.TAG_EXPOSURE_TIME);
    833         return value==null ? null : value + " sec";
     740        String value = _directory.getString(TAG_EXPOSURE_TIME);
     741        return value == null ? null : value + " sec";
    834742    }
    835743
     
    847755        // description (spotted bug using a Canon EOS 300D)
    848756        // thanks also to Gli Blr for spotting this bug
    849         Float apexValue = _directory.getFloatObject(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
    850         if (apexValue==null)
    851             return null;
    852         if (apexValue<=1) {
    853             float apexPower = (float)(1/(Math.exp(apexValue*Math.log(2))));
     757        Float apexValue = _directory.getFloatObject(TAG_SHUTTER_SPEED);
     758        if (apexValue == null)
     759            return null;
     760        if (apexValue <= 1) {
     761            float apexPower = (float)(1 / (Math.exp(apexValue * Math.log(2))));
    854762            long apexPower10 = Math.round((double)apexPower * 10.0);
    855             float fApexPower = (float) apexPower10 / 10.0f;
     763            float fApexPower = (float)apexPower10 / 10.0f;
    856764            return fApexPower + " sec";
    857765        } else {
    858             int apexPower = (int)((Math.exp(apexValue*Math.log(2))));
     766            int apexPower = (int)((Math.exp(apexValue * Math.log(2))));
    859767            return "1/" + apexPower + " sec";
    860768        }
     
    879787        return sb.toString();
    880788*/
    881 
    882789    }
    883790
     
    885792    public String getFNumberDescription()
    886793    {
    887         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_FNUMBER);
    888         if (value==null)
     794        Rational value = _directory.getRational(TAG_FNUMBER);
     795        if (value == null)
    889796            return null;
    890797        return "F" + SimpleDecimalFormatter.format(value.doubleValue());
     
    897804        // '4' Three-chip color area sensor, '5' Color sequential area sensor
    898805        // '7' Trilinear sensor '8' Color sequential linear sensor,  'Other' reserved
    899         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SENSING_METHOD);
    900         if (value==null)
    901             return null;
    902         switch (value) {
    903             case 1: return "(Not defined)";
    904             case 2: return "One-chip color area sensor";
    905             case 3: return "Two-chip color area sensor";
    906             case 4: return "Three-chip color area sensor";
    907             case 5: return "Color sequential area sensor";
    908             case 7: return "Trilinear sensor";
    909             case 8: return "Color sequential linear sensor";
    910             default:
    911                 return "";
    912         }
     806        return getIndexedDescription(TAG_SENSING_METHOD,
     807            1,
     808            "(Not defined)",
     809            "One-chip color area sensor",
     810            "Two-chip color area sensor",
     811            "Three-chip color area sensor",
     812            "Color sequential area sensor",
     813            null,
     814            "Trilinear sensor",
     815            "Color sequential linear sensor"
     816        );
    913817    }
    914818
     
    916820    public String getComponentConfigurationDescription()
    917821    {
    918         int[] components = _directory.getIntArray(ExifSubIFDDirectory.TAG_COMPONENTS_CONFIGURATION);
    919         if (components==null)
     822        int[] components = _directory.getIntArray(TAG_COMPONENTS_CONFIGURATION);
     823        if (components == null)
    920824            return null;
    921825        String[] componentStrings = {"", "Y", "Cb", "Cr", "R", "G", "B"};
  • trunk/src/com/drew/metadata/exif/ExifSubIFDDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    2929 * Describes Exif tags from the SubIFD directory.
    3030 *
    31  * @author Drew Noakes http://drewnoakes.com
     31 * @author Drew Noakes https://drewnoakes.com
    3232 */
    3333public class ExifSubIFDDirectory extends Directory
     
    133133    /**
    134134     * Indicates the Opto-Electric Conversion Function (OECF) specified in ISO 14524.
    135      * <p/>
     135     * <p>
    136136     * OECF is the relationship between the camera optical input and the image values.
    137      * <p/>
     137     * <p>
    138138     * The values are:
    139139     * <ul>
     
    260260     */
    261261    public static final int TAG_FOCAL_LENGTH = 0x920A;
     262
     263    /**
     264     * This tag holds the Exif Makernote. Makernotes are free to be in any format, though they are often IFDs.
     265     * To determine the format, we consider the starting bytes of the makernote itself and sometimes the
     266     * camera model and make.
     267     * <p>
     268     * The component count for this tag includes all of the bytes needed for the makernote.
     269     */
     270    public static final int TAG_MAKERNOTE = 0x927C;
     271
    262272    public static final int TAG_USER_COMMENT = 0x9286;
     273
    263274    public static final int TAG_SUBSECOND_TIME = 0x9290;
    264275    public static final int TAG_SUBSECOND_TIME_ORIGINAL = 0x9291;
    265276    public static final int TAG_SUBSECOND_TIME_DIGITIZED = 0x9292;
     277
    266278    public static final int TAG_FLASHPIX_VERSION = 0xA000;
    267279    /**
     
    274286    public static final int TAG_EXIF_IMAGE_HEIGHT = 0xA003;
    275287    public static final int TAG_RELATED_SOUND_FILE = 0xA004;
    276     public static final int TAG_FOCAL_PLANE_X_RES = 0xA20E;
    277     public static final int TAG_FOCAL_PLANE_Y_RES = 0xA20F;
     288
     289    /** This tag is a pointer to the Exif Interop IFD. */
     290    public static final int TAG_INTEROP_OFFSET = 0xA005;
     291
     292    public static final int TAG_FOCAL_PLANE_X_RESOLUTION = 0xA20E;
     293    public static final int TAG_FOCAL_PLANE_Y_RESOLUTION = 0xA20F;
    278294    /**
    279295     * Unit of FocalPlaneXResolution/FocalPlaneYResolution. '1' means no-unit,
     
    285301     * been changed to use value '2' but it doesn't match to actual value also.
    286302     */
    287     public static final int TAG_FOCAL_PLANE_UNIT = 0xA210;
     303    public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT = 0xA210;
    288304    public static final int TAG_EXPOSURE_INDEX = 0xA215;
    289305    public static final int TAG_SENSING_METHOD = 0xA217;
     
    512528        _tagNameMap.put(0x0200, "JPEG Proc");
    513529        _tagNameMap.put(TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL, "Compressed Bits Per Pixel");
    514         _tagNameMap.put(0x927C, "Maker Note");
    515         _tagNameMap.put(0xA005, "Interoperability Offset");
     530        _tagNameMap.put(TAG_MAKERNOTE, "Makernote");
     531        _tagNameMap.put(TAG_INTEROP_OFFSET, "Interoperability Offset");
    516532
    517533        _tagNameMap.put(TAG_NEW_SUBFILE_TYPE, "New Subfile Type");
     
    586602        _tagNameMap.put(TAG_SPATIAL_FREQ_RESPONSE_2, "Spatial Frequency Response");
    587603        // 0x920E in TIFF/EP
    588         _tagNameMap.put(TAG_FOCAL_PLANE_X_RES, "Focal Plane X Resolution");
     604        _tagNameMap.put(TAG_FOCAL_PLANE_X_RESOLUTION, "Focal Plane X Resolution");
    589605        // 0x920F in TIFF/EP
    590         _tagNameMap.put(TAG_FOCAL_PLANE_Y_RES, "Focal Plane Y Resolution");
     606        _tagNameMap.put(TAG_FOCAL_PLANE_Y_RESOLUTION, "Focal Plane Y Resolution");
    591607        // 0x9210 in TIFF/EP
    592         _tagNameMap.put(TAG_FOCAL_PLANE_UNIT, "Focal Plane Resolution Unit");
     608        _tagNameMap.put(TAG_FOCAL_PLANE_RESOLUTION_UNIT, "Focal Plane Resolution Unit");
    593609        // 0x9214 in TIFF/EP
    594610        _tagNameMap.put(TAG_SUBJECT_LOCATION_2, "Subject Location");
     
    614630        _tagNameMap.put(TAG_SUBJECT_DISTANCE_RANGE, "Subject Distance Range");
    615631        _tagNameMap.put(TAG_IMAGE_UNIQUE_ID, "Unique Image ID");
    616        
     632
    617633        _tagNameMap.put(TAG_CAMERA_OWNER_NAME, "Camera Owner Name");
    618634        _tagNameMap.put(TAG_BODY_SERIAL_NUMBER, "Body Serial Number");
     
    634650    }
    635651
     652    @Override
    636653    @NotNull
    637654    public String getName()
     
    640657    }
    641658
     659    @Override
    642660    @NotNull
    643661    protected HashMap<Integer, String> getTagNameMap()
  • trunk/src/com/drew/metadata/exif/ExifThumbnailDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121
     
    2727import com.drew.metadata.TagDescriptor;
    2828
     29import static com.drew.metadata.exif.ExifThumbnailDirectory.*;
     30
    2931/**
    30  * Provides human-readable string representations of tag values stored in a <code>ExifThumbnailDirectory</code>.
    31  *
    32  * @author Drew Noakes http://drewnoakes.com
     32 * Provides human-readable string representations of tag values stored in a {@link ExifThumbnailDirectory}.
     33 *
     34 * @author Drew Noakes https://drewnoakes.com
    3335 */
    3436public class ExifThumbnailDescriptor extends TagDescriptor<ExifThumbnailDirectory>
     
    5254
    5355    /**
    54      * Returns a descriptive value of the the specified tag for this image.
     56     * Returns a descriptive value of the specified tag for this image.
    5557     * Where possible, known values will be substituted here in place of the raw
    5658     * tokens actually kept in the Exif segment.  If no substitution is
    5759     * available, the value provided by getString(int) will be returned.
     60     *
    5861     * @param tagType the tag to find a description for
    5962     * @return a description of the image's value for the specified tag, or
    6063     *         <code>null</code> if the tag hasn't been defined.
    6164     */
     65    @Override
    6266    @Nullable
    6367    public String getDescription(int tagType)
    6468    {
    6569        switch (tagType) {
    66             case ExifThumbnailDirectory.TAG_ORIENTATION:
     70            case TAG_ORIENTATION:
    6771                return getOrientationDescription();
    68             case ExifThumbnailDirectory.TAG_RESOLUTION_UNIT:
     72            case TAG_RESOLUTION_UNIT:
    6973                return getResolutionDescription();
    70             case ExifThumbnailDirectory.TAG_YCBCR_POSITIONING:
     74            case TAG_YCBCR_POSITIONING:
    7175                return getYCbCrPositioningDescription();
    72             case ExifThumbnailDirectory.TAG_X_RESOLUTION:
     76            case TAG_X_RESOLUTION:
    7377                return getXResolutionDescription();
    74             case ExifThumbnailDirectory.TAG_Y_RESOLUTION:
     78            case TAG_Y_RESOLUTION:
    7579                return getYResolutionDescription();
    76             case ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET:
     80            case TAG_THUMBNAIL_OFFSET:
    7781                return getThumbnailOffsetDescription();
    78             case ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH:
     82            case TAG_THUMBNAIL_LENGTH:
    7983                return getThumbnailLengthDescription();
    80             case ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_WIDTH:
     84            case TAG_THUMBNAIL_IMAGE_WIDTH:
    8185                return getThumbnailImageWidthDescription();
    82             case ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT:
     86            case TAG_THUMBNAIL_IMAGE_HEIGHT:
    8387                return getThumbnailImageHeightDescription();
    84             case ExifThumbnailDirectory.TAG_BITS_PER_SAMPLE:
     88            case TAG_BITS_PER_SAMPLE:
    8589                return getBitsPerSampleDescription();
    86             case ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION:
     90            case TAG_THUMBNAIL_COMPRESSION:
    8791                return getCompressionDescription();
    88             case ExifThumbnailDirectory.TAG_PHOTOMETRIC_INTERPRETATION:
     92            case TAG_PHOTOMETRIC_INTERPRETATION:
    8993                return getPhotometricInterpretationDescription();
    90             case ExifThumbnailDirectory.TAG_ROWS_PER_STRIP:
     94            case TAG_ROWS_PER_STRIP:
    9195                return getRowsPerStripDescription();
    92             case ExifThumbnailDirectory.TAG_STRIP_BYTE_COUNTS:
     96            case TAG_STRIP_BYTE_COUNTS:
    9397                return getStripByteCountsDescription();
    94             case ExifThumbnailDirectory.TAG_SAMPLES_PER_PIXEL:
     98            case TAG_SAMPLES_PER_PIXEL:
    9599                return getSamplesPerPixelDescription();
    96             case ExifThumbnailDirectory.TAG_PLANAR_CONFIGURATION:
     100            case TAG_PLANAR_CONFIGURATION:
    97101                return getPlanarConfigurationDescription();
    98             case ExifThumbnailDirectory.TAG_YCBCR_SUBSAMPLING:
     102            case TAG_YCBCR_SUBSAMPLING:
    99103                return getYCbCrSubsamplingDescription();
    100             case ExifThumbnailDirectory.TAG_REFERENCE_BLACK_WHITE:
     104            case TAG_REFERENCE_BLACK_WHITE:
    101105                return getReferenceBlackWhiteDescription();
    102106            default:
     
    108112    public String getReferenceBlackWhiteDescription()
    109113    {
    110         int[] ints = _directory.getIntArray(ExifThumbnailDirectory.TAG_REFERENCE_BLACK_WHITE);
    111         if (ints==null)
     114        int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE);
     115        if (ints == null || ints.length < 6)
    112116            return null;
    113117        int blackR = ints[0];
     
    117121        int blackB = ints[4];
    118122        int whiteB = ints[5];
    119         return "[" + blackR + "," + blackG + "," + blackB + "] " +
    120                "[" + whiteR + "," + whiteG + "," + whiteB + "]";
     123        return String.format("[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB);
    121124    }
    122125
     
    124127    public String getYCbCrSubsamplingDescription()
    125128    {
    126         int[] positions = _directory.getIntArray(ExifThumbnailDirectory.TAG_YCBCR_SUBSAMPLING);
    127         if (positions==null || positions.length < 2)
     129        int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING);
     130        if (positions == null || positions.length < 2)
    128131            return null;
    129132        if (positions[0] == 2 && positions[1] == 1) {
     
    143146        // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr
    144147        // plane format.
    145         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_PLANAR_CONFIGURATION);
    146         if (value==null)
    147             return null;
    148         switch (value) {
    149             case 1: return "Chunky (contiguous for each subsampling pixel)";
    150             case 2: return "Separate (Y-plane/Cb-plane/Cr-plane format)";
    151             default:
    152                 return "Unknown configuration";
    153         }
     148        return getIndexedDescription(TAG_PLANAR_CONFIGURATION,
     149            1,
     150            "Chunky (contiguous for each subsampling pixel)",
     151            "Separate (Y-plane/Cb-plane/Cr-plane format)"
     152        );
    154153    }
    155154
     
    157156    public String getSamplesPerPixelDescription()
    158157    {
    159         String value = _directory.getString(ExifThumbnailDirectory.TAG_SAMPLES_PER_PIXEL);
    160         return value==null ? null : value + " samples/pixel";
     158        String value = _directory.getString(TAG_SAMPLES_PER_PIXEL);
     159        return value == null ? null : value + " samples/pixel";
    161160    }
    162161
     
    164163    public String getRowsPerStripDescription()
    165164    {
    166         final String value = _directory.getString(ExifThumbnailDirectory.TAG_ROWS_PER_STRIP);
    167         return value==null ? null : value + " rows/strip";
     165        final String value = _directory.getString(TAG_ROWS_PER_STRIP);
     166        return value == null ? null : value + " rows/strip";
    168167    }
    169168
     
    171170    public String getStripByteCountsDescription()
    172171    {
    173         final String value = _directory.getString(ExifThumbnailDirectory.TAG_STRIP_BYTE_COUNTS);
    174         return value==null ? null : value + " bytes";
     172        final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS);
     173        return value == null ? null : value + " bytes";
    175174    }
    176175
     
    179178    {
    180179        // Shows the color space of the image data components
    181         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_PHOTOMETRIC_INTERPRETATION);
    182         if (value==null)
     180        Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION);
     181        if (value == null)
    183182            return null;
    184183        switch (value) {
     
    205204    public String getCompressionDescription()
    206205    {
    207         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION);
    208         if (value==null)
     206        Integer value = _directory.getInteger(TAG_THUMBNAIL_COMPRESSION);
     207        if (value == null)
    209208            return null;
    210209        switch (value) {
     
    244243    public String getBitsPerSampleDescription()
    245244    {
    246         String value = _directory.getString(ExifThumbnailDirectory.TAG_BITS_PER_SAMPLE);
    247         return value==null ? null : value + " bits/component/pixel";
     245        String value = _directory.getString(TAG_BITS_PER_SAMPLE);
     246        return value == null ? null : value + " bits/component/pixel";
    248247    }
    249248
     
    251250    public String getThumbnailImageWidthDescription()
    252251    {
    253         String value = _directory.getString(ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
    254         return value==null ? null : value + " pixels";
     252        String value = _directory.getString(TAG_THUMBNAIL_IMAGE_WIDTH);
     253        return value == null ? null : value + " pixels";
    255254    }
    256255
     
    258257    public String getThumbnailImageHeightDescription()
    259258    {
    260         String value = _directory.getString(ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
    261         return value==null ? null : value + " pixels";
     259        String value = _directory.getString(TAG_THUMBNAIL_IMAGE_HEIGHT);
     260        return value == null ? null : value + " pixels";
    262261    }
    263262
     
    265264    public String getThumbnailLengthDescription()
    266265    {
    267         String value = _directory.getString(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
    268         return value==null ? null : value + " bytes";
     266        String value = _directory.getString(TAG_THUMBNAIL_LENGTH);
     267        return value == null ? null : value + " bytes";
    269268    }
    270269
     
    272271    public String getThumbnailOffsetDescription()
    273272    {
    274         String value = _directory.getString(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
    275         return value==null ? null : value + " bytes";
     273        String value = _directory.getString(TAG_THUMBNAIL_OFFSET);
     274        return value == null ? null : value + " bytes";
    276275    }
    277276
     
    279278    public String getYResolutionDescription()
    280279    {
    281         Rational value = _directory.getRational(ExifThumbnailDirectory.TAG_Y_RESOLUTION);
    282         if (value==null)
     280        Rational value = _directory.getRational(TAG_Y_RESOLUTION);
     281        if (value == null)
    283282            return null;
    284283        final String unit = getResolutionDescription();
    285284        return value.toSimpleString(_allowDecimalRepresentationOfRationals) +
    286                 " dots per " +
    287                 (unit==null ? "unit" : unit.toLowerCase());
     285            " dots per " +
     286            (unit == null ? "unit" : unit.toLowerCase());
    288287    }
    289288
     
    291290    public String getXResolutionDescription()
    292291    {
    293         Rational value = _directory.getRational(ExifThumbnailDirectory.TAG_X_RESOLUTION);
    294         if (value==null)
     292        Rational value = _directory.getRational(TAG_X_RESOLUTION);
     293        if (value == null)
    295294            return null;
    296295        final String unit = getResolutionDescription();
    297296        return value.toSimpleString(_allowDecimalRepresentationOfRationals) +
    298                 " dots per " +
    299                 (unit==null ? "unit" : unit.toLowerCase());
     297            " dots per " +
     298            (unit == null ? "unit" : unit.toLowerCase());
    300299    }
    301300
     
    303302    public String getYCbCrPositioningDescription()
    304303    {
    305         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_YCBCR_POSITIONING);
    306         if (value==null)
    307             return null;
    308         switch (value) {
    309             case 1: return "Center of pixel array";
    310             case 2: return "Datum point";
    311             default:
    312                 return String.valueOf(value);
    313         }
     304        return getIndexedDescription(TAG_YCBCR_POSITIONING, 1, "Center of pixel array", "Datum point");
    314305    }
    315306
     
    317308    public String getOrientationDescription()
    318309    {
    319         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_ORIENTATION);
    320         if (value==null)
    321             return null;
    322         switch (value) {
    323             case 1: return "Top, left side (Horizontal / normal)";
    324             case 2: return "Top, right side (Mirror horizontal)";
    325             case 3: return "Bottom, right side (Rotate 180)";
    326             case 4: return "Bottom, left side (Mirror vertical)";
    327             case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)";
    328             case 6: return "Right side, top (Rotate 90 CW)";
    329             case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)";
    330             case 8: return "Left side, bottom (Rotate 270 CW)";
    331             default:
    332                 return String.valueOf(value);
    333         }
     310        return getIndexedDescription(TAG_ORIENTATION, 1,
     311            "Top, left side (Horizontal / normal)",
     312            "Top, right side (Mirror horizontal)",
     313            "Bottom, right side (Rotate 180)",
     314            "Bottom, left side (Mirror vertical)",
     315            "Left side, top (Mirror horizontal and rotate 270 CW)",
     316            "Right side, top (Rotate 90 CW)",
     317            "Right side, bottom (Mirror horizontal and rotate 90 CW)",
     318            "Left side, bottom (Rotate 270 CW)");
    334319    }
    335320
     
    338323    {
    339324        // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)
    340         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_RESOLUTION_UNIT);
    341         if (value==null)
    342             return null;
    343         switch (value) {
    344             case 1: return "(No unit)";
    345             case 2: return "Inch";
    346             case 3: return "cm";
    347             default:
    348                 return "";
    349         }
     325        return getIndexedDescription(TAG_RESOLUTION_UNIT, 1, "(No unit)", "Inch", "cm");
    350326    }
    351327}
  • trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121
     
    3434 * One of several Exif directories.  Otherwise known as IFD1, this directory holds information about an embedded thumbnail image.
    3535 *
    36  * @author Drew Noakes http://drewnoakes.com
     36 * @author Drew Noakes https://drewnoakes.com
    3737 */
    3838public class ExifThumbnailDirectory extends Directory
     
    5757     * 7 = JPEG
    5858     * 8 = Adobe Deflate
    59      * 9 = JBIG B&W
     59     * 9 = JBIG B&amp;W
    6060     * 10 = JBIG Color
    6161     * 32766 = Next
     
    9898    public static final int TAG_PHOTOMETRIC_INTERPRETATION = 0x0106;
    9999
    100     /** The position in the file of raster data. */
     100    /**
     101     * The position in the file of raster data.
     102     */
    101103    public static final int TAG_STRIP_OFFSETS = 0x0111;
    102104    public static final int TAG_ORIENTATION = 0x0112;
    103     /** Each pixel is composed of this many samples. */
     105    /**
     106     * Each pixel is composed of this many samples.
     107     */
    104108    public static final int TAG_SAMPLES_PER_PIXEL = 0x0115;
    105     /** The raster is codified by a single block of data holding this many rows. */
     109    /**
     110     * The raster is codified by a single block of data holding this many rows.
     111     */
    106112    public static final int TAG_ROWS_PER_STRIP = 0x116;
    107     /** The size of the raster data in bytes. */
     113    /**
     114     * The size of the raster data in bytes.
     115     */
    108116    public static final int TAG_STRIP_BYTE_COUNTS = 0x0117;
    109117    /**
     
    117125    public static final int TAG_PLANAR_CONFIGURATION = 0x011C;
    118126    public static final int TAG_RESOLUTION_UNIT = 0x0128;
    119     /** The offset to thumbnail image bytes. */
     127    /**
     128     * The offset to thumbnail image bytes.
     129     */
    120130    public static final int TAG_THUMBNAIL_OFFSET = 0x0201;
    121     /** The size of the thumbnail image data in bytes. */
     131    /**
     132     * The size of the thumbnail image data in bytes.
     133     */
    122134    public static final int TAG_THUMBNAIL_LENGTH = 0x0202;
    123135    public static final int TAG_YCBCR_COEFFICIENTS = 0x0211;
     
    129141    protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
    130142
    131     static
    132     {
     143    static {
    133144        _tagNameMap.put(TAG_THUMBNAIL_IMAGE_WIDTH, "Thumbnail Image Width");
    134145        _tagNameMap.put(TAG_THUMBNAIL_IMAGE_HEIGHT, "Thumbnail Image Height");
     
    161172    }
    162173
     174    @Override
    163175    @NotNull
    164176    public String getName()
     
    167179    }
    168180
     181    @Override
    169182    @NotNull
    170183    protected HashMap<Integer, String> getTagNameMap()
     
    193206        byte[] data = _thumbnailData;
    194207
    195         if (data==null)
     208        if (data == null)
    196209            throw new MetadataException("No thumbnail data exists.");
    197210
     
    201214            stream.write(data);
    202215        } finally {
    203             if (stream!=null)
     216            if (stream != null)
    204217                stream.close();
    205218        }
  • trunk/src/com/drew/metadata/exif/GpsDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    2929import java.text.DecimalFormat;
    3030
     31import static com.drew.metadata.exif.GpsDirectory.*;
     32
    3133/**
    32  * Provides human-readable string representations of tag values stored in a <code>GpsDirectory</code>.
    33  *
    34  * @author Drew Noakes http://drewnoakes.com
     34 * Provides human-readable string representations of tag values stored in a {@link GpsDirectory}.
     35 *
     36 * @author Drew Noakes https://drewnoakes.com
    3537 */
    3638public class GpsDescriptor extends TagDescriptor<GpsDirectory>
     
    4143    }
    4244
     45    @Override
    4346    @Nullable
    4447    public String getDescription(int tagType)
    4548    {
    4649        switch (tagType) {
    47             case GpsDirectory.TAG_GPS_VERSION_ID:
     50            case TAG_VERSION_ID:
    4851                return getGpsVersionIdDescription();
    49             case GpsDirectory.TAG_GPS_ALTITUDE:
     52            case TAG_ALTITUDE:
    5053                return getGpsAltitudeDescription();
    51             case GpsDirectory.TAG_GPS_ALTITUDE_REF:
     54            case TAG_ALTITUDE_REF:
    5255                return getGpsAltitudeRefDescription();
    53             case GpsDirectory.TAG_GPS_STATUS:
     56            case TAG_STATUS:
    5457                return getGpsStatusDescription();
    55             case GpsDirectory.TAG_GPS_MEASURE_MODE:
     58            case TAG_MEASURE_MODE:
    5659                return getGpsMeasureModeDescription();
    57             case GpsDirectory.TAG_GPS_SPEED_REF:
     60            case TAG_SPEED_REF:
    5861                return getGpsSpeedRefDescription();
    59             case GpsDirectory.TAG_GPS_TRACK_REF:
    60             case GpsDirectory.TAG_GPS_IMG_DIRECTION_REF:
    61             case GpsDirectory.TAG_GPS_DEST_BEARING_REF:
     62            case TAG_TRACK_REF:
     63            case TAG_IMG_DIRECTION_REF:
     64            case TAG_DEST_BEARING_REF:
    6265                return getGpsDirectionReferenceDescription(tagType);
    63             case GpsDirectory.TAG_GPS_TRACK:
    64             case GpsDirectory.TAG_GPS_IMG_DIRECTION:
    65             case GpsDirectory.TAG_GPS_DEST_BEARING:
     66            case TAG_TRACK:
     67            case TAG_IMG_DIRECTION:
     68            case TAG_DEST_BEARING:
    6669                return getGpsDirectionDescription(tagType);
    67             case GpsDirectory.TAG_GPS_DEST_DISTANCE_REF:
     70            case TAG_DEST_DISTANCE_REF:
    6871                return getGpsDestinationReferenceDescription();
    69             case GpsDirectory.TAG_GPS_TIME_STAMP:
     72            case TAG_TIME_STAMP:
    7073                return getGpsTimeStampDescription();
    71             case GpsDirectory.TAG_GPS_LONGITUDE:
     74            case TAG_LONGITUDE:
    7275                // three rational numbers -- displayed in HH"MM"SS.ss
    7376                return getGpsLongitudeDescription();
    74             case GpsDirectory.TAG_GPS_LATITUDE:
     77            case TAG_LATITUDE:
    7578                // three rational numbers -- displayed in HH"MM"SS.ss
    7679                return getGpsLatitudeDescription();
    77             case GpsDirectory.TAG_GPS_DIFFERENTIAL:
     80            case TAG_DIFFERENTIAL:
    7881                return getGpsDifferentialDescription();
    7982            default:
     
    8588    private String getGpsVersionIdDescription()
    8689    {
    87         return convertBytesToVersionString(_directory.getIntArray(GpsDirectory.TAG_GPS_VERSION_ID), 1);
     90        return getVersionBytesDescription(TAG_VERSION_ID, 1);
    8891    }
    8992
     
    9295    {
    9396        GeoLocation location = _directory.getGeoLocation();
    94 
    95         if (location == null)
    96             return null;
    97 
    98         return GeoLocation.decimalToDegreesMinutesSecondsString(location.getLatitude());
     97        return location == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(location.getLatitude());
    9998    }
    10099
     
    103102    {
    104103        GeoLocation location = _directory.getGeoLocation();
    105 
    106         if (location == null)
    107             return null;
    108 
    109         return GeoLocation.decimalToDegreesMinutesSecondsString(location.getLongitude());
     104        return location == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(location.getLongitude());
    110105    }
    111106
     
    114109    {
    115110        // time in hour, min, sec
    116         int[] timeComponents = _directory.getIntArray(GpsDirectory.TAG_GPS_TIME_STAMP);
    117         if (timeComponents==null)
    118             return null;
    119         StringBuilder description = new StringBuilder();
    120         description.append(timeComponents[0]);
    121         description.append(":");
    122         description.append(timeComponents[1]);
    123         description.append(":");
    124         description.append(timeComponents[2]);
    125         description.append(" UTC");
    126         return description.toString();
     111        int[] timeComponents = _directory.getIntArray(TAG_TIME_STAMP);
     112        return timeComponents == null ? null : String.format("%d:%d:%d UTC", timeComponents[0], timeComponents[1], timeComponents[2]);
    127113    }
    128114
     
    130116    public String getGpsDestinationReferenceDescription()
    131117    {
    132         final String value = _directory.getString(GpsDirectory.TAG_GPS_DEST_DISTANCE_REF);
    133         if (value==null)
     118        final String value = _directory.getString(TAG_DEST_DISTANCE_REF);
     119        if (value == null)
    134120            return null;
    135121        String distanceRef = value.trim();
     
    151137        // provide a decimal version of rational numbers in the description, to avoid strings like "35334/199 degrees"
    152138        String value = angle != null
    153                 ? new DecimalFormat("0.##").format(angle.doubleValue())
    154                 : _directory.getString(tagType);
    155         if (value==null || value.trim().length()==0)
    156             return null;
    157         return value.trim() + " degrees";
     139            ? new DecimalFormat("0.##").format(angle.doubleValue())
     140            : _directory.getString(tagType);
     141        return value == null || value.trim().length() == 0 ? null : value.trim() + " degrees";
    158142    }
    159143
     
    162146    {
    163147        final String value = _directory.getString(tagType);
    164         if (value==null)
     148        if (value == null)
    165149            return null;
    166150        String gpsDistRef = value.trim();
     
    177161    public String getGpsSpeedRefDescription()
    178162    {
    179         final String value = _directory.getString(GpsDirectory.TAG_GPS_SPEED_REF);
    180         if (value==null)
     163        final String value = _directory.getString(TAG_SPEED_REF);
     164        if (value == null)
    181165            return null;
    182166        String gpsSpeedRef = value.trim();
     
    195179    public String getGpsMeasureModeDescription()
    196180    {
    197         final String value = _directory.getString(GpsDirectory.TAG_GPS_MEASURE_MODE);
    198         if (value==null)
     181        final String value = _directory.getString(TAG_MEASURE_MODE);
     182        if (value == null)
    199183            return null;
    200184        String gpsSpeedMeasureMode = value.trim();
     
    211195    public String getGpsStatusDescription()
    212196    {
    213         final String value = _directory.getString(GpsDirectory.TAG_GPS_STATUS);
    214         if (value==null)
     197        final String value = _directory.getString(TAG_STATUS);
     198        if (value == null)
    215199            return null;
    216200        String gpsStatus = value.trim();
     
    227211    public String getGpsAltitudeRefDescription()
    228212    {
    229         Integer value = _directory.getInteger(GpsDirectory.TAG_GPS_ALTITUDE_REF);
    230         if (value==null)
    231             return null;
    232         if (value == 0)
    233             return "Sea level";
    234         if (value == 1)
    235             return "Below sea level";
    236         return "Unknown (" + value + ")";
     213        return getIndexedDescription(TAG_ALTITUDE_REF, "Sea level", "Below sea level");
    237214    }
    238215
     
    240217    public String getGpsAltitudeDescription()
    241218    {
    242         final Rational value = _directory.getRational(GpsDirectory.TAG_GPS_ALTITUDE);
    243         if (value==null)
    244             return null;
    245         return value.intValue() + " metres";
     219        final Rational value = _directory.getRational(TAG_ALTITUDE);
     220        return value == null ? null : value.intValue() + " metres";
    246221    }
    247222
     
    249224    public String getGpsDifferentialDescription()
    250225    {
    251         final Integer value = _directory.getInteger(GpsDirectory.TAG_GPS_DIFFERENTIAL);
    252         if (value==null)
    253             return null;
    254         if (value == 0)
    255             return "No Correction";
    256         if (value == 1)
    257             return "Differential Corrected";
    258         return "Unknown (" + value + ")";
     226        return getIndexedDescription(TAG_DIFFERENTIAL, "No Correction", "Differential Corrected");
    259227    }
    260228
     
    263231    {
    264232        GeoLocation location = _directory.getGeoLocation();
    265 
    266         if (location == null)
    267             return null;
    268 
    269         return location.toDMSString();
     233        return location == null ? null : location.toDMSString();
    270234    }
    271235}
  • trunk/src/com/drew/metadata/exif/GpsDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    3232 * Describes Exif tags that contain Global Positioning System (GPS) data.
    3333 *
    34  * @author Drew Noakes http://drewnoakes.com
     34 * @author Drew Noakes https://drewnoakes.com
    3535 */
    3636public class GpsDirectory extends Directory
    3737{
    3838    /** GPS tag version GPSVersionID 0 0 BYTE 4 */
    39     public static final int TAG_GPS_VERSION_ID = 0x0000;
     39    public static final int TAG_VERSION_ID = 0x0000;
    4040    /** North or South Latitude GPSLatitudeRef 1 1 ASCII 2 */
    41     public static final int TAG_GPS_LATITUDE_REF = 0x0001;
     41    public static final int TAG_LATITUDE_REF = 0x0001;
    4242    /** Latitude GPSLatitude 2 2 RATIONAL 3 */
    43     public static final int TAG_GPS_LATITUDE = 0x0002;
     43    public static final int TAG_LATITUDE = 0x0002;
    4444    /** East or West Longitude GPSLongitudeRef 3 3 ASCII 2 */
    45     public static final int TAG_GPS_LONGITUDE_REF = 0x0003;
     45    public static final int TAG_LONGITUDE_REF = 0x0003;
    4646    /** Longitude GPSLongitude 4 4 RATIONAL 3 */
    47     public static final int TAG_GPS_LONGITUDE = 0x0004;
     47    public static final int TAG_LONGITUDE = 0x0004;
    4848    /** Altitude reference GPSAltitudeRef 5 5 BYTE 1 */
    49     public static final int TAG_GPS_ALTITUDE_REF = 0x0005;
     49    public static final int TAG_ALTITUDE_REF = 0x0005;
    5050    /** Altitude GPSAltitude 6 6 RATIONAL 1 */
    51     public static final int TAG_GPS_ALTITUDE = 0x0006;
     51    public static final int TAG_ALTITUDE = 0x0006;
    5252    /** GPS time (atomic clock) GPSTimeStamp 7 7 RATIONAL 3 */
    53     public static final int TAG_GPS_TIME_STAMP = 0x0007;
     53    public static final int TAG_TIME_STAMP = 0x0007;
    5454    /** GPS satellites used for measurement GPSSatellites 8 8 ASCII Any */
    55     public static final int TAG_GPS_SATELLITES = 0x0008;
     55    public static final int TAG_SATELLITES = 0x0008;
    5656    /** GPS receiver status GPSStatus 9 9 ASCII 2 */
    57     public static final int TAG_GPS_STATUS = 0x0009;
     57    public static final int TAG_STATUS = 0x0009;
    5858    /** GPS measurement mode GPSMeasureMode 10 A ASCII 2 */
    59     public static final int TAG_GPS_MEASURE_MODE = 0x000A;
     59    public static final int TAG_MEASURE_MODE = 0x000A;
    6060    /** Measurement precision GPSDOP 11 B RATIONAL 1 */
    61     public static final int TAG_GPS_DOP = 0x000B;
     61    public static final int TAG_DOP = 0x000B;
    6262    /** Speed unit GPSSpeedRef 12 C ASCII 2 */
    63     public static final int TAG_GPS_SPEED_REF = 0x000C;
     63    public static final int TAG_SPEED_REF = 0x000C;
    6464    /** Speed of GPS receiver GPSSpeed 13 D RATIONAL 1 */
    65     public static final int TAG_GPS_SPEED = 0x000D;
     65    public static final int TAG_SPEED = 0x000D;
    6666    /** Reference for direction of movement GPSTrackRef 14 E ASCII 2 */
    67     public static final int TAG_GPS_TRACK_REF = 0x000E;
     67    public static final int TAG_TRACK_REF = 0x000E;
    6868    /** Direction of movement GPSTrack 15 F RATIONAL 1 */
    69     public static final int TAG_GPS_TRACK = 0x000F;
     69    public static final int TAG_TRACK = 0x000F;
    7070    /** Reference for direction of image GPSImgDirectionRef 16 10 ASCII 2 */
    71     public static final int TAG_GPS_IMG_DIRECTION_REF = 0x0010;
     71    public static final int TAG_IMG_DIRECTION_REF = 0x0010;
    7272    /** Direction of image GPSImgDirection 17 11 RATIONAL 1 */
    73     public static final int TAG_GPS_IMG_DIRECTION = 0x0011;
     73    public static final int TAG_IMG_DIRECTION = 0x0011;
    7474    /** Geodetic survey data used GPSMapDatum 18 12 ASCII Any */
    75     public static final int TAG_GPS_MAP_DATUM = 0x0012;
     75    public static final int TAG_MAP_DATUM = 0x0012;
    7676    /** Reference for latitude of destination GPSDestLatitudeRef 19 13 ASCII 2 */
    77     public static final int TAG_GPS_DEST_LATITUDE_REF = 0x0013;
     77    public static final int TAG_DEST_LATITUDE_REF = 0x0013;
    7878    /** Latitude of destination GPSDestLatitude 20 14 RATIONAL 3 */
    79     public static final int TAG_GPS_DEST_LATITUDE = 0x0014;
     79    public static final int TAG_DEST_LATITUDE = 0x0014;
    8080    /** Reference for longitude of destination GPSDestLongitudeRef 21 15 ASCII 2 */
    81     public static final int TAG_GPS_DEST_LONGITUDE_REF = 0x0015;
     81    public static final int TAG_DEST_LONGITUDE_REF = 0x0015;
    8282    /** Longitude of destination GPSDestLongitude 22 16 RATIONAL 3 */
    83     public static final int TAG_GPS_DEST_LONGITUDE = 0x0016;
     83    public static final int TAG_DEST_LONGITUDE = 0x0016;
    8484    /** Reference for bearing of destination GPSDestBearingRef 23 17 ASCII 2 */
    85     public static final int TAG_GPS_DEST_BEARING_REF = 0x0017;
     85    public static final int TAG_DEST_BEARING_REF = 0x0017;
    8686    /** Bearing of destination GPSDestBearing 24 18 RATIONAL 1 */
    87     public static final int TAG_GPS_DEST_BEARING = 0x0018;
     87    public static final int TAG_DEST_BEARING = 0x0018;
    8888    /** Reference for distance to destination GPSDestDistanceRef 25 19 ASCII 2 */
    89     public static final int TAG_GPS_DEST_DISTANCE_REF = 0x0019;
     89    public static final int TAG_DEST_DISTANCE_REF = 0x0019;
    9090    /** Distance to destination GPSDestDistance 26 1A RATIONAL 1 */
    91     public static final int TAG_GPS_DEST_DISTANCE = 0x001A;
     91    public static final int TAG_DEST_DISTANCE = 0x001A;
    9292
    9393    /** Values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec. */
    94     public static final int TAG_GPS_PROCESSING_METHOD = 0x001B;
    95     public static final int TAG_GPS_AREA_INFORMATION = 0x001C;
    96     public static final int TAG_GPS_DATE_STAMP = 0x001D;
    97     public static final int TAG_GPS_DIFFERENTIAL = 0x001E;
     94    public static final int TAG_PROCESSING_METHOD = 0x001B;
     95    public static final int TAG_AREA_INFORMATION = 0x001C;
     96    public static final int TAG_DATE_STAMP = 0x001D;
     97    public static final int TAG_DIFFERENTIAL = 0x001E;
    9898
    9999    @NotNull
     
    102102    static
    103103    {
    104         _tagNameMap.put(TAG_GPS_VERSION_ID, "GPS Version ID");
    105         _tagNameMap.put(TAG_GPS_LATITUDE_REF, "GPS Latitude Ref");
    106         _tagNameMap.put(TAG_GPS_LATITUDE, "GPS Latitude");
    107         _tagNameMap.put(TAG_GPS_LONGITUDE_REF, "GPS Longitude Ref");
    108         _tagNameMap.put(TAG_GPS_LONGITUDE, "GPS Longitude");
    109         _tagNameMap.put(TAG_GPS_ALTITUDE_REF, "GPS Altitude Ref");
    110         _tagNameMap.put(TAG_GPS_ALTITUDE, "GPS Altitude");
    111         _tagNameMap.put(TAG_GPS_TIME_STAMP, "GPS Time-Stamp");
    112         _tagNameMap.put(TAG_GPS_SATELLITES, "GPS Satellites");
    113         _tagNameMap.put(TAG_GPS_STATUS, "GPS Status");
    114         _tagNameMap.put(TAG_GPS_MEASURE_MODE, "GPS Measure Mode");
    115         _tagNameMap.put(TAG_GPS_DOP, "GPS DOP");
    116         _tagNameMap.put(TAG_GPS_SPEED_REF, "GPS Speed Ref");
    117         _tagNameMap.put(TAG_GPS_SPEED, "GPS Speed");
    118         _tagNameMap.put(TAG_GPS_TRACK_REF, "GPS Track Ref");
    119         _tagNameMap.put(TAG_GPS_TRACK, "GPS Track");
    120         _tagNameMap.put(TAG_GPS_IMG_DIRECTION_REF, "GPS Img Direction Ref");
    121         _tagNameMap.put(TAG_GPS_IMG_DIRECTION, "GPS Img Direction");
    122         _tagNameMap.put(TAG_GPS_MAP_DATUM, "GPS Map Datum");
    123         _tagNameMap.put(TAG_GPS_DEST_LATITUDE_REF, "GPS Dest Latitude Ref");
    124         _tagNameMap.put(TAG_GPS_DEST_LATITUDE, "GPS Dest Latitude");
    125         _tagNameMap.put(TAG_GPS_DEST_LONGITUDE_REF, "GPS Dest Longitude Ref");
    126         _tagNameMap.put(TAG_GPS_DEST_LONGITUDE, "GPS Dest Longitude");
    127         _tagNameMap.put(TAG_GPS_DEST_BEARING_REF, "GPS Dest Bearing Ref");
    128         _tagNameMap.put(TAG_GPS_DEST_BEARING, "GPS Dest Bearing");
    129         _tagNameMap.put(TAG_GPS_DEST_DISTANCE_REF, "GPS Dest Distance Ref");
    130         _tagNameMap.put(TAG_GPS_DEST_DISTANCE, "GPS Dest Distance");
    131         _tagNameMap.put(TAG_GPS_PROCESSING_METHOD, "GPS Processing Method");
    132         _tagNameMap.put(TAG_GPS_AREA_INFORMATION, "GPS Area Information");
    133         _tagNameMap.put(TAG_GPS_DATE_STAMP, "GPS Date Stamp");
    134         _tagNameMap.put(TAG_GPS_DIFFERENTIAL, "GPS Differential");
     104        _tagNameMap.put(TAG_VERSION_ID, "GPS Version ID");
     105        _tagNameMap.put(TAG_LATITUDE_REF, "GPS Latitude Ref");
     106        _tagNameMap.put(TAG_LATITUDE, "GPS Latitude");
     107        _tagNameMap.put(TAG_LONGITUDE_REF, "GPS Longitude Ref");
     108        _tagNameMap.put(TAG_LONGITUDE, "GPS Longitude");
     109        _tagNameMap.put(TAG_ALTITUDE_REF, "GPS Altitude Ref");
     110        _tagNameMap.put(TAG_ALTITUDE, "GPS Altitude");
     111        _tagNameMap.put(TAG_TIME_STAMP, "GPS Time-Stamp");
     112        _tagNameMap.put(TAG_SATELLITES, "GPS Satellites");
     113        _tagNameMap.put(TAG_STATUS, "GPS Status");
     114        _tagNameMap.put(TAG_MEASURE_MODE, "GPS Measure Mode");
     115        _tagNameMap.put(TAG_DOP, "GPS DOP");
     116        _tagNameMap.put(TAG_SPEED_REF, "GPS Speed Ref");
     117        _tagNameMap.put(TAG_SPEED, "GPS Speed");
     118        _tagNameMap.put(TAG_TRACK_REF, "GPS Track Ref");
     119        _tagNameMap.put(TAG_TRACK, "GPS Track");
     120        _tagNameMap.put(TAG_IMG_DIRECTION_REF, "GPS Img Direction Ref");
     121        _tagNameMap.put(TAG_IMG_DIRECTION, "GPS Img Direction");
     122        _tagNameMap.put(TAG_MAP_DATUM, "GPS Map Datum");
     123        _tagNameMap.put(TAG_DEST_LATITUDE_REF, "GPS Dest Latitude Ref");
     124        _tagNameMap.put(TAG_DEST_LATITUDE, "GPS Dest Latitude");
     125        _tagNameMap.put(TAG_DEST_LONGITUDE_REF, "GPS Dest Longitude Ref");
     126        _tagNameMap.put(TAG_DEST_LONGITUDE, "GPS Dest Longitude");
     127        _tagNameMap.put(TAG_DEST_BEARING_REF, "GPS Dest Bearing Ref");
     128        _tagNameMap.put(TAG_DEST_BEARING, "GPS Dest Bearing");
     129        _tagNameMap.put(TAG_DEST_DISTANCE_REF, "GPS Dest Distance Ref");
     130        _tagNameMap.put(TAG_DEST_DISTANCE, "GPS Dest Distance");
     131        _tagNameMap.put(TAG_PROCESSING_METHOD, "GPS Processing Method");
     132        _tagNameMap.put(TAG_AREA_INFORMATION, "GPS Area Information");
     133        _tagNameMap.put(TAG_DATE_STAMP, "GPS Date Stamp");
     134        _tagNameMap.put(TAG_DIFFERENTIAL, "GPS Differential");
    135135    }
    136136
     
    140140    }
    141141
     142    @Override
    142143    @NotNull
    143144    public String getName()
     
    146147    }
    147148
     149    @Override
    148150    @NotNull
    149151    protected HashMap<Integer, String> getTagNameMap()
     
    161163    public GeoLocation getGeoLocation()
    162164    {
    163         Rational[] latitudes = getRationalArray(GpsDirectory.TAG_GPS_LATITUDE);
    164         Rational[] longitudes = getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE);
    165         String latitudeRef = getString(GpsDirectory.TAG_GPS_LATITUDE_REF);
    166         String longitudeRef = getString(GpsDirectory.TAG_GPS_LONGITUDE_REF);
     165        Rational[] latitudes = getRationalArray(GpsDirectory.TAG_LATITUDE);
     166        Rational[] longitudes = getRationalArray(GpsDirectory.TAG_LONGITUDE);
     167        String latitudeRef = getString(GpsDirectory.TAG_LATITUDE_REF);
     168        String longitudeRef = getString(GpsDirectory.TAG_LONGITUDE_REF);
    167169
    168170        // Make sure we have the required values
  • trunk/src/com/drew/metadata/iptc/IptcDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.iptc;
     
    2727
    2828/**
    29  * Provides human-readable string representations of tag values stored in a <code>IptcDirectory</code>.
    30  * <p/>
     29 * Provides human-readable string representations of tag values stored in a {@link IptcDirectory}.
     30 * <p>
    3131 * As the IPTC directory already stores values as strings, this class simply returns the tag's value.
    3232 *
    33  * @author Drew Noakes http://drewnoakes.com
     33 * @author Drew Noakes https://drewnoakes.com
    3434 */
    3535public class IptcDescriptor extends TagDescriptor<IptcDirectory>
     
    4040    }
    4141
     42    @Override
    4243    @Nullable
    4344    public String getDescription(int tagType)
  • trunk/src/com/drew/metadata/iptc/IptcDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.iptc;
     
    3232 * Describes tags used by the International Press Telecommunications Council (IPTC) metadata format.
    3333 *
    34  * @author Drew Noakes http://drewnoakes.com
     34 * @author Drew Noakes https://drewnoakes.com
    3535 */
    3636public class IptcDirectory extends Directory
     
    211211    }
    212212
     213    @Override
    213214    @NotNull
    214215    public String getName()
    215216    {
    216         return "Iptc";
    217     }
    218 
     217        return "IPTC";
     218    }
     219
     220    @Override
    219221    @NotNull
    220222    protected HashMap<Integer, String> getTagNameMap()
  • trunk/src/com/drew/metadata/iptc/IptcReader.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.iptc;
    2222
    23 import com.drew.lang.BufferBoundsException;
    24 import com.drew.lang.BufferReader;
     23import com.drew.imaging.jpeg.JpegSegmentMetadataReader;
     24import com.drew.imaging.jpeg.JpegSegmentType;
     25import com.drew.lang.SequentialByteArrayReader;
     26import com.drew.lang.SequentialReader;
    2527import com.drew.lang.annotations.NotNull;
    2628import com.drew.metadata.Directory;
    2729import com.drew.metadata.Metadata;
    28 import com.drew.metadata.MetadataReader;
    29 
     30
     31import java.io.IOException;
     32import java.util.Arrays;
    3033import java.util.Date;
    3134
    3235/**
    33  * Decodes IPTC binary data, populating a <code>Metadata</code> object with tag values in an <code>IptcDirectory</code>.
    34  *
    35  * @author Drew Noakes http://drewnoakes.com
     36 * Decodes IPTC binary data, populating a {@link Metadata} object with tag values in an {@link IptcDirectory}.
     37 * <p>
     38 * http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
     39 *
     40 * @author Drew Noakes https://drewnoakes.com
    3641 */
    37 public class IptcReader implements MetadataReader
     42public class IptcReader implements JpegSegmentMetadataReader
    3843{
    3944    // TODO consider breaking the IPTC section up into multiple directories and providing segregation of each IPTC directory
     
    5257*/
    5358
    54     /** Performs the IPTC data extraction, adding found values to the specified instance of <code>Metadata</code>. */
    55     public void extract(@NotNull final BufferReader reader, @NotNull final Metadata metadata)
     59    @NotNull
     60    public Iterable<JpegSegmentType> getSegmentTypes()
     61    {
     62        return Arrays.asList(JpegSegmentType.APPD);
     63    }
     64
     65    public boolean canProcess(@NotNull byte[] segmentBytes, @NotNull JpegSegmentType segmentType)
     66    {
     67        // Check whether the first byte resembles
     68        return segmentBytes.length != 0 && segmentBytes[0] == 0x1c;
     69    }
     70
     71    public void extract(@NotNull byte[] segmentBytes, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType)
     72    {
     73        extract(new SequentialByteArrayReader(segmentBytes), metadata, segmentBytes.length);
     74    }
     75
     76    /**
     77     * Performs the IPTC data extraction, adding found values to the specified instance of {@link Metadata}.
     78     */
     79    public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata, long length)
    5680    {
    5781        IptcDirectory directory = metadata.getOrCreateDirectory(IptcDirectory.class);
     
    5983        int offset = 0;
    6084
    61 /*
    62         // find start-of-segment marker (potentially need to skip some ASCII photoshop header info)
    63         try {
    64             while (offset < data.length - 1 && reader.getUInt16(offset) != 0x1c01 && reader.getUInt16(offset) != 0x1c02)
    65                 offset++;
    66         } catch (BufferBoundsException e) {
    67             directory.addError("Couldn't find start of IPTC data (invalid segment)");
    68             return;
    69         }
    70 */
    71 
    7285        // for each tag
    73         while (offset < reader.getLength()) {
     86        while (offset < length) {
    7487
    7588            // identifies start of a tag
    7689            short startByte;
    7790            try {
    78                 startByte = reader.getUInt8(offset);
    79             } catch (BufferBoundsException e) {
     91                startByte = reader.getUInt8();
     92                offset++;
     93            } catch (IOException e) {
    8094                directory.addError("Unable to read starting byte of IPTC tag");
    81                 break;
     95                return;
    8296            }
    8397
    8498            if (startByte != 0x1c) {
    85                 directory.addError("Invalid start to IPTC tag");
    86                 break;
     99                // NOTE have seen images where there was one extra byte at the end, giving
     100                // offset==length at this point, which is not worth logging as an error.
     101                if (offset != length)
     102                    directory.addError("Invalid IPTC tag marker at offset " + (offset - 1) + ". Expected '0x1c' but got '0x" + Integer.toHexString(startByte) + "'.");
     103                return;
    87104            }
    88105
    89106            // we need at least five bytes left to read a tag
    90             if (offset + 5 >= reader.getLength()) {
     107            if (offset + 5 >= length) {
    91108                directory.addError("Too few bytes remain for a valid IPTC tag");
    92                 break;
    93             }
    94 
    95             offset++;
     109                return;
     110            }
    96111
    97112            int directoryType;
     
    99114            int tagByteCount;
    100115            try {
    101                 directoryType = reader.getUInt8(offset++);
    102                 tagType = reader.getUInt8(offset++);
    103                 tagByteCount = reader.getUInt16(offset);
    104                 offset += 2;
    105             } catch (BufferBoundsException e) {
     116                directoryType = reader.getUInt8();
     117                tagType = reader.getUInt8();
     118                // TODO support Extended DataSet Tag (see 1.5(c), p14, IPTC-IIMV4.2.pdf)
     119                tagByteCount = reader.getUInt16();
     120                offset += 4;
     121            } catch (IOException e) {
    106122                directory.addError("IPTC data segment ended mid-way through tag descriptor");
    107123                return;
    108124            }
    109125
    110             if (offset + tagByteCount > reader.getLength()) {
     126            if (offset + tagByteCount > length) {
    111127                directory.addError("Data for tag extends beyond end of IPTC segment");
     128                return;
     129            }
     130
     131            try {
     132                processTag(reader, directory, directoryType, tagType, tagByteCount);
     133            } catch (IOException e) {
     134                directory.addError("Error processing IPTC tag");
     135                return;
     136            }
     137
     138            offset += tagByteCount;
     139        }
     140    }
     141
     142    private void processTag(@NotNull SequentialReader reader, @NotNull Directory directory, int directoryType, int tagType, int tagByteCount) throws IOException
     143    {
     144        int tagIdentifier = tagType | (directoryType << 8);
     145
     146        // Some images have been seen that specify a zero byte tag, which cannot be of much use.
     147        // We elect here to completely ignore the tag. The IPTC specification doesn't mention
     148        // anything about the interpretation of this situation.
     149        // https://raw.githubusercontent.com/wiki/drewnoakes/metadata-extractor/docs/IPTC-IIMV4.2.pdf
     150        if (tagByteCount == 0) {
     151            directory.setString(tagIdentifier, "");
     152            return;
     153        }
     154
     155        String string = null;
     156
     157        switch (tagIdentifier) {
     158            case IptcDirectory.TAG_CODED_CHARACTER_SET:
     159                byte[] bytes = reader.getBytes(tagByteCount);
     160                String charset = Iso2022Converter.convertISO2022CharsetToJavaCharset(bytes);
     161                if (charset == null) {
     162                    // Unable to determine the charset, so fall through and treat tag as a regular string
     163                    string = new String(bytes);
     164                    break;
     165                }
     166                directory.setString(tagIdentifier, charset);
     167                return;
     168            case IptcDirectory.TAG_ENVELOPE_RECORD_VERSION:
     169            case IptcDirectory.TAG_APPLICATION_RECORD_VERSION:
     170            case IptcDirectory.TAG_FILE_VERSION:
     171            case IptcDirectory.TAG_ARM_VERSION:
     172            case IptcDirectory.TAG_PROGRAM_VERSION:
     173                // short
     174                if (tagByteCount >= 2) {
     175                    int shortValue = reader.getUInt16();
     176                    reader.skip(tagByteCount - 2);
     177                    directory.setInt(tagIdentifier, shortValue);
     178                    return;
     179                }
    112180                break;
    113             }
    114 
    115             try {
    116                 processTag(reader, directory, directoryType, tagType, offset, tagByteCount);
    117             } catch (BufferBoundsException e) {
    118                 directory.addError("Error processing IPTC tag");
    119                 break;
    120             }
    121 
    122             offset += tagByteCount;
    123         }
    124     }
    125 
    126     private void processTag(@NotNull BufferReader reader, @NotNull Directory directory, int directoryType, int tagType, int offset, int tagByteCount) throws BufferBoundsException
    127     {
    128         int tagIdentifier = tagType | (directoryType << 8);
    129 
    130         switch (tagIdentifier) {
    131             case IptcDirectory.TAG_APPLICATION_RECORD_VERSION:
    132                 // short
    133                 int shortValue = reader.getUInt16(offset);
    134                 directory.setInt(tagIdentifier, shortValue);
    135                 return;
    136181            case IptcDirectory.TAG_URGENCY:
    137182                // byte
    138                 directory.setInt(tagIdentifier, reader.getUInt8(offset));
     183                directory.setInt(tagIdentifier, reader.getUInt8());
     184                reader.skip(tagByteCount - 1);
    139185                return;
    140186            case IptcDirectory.TAG_RELEASE_DATE:
     
    142188                // Date object
    143189                if (tagByteCount >= 8) {
    144                     String dateStr = reader.getString(offset, tagByteCount);
     190                    string = reader.getString(tagByteCount);
    145191                    try {
    146                         int year = Integer.parseInt(dateStr.substring(0, 4));
    147                         int month = Integer.parseInt(dateStr.substring(4, 6)) - 1;
    148                         int day = Integer.parseInt(dateStr.substring(6, 8));
     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));
    149195                        Date date = new java.util.GregorianCalendar(year, month, day).getTime();
    150196                        directory.setDate(tagIdentifier, date);
    151197                        return;
    152198                    } catch (NumberFormatException e) {
    153                         // fall through and we'll store whatever was there as a String
     199                        // fall through and we'll process the 'string' value below
    154200                    }
     201                } else {
     202                    reader.skip(tagByteCount);
    155203                }
    156204            case IptcDirectory.TAG_RELEASE_TIME:
     
    162210
    163211        // If we haven't returned yet, treat it as a string
    164         String str;
    165         if (tagByteCount < 1) {
    166             str = "";
    167         } else {
    168             str = reader.getString(offset, tagByteCount, System.getProperty("file.encoding")); // "ISO-8859-1"
     212        // NOTE that there's a chance we've already loaded the value as a string above, but failed to parse the value
     213        if (string == null) {
     214            String encoding = directory.getString(IptcDirectory.TAG_CODED_CHARACTER_SET);
     215            if (encoding != null) {
     216                string = reader.getString(tagByteCount, encoding);
     217            } else {
     218                byte[] bytes = reader.getBytes(tagByteCount);
     219                encoding = Iso2022Converter.guessEncoding(bytes);
     220                string = encoding != null ? new String(bytes, encoding) : new String(bytes);
     221            }
    169222        }
    170223
     
    179232                System.arraycopy(oldStrings, 0, newStrings, 0, oldStrings.length);
    180233            }
    181             newStrings[newStrings.length - 1] = str;
     234            newStrings[newStrings.length - 1] = string;
    182235            directory.setStringArray(tagIdentifier, newStrings);
    183236        } else {
    184             directory.setString(tagIdentifier, str);
     237            directory.setString(tagIdentifier, string);
    185238        }
    186239    }
  • trunk/src/com/drew/metadata/jpeg/JpegCommentDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.jpeg;
     
    2626
    2727/**
    28  * Provides human-readable string representations of tag values stored in a <code>JpegCommentDirectory</code>.
     28 * Provides human-readable string representations of tag values stored in a {@link JpegCommentDirectory}.
    2929 *
    30  * @author Drew Noakes http://drewnoakes.com
     30 * @author Drew Noakes https://drewnoakes.com
    3131 */
    3232public class JpegCommentDescriptor extends TagDescriptor<JpegCommentDirectory>
     
    4040    public String getJpegCommentDescription()
    4141    {
    42         return _directory.getString(JpegCommentDirectory.TAG_JPEG_COMMENT);
     42        return _directory.getString(JpegCommentDirectory.TAG_COMMENT);
    4343    }
    4444}
  • trunk/src/com/drew/metadata/jpeg/JpegCommentDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.jpeg;
     
    2929 * Describes tags used by a JPEG file comment.
    3030 *
    31  * @author Drew Noakes http://drewnoakes.com
     31 * @author Drew Noakes https://drewnoakes.com
    3232 */
    3333public class JpegCommentDirectory extends Directory
     
    3737     * consistency with other directory types.
    3838     */
    39     public static final int TAG_JPEG_COMMENT = 0;
     39    public static final int TAG_COMMENT = 0;
    4040
    4141    @NotNull
     
    4343
    4444    static {
    45         _tagNameMap.put(TAG_JPEG_COMMENT, "Jpeg Comment");
     45        _tagNameMap.put(TAG_COMMENT, "JPEG Comment");
    4646    }
    4747
     
    5151    }
    5252
     53    @Override
    5354    @NotNull
    5455    public String getName()
     
    5758    }
    5859
     60    @Override
    5961    @NotNull
    6062    protected HashMap<Integer, String> getTagNameMap()
  • trunk/src/com/drew/metadata/jpeg/JpegCommentReader.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.jpeg;
    2222
    23 import com.drew.lang.BufferBoundsException;
    24 import com.drew.lang.BufferReader;
     23import com.drew.imaging.jpeg.JpegSegmentMetadataReader;
     24import com.drew.imaging.jpeg.JpegSegmentType;
    2525import com.drew.lang.annotations.NotNull;
    2626import com.drew.metadata.Metadata;
    27 import com.drew.metadata.MetadataReader;
     27
     28import java.util.Arrays;
    2829
    2930/**
    30  * Decodes the comment stored within Jpeg files, populating a <code>Metadata</code> object with tag values in a
    31  * <code>JpegCommentDirectory</code>.
     31 * Decodes the comment stored within JPEG files, populating a {@link Metadata} object with tag values in a
     32 * {@link JpegCommentDirectory}.
    3233 *
    33  * @author Drew Noakes http://drewnoakes.com
     34 * @author Drew Noakes https://drewnoakes.com
    3435 */
    35 public class JpegCommentReader implements MetadataReader
     36public class JpegCommentReader implements JpegSegmentMetadataReader
    3637{
    37     /**
    38      * Performs the Jpeg data extraction, adding found values to the specified
    39      * instance of <code>Metadata</code>.
    40      */
    41     public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata)
     38    @NotNull
     39    public Iterable<JpegSegmentType> getSegmentTypes()
     40    {
     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;
     48    }
     49
     50    public void extract(@NotNull byte[] segmentBytes, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType)
    4251    {
    4352        JpegCommentDirectory directory = metadata.getOrCreateDirectory(JpegCommentDirectory.class);
    4453
    45         try {
    46             directory.setString(JpegCommentDirectory.TAG_JPEG_COMMENT, reader.getString(0, (int)reader.getLength()));
    47         } catch (BufferBoundsException e) {
    48             directory.addError("Exception reading JPEG comment string");
    49         }
     54        // The entire contents of the directory are the comment
     55        directory.setString(JpegCommentDirectory.TAG_COMMENT, new String(segmentBytes));
    5056    }
    5157}
  • trunk/src/com/drew/metadata/jpeg/JpegComponent.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.jpeg;
     
    2626
    2727/**
    28  * Stores information about a Jpeg image component such as the component id, horiz/vert sampling factor and
     28 * Stores information about a JPEG image component such as the component id, horiz/vert sampling factor and
    2929 * quantization table number.
    3030 *
    31  * @author Drew Noakes http://drewnoakes.com
     31 * @author Drew Noakes https://drewnoakes.com
    3232 */
    3333public class JpegComponent implements Serializable
  • trunk/src/com/drew/metadata/jpeg/JpegDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.jpeg;
     
    2929 * Thanks to Darrell Silver (www.darrellsilver.com) for the initial version of this class.
    3030 *
    31  * @author Drew Noakes http://drewnoakes.com
     31 * @author Drew Noakes https://drewnoakes.com
    3232 */
    3333public class JpegDescriptor extends TagDescriptor<JpegDirectory>
     
    3838    }
    3939
     40    @Override
    4041    @Nullable
    4142    public String getDescription(int tagType)
     
    4344        switch (tagType)
    4445        {
    45             case JpegDirectory.TAG_JPEG_COMPRESSION_TYPE:
     46            case JpegDirectory.TAG_COMPRESSION_TYPE:
    4647                return getImageCompressionTypeDescription();
    47             case JpegDirectory.TAG_JPEG_COMPONENT_DATA_1:
     48            case JpegDirectory.TAG_COMPONENT_DATA_1:
    4849                return getComponentDataDescription(0);
    49             case JpegDirectory.TAG_JPEG_COMPONENT_DATA_2:
     50            case JpegDirectory.TAG_COMPONENT_DATA_2:
    5051                return getComponentDataDescription(1);
    51             case JpegDirectory.TAG_JPEG_COMPONENT_DATA_3:
     52            case JpegDirectory.TAG_COMPONENT_DATA_3:
    5253                return getComponentDataDescription(2);
    53             case JpegDirectory.TAG_JPEG_COMPONENT_DATA_4:
     54            case JpegDirectory.TAG_COMPONENT_DATA_4:
    5455                return getComponentDataDescription(3);
    55             case JpegDirectory.TAG_JPEG_DATA_PRECISION:
     56            case JpegDirectory.TAG_DATA_PRECISION:
    5657                return getDataPrecisionDescription();
    57             case JpegDirectory.TAG_JPEG_IMAGE_HEIGHT:
     58            case JpegDirectory.TAG_IMAGE_HEIGHT:
    5859                return getImageHeightDescription();
    59             case JpegDirectory.TAG_JPEG_IMAGE_WIDTH:
     60            case JpegDirectory.TAG_IMAGE_WIDTH:
    6061                return getImageWidthDescription();
    6162            default:
     
    6768    public String getImageCompressionTypeDescription()
    6869    {
    69         Integer value = _directory.getInteger(JpegDirectory.TAG_JPEG_COMPRESSION_TYPE);
     70        Integer value = _directory.getInteger(JpegDirectory.TAG_COMPRESSION_TYPE);
    7071        if (value==null)
    7172            return null;
     
    9394    public String getImageWidthDescription()
    9495    {
    95         final String value = _directory.getString(JpegDirectory.TAG_JPEG_IMAGE_WIDTH);
     96        final String value = _directory.getString(JpegDirectory.TAG_IMAGE_WIDTH);
    9697        if (value==null)
    9798            return null;
     
    102103    public String getImageHeightDescription()
    103104    {
    104         final String value = _directory.getString(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT);
     105        final String value = _directory.getString(JpegDirectory.TAG_IMAGE_HEIGHT);
    105106        if (value==null)
    106107            return null;
     
    111112    public String getDataPrecisionDescription()
    112113    {
    113         final String value = _directory.getString(JpegDirectory.TAG_JPEG_DATA_PRECISION);
     114        final String value = _directory.getString(JpegDirectory.TAG_DATA_PRECISION);
    114115        if (value==null)
    115116            return null;
     
    125126            return null;
    126127
    127         StringBuilder sb = new StringBuilder();
    128         sb.append(value.getComponentName());
    129         sb.append(" component: Quantization table ");
    130         sb.append(value.getQuantizationTableNumber());
    131         sb.append(", Sampling factors ");
    132         sb.append(value.getHorizontalSamplingFactor());
    133         sb.append(" horiz/");
    134         sb.append(value.getVerticalSamplingFactor());
    135         sb.append(" vert");
    136         return sb.toString();
     128        return value.getComponentName() + " component: Quantization table " + value.getQuantizationTableNumber()
     129            + ", Sampling factors " + value.getHorizontalSamplingFactor()
     130            + " horiz/" + value.getVerticalSamplingFactor() + " vert";
    137131    }
    138132}
  • trunk/src/com/drew/metadata/jpeg/JpegDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.jpeg;
     
    2929
    3030/**
    31  * Directory of tags and values for the SOF0 Jpeg segment.  This segment holds basic metadata about the image.
     31 * Directory of tags and values for the SOF0 JPEG segment.  This segment holds basic metadata about the image.
    3232 *
    33  * @author Darrell Silver http://www.darrellsilver.com and Drew Noakes http://drewnoakes.com
     33 * @author Darrell Silver http://www.darrellsilver.com and Drew Noakes https://drewnoakes.com
    3434 */
    3535public class JpegDirectory extends Directory
    3636{
    37     public static final int TAG_JPEG_COMPRESSION_TYPE = -3;
     37    public static final int TAG_COMPRESSION_TYPE = -3;
    3838    /** This is in bits/sample, usually 8 (12 and 16 not supported by most software). */
    39     public static final int TAG_JPEG_DATA_PRECISION = 0;
     39    public static final int TAG_DATA_PRECISION = 0;
    4040    /** The image's height.  Necessary for decoding the image, so it should always be there. */
    41     public static final int TAG_JPEG_IMAGE_HEIGHT = 1;
     41    public static final int TAG_IMAGE_HEIGHT = 1;
    4242    /** The image's width.  Necessary for decoding the image, so it should always be there. */
    43     public static final int TAG_JPEG_IMAGE_WIDTH = 3;
     43    public static final int TAG_IMAGE_WIDTH = 3;
    4444    /**
    4545     * Usually 1 = grey scaled, 3 = color YcbCr or YIQ, 4 = color CMYK
     
    4848     * sampling factors (1byte) (bit 0-3 vertical., 4-7 horizontal.),
    4949     * quantization table number (1 byte).
    50      * <p/>
     50     * <p>
    5151     * This info is from http://www.funducode.com/freec/Fileformats/format3/format3b.htm
    5252     */
    53     public static final int TAG_JPEG_NUMBER_OF_COMPONENTS = 5;
     53    public static final int TAG_NUMBER_OF_COMPONENTS = 5;
    5454
    5555    // NOTE!  Component tag type int values must increment in steps of 1
    5656
    57     /** the first of a possible 4 color components.  Number of components specified in TAG_JPEG_NUMBER_OF_COMPONENTS. */
    58     public static final int TAG_JPEG_COMPONENT_DATA_1 = 6;
    59     /** the second of a possible 4 color components.  Number of components specified in TAG_JPEG_NUMBER_OF_COMPONENTS. */
    60     public static final int TAG_JPEG_COMPONENT_DATA_2 = 7;
    61     /** the third of a possible 4 color components.  Number of components specified in TAG_JPEG_NUMBER_OF_COMPONENTS. */
    62     public static final int TAG_JPEG_COMPONENT_DATA_3 = 8;
    63     /** the fourth of a possible 4 color components.  Number of components specified in TAG_JPEG_NUMBER_OF_COMPONENTS. */
    64     public static final int TAG_JPEG_COMPONENT_DATA_4 = 9;
     57    /** the first of a possible 4 color components.  Number of components specified in TAG_NUMBER_OF_COMPONENTS. */
     58    public static final int TAG_COMPONENT_DATA_1 = 6;
     59    /** the second of a possible 4 color components.  Number of components specified in TAG_NUMBER_OF_COMPONENTS. */
     60    public static final int TAG_COMPONENT_DATA_2 = 7;
     61    /** the third of a possible 4 color components.  Number of components specified in TAG_NUMBER_OF_COMPONENTS. */
     62    public static final int TAG_COMPONENT_DATA_3 = 8;
     63    /** the fourth of a possible 4 color components.  Number of components specified in TAG_NUMBER_OF_COMPONENTS. */
     64    public static final int TAG_COMPONENT_DATA_4 = 9;
    6565
    6666    @NotNull
     
    6868
    6969    static {
    70         _tagNameMap.put(TAG_JPEG_COMPRESSION_TYPE, "Compression Type");
    71         _tagNameMap.put(TAG_JPEG_DATA_PRECISION, "Data Precision");
    72         _tagNameMap.put(TAG_JPEG_IMAGE_WIDTH, "Image Width");
    73         _tagNameMap.put(TAG_JPEG_IMAGE_HEIGHT, "Image Height");
    74         _tagNameMap.put(TAG_JPEG_NUMBER_OF_COMPONENTS, "Number of Components");
    75         _tagNameMap.put(TAG_JPEG_COMPONENT_DATA_1, "Component 1");
    76         _tagNameMap.put(TAG_JPEG_COMPONENT_DATA_2, "Component 2");
    77         _tagNameMap.put(TAG_JPEG_COMPONENT_DATA_3, "Component 3");
    78         _tagNameMap.put(TAG_JPEG_COMPONENT_DATA_4, "Component 4");
     70        _tagNameMap.put(TAG_COMPRESSION_TYPE, "Compression Type");
     71        _tagNameMap.put(TAG_DATA_PRECISION, "Data Precision");
     72        _tagNameMap.put(TAG_IMAGE_WIDTH, "Image Width");
     73        _tagNameMap.put(TAG_IMAGE_HEIGHT, "Image Height");
     74        _tagNameMap.put(TAG_NUMBER_OF_COMPONENTS, "Number of Components");
     75        _tagNameMap.put(TAG_COMPONENT_DATA_1, "Component 1");
     76        _tagNameMap.put(TAG_COMPONENT_DATA_2, "Component 2");
     77        _tagNameMap.put(TAG_COMPONENT_DATA_3, "Component 3");
     78        _tagNameMap.put(TAG_COMPONENT_DATA_4, "Component 4");
    7979    }
    8080
     
    8484    }
    8585
     86    @Override
    8687    @NotNull
    8788    public String getName()
    8889    {
    89         return "Jpeg";
     90        return "JPEG";
    9091    }
    9192
     93    @Override
    9294    @NotNull
    9395    protected HashMap<Integer, String> getTagNameMap()
     
    104106    public JpegComponent getComponent(int componentNumber)
    105107    {
    106         int tagType = JpegDirectory.TAG_JPEG_COMPONENT_DATA_1 + componentNumber;
     108        int tagType = JpegDirectory.TAG_COMPONENT_DATA_1 + componentNumber;
    107109        return (JpegComponent)getObject(tagType);
    108110    }
     
    110112    public int getImageWidth() throws MetadataException
    111113    {
    112         return getInt(JpegDirectory.TAG_JPEG_IMAGE_WIDTH);
     114        return getInt(JpegDirectory.TAG_IMAGE_WIDTH);
    113115    }
    114116
    115117    public int getImageHeight() throws MetadataException
    116118    {
    117         return getInt(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT);
     119        return getInt(JpegDirectory.TAG_IMAGE_HEIGHT);
    118120    }
    119121
    120122    public int getNumberOfComponents() throws MetadataException
    121123    {
    122         return getInt(JpegDirectory.TAG_JPEG_NUMBER_OF_COMPONENTS);
     124        return getInt(JpegDirectory.TAG_NUMBER_OF_COMPONENTS);
    123125    }
    124126}
  • trunk/src/com/drew/metadata/jpeg/JpegReader.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.jpeg;
    2222
    23 import com.drew.lang.BufferBoundsException;
    24 import com.drew.lang.BufferReader;
     23import com.drew.imaging.jpeg.JpegSegmentMetadataReader;
     24import com.drew.imaging.jpeg.JpegSegmentType;
     25import com.drew.lang.SequentialByteArrayReader;
     26import com.drew.lang.SequentialReader;
    2527import com.drew.lang.annotations.NotNull;
    2628import com.drew.metadata.Metadata;
    27 import com.drew.metadata.MetadataReader;
     29
     30import java.io.IOException;
     31import java.util.Arrays;
    2832
    2933/**
    30  * Decodes Jpeg SOF0 data, populating a <code>Metadata</code> object with tag values in a <code>JpegDirectory</code>.
     34 * Decodes JPEG SOFn data, populating a {@link Metadata} object with tag values in a {@link JpegDirectory}.
    3135 *
    32  * @author Darrell Silver http://www.darrellsilver.com and Drew Noakes http://drewnoakes.com
     36 * @author Drew Noakes https://drewnoakes.com
     37 * @author Darrell Silver http://www.darrellsilver.com
    3338 */
    34 public class JpegReader implements MetadataReader
     39public class JpegReader implements JpegSegmentMetadataReader
    3540{
    36     /**
    37      * Performs the Jpeg data extraction, adding found values to the specified
    38      * instance of <code>Metadata</code>.
    39      */
    40     public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata)
     41    @NotNull
     42    public Iterable<JpegSegmentType> getSegmentTypes()
    4143    {
     44        // NOTE that some SOFn values do not exist
     45        return Arrays.asList(
     46            JpegSegmentType.SOF0,
     47            JpegSegmentType.SOF1,
     48            JpegSegmentType.SOF2,
     49            JpegSegmentType.SOF3,
     50//            JpegSegmentType.SOF4,
     51            JpegSegmentType.SOF5,
     52            JpegSegmentType.SOF6,
     53            JpegSegmentType.SOF7,
     54            JpegSegmentType.SOF8,
     55            JpegSegmentType.SOF9,
     56            JpegSegmentType.SOF10,
     57            JpegSegmentType.SOF11,
     58//            JpegSegmentType.SOF12,
     59            JpegSegmentType.SOF13,
     60            JpegSegmentType.SOF14,
     61            JpegSegmentType.SOF15
     62        );
     63    }
     64
     65    public boolean canProcess(@NotNull byte[] segmentBytes, @NotNull JpegSegmentType segmentType)
     66    {
     67        return true;
     68    }
     69
     70    public void extract(@NotNull byte[] segmentBytes, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType)
     71    {
     72        if (metadata.containsDirectory(JpegDirectory.class)) {
     73            // If this directory is already present, discontinue this operation.
     74            // We only store metadata for the *first* matching SOFn segment.
     75            return;
     76        }
     77
    4278        JpegDirectory directory = metadata.getOrCreateDirectory(JpegDirectory.class);
    4379
     80        // The value of TAG_COMPRESSION_TYPE is determined by the segment type found
     81        directory.setInt(JpegDirectory.TAG_COMPRESSION_TYPE, segmentType.byteValue - JpegSegmentType.SOF0.byteValue);
     82
     83        SequentialReader reader = new SequentialByteArrayReader(segmentBytes);
     84
    4485        try {
    45             // data precision
    46             int dataPrecision = reader.getUInt8(JpegDirectory.TAG_JPEG_DATA_PRECISION);
    47             directory.setInt(JpegDirectory.TAG_JPEG_DATA_PRECISION, dataPrecision);
    48 
    49             // process height
    50             int height = reader.getUInt16(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT);
    51             directory.setInt(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT, height);
    52 
    53             // process width
    54             int width = reader.getUInt16(JpegDirectory.TAG_JPEG_IMAGE_WIDTH);
    55             directory.setInt(JpegDirectory.TAG_JPEG_IMAGE_WIDTH, width);
    56 
    57             // number of components
    58             int numberOfComponents = reader.getUInt8(JpegDirectory.TAG_JPEG_NUMBER_OF_COMPONENTS);
    59             directory.setInt(JpegDirectory.TAG_JPEG_NUMBER_OF_COMPONENTS, numberOfComponents);
     86            directory.setInt(JpegDirectory.TAG_DATA_PRECISION, reader.getUInt8());
     87            directory.setInt(JpegDirectory.TAG_IMAGE_HEIGHT, reader.getUInt16());
     88            directory.setInt(JpegDirectory.TAG_IMAGE_WIDTH, reader.getUInt16());
     89            short componentCount = reader.getUInt8();
     90            directory.setInt(JpegDirectory.TAG_NUMBER_OF_COMPONENTS, componentCount);
    6091
    6192            // for each component, there are three bytes of data:
     
    6394            // 2 - Sampling factors: bit 0-3 vertical, 4-7 horizontal
    6495            // 3 - Quantization table number
    65             int offset = 6;
    66             for (int i = 0; i < numberOfComponents; i++) {
    67                 int componentId = reader.getUInt8(offset++);
    68                 int samplingFactorByte = reader.getUInt8(offset++);
    69                 int quantizationTableNumber = reader.getUInt8(offset++);
    70                 JpegComponent component = new JpegComponent(componentId, samplingFactorByte, quantizationTableNumber);
    71                 directory.setObject(JpegDirectory.TAG_JPEG_COMPONENT_DATA_1 + i, component);
     96            for (int i = 0; i < (int)componentCount; i++) {
     97                final int componentId = reader.getUInt8();
     98                final int samplingFactorByte = reader.getUInt8();
     99                final int quantizationTableNumber = reader.getUInt8();
     100                final JpegComponent component = new JpegComponent(componentId, samplingFactorByte, quantizationTableNumber);
     101                directory.setObject(JpegDirectory.TAG_COMPONENT_DATA_1 + i, component);
    72102            }
    73103
    74         } catch (BufferBoundsException ex) {
     104        } catch (IOException ex) {
    75105            directory.addError(ex.getMessage());
    76106        }
Note: See TracChangeset for help on using the changeset viewer.