source: josm/trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java@ 6209

Last change on this file since 6209 was 6127, checked in by bastiK, 11 years ago

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

File size: 12.3 KB
Line 
1/*
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/
20 */
21package com.drew.imaging.jpeg;
22
23import com.drew.lang.annotations.NotNull;
24import com.drew.lang.annotations.Nullable;
25
26import java.io.*;
27
28/**
29 * Performs read functions of Jpeg files, returning specific file segments.
30 * @author Drew Noakes http://drewnoakes.com
31 */
32public class JpegSegmentReader
33{
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).
43 */
44 private static final byte SEGMENT_SOS = (byte)0xDA;
45
46 /**
47 * Private, because one wouldn't search for it.
48 */
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;
93
94 /**
95 * Creates a JpegSegmentReader for a specific file.
96 * @param file the Jpeg file to read segments from
97 */
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 }
112 }
113
114 /**
115 * Creates a JpegSegmentReader for a byte array.
116 * @param fileContents the byte array containing Jpeg data
117 */
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);
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;
292 }
293}
Note: See TracBrowser for help on using the repository browser.