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

Last change on this file since 13500 was 13061, checked in by Don-vip, 6 years ago

fix #15505 - update to metadata-extractor 2.10.1

File size: 49.7 KB
Line 
1/*
2 * Copyright 2002-2017 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 java.io.ByteArrayInputStream;
24import java.io.IOException;
25import java.util.Arrays;
26import java.util.Set;
27
28import com.drew.imaging.jpeg.JpegMetadataReader;
29import com.drew.imaging.jpeg.JpegProcessingException;
30import com.drew.imaging.tiff.TiffProcessingException;
31import com.drew.imaging.tiff.TiffReader;
32import com.drew.lang.Charsets;
33import com.drew.lang.RandomAccessReader;
34import com.drew.lang.SequentialByteArrayReader;
35import com.drew.lang.annotations.NotNull;
36import com.drew.lang.annotations.Nullable;
37import com.drew.metadata.Directory;
38import com.drew.metadata.Metadata;
39import com.drew.metadata.StringValue;
40import com.drew.metadata.exif.makernotes.*;
41import com.drew.metadata.iptc.IptcReader;
42import com.drew.metadata.tiff.DirectoryTiffHandler;
43
44/**
45 * Implementation of {@link com.drew.imaging.tiff.TiffHandler} used for handling TIFF tags according to the Exif
46 * standard.
47 * <p>
48 * Includes support for camera manufacturer makernotes.
49 *
50 * @author Drew Noakes https://drewnoakes.com
51 */
52public class ExifTiffHandler extends DirectoryTiffHandler
53{
54 public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDirectory)
55 {
56 super(metadata);
57
58 if (parentDirectory != null)
59 _currentDirectory.setParent(parentDirectory);
60 }
61
62 public void setTiffMarker(int marker) throws TiffProcessingException
63 {
64 final int standardTiffMarker = 0x002A;
65 final int olympusRawTiffMarker = 0x4F52; // for ORF files
66 final int olympusRawTiffMarker2 = 0x5352; // for ORF files
67 final int panasonicRawTiffMarker = 0x0055; // for RW2 files
68
69 switch (marker)
70 {
71 case standardTiffMarker:
72 case olympusRawTiffMarker: // Todo: implement an IFD0, if there is one
73 case olympusRawTiffMarker2: // Todo: implement an IFD0, if there is one
74 pushDirectory(ExifIFD0Directory.class);
75 break;
76 case panasonicRawTiffMarker:
77 pushDirectory(PanasonicRawIFD0Directory.class);
78 break;
79 default:
80 throw new TiffProcessingException(String.format("Unexpected TIFF marker: 0x%X", marker));
81 }
82 }
83
84 public boolean tryEnterSubIfd(int tagId)
85 {
86 if (tagId == ExifDirectoryBase.TAG_SUB_IFD_OFFSET) {
87 pushDirectory(ExifSubIFDDirectory.class);
88 return true;
89 }
90
91 if (_currentDirectory instanceof ExifIFD0Directory || _currentDirectory instanceof PanasonicRawIFD0Directory) {
92 if (tagId == ExifIFD0Directory.TAG_EXIF_SUB_IFD_OFFSET) {
93 pushDirectory(ExifSubIFDDirectory.class);
94 return true;
95 }
96
97 if (tagId == ExifIFD0Directory.TAG_GPS_INFO_OFFSET) {
98 pushDirectory(GpsDirectory.class);
99 return true;
100 }
101 }
102
103 if (_currentDirectory instanceof ExifSubIFDDirectory) {
104 if (tagId == ExifSubIFDDirectory.TAG_INTEROP_OFFSET) {
105 pushDirectory(ExifInteropDirectory.class);
106 return true;
107 }
108 }
109
110 if (_currentDirectory instanceof OlympusMakernoteDirectory) {
111 // Note: these also appear in customProcessTag because some are IFD pointers while others begin immediately
112 // for the same directories
113 switch(tagId) {
114 case OlympusMakernoteDirectory.TAG_EQUIPMENT:
115 pushDirectory(OlympusEquipmentMakernoteDirectory.class);
116 return true;
117 case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS:
118 pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
119 return true;
120 case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT:
121 pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class);
122 return true;
123 case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2:
124 pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class);
125 return true;
126 case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING:
127 pushDirectory(OlympusImageProcessingMakernoteDirectory.class);
128 return true;
129 case OlympusMakernoteDirectory.TAG_FOCUS_INFO:
130 pushDirectory(OlympusFocusInfoMakernoteDirectory.class);
131 return true;
132 case OlympusMakernoteDirectory.TAG_RAW_INFO:
133 pushDirectory(OlympusRawInfoMakernoteDirectory.class);
134 return true;
135 case OlympusMakernoteDirectory.TAG_MAIN_INFO:
136 pushDirectory(OlympusMakernoteDirectory.class);
137 return true;
138 }
139 }
140
141 return false;
142 }
143
144 public boolean hasFollowerIfd()
145 {
146 // In Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case.
147 // UPDATE: In multipage TIFFs, the 'follower' IFD points to the next image in the set
148 if (_currentDirectory instanceof ExifIFD0Directory || _currentDirectory instanceof ExifImageDirectory) {
149 // If the PageNumber tag is defined, assume this is a multipage TIFF or similar
150 // TODO: Find better ways to know which follower Directory should be used
151 if (_currentDirectory.containsTag(ExifDirectoryBase.TAG_PAGE_NUMBER))
152 pushDirectory(ExifImageDirectory.class);
153 else
154 pushDirectory(ExifThumbnailDirectory.class);
155 return true;
156 }
157
158 // The Canon EOS 7D (CR2) has three chained/following thumbnail IFDs
159 if (_currentDirectory instanceof ExifThumbnailDirectory)
160 return true;
161
162 // This should not happen, as Exif doesn't use follower IFDs apart from that above.
163 // NOTE have seen the CanonMakernoteDirectory IFD have a follower pointer, but it points to invalid data.
164 return false;
165 }
166
167 @Nullable
168 public Long tryCustomProcessFormat(final int tagId, final int formatCode, final long componentCount)
169 {
170 if (formatCode == 13)
171 return componentCount * 4;
172
173 // an unknown (0) formatCode needs to be potentially handled later as a highly custom directory tag
174 if(formatCode == 0)
175 return 0L;
176
177 return null;
178 }
179
180 public boolean customProcessTag(final int tagOffset,
181 final @NotNull Set<Integer> processedIfdOffsets,
182 final int tiffHeaderOffset,
183 final @NotNull RandomAccessReader reader,
184 final int tagId,
185 final int byteCount) throws IOException
186 {
187 // Some 0x0000 tags have a 0 byteCount. Determine whether it's bad.
188 if (tagId == 0)
189 {
190 if (_currentDirectory.containsTag(tagId))
191 {
192 // Let it go through for now. Some directories handle it, some don't
193 return false;
194 }
195
196 // Skip over 0x0000 tags that don't have any associated bytes. No idea what it contains in this case, if anything.
197 if (byteCount == 0)
198 return true;
199 }
200
201 // Custom processing for the Makernote tag
202 if (tagId == ExifSubIFDDirectory.TAG_MAKERNOTE && _currentDirectory instanceof ExifSubIFDDirectory) {
203 return processMakernote(tagOffset, processedIfdOffsets, tiffHeaderOffset, reader);
204 }
205
206 // Custom processing for embedded IPTC data
207 if (tagId == ExifSubIFDDirectory.TAG_IPTC_NAA && _currentDirectory instanceof ExifIFD0Directory) {
208 // NOTE Adobe sets type 4 for IPTC instead of 7
209 if (reader.getInt8(tagOffset) == 0x1c) {
210 final byte[] iptcBytes = reader.getBytes(tagOffset, byteCount);
211 new IptcReader().extract(new SequentialByteArrayReader(iptcBytes), _metadata, iptcBytes.length, _currentDirectory);
212 return true;
213 }
214 return false;
215 }
216
217 if (HandlePrintIM(_currentDirectory, tagId))
218 {
219 PrintIMDirectory printIMDirectory = new PrintIMDirectory();
220 printIMDirectory.setParent(_currentDirectory);
221 _metadata.addDirectory(printIMDirectory);
222 ProcessPrintIM(printIMDirectory, tagOffset, reader, byteCount);
223 return true;
224 }
225
226 // Note: these also appear in tryEnterSubIfd because some are IFD pointers while others begin immediately
227 // for the same directories
228 if(_currentDirectory instanceof OlympusMakernoteDirectory)
229 {
230 switch (tagId)
231 {
232 case OlympusMakernoteDirectory.TAG_EQUIPMENT:
233 pushDirectory(OlympusEquipmentMakernoteDirectory.class);
234 TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
235 return true;
236 case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS:
237 pushDirectory(OlympusCameraSettingsMakernoteDirectory.class);
238 TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
239 return true;
240 case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT:
241 pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class);
242 TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
243 return true;
244 case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2:
245 pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class);
246 TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
247 return true;
248 case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING:
249 pushDirectory(OlympusImageProcessingMakernoteDirectory.class);
250 TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
251 return true;
252 case OlympusMakernoteDirectory.TAG_FOCUS_INFO:
253 pushDirectory(OlympusFocusInfoMakernoteDirectory.class);
254 TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
255 return true;
256 case OlympusMakernoteDirectory.TAG_RAW_INFO:
257 pushDirectory(OlympusRawInfoMakernoteDirectory.class);
258 TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
259 return true;
260 case OlympusMakernoteDirectory.TAG_MAIN_INFO:
261 pushDirectory(OlympusMakernoteDirectory.class);
262 TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset);
263 return true;
264 }
265 }
266
267 if (_currentDirectory instanceof PanasonicRawIFD0Directory)
268 {
269 // these contain binary data with specific offsets, and can't be processed as regular ifd's.
270 // The binary data is broken into 'fake' tags and there is a pattern.
271 switch (tagId)
272 {
273 case PanasonicRawIFD0Directory.TagWbInfo:
274 PanasonicRawWbInfoDirectory dirWbInfo = new PanasonicRawWbInfoDirectory();
275 dirWbInfo.setParent(_currentDirectory);
276 _metadata.addDirectory(dirWbInfo);
277 ProcessBinary(dirWbInfo, tagOffset, reader, byteCount, false, 2);
278 return true;
279 case PanasonicRawIFD0Directory.TagWbInfo2:
280 PanasonicRawWbInfo2Directory dirWbInfo2 = new PanasonicRawWbInfo2Directory();
281 dirWbInfo2.setParent(_currentDirectory);
282 _metadata.addDirectory(dirWbInfo2);
283 ProcessBinary(dirWbInfo2, tagOffset, reader, byteCount, false, 3);
284 return true;
285 case PanasonicRawIFD0Directory.TagDistortionInfo:
286 PanasonicRawDistortionDirectory dirDistort = new PanasonicRawDistortionDirectory();
287 dirDistort.setParent(_currentDirectory);
288 _metadata.addDirectory(dirDistort);
289 ProcessBinary(dirDistort, tagOffset, reader, byteCount, true, 1);
290 return true;
291 }
292 }
293
294 // Panasonic RAW sometimes contains an embedded version of the data as a JPG file.
295 if (tagId == PanasonicRawIFD0Directory.TagJpgFromRaw && _currentDirectory instanceof PanasonicRawIFD0Directory)
296 {
297 byte[] jpegrawbytes = reader.getBytes(tagOffset, byteCount);
298
299 // Extract information from embedded image since it is metadata-rich
300 ByteArrayInputStream jpegmem = new ByteArrayInputStream(jpegrawbytes);
301 try {
302 Metadata jpegDirectory = JpegMetadataReader.readMetadata(jpegmem);
303 for (Directory directory : jpegDirectory.getDirectories()) {
304 directory.setParent(_currentDirectory);
305 _metadata.addDirectory(directory);
306 }
307 return true;
308 } catch (JpegProcessingException e) {
309 _currentDirectory.addError("Error processing JpgFromRaw: " + e.getMessage());
310 } catch (IOException e) {
311 _currentDirectory.addError("Error reading JpgFromRaw: " + e.getMessage());
312 }
313 }
314
315 return false;
316 }
317
318 private static void ProcessBinary(@NotNull final Directory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount, final Boolean issigned, final int arrayLength) throws IOException
319 {
320 // expects signed/unsigned int16 (for now)
321 //int byteSize = issigned ? sizeof(short) : sizeof(ushort);
322 int byteSize = 2;
323
324 // 'directory' is assumed to contain tags that correspond to the byte position unless it's a set of bytes
325 for (int i = 0; i < byteCount; i++)
326 {
327 if (directory.hasTagName(i))
328 {
329 // only process this tag if the 'next' integral tag exists. Otherwise, it's a set of bytes
330 if (i < byteCount - 1 && directory.hasTagName(i + 1))
331 {
332 if(issigned)
333 directory.setObject(i, reader.getInt16(tagValueOffset + (i* byteSize)));
334 else
335 directory.setObject(i, reader.getUInt16(tagValueOffset + (i* byteSize)));
336 }
337 else
338 {
339 // the next arrayLength bytes are a multi-byte value
340 if (issigned)
341 {
342 short[] val = new short[arrayLength];
343 for (int j = 0; j<val.length; j++)
344 val[j] = reader.getInt16(tagValueOffset + ((i + j) * byteSize));
345 directory.setObjectArray(i, val);
346 }
347 else
348 {
349 int[] val = new int[arrayLength];
350 for (int j = 0; j<val.length; j++)
351 val[j] = reader.getUInt16(tagValueOffset + ((i + j) * byteSize));
352 directory.setObjectArray(i, val);
353 }
354
355 i += arrayLength - 1;
356 }
357 }
358 }
359 }
360
361 private boolean processMakernote(final int makernoteOffset,
362 final @NotNull Set<Integer> processedIfdOffsets,
363 final int tiffHeaderOffset,
364 final @NotNull RandomAccessReader reader) throws IOException
365 {
366 // Determine the camera model and makernote format.
367 Directory ifd0Directory = _metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
368
369 String cameraMake = ifd0Directory == null ? null : ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
370
371 final String firstTwoChars = reader.getString(makernoteOffset, 2, Charsets.UTF_8);
372 final String firstThreeChars = reader.getString(makernoteOffset, 3, Charsets.UTF_8);
373 final String firstFourChars = reader.getString(makernoteOffset, 4, Charsets.UTF_8);
374 final String firstFiveChars = reader.getString(makernoteOffset, 5, Charsets.UTF_8);
375 final String firstSixChars = reader.getString(makernoteOffset, 6, Charsets.UTF_8);
376 final String firstSevenChars = reader.getString(makernoteOffset, 7, Charsets.UTF_8);
377 final String firstEightChars = reader.getString(makernoteOffset, 8, Charsets.UTF_8);
378 final String firstNineChars = reader.getString(makernoteOffset, 9, Charsets.UTF_8);
379 final String firstTenChars = reader.getString(makernoteOffset, 10, Charsets.UTF_8);
380 final String firstTwelveChars = reader.getString(makernoteOffset, 12, Charsets.UTF_8);
381
382 boolean byteOrderBefore = reader.isMotorolaByteOrder();
383
384 if ("OLYMP\0".equals(firstSixChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {
385 // Olympus Makernote
386 // Epson and Agfa use Olympus makernote standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/
387 pushDirectory(OlympusMakernoteDirectory.class);
388 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
389 } else if ("OLYMPUS\0II".equals(firstTenChars)) {
390 // Olympus Makernote (alternate)
391 // Note that data is relative to the beginning of the makernote
392 // http://exiv2.org/makernote.html
393 pushDirectory(OlympusMakernoteDirectory.class);
394 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, makernoteOffset);
395 } else if (cameraMake != null && cameraMake.toUpperCase().startsWith("MINOLTA")) {
396 // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote
397 // area that commences immediately.
398 pushDirectory(OlympusMakernoteDirectory.class);
399 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
400 } else if (cameraMake != null && cameraMake.trim().toUpperCase().startsWith("NIKON")) {
401 if ("Nikon".equals(firstFiveChars)) {
402 /* There are two scenarios here:
403 * Type 1: **
404 * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon...........
405 * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................
406 * Type 3: **
407 * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*...
408 * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200
409 */
410 switch (reader.getUInt8(makernoteOffset + 6)) {
411 case 1:
412 pushDirectory(NikonType1MakernoteDirectory.class);
413 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
414 break;
415 case 2:
416 pushDirectory(NikonType2MakernoteDirectory.class);
417 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 18, makernoteOffset + 10);
418 break;
419 default:
420 _currentDirectory.addError("Unsupported Nikon makernote data ignored.");
421 break;
422 }
423 } else {
424 // The IFD begins with the first Makernote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models.
425 pushDirectory(NikonType2MakernoteDirectory.class);
426 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
427 }
428 } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) {
429 pushDirectory(SonyType1MakernoteDirectory.class);
430 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset);
431 // Do this check LAST after most other Sony checks
432 } else if (cameraMake != null && cameraMake.startsWith("SONY") &&
433 !Arrays.equals(reader.getBytes(makernoteOffset, 2), new byte[]{ 0x01, 0x00 }) ) {
434 // The IFD begins with the first Makernote byte (no ASCII name). Used in SR2 and ARW images
435 pushDirectory(SonyType1MakernoteDirectory.class);
436 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
437 } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) {
438 // force MM for this directory
439 reader.setMotorolaByteOrder(true);
440 // skip 12 byte header + 2 for "MM" + 6
441 pushDirectory(SonyType6MakernoteDirectory.class);
442 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 20, tiffHeaderOffset);
443 } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) {
444 pushDirectory(SigmaMakernoteDirectory.class);
445 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 10, tiffHeaderOffset);
446 } else if ("KDK".equals(firstThreeChars)) {
447 reader.setMotorolaByteOrder(firstSevenChars.equals("KDK INFO"));
448 KodakMakernoteDirectory directory = new KodakMakernoteDirectory();
449 _metadata.addDirectory(directory);
450 processKodakMakernote(directory, makernoteOffset, reader);
451 } else if ("Canon".equalsIgnoreCase(cameraMake)) {
452 pushDirectory(CanonMakernoteDirectory.class);
453 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
454 } else if (cameraMake != null && cameraMake.toUpperCase().startsWith("CASIO")) {
455 if ("QVC\u0000\u0000\u0000".equals(firstSixChars)) {
456 pushDirectory(CasioType2MakernoteDirectory.class);
457 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6, tiffHeaderOffset);
458 } else {
459 pushDirectory(CasioType1MakernoteDirectory.class);
460 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
461 }
462 } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraMake)) {
463 // Note that this also applies to certain Leica cameras, such as the Digilux-4.3
464 reader.setMotorolaByteOrder(false);
465 // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote
466 // IFD, though the offset is relative to the start of the makernote, not the TIFF
467 // header (like everywhere else)
468 int ifdStart = makernoteOffset + reader.getInt32(makernoteOffset + 8);
469 pushDirectory(FujifilmMakernoteDirectory.class);
470 TiffReader.processIfd(this, reader, processedIfdOffsets, ifdStart, makernoteOffset);
471 } else if ("KYOCERA".equals(firstSevenChars)) {
472 // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html
473 pushDirectory(KyoceraMakernoteDirectory.class);
474 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 22, tiffHeaderOffset);
475 } else if ("LEICA".equals(firstFiveChars)) {
476 reader.setMotorolaByteOrder(false);
477
478 // used by the X1/X2/X VARIO/T
479 // (X1 starts with "LEICA\0\x01\0", Make is "LEICA CAMERA AG")
480 // (X2 starts with "LEICA\0\x05\0", Make is "LEICA CAMERA AG")
481 // (X VARIO starts with "LEICA\0\x04\0", Make is "LEICA CAMERA AG")
482 // (T (Typ 701) starts with "LEICA\0\0x6", Make is "LEICA CAMERA AG")
483 // (X (Typ 113) starts with "LEICA\0\0x7", Make is "LEICA CAMERA AG")
484
485 if ("LEICA\0\u0001\0".equals(firstEightChars) ||
486 "LEICA\0\u0004\0".equals(firstEightChars) ||
487 "LEICA\0\u0005\0".equals(firstEightChars) ||
488 "LEICA\0\u0006\0".equals(firstEightChars) ||
489 "LEICA\0\u0007\0".equals(firstEightChars))
490 {
491 pushDirectory(LeicaType5MakernoteDirectory.class);
492 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset);
493 } else if ("Leica Camera AG".equals(cameraMake)) {
494 pushDirectory(LeicaMakernoteDirectory.class);
495 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
496 } else if ("LEICA".equals(cameraMake)) {
497 // Some Leica cameras use Panasonic makernote tags
498 pushDirectory(PanasonicMakernoteDirectory.class);
499 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset);
500 } else {
501 return false;
502 }
503 } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(makernoteOffset, 12, Charsets.UTF_8))) {
504 // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD
505 // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
506 // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html
507 pushDirectory(PanasonicMakernoteDirectory.class);
508 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset);
509 } else if ("AOC\u0000".equals(firstFourChars)) {
510 // NON-Standard TIFF IFD Data using Casio Type 2 Tags
511 // IFD has no Next-IFD pointer at end of IFD, and
512 // Offsets are relative to the start of the current IFD tag, not the TIFF header
513 // Observed for:
514 // - Pentax ist D
515 pushDirectory(CasioType2MakernoteDirectory.class);
516 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6, makernoteOffset);
517 } else if (cameraMake != null && (cameraMake.toUpperCase().startsWith("PENTAX") || cameraMake.toUpperCase().startsWith("ASAHI"))) {
518 // NON-Standard TIFF IFD Data using Pentax Tags
519 // IFD has no Next-IFD pointer at end of IFD, and
520 // Offsets are relative to the start of the current IFD tag, not the TIFF header
521 // Observed for:
522 // - PENTAX Optio 330
523 // - PENTAX Optio 430
524 pushDirectory(PentaxMakernoteDirectory.class);
525 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, makernoteOffset);
526// } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) {
527// // This Konica data is not understood. Header identified in accordance with information at this site:
528// // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html
529// // TODO add support for minolta/konica cameras
530// exifDirectory.addError("Unsupported Konica/Minolta data ignored.");
531 } else if ("SANYO\0\1\0".equals(firstEightChars)) {
532 pushDirectory(SanyoMakernoteDirectory.class);
533 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset);
534 } else if (cameraMake != null && cameraMake.toLowerCase().startsWith("ricoh")) {
535 if (firstTwoChars.equals("Rv") || firstThreeChars.equals("Rev")) {
536 // This is a textual format, where the makernote bytes look like:
537 // Rv0103;Rg1C;Bg18;Ll0;Ld0;Aj0000;Bn0473800;Fp2E00:������������������������������
538 // Rv0103;Rg1C;Bg18;Ll0;Ld0;Aj0000;Bn0473800;Fp2D05:������������������������������
539 // Rv0207;Sf6C84;Rg76;Bg60;Gg42;Ll0;Ld0;Aj0004;Bn0B02900;Fp10B8;Md6700;Ln116900086D27;Sv263:0000000000000000000000��
540 // This format is currently unsupported
541 return false;
542 } else if (firstFiveChars.equalsIgnoreCase("Ricoh")) {
543 // Always in Motorola byte order
544 reader.setMotorolaByteOrder(true);
545 pushDirectory(RicohMakernoteDirectory.class);
546 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset);
547 }
548 } else if (firstTenChars.equals("Apple iOS\0")) {
549 // Always in Motorola byte order
550 boolean orderBefore = reader.isMotorolaByteOrder();
551 reader.setMotorolaByteOrder(true);
552 pushDirectory(AppleMakernoteDirectory.class);
553 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 14, makernoteOffset);
554 reader.setMotorolaByteOrder(orderBefore);
555 } else if (reader.getUInt16(makernoteOffset) == ReconyxHyperFireMakernoteDirectory.MAKERNOTE_VERSION) {
556 ReconyxHyperFireMakernoteDirectory directory = new ReconyxHyperFireMakernoteDirectory();
557 _metadata.addDirectory(directory);
558 processReconyxHyperFireMakernote(directory, makernoteOffset, reader);
559 } else if (firstNineChars.equalsIgnoreCase("RECONYXUF")) {
560 ReconyxUltraFireMakernoteDirectory directory = new ReconyxUltraFireMakernoteDirectory();
561 _metadata.addDirectory(directory);
562 processReconyxUltraFireMakernote(directory, makernoteOffset, reader);
563 } else if ("SAMSUNG".equals(cameraMake)) {
564 // Only handles Type2 notes correctly. Others aren't implemented, and it's complex to determine which ones to use
565 pushDirectory(SamsungType2MakernoteDirectory.class);
566 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset);
567 } else {
568 // The makernote is not comprehended by this library.
569 // If you are reading this and believe a particular camera's image should be processed, get in touch.
570 return false;
571 }
572
573 reader.setMotorolaByteOrder(byteOrderBefore);
574 return true;
575 }
576
577 private static Boolean HandlePrintIM(@NotNull final Directory directory, final int tagId)
578 {
579 if (tagId == ExifDirectoryBase.TAG_PRINT_IMAGE_MATCHING_INFO)
580 return true;
581
582 if (tagId == 0x0E00)
583 {
584 // Tempting to say every tagid of 0x0E00 is a PIM tag, but can't be 100% sure
585 if (directory instanceof CasioType2MakernoteDirectory ||
586 directory instanceof KyoceraMakernoteDirectory ||
587 directory instanceof NikonType2MakernoteDirectory ||
588 directory instanceof OlympusMakernoteDirectory ||
589 directory instanceof PanasonicMakernoteDirectory ||
590 directory instanceof PentaxMakernoteDirectory ||
591 directory instanceof RicohMakernoteDirectory ||
592 directory instanceof SanyoMakernoteDirectory ||
593 directory instanceof SonyType1MakernoteDirectory)
594 return true;
595 }
596
597 return false;
598 }
599
600 /// <summary>
601 /// Process PrintIM IFD
602 /// </summary>
603 /// <remarks>
604 /// Converted from Exiftool version 10.33 created by Phil Harvey
605 /// http://www.sno.phy.queensu.ca/~phil/exiftool/
606 /// lib\Image\ExifTool\PrintIM.pm
607 /// </remarks>
608 private static void ProcessPrintIM(@NotNull final PrintIMDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount) throws IOException
609 {
610 Boolean resetByteOrder = null;
611
612 if (byteCount == 0)
613 {
614 directory.addError("Empty PrintIM data");
615 return;
616 }
617
618 if (byteCount <= 15)
619 {
620 directory.addError("Bad PrintIM data");
621 return;
622 }
623
624 String header = reader.getString(tagValueOffset, 12, Charsets.UTF_8);
625
626 if (!header.startsWith("PrintIM")) //, StringComparison.Ordinal))
627 {
628 directory.addError("Invalid PrintIM header");
629 return;
630 }
631
632 // check size of PrintIM block
633 int num = reader.getUInt16(tagValueOffset + 14);
634 if (byteCount < 16 + num * 6)
635 {
636 // size is too big, maybe byte ordering is wrong
637 resetByteOrder = reader.isMotorolaByteOrder();
638 reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder());
639 num = reader.getUInt16(tagValueOffset + 14);
640 if (byteCount < 16 + num * 6)
641 {
642 directory.addError("Bad PrintIM size");
643 return;
644 }
645 }
646
647 directory.setObject(PrintIMDirectory.TagPrintImVersion, header.substring(8, 12));
648
649 for (int n = 0; n < num; n++)
650 {
651 int pos = tagValueOffset + 16 + n * 6;
652 int tag = reader.getUInt16(pos);
653 long val = reader.getUInt32(pos + 2);
654
655 directory.setObject(tag, val);
656 }
657
658 if (resetByteOrder != null)
659 reader.setMotorolaByteOrder(resetByteOrder);
660 }
661
662 private static void processKodakMakernote(@NotNull final KodakMakernoteDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader)
663 {
664 // Kodak's makernote is not in IFD format. It has values at fixed offsets.
665 int dataOffset = tagValueOffset + 8;
666 try {
667 directory.setStringValue(KodakMakernoteDirectory.TAG_KODAK_MODEL, reader.getStringValue(dataOffset, 8, Charsets.UTF_8));
668 directory.setInt(KodakMakernoteDirectory.TAG_QUALITY, reader.getUInt8(dataOffset + 9));
669 directory.setInt(KodakMakernoteDirectory.TAG_BURST_MODE, reader.getUInt8(dataOffset + 10));
670 directory.setInt(KodakMakernoteDirectory.TAG_IMAGE_WIDTH, reader.getUInt16(dataOffset + 12));
671 directory.setInt(KodakMakernoteDirectory.TAG_IMAGE_HEIGHT, reader.getUInt16(dataOffset + 14));
672 directory.setInt(KodakMakernoteDirectory.TAG_YEAR_CREATED, reader.getUInt16(dataOffset + 16));
673 directory.setByteArray(KodakMakernoteDirectory.TAG_MONTH_DAY_CREATED, reader.getBytes(dataOffset + 18, 2));
674 directory.setByteArray(KodakMakernoteDirectory.TAG_TIME_CREATED, reader.getBytes(dataOffset + 20, 4));
675 directory.setInt(KodakMakernoteDirectory.TAG_BURST_MODE_2, reader.getUInt16(dataOffset + 24));
676 directory.setInt(KodakMakernoteDirectory.TAG_SHUTTER_MODE, reader.getUInt8(dataOffset + 27));
677 directory.setInt(KodakMakernoteDirectory.TAG_METERING_MODE, reader.getUInt8(dataOffset + 28));
678 directory.setInt(KodakMakernoteDirectory.TAG_SEQUENCE_NUMBER, reader.getUInt8(dataOffset + 29));
679 directory.setInt(KodakMakernoteDirectory.TAG_F_NUMBER, reader.getUInt16(dataOffset + 30));
680 directory.setLong(KodakMakernoteDirectory.TAG_EXPOSURE_TIME, reader.getUInt32(dataOffset + 32));
681 directory.setInt(KodakMakernoteDirectory.TAG_EXPOSURE_COMPENSATION, reader.getInt16(dataOffset + 36));
682 directory.setInt(KodakMakernoteDirectory.TAG_FOCUS_MODE, reader.getUInt8(dataOffset + 56));
683 directory.setInt(KodakMakernoteDirectory.TAG_WHITE_BALANCE, reader.getUInt8(dataOffset + 64));
684 directory.setInt(KodakMakernoteDirectory.TAG_FLASH_MODE, reader.getUInt8(dataOffset + 92));
685 directory.setInt(KodakMakernoteDirectory.TAG_FLASH_FIRED, reader.getUInt8(dataOffset + 93));
686 directory.setInt(KodakMakernoteDirectory.TAG_ISO_SETTING, reader.getUInt16(dataOffset + 94));
687 directory.setInt(KodakMakernoteDirectory.TAG_ISO, reader.getUInt16(dataOffset + 96));
688 directory.setInt(KodakMakernoteDirectory.TAG_TOTAL_ZOOM, reader.getUInt16(dataOffset + 98));
689 directory.setInt(KodakMakernoteDirectory.TAG_DATE_TIME_STAMP, reader.getUInt16(dataOffset + 100));
690 directory.setInt(KodakMakernoteDirectory.TAG_COLOR_MODE, reader.getUInt16(dataOffset + 102));
691 directory.setInt(KodakMakernoteDirectory.TAG_DIGITAL_ZOOM, reader.getUInt16(dataOffset + 104));
692 directory.setInt(KodakMakernoteDirectory.TAG_SHARPNESS, reader.getInt8(dataOffset + 107));
693 } catch (IOException ex) {
694 directory.addError("Error processing Kodak makernote data: " + ex.getMessage());
695 }
696 }
697
698 private static void processReconyxHyperFireMakernote(@NotNull final ReconyxHyperFireMakernoteDirectory directory, final int makernoteOffset, @NotNull final RandomAccessReader reader) throws IOException
699 {
700 directory.setObject(ReconyxHyperFireMakernoteDirectory.TAG_MAKERNOTE_VERSION, reader.getUInt16(makernoteOffset));
701
702 int major = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION);
703 int minor = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 2);
704 int revision = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 4);
705 String buildYear = String.format("%04X", reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 6));
706 String buildDate = String.format("%04X", reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION + 8));
707 String buildYearAndDate = buildYear + buildDate;
708 Integer build;
709 try {
710 build = Integer.parseInt(buildYearAndDate);
711 } catch (NumberFormatException e) {
712 build = null;
713 }
714 if (build != null)
715 {
716 directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION, String.format("%d.%d.%d.%s", major, minor, revision, build));
717 }
718 else
719 {
720 directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION, String.format("%d.%d.%d", major, minor, revision));
721 directory.addError("Error processing Reconyx HyperFire makernote data: build '" + buildYearAndDate + "' is not in the expected format and will be omitted from Firmware Version.");
722 }
723
724 directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_TRIGGER_MODE, String.valueOf((char)reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_TRIGGER_MODE)));
725 directory.setIntArray(ReconyxHyperFireMakernoteDirectory.TAG_SEQUENCE,
726 new int[]
727 {
728 reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SEQUENCE),
729 reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SEQUENCE + 2)
730 });
731
732 int eventNumberHigh = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_EVENT_NUMBER);
733 int eventNumberLow = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_EVENT_NUMBER + 2);
734 directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_EVENT_NUMBER, (eventNumberHigh << 16) + eventNumberLow);
735
736 int seconds = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL);
737 int minutes = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 2);
738 int hour = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 4);
739 int month = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 6);
740 int day = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 8);
741 int year = reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 10);
742
743 if ((seconds >= 0 && seconds < 60) &&
744 (minutes >= 0 && minutes < 60) &&
745 (hour >= 0 && hour < 24) &&
746 (month >= 1 && month < 13) &&
747 (day >= 1 && day < 32) &&
748 (year >= 1 && year <= 9999))
749 {
750 directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL,
751 String.format("%4d:%2d:%2d %2d:%2d:%2d", year, month, day, hour, minutes, seconds));
752 }
753 else
754 {
755 directory.addError("Error processing Reconyx HyperFire makernote data: Date/Time Original " + year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + " is not a valid date/time.");
756 }
757
758 directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_MOON_PHASE, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_MOON_PHASE));
759 directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE_FAHRENHEIT, reader.getInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE_FAHRENHEIT));
760 directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE, reader.getInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_AMBIENT_TEMPERATURE));
761 //directory.setByteArray(ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, reader.getBytes(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, 28));
762 directory.setStringValue(ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, new StringValue(reader.getBytes(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SERIAL_NUMBER, 28), Charsets.UTF_16LE));
763 // two unread bytes: the serial number's terminating null
764
765 directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_CONTRAST, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_CONTRAST));
766 directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_BRIGHTNESS, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_BRIGHTNESS));
767 directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_SHARPNESS, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SHARPNESS));
768 directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_SATURATION, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_SATURATION));
769 directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_INFRARED_ILLUMINATOR, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_INFRARED_ILLUMINATOR));
770 directory.setInt(ReconyxHyperFireMakernoteDirectory.TAG_MOTION_SENSITIVITY, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_MOTION_SENSITIVITY));
771 directory.setDouble(ReconyxHyperFireMakernoteDirectory.TAG_BATTERY_VOLTAGE, reader.getUInt16(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_BATTERY_VOLTAGE) / 1000.0);
772 directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_USER_LABEL, reader.getNullTerminatedString(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_USER_LABEL, 44, Charsets.UTF_8));
773 }
774
775 private static void processReconyxUltraFireMakernote(@NotNull final ReconyxUltraFireMakernoteDirectory directory, final int makernoteOffset, @NotNull final RandomAccessReader reader) throws IOException
776 {
777 directory.setString(ReconyxUltraFireMakernoteDirectory.TAG_LABEL, reader.getString(makernoteOffset, 9, Charsets.UTF_8));
778 /*uint makernoteID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernoteID));
779 directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernoteID, makernoteID);
780 if (makernoteID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_ID)
781 {
782 directory.addError("Error processing Reconyx UltraFire makernote data: unknown Makernote ID 0x" + makernoteID.ToString("x8"));
783 return;
784 }
785 directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernoteSize, ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernoteSize)));
786 uint makernotePublicID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernotePublicID));
787 directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernotePublicID, makernotePublicID);
788 if (makernotePublicID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_PUBLIC_ID)
789 {
790 directory.addError("Error processing Reconyx UltraFire makernote data: unknown Makernote Public ID 0x" + makernotePublicID.ToString("x8"));
791 return;
792 }*/
793 //directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernotePublicSize, ByteConvert.FromBigEndianToNative(reader.GetUInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernotePublicSize)));
794
795 //directory.Set(ReconyxUltraFireMakernoteDirectory.TagCameraVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagCameraVersion, reader));
796 //directory.Set(ReconyxUltraFireMakernoteDirectory.TagUibVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagUibVersion, reader));
797 //directory.Set(ReconyxUltraFireMakernoteDirectory.TagBtlVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagBtlVersion, reader));
798 //directory.Set(ReconyxUltraFireMakernoteDirectory.TagPexVersion, ProcessReconyxUltraFireVersion(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagPexVersion, reader));
799
800 directory.setString(ReconyxUltraFireMakernoteDirectory.TAG_EVENT_TYPE, reader.getString(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_EVENT_TYPE, 1, Charsets.UTF_8));
801 directory.setIntArray(ReconyxUltraFireMakernoteDirectory.TAG_SEQUENCE,
802 new int[]
803 {
804 reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_SEQUENCE),
805 reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_SEQUENCE + 1)
806 });
807 //directory.Set(ReconyxUltraFireMakernoteDirectory.TagEventNumber, ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagEventNumber)));
808
809 byte seconds = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL);
810 byte minutes = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 1);
811 byte hour = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 2);
812 byte day = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 3);
813 byte month = reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL + 4);
814 /*ushort year = ByteConvert.FromBigEndianToNative(reader.GetUInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagDateTimeOriginal + 5));
815 if ((seconds >= 0 && seconds < 60) &&
816 (minutes >= 0 && minutes < 60) &&
817 (hour >= 0 && hour < 24) &&
818 (month >= 1 && month < 13) &&
819 (day >= 1 && day < 32) &&
820 (year >= 1 && year <= 9999))
821 {
822 directory.Set(ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL, new DateTime(year, month, day, hour, minutes, seconds, DateTimeKind.Unspecified));
823 }
824 else
825 {
826 directory.addError("Error processing Reconyx UltraFire makernote data: Date/Time Original " + year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + " is not a valid date/time.");
827 }*/
828 //directory.Set(ReconyxUltraFireMakernoteDirectory.TagDayOfWeek, reader.GetByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagDayOfWeek));
829
830 directory.setInt(ReconyxUltraFireMakernoteDirectory.TAG_MOON_PHASE, reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_MOON_PHASE));
831 //directory.Set(ReconyxUltraFireMakernoteDirectory.TagAmbientTemperatureFahrenheit, ByteConvert.FromBigEndianToNative(reader.GetInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagAmbientTemperatureFahrenheit)));
832 //directory.Set(ReconyxUltraFireMakernoteDirectory.TagAmbientTemperature, ByteConvert.FromBigEndianToNative(reader.GetInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagAmbientTemperature)));
833
834 directory.setInt(ReconyxUltraFireMakernoteDirectory.TAG_FLASH, reader.getByte(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_FLASH));
835 //directory.Set(ReconyxUltraFireMakernoteDirectory.TagBatteryVoltage, ByteConvert.FromBigEndianToNative(reader.GetUInt16(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagBatteryVoltage)) / 1000.0);
836 directory.setStringValue(ReconyxUltraFireMakernoteDirectory.TAG_SERIAL_NUMBER, new StringValue(reader.getBytes(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_SERIAL_NUMBER, 14), Charsets.UTF_8));
837 // unread byte: the serial number's terminating null
838 directory.setString(ReconyxUltraFireMakernoteDirectory.TAG_USER_LABEL, reader.getNullTerminatedString(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TAG_USER_LABEL, 20, Charsets.UTF_8));
839 }
840}
841
Note: See TracBrowser for help on using the repository browser.