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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • 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.