source: josm/trunk/src/com/drew/metadata/exif/ExifTiffHandler.java@ 12187

Last change on this file since 12187 was 10862, checked in by Don-vip, 8 years ago

update to metadata-extractor 2.9.1

File size: 22.1 KB
Line 
1/*
2 * Copyright 2002-2016 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 * https://drewnoakes.com/code/exif/
19 * https://github.com/drewnoakes/metadata-extractor
20 */
21package com.drew.metadata.exif;
22
23import com.drew.imaging.tiff.TiffProcessingException;
24import com.drew.imaging.tiff.TiffReader;
25import com.drew.lang.RandomAccessReader;
26import com.drew.lang.SequentialByteArrayReader;
27import com.drew.lang.annotations.NotNull;
28import com.drew.lang.annotations.Nullable;
29import com.drew.metadata.Directory;
30import com.drew.metadata.Metadata;
31import com.drew.metadata.exif.makernotes.*;
32import com.drew.metadata.iptc.IptcReader;
33import com.drew.metadata.tiff.DirectoryTiffHandler;
34
35import java.io.IOException;
36import java.util.Set;
37
38/**
39 * Implementation of {@link com.drew.imaging.tiff.TiffHandler} used for handling TIFF tags according to the Exif
40 * standard.
41 * <p>
42 * Includes support for camera manufacturer makernotes.
43 *
44 * @author Drew Noakes https://drewnoakes.com
45 */
46public class ExifTiffHandler extends DirectoryTiffHandler
47{
48 private final boolean _storeThumbnailBytes;
49
50 public ExifTiffHandler(@NotNull Metadata metadata, boolean storeThumbnailBytes, @Nullable Directory parentDirectory)
51 {
52 super(metadata, ExifIFD0Directory.class);
53 _storeThumbnailBytes = storeThumbnailBytes;
54
55 if (parentDirectory != null)
56 _currentDirectory.setParent(parentDirectory);
57 }
58
59 public void setTiffMarker(int marker) throws TiffProcessingException
60 {
61 final int standardTiffMarker = 0x002A;
62 final int olympusRawTiffMarker = 0x4F52; // for ORF files
63 final int olympusRawTiffMarker2 = 0x5352; // for ORF files
64 final int panasonicRawTiffMarker = 0x0055; // for RW2 files
65
66 if (marker != standardTiffMarker && marker != olympusRawTiffMarker && marker != olympusRawTiffMarker2 && marker != panasonicRawTiffMarker) {
67 throw new TiffProcessingException("Unexpected TIFF marker: 0x" + Integer.toHexString(marker));
68 }
69 }
70
71 public boolean tryEnterSubIfd(int tagId)
72 {
73 if (tagId == ExifDirectoryBase.TAG_SUB_IFD_OFFSET) {
74 pushDirectory(ExifSubIFDDirectory.class);
75 return true;
76 }
77
78 if (_currentDirectory instanceof ExifIFD0Directory) {
79 if (tagId == ExifIFD0Directory.TAG_EXIF_SUB_IFD_OFFSET) {
80 pushDirectory(ExifSubIFDDirectory.class);
81 return true;
82 }
83
84 if (tagId == ExifIFD0Directory.TAG_GPS_INFO_OFFSET) {
85 pushDirectory(GpsDirectory.class);
86 return true;
87 }
88 }
89
90 if (_currentDirectory instanceof ExifSubIFDDirectory) {
91 if (tagId == ExifSubIFDDirectory.TAG_INTEROP_OFFSET) {
92 pushDirectory(ExifInteropDirectory.class);
93 return true;
94 }
95 }
96
97 if (_currentDirectory instanceof OlympusMakernoteDirectory) {
98 if (tagId == OlympusMakernoteDirectory.TAG_EQUIPMENT) {
99 pushDirectory(OlympusEquipmentMakernoteDirectory.class);
100 return true;
101 }
102
103 if (tagId == OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS) {
104 pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
105 return true;
106 }
107 }
108
109 return false;
110 }
111
112 public boolean hasFollowerIfd()
113 {
114 // In Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case.
115 if (_currentDirectory instanceof ExifIFD0Directory) {
116 pushDirectory(ExifThumbnailDirectory.class);
117 return true;
118 }
119
120 // The Canon EOS 7D (CR2) has three chained/following thumbnail IFDs
121 if (_currentDirectory instanceof ExifThumbnailDirectory)
122 return true;
123
124 // This should not happen, as Exif doesn't use follower IFDs apart from that above.
125 // NOTE have seen the CanonMakernoteDirectory IFD have a follower pointer, but it points to invalid data.
126 return false;
127 }
128
129 @Nullable
130 public Long tryCustomProcessFormat(final int tagId, final int formatCode, final long componentCount)
131 {
132 if (formatCode == 13)
133 return componentCount * 4;
134
135 return null;
136 }
137
138 public boolean customProcessTag(final int tagOffset,
139 final @NotNull Set<Integer> processedIfdOffsets,
140 final int tiffHeaderOffset,
141 final @NotNull RandomAccessReader reader,
142 final int tagId,
143 final int byteCount) throws IOException
144 {
145 // Custom processing for the Makernote tag
146 if (tagId == ExifSubIFDDirectory.TAG_MAKERNOTE && _currentDirectory instanceof ExifSubIFDDirectory) {
147 return processMakernote(tagOffset, processedIfdOffsets, tiffHeaderOffset, reader);
148 }
149
150 // Custom processing for embedded IPTC data
151 if (tagId == ExifSubIFDDirectory.TAG_IPTC_NAA && _currentDirectory instanceof ExifIFD0Directory) {
152 // NOTE Adobe sets type 4 for IPTC instead of 7
153 if (reader.getInt8(tagOffset) == 0x1c) {
154 final byte[] iptcBytes = reader.getBytes(tagOffset, byteCount);
155 new IptcReader().extract(new SequentialByteArrayReader(iptcBytes), _metadata, iptcBytes.length, _currentDirectory);
156 return true;
157 }
158 return false;
159 }
160
161 return false;
162 }
163
164 public void completed(@NotNull final RandomAccessReader reader, final int tiffHeaderOffset)
165 {
166 if (_storeThumbnailBytes) {
167 // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
168 ExifThumbnailDirectory thumbnailDirectory = _metadata.getFirstDirectoryOfType(ExifThumbnailDirectory.class);
169 if (thumbnailDirectory != null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_COMPRESSION)) {
170 Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
171 Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
172 if (offset != null && length != null) {
173 try {
174 byte[] thumbnailData = reader.getBytes(tiffHeaderOffset + offset, length);
175 thumbnailDirectory.setThumbnailData(thumbnailData);
176 } catch (IOException ex) {
177 thumbnailDirectory.addError("Invalid thumbnail data specification: " + ex.getMessage());
178 }
179 }
180 }
181 }
182 }
183
184 private boolean processMakernote(final int makernoteOffset,
185 final @NotNull Set<Integer> processedIfdOffsets,
186 final int tiffHeaderOffset,
187 final @NotNull RandomAccessReader reader) throws IOException
188 {
189 // Determine the camera model and makernote format.
190 Directory ifd0Directory = _metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
191
192 if (ifd0Directory == null)
193 return false;
194
195 String cameraMake = ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
196
197 final String firstTwoChars = reader.getString(makernoteOffset, 2);
198 final String firstThreeChars = reader.getString(makernoteOffset, 3);
199 final String firstFourChars = reader.getString(makernoteOffset, 4);
200 final String firstFiveChars = reader.getString(makernoteOffset, 5);
201 final String firstSixChars = reader.getString(makernoteOffset, 6);
202 final String firstSevenChars = reader.getString(makernoteOffset, 7);
203 final String firstEightChars = reader.getString(makernoteOffset, 8);
204 final String firstTenChars = reader.getString(makernoteOffset, 10);
205 final String firstTwelveChars = reader.getString(makernoteOffset, 12);
206
207 boolean byteOrderBefore = reader.isMotorolaByteOrder();
208
209 if ("OLYMP\0".equals(firstSixChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {
210 // Olympus Makernote
211 // Epson and Agfa use Olympus makernote standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/
212 pushDirectory(OlympusMakernoteDirectory.class);
213 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
214 } else if ("OLYMPUS\0II".equals(firstTenChars)) {
215 // Olympus Makernote (alternate)
216 // Note that data is relative to the beginning of the makernote
217 // http://exiv2.org/makernote.html
218 pushDirectory(OlympusMakernoteDirectory.class);
219 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, makernoteOffset);
220 } else if (cameraMake != null && cameraMake.toUpperCase().startsWith("MINOLTA")) {
221 // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote
222 // area that commences immediately.
223 pushDirectory(OlympusMakernoteDirectory.class);
224 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
225 } else if (cameraMake != null && cameraMake.trim().toUpperCase().startsWith("NIKON")) {
226 if ("Nikon".equals(firstFiveChars)) {
227 /* There are two scenarios here:
228 * Type 1: **
229 * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon...........
230 * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................
231 * Type 3: **
232 * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*...
233 * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200
234 */
235 switch (reader.getUInt8(makernoteOffset + 6)) {
236 case 1:
237 pushDirectory(NikonType1MakernoteDirectory.class);
238 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
239 break;
240 case 2:
241 pushDirectory(NikonType2MakernoteDirectory.class);
242 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 18, makernoteOffset + 10);
243 break;
244 default:
245 ifd0Directory.addError("Unsupported Nikon makernote data ignored.");
246 break;
247 }
248 } else {
249 // The IFD begins with the first Makernote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models.
250 pushDirectory(NikonType2MakernoteDirectory.class);
251 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
252 }
253 } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) {
254 pushDirectory(SonyType1MakernoteDirectory.class);
255 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset);
256 } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) {
257 // force MM for this directory
258 reader.setMotorolaByteOrder(true);
259 // skip 12 byte header + 2 for "MM" + 6
260 pushDirectory(SonyType6MakernoteDirectory.class);
261 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 20, tiffHeaderOffset);
262 } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) {
263 pushDirectory(SigmaMakernoteDirectory.class);
264 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 10, tiffHeaderOffset);
265 } else if ("KDK".equals(firstThreeChars)) {
266 reader.setMotorolaByteOrder(firstSevenChars.equals("KDK INFO"));
267 KodakMakernoteDirectory directory = new KodakMakernoteDirectory();
268 _metadata.addDirectory(directory);
269 processKodakMakernote(directory, makernoteOffset, reader);
270 } else if ("Canon".equalsIgnoreCase(cameraMake)) {
271 pushDirectory(CanonMakernoteDirectory.class);
272 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
273 } else if (cameraMake != null && cameraMake.toUpperCase().startsWith("CASIO")) {
274 if ("QVC\u0000\u0000\u0000".equals(firstSixChars)) {
275 pushDirectory(CasioType2MakernoteDirectory.class);
276 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6, tiffHeaderOffset);
277 } else {
278 pushDirectory(CasioType1MakernoteDirectory.class);
279 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
280 }
281 } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraMake)) {
282 // Note that this also applies to certain Leica cameras, such as the Digilux-4.3
283 reader.setMotorolaByteOrder(false);
284 // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote
285 // IFD, though the offset is relative to the start of the makernote, not the TIFF
286 // header (like everywhere else)
287 int ifdStart = makernoteOffset + reader.getInt32(makernoteOffset + 8);
288 pushDirectory(FujifilmMakernoteDirectory.class);
289 TiffReader.processIfd(this, reader, processedIfdOffsets, ifdStart, makernoteOffset);
290 } else if ("KYOCERA".equals(firstSevenChars)) {
291 // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html
292 pushDirectory(KyoceraMakernoteDirectory.class);
293 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 22, tiffHeaderOffset);
294 } else if ("LEICA".equals(firstFiveChars)) {
295 reader.setMotorolaByteOrder(false);
296 if ("Leica Camera AG".equals(cameraMake)) {
297 pushDirectory(LeicaMakernoteDirectory.class);
298 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
299 } else if ("LEICA".equals(cameraMake)) {
300 // Some Leica cameras use Panasonic makernote tags
301 pushDirectory(PanasonicMakernoteDirectory.class);
302 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
303 } else {
304 return false;
305 }
306 } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(makernoteOffset, 12))) {
307 // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD
308 // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
309 // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html
310 pushDirectory(PanasonicMakernoteDirectory.class);
311 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset);
312 } else if ("AOC\u0000".equals(firstFourChars)) {
313 // NON-Standard TIFF IFD Data using Casio Type 2 Tags
314 // IFD has no Next-IFD pointer at end of IFD, and
315 // Offsets are relative to the start of the current IFD tag, not the TIFF header
316 // Observed for:
317 // - Pentax ist D
318 pushDirectory(CasioType2MakernoteDirectory.class);
319 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6, makernoteOffset);
320 } else if (cameraMake != null && (cameraMake.toUpperCase().startsWith("PENTAX") || cameraMake.toUpperCase().startsWith("ASAHI"))) {
321 // NON-Standard TIFF IFD Data using Pentax Tags
322 // IFD has no Next-IFD pointer at end of IFD, and
323 // Offsets are relative to the start of the current IFD tag, not the TIFF header
324 // Observed for:
325 // - PENTAX Optio 330
326 // - PENTAX Optio 430
327 pushDirectory(PentaxMakernoteDirectory.class);
328 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, makernoteOffset);
329// } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) {
330// // This Konica data is not understood. Header identified in accordance with information at this site:
331// // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html
332// // TODO add support for minolta/konica cameras
333// exifDirectory.addError("Unsupported Konica/Minolta data ignored.");
334 } else if ("SANYO\0\1\0".equals(firstEightChars)) {
335 pushDirectory(SanyoMakernoteDirectory.class);
336 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset);
337 } else if (cameraMake != null && cameraMake.toLowerCase().startsWith("ricoh")) {
338 if (firstTwoChars.equals("Rv") || firstThreeChars.equals("Rev")) {
339 // This is a textual format, where the makernote bytes look like:
340 // Rv0103;Rg1C;Bg18;Ll0;Ld0;Aj0000;Bn0473800;Fp2E00:������������������������������
341 // Rv0103;Rg1C;Bg18;Ll0;Ld0;Aj0000;Bn0473800;Fp2D05:������������������������������
342 // Rv0207;Sf6C84;Rg76;Bg60;Gg42;Ll0;Ld0;Aj0004;Bn0B02900;Fp10B8;Md6700;Ln116900086D27;Sv263:0000000000000000000000��
343 // This format is currently unsupported
344 return false;
345 } else if (firstFiveChars.equalsIgnoreCase("Ricoh")) {
346 // Always in Motorola byte order
347 reader.setMotorolaByteOrder(true);
348 pushDirectory(RicohMakernoteDirectory.class);
349 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset);
350 }
351 } else {
352 // The makernote is not comprehended by this library.
353 // If you are reading this and believe a particular camera's image should be processed, get in touch.
354 return false;
355 }
356
357 reader.setMotorolaByteOrder(byteOrderBefore);
358 return true;
359 }
360
361 private static void processKodakMakernote(@NotNull final KodakMakernoteDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader)
362 {
363 // Kodak's makernote is not in IFD format. It has values at fixed offsets.
364 int dataOffset = tagValueOffset + 8;
365 try {
366 directory.setString(KodakMakernoteDirectory.TAG_KODAK_MODEL, reader.getString(dataOffset, 8));
367 directory.setInt(KodakMakernoteDirectory.TAG_QUALITY, reader.getUInt8(dataOffset + 9));
368 directory.setInt(KodakMakernoteDirectory.TAG_BURST_MODE, reader.getUInt8(dataOffset + 10));
369 directory.setInt(KodakMakernoteDirectory.TAG_IMAGE_WIDTH, reader.getUInt16(dataOffset + 12));
370 directory.setInt(KodakMakernoteDirectory.TAG_IMAGE_HEIGHT, reader.getUInt16(dataOffset + 14));
371 directory.setInt(KodakMakernoteDirectory.TAG_YEAR_CREATED, reader.getUInt16(dataOffset + 16));
372 directory.setByteArray(KodakMakernoteDirectory.TAG_MONTH_DAY_CREATED, reader.getBytes(dataOffset + 18, 2));
373 directory.setByteArray(KodakMakernoteDirectory.TAG_TIME_CREATED, reader.getBytes(dataOffset + 20, 4));
374 directory.setInt(KodakMakernoteDirectory.TAG_BURST_MODE_2, reader.getUInt16(dataOffset + 24));
375 directory.setInt(KodakMakernoteDirectory.TAG_SHUTTER_MODE, reader.getUInt8(dataOffset + 27));
376 directory.setInt(KodakMakernoteDirectory.TAG_METERING_MODE, reader.getUInt8(dataOffset + 28));
377 directory.setInt(KodakMakernoteDirectory.TAG_SEQUENCE_NUMBER, reader.getUInt8(dataOffset + 29));
378 directory.setInt(KodakMakernoteDirectory.TAG_F_NUMBER, reader.getUInt16(dataOffset + 30));
379 directory.setLong(KodakMakernoteDirectory.TAG_EXPOSURE_TIME, reader.getUInt32(dataOffset + 32));
380 directory.setInt(KodakMakernoteDirectory.TAG_EXPOSURE_COMPENSATION, reader.getInt16(dataOffset + 36));
381 directory.setInt(KodakMakernoteDirectory.TAG_FOCUS_MODE, reader.getUInt8(dataOffset + 56));
382 directory.setInt(KodakMakernoteDirectory.TAG_WHITE_BALANCE, reader.getUInt8(dataOffset + 64));
383 directory.setInt(KodakMakernoteDirectory.TAG_FLASH_MODE, reader.getUInt8(dataOffset + 92));
384 directory.setInt(KodakMakernoteDirectory.TAG_FLASH_FIRED, reader.getUInt8(dataOffset + 93));
385 directory.setInt(KodakMakernoteDirectory.TAG_ISO_SETTING, reader.getUInt16(dataOffset + 94));
386 directory.setInt(KodakMakernoteDirectory.TAG_ISO, reader.getUInt16(dataOffset + 96));
387 directory.setInt(KodakMakernoteDirectory.TAG_TOTAL_ZOOM, reader.getUInt16(dataOffset + 98));
388 directory.setInt(KodakMakernoteDirectory.TAG_DATE_TIME_STAMP, reader.getUInt16(dataOffset + 100));
389 directory.setInt(KodakMakernoteDirectory.TAG_COLOR_MODE, reader.getUInt16(dataOffset + 102));
390 directory.setInt(KodakMakernoteDirectory.TAG_DIGITAL_ZOOM, reader.getUInt16(dataOffset + 104));
391 directory.setInt(KodakMakernoteDirectory.TAG_SHARPNESS, reader.getInt8(dataOffset + 107));
392 } catch (IOException ex) {
393 directory.addError("Error processing Kodak makernote data: " + ex.getMessage());
394 }
395 }
396}
397
Note: See TracBrowser for help on using the repository browser.