Ignore:
Timestamp:
2013-08-09T18:05:11+02:00 (13 years ago)
Author:
bastiK
Message:

applied #8895 - Upgrade metadata-extractor to v. 2.6.4 (patch by ebourg)

File:
1 edited

Legend:

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

    r4231 r6127  
    11/*
    2  * JpegSegmentReader.java
    3  *
    4  * This class written by Drew Noakes, in accordance with the Jpeg specification.
    5  *
    6  * This is public domain software - that is, you can do whatever you want
    7  * with it, and include it software that is licensed under the GNU or the
    8  * BSD license, or whatever other licence you choose, including proprietary
    9  * closed source licenses.  I do ask that you leave this header in tact.
    10  *
    11  * If you make modifications to this code that you think would benefit the
    12  * wider community, please send me a copy and I'll post it on my site.
    13  *
    14  * If you make use of this code, I'd appreciate hearing about it.
    15  *   drew@drewnoakes.com
    16  * Latest version of this software kept at
    17  *   http://drewnoakes.com/
    18  *
    19  * Created by dnoakes on 04-Nov-2002 00:54:00 using IntelliJ IDEA
     2 * Copyright 2002-2012 Drew Noakes
     3 *
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
     7 *
     8 *        http://www.apache.org/licenses/LICENSE-2.0
     9 *
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
     15 *
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    2020 */
    2121package com.drew.imaging.jpeg;
    2222
     23import com.drew.lang.annotations.NotNull;
     24import com.drew.lang.annotations.Nullable;
     25
    2326import java.io.*;
    2427
    2528/**
    2629 * Performs read functions of Jpeg files, returning specific file segments.
    27  * TODO add a findAvailableSegments() method
    28  * TODO add more segment identifiers
    29  * TODO add a getSegmentDescription() method, returning for example 'App1 application data segment, commonly containing Exif data'
    3030 * @author  Drew Noakes http://drewnoakes.com
    3131 */
    3232public class JpegSegmentReader
    3333{
    34     // Jpeg data can be sourced from either a file, byte[] or InputStream
    35 
    36     /** Jpeg file */
    37     private final File _file;
    38     /** Jpeg data as byte array */
    39     private final byte[] _data;
    40     /** Jpeg data as an InputStream */
    41     private final InputStream _stream;
    42 
    43     private JpegSegmentData _segmentData;
    44 
    45     /**
    46      * Private, because this segment crashes my algorithm, and searching for
    47      * it doesn't work (yet).
     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
     41    /**
     42     * Private, because this segment crashes my algorithm, and searching for it doesn't work (yet).
    4843     */
    4944    private static final byte SEGMENT_SOS = (byte)0xDA;
     
    5449    private static final byte MARKER_EOI = (byte)0xD9;
    5550
    56     /** APP0 Jpeg segment identifier -- Jfif data. */
     51    /** APP0 Jpeg segment identifier -- JFIF data (also JFXX apparently). */
    5752    public static final byte SEGMENT_APP0 = (byte)0xE0;
    58     /** APP1 Jpeg segment identifier -- where Exif data is kept. */
     53    /** APP1 Jpeg segment identifier -- where Exif data is kept.  XMP data is also kept in here, though usually in a second instance. */
    5954    public static final byte SEGMENT_APP1 = (byte)0xE1;
    6055    /** APP2 Jpeg segment identifier. */
     
    7469    /** APP9 Jpeg segment identifier. */
    7570    public static final byte SEGMENT_APP9 = (byte)0xE9;
    76     /** APPA Jpeg segment identifier -- can hold Unicode comments. */
     71    /** APPA (App10) Jpeg segment identifier -- can hold Unicode comments. */
    7772    public static final byte SEGMENT_APPA = (byte)0xEA;
    78     /** APPB Jpeg segment identifier. */
     73    /** APPB (App11) Jpeg segment identifier. */
    7974    public static final byte SEGMENT_APPB = (byte)0xEB;
    80     /** APPC Jpeg segment identifier. */
     75    /** APPC (App12) Jpeg segment identifier. */
    8176    public static final byte SEGMENT_APPC = (byte)0xEC;
    82     /** APPD Jpeg segment identifier -- IPTC data in here. */
     77    /** APPD (App13) Jpeg segment identifier -- IPTC data in here. */
    8378    public static final byte SEGMENT_APPD = (byte)0xED;
    84     /** APPE Jpeg segment identifier. */
     79    /** APPE (App14) Jpeg segment identifier. */
    8580    public static final byte SEGMENT_APPE = (byte)0xEE;
    86     /** APPF Jpeg segment identifier. */
     81    /** APPF (App15) Jpeg segment identifier. */
    8782    public static final byte SEGMENT_APPF = (byte)0xEF;
    8883    /** Start Of Image segment identifier. */
     
    10196     * @param file the Jpeg file to read segments from
    10297     */
    103     public JpegSegmentReader(File file) throws JpegProcessingException
    104     {
    105         _file = file;
    106         _data = null;
    107         _stream = null;
    108 
    109         readSegments();
     98    @SuppressWarnings({ "ConstantConditions" })
     99    public JpegSegmentReader(@NotNull File file) throws JpegProcessingException, IOException
     100    {
     101        if (file==null)
     102            throw new NullPointerException();
     103
     104        InputStream inputStream = null;
     105        try {
     106            inputStream = new FileInputStream(file);
     107            _segmentData = readSegments(new BufferedInputStream(inputStream), false);
     108        } finally {
     109            if (inputStream != null)
     110                inputStream.close();
     111        }
    110112    }
    111113
     
    114116     * @param fileContents the byte array containing Jpeg data
    115117     */
    116     public JpegSegmentReader(byte[] fileContents) throws JpegProcessingException
    117     {
    118         _file = null;
    119         _data = fileContents;
    120         _stream = null;
    121 
    122         readSegments();
    123     }
    124 
    125     public JpegSegmentReader(InputStream in) throws JpegProcessingException
    126     {
    127         _stream = in;
    128         _file = null;
    129         _data = null;
    130        
    131         readSegments();
    132     }
    133 
    134     public JpegSegmentReader(JpegSegmentData segmentData)
    135     {
    136         _file = null;
    137         _data = null;
    138         _stream = null;
    139 
    140         _segmentData = segmentData;
     118    @SuppressWarnings({ "ConstantConditions" })
     119    public JpegSegmentReader(@NotNull byte[] fileContents) throws JpegProcessingException
     120    {
     121        if (fileContents==null)
     122            throw new NullPointerException();
     123
     124        BufferedInputStream stream = new BufferedInputStream(new ByteArrayInputStream(fileContents));
     125        _segmentData = readSegments(stream, false);
     126    }
     127
     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
     134    {
     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);
    141143    }
    142144
     
    146148     * @param segmentMarker the byte identifier for the desired segment
    147149     * @return the byte array if found, else null
    148      * @throws JpegProcessingException for any problems processing the Jpeg data,
    149      *         including inner IOExceptions
    150      */
    151     public byte[] readSegment(byte segmentMarker) throws JpegProcessingException
     150     */
     151    @Nullable
     152    public byte[] readSegment(byte segmentMarker)
    152153    {
    153154        return readSegment(segmentMarker, 0);
     
    155156
    156157    /**
    157      * Reads the first instance of a given Jpeg segment, returning the contents as
    158      * a byte array.
     158     * Reads the Nth instance of a given Jpeg segment, returning the contents as a byte array.
     159     *
    159160     * @param segmentMarker the byte identifier for the desired segment
    160161     * @param occurrence the occurrence of the specified segment within the jpeg file
    161162     * @return the byte array if found, else null
    162163     */
     164    @Nullable
    163165    public byte[] readSegment(byte segmentMarker, int occurrence)
    164166    {
     
    166168    }
    167169
     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     */
    168187    public final int getSegmentCount(byte segmentMarker)
    169188    {
     
    171190    }
    172191
     192    /**
     193     * Returns the JpegSegmentData object used by this reader.
     194     * @return the JpegSegmentData object.
     195     */
     196    @NotNull
    173197    public final JpegSegmentData getSegmentData()
    174198    {
     
    176200    }
    177201
    178     private void readSegments() throws JpegProcessingException
    179     {
    180         _segmentData = new JpegSegmentData();
    181 
    182         BufferedInputStream inStream = getJpegInputStream();
     202    @NotNull
     203    private JpegSegmentData readSegments(@NotNull final BufferedInputStream jpegInputStream, boolean waitForBytes) throws JpegProcessingException
     204    {
     205        JpegSegmentData segmentData = new JpegSegmentData();
     206
    183207        try {
    184208            int offset = 0;
    185209            // first two bytes should be jpeg magic number
    186             if (!isValidJpegHeaderBytes(inStream)) {
     210            byte[] headerBytes = new byte[2];
     211            if (jpegInputStream.read(headerBytes, 0, 2)!=2)
    187212                throw new JpegProcessingException("not a jpeg file");
    188             }
     213            final boolean hasValidHeader = (headerBytes[0] & 0xFF) == 0xFF && (headerBytes[1] & 0xFF) == 0xD8;
     214            if (!hasValidHeader)
     215                throw new JpegProcessingException("not a jpeg file");
     216
    189217            offset += 2;
    190218            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
    191223                // next byte is 0xFF
    192                 byte segmentIdentifier = (byte)(inStream.read() & 0xFF);
     224                byte segmentIdentifier = (byte)(jpegInputStream.read() & 0xFF);
    193225                if ((segmentIdentifier & 0xFF) != 0xFF) {
    194226                    throw new JpegProcessingException("expected jpeg segment start identifier 0xFF at offset " + offset + ", not 0x" + Integer.toHexString(segmentIdentifier & 0xFF));
     
    196228                offset++;
    197229                // next byte is <segment-marker>
    198                 byte thisSegmentMarker = (byte)(inStream.read() & 0xFF);
     230                byte thisSegmentMarker = (byte)(jpegInputStream.read() & 0xFF);
    199231                offset++;
    200232                // next 2-bytes are <segment-size>: [high-byte] [low-byte]
    201233                byte[] segmentLengthBytes = new byte[2];
    202                 inStream.read(segmentLengthBytes, 0, 2);
     234                if (jpegInputStream.read(segmentLengthBytes, 0, 2) != 2)
     235                    throw new JpegProcessingException("Jpeg data ended unexpectedly.");
    203236                offset += 2;
    204237                int segmentLength = ((segmentLengthBytes[0] << 8) & 0xFF00) | (segmentLengthBytes[1] & 0xFF);
    205238                // segment length includes size bytes, so subtract two
    206239                segmentLength -= 2;
    207                 if (segmentLength > inStream.available())
     240                if (!checkForBytesOnStream(jpegInputStream, segmentLength, waitForBytes))
    208241                    throw new JpegProcessingException("segment size would extend beyond file stream length");
    209                 else if (segmentLength < 0)
     242                if (segmentLength < 0)
    210243                    throw new JpegProcessingException("segment size would be less than zero");
    211244                byte[] segmentBytes = new byte[segmentLength];
    212                 inStream.read(segmentBytes, 0, segmentLength);
     245                if (jpegInputStream.read(segmentBytes, 0, segmentLength) != segmentLength)
     246                    throw new JpegProcessingException("Jpeg data ended unexpectedly.");
    213247                offset += segmentLength;
    214248                if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF)) {
     
    216250                    // have to search for the two bytes: 0xFF 0xD9 (EOI).
    217251                    // It comes last so simply return at this point
    218                     return;
     252                    return segmentData;
    219253                } else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF)) {
    220254                    // the 'End-Of-Image' segment -- this should never be found in this fashion
    221                     return;
     255                    return segmentData;
    222256                } else {
    223                     _segmentData.addSegment(thisSegmentMarker, segmentBytes);
     257                    segmentData.addSegment(thisSegmentMarker, segmentBytes);
    224258                }
    225                 // didn't find the one we're looking for, loop through to the next segment
    226259            } while (true);
    227260        } catch (IOException ioe) {
    228             //throw new JpegProcessingException("IOException processing Jpeg file", ioe);
    229261            throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);
    230262        } finally {
    231263            try {
    232                 if (inStream != null) {
    233                     inStream.close();
     264                if (jpegInputStream != null) {
     265                    jpegInputStream.close();
    234266                }
    235267            } catch (IOException ioe) {
    236                 //throw new JpegProcessingException("IOException processing Jpeg file", ioe);
    237268                throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);
    238269            }
     
    240271    }
    241272
    242     /**
    243      * Private helper method to create a BufferedInputStream of Jpeg data from whichever
    244      * data source was specified upon construction of this instance.
    245      * @return a a BufferedInputStream of Jpeg data
    246      * @throws JpegProcessingException for any problems obtaining the stream
    247      */
    248     private BufferedInputStream getJpegInputStream() throws JpegProcessingException
    249     {
    250         if (_stream!=null) {
    251             if (_stream instanceof BufferedInputStream) {
    252                 return (BufferedInputStream) _stream;
    253             } else {
    254                 return new BufferedInputStream(_stream);
     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
    255288            }
     289            count--;
    256290        }
    257         InputStream inputStream;
    258         if (_data == null) {
    259             try {
    260                 inputStream = new FileInputStream(_file);
    261             } catch (FileNotFoundException e) {
    262                 throw new JpegProcessingException("Jpeg file does not exist", e);
    263             }
    264         } else {
    265             inputStream = new ByteArrayInputStream(_data);
    266         }
    267         return new BufferedInputStream(inputStream);
    268     }
    269 
    270     /**
    271      * Helper method that validates the Jpeg file's magic number.
    272      * @param fileStream the InputStream to read bytes from, which must be positioned
    273      *        at its start (i.e. no bytes read yet)
    274      * @return true if the magic number is Jpeg (0xFFD8)
    275      * @throws IOException for any problem in reading the file
    276      */
    277     private boolean isValidJpegHeaderBytes(InputStream fileStream) throws IOException
    278     {
    279         byte[] header = new byte[2];
    280         fileStream.read(header, 0, 2);
    281         return (header[0] & 0xFF) == 0xFF && (header[1] & 0xFF) == 0xD8;
     291        return false;
    282292    }
    283293}
Note: See TracChangeset for help on using the changeset viewer.