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

Last change on this file since 8132 was 8132, checked in by Don-vip, 9 years ago

fix #11162 - update to metadata-extractor 2.7.2

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