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


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

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

Location:
trunk/src/com/drew/imaging
Files:
1 added
5 edited

Legend:

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

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     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
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     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/
    1420 */
    1521package com.drew.imaging;
     
    1723/**
    1824 * Contains helper methods that perform photographic conversions.
     25 *
     26 * @author Drew Noakes http://drewnoakes.com
    1927 */
    20 public class PhotographicConversions
     28public final class PhotographicConversions
    2129{
    2230    public final static double ROOT_TWO = Math.sqrt(2);
    2331
    24     private PhotographicConversions()
    25     {}
     32    private PhotographicConversions() throws Exception
     33    {
     34        throw new Exception("Not intended for instantiation.");
     35    }
    2636
    2737    /**
    2838     * Converts an aperture value to its corresponding F-stop number.
     39     *
    2940     * @param aperture the aperture value to convert
    3041     * @return the F-stop number of the specified aperture
     
    3243    public static double apertureToFStop(double aperture)
    3344    {
    34         double fStop = Math.pow(ROOT_TWO, aperture);
    35         return fStop;
     45        return Math.pow(ROOT_TWO, aperture);
    3646
    37         // Puzzle?!
    38         // jhead uses a different calculation as far as i can tell...  this confuses me...
     47        // NOTE jhead uses a different calculation as far as i can tell...  this confuses me...
    3948        // fStop = (float)Math.exp(aperture * Math.log(2) * 0.5));
    4049    }
     
    4251    /**
    4352     * Converts a shutter speed to an exposure time.
     53     *
    4454     * @param shutterSpeed the shutter speed to convert
    4555     * @return the exposure time of the specified shutter speed
     
    4757    public static double shutterSpeedToExposureTime(double shutterSpeed)
    4858    {
    49         return (float)(1 / Math.exp(shutterSpeed * Math.log(2)));
     59        return (float) (1 / Math.exp(shutterSpeed * Math.log(2)));
    5060    }
    5161}
  • trunk/src/com/drew/imaging/jpeg/JpegMetadataReader.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
     2 * Copyright 2002-2012 Drew Noakes
    63 *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
     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
    97 *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    149 *
    15  * Created by dnoakes on 12-Nov-2002 18:51:36 using IntelliJ IDEA.
     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/
    1620 */
    1721package com.drew.imaging.jpeg;
    1822
    19 import com.drew.metadata.Directory;
     23import com.drew.lang.ByteArrayReader;
     24import com.drew.lang.annotations.NotNull;
    2025import com.drew.metadata.Metadata;
    21 import com.drew.metadata.MetadataException;
    22 import com.drew.metadata.Tag;
    23 import com.drew.metadata.exif.ExifDirectory;
    2426import com.drew.metadata.exif.ExifReader;
    2527import com.drew.metadata.iptc.IptcReader;
    2628import com.drew.metadata.jpeg.JpegCommentReader;
     29import com.drew.metadata.jpeg.JpegDirectory;
    2730import com.drew.metadata.jpeg.JpegReader;
    2831
     
    3033import java.io.IOException;
    3134import java.io.InputStream;
    32 import java.util.Iterator;
    3335
    3436/**
     37 * Obtains all available metadata from Jpeg formatted files.
    3538 *
     39 * @author Drew Noakes http://drewnoakes.com
    3640 */
    3741public class JpegMetadataReader
    3842{
     43    // TODO investigate supporting javax.imageio
    3944//    public static Metadata readMetadata(IIOMetadata metadata) throws JpegProcessingException {}
    4045//    public static Metadata readMetadata(ImageInputStream in) throws JpegProcessingException{}
     
    4247//    public static Metadata readMetadata(ImageReader reader) throws JpegProcessingException{}
    4348
    44     public static Metadata readMetadata(InputStream in) throws JpegProcessingException
     49    @NotNull
     50    public static Metadata readMetadata(@NotNull InputStream inputStream) throws JpegProcessingException
    4551    {
    46         JpegSegmentReader segmentReader = new JpegSegmentReader(in);
    47         return extractMetadataFromJpegSegmentReader(segmentReader);
     52        return readMetadata(inputStream, true);
    4853    }
    4954
    50     public static Metadata readMetadata(File file) throws JpegProcessingException
     55    @NotNull
     56    public static Metadata readMetadata(@NotNull InputStream inputStream, final boolean waitForBytes) throws JpegProcessingException
     57    {
     58        JpegSegmentReader segmentReader = new JpegSegmentReader(inputStream, waitForBytes);
     59        return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData());
     60    }
     61
     62    @NotNull
     63    public static Metadata readMetadata(@NotNull File file) throws JpegProcessingException, IOException
    5164    {
    5265        JpegSegmentReader segmentReader = new JpegSegmentReader(file);
    53         return extractMetadataFromJpegSegmentReader(segmentReader);
     66        return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData());
    5467    }
    5568
    56     public static Metadata extractMetadataFromJpegSegmentReader(JpegSegmentReader segmentReader)
     69    @NotNull
     70    public static Metadata extractMetadataFromJpegSegmentReader(@NotNull JpegSegmentData segmentReader)
    5771    {
    5872        final Metadata metadata = new Metadata();
    59         try {
    60             byte[] exifSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_APP1);
    61             new ExifReader(exifSegment).extract(metadata);
    62         } catch (JpegProcessingException e) {
    63             // in the interests of catching as much data as possible, continue
    64             // TODO lodge error message within exif directory?
     73
     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;
    6588        }
    6689
    67         try {
    68             byte[] iptcSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_APPD);
    69             new IptcReader(iptcSegment).extract(metadata);
    70         } catch (JpegProcessingException e) {
    71             // TODO lodge error message within iptc directory?
     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);
    72102        }
    73103
    74                 try {
    75                         byte[] jpegSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_SOF0);
    76                         new JpegReader(jpegSegment).extract(metadata);
    77                 } catch (JpegProcessingException e) {
    78                         // TODO lodge error message within jpeg directory?
    79                 }
    80 
    81                 try {
    82                         byte[] jpegCommentSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_COM);
    83                         new JpegCommentReader(jpegCommentSegment).extract(metadata);
    84                 } catch (JpegProcessingException e) {
    85                         // TODO lodge error message within jpegcomment directory?
    86                 }
     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);
     111            }
     112        }
    87113
    88114        return metadata;
    89115    }
    90116
    91     private JpegMetadataReader()
     117    private JpegMetadataReader() throws Exception
    92118    {
    93     }
    94 
    95     public static void main(String[] args) throws MetadataException, IOException
    96     {
    97         Metadata metadata = null;
    98         try {
    99             metadata = JpegMetadataReader.readMetadata(new File(args[0]));
    100         } catch (Exception e) {
    101             e.printStackTrace(System.err);
    102             System.exit(1);
    103         }
    104 
    105         // iterate over the exif data and print to System.out
    106         Iterator directories = metadata.getDirectoryIterator();
    107         while (directories.hasNext()) {
    108             Directory directory = (Directory)directories.next();
    109             Iterator tags = directory.getTagIterator();
    110             while (tags.hasNext()) {
    111                 Tag tag = (Tag)tags.next();
    112                 try {
    113                     System.out.println("[" + directory.getName() + "] " + tag.getTagName() + " = " + tag.getDescription());
    114                 } catch (MetadataException e) {
    115                     System.err.println(e.getMessage());
    116                     System.err.println(tag.getDirectoryName() + " " + tag.getTagName() + " (error)");
    117                 }
    118             }
    119             if (directory.hasErrors()) {
    120                 Iterator errors = directory.getErrors();
    121                 while (errors.hasNext()) {
    122                     System.out.println("ERROR: " + errors.next());
    123                 }
    124             }
    125         }
    126 
    127         if (args.length>1 && args[1].trim().equals("/thumb"))
    128         {
    129             ExifDirectory directory = (ExifDirectory)metadata.getDirectory(ExifDirectory.class);
    130             if (directory.containsThumbnail())
    131             {
    132                 System.out.println("Writing thumbnail...");
    133                 directory.writeThumbnail(args[0].trim() + ".thumb.jpg");
    134             }
    135             else
    136             {
    137                 System.out.println("No thumbnail data exists in this image");
    138             }
    139         }
     119        throw new Exception("Not intended for instantiation");
    140120    }
    141121}
     122
  • trunk/src/com/drew/imaging/jpeg/JpegProcessingException.java

    r4231 r6127  
    11/*
    2  * JpegProcessingException.java
     2 * Copyright 2002-2012 Drew Noakes
    33 *
    4  * This class is public domain software - that is, you can do whatever you want
    5  * with it, and include it software that is licensed under the GNU or the
    6  * BSD license, or whatever other licence you choose, including proprietary
    7  * closed source licenses.  I do ask that you leave this header in tact.
     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
    87 *
    9  * If you make modifications to this code that you think would benefit the
    10  * wider community, please send me a copy and I'll post it on my site.
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    119 *
    12  * If you make use of this code, I'd appreciate hearing about it.
    13  *   drew@drewnoakes.com
    14  * Latest version of this software kept at
    15  *   http://drewnoakes.com/
     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.
    1615 *
    17  * Created by dnoakes on 04-Nov-2002 19:31:29 using IntelliJ IDEA.
     16 * More information about this project is available at:
     17 *
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    1820 */
    1921package com.drew.imaging.jpeg;
    2022
    21 import com.drew.lang.CompoundException;
     23import com.drew.imaging.ImageProcessingException;
     24import com.drew.lang.annotations.Nullable;
    2225
    2326/**
    24  * An exception class thrown upon unexpected and fatal conditions while processing
    25  * a Jpeg file.
    26  * @author  Drew Noakes http://drewnoakes.com
     27 * An exception class thrown upon unexpected and fatal conditions while processing a Jpeg file.
     28 *
     29 * @author Drew Noakes http://drewnoakes.com
    2730 */
    28 public class JpegProcessingException extends CompoundException
     31public class JpegProcessingException extends ImageProcessingException
    2932{
    30     public JpegProcessingException(String message)
     33    private static final long serialVersionUID = -7870179776125450158L;
     34
     35    public JpegProcessingException(@Nullable String message)
    3136    {
    3237        super(message);
    3338    }
    3439
    35     public JpegProcessingException(String message, Throwable cause)
     40    public JpegProcessingException(@Nullable String message, @Nullable Throwable cause)
    3641    {
    3742        super(message, cause);
    3843    }
    3944
    40     public JpegProcessingException(Throwable cause)
     45    public JpegProcessingException(@Nullable Throwable cause)
    4146    {
    4247        super(cause);
  • trunk/src/com/drew/imaging/jpeg/JpegSegmentData.java

    r4231 r6127  
    11/*
    2  * This is public domain software - that is, you can do whatever you want
    3  * with it, and include it software that is licensed under the GNU or the
    4  * BSD license, or whatever other licence you choose, including proprietary
    5  * closed source licenses.  I do ask that you leave this header in tact.
    6  *
    7  * If you make modifications to this code that you think would benefit the
    8  * wider community, please send me a copy and I'll post it on my site.
    9  *
    10  * If you make use of this code, I'd appreciate hearing about it.
    11  *   drew@drewnoakes.com
    12  * Latest version of this software kept at
    13  *   http://drewnoakes.com/
     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/
    1420 */
    1521package com.drew.imaging.jpeg;
     22
     23import com.drew.lang.annotations.NotNull;
     24import com.drew.lang.annotations.Nullable;
    1625
    1726import java.io.*;
     
    2231/**
    2332 * Holds a collection of Jpeg data segments.  This need not necessarily be all segments
    24  * within the Jpeg.  For example, it may be convenient to port about only the non-image
     33 * within the Jpeg.  For example, it may be convenient to store only the non-image
    2534 * 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
    2640 */
    2741public class JpegSegmentData implements Serializable
    2842{
    29     static final long serialVersionUID = 7110175216435025451L;
     43    private static final long serialVersionUID = 7110175216435025451L;
    3044   
    3145    /** A map of byte[], keyed by the segment marker */
    32     private final HashMap _segmentDataMap;
    33 
    34     public JpegSegmentData()
    35     {
    36         _segmentDataMap = new HashMap(10);
    37     }
    38 
    39     public void addSegment(byte segmentMarker, byte[] segmentBytes)
    40     {
    41         List segmentList = getOrCreateSegmentList(segmentMarker);
     46    @NotNull
     47    private final HashMap<Byte, List<byte[]>> _segmentDataMap = new HashMap<Byte, List<byte[]>>(10);
     48
     49    /**
     50     * 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);
    4258        segmentList.add(segmentBytes);
    4359    }
    4460
     61    /**
     62     * Gets the first Jpeg segment data for the specified marker.
     63     * @param segmentMarker the byte identifier for the desired segment
     64     * @return a byte[] containing segment data or null if no data exists for that segment
     65     */
     66    @Nullable
    4567    public byte[] getSegment(byte segmentMarker)
    4668    {
     
    4870    }
    4971
     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
    5080    public byte[] getSegment(byte segmentMarker, int occurrence)
    5181    {
    52         final List segmentList = getSegmentList(segmentMarker);
     82        final List<byte[]> segmentList = getSegmentList(segmentMarker);
    5383
    5484        if (segmentList==null || segmentList.size()<=occurrence)
    5585            return null;
    5686        else
    57             return (byte[]) segmentList.get(occurrence);
    58     }
    59 
     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)
     111    {
     112        List<byte[]> segmentList;
     113        if (_segmentDataMap.containsKey(segmentMarker)) {
     114            segmentList = _segmentDataMap.get(segmentMarker);
     115        } else {
     116            segmentList = new ArrayList<byte[]>();
     117            _segmentDataMap.put(segmentMarker, segmentList);
     118        }
     119        return segmentList;
     120    }
     121
     122    /**
     123     * Returns the count of segment data byte arrays stored for a given segment marker.
     124     * @param segmentMarker identifies the required segment
     125     * @return the segment count (zero if no segments exist).
     126     */
    60127    public int getSegmentCount(byte segmentMarker)
    61128    {
    62         final List segmentList = getSegmentList(segmentMarker);
    63         if (segmentList==null)
    64             return 0;
    65         else
    66             return segmentList.size();
    67     }
    68 
     129        final List<byte[]> segmentList = getSegmentList(segmentMarker);
     130        return segmentList == null ? 0 : segmentList.size();
     131    }
     132
     133    /**
     134     * 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" })
    69140    public void removeSegmentOccurrence(byte segmentMarker, int occurrence)
    70141    {
    71         final List segmentList = (List)_segmentDataMap.get(new Byte(segmentMarker));
     142        final List<byte[]> segmentList = _segmentDataMap.get(Byte.valueOf(segmentMarker));
    72143        segmentList.remove(occurrence);
    73144    }
    74145
     146    /**
     147     * Removes all segments from the collection having the specified marker.
     148     * @param segmentMarker identifies the required segment
     149     */
    75150    public void removeSegment(byte segmentMarker)
    76151    {
    77         _segmentDataMap.remove(new Byte(segmentMarker));
    78     }
    79 
    80     private List getSegmentList(byte segmentMarker)
    81     {
    82         return (List)_segmentDataMap.get(new Byte(segmentMarker));
    83     }
    84 
    85     private List getOrCreateSegmentList(byte segmentMarker)
    86     {
    87         List segmentList;
    88         Byte key = new Byte(segmentMarker);
    89         if (_segmentDataMap.containsKey(key)) {
    90             segmentList = (List)_segmentDataMap.get(key);
    91         } else {
    92             segmentList = new ArrayList();
    93             _segmentDataMap.put(key, segmentList);
    94         }
    95         return segmentList;
    96     }
    97 
     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
     158     * @return true if data exists, otherwise false
     159     */
    98160    public boolean containsSegment(byte segmentMarker)
    99161    {
    100         return _segmentDataMap.containsKey(new Byte(segmentMarker));
    101     }
    102 
    103     public static void ToFile(File file, JpegSegmentData segmentData) throws IOException
    104     {
    105         ObjectOutputStream outputStream = null;
     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;
    106174        try
    107175        {
    108             outputStream = new ObjectOutputStream(new FileOutputStream(file));
    109             outputStream.writeObject(segmentData);
     176            fileOutputStream = new FileOutputStream(file);
     177            new ObjectOutputStream(fileOutputStream).writeObject(segmentData);
    110178        }
    111179        finally
    112180        {
    113             if (outputStream!=null)
    114                 outputStream.close();
    115         }
    116     }
    117 
    118     public static JpegSegmentData FromFile(File file) throws IOException, ClassNotFoundException
     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
    119195    {
    120196        ObjectInputStream inputStream = null;
  • 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.