1 package tim.prune.jpeg.drew;
3 import java.io.BufferedInputStream;
5 import java.io.FileInputStream;
6 import java.io.IOException;
8 import tim.prune.jpeg.JpegData;
12 * Extracts Exif data from a JPEG header segment
13 * Based on Drew Noakes' Metadata extractor at https://drewnoakes.com
14 * which in turn is based on code from Jhead http://www.sentex.net/~mwandel/jhead/
16 public class ExifReader
18 /** Magic numbers to mark the beginning of all Jpegs */
19 private static final int MAGIC_JPEG_BYTE_1 = 0xFF;
20 private static final int MAGIC_JPEG_BYTE_2 = 0xD8;
22 /** 6-byte preamble before starting the TIFF data. */
23 private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
25 /** Start of segment marker */
26 private static final byte SEGMENT_SOS = (byte) 0xDA;
28 /** End of segment marker */
29 private static final byte MARKER_EOI = (byte) 0xD9;
32 * Processes the provided JPEG data, and extracts the specified JPEG segments into a JpegData object.
33 * @param inFile a {@link File} from which the JPEG data will be read.
35 public static JpegData readMetadata(File inFile) throws ExifException
37 JpegData jpegData = new JpegData();
38 BufferedInputStream bStream = null;
42 bStream = new BufferedInputStream(new FileInputStream(inFile));
43 byte[] segmentBytes = readSegments(bStream);
44 if (segmentBytes != null)
46 // Got the bytes for the required segment, now extract the data
47 extract(segmentBytes, jpegData);
50 catch (IOException ioe) {
51 throw new ExifException("IO Exception: " + ioe.getMessage());
55 if (bStream != null) {
58 } catch (IOException ioe) {}
65 * Reads the relevant segment and returns the bytes.
67 private static byte[] readSegments(final BufferedInputStream bStream)
68 throws ExifException, IOException
70 // first two bytes should be JPEG magic number
71 final int magic1 = bStream.read() & 0xFF;
72 final int magic2 = bStream.read() & 0xFF;
73 if (magic1 != MAGIC_JPEG_BYTE_1 || magic2 != MAGIC_JPEG_BYTE_2) {
74 throw new ExifException("Jpeg file failed Magic check");
77 final Byte segmentTypeByte = (byte)0xE1; // JpegSegmentType.APP1.byteValue;
80 // Find the segment marker. Markers are zero or more 0xFF bytes, followed
81 // by a 0xFF and then a byte not equal to 0x00 or 0xFF.
83 final short segmentIdentifier = (short) bStream.read();
85 // We must have at least one 0xFF byte
86 if (segmentIdentifier != 0xFF)
87 throw new ExifException("Expected JPEG segment start identifier 0xFF, not 0x" + Integer.toHexString(segmentIdentifier).toUpperCase());
89 // Read until we have a non-0xFF byte. This identifies the segment type.
90 byte currSegmentType = (byte) bStream.read();
91 while (currSegmentType == (byte)0xFF) {
92 currSegmentType = (byte) bStream.read();
95 if (currSegmentType == 0)
96 throw new ExifException("Expected non-zero byte as part of JPEG marker identifier");
98 if (currSegmentType == SEGMENT_SOS) {
99 // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would
100 // have to search for the two bytes: 0xFF 0xD9 (EOI).
101 // It comes last so simply return at this point
105 if (currSegmentType == MARKER_EOI) {
106 // the 'End-Of-Image' segment -- this should never be found in this fashion
110 // next 2-bytes are <segment-size>: [high-byte] [low-byte]
111 int segmentLength = (bStream.read() << 8) + bStream.read();
112 // segment length includes size bytes, so subtract two
115 if (segmentLength < 0)
116 throw new ExifException("JPEG segment size would be less than zero");
118 byte[] segmentBytes = new byte[segmentLength];
119 int bytesRead = bStream.read(segmentBytes, 0, segmentLength);
120 // Bail if not all bytes read in one go - otherwise following sections will be out of step
121 if (bytesRead != segmentLength) {
122 throw new ExifException("Tried to read " + segmentLength + " bytes but only got " + bytesRead);
124 // Check whether we are interested in this segment
125 if (segmentTypeByte == currSegmentType)
127 // Pass the appropriate byte arrays to reader.
128 if (canProcess(segmentBytes)) {
136 private static boolean canProcess(final byte[] segmentBytes)
138 return segmentBytes.length >= JPEG_EXIF_SEGMENT_PREAMBLE.length() && new String(segmentBytes, 0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equalsIgnoreCase(JPEG_EXIF_SEGMENT_PREAMBLE);
142 * Given the bytes, parse them recursively to fill the JpegData
143 * @param segmentBytes bytes out of the file
144 * @param jdata jpeg data to be populated
146 private static void extract(final byte[] segmentBytes, final JpegData jdata)
148 if (segmentBytes == null)
149 throw new NullPointerException("segmentBytes cannot be null");
153 ByteArrayReader reader = new ByteArrayReader(segmentBytes);
155 // Check for the header preamble
157 if (!reader.getString(0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equals(JPEG_EXIF_SEGMENT_PREAMBLE)) {
158 // TODO what to do with this error state?
159 System.err.println("Invalid JPEG Exif segment preamble");
162 } catch (ExifException e) {
163 // TODO what to do with this error state?
164 e.printStackTrace(System.err);
168 // Read the TIFF-formatted Exif data
169 TiffProcessor.processTiff(
172 JPEG_EXIF_SEGMENT_PREAMBLE.length()
175 } catch (ExifException e) {
176 // TODO what to do with this error state?
177 e.printStackTrace(System.err);
178 } catch (IOException e) {
179 // TODO what to do with this error state?
180 e.printStackTrace(System.err);