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


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/imaging
Files:
10 added
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/com/drew/imaging/ImageProcessingException.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.imaging;
     
    2626/**
    2727 * An exception class thrown upon an unexpected condition that was fatal for the processing of an image.
    28  * 
    29  * @author Drew Noakes http://drewnoakes.com
     28 *
     29 * @author Drew Noakes https://drewnoakes.com
    3030 */
    3131public class ImageProcessingException extends CompoundException
  • trunk/src/com/drew/imaging/PhotographicConversions.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.imaging;
     
    2424 * Contains helper methods that perform photographic conversions.
    2525 *
    26  * @author Drew Noakes http://drewnoakes.com
     26 * @author Drew Noakes https://drewnoakes.com
    2727 */
    2828public final class PhotographicConversions
  • trunk/src/com/drew/imaging/jpeg/JpegMetadataReader.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.imaging.jpeg;
    2222
    23 import com.drew.lang.ByteArrayReader;
    24 import com.drew.lang.annotations.NotNull;
    25 import com.drew.metadata.Metadata;
    26 import com.drew.metadata.exif.ExifReader;
    27 import com.drew.metadata.iptc.IptcReader;
    28 import com.drew.metadata.jpeg.JpegCommentReader;
    29 import com.drew.metadata.jpeg.JpegDirectory;
    30 import com.drew.metadata.jpeg.JpegReader;
    31 
    3223import java.io.File;
     24import java.io.FileInputStream;
    3325import java.io.IOException;
    3426import java.io.InputStream;
     27import java.util.Arrays;
     28import java.util.HashSet;
     29import java.util.Set;
     30
     31import com.drew.lang.StreamReader;
     32import com.drew.lang.annotations.NotNull;
     33import com.drew.lang.annotations.Nullable;
     34import com.drew.metadata.Metadata;
     35//import com.drew.metadata.adobe.AdobeJpegReader;
     36import com.drew.metadata.exif.ExifReader;
     37//import com.drew.metadata.icc.IccReader;
     38import com.drew.metadata.iptc.IptcReader;
     39//import com.drew.metadata.jfif.JfifReader;
     40import com.drew.metadata.jpeg.JpegCommentReader;
     41import com.drew.metadata.jpeg.JpegReader;
     42//import com.drew.metadata.photoshop.PhotoshopReader;
     43//import com.drew.metadata.xmp.XmpReader;
    3544
    3645/**
    37  * Obtains all available metadata from Jpeg formatted files.
     46 * Obtains all available metadata from JPEG formatted files.
    3847 *
    39  * @author Drew Noakes http://drewnoakes.com
     48 * @author Drew Noakes https://drewnoakes.com
    4049 */
    4150public class JpegMetadataReader
    4251{
    43     // TODO investigate supporting javax.imageio
    44 //    public static Metadata readMetadata(IIOMetadata metadata) throws JpegProcessingException {}
    45 //    public static Metadata readMetadata(ImageInputStream in) throws JpegProcessingException{}
    46 //    public static Metadata readMetadata(IIOImage image) throws JpegProcessingException{}
    47 //    public static Metadata readMetadata(ImageReader reader) throws JpegProcessingException{}
     52    public static final Iterable<JpegSegmentMetadataReader> ALL_READERS = Arrays.asList(
     53            new JpegReader(),
     54            new JpegCommentReader(),
     55            //new JfifReader(),
     56            new ExifReader(),
     57            //new XmpReader(),
     58            //new IccReader(),
     59            //new PhotoshopReader(),
     60            new IptcReader()//,
     61            //new AdobeJpegReader()
     62    );
    4863
    4964    @NotNull
    50     public static Metadata readMetadata(@NotNull InputStream inputStream) throws JpegProcessingException
     65    public static Metadata readMetadata(@NotNull InputStream inputStream, @Nullable Iterable<JpegSegmentMetadataReader> readers) throws JpegProcessingException, IOException
    5166    {
    52         return readMetadata(inputStream, true);
     67        Metadata metadata = new Metadata();
     68        process(metadata, inputStream, readers);
     69        return metadata;
    5370    }
    5471
    5572    @NotNull
    56     public static Metadata readMetadata(@NotNull InputStream inputStream, final boolean waitForBytes) throws JpegProcessingException
     73    public static Metadata readMetadata(@NotNull InputStream inputStream) throws JpegProcessingException, IOException
    5774    {
    58         JpegSegmentReader segmentReader = new JpegSegmentReader(inputStream, waitForBytes);
    59         return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData());
     75        return readMetadata(inputStream, null);
     76    }
     77
     78    @NotNull
     79    public static Metadata readMetadata(@NotNull File file, @Nullable Iterable<JpegSegmentMetadataReader> readers) throws JpegProcessingException, IOException
     80    {
     81        InputStream inputStream = null;
     82        try
     83        {
     84            inputStream = new FileInputStream(file);
     85            return readMetadata(inputStream, readers);
     86        } finally {
     87            if (inputStream != null)
     88                inputStream.close();
     89        }
    6090    }
    6191
     
    6393    public static Metadata readMetadata(@NotNull File file) throws JpegProcessingException, IOException
    6494    {
    65         JpegSegmentReader segmentReader = new JpegSegmentReader(file);
    66         return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData());
     95        return readMetadata(file, null);
    6796    }
    6897
    69     @NotNull
    70     public static Metadata extractMetadataFromJpegSegmentReader(@NotNull JpegSegmentData segmentReader)
     98    public static void process(@NotNull Metadata metadata, @NotNull InputStream inputStream) throws JpegProcessingException, IOException
    7199    {
    72         final Metadata metadata = new Metadata();
     100        process(metadata, inputStream, null);
     101    }
    73102
    74         // Loop through looking for all SOFn segments.  When we find one, we know what type of compression
    75         // was used for the JPEG, and we can process the JPEG metadata in the segment too.
    76         for (byte i = 0; i < 16; i++) {
    77             // There are no SOF4 or SOF12 segments, so don't bother
    78             if (i == 4 || i == 12)
    79                 continue;
    80             // Should never have more than one SOFn for a given 'n'.
    81             byte[] jpegSegment = segmentReader.getSegment((byte)(JpegSegmentReader.SEGMENT_SOF0 + i));
    82             if (jpegSegment == null)
    83                 continue;
    84             JpegDirectory directory = metadata.getOrCreateDirectory(JpegDirectory.class);
    85             directory.setInt(JpegDirectory.TAG_JPEG_COMPRESSION_TYPE, i);
    86             new JpegReader().extract(new ByteArrayReader(jpegSegment), metadata);
    87             break;
    88         }
     103    public static void process(@NotNull Metadata metadata, @NotNull InputStream inputStream, @Nullable Iterable<JpegSegmentMetadataReader> readers) throws JpegProcessingException, IOException
     104    {
     105        if (readers == null)
     106            readers = ALL_READERS;
    89107
    90         // There should never be more than one COM segment.
    91         byte[] comSegment = segmentReader.getSegment(JpegSegmentReader.SEGMENT_COM);
    92         if (comSegment != null)
    93             new JpegCommentReader().extract(new ByteArrayReader(comSegment), metadata);
    94 
    95         // Loop through all APP1 segments, checking the leading bytes to identify the format of each.
    96         for (byte[] app1Segment : segmentReader.getSegments(JpegSegmentReader.SEGMENT_APP1)) {
    97             if (app1Segment.length > 3 && "EXIF".equalsIgnoreCase(new String(app1Segment, 0, 4)))
    98                 new ExifReader().extract(new ByteArrayReader(app1Segment), metadata);
    99 
    100             //if (app1Segment.length > 27 && "http://ns.adobe.com/xap/1.0/".equalsIgnoreCase(new String(app1Segment, 0, 28)))
    101             //    new XmpReader().extract(new ByteArrayReader(app1Segment), metadata);
    102         }
    103 
    104         // Loop through all APPD segments, checking the leading bytes to identify the format of each.
    105         for (byte[] appdSegment : segmentReader.getSegments(JpegSegmentReader.SEGMENT_APPD)) {
    106             if (appdSegment.length > 12 && "Photoshop 3.0".compareTo(new String(appdSegment, 0, 13))==0) {
    107                 //new PhotoshopReader().extract(new ByteArrayReader(appdSegment), metadata);
    108             } else {
    109                 // TODO might be able to check for a leading 0x1c02 for IPTC data...
    110                 new IptcReader().extract(new ByteArrayReader(appdSegment), metadata);
     108        Set<JpegSegmentType> segmentTypes = new HashSet<JpegSegmentType>();
     109        for (JpegSegmentMetadataReader reader : readers) {
     110            for (JpegSegmentType type : reader.getSegmentTypes()) {
     111                segmentTypes.add(type);
    111112            }
    112113        }
    113114
    114         return metadata;
     115        JpegSegmentData segmentData = JpegSegmentReader.readSegments(new StreamReader(inputStream), segmentTypes);
     116
     117        processJpegSegmentData(metadata, readers, segmentData);
     118    }
     119
     120    public static void processJpegSegmentData(Metadata metadata, Iterable<JpegSegmentMetadataReader> readers, JpegSegmentData segmentData)
     121    {
     122        // Pass the appropriate byte arrays to each reader.
     123        for (JpegSegmentMetadataReader reader : readers) {
     124            for (JpegSegmentType segmentType : reader.getSegmentTypes()) {
     125                for (byte[] segmentBytes : segmentData.getSegments(segmentType)) {
     126                    if (reader.canProcess(segmentBytes, segmentType)) {
     127                        reader.extract(segmentBytes, metadata, segmentType);
     128                    }
     129                }
     130            }
     131        }
    115132    }
    116133
     
    120137    }
    121138}
    122 
  • trunk/src/com/drew/imaging/jpeg/JpegProcessingException.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.imaging.jpeg;
     
    2525
    2626/**
    27  * An exception class thrown upon unexpected and fatal conditions while processing a Jpeg file.
     27 * An exception class thrown upon unexpected and fatal conditions while processing a JPEG file.
    2828 *
    29  * @author Drew Noakes http://drewnoakes.com
     29 * @author Drew Noakes https://drewnoakes.com
    3030 */
    3131public class JpegProcessingException extends ImageProcessingException
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentData.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.imaging.jpeg;
     
    2424import com.drew.lang.annotations.Nullable;
    2525
    26 import java.io.*;
    27 import java.util.ArrayList;
    28 import java.util.HashMap;
    29 import java.util.List;
     26import java.util.*;
    3027
    3128/**
    32  * Holds a collection of Jpeg data segments.  This need not necessarily be all segments
    33  * within the Jpeg.  For example, it may be convenient to store only the non-image
    34  * segments when analysing (or serializing) metadata.
    35  * <p/>
    36  * Segments are keyed via their segment marker (a byte).  Where multiple segments use the
    37  * same segment marker, they will all be stored and available.
    38  *
    39  * @author Drew Noakes http://drewnoakes.com
     29 * Holds a collection of JPEG data segments.  This need not necessarily be all segments
     30 * within the JPEG. For example, it may be convenient to store only the non-image
     31 * segments when analysing metadata.
     32 * <p>
     33 * Segments are keyed via their {@link JpegSegmentType}. Where multiple segments use the
     34 * same segment type, they will all be stored and available.
     35 * <p>
     36 * Each segment type may contain multiple entries. Conceptually the model is:
     37 * <code>Map&lt;JpegSegmentType, Collection&lt;byte[]&gt;&gt;</code>. This class provides
     38 * convenience methods around that structure.
     39 *
     40 * @author Drew Noakes https://drewnoakes.com
    4041 */
    41 public class JpegSegmentData implements Serializable
     42public class JpegSegmentData
    4243{
    43     private static final long serialVersionUID = 7110175216435025451L;
    44    
    45     /** A map of byte[], keyed by the segment marker */
     44    // TODO key this on JpegSegmentType rather than Byte, and hopefully lose much of the use of 'byte' with this class
    4645    @NotNull
    4746    private final HashMap<Byte, List<byte[]>> _segmentDataMap = new HashMap<Byte, List<byte[]>>(10);
     
    4948    /**
    5049     * Adds segment bytes to the collection.
    51      * @param segmentMarker
    52      * @param segmentBytes
    53      */
    54     @SuppressWarnings({ "MismatchedQueryAndUpdateOfCollection" })
    55     public void addSegment(byte segmentMarker, @NotNull byte[] segmentBytes)
    56     {
    57         final List<byte[]> segmentList = getOrCreateSegmentList(segmentMarker);
    58         segmentList.add(segmentBytes);
    59     }
    60 
    61     /**
    62      * Gets the first Jpeg segment data for the specified marker.
    63      * @param segmentMarker the byte identifier for the desired segment
     50     *
     51     * @param segmentType  the type of the segment being added
     52     * @param segmentBytes the byte array holding data for the segment being added
     53     */
     54    @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
     55    public void addSegment(byte segmentType, @NotNull byte[] segmentBytes)
     56    {
     57        getOrCreateSegmentList(segmentType).add(segmentBytes);
     58    }
     59
     60    /**
     61     * Gets the set of JPEG segment type identifiers.
     62     */
     63    public Iterable<JpegSegmentType> getSegmentTypes()
     64    {
     65        Set<JpegSegmentType> segmentTypes = new HashSet<JpegSegmentType>();
     66
     67        for (Byte segmentTypeByte : _segmentDataMap.keySet())
     68        {
     69            JpegSegmentType segmentType = JpegSegmentType.fromByte(segmentTypeByte);
     70            if (segmentType == null) {
     71                throw new IllegalStateException("Should not have a segmentTypeByte that is not in the enum: " + Integer.toHexString(segmentTypeByte));
     72            }
     73            segmentTypes.add(segmentType);
     74        }
     75
     76        return segmentTypes;
     77    }
     78
     79    /**
     80     * Gets the first JPEG segment data for the specified type.
     81     *
     82     * @param segmentType the JpegSegmentType for the desired segment
    6483     * @return a byte[] containing segment data or null if no data exists for that segment
    6584     */
    6685    @Nullable
    67     public byte[] getSegment(byte segmentMarker)
    68     {
    69         return getSegment(segmentMarker, 0);
    70     }
    71 
    72     /**
    73      * Gets segment data for a specific occurrence and marker.  Use this method when more than one occurrence
    74      * of segment data for a given marker exists.
    75      * @param segmentMarker identifies the required segment
    76      * @param occurrence the zero-based index of the occurrence
    77      * @return the segment data as a byte[], or null if no segment exists for the marker & occurrence
    78      */
    79     @Nullable
    80     public byte[] getSegment(byte segmentMarker, int occurrence)
    81     {
    82         final List<byte[]> segmentList = getSegmentList(segmentMarker);
    83 
    84         if (segmentList==null || segmentList.size()<=occurrence)
    85             return null;
    86         else
    87             return segmentList.get(occurrence);
    88     }
    89 
    90     /**
    91      * Returns all instances of a given Jpeg segment.  If no instances exist, an empty sequence is returned.
    92      *
    93      * @param segmentMarker a number which identifies the type of Jpeg segment being queried
    94      * @return zero or more byte arrays, each holding the data of a Jpeg segment
    95      */
    96     @NotNull
    97     public Iterable<byte[]> getSegments(byte segmentMarker)
    98     {
    99         final List<byte[]> segmentList = getSegmentList(segmentMarker);
    100         return segmentList==null ? new ArrayList<byte[]>() : segmentList;
    101     }
    102 
    103     @Nullable
    104     public List<byte[]> getSegmentList(byte segmentMarker)
    105     {
    106         return _segmentDataMap.get(Byte.valueOf(segmentMarker));
    107     }
    108 
    109     @NotNull
    110     private List<byte[]> getOrCreateSegmentList(byte segmentMarker)
     86    public byte[] getSegment(byte segmentType)
     87    {
     88        return getSegment(segmentType, 0);
     89    }
     90
     91    /**
     92     * Gets the first JPEG segment data for the specified type.
     93     *
     94     * @param segmentType the JpegSegmentType for the desired segment
     95     * @return a byte[] containing segment data or null if no data exists for that segment
     96     */
     97    @Nullable
     98    public byte[] getSegment(@NotNull JpegSegmentType segmentType)
     99    {
     100        return getSegment(segmentType.byteValue, 0);
     101    }
     102
     103    /**
     104     * Gets segment data for a specific occurrence and type.  Use this method when more than one occurrence
     105     * of segment data for a given type exists.
     106     *
     107     * @param segmentType identifies the required segment
     108     * @param occurrence  the zero-based index of the occurrence
     109     * @return the segment data as a byte[], or null if no segment exists for the type &amp; occurrence
     110     */
     111    @Nullable
     112    public byte[] getSegment(@NotNull JpegSegmentType segmentType, int occurrence)
     113    {
     114        return getSegment(segmentType.byteValue, occurrence);
     115    }
     116
     117    /**
     118     * Gets segment data for a specific occurrence and type.  Use this method when more than one occurrence
     119     * of segment data for a given type exists.
     120     *
     121     * @param segmentType identifies the required segment
     122     * @param occurrence  the zero-based index of the occurrence
     123     * @return the segment data as a byte[], or null if no segment exists for the type &amp; occurrence
     124     */
     125    @Nullable
     126    public byte[] getSegment(byte segmentType, int occurrence)
     127    {
     128        final List<byte[]> segmentList = getSegmentList(segmentType);
     129
     130        return segmentList != null && segmentList.size() > occurrence
     131                ? segmentList.get(occurrence)
     132                : null;
     133    }
     134
     135    /**
     136     * Returns all instances of a given JPEG segment.  If no instances exist, an empty sequence is returned.
     137     *
     138     * @param segmentType a number which identifies the type of JPEG segment being queried
     139     * @return zero or more byte arrays, each holding the data of a JPEG segment
     140     */
     141    @NotNull
     142    public Iterable<byte[]> getSegments(@NotNull JpegSegmentType segmentType)
     143    {
     144        return getSegments(segmentType.byteValue);
     145    }
     146
     147    /**
     148     * Returns all instances of a given JPEG segment.  If no instances exist, an empty sequence is returned.
     149     *
     150     * @param segmentType a number which identifies the type of JPEG segment being queried
     151     * @return zero or more byte arrays, each holding the data of a JPEG segment
     152     */
     153    @NotNull
     154    public Iterable<byte[]> getSegments(byte segmentType)
     155    {
     156        final List<byte[]> segmentList = getSegmentList(segmentType);
     157        return segmentList == null ? new ArrayList<byte[]>() : segmentList;
     158    }
     159
     160    @Nullable
     161    private List<byte[]> getSegmentList(byte segmentType)
     162    {
     163        return _segmentDataMap.get(segmentType);
     164    }
     165
     166    @NotNull
     167    private List<byte[]> getOrCreateSegmentList(byte segmentType)
    111168    {
    112169        List<byte[]> segmentList;
    113         if (_segmentDataMap.containsKey(segmentMarker)) {
    114             segmentList = _segmentDataMap.get(segmentMarker);
     170        if (_segmentDataMap.containsKey(segmentType)) {
     171            segmentList = _segmentDataMap.get(segmentType);
    115172        } else {
    116173            segmentList = new ArrayList<byte[]>();
    117             _segmentDataMap.put(segmentMarker, segmentList);
     174            _segmentDataMap.put(segmentType, segmentList);
    118175        }
    119176        return segmentList;
     
    121178
    122179    /**
    123      * Returns the count of segment data byte arrays stored for a given segment marker.
    124      * @param segmentMarker identifies the required segment
     180     * Returns the count of segment data byte arrays stored for a given segment type.
     181     *
     182     * @param segmentType identifies the required segment
    125183     * @return the segment count (zero if no segments exist).
    126184     */
    127     public int getSegmentCount(byte segmentMarker)
    128     {
    129         final List<byte[]> segmentList = getSegmentList(segmentMarker);
     185    public int getSegmentCount(@NotNull JpegSegmentType segmentType)
     186    {
     187        return getSegmentCount(segmentType.byteValue);
     188    }
     189
     190    /**
     191     * Returns the count of segment data byte arrays stored for a given segment type.
     192     *
     193     * @param segmentType identifies the required segment
     194     * @return the segment count (zero if no segments exist).
     195     */
     196    public int getSegmentCount(byte segmentType)
     197    {
     198        final List<byte[]> segmentList = getSegmentList(segmentType);
    130199        return segmentList == null ? 0 : segmentList.size();
    131200    }
     
    133202    /**
    134203     * Removes a specified instance of a segment's data from the collection.  Use this method when more than one
    135      * occurrence of segment data for a given marker exists.
    136      * @param segmentMarker identifies the required segment
    137      * @param occurrence the zero-based index of the segment occurrence to remove.
    138      */
    139     @SuppressWarnings({ "MismatchedQueryAndUpdateOfCollection" })
    140     public void removeSegmentOccurrence(byte segmentMarker, int occurrence)
    141     {
    142         final List<byte[]> segmentList = _segmentDataMap.get(Byte.valueOf(segmentMarker));
     204     * occurrence of segment data exists for a given type exists.
     205     *
     206     * @param segmentType identifies the required segment
     207     * @param occurrence  the zero-based index of the segment occurrence to remove.
     208     */
     209    @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
     210    public void removeSegmentOccurrence(@NotNull JpegSegmentType segmentType, int occurrence)
     211    {
     212        removeSegmentOccurrence(segmentType.byteValue, occurrence);
     213    }
     214
     215    /**
     216     * Removes a specified instance of a segment's data from the collection.  Use this method when more than one
     217     * occurrence of segment data exists for a given type exists.
     218     *
     219     * @param segmentType identifies the required segment
     220     * @param occurrence  the zero-based index of the segment occurrence to remove.
     221     */
     222    @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
     223    public void removeSegmentOccurrence(byte segmentType, int occurrence)
     224    {
     225        final List<byte[]> segmentList = _segmentDataMap.get(segmentType);
    143226        segmentList.remove(occurrence);
    144227    }
    145228
    146229    /**
    147      * Removes all segments from the collection having the specified marker.
    148      * @param segmentMarker identifies the required segment
    149      */
    150     public void removeSegment(byte segmentMarker)
    151     {
    152         _segmentDataMap.remove(Byte.valueOf(segmentMarker));
    153     }
    154 
    155     /**
    156      * Determines whether data is present for a given segment marker.
    157      * @param segmentMarker identifies the required segment
     230     * Removes all segments from the collection having the specified type.
     231     *
     232     * @param segmentType identifies the required segment
     233     */
     234    public void removeSegment(@NotNull JpegSegmentType segmentType)
     235    {
     236        removeSegment(segmentType.byteValue);
     237    }
     238
     239    /**
     240     * Removes all segments from the collection having the specified type.
     241     *
     242     * @param segmentType identifies the required segment
     243     */
     244    public void removeSegment(byte segmentType)
     245    {
     246        _segmentDataMap.remove(segmentType);
     247    }
     248
     249    /**
     250     * Determines whether data is present for a given segment type.
     251     *
     252     * @param segmentType identifies the required segment
    158253     * @return true if data exists, otherwise false
    159254     */
    160     public boolean containsSegment(byte segmentMarker)
    161     {
    162         return _segmentDataMap.containsKey(Byte.valueOf(segmentMarker));
    163     }
    164 
    165     /**
    166      * Serialises the contents of a JpegSegmentData to a file.
    167      * @param file to file to write from
    168      * @param segmentData the data to write
    169      * @throws IOException if problems occur while writing
    170      */
    171     public static void toFile(@NotNull File file, @NotNull JpegSegmentData segmentData) throws IOException
    172     {
    173         FileOutputStream fileOutputStream = null;
    174         try
    175         {
    176             fileOutputStream = new FileOutputStream(file);
    177             new ObjectOutputStream(fileOutputStream).writeObject(segmentData);
    178         }
    179         finally
    180         {
    181             if (fileOutputStream!=null)
    182                 fileOutputStream.close();
    183         }
    184     }
    185 
    186     /**
    187      * Deserialises the contents of a JpegSegmentData from a file.
    188      * @param file the file to read from
    189      * @return the JpegSegmentData as read
    190      * @throws IOException if problems occur while reading
    191      * @throws ClassNotFoundException if problems occur while deserialising
    192      */
    193     @NotNull
    194     public static JpegSegmentData fromFile(@NotNull File file) throws IOException, ClassNotFoundException
    195     {
    196         ObjectInputStream inputStream = null;
    197         try
    198         {
    199             inputStream = new ObjectInputStream(new FileInputStream(file));
    200             return (JpegSegmentData)inputStream.readObject();
    201         }
    202         finally
    203         {
    204             if (inputStream!=null)
    205                 inputStream.close();
    206         }
     255    public boolean containsSegment(@NotNull JpegSegmentType segmentType)
     256    {
     257        return containsSegment(segmentType.byteValue);
     258    }
     259
     260    /**
     261     * Determines whether data is present for a given segment type.
     262     *
     263     * @param segmentType identifies the required segment
     264     * @return true if data exists, otherwise false
     265     */
     266    public boolean containsSegment(byte segmentType)
     267    {
     268        return _segmentDataMap.containsKey(segmentType);
    207269    }
    208270}
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.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.imaging.jpeg;
    2222
     23import com.drew.lang.SequentialReader;
     24import com.drew.lang.StreamReader;
    2325import com.drew.lang.annotations.NotNull;
    2426import com.drew.lang.annotations.Nullable;
    2527
    26 import java.io.*;
     28import java.io.File;
     29import java.io.FileInputStream;
     30import java.io.IOException;
     31import java.util.HashSet;
     32import java.util.Set;
    2733
    2834/**
    29  * Performs read functions of Jpeg files, returning specific file segments.
    30  * @author  Drew Noakes http://drewnoakes.com
     35 * Performs read functions of JPEG files, returning specific file segments.
     36 * <p>
     37 * JPEG files are composed of a sequence of consecutive JPEG 'segments'. Each is identified by one of a set of byte
     38 * values, modelled in the {@link JpegSegmentType} enumeration. Use <code>readSegments</code> to read out the some
     39 * or all segments into a {@link JpegSegmentData} object, from which the raw JPEG segment byte arrays may be accessed.
     40 *
     41 * @author Drew Noakes https://drewnoakes.com
    3142 */
    3243public class JpegSegmentReader
    3344{
    34     // TODO add a findAvailableSegments() method
    35     // TODO add more segment identifiers
    36     // TODO add a getSegmentDescription() method, returning for example 'App1 application data segment, commonly containing Exif data'
    37 
    38     @NotNull
    39     private final JpegSegmentData _segmentData;
    40 
    4145    /**
    4246     * Private, because this segment crashes my algorithm, and searching for it doesn't work (yet).
    4347     */
    44     private static final byte SEGMENT_SOS = (byte)0xDA;
     48    private static final byte SEGMENT_SOS = (byte) 0xDA;
    4549
    4650    /**
    4751     * Private, because one wouldn't search for it.
    4852     */
    49     private static final byte MARKER_EOI = (byte)0xD9;
    50 
    51     /** APP0 Jpeg segment identifier -- JFIF data (also JFXX apparently). */
    52     public static final byte SEGMENT_APP0 = (byte)0xE0;
    53     /** APP1 Jpeg segment identifier -- where Exif data is kept.  XMP data is also kept in here, though usually in a second instance. */
    54     public static final byte SEGMENT_APP1 = (byte)0xE1;
    55     /** APP2 Jpeg segment identifier. */
    56     public static final byte SEGMENT_APP2 = (byte)0xE2;
    57     /** APP3 Jpeg segment identifier. */
    58     public static final byte SEGMENT_APP3 = (byte)0xE3;
    59     /** APP4 Jpeg segment identifier. */
    60     public static final byte SEGMENT_APP4 = (byte)0xE4;
    61     /** APP5 Jpeg segment identifier. */
    62     public static final byte SEGMENT_APP5 = (byte)0xE5;
    63     /** APP6 Jpeg segment identifier. */
    64     public static final byte SEGMENT_APP6 = (byte)0xE6;
    65     /** APP7 Jpeg segment identifier. */
    66     public static final byte SEGMENT_APP7 = (byte)0xE7;
    67     /** APP8 Jpeg segment identifier. */
    68     public static final byte SEGMENT_APP8 = (byte)0xE8;
    69     /** APP9 Jpeg segment identifier. */
    70     public static final byte SEGMENT_APP9 = (byte)0xE9;
    71     /** APPA (App10) Jpeg segment identifier -- can hold Unicode comments. */
    72     public static final byte SEGMENT_APPA = (byte)0xEA;
    73     /** APPB (App11) Jpeg segment identifier. */
    74     public static final byte SEGMENT_APPB = (byte)0xEB;
    75     /** APPC (App12) Jpeg segment identifier. */
    76     public static final byte SEGMENT_APPC = (byte)0xEC;
    77     /** APPD (App13) Jpeg segment identifier -- IPTC data in here. */
    78     public static final byte SEGMENT_APPD = (byte)0xED;
    79     /** APPE (App14) Jpeg segment identifier. */
    80     public static final byte SEGMENT_APPE = (byte)0xEE;
    81     /** APPF (App15) Jpeg segment identifier. */
    82     public static final byte SEGMENT_APPF = (byte)0xEF;
    83     /** Start Of Image segment identifier. */
    84     public static final byte SEGMENT_SOI = (byte)0xD8;
    85     /** Define Quantization Table segment identifier. */
    86     public static final byte SEGMENT_DQT = (byte)0xDB;
    87     /** Define Huffman Table segment identifier. */
    88     public static final byte SEGMENT_DHT = (byte)0xC4;
    89     /** Start-of-Frame Zero segment identifier. */
    90     public static final byte SEGMENT_SOF0 = (byte)0xC0;
    91     /** Jpeg comment segment identifier. */
    92     public static final byte SEGMENT_COM = (byte)0xFE;
     53    private static final byte MARKER_EOI = (byte) 0xD9;
    9354
    9455    /**
    95      * Creates a JpegSegmentReader for a specific file.
    96      * @param file the Jpeg file to read segments from
     56     * Processes the provided JPEG data, and extracts the specified JPEG segments into a {@link JpegSegmentData} object.
     57     * <p>
     58     * Will not return SOS (start of scan) or EOI (end of image) segments.
     59     *
     60     * @param file a {@link File} from which the JPEG data will be read.
     61     * @param segmentTypes the set of JPEG segments types that are to be returned. If this argument is <code>null</code>
     62     *                     then all found segment types are returned.
    9763     */
    98     @SuppressWarnings({ "ConstantConditions" })
    99     public JpegSegmentReader(@NotNull File file) throws JpegProcessingException, IOException
     64    @NotNull
     65    public static JpegSegmentData readSegments(@NotNull File file, @Nullable Iterable<JpegSegmentType> segmentTypes) throws JpegProcessingException, IOException
    10066    {
    101         if (file==null)
    102             throw new NullPointerException();
    103 
    104         InputStream inputStream = null;
     67        FileInputStream stream = null;
    10568        try {
    106             inputStream = new FileInputStream(file);
    107             _segmentData = readSegments(new BufferedInputStream(inputStream), false);
     69            stream = new FileInputStream(file);
     70            return readSegments(new StreamReader(stream), segmentTypes);
    10871        } finally {
    109             if (inputStream != null)
    110                 inputStream.close();
     72            if (stream != null) {
     73                stream.close();
     74            }
    11175        }
    11276    }
    11377
    11478    /**
    115      * Creates a JpegSegmentReader for a byte array.
    116      * @param fileContents the byte array containing Jpeg data
     79     * Processes the provided JPEG data, and extracts the specified JPEG segments into a {@link JpegSegmentData} object.
     80     * <p>
     81     * Will not return SOS (start of scan) or EOI (end of image) segments.
     82     *
     83     * @param reader a {@link SequentialReader} from which the JPEG data will be read. It must be positioned at the
     84     *               beginning of the JPEG data stream.
     85     * @param segmentTypes the set of JPEG segments types that are to be returned. If this argument is <code>null</code>
     86     *                     then all found segment types are returned.
    11787     */
    118     @SuppressWarnings({ "ConstantConditions" })
    119     public JpegSegmentReader(@NotNull byte[] fileContents) throws JpegProcessingException
     88    @NotNull
     89    public static JpegSegmentData readSegments(@NotNull final SequentialReader reader, @Nullable Iterable<JpegSegmentType> segmentTypes) throws JpegProcessingException, IOException
    12090    {
    121         if (fileContents==null)
    122             throw new NullPointerException();
     91        // Must be big-endian
     92        assert (reader.isMotorolaByteOrder());
    12393
    124         BufferedInputStream stream = new BufferedInputStream(new ByteArrayInputStream(fileContents));
    125         _segmentData = readSegments(stream, false);
     94        // first two bytes should be JPEG magic number
     95        final int magicNumber = reader.getUInt16();
     96        if (magicNumber != 0xFFD8) {
     97            throw new JpegProcessingException("JPEG data is expected to begin with 0xFFD8 (ÿØ) not 0x" + Integer.toHexString(magicNumber));
     98        }
     99
     100        Set<Byte> segmentTypeBytes = null;
     101        if (segmentTypes != null) {
     102            segmentTypeBytes = new HashSet<Byte>();
     103            for (JpegSegmentType segmentType : segmentTypes) {
     104                segmentTypeBytes.add(segmentType.byteValue);
     105            }
     106        }
     107
     108        JpegSegmentData segmentData = new JpegSegmentData();
     109
     110        do {
     111            // Find the segment marker. Markers are zero or more 0xFF bytes, followed
     112            // by a 0xFF and then a byte not equal to 0x00 or 0xFF.
     113
     114            final short segmentIdentifier = reader.getUInt8();
     115
     116            // We must have at least one 0xFF byte
     117            if (segmentIdentifier != 0xFF)
     118                throw new JpegProcessingException("Expected JPEG segment start identifier 0xFF, not 0x" + Integer.toHexString(segmentIdentifier).toUpperCase());
     119
     120            // Read until we have a non-0xFF byte. This identifies the segment type.
     121            byte segmentType = reader.getInt8();
     122            while (segmentType == (byte)0xFF)
     123                segmentType = reader.getInt8();
     124
     125            if (segmentType == 0)
     126                throw new JpegProcessingException("Expected non-zero byte as part of JPEG marker identifier");
     127
     128            if (segmentType == SEGMENT_SOS) {
     129                // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would
     130                // have to search for the two bytes: 0xFF 0xD9 (EOI).
     131                // It comes last so simply return at this point
     132                return segmentData;
     133            }
     134
     135            if (segmentType == MARKER_EOI) {
     136                // the 'End-Of-Image' segment -- this should never be found in this fashion
     137                return segmentData;
     138            }
     139
     140            // next 2-bytes are <segment-size>: [high-byte] [low-byte]
     141            int segmentLength = reader.getUInt16();
     142
     143            // segment length includes size bytes, so subtract two
     144            segmentLength -= 2;
     145
     146            if (segmentLength < 0)
     147                throw new JpegProcessingException("JPEG segment size would be less than zero");
     148
     149            // Check whether we are interested in this segment
     150            if (segmentTypeBytes == null || segmentTypeBytes.contains(segmentType)) {
     151                byte[] segmentBytes = reader.getBytes(segmentLength);
     152                assert (segmentLength == segmentBytes.length);
     153                segmentData.addSegment(segmentType, segmentBytes);
     154            } else {
     155                // Some if the JPEG is truncated, just return what data we've already gathered
     156                if (!reader.trySkip(segmentLength)) {
     157                    return segmentData;
     158                }
     159            }
     160
     161        } while (true);
    126162    }
    127163
    128     /**
    129      * Creates a JpegSegmentReader for an InputStream.
    130      * @param inputStream the InputStream containing Jpeg data
    131      */
    132     @SuppressWarnings({ "ConstantConditions" })
    133     public JpegSegmentReader(@NotNull InputStream inputStream, boolean waitForBytes) throws JpegProcessingException
     164    private JpegSegmentReader() throws Exception
    134165    {
    135         if (inputStream==null)
    136             throw new NullPointerException();
    137 
    138         BufferedInputStream bufferedInputStream = inputStream instanceof BufferedInputStream
    139                 ? (BufferedInputStream)inputStream
    140                 : new BufferedInputStream(inputStream);
    141 
    142         _segmentData = readSegments(bufferedInputStream, waitForBytes);
    143     }
    144 
    145     /**
    146      * Reads the first instance of a given Jpeg segment, returning the contents as
    147      * a byte array.
    148      * @param segmentMarker the byte identifier for the desired segment
    149      * @return the byte array if found, else null
    150      */
    151     @Nullable
    152     public byte[] readSegment(byte segmentMarker)
    153     {
    154         return readSegment(segmentMarker, 0);
    155     }
    156 
    157     /**
    158      * Reads the Nth instance of a given Jpeg segment, returning the contents as a byte array.
    159      *
    160      * @param segmentMarker the byte identifier for the desired segment
    161      * @param occurrence the occurrence of the specified segment within the jpeg file
    162      * @return the byte array if found, else null
    163      */
    164     @Nullable
    165     public byte[] readSegment(byte segmentMarker, int occurrence)
    166     {
    167         return _segmentData.getSegment(segmentMarker, occurrence);
    168     }
    169 
    170     /**
    171      * Returns all instances of a given Jpeg segment.  If no instances exist, an empty sequence is returned.
    172      *
    173      * @param segmentMarker a number which identifies the type of Jpeg segment being queried
    174      * @return zero or more byte arrays, each holding the data of a Jpeg segment
    175      */
    176     @NotNull
    177     public Iterable<byte[]> readSegments(byte segmentMarker)
    178     {
    179         return _segmentData.getSegments(segmentMarker);
    180     }
    181 
    182     /**
    183      * Returns the number of segments having the specified JPEG segment marker.
    184      * @param segmentMarker the JPEG segment identifying marker.
    185      * @return the count of matching segments.
    186      */
    187     public final int getSegmentCount(byte segmentMarker)
    188     {
    189         return _segmentData.getSegmentCount(segmentMarker);
    190     }
    191 
    192     /**
    193      * Returns the JpegSegmentData object used by this reader.
    194      * @return the JpegSegmentData object.
    195      */
    196     @NotNull
    197     public final JpegSegmentData getSegmentData()
    198     {
    199         return _segmentData;
    200     }
    201 
    202     @NotNull
    203     private JpegSegmentData readSegments(@NotNull final BufferedInputStream jpegInputStream, boolean waitForBytes) throws JpegProcessingException
    204     {
    205         JpegSegmentData segmentData = new JpegSegmentData();
    206 
    207         try {
    208             int offset = 0;
    209             // first two bytes should be jpeg magic number
    210             byte[] headerBytes = new byte[2];
    211             if (jpegInputStream.read(headerBytes, 0, 2)!=2)
    212                 throw new JpegProcessingException("not a jpeg file");
    213             final boolean hasValidHeader = (headerBytes[0] & 0xFF) == 0xFF && (headerBytes[1] & 0xFF) == 0xD8;
    214             if (!hasValidHeader)
    215                 throw new JpegProcessingException("not a jpeg file");
    216 
    217             offset += 2;
    218             do {
    219                 // need four bytes from stream for segment header before continuing
    220                 if (!checkForBytesOnStream(jpegInputStream, 4, waitForBytes))
    221                     throw new JpegProcessingException("stream ended before segment header could be read");
    222 
    223                 // next byte is 0xFF
    224                 byte segmentIdentifier = (byte)(jpegInputStream.read() & 0xFF);
    225                 if ((segmentIdentifier & 0xFF) != 0xFF) {
    226                     throw new JpegProcessingException("expected jpeg segment start identifier 0xFF at offset " + offset + ", not 0x" + Integer.toHexString(segmentIdentifier & 0xFF));
    227                 }
    228                 offset++;
    229                 // next byte is <segment-marker>
    230                 byte thisSegmentMarker = (byte)(jpegInputStream.read() & 0xFF);
    231                 offset++;
    232                 // next 2-bytes are <segment-size>: [high-byte] [low-byte]
    233                 byte[] segmentLengthBytes = new byte[2];
    234                 if (jpegInputStream.read(segmentLengthBytes, 0, 2) != 2)
    235                     throw new JpegProcessingException("Jpeg data ended unexpectedly.");
    236                 offset += 2;
    237                 int segmentLength = ((segmentLengthBytes[0] << 8) & 0xFF00) | (segmentLengthBytes[1] & 0xFF);
    238                 // segment length includes size bytes, so subtract two
    239                 segmentLength -= 2;
    240                 if (!checkForBytesOnStream(jpegInputStream, segmentLength, waitForBytes))
    241                     throw new JpegProcessingException("segment size would extend beyond file stream length");
    242                 if (segmentLength < 0)
    243                     throw new JpegProcessingException("segment size would be less than zero");
    244                 byte[] segmentBytes = new byte[segmentLength];
    245                 if (jpegInputStream.read(segmentBytes, 0, segmentLength) != segmentLength)
    246                     throw new JpegProcessingException("Jpeg data ended unexpectedly.");
    247                 offset += segmentLength;
    248                 if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF)) {
    249                     // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would
    250                     // have to search for the two bytes: 0xFF 0xD9 (EOI).
    251                     // It comes last so simply return at this point
    252                     return segmentData;
    253                 } else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF)) {
    254                     // the 'End-Of-Image' segment -- this should never be found in this fashion
    255                     return segmentData;
    256                 } else {
    257                     segmentData.addSegment(thisSegmentMarker, segmentBytes);
    258                 }
    259             } while (true);
    260         } catch (IOException ioe) {
    261             throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);
    262         } finally {
    263             try {
    264                 if (jpegInputStream != null) {
    265                     jpegInputStream.close();
    266                 }
    267             } catch (IOException ioe) {
    268                 throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);
    269             }
    270         }
    271     }
    272 
    273     private boolean checkForBytesOnStream(@NotNull BufferedInputStream stream, int bytesNeeded, boolean waitForBytes) throws IOException
    274     {
    275         // NOTE  waiting is essential for network streams where data can be delayed, but it is not necessary for byte[] or filesystems
    276 
    277         if (!waitForBytes)
    278             return bytesNeeded <= stream.available();
    279 
    280         int count = 40; // * 100ms = approx 4 seconds
    281         while (count > 0) {
    282             if (bytesNeeded <= stream.available())
    283                return true;
    284             try {
    285                 Thread.sleep(100);
    286             } catch (InterruptedException e) {
    287                 // continue
    288             }
    289             count--;
    290         }
    291         return false;
     166        throw new Exception("Not intended for instantiation.");
    292167    }
    293168}
Note: See TracChangeset for help on using the changeset viewer.