Changeset 13061 in josm for trunk/src/com/drew/metadata/exif/ExifTiffHandler.java
- Timestamp:
- 2017-10-30T22:46:09+01:00 (6 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/com/drew/metadata/exif/ExifTiffHandler.java
r10862 r13061 1 1 /* 2 * Copyright 2002-201 6Drew Noakes2 * Copyright 2002-2017 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 21 21 package com.drew.metadata.exif; 22 22 23 import java.io.ByteArrayInputStream; 24 import java.io.IOException; 25 import java.util.Arrays; 26 import java.util.Set; 27 28 import com.drew.imaging.jpeg.JpegMetadataReader; 29 import com.drew.imaging.jpeg.JpegProcessingException; 23 30 import com.drew.imaging.tiff.TiffProcessingException; 24 31 import com.drew.imaging.tiff.TiffReader; 32 import com.drew.lang.Charsets; 25 33 import com.drew.lang.RandomAccessReader; 26 34 import com.drew.lang.SequentialByteArrayReader; … … 29 37 import com.drew.metadata.Directory; 30 38 import com.drew.metadata.Metadata; 39 import com.drew.metadata.StringValue; 31 40 import com.drew.metadata.exif.makernotes.*; 32 41 import com.drew.metadata.iptc.IptcReader; 33 42 import com.drew.metadata.tiff.DirectoryTiffHandler; 34 35 import java.io.IOException;36 import java.util.Set;37 43 38 44 /** … … 46 52 public class ExifTiffHandler extends DirectoryTiffHandler 47 53 { 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 public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDirectory) 55 { 56 super(metadata); 54 57 55 58 if (parentDirectory != null) … … 64 67 final int panasonicRawTiffMarker = 0x0055; // for RW2 files 65 68 66 if (marker != standardTiffMarker && marker != olympusRawTiffMarker && marker != olympusRawTiffMarker2 && marker != panasonicRawTiffMarker) { 67 throw new TiffProcessingException("Unexpected TIFF marker: 0x" + Integer.toHexString(marker)); 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)); 68 81 } 69 82 } … … 76 89 } 77 90 78 if (_currentDirectory instanceof ExifIFD0Directory ) {91 if (_currentDirectory instanceof ExifIFD0Directory || _currentDirectory instanceof PanasonicRawIFD0Directory) { 79 92 if (tagId == ExifIFD0Directory.TAG_EXIF_SUB_IFD_OFFSET) { 80 93 pushDirectory(ExifSubIFDDirectory.class); … … 96 109 97 110 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; 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; 106 138 } 107 139 } … … 113 145 { 114 146 // 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); 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); 117 155 return true; 118 156 } … … 132 170 if (formatCode == 13) 133 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; 134 176 135 177 return null; … … 143 185 final int byteCount) throws IOException 144 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 145 201 // Custom processing for the Makernote tag 146 202 if (tagId == ExifSubIFDDirectory.TAG_MAKERNOTE && _currentDirectory instanceof ExifSubIFDDirectory) { … … 159 215 } 160 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 161 315 return false; 162 316 } 163 317 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()); 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); 178 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; 179 356 } 180 357 } … … 190 367 Directory ifd0Directory = _metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); 191 368 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); 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); 206 381 207 382 boolean byteOrderBefore = reader.isMotorolaByteOrder(); … … 243 418 break; 244 419 default: 245 ifd0Directory.addError("Unsupported Nikon makernote data ignored.");420 _currentDirectory.addError("Unsupported Nikon makernote data ignored."); 246 421 break; 247 422 } … … 254 429 pushDirectory(SonyType1MakernoteDirectory.class); 255 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); 256 437 } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) { 257 438 // force MM for this directory … … 294 475 } else if ("LEICA".equals(firstFiveChars)) { 295 476 reader.setMotorolaByteOrder(false); 296 if ("Leica Camera AG".equals(cameraMake)) { 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)) { 297 494 pushDirectory(LeicaMakernoteDirectory.class); 298 495 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); … … 304 501 return false; 305 502 } 306 } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(makernoteOffset, 12 ))) {503 } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(makernoteOffset, 12, Charsets.UTF_8))) { 307 504 // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD 308 505 // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment … … 349 546 TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset); 350 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); 351 567 } else { 352 568 // The makernote is not comprehended by this library. … … 359 575 } 360 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 361 662 private static void processKodakMakernote(@NotNull final KodakMakernoteDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader) 362 663 { … … 364 665 int dataOffset = tagValueOffset + 8; 365 666 try { 366 directory.setString (KodakMakernoteDirectory.TAG_KODAK_MODEL, reader.getString(dataOffset,8));667 directory.setStringValue(KodakMakernoteDirectory.TAG_KODAK_MODEL, reader.getStringValue(dataOffset, 8, Charsets.UTF_8)); 367 668 directory.setInt(KodakMakernoteDirectory.TAG_QUALITY, reader.getUInt8(dataOffset + 9)); 368 669 directory.setInt(KodakMakernoteDirectory.TAG_BURST_MODE, reader.getUInt8(dataOffset + 10)); … … 394 695 } 395 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 } 396 840 } 397 841
Note:
See TracChangeset
for help on using the changeset viewer.