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

Last change on this file since 8317 was 8243, checked in by Don-vip, 11 years ago

fix #11359 - update to metadata-extractor 2.8.1

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