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 | */
|
---|
21 | package com.drew.metadata.exif;
|
---|
22 |
|
---|
23 | import java.util.HashSet;
|
---|
24 | import java.util.Set;
|
---|
25 |
|
---|
26 | import com.drew.lang.BufferBoundsException;
|
---|
27 | import com.drew.lang.BufferReader;
|
---|
28 | import com.drew.lang.Rational;
|
---|
29 | import com.drew.lang.annotations.NotNull;
|
---|
30 | import com.drew.metadata.Directory;
|
---|
31 | import com.drew.metadata.Metadata;
|
---|
32 | import com.drew.metadata.MetadataReader;
|
---|
33 |
|
---|
34 | /**
|
---|
35 | * Decodes Exif binary data, populating a {@link Metadata} object with tag values in {@link ExifSubIFDDirectory},
|
---|
36 | * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera makernote directories.
|
---|
37 | *
|
---|
38 | * @author Drew Noakes http://drewnoakes.com
|
---|
39 | */
|
---|
40 | public class ExifReader implements MetadataReader
|
---|
41 | {
|
---|
42 | // TODO extract a reusable TiffReader from this class with hooks for special tag handling and subdir following
|
---|
43 |
|
---|
44 | /** The number of bytes used per format descriptor. */
|
---|
45 | @NotNull
|
---|
46 | private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
|
---|
47 |
|
---|
48 | /** The number of formats known. */
|
---|
49 | private static final int MAX_FORMAT_CODE = 12;
|
---|
50 |
|
---|
51 | // Format types
|
---|
52 | // TODO use an enum for these?
|
---|
53 | /** An 8-bit unsigned integer. */
|
---|
54 | private static final int FMT_BYTE = 1;
|
---|
55 | /** A fixed-length character string. */
|
---|
56 | private static final int FMT_STRING = 2;
|
---|
57 | /** An unsigned 16-bit integer. */
|
---|
58 | private static final int FMT_USHORT = 3;
|
---|
59 | /** An unsigned 32-bit integer. */
|
---|
60 | private static final int FMT_ULONG = 4;
|
---|
61 | private static final int FMT_URATIONAL = 5;
|
---|
62 | /** An 8-bit signed integer. */
|
---|
63 | private static final int FMT_SBYTE = 6;
|
---|
64 | private static final int FMT_UNDEFINED = 7;
|
---|
65 | /** A signed 16-bit integer. */
|
---|
66 | private static final int FMT_SSHORT = 8;
|
---|
67 | /** A signed 32-bit integer. */
|
---|
68 | private static final int FMT_SLONG = 9;
|
---|
69 | private static final int FMT_SRATIONAL = 10;
|
---|
70 | /** A 32-bit floating point number. */
|
---|
71 | private static final int FMT_SINGLE = 11;
|
---|
72 | /** A 64-bit floating point number. */
|
---|
73 | private static final int FMT_DOUBLE = 12;
|
---|
74 |
|
---|
75 | /** This tag is a pointer to the Exif SubIFD. */
|
---|
76 | public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769;
|
---|
77 | /** This tag is a pointer to the Exif Interop IFD. */
|
---|
78 | public static final int TAG_INTEROP_OFFSET = 0xA005;
|
---|
79 | /** This tag is a pointer to the Exif GPS IFD. */
|
---|
80 | public static final int TAG_GPS_INFO_OFFSET = 0x8825;
|
---|
81 | /** This tag is a pointer to the Exif Makernote IFD. */
|
---|
82 | public static final int TAG_MAKER_NOTE_OFFSET = 0x927C;
|
---|
83 |
|
---|
84 | public static final int TIFF_HEADER_START_OFFSET = 6;
|
---|
85 |
|
---|
86 | /**
|
---|
87 | * Performs the Exif data extraction, adding found values to the specified
|
---|
88 | * instance of <code>Metadata</code>.
|
---|
89 | *
|
---|
90 | * @param reader The buffer reader from which Exif data should be read.
|
---|
91 | * @param metadata The Metadata object into which extracted values should be merged.
|
---|
92 | */
|
---|
93 | public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata)
|
---|
94 | {
|
---|
95 | final ExifSubIFDDirectory directory = metadata.getOrCreateDirectory(ExifSubIFDDirectory.class);
|
---|
96 |
|
---|
97 | // check for the header length
|
---|
98 | if (reader.getLength() <= 14) {
|
---|
99 | directory.addError("Exif data segment must contain at least 14 bytes");
|
---|
100 | return;
|
---|
101 | }
|
---|
102 |
|
---|
103 | // check for the header preamble
|
---|
104 | try {
|
---|
105 | if (!reader.getString(0, 6).equals("Exif\0\0")) {
|
---|
106 | directory.addError("Exif data segment doesn't begin with 'Exif'");
|
---|
107 | return;
|
---|
108 | }
|
---|
109 |
|
---|
110 | extractIFD(metadata, metadata.getOrCreateDirectory(ExifIFD0Directory.class), TIFF_HEADER_START_OFFSET, reader);
|
---|
111 | } catch (BufferBoundsException e) {
|
---|
112 | directory.addError("Exif data segment ended prematurely");
|
---|
113 | }
|
---|
114 | }
|
---|
115 |
|
---|
116 | /**
|
---|
117 | * Performs the Exif data extraction on a TIFF/RAW, adding found values to the specified
|
---|
118 | * instance of <code>Metadata</code>.
|
---|
119 | *
|
---|
120 | * @param reader The BufferReader from which TIFF data should be read.
|
---|
121 | * @param metadata The Metadata object into which extracted values should be merged.
|
---|
122 | */
|
---|
123 | public void extractTiff(@NotNull BufferReader reader, @NotNull Metadata metadata)
|
---|
124 | {
|
---|
125 | final ExifIFD0Directory directory = metadata.getOrCreateDirectory(ExifIFD0Directory.class);
|
---|
126 |
|
---|
127 | try {
|
---|
128 | extractIFD(metadata, directory, 0, reader);
|
---|
129 | } catch (BufferBoundsException e) {
|
---|
130 | directory.addError("Exif data segment ended prematurely");
|
---|
131 | }
|
---|
132 | }
|
---|
133 |
|
---|
134 | private void extractIFD(@NotNull Metadata metadata, @NotNull final ExifIFD0Directory directory, int tiffHeaderOffset, @NotNull BufferReader reader) throws BufferBoundsException
|
---|
135 | {
|
---|
136 | // this should be either "MM" or "II"
|
---|
137 | String byteOrderIdentifier = reader.getString(tiffHeaderOffset, 2);
|
---|
138 |
|
---|
139 | if ("MM".equals(byteOrderIdentifier)) {
|
---|
140 | reader.setMotorolaByteOrder(true);
|
---|
141 | } else if ("II".equals(byteOrderIdentifier)) {
|
---|
142 | reader.setMotorolaByteOrder(false);
|
---|
143 | } else {
|
---|
144 | directory.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);
|
---|
145 | return;
|
---|
146 | }
|
---|
147 |
|
---|
148 | // Check the next two values for correctness.
|
---|
149 | final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset);
|
---|
150 |
|
---|
151 | final int standardTiffMarker = 0x002A;
|
---|
152 | final int olympusRawTiffMarker = 0x4F52; // for ORF files
|
---|
153 | final int panasonicRawTiffMarker = 0x0055; // for RW2 files
|
---|
154 |
|
---|
155 | if (tiffMarker != standardTiffMarker && tiffMarker != olympusRawTiffMarker && tiffMarker != panasonicRawTiffMarker) {
|
---|
156 | directory.addError("Unexpected TIFF marker after byte order identifier: 0x" + Integer.toHexString(tiffMarker));
|
---|
157 | return;
|
---|
158 | }
|
---|
159 |
|
---|
160 | int firstDirectoryOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;
|
---|
161 |
|
---|
162 | // David Ekholm sent a digital camera image that has this problem
|
---|
163 | if (firstDirectoryOffset >= reader.getLength() - 1) {
|
---|
164 | directory.addError("First exif directory offset is beyond end of Exif data segment");
|
---|
165 | // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case
|
---|
166 | firstDirectoryOffset = 14;
|
---|
167 | }
|
---|
168 |
|
---|
169 | Set<Integer> processedDirectoryOffsets = new HashSet<Integer>();
|
---|
170 |
|
---|
171 | processDirectory(directory, processedDirectoryOffsets, firstDirectoryOffset, tiffHeaderOffset, metadata, reader);
|
---|
172 |
|
---|
173 | // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
|
---|
174 | ExifThumbnailDirectory thumbnailDirectory = metadata.getDirectory(ExifThumbnailDirectory.class);
|
---|
175 | if (thumbnailDirectory!=null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION)) {
|
---|
176 | Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
|
---|
177 | Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
|
---|
178 | if (offset != null && length != null) {
|
---|
179 | try {
|
---|
180 | byte[] thumbnailData = reader.getBytes(tiffHeaderOffset + offset, length);
|
---|
181 | thumbnailDirectory.setThumbnailData(thumbnailData);
|
---|
182 | } catch (BufferBoundsException ex) {
|
---|
183 | directory.addError("Invalid thumbnail data specification: " + ex.getMessage());
|
---|
184 | }
|
---|
185 | }
|
---|
186 | }
|
---|
187 | }
|
---|
188 |
|
---|
189 | /**
|
---|
190 | * Process one of the nested Tiff IFD directories.
|
---|
191 | * <p/>
|
---|
192 | * Header
|
---|
193 | * 2 bytes: number of tags
|
---|
194 | * <p/>
|
---|
195 | * Then for each tag
|
---|
196 | * 2 bytes: tag type
|
---|
197 | * 2 bytes: format code
|
---|
198 | * 4 bytes: component count
|
---|
199 | */
|
---|
200 | private void processDirectory(@NotNull Directory directory, @NotNull Set<Integer> processedDirectoryOffsets, int dirStartOffset, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull final BufferReader reader) throws BufferBoundsException
|
---|
201 | {
|
---|
202 | // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist
|
---|
203 | if (processedDirectoryOffsets.contains(Integer.valueOf(dirStartOffset)))
|
---|
204 | return;
|
---|
205 |
|
---|
206 | // remember that we've visited this directory so that we don't visit it again later
|
---|
207 | processedDirectoryOffsets.add(dirStartOffset);
|
---|
208 |
|
---|
209 | if (dirStartOffset >= reader.getLength() || dirStartOffset < 0) {
|
---|
210 | directory.addError("Ignored directory marked to start outside data segment");
|
---|
211 | return;
|
---|
212 | }
|
---|
213 |
|
---|
214 | // First two bytes in the IFD are the number of tags in this directory
|
---|
215 | int dirTagCount = reader.getUInt16(dirStartOffset);
|
---|
216 |
|
---|
217 | int dirLength = (2 + (12 * dirTagCount) + 4);
|
---|
218 | if (dirLength + dirStartOffset > reader.getLength()) {
|
---|
219 | directory.addError("Illegally sized directory");
|
---|
220 | return;
|
---|
221 | }
|
---|
222 |
|
---|
223 | // Handle each tag in this directory
|
---|
224 | for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) {
|
---|
225 | final int tagOffset = calculateTagOffset(dirStartOffset, tagNumber);
|
---|
226 |
|
---|
227 | // 2 bytes for the tag type
|
---|
228 | final int tagType = reader.getUInt16(tagOffset);
|
---|
229 |
|
---|
230 | // 2 bytes for the format code
|
---|
231 | final int formatCode = reader.getUInt16(tagOffset + 2);
|
---|
232 | if (formatCode < 1 || formatCode > MAX_FORMAT_CODE) {
|
---|
233 | // This error suggests that we are processing at an incorrect index and will generate
|
---|
234 | // rubbish until we go out of bounds (which may be a while). Exit now.
|
---|
235 | directory.addError("Invalid TIFF tag format code: " + formatCode);
|
---|
236 | continue; // JOSM patch to fix #9030
|
---|
237 | }
|
---|
238 |
|
---|
239 | // 4 bytes dictate the number of components in this tag's data
|
---|
240 | final int componentCount = reader.getInt32(tagOffset + 4);
|
---|
241 | if (componentCount < 0) {
|
---|
242 | directory.addError("Negative TIFF tag component count");
|
---|
243 | continue;
|
---|
244 | }
|
---|
245 | // each component may have more than one byte... calculate the total number of bytes
|
---|
246 | final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode];
|
---|
247 | final int tagValueOffset;
|
---|
248 | if (byteCount > 4) {
|
---|
249 | // If it's bigger than 4 bytes, the dir entry contains an offset.
|
---|
250 | // dirEntryOffset must be passed, as some makernote implementations (e.g. FujiFilm) incorrectly use an
|
---|
251 | // offset relative to the start of the makernote itself, not the TIFF segment.
|
---|
252 | final int offsetVal = reader.getInt32(tagOffset + 8);
|
---|
253 | if (offsetVal + byteCount > reader.getLength()) {
|
---|
254 | // Bogus pointer offset and / or byteCount value
|
---|
255 | directory.addError("Illegal TIFF tag pointer offset");
|
---|
256 | continue;
|
---|
257 | }
|
---|
258 | tagValueOffset = tiffHeaderOffset + offsetVal;
|
---|
259 | } else {
|
---|
260 | // 4 bytes or less and value is in the dir entry itself
|
---|
261 | tagValueOffset = tagOffset + 8;
|
---|
262 | }
|
---|
263 |
|
---|
264 | if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) {
|
---|
265 | directory.addError("Illegal TIFF tag pointer offset");
|
---|
266 | continue;
|
---|
267 | }
|
---|
268 |
|
---|
269 | // Check that this tag isn't going to allocate outside the bounds of the data array.
|
---|
270 | // This addresses an uncommon OutOfMemoryError.
|
---|
271 | if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) {
|
---|
272 | directory.addError("Illegal number of bytes: " + byteCount);
|
---|
273 | continue;
|
---|
274 | }
|
---|
275 |
|
---|
276 | switch (tagType) {
|
---|
277 | case TAG_EXIF_SUB_IFD_OFFSET: {
|
---|
278 | final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
|
---|
279 | processDirectory(metadata.getOrCreateDirectory(ExifSubIFDDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
|
---|
280 | continue;
|
---|
281 | }
|
---|
282 | case TAG_INTEROP_OFFSET: {
|
---|
283 | final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
|
---|
284 | processDirectory(metadata.getOrCreateDirectory(ExifInteropDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
|
---|
285 | continue;
|
---|
286 | }
|
---|
287 | case TAG_GPS_INFO_OFFSET: {
|
---|
288 | final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
|
---|
289 | processDirectory(metadata.getOrCreateDirectory(GpsDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
|
---|
290 | continue;
|
---|
291 | }
|
---|
292 | case TAG_MAKER_NOTE_OFFSET: {
|
---|
293 | processMakerNote(tagValueOffset, processedDirectoryOffsets, tiffHeaderOffset, metadata, reader);
|
---|
294 | continue;
|
---|
295 | }
|
---|
296 | default: {
|
---|
297 | processTag(directory, tagType, tagValueOffset, componentCount, formatCode, reader);
|
---|
298 | break;
|
---|
299 | }
|
---|
300 | }
|
---|
301 | }
|
---|
302 |
|
---|
303 | // at the end of each IFD is an optional link to the next IFD
|
---|
304 | final int finalTagOffset = calculateTagOffset(dirStartOffset, dirTagCount);
|
---|
305 | int nextDirectoryOffset = reader.getInt32(finalTagOffset);
|
---|
306 | if (nextDirectoryOffset != 0) {
|
---|
307 | nextDirectoryOffset += tiffHeaderOffset;
|
---|
308 | if (nextDirectoryOffset >= reader.getLength()) {
|
---|
309 | // Last 4 bytes of IFD reference another IFD with an address that is out of bounds
|
---|
310 | // Note this could have been caused by jhead 1.3 cropping too much
|
---|
311 | return;
|
---|
312 | } else if (nextDirectoryOffset < dirStartOffset) {
|
---|
313 | // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory
|
---|
314 | return;
|
---|
315 | }
|
---|
316 | // TODO in Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case
|
---|
317 | final ExifThumbnailDirectory nextDirectory = metadata.getOrCreateDirectory(ExifThumbnailDirectory.class);
|
---|
318 | processDirectory(nextDirectory, processedDirectoryOffsets, nextDirectoryOffset, tiffHeaderOffset, metadata, reader);
|
---|
319 | }
|
---|
320 | }
|
---|
321 |
|
---|
322 | private void processMakerNote(int subdirOffset, @NotNull Set<Integer> processedDirectoryOffsets, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull BufferReader reader) throws BufferBoundsException
|
---|
323 | {
|
---|
324 | // Determine the camera model and makernote format
|
---|
325 | Directory ifd0Directory = metadata.getDirectory(ExifIFD0Directory.class);
|
---|
326 |
|
---|
327 | if (ifd0Directory==null)
|
---|
328 | return;
|
---|
329 |
|
---|
330 | String cameraModel = ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
|
---|
331 |
|
---|
332 | //final String firstTwoChars = reader.getString(subdirOffset, 2);
|
---|
333 | final String firstThreeChars = reader.getString(subdirOffset, 3);
|
---|
334 | final String firstFourChars = reader.getString(subdirOffset, 4);
|
---|
335 | final String firstFiveChars = reader.getString(subdirOffset, 5);
|
---|
336 | final String firstSixChars = reader.getString(subdirOffset, 6);
|
---|
337 | final String firstSevenChars = reader.getString(subdirOffset, 7);
|
---|
338 | final String firstEightChars = reader.getString(subdirOffset, 8);
|
---|
339 | final String firstTwelveChars = reader.getString(subdirOffset, 12);
|
---|
340 |
|
---|
341 | if ("OLYMP".equals(firstFiveChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {
|
---|
342 | // Olympus Makernote
|
---|
343 | // Epson and Agfa use Olympus maker note standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/
|
---|
344 | processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);
|
---|
345 | } else if (cameraModel != null && cameraModel.trim().toUpperCase().startsWith("NIKON")) {
|
---|
346 | if ("Nikon".equals(firstFiveChars)) {
|
---|
347 | /* There are two scenarios here:
|
---|
348 | * Type 1: **
|
---|
349 | * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon...........
|
---|
350 | * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................
|
---|
351 | * Type 3: **
|
---|
352 | * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*...
|
---|
353 | * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200
|
---|
354 | */
|
---|
355 | switch (reader.getUInt8(subdirOffset + 6)) {
|
---|
356 | case 1:
|
---|
357 | processDirectory(metadata.getOrCreateDirectory(NikonType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);
|
---|
358 | break;
|
---|
359 | case 2:
|
---|
360 | processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 18, subdirOffset + 10, metadata, reader);
|
---|
361 | break;
|
---|
362 | default:
|
---|
363 | ifd0Directory.addError("Unsupported Nikon makernote data ignored.");
|
---|
364 | break;
|
---|
365 | }
|
---|
366 | } else {
|
---|
367 | // The IFD begins with the first MakerNote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models.
|
---|
368 | processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
|
---|
369 | }
|
---|
370 | } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) {
|
---|
371 | processDirectory(metadata.getOrCreateDirectory(SonyType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);
|
---|
372 | } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) {
|
---|
373 | processDirectory(metadata.getOrCreateDirectory(SigmaMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 10, tiffHeaderOffset, metadata, reader);
|
---|
374 | } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) {
|
---|
375 | // force MM for this directory
|
---|
376 | boolean isMotorola = reader.isMotorolaByteOrder();
|
---|
377 | reader.setMotorolaByteOrder(true);
|
---|
378 | // skip 12 byte header + 2 for "MM" + 6
|
---|
379 | processDirectory(metadata.getOrCreateDirectory(SonyType6MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);
|
---|
380 | reader.setMotorolaByteOrder(isMotorola);
|
---|
381 | } else if ("KDK".equals(firstThreeChars)) {
|
---|
382 | processDirectory(metadata.getOrCreateDirectory(KodakMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);
|
---|
383 | } else if ("Canon".equalsIgnoreCase(cameraModel)) {
|
---|
384 | processDirectory(metadata.getOrCreateDirectory(CanonMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
|
---|
385 | } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("CASIO")) {
|
---|
386 | if ("QVC\u0000\u0000\u0000".equals(firstSixChars))
|
---|
387 | processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, tiffHeaderOffset, metadata, reader);
|
---|
388 | else
|
---|
389 | processDirectory(metadata.getOrCreateDirectory(CasioType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
|
---|
390 | } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraModel)) {
|
---|
391 | boolean byteOrderBefore = reader.isMotorolaByteOrder();
|
---|
392 | // bug in fujifilm makernote ifd means we temporarily use Intel byte ordering
|
---|
393 | reader.setMotorolaByteOrder(false);
|
---|
394 | // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote
|
---|
395 | // IFD, though the offset is relative to the start of the makernote, not the TIFF
|
---|
396 | // header (like everywhere else)
|
---|
397 | int ifdStart = subdirOffset + reader.getInt32(subdirOffset + 8);
|
---|
398 | processDirectory(metadata.getOrCreateDirectory(FujifilmMakernoteDirectory.class), processedDirectoryOffsets, ifdStart, tiffHeaderOffset, metadata, reader);
|
---|
399 | reader.setMotorolaByteOrder(byteOrderBefore);
|
---|
400 | } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("MINOLTA")) {
|
---|
401 | // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote
|
---|
402 | // area that commences immediately.
|
---|
403 | processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
|
---|
404 | } else if ("KYOCERA".equals(firstSevenChars)) {
|
---|
405 | // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html
|
---|
406 | processDirectory(metadata.getOrCreateDirectory(KyoceraMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 22, tiffHeaderOffset, metadata, reader);
|
---|
407 | } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(subdirOffset, 12))) {
|
---|
408 | // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD
|
---|
409 | // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
|
---|
410 | // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html
|
---|
411 | processDirectory(metadata.getOrCreateDirectory(PanasonicMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);
|
---|
412 | } else if ("AOC\u0000".equals(firstFourChars)) {
|
---|
413 | // NON-Standard TIFF IFD Data using Casio Type 2 Tags
|
---|
414 | // IFD has no Next-IFD pointer at end of IFD, and
|
---|
415 | // Offsets are relative to the start of the current IFD tag, not the TIFF header
|
---|
416 | // Observed for:
|
---|
417 | // - Pentax ist D
|
---|
418 | processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, subdirOffset, metadata, reader);
|
---|
419 | } else if (cameraModel != null && (cameraModel.toUpperCase().startsWith("PENTAX") || cameraModel.toUpperCase().startsWith("ASAHI"))) {
|
---|
420 | // NON-Standard TIFF IFD Data using Pentax Tags
|
---|
421 | // IFD has no Next-IFD pointer at end of IFD, and
|
---|
422 | // Offsets are relative to the start of the current IFD tag, not the TIFF header
|
---|
423 | // Observed for:
|
---|
424 | // - PENTAX Optio 330
|
---|
425 | // - PENTAX Optio 430
|
---|
426 | processDirectory(metadata.getOrCreateDirectory(PentaxMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, subdirOffset, metadata, reader);
|
---|
427 | // } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) {
|
---|
428 | // // This Konica data is not understood. Header identified in accordance with information at this site:
|
---|
429 | // // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html
|
---|
430 | // // TODO add support for minolta/konica cameras
|
---|
431 | // exifDirectory.addError("Unsupported Konica/Minolta data ignored.");
|
---|
432 | } else {
|
---|
433 | // TODO how to store makernote data when it's not from a supported camera model?
|
---|
434 | // this is difficult as the starting offset is not known. we could look for it...
|
---|
435 | }
|
---|
436 | }
|
---|
437 |
|
---|
438 | private void processTag(@NotNull Directory directory, int tagType, int tagValueOffset, int componentCount, int formatCode, @NotNull final BufferReader reader) throws BufferBoundsException
|
---|
439 | {
|
---|
440 | // Directory simply stores raw values
|
---|
441 | // The display side uses a Descriptor class per directory to turn the raw values into 'pretty' descriptions
|
---|
442 | switch (formatCode) {
|
---|
443 | case FMT_UNDEFINED:
|
---|
444 | // this includes exif user comments
|
---|
445 | directory.setByteArray(tagType, reader.getBytes(tagValueOffset, componentCount));
|
---|
446 | break;
|
---|
447 | case FMT_STRING:
|
---|
448 | String string = reader.getNullTerminatedString(tagValueOffset, componentCount);
|
---|
449 | directory.setString(tagType, string);
|
---|
450 | /*
|
---|
451 | // special handling for certain known tags, proposed by Yuri Binev but left out for now,
|
---|
452 | // as it gives the false impression that the image was captured in the same timezone
|
---|
453 | // in which the string is parsed
|
---|
454 | if (tagType==ExifSubIFDDirectory.TAG_DATETIME ||
|
---|
455 | tagType==ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL ||
|
---|
456 | tagType==ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED) {
|
---|
457 | String[] datePatterns = {
|
---|
458 | "yyyy:MM:dd HH:mm:ss",
|
---|
459 | "yyyy:MM:dd HH:mm",
|
---|
460 | "yyyy-MM-dd HH:mm:ss",
|
---|
461 | "yyyy-MM-dd HH:mm"};
|
---|
462 | for (String datePattern : datePatterns) {
|
---|
463 | try {
|
---|
464 | DateFormat parser = new SimpleDateFormat(datePattern);
|
---|
465 | Date date = parser.parse(string);
|
---|
466 | directory.setDate(tagType, date);
|
---|
467 | break;
|
---|
468 | } catch (ParseException ex) {
|
---|
469 | // simply try the next pattern
|
---|
470 | }
|
---|
471 | }
|
---|
472 | }
|
---|
473 | */
|
---|
474 | break;
|
---|
475 | case FMT_SRATIONAL:
|
---|
476 | if (componentCount == 1) {
|
---|
477 | directory.setRational(tagType, new Rational(reader.getInt32(tagValueOffset), reader.getInt32(tagValueOffset + 4)));
|
---|
478 | } else if (componentCount > 1) {
|
---|
479 | Rational[] rationals = new Rational[componentCount];
|
---|
480 | for (int i = 0; i < componentCount; i++)
|
---|
481 | rationals[i] = new Rational(reader.getInt32(tagValueOffset + (8 * i)), reader.getInt32(tagValueOffset + 4 + (8 * i)));
|
---|
482 | directory.setRationalArray(tagType, rationals);
|
---|
483 | }
|
---|
484 | break;
|
---|
485 | case FMT_URATIONAL:
|
---|
486 | if (componentCount == 1) {
|
---|
487 | directory.setRational(tagType, new Rational(reader.getUInt32(tagValueOffset), reader.getUInt32(tagValueOffset + 4)));
|
---|
488 | } else if (componentCount > 1) {
|
---|
489 | Rational[] rationals = new Rational[componentCount];
|
---|
490 | for (int i = 0; i < componentCount; i++)
|
---|
491 | rationals[i] = new Rational(reader.getUInt32(tagValueOffset + (8 * i)), reader.getUInt32(tagValueOffset + 4 + (8 * i)));
|
---|
492 | directory.setRationalArray(tagType, rationals);
|
---|
493 | }
|
---|
494 | break;
|
---|
495 | case FMT_SINGLE:
|
---|
496 | if (componentCount == 1) {
|
---|
497 | directory.setFloat(tagType, reader.getFloat32(tagValueOffset));
|
---|
498 | } else {
|
---|
499 | float[] floats = new float[componentCount];
|
---|
500 | for (int i = 0; i < componentCount; i++)
|
---|
501 | floats[i] = reader.getFloat32(tagValueOffset + (i * 4));
|
---|
502 | directory.setFloatArray(tagType, floats);
|
---|
503 | }
|
---|
504 | break;
|
---|
505 | case FMT_DOUBLE:
|
---|
506 | if (componentCount == 1) {
|
---|
507 | directory.setDouble(tagType, reader.getDouble64(tagValueOffset));
|
---|
508 | } else {
|
---|
509 | double[] doubles = new double[componentCount];
|
---|
510 | for (int i = 0; i < componentCount; i++)
|
---|
511 | doubles[i] = reader.getDouble64(tagValueOffset + (i * 4));
|
---|
512 | directory.setDoubleArray(tagType, doubles);
|
---|
513 | }
|
---|
514 | break;
|
---|
515 |
|
---|
516 | //
|
---|
517 | // Note that all integral types are stored as int32 internally (the largest supported by TIFF)
|
---|
518 | //
|
---|
519 |
|
---|
520 | case FMT_SBYTE:
|
---|
521 | if (componentCount == 1) {
|
---|
522 | directory.setInt(tagType, reader.getInt8(tagValueOffset));
|
---|
523 | } else {
|
---|
524 | int[] bytes = new int[componentCount];
|
---|
525 | for (int i = 0; i < componentCount; i++)
|
---|
526 | bytes[i] = reader.getInt8(tagValueOffset + i);
|
---|
527 | directory.setIntArray(tagType, bytes);
|
---|
528 | }
|
---|
529 | break;
|
---|
530 | case FMT_BYTE:
|
---|
531 | if (componentCount == 1) {
|
---|
532 | directory.setInt(tagType, reader.getUInt8(tagValueOffset));
|
---|
533 | } else {
|
---|
534 | int[] bytes = new int[componentCount];
|
---|
535 | for (int i = 0; i < componentCount; i++)
|
---|
536 | bytes[i] = reader.getUInt8(tagValueOffset + i);
|
---|
537 | directory.setIntArray(tagType, bytes);
|
---|
538 | }
|
---|
539 | break;
|
---|
540 | case FMT_USHORT:
|
---|
541 | if (componentCount == 1) {
|
---|
542 | int i = reader.getUInt16(tagValueOffset);
|
---|
543 | directory.setInt(tagType, i);
|
---|
544 | } else {
|
---|
545 | int[] ints = new int[componentCount];
|
---|
546 | for (int i = 0; i < componentCount; i++)
|
---|
547 | ints[i] = reader.getUInt16(tagValueOffset + (i * 2));
|
---|
548 | directory.setIntArray(tagType, ints);
|
---|
549 | }
|
---|
550 | break;
|
---|
551 | case FMT_SSHORT:
|
---|
552 | if (componentCount == 1) {
|
---|
553 | int i = reader.getInt16(tagValueOffset);
|
---|
554 | directory.setInt(tagType, i);
|
---|
555 | } else {
|
---|
556 | int[] ints = new int[componentCount];
|
---|
557 | for (int i = 0; i < componentCount; i++)
|
---|
558 | ints[i] = reader.getInt16(tagValueOffset + (i * 2));
|
---|
559 | directory.setIntArray(tagType, ints);
|
---|
560 | }
|
---|
561 | break;
|
---|
562 | case FMT_SLONG:
|
---|
563 | case FMT_ULONG:
|
---|
564 | // NOTE 'long' in this case means 32 bit, not 64
|
---|
565 | if (componentCount == 1) {
|
---|
566 | int i = reader.getInt32(tagValueOffset);
|
---|
567 | directory.setInt(tagType, i);
|
---|
568 | } else {
|
---|
569 | int[] ints = new int[componentCount];
|
---|
570 | for (int i = 0; i < componentCount; i++)
|
---|
571 | ints[i] = reader.getInt32(tagValueOffset + (i * 4));
|
---|
572 | directory.setIntArray(tagType, ints);
|
---|
573 | }
|
---|
574 | break;
|
---|
575 | default:
|
---|
576 | directory.addError("Unknown format code " + formatCode + " for tag " + tagType);
|
---|
577 | }
|
---|
578 | }
|
---|
579 |
|
---|
580 | /**
|
---|
581 | * Determine the offset at which a given InteropArray entry begins within the specified IFD.
|
---|
582 | *
|
---|
583 | * @param dirStartOffset the offset at which the IFD starts
|
---|
584 | * @param entryNumber the zero-based entry number
|
---|
585 | */
|
---|
586 | private int calculateTagOffset(int dirStartOffset, int entryNumber)
|
---|
587 | {
|
---|
588 | // add 2 bytes for the tag count
|
---|
589 | // each entry is 12 bytes, so we skip 12 * the number seen so far
|
---|
590 | return dirStartOffset + 2 + (12 * entryNumber);
|
---|
591 | }
|
---|
592 | }
|
---|