Changeset 8132 in josm for trunk/src/com/drew/metadata/exif/ExifReader.java
- Timestamp:
- 2015-03-10T01:17:39+01:00 (11 years ago)
- File:
-
- 1 edited
-
trunk/src/com/drew/metadata/exif/ExifReader.java (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/com/drew/metadata/exif/ExifReader.java
r6209 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http://drewnoakes.com/code/exif/ 19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; 22 22 23 import java.util.HashSet; 24 import java.util.Set; 23 import com.drew.imaging.jpeg.JpegSegmentMetadataReader; 24 import com.drew.imaging.jpeg.JpegSegmentType; 25 import com.drew.imaging.tiff.TiffProcessingException; 26 import com.drew.imaging.tiff.TiffReader; 27 import com.drew.lang.ByteArrayReader; 28 import com.drew.lang.annotations.NotNull; 29 import com.drew.metadata.Metadata; 25 30 26 import com.drew.lang.BufferBoundsException; 27 import com.drew.lang.BufferReader; 28 import com.drew.lang.Rational; 29 import com.drew.lang.annotations.NotNull; 30 import com.drew.metadata.Directory; 31 import com.drew.metadata.Metadata; 32 import com.drew.metadata.MetadataReader; 31 import java.io.IOException; 32 import java.util.Arrays; 33 33 34 34 /** 35 35 * Decodes Exif binary data, populating a {@link Metadata} object with tag values in {@link ExifSubIFDDirectory}, 36 * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera makernote directories. 36 * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera 37 * makernote directories. 37 38 * 38 * @author Drew Noakes http://drewnoakes.com 39 * @author Drew Noakes https://drewnoakes.com 39 40 */ 40 public class ExifReader implements MetadataReader 41 public class ExifReader implements JpegSegmentMetadataReader 41 42 { 42 // TODO extract a reusable TiffReader from this class with hooks for special tag handling and subdir following 43 44 /** The number of bytes used per format descriptor. */ 43 /** 44 * The offset at which the TIFF data actually starts. This may be necessary when, for example, processing 45 * JPEG Exif data from APP0 which has a 6-byte preamble before starting the TIFF data. 46 */ 47 private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0"; 48 49 private boolean _storeThumbnailBytes = true; 50 51 public boolean isStoreThumbnailBytes() 52 { 53 return _storeThumbnailBytes; 54 } 55 56 public void setStoreThumbnailBytes(boolean storeThumbnailBytes) 57 { 58 _storeThumbnailBytes = storeThumbnailBytes; 59 } 60 45 61 @NotNull 46 private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 }; 62 public Iterable<JpegSegmentType> getSegmentTypes() 63 { 64 return Arrays.asList(JpegSegmentType.APP1); 65 } 47 66 48 /** The number of formats known. */ 49 private static final int MAX_FORMAT_CODE = 12; 67 public boolean canProcess(@NotNull final byte[] segmentBytes, @NotNull final JpegSegmentType segmentType) 68 { 69 return segmentBytes.length >= JPEG_EXIF_SEGMENT_PREAMBLE.length() && new String(segmentBytes, 0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equalsIgnoreCase(JPEG_EXIF_SEGMENT_PREAMBLE); 70 } 50 71 51 // Format types 52 // TODO use an enum for these? 53 /** An 8-bit unsigned integer. */ 54 private static final int FMT_BYTE = 1; 55 /** A fixed-length character string. */ 56 private static final int FMT_STRING = 2; 57 /** An unsigned 16-bit integer. */ 58 private static final int FMT_USHORT = 3; 59 /** An unsigned 32-bit integer. */ 60 private static final int FMT_ULONG = 4; 61 private static final int FMT_URATIONAL = 5; 62 /** An 8-bit signed integer. */ 63 private static final int FMT_SBYTE = 6; 64 private static final int FMT_UNDEFINED = 7; 65 /** A signed 16-bit integer. */ 66 private static final int FMT_SSHORT = 8; 67 /** A signed 32-bit integer. */ 68 private static final int FMT_SLONG = 9; 69 private static final int FMT_SRATIONAL = 10; 70 /** A 32-bit floating point number. */ 71 private static final int FMT_SINGLE = 11; 72 /** A 64-bit floating point number. */ 73 private static final int FMT_DOUBLE = 12; 72 public void extract(@NotNull final byte[] segmentBytes, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) 73 { 74 if (segmentBytes == null) 75 throw new NullPointerException("segmentBytes cannot be null"); 76 if (metadata == null) 77 throw new NullPointerException("metadata cannot be null"); 78 if (segmentType == null) 79 throw new NullPointerException("segmentType cannot be null"); 74 80 75 /** This tag is a pointer to the Exif SubIFD. */ 76 public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769; 77 /** This tag is a pointer to the Exif Interop IFD. */ 78 public static final int TAG_INTEROP_OFFSET = 0xA005; 79 /** This tag is a pointer to the Exif GPS IFD. */ 80 public static final int TAG_GPS_INFO_OFFSET = 0x8825; 81 /** This tag is a pointer to the Exif Makernote IFD. */ 82 public static final int TAG_MAKER_NOTE_OFFSET = 0x927C; 81 try { 82 ByteArrayReader reader = new ByteArrayReader(segmentBytes); 83 83 84 public static final int TIFF_HEADER_START_OFFSET = 6; 85 86 /** 87 * Performs the Exif data extraction, adding found values to the specified 88 * instance of <code>Metadata</code>. 89 * 90 * @param reader The buffer reader from which Exif data should be read. 91 * @param metadata The Metadata object into which extracted values should be merged. 92 */ 93 public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata) 94 { 95 final ExifSubIFDDirectory directory = metadata.getOrCreateDirectory(ExifSubIFDDirectory.class); 96 97 // check for the header length 98 if (reader.getLength() <= 14) { 99 directory.addError("Exif data segment must contain at least 14 bytes"); 100 return; 101 } 102 103 // check for the header preamble 104 try { 105 if (!reader.getString(0, 6).equals("Exif\0\0")) { 106 directory.addError("Exif data segment doesn't begin with 'Exif'"); 84 // 85 // Check for the header preamble 86 // 87 try { 88 if (!reader.getString(0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equals(JPEG_EXIF_SEGMENT_PREAMBLE)) { 89 // TODO what do to with this error state? 90 System.err.println("Invalid JPEG Exif segment preamble"); 91 return; 92 } 93 } catch (IOException e) { 94 // TODO what do to with this error state? 95 e.printStackTrace(System.err); 107 96 return; 108 97 } 109 98 110 extractIFD(metadata, metadata.getOrCreateDirectory(ExifIFD0Directory.class), TIFF_HEADER_START_OFFSET, reader); 111 } catch (BufferBoundsException e) { 112 directory.addError("Exif data segment ended prematurely"); 99 // 100 // Read the TIFF-formatted Exif data 101 // 102 new TiffReader().processTiff( 103 reader, 104 new ExifTiffHandler(metadata, _storeThumbnailBytes), 105 JPEG_EXIF_SEGMENT_PREAMBLE.length() 106 ); 107 108 } catch (TiffProcessingException e) { 109 // TODO what do to with this error state? 110 e.printStackTrace(System.err); 111 } catch (IOException e) { 112 // TODO what do to with this error state? 113 e.printStackTrace(System.err); 113 114 } 114 115 } 115 116 /**117 * Performs the Exif data extraction on a TIFF/RAW, adding found values to the specified118 * instance of <code>Metadata</code>.119 *120 * @param reader The BufferReader from which TIFF data should be read.121 * @param metadata The Metadata object into which extracted values should be merged.122 */123 public void extractTiff(@NotNull BufferReader reader, @NotNull Metadata metadata)124 {125 final ExifIFD0Directory directory = metadata.getOrCreateDirectory(ExifIFD0Directory.class);126 127 try {128 extractIFD(metadata, directory, 0, reader);129 } catch (BufferBoundsException e) {130 directory.addError("Exif data segment ended prematurely");131 }132 }133 134 private void extractIFD(@NotNull Metadata metadata, @NotNull final ExifIFD0Directory directory, int tiffHeaderOffset, @NotNull BufferReader reader) throws BufferBoundsException135 {136 // this should be either "MM" or "II"137 String byteOrderIdentifier = reader.getString(tiffHeaderOffset, 2);138 139 if ("MM".equals(byteOrderIdentifier)) {140 reader.setMotorolaByteOrder(true);141 } else if ("II".equals(byteOrderIdentifier)) {142 reader.setMotorolaByteOrder(false);143 } else {144 directory.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);145 return;146 }147 148 // Check the next two values for correctness.149 final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset);150 151 final int standardTiffMarker = 0x002A;152 final int olympusRawTiffMarker = 0x4F52; // for ORF files153 final int panasonicRawTiffMarker = 0x0055; // for RW2 files154 155 if (tiffMarker != standardTiffMarker && tiffMarker != olympusRawTiffMarker && tiffMarker != panasonicRawTiffMarker) {156 directory.addError("Unexpected TIFF marker after byte order identifier: 0x" + Integer.toHexString(tiffMarker));157 return;158 }159 160 int firstDirectoryOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;161 162 // David Ekholm sent a digital camera image that has this problem163 if (firstDirectoryOffset >= reader.getLength() - 1) {164 directory.addError("First exif directory offset is beyond end of Exif data segment");165 // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case166 firstDirectoryOffset = 14;167 }168 169 Set<Integer> processedDirectoryOffsets = new HashSet<Integer>();170 171 processDirectory(directory, processedDirectoryOffsets, firstDirectoryOffset, tiffHeaderOffset, metadata, reader);172 173 // after the extraction process, if we have the correct tags, we may be able to store thumbnail information174 ExifThumbnailDirectory thumbnailDirectory = metadata.getDirectory(ExifThumbnailDirectory.class);175 if (thumbnailDirectory!=null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION)) {176 Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);177 Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);178 if (offset != null && length != null) {179 try {180 byte[] thumbnailData = reader.getBytes(tiffHeaderOffset + offset, length);181 thumbnailDirectory.setThumbnailData(thumbnailData);182 } catch (BufferBoundsException ex) {183 directory.addError("Invalid thumbnail data specification: " + ex.getMessage());184 }185 }186 }187 }188 189 /**190 * Process one of the nested Tiff IFD directories.191 * <p/>192 * Header193 * 2 bytes: number of tags194 * <p/>195 * Then for each tag196 * 2 bytes: tag type197 * 2 bytes: format code198 * 4 bytes: component count199 */200 private void processDirectory(@NotNull Directory directory, @NotNull Set<Integer> processedDirectoryOffsets, int dirStartOffset, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull final BufferReader reader) throws BufferBoundsException201 {202 // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist203 if (processedDirectoryOffsets.contains(Integer.valueOf(dirStartOffset)))204 return;205 206 // remember that we've visited this directory so that we don't visit it again later207 processedDirectoryOffsets.add(dirStartOffset);208 209 if (dirStartOffset >= reader.getLength() || dirStartOffset < 0) {210 directory.addError("Ignored directory marked to start outside data segment");211 return;212 }213 214 // First two bytes in the IFD are the number of tags in this directory215 int dirTagCount = reader.getUInt16(dirStartOffset);216 217 int dirLength = (2 + (12 * dirTagCount) + 4);218 if (dirLength + dirStartOffset > reader.getLength()) {219 directory.addError("Illegally sized directory");220 return;221 }222 223 // Handle each tag in this directory224 for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) {225 final int tagOffset = calculateTagOffset(dirStartOffset, tagNumber);226 227 // 2 bytes for the tag type228 final int tagType = reader.getUInt16(tagOffset);229 230 // 2 bytes for the format code231 final int formatCode = reader.getUInt16(tagOffset + 2);232 if (formatCode < 1 || formatCode > MAX_FORMAT_CODE) {233 // This error suggests that we are processing at an incorrect index and will generate234 // rubbish until we go out of bounds (which may be a while). Exit now.235 directory.addError("Invalid TIFF tag format code: " + formatCode);236 continue; // JOSM patch to fix #9030237 }238 239 // 4 bytes dictate the number of components in this tag's data240 final int componentCount = reader.getInt32(tagOffset + 4);241 if (componentCount < 0) {242 directory.addError("Negative TIFF tag component count");243 continue;244 }245 // each component may have more than one byte... calculate the total number of bytes246 final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode];247 final int tagValueOffset;248 if (byteCount > 4) {249 // If it's bigger than 4 bytes, the dir entry contains an offset.250 // dirEntryOffset must be passed, as some makernote implementations (e.g. FujiFilm) incorrectly use an251 // offset relative to the start of the makernote itself, not the TIFF segment.252 final int offsetVal = reader.getInt32(tagOffset + 8);253 if (offsetVal + byteCount > reader.getLength()) {254 // Bogus pointer offset and / or byteCount value255 directory.addError("Illegal TIFF tag pointer offset");256 continue;257 }258 tagValueOffset = tiffHeaderOffset + offsetVal;259 } else {260 // 4 bytes or less and value is in the dir entry itself261 tagValueOffset = tagOffset + 8;262 }263 264 if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) {265 directory.addError("Illegal TIFF tag pointer offset");266 continue;267 }268 269 // Check that this tag isn't going to allocate outside the bounds of the data array.270 // This addresses an uncommon OutOfMemoryError.271 if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) {272 directory.addError("Illegal number of bytes: " + byteCount);273 continue;274 }275 276 switch (tagType) {277 case TAG_EXIF_SUB_IFD_OFFSET: {278 final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);279 processDirectory(metadata.getOrCreateDirectory(ExifSubIFDDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);280 continue;281 }282 case TAG_INTEROP_OFFSET: {283 final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);284 processDirectory(metadata.getOrCreateDirectory(ExifInteropDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);285 continue;286 }287 case TAG_GPS_INFO_OFFSET: {288 final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);289 processDirectory(metadata.getOrCreateDirectory(GpsDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);290 continue;291 }292 case TAG_MAKER_NOTE_OFFSET: {293 processMakerNote(tagValueOffset, processedDirectoryOffsets, tiffHeaderOffset, metadata, reader);294 continue;295 }296 default: {297 processTag(directory, tagType, tagValueOffset, componentCount, formatCode, reader);298 break;299 }300 }301 }302 303 // at the end of each IFD is an optional link to the next IFD304 final int finalTagOffset = calculateTagOffset(dirStartOffset, dirTagCount);305 int nextDirectoryOffset = reader.getInt32(finalTagOffset);306 if (nextDirectoryOffset != 0) {307 nextDirectoryOffset += tiffHeaderOffset;308 if (nextDirectoryOffset >= reader.getLength()) {309 // Last 4 bytes of IFD reference another IFD with an address that is out of bounds310 // Note this could have been caused by jhead 1.3 cropping too much311 return;312 } else if (nextDirectoryOffset < dirStartOffset) {313 // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory314 return;315 }316 // TODO in Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case317 final ExifThumbnailDirectory nextDirectory = metadata.getOrCreateDirectory(ExifThumbnailDirectory.class);318 processDirectory(nextDirectory, processedDirectoryOffsets, nextDirectoryOffset, tiffHeaderOffset, metadata, reader);319 }320 }321 322 private void processMakerNote(int subdirOffset, @NotNull Set<Integer> processedDirectoryOffsets, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull BufferReader reader) throws BufferBoundsException323 {324 // Determine the camera model and makernote format325 Directory ifd0Directory = metadata.getDirectory(ExifIFD0Directory.class);326 327 if (ifd0Directory==null)328 return;329 330 String cameraModel = ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);331 332 //final String firstTwoChars = reader.getString(subdirOffset, 2);333 final String firstThreeChars = reader.getString(subdirOffset, 3);334 final String firstFourChars = reader.getString(subdirOffset, 4);335 final String firstFiveChars = reader.getString(subdirOffset, 5);336 final String firstSixChars = reader.getString(subdirOffset, 6);337 final String firstSevenChars = reader.getString(subdirOffset, 7);338 final String firstEightChars = reader.getString(subdirOffset, 8);339 final String firstTwelveChars = reader.getString(subdirOffset, 12);340 341 if ("OLYMP".equals(firstFiveChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {342 // Olympus Makernote343 // Epson and Agfa use Olympus maker note standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/344 processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);345 } else if (cameraModel != null && cameraModel.trim().toUpperCase().startsWith("NIKON")) {346 if ("Nikon".equals(firstFiveChars)) {347 /* There are two scenarios here:348 * Type 1: **349 * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon...........350 * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................351 * Type 3: **352 * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*...353 * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200354 */355 switch (reader.getUInt8(subdirOffset + 6)) {356 case 1:357 processDirectory(metadata.getOrCreateDirectory(NikonType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);358 break;359 case 2:360 processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 18, subdirOffset + 10, metadata, reader);361 break;362 default:363 ifd0Directory.addError("Unsupported Nikon makernote data ignored.");364 break;365 }366 } else {367 // The IFD begins with the first MakerNote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models.368 processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);369 }370 } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) {371 processDirectory(metadata.getOrCreateDirectory(SonyType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);372 } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) {373 processDirectory(metadata.getOrCreateDirectory(SigmaMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 10, tiffHeaderOffset, metadata, reader);374 } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) {375 // force MM for this directory376 boolean isMotorola = reader.isMotorolaByteOrder();377 reader.setMotorolaByteOrder(true);378 // skip 12 byte header + 2 for "MM" + 6379 processDirectory(metadata.getOrCreateDirectory(SonyType6MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);380 reader.setMotorolaByteOrder(isMotorola);381 } else if ("KDK".equals(firstThreeChars)) {382 processDirectory(metadata.getOrCreateDirectory(KodakMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);383 } else if ("Canon".equalsIgnoreCase(cameraModel)) {384 processDirectory(metadata.getOrCreateDirectory(CanonMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);385 } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("CASIO")) {386 if ("QVC\u0000\u0000\u0000".equals(firstSixChars))387 processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, tiffHeaderOffset, metadata, reader);388 else389 processDirectory(metadata.getOrCreateDirectory(CasioType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);390 } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraModel)) {391 boolean byteOrderBefore = reader.isMotorolaByteOrder();392 // bug in fujifilm makernote ifd means we temporarily use Intel byte ordering393 reader.setMotorolaByteOrder(false);394 // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote395 // IFD, though the offset is relative to the start of the makernote, not the TIFF396 // header (like everywhere else)397 int ifdStart = subdirOffset + reader.getInt32(subdirOffset + 8);398 processDirectory(metadata.getOrCreateDirectory(FujifilmMakernoteDirectory.class), processedDirectoryOffsets, ifdStart, tiffHeaderOffset, metadata, reader);399 reader.setMotorolaByteOrder(byteOrderBefore);400 } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("MINOLTA")) {401 // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote402 // area that commences immediately.403 processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);404 } else if ("KYOCERA".equals(firstSevenChars)) {405 // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html406 processDirectory(metadata.getOrCreateDirectory(KyoceraMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 22, tiffHeaderOffset, metadata, reader);407 } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(subdirOffset, 12))) {408 // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD409 // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment410 // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html411 processDirectory(metadata.getOrCreateDirectory(PanasonicMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);412 } else if ("AOC\u0000".equals(firstFourChars)) {413 // NON-Standard TIFF IFD Data using Casio Type 2 Tags414 // IFD has no Next-IFD pointer at end of IFD, and415 // Offsets are relative to the start of the current IFD tag, not the TIFF header416 // Observed for:417 // - Pentax ist D418 processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, subdirOffset, metadata, reader);419 } else if (cameraModel != null && (cameraModel.toUpperCase().startsWith("PENTAX") || cameraModel.toUpperCase().startsWith("ASAHI"))) {420 // NON-Standard TIFF IFD Data using Pentax Tags421 // IFD has no Next-IFD pointer at end of IFD, and422 // Offsets are relative to the start of the current IFD tag, not the TIFF header423 // Observed for:424 // - PENTAX Optio 330425 // - PENTAX Optio 430426 processDirectory(metadata.getOrCreateDirectory(PentaxMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, subdirOffset, metadata, reader);427 // } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) {428 // // This Konica data is not understood. Header identified in accordance with information at this site:429 // // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html430 // // TODO add support for minolta/konica cameras431 // exifDirectory.addError("Unsupported Konica/Minolta data ignored.");432 } else {433 // TODO how to store makernote data when it's not from a supported camera model?434 // this is difficult as the starting offset is not known. we could look for it...435 }436 }437 438 private void processTag(@NotNull Directory directory, int tagType, int tagValueOffset, int componentCount, int formatCode, @NotNull final BufferReader reader) throws BufferBoundsException439 {440 // Directory simply stores raw values441 // The display side uses a Descriptor class per directory to turn the raw values into 'pretty' descriptions442 switch (formatCode) {443 case FMT_UNDEFINED:444 // this includes exif user comments445 directory.setByteArray(tagType, reader.getBytes(tagValueOffset, componentCount));446 break;447 case FMT_STRING:448 String string = reader.getNullTerminatedString(tagValueOffset, componentCount);449 directory.setString(tagType, string);450 /*451 // special handling for certain known tags, proposed by Yuri Binev but left out for now,452 // as it gives the false impression that the image was captured in the same timezone453 // in which the string is parsed454 if (tagType==ExifSubIFDDirectory.TAG_DATETIME ||455 tagType==ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL ||456 tagType==ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED) {457 String[] datePatterns = {458 "yyyy:MM:dd HH:mm:ss",459 "yyyy:MM:dd HH:mm",460 "yyyy-MM-dd HH:mm:ss",461 "yyyy-MM-dd HH:mm"};462 for (String datePattern : datePatterns) {463 try {464 DateFormat parser = new SimpleDateFormat(datePattern);465 Date date = parser.parse(string);466 directory.setDate(tagType, date);467 break;468 } catch (ParseException ex) {469 // simply try the next pattern470 }471 }472 }473 */474 break;475 case FMT_SRATIONAL:476 if (componentCount == 1) {477 directory.setRational(tagType, new Rational(reader.getInt32(tagValueOffset), reader.getInt32(tagValueOffset + 4)));478 } else if (componentCount > 1) {479 Rational[] rationals = new Rational[componentCount];480 for (int i = 0; i < componentCount; i++)481 rationals[i] = new Rational(reader.getInt32(tagValueOffset + (8 * i)), reader.getInt32(tagValueOffset + 4 + (8 * i)));482 directory.setRationalArray(tagType, rationals);483 }484 break;485 case FMT_URATIONAL:486 if (componentCount == 1) {487 directory.setRational(tagType, new Rational(reader.getUInt32(tagValueOffset), reader.getUInt32(tagValueOffset + 4)));488 } else if (componentCount > 1) {489 Rational[] rationals = new Rational[componentCount];490 for (int i = 0; i < componentCount; i++)491 rationals[i] = new Rational(reader.getUInt32(tagValueOffset + (8 * i)), reader.getUInt32(tagValueOffset + 4 + (8 * i)));492 directory.setRationalArray(tagType, rationals);493 }494 break;495 case FMT_SINGLE:496 if (componentCount == 1) {497 directory.setFloat(tagType, reader.getFloat32(tagValueOffset));498 } else {499 float[] floats = new float[componentCount];500 for (int i = 0; i < componentCount; i++)501 floats[i] = reader.getFloat32(tagValueOffset + (i * 4));502 directory.setFloatArray(tagType, floats);503 }504 break;505 case FMT_DOUBLE:506 if (componentCount == 1) {507 directory.setDouble(tagType, reader.getDouble64(tagValueOffset));508 } else {509 double[] doubles = new double[componentCount];510 for (int i = 0; i < componentCount; i++)511 doubles[i] = reader.getDouble64(tagValueOffset + (i * 4));512 directory.setDoubleArray(tagType, doubles);513 }514 break;515 516 //517 // Note that all integral types are stored as int32 internally (the largest supported by TIFF)518 //519 520 case FMT_SBYTE:521 if (componentCount == 1) {522 directory.setInt(tagType, reader.getInt8(tagValueOffset));523 } else {524 int[] bytes = new int[componentCount];525 for (int i = 0; i < componentCount; i++)526 bytes[i] = reader.getInt8(tagValueOffset + i);527 directory.setIntArray(tagType, bytes);528 }529 break;530 case FMT_BYTE:531 if (componentCount == 1) {532 directory.setInt(tagType, reader.getUInt8(tagValueOffset));533 } else {534 int[] bytes = new int[componentCount];535 for (int i = 0; i < componentCount; i++)536 bytes[i] = reader.getUInt8(tagValueOffset + i);537 directory.setIntArray(tagType, bytes);538 }539 break;540 case FMT_USHORT:541 if (componentCount == 1) {542 int i = reader.getUInt16(tagValueOffset);543 directory.setInt(tagType, i);544 } else {545 int[] ints = new int[componentCount];546 for (int i = 0; i < componentCount; i++)547 ints[i] = reader.getUInt16(tagValueOffset + (i * 2));548 directory.setIntArray(tagType, ints);549 }550 break;551 case FMT_SSHORT:552 if (componentCount == 1) {553 int i = reader.getInt16(tagValueOffset);554 directory.setInt(tagType, i);555 } else {556 int[] ints = new int[componentCount];557 for (int i = 0; i < componentCount; i++)558 ints[i] = reader.getInt16(tagValueOffset + (i * 2));559 directory.setIntArray(tagType, ints);560 }561 break;562 case FMT_SLONG:563 case FMT_ULONG:564 // NOTE 'long' in this case means 32 bit, not 64565 if (componentCount == 1) {566 int i = reader.getInt32(tagValueOffset);567 directory.setInt(tagType, i);568 } else {569 int[] ints = new int[componentCount];570 for (int i = 0; i < componentCount; i++)571 ints[i] = reader.getInt32(tagValueOffset + (i * 4));572 directory.setIntArray(tagType, ints);573 }574 break;575 default:576 directory.addError("Unknown format code " + formatCode + " for tag " + tagType);577 }578 }579 580 /**581 * Determine the offset at which a given InteropArray entry begins within the specified IFD.582 *583 * @param dirStartOffset the offset at which the IFD starts584 * @param entryNumber the zero-based entry number585 */586 private int calculateTagOffset(int dirStartOffset, int entryNumber)587 {588 // add 2 bytes for the tag count589 // each entry is 12 bytes, so we skip 12 * the number seen so far590 return dirStartOffset + 2 + (12 * entryNumber);591 }592 116 }
Note:
See TracChangeset
for help on using the changeset viewer.
