diff --git a/Source/com/drew/imaging/FileTypeDetector.java b/Source/com/drew/imaging/FileTypeDetector.java index fda22575e..e41d79a3b 100644 --- a/Source/com/drew/imaging/FileTypeDetector.java +++ b/Source/com/drew/imaging/FileTypeDetector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,6 +54,8 @@ public class FileTypeDetector _root.addPath(FileType.Jpeg, new byte[]{(byte)0xff, (byte)0xd8}); _root.addPath(FileType.Tiff, "II".getBytes(), new byte[]{0x2a, 0x00}); _root.addPath(FileType.Tiff, "MM".getBytes(), new byte[]{0x00, 0x2a}); + _root.addPath(FileType.Tiff, "II".getBytes(), new byte[]{0x2b, 0x00}); // BigTIFF + _root.addPath(FileType.Tiff, "MM".getBytes(), new byte[]{0x00, 0x2b}); // BigTIFF _root.addPath(FileType.Psd, "8BPS".getBytes()); _root.addPath(FileType.Png, new byte[]{(byte)0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52}); _root.addPath(FileType.Bmp, "BM".getBytes()); // Standard Bitmap Windows and OS/2 diff --git a/Source/com/drew/imaging/jpeg/JpegMetadataReader.java b/Source/com/drew/imaging/jpeg/JpegMetadataReader.java index 4d92f09a7..0d0df967d 100644 --- a/Source/com/drew/imaging/jpeg/JpegMetadataReader.java +++ b/Source/com/drew/imaging/jpeg/JpegMetadataReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -126,7 +126,7 @@ public static void process(@NotNull Metadata metadata, @NotNull InputStream inpu processJpegSegmentData(metadata, readers, segmentData); } - public static void processJpegSegmentData(Metadata metadata, Iterable readers, JpegSegmentData segmentData) + public static void processJpegSegmentData(Metadata metadata, Iterable readers, JpegSegmentData segmentData) throws IOException { // Pass the appropriate byte arrays to each reader. for (JpegSegmentMetadataReader reader : readers) { diff --git a/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java b/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java index e1d2aef34..e6b0679d0 100644 --- a/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java +++ b/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java @@ -1,5 +1,27 @@ +/* + * Copyright 2002-2022 Drew Noakes and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More information about this project is available at: + * + * https://drewnoakes.com/code/exif/ + * https://github.com/drewnoakes/metadata-extractor + */ package com.drew.imaging.jpeg; +import java.io.IOException; + import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; @@ -21,6 +43,7 @@ public interface JpegSegmentMetadataReader * encountered in the original file. * @param metadata The {@link Metadata} object into which extracted values should be merged. * @param segmentType The {@link JpegSegmentType} being read. + * @throws IOException an error occurred while accessing the required data */ - void readJpegSegments(@NotNull final Iterable segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType); + void readJpegSegments(@NotNull final Iterable segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) throws IOException; } diff --git a/Source/com/drew/imaging/png/PngMetadataReader.java b/Source/com/drew/imaging/png/PngMetadataReader.java index c2dd8b9e9..68c206391 100644 --- a/Source/com/drew/imaging/png/PngMetadataReader.java +++ b/Source/com/drew/imaging/png/PngMetadataReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -331,7 +331,7 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c } else if (chunkType.equals(PngChunkType.eXIf)) { try { ExifTiffHandler handler = new ExifTiffHandler(metadata, null, 0); - new TiffReader().processTiff(new ByteArrayReader(bytes), handler, 0); + new TiffReader().processTiff(new ByteArrayReader(bytes), handler); } catch (TiffProcessingException ex) { PngDirectory directory = new PngDirectory(PngChunkType.eXIf); directory.addError(ex.getMessage()); diff --git a/Source/com/drew/imaging/tiff/TiffDataFormat.java b/Source/com/drew/imaging/tiff/TiffDataFormat.java index 83176b7fc..30c15985b 100644 --- a/Source/com/drew/imaging/tiff/TiffDataFormat.java +++ b/Source/com/drew/imaging/tiff/TiffDataFormat.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,10 @@ public class TiffDataFormat public static final int CODE_RATIONAL_S = 10; public static final int CODE_SINGLE = 11; public static final int CODE_DOUBLE = 12; + // From BigTIFF + public static final int CODE_INT64_U = 16; + public static final int CODE_INT64_S = 17; + public static final int CODE_IFD8 = 18; @NotNull public static final TiffDataFormat INT8_U = new TiffDataFormat("BYTE", CODE_INT8_U, 1); @NotNull public static final TiffDataFormat STRING = new TiffDataFormat("STRING", CODE_STRING, 1); @@ -55,6 +59,10 @@ public class TiffDataFormat @NotNull public static final TiffDataFormat RATIONAL_S = new TiffDataFormat("SRATIONAL", CODE_RATIONAL_S, 8); @NotNull public static final TiffDataFormat SINGLE = new TiffDataFormat("SINGLE", CODE_SINGLE, 4); @NotNull public static final TiffDataFormat DOUBLE = new TiffDataFormat("DOUBLE", CODE_DOUBLE, 8); + // From BigTIFF + @NotNull public static final TiffDataFormat INT64_U = new TiffDataFormat("ULONG8", CODE_INT64_U, 8); + @NotNull public static final TiffDataFormat INT64_S = new TiffDataFormat("SLONG8", CODE_INT64_S, 8); + @NotNull public static final TiffDataFormat IFD8 = new TiffDataFormat("IFD8", CODE_IFD8, 8); @NotNull private final String _name; @@ -62,7 +70,7 @@ public class TiffDataFormat private final int _componentSizeBytes; @Nullable - public static TiffDataFormat fromTiffFormatCode(int tiffFormatCode) + public static TiffDataFormat fromTiffFormatCode(int tiffFormatCode, boolean isBigTiff) { switch (tiffFormatCode) { case 1: return INT8_U; @@ -77,6 +85,10 @@ public static TiffDataFormat fromTiffFormatCode(int tiffFormatCode) case 10: return RATIONAL_S; case 11: return SINGLE; case 12: return DOUBLE; + // From BigTIFF + case 16: return isBigTiff ? INT64_U : null; + case 17: return isBigTiff ? INT64_S : null; + case 18: return isBigTiff ? IFD8 : null; } return null; } diff --git a/Source/com/drew/imaging/tiff/TiffHandler.java b/Source/com/drew/imaging/tiff/TiffHandler.java index bada4224a..6f99ae922 100644 --- a/Source/com/drew/imaging/tiff/TiffHandler.java +++ b/Source/com/drew/imaging/tiff/TiffHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,8 +44,9 @@ public interface TiffHandler * validation or perhaps differentiating the type of mapping to use for observed tags and IFDs. * * @param marker the 2-byte value found at position 2 of the TIFF header + * @return The TIFF standard via which to interpret the data stream. */ - void setTiffMarker(int marker) throws TiffProcessingException; + TiffStandard processTiffMarker(short marker) throws TiffProcessingException; boolean tryEnterSubIfd(int tagId); boolean hasFollowerIfd(); @@ -57,10 +58,10 @@ public interface TiffHandler boolean customProcessTag(int tagOffset, @NotNull Set processedIfdOffsets, - int tiffHeaderOffset, @NotNull RandomAccessReader reader, int tagId, - int byteCount) throws IOException; + int byteCount, + boolean isBigTiff) throws IOException; void warn(@NotNull String message); void error(@NotNull String message); @@ -85,4 +86,8 @@ boolean customProcessTag(int tagOffset, void setInt32sArray(int tagId, @NotNull int[] array); void setInt32u(int tagId, long int32u); void setInt32uArray(int tagId, @NotNull long[] array); + void setInt64S(int tagId, long int64S); + void setInt64SArray(int tagId, @NotNull long[] array); + void setInt64U(int tagId, long int64U); + void setInt64UArray(int tagId, @NotNull long[] array); } diff --git a/Source/com/drew/imaging/tiff/TiffMetadataReader.java b/Source/com/drew/imaging/tiff/TiffMetadataReader.java index 5db7883d4..c67be81d5 100644 --- a/Source/com/drew/imaging/tiff/TiffMetadataReader.java +++ b/Source/com/drew/imaging/tiff/TiffMetadataReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,7 +68,7 @@ public static Metadata readMetadata(@NotNull RandomAccessReader reader) throws I { Metadata metadata = new Metadata(); ExifTiffHandler handler = new ExifTiffHandler(metadata, null, 0); - new TiffReader().processTiff(reader, handler, 0); + new TiffReader().processTiff(reader, handler); return metadata; } } diff --git a/Source/com/drew/imaging/tiff/TiffReader.java b/Source/com/drew/imaging/tiff/TiffReader.java index 1215d9287..00ec1d1d3 100644 --- a/Source/com/drew/imaging/tiff/TiffReader.java +++ b/Source/com/drew/imaging/tiff/TiffReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,17 +40,31 @@ public class TiffReader * * @param reader the {@link RandomAccessReader} from which the data should be read * @param handler the {@link TiffHandler} that will coordinate processing and accept read values - * @param tiffHeaderOffset the offset within reader at which the TIFF header starts * @throws TiffProcessingException if an error occurred during the processing of TIFF data that could not be * ignored or recovered from * @throws IOException an error occurred while accessing the required data */ public void processTiff(@NotNull final RandomAccessReader reader, - @NotNull final TiffHandler handler, - final int tiffHeaderOffset) throws TiffProcessingException, IOException + @NotNull final TiffHandler handler) throws TiffProcessingException, IOException { + // Standard TIFF + // + // TIFF Header: + // - 2 bytes: byte order (MM or II) + // - 2 bytes: version (always 42) + // - 4 bytes: offset to first IFD + + // Big TIFF + // + // TIFF Header: + // - 2 bytes: byte order (MM or II) + // - 2 bytes: version (always 43) + // - 2 bytes: byte size of offsets (always 8) + // - 2 bytes: reserved (always 0) + // - 8 bytes: offset to first IFD + // This must be either "MM" or "II". - short byteOrderIdentifier = reader.getInt16(tiffHeaderOffset); + short byteOrderIdentifier = reader.getInt16(0); if (byteOrderIdentifier == 0x4d4d) { // "MM" reader.setMotorolaByteOrder(true); @@ -61,102 +75,149 @@ public void processTiff(@NotNull final RandomAccessReader reader, } // Check the next two values for correctness. - final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset); - handler.setTiffMarker(tiffMarker); + final short tiffMarker = (short) reader.getUInt16(2); + final TiffStandard tiffStandard = handler.processTiffMarker(tiffMarker); + + boolean isBigTiff; + + int firstIfdOffset = reader.getInt32(4); + + switch (tiffStandard) + { + case TIFF: + isBigTiff = false; + firstIfdOffset = (int) reader.getUInt32(4); - int firstIfdOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset; + // David Ekholm sent a digital camera image that has this problem + // TODO getLength should be avoided as it causes RandomAccessStreamReader to read to the end of the stream + if (firstIfdOffset >= reader.getLength() - 1) { + handler.warn("First IFD offset is beyond the end of the TIFF data segment -- trying default offset"); + // First directory normally starts immediately after the offset bytes, so try that + firstIfdOffset = 2 + 2 + 4; + } + + break; - // David Ekholm sent a digital camera image that has this problem - // TODO getLength should be avoided as it causes RandomAccessStreamReader to read to the end of the stream - if (firstIfdOffset >= reader.getLength() - 1) { - handler.warn("First IFD offset is beyond the end of the TIFF data segment -- trying default offset"); - // First directory normally starts immediately after the offset bytes, so try that - firstIfdOffset = tiffHeaderOffset + 2 + 2 + 4; + case BIG_TIFF: + isBigTiff = true; + short offsetByteSize = reader.getInt16(4); + + if (offsetByteSize != 8) + { + handler.error("Unsupported offset byte size: {offsetByteSize}"); + return; + } + + // There are two reserved bytes at offset 6, which are expected to have zero value. + // We skip without validation for now, but may change this in future. + + firstIfdOffset = (int) reader.getUInt64(8); + break; + + default: + handler.error("Unsupported TiffStandard {tiffStandard}."); + return; } Set processedIfdOffsets = new HashSet(); - processIfd(handler, reader, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset); + processIfd(handler, reader, processedIfdOffsets, firstIfdOffset, isBigTiff); } /** * Processes a TIFF IFD. * - * IFD Header: - *
    - *
  • 2 bytes number of tags
  • - *
- * Tag structure: - *
    - *
  • 2 bytes tag type
  • - *
  • 2 bytes format code (values 1 to 12, inclusive)
  • - *
  • 4 bytes component count
  • - *
  • 4 bytes inline value, or offset pointer if too large to fit in four bytes
  • - *
- * - * * @param handler the {@link com.drew.imaging.tiff.TiffHandler} that will coordinate processing and accept read values * @param reader the {@link com.drew.lang.RandomAccessReader} from which the data should be read * @param processedIfdOffsets the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop * @param ifdOffset the offset within reader at which the IFD data starts - * @param tiffHeaderOffset the offset within reader at which the TIFF header starts + * @param isBigTiff Whether the IFD uses the BigTIFF data format. * @throws IOException an error occurred while accessing the required data */ public static void processIfd(@NotNull final TiffHandler handler, @NotNull final RandomAccessReader reader, @NotNull final Set processedIfdOffsets, final int ifdOffset, - final int tiffHeaderOffset) throws IOException + final boolean isBigTiff) throws IOException { + // Standard TIFF + // + // IFD Header: + // - 2 bytes: number of tags + // + // Tag structure: + // - 2 bytes: tag type + // - 2 bytes: format code (values 1 to 12, inclusive) + // - 4 bytes: component count + // - 4 bytes: inline value, or offset pointer if too large to fit in four bytes + + // BigTIFF + // + // IFD Header: + // - 8 bytes: number of tags + // + // Tag structure: + // - 2 bytes: tag type + // - 2 bytes: format code (values 1 to 12, inclusive) + // - 8 bytes: component count + // - 8 bytes: inline value, or offset pointer if too large to fit in eight bytes + Boolean resetByteOrder = null; try { - // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist - if (processedIfdOffsets.contains(Integer.valueOf(ifdOffset))) { - return; - } + // Check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist. + // Note that we track these offsets in the global frame, not the reader's local frame. + int globalIfdOffset = reader.toUnshiftedOffset(ifdOffset); // remember that we've visited this directory so that we don't visit it again later - processedIfdOffsets.add(ifdOffset); + if (!processedIfdOffsets.add(globalIfdOffset)) { + return; + } + // Validate IFD offset if (ifdOffset >= reader.getLength() || ifdOffset < 0) { handler.error("Ignored IFD marked to start outside data segment"); return; } - // First two bytes in the IFD are the number of tags in this directory - int dirTagCount = reader.getUInt16(ifdOffset); + // The number of tags in this directory + int dirTagCount = isBigTiff + ? (int) reader.getUInt64(ifdOffset) + : reader.getUInt16(ifdOffset); // Some software modifies the byte order of the file, but misses some IFDs (such as makernotes). // The entire test image repository doesn't contain a single IFD with more than 255 entries. // Here we detect switched bytes that suggest this problem, and temporarily swap the byte order. // This was discussed in GitHub issue #136. - if (dirTagCount > 0xFF && (dirTagCount & 0xFF) == 0) { + if (!isBigTiff && dirTagCount > 0xFF && (dirTagCount & 0xFF) == 0) { resetByteOrder = reader.isMotorolaByteOrder(); dirTagCount >>= 8; reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder()); } - int dirLength = (2 + (12 * dirTagCount) + 4); + int dirLength = isBigTiff + ? 8 + 20 * dirTagCount + 8 + : 2 + 12 * dirTagCount + 4; if (dirLength + ifdOffset > reader.getLength()) { handler.error("Illegally sized IFD"); return; } + int inlineValueSize = isBigTiff ? 8 : 4; + // // Handle each tag in this directory // int invalidTiffFormatCodeCount = 0; for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) { - final int tagOffset = calculateTagOffset(ifdOffset, tagNumber); + final int tagOffset = calculateTagOffset(ifdOffset, tagNumber, isBigTiff); - // 2 bytes for the tag id final int tagId = reader.getUInt16(tagOffset); - // 2 bytes for the format code final int formatCode = reader.getUInt16(tagOffset + 2); - final TiffDataFormat format = TiffDataFormat.fromTiffFormatCode(formatCode); + final TiffDataFormat format = TiffDataFormat.fromTiffFormatCode(formatCode, isBigTiff); - // 4 bytes dictate the number of components in this tag's data - final long componentCount = reader.getUInt32(tagOffset + 4); + final long componentCount = isBigTiff + ? reader.getUInt64(tagOffset + 4) + : reader.getUInt32(tagOffset + 4); final long byteCount; if (format == null) { @@ -178,59 +239,66 @@ public static void processIfd(@NotNull final TiffHandler handler, } final long tagValueOffset; - if (byteCount > 4) { - // If it's bigger than 4 bytes, the dir entry contains an offset. - final long offsetVal = reader.getUInt32(tagOffset + 8); - if (offsetVal + byteCount > reader.getLength()) { - // Bogus pointer offset and / or byteCount value + if (byteCount > inlineValueSize) { + // Value(s) are too big to fit inline. Follow the pointer. + tagValueOffset = isBigTiff + ? reader.getUInt64(tagOffset + 12) + : reader.getUInt32(tagOffset + 8); + if (tagValueOffset + byteCount > reader.getLength()) { + // Bogus pointer offset and/or byteCount value handler.error("Illegal TIFF tag pointer offset"); continue; } - tagValueOffset = tiffHeaderOffset + offsetVal; } else { - // 4 bytes or less and value is in the dir entry itself. - tagValueOffset = tagOffset + 8; + // Value(s) can fit inline. + tagValueOffset = isBigTiff + ? tagOffset + 12 + : tagOffset + 8; } - if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) { + if (tagValueOffset > reader.getLength()) { handler.error("Illegal TIFF tag pointer offset"); continue; } // Check that this tag isn't going to allocate outside the bounds of the data array. // This addresses an uncommon OutOfMemoryError. - if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) { + if (tagValueOffset + byteCount > reader.getLength()) { handler.error("Illegal number of bytes for TIFF tag data: " + byteCount); continue; } // Some tags point to one or more additional IFDs to process boolean isIfdPointer = false; - if (byteCount == 4 * componentCount) { + if (byteCount == 4 * componentCount || formatCode == TiffDataFormat.CODE_IFD8) { for (int i = 0; i < componentCount; i++) { if (handler.tryEnterSubIfd(tagId)) { isIfdPointer = true; - int subDirOffset = tiffHeaderOffset + reader.getInt32((int) (tagValueOffset + i * 4)); - processIfd(handler, reader, processedIfdOffsets, subDirOffset, tiffHeaderOffset); + long subDirOffset = reader.getUInt32((int) (tagValueOffset + i*4)); + processIfd(handler, reader, processedIfdOffsets, (int) subDirOffset, isBigTiff); } } } // If it wasn't an IFD pointer, allow custom tag processing to occur - if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedIfdOffsets, tiffHeaderOffset, reader, tagId, (int) byteCount)) { + if (!isIfdPointer && !handler.customProcessTag((int) tagValueOffset, processedIfdOffsets, reader, tagId, (int) byteCount, isBigTiff)) { // If no custom processing occurred, process the tag in the standard fashion processTag(handler, tagId, (int) tagValueOffset, (int) componentCount, formatCode, reader); } } // at the end of each IFD is an optional link to the next IFD - final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount); - int nextIfdOffset = reader.getInt32(finalTagOffset); - if (nextIfdOffset != 0) { - nextIfdOffset += tiffHeaderOffset; + final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount, isBigTiff); + + long nextIfdOffsetLong = isBigTiff + ? reader.getUInt64(finalTagOffset) + : reader.getUInt32(finalTagOffset); + + if (nextIfdOffsetLong != 0 && nextIfdOffsetLong <= Integer.MAX_VALUE) { + int nextIfdOffset = (int) nextIfdOffsetLong; + if (nextIfdOffset >= reader.getLength()) { // Last 4 bytes of IFD reference another IFD with an address that is out of bounds - // Note this could have been caused by jhead 1.3 cropping too much return; } else if (nextIfdOffset < ifdOffset) { // TODO is this a valid restriction? @@ -239,7 +307,7 @@ public static void processIfd(@NotNull final TiffHandler handler, } if (handler.hasFollowerIfd()) { - processIfd(handler, reader, processedIfdOffsets, nextIfdOffset, tiffHeaderOffset); + processIfd(handler, reader, processedIfdOffsets, nextIfdOffset, isBigTiff); } } } finally { @@ -326,7 +394,7 @@ private static void processTag(@NotNull final TiffHandler handler, break; case TiffDataFormat.CODE_INT16_S: if (componentCount == 1) { - handler.setInt16s(tagId, (int)reader.getInt16(tagValueOffset)); + handler.setInt16s(tagId, reader.getInt16(tagValueOffset)); } else { short[] array = new short[componentCount]; for (int i = 0; i < componentCount; i++) @@ -366,6 +434,32 @@ private static void processTag(@NotNull final TiffHandler handler, handler.setInt32uArray(tagId, array); } break; + case TiffDataFormat.CODE_INT64_S: + if (componentCount == 1) + { + handler.setInt64S(tagId, reader.getInt64(tagValueOffset)); + } + else + { + long[] array = new long[componentCount]; + for (int i = 0; i < componentCount; i++) + array[i] = reader.getInt64(tagValueOffset + i * 8); + handler.setInt64SArray(tagId, array); + } + break; + case TiffDataFormat.CODE_INT64_U: + if (componentCount == 1) + { + handler.setInt64U(tagId, reader.getUInt64(tagValueOffset)); + } + else + { + long[] array = new long[componentCount]; + for (int i = 0; i < componentCount; i++) + array[i] = reader.getUInt64(tagValueOffset + i * 8); + handler.setInt64UArray(tagId, array); + } + break; default: handler.error(String.format("Invalid TIFF tag format code %d for tag 0x%04X", formatCode, tagId)); } @@ -376,11 +470,14 @@ private static void processTag(@NotNull final TiffHandler handler, * * @param ifdStartOffset the offset at which the IFD starts * @param entryNumber the zero-based entry number + * @param isBigTiff Whether we are using BigTIFF encoding */ - private static int calculateTagOffset(int ifdStartOffset, int entryNumber) + private static int calculateTagOffset(int ifdStartOffset, int entryNumber, boolean isBigTiff) { // Add 2 bytes for the tag count. - // Each entry is 12 bytes. - return ifdStartOffset + 2 + (12 * entryNumber); + // Each entry is 12 bytes for regular TIFF, or 20 bytes for BigTIFF. + return !isBigTiff + ? ifdStartOffset + 2 + 12 * entryNumber + : ifdStartOffset + 8 + 20 * entryNumber; } } diff --git a/Source/com/drew/imaging/tiff/TiffStandard.java b/Source/com/drew/imaging/tiff/TiffStandard.java new file mode 100644 index 000000000..5973395c0 --- /dev/null +++ b/Source/com/drew/imaging/tiff/TiffStandard.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2022 Drew Noakes and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More information about this project is available at: + * + * https://drewnoakes.com/code/exif/ + * https://github.com/drewnoakes/metadata-extractor + */ +package com.drew.imaging.tiff; + +public enum TiffStandard { + + /** + * Regular TIFF. + */ + TIFF, + /** + * The "BigTIFF" standard, which supports greater than 4GB files, more entries + * in IFDs, and larger tag value arrays. + */ + BIG_TIFF +} diff --git a/Source/com/drew/lang/ByteArrayReader.java b/Source/com/drew/lang/ByteArrayReader.java index 3429192af..6a47d78c0 100644 --- a/Source/com/drew/lang/ByteArrayReader.java +++ b/Source/com/drew/lang/ByteArrayReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,17 @@ public ByteArrayReader(@NotNull byte[] buffer, int baseOffset) _baseOffset = baseOffset; } + @Override + public RandomAccessReader withShiftedBaseOffset(int shift) throws IOException { + if (shift == 0) { + return this; + } else { + RandomAccessReader reader = new ByteArrayReader(_buffer, _baseOffset + shift); + reader.setMotorolaByteOrder(isMotorolaByteOrder()); + return reader; + } + } + @Override public int toUnshiftedOffset(int localOffset) { diff --git a/Source/com/drew/lang/RandomAccessFileReader.java b/Source/com/drew/lang/RandomAccessFileReader.java index 502feb55e..513bb0e34 100644 --- a/Source/com/drew/lang/RandomAccessFileReader.java +++ b/Source/com/drew/lang/RandomAccessFileReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,17 @@ public RandomAccessFileReader(@NotNull RandomAccessFile file, int baseOffset) th _length = _file.length(); } + @Override + public RandomAccessReader withShiftedBaseOffset(int shift) throws IOException { + if (shift == 0) { + return this; + } else { + RandomAccessReader reader = new RandomAccessFileReader(_file, _baseOffset + shift); + reader.setMotorolaByteOrder(isMotorolaByteOrder()); + return reader; + } + } + @Override public int toUnshiftedOffset(int localOffset) { @@ -69,7 +80,7 @@ public int toUnshiftedOffset(int localOffset) @Override public long getLength() { - return _length; + return _length - _baseOffset; } @Override @@ -108,7 +119,7 @@ private void seek(final int index) throws IOException if (index == _currentIndex) return; - _file.seek(index); + _file.seek(index + _baseOffset); _currentIndex = index; } diff --git a/Source/com/drew/lang/RandomAccessReader.java b/Source/com/drew/lang/RandomAccessReader.java index 0eb430a0d..8424d6917 100644 --- a/Source/com/drew/lang/RandomAccessReader.java +++ b/Source/com/drew/lang/RandomAccessReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,6 +47,8 @@ public abstract class RandomAccessReader { private boolean _isMotorolaByteOrder = true; + public abstract RandomAccessReader withShiftedBaseOffset(int shift) throws IOException; + public abstract int toUnshiftedOffset(int localOffset); /** @@ -330,6 +332,41 @@ public long getInt64(int index) throws IOException } } + /** + * Get an unsigned 64-bit integer from the buffer. + * + * @param index position within the data buffer to read first byte + * @return the 64 bit int value, between 0x0000000000000000 and 0xFFFFFFFFFFFFFFFF + * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative + */ + public long getUInt64(int index) throws IOException + { + validateIndex(index, 8); + if (_isMotorolaByteOrder) + { + // Motorola - MSB first + return + (long)getByte(index ) << 56 | + (long)getByte(index + 1) << 48 | + (long)getByte(index + 2) << 40 | + (long)getByte(index + 3) << 32 | + (long)getByte(index + 4) << 24 | + (long)getByte(index + 5) << 16 | + (long)getByte(index + 6) << 8 | + getByte(index + 7); + } + // Intel ordering - LSB first + return + (long)getByte(index + 7) << 56 | + (long)getByte(index + 6) << 48 | + (long)getByte(index + 5) << 40 | + (long)getByte(index + 4) << 32 | + (long)getByte(index + 3) << 24 | + (long)getByte(index + 2) << 16 | + (long)getByte(index + 1) << 8 | + getByte(index ); + } + /** * Gets a s15.16 fixed point float from the buffer. *

diff --git a/Source/com/drew/lang/RandomAccessStreamReader.java b/Source/com/drew/lang/RandomAccessStreamReader.java index 133bb4bf1..3f4aae628 100644 --- a/Source/com/drew/lang/RandomAccessStreamReader.java +++ b/Source/com/drew/lang/RandomAccessStreamReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -107,7 +107,7 @@ protected void validateIndex(int index, int bytesRequested) throws IOException if (!isValidIndex(index, bytesRequested)) { assert(_isStreamFinished); // TODO test that can continue using an instance of this type after this exception - throw new BufferBoundsException(index, bytesRequested, _streamLength); + throw new BufferBoundsException(toUnshiftedOffset(index), bytesRequested, _streamLength); } } @@ -212,4 +212,71 @@ public byte[] getBytes(int index, int count) throws IOException return bytes; } + + @Override + public RandomAccessReader withShiftedBaseOffset(int shift) { + if (shift == 0) { + return this; + } else { + RandomAccessReader reader = new ShiftedRandomAccessStreamReader(this, shift); + reader.setMotorolaByteOrder(isMotorolaByteOrder()); + return reader; + } + } + + private static class ShiftedRandomAccessStreamReader extends RandomAccessReader + { + private final RandomAccessStreamReader _baseReader; + private final int _baseOffset; + + public ShiftedRandomAccessStreamReader(RandomAccessStreamReader baseReader, int baseOffset) + { + if (baseOffset < 0) + throw new IllegalArgumentException("Must be zero or greater."); + + _baseReader = baseReader; + _baseOffset = baseOffset; + } + + @Override + public RandomAccessReader withShiftedBaseOffset(int shift) { + if (shift == 0) { + return this; + } else { + RandomAccessReader reader = new ShiftedRandomAccessStreamReader(_baseReader, _baseOffset + shift); + reader.setMotorolaByteOrder(isMotorolaByteOrder()); + return reader; + } + } + + @Override + public int toUnshiftedOffset(int localOffset) { + return localOffset + _baseOffset; + } + + @Override + public byte getByte(int index) throws IOException { + return _baseReader.getByte(_baseOffset + index); + } + + @Override + public byte[] getBytes(int index, int count) throws IOException { + return _baseReader.getBytes(_baseOffset + index, count); + } + + @Override + protected void validateIndex(int index, int bytesRequested) throws IOException { + _baseReader.validateIndex(index + _baseOffset, bytesRequested); + } + + @Override + protected boolean isValidIndex(int index, int bytesRequested) throws IOException { + return _baseReader.isValidIndex(index + _baseOffset, bytesRequested); + } + + @Override + public long getLength() throws IOException { + return _baseReader.getLength() - _baseOffset; + } + } } diff --git a/Source/com/drew/metadata/eps/EpsReader.java b/Source/com/drew/metadata/eps/EpsReader.java index 24d8233c5..d816253dd 100644 --- a/Source/com/drew/metadata/eps/EpsReader.java +++ b/Source/com/drew/metadata/eps/EpsReader.java @@ -80,7 +80,7 @@ public void extract(@NotNull final InputStream inputStream, @NotNull final Metad // Get Tiff metadata try { ByteArrayReader byteArrayReader = new ByteArrayReader(reader.getBytes(tifOffset, tifSize)); - new TiffReader().processTiff(byteArrayReader, new PhotoshopTiffHandler(metadata, null), 0); + new TiffReader().processTiff(byteArrayReader, new PhotoshopTiffHandler(metadata, null)); } catch (TiffProcessingException ex) { directory.addError("Unable to process TIFF data: " + ex.getMessage()); } diff --git a/Source/com/drew/metadata/exif/ExifReader.java b/Source/com/drew/metadata/exif/ExifReader.java index d343597f1..1c81e0ca9 100644 --- a/Source/com/drew/metadata/exif/ExifReader.java +++ b/Source/com/drew/metadata/exif/ExifReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,20 +47,23 @@ public class ExifReader implements JpegSegmentMetadataReader /** Exif data stored in JPEG files' APP1 segment are preceded by this six character preamble "Exif\0\0". */ public static final String JPEG_SEGMENT_PREAMBLE = "Exif\0\0"; + @Override @NotNull public Iterable getSegmentTypes() { return Collections.singletonList(JpegSegmentType.APP1); } + @Override public void readJpegSegments(@NotNull final Iterable segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) + throws IOException { assert(segmentType == JpegSegmentType.APP1); for (byte[] segmentBytes : segments) { // Segment must have the expected preamble if (startsWithJpegExifPreamble(segmentBytes)) { - extract(new ByteArrayReader(segmentBytes), metadata, JPEG_SEGMENT_PREAMBLE.length()); + extract(new ByteArrayReader(segmentBytes, JPEG_SEGMENT_PREAMBLE.length()), metadata, JPEG_SEGMENT_PREAMBLE.length()); } } } @@ -72,29 +75,22 @@ public static boolean startsWithJpegExifPreamble(byte[] bytes) new String(bytes, 0, JPEG_SEGMENT_PREAMBLE.length()).equals(JPEG_SEGMENT_PREAMBLE); } - /** Reads TIFF formatted Exif data from start of the specified {@link RandomAccessReader}. */ - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata) - { - extract(reader, metadata, 0); - } - /** Reads TIFF formatted Exif data a specified offset within a {@link RandomAccessReader}. */ - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int readerOffset) + public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int preambleLength) { - extract(reader, metadata, readerOffset, null); + extract(reader, metadata, null, preambleLength); } /** Reads TIFF formatted Exif data at a specified offset within a {@link RandomAccessReader}. */ - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, int readerOffset, @Nullable Directory parentDirectory) + public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, @Nullable Directory parentDirectory, int exifStartOffset) { - ExifTiffHandler exifTiffHandler = new ExifTiffHandler(metadata, parentDirectory, readerOffset); + ExifTiffHandler exifTiffHandler = new ExifTiffHandler(metadata, parentDirectory, exifStartOffset); try { // Read the TIFF-formatted Exif data new TiffReader().processTiff( reader, - exifTiffHandler, - readerOffset + exifTiffHandler ); } catch (TiffProcessingException e) { exifTiffHandler.error("Exception processing TIFF data: " + e.getMessage()); diff --git a/Source/com/drew/metadata/exif/ExifTiffHandler.java b/Source/com/drew/metadata/exif/ExifTiffHandler.java index 6189588f6..379628cc1 100644 --- a/Source/com/drew/metadata/exif/ExifTiffHandler.java +++ b/Source/com/drew/metadata/exif/ExifTiffHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import com.drew.imaging.jpeg.JpegProcessingException; import com.drew.imaging.tiff.TiffProcessingException; import com.drew.imaging.tiff.TiffReader; +import com.drew.imaging.tiff.TiffStandard; import com.drew.lang.BufferBoundsException; import com.drew.lang.ByteArrayReader; import com.drew.lang.Charsets; @@ -65,15 +66,18 @@ public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDir _exifStartOffset = exifStartOffset; } - public void setTiffMarker(int marker) throws TiffProcessingException + @Override + public TiffStandard processTiffMarker(short marker) throws TiffProcessingException { - final int standardTiffMarker = 0x002A; - final int olympusRawTiffMarker = 0x4F52; // for ORF files - final int olympusRawTiffMarker2 = 0x5352; // for ORF files - final int panasonicRawTiffMarker = 0x0055; // for RW2 files + final short standardTiffMarker = 0x002A; + final short bigTiffMarker = 0x002B; + final short olympusRawTiffMarker = 0x4F52; // for ORF files + final short olympusRawTiffMarker2 = 0x5352; // for ORF files + final short panasonicRawTiffMarker = 0x0055; // for RW2 files switch (marker) { case standardTiffMarker: + case bigTiffMarker: case olympusRawTiffMarker: // TODO implement an IFD0, if there is one case olympusRawTiffMarker2: // TODO implement an IFD0, if there is one pushDirectory(ExifIFD0Directory.class); @@ -84,8 +88,13 @@ public void setTiffMarker(int marker) throws TiffProcessingException default: throw new TiffProcessingException(String.format("Unexpected TIFF marker: 0x%X", marker)); } + + return marker == bigTiffMarker + ? TiffStandard.BIG_TIFF + : TiffStandard.TIFF; } + @Override public boolean tryEnterSubIfd(int tagId) { if (tagId == ExifDirectoryBase.TAG_SUB_IFD_OFFSET) { @@ -142,6 +151,7 @@ public boolean tryEnterSubIfd(int tagId) return false; } + @Override public boolean hasFollowerIfd() { // In Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case. @@ -165,6 +175,7 @@ public boolean hasFollowerIfd() return false; } + @Override @Nullable public Long tryCustomProcessFormat(final int tagId, final int formatCode, final long componentCount) { @@ -178,12 +189,13 @@ public Long tryCustomProcessFormat(final int tagId, final int formatCode, final return null; } + @Override public boolean customProcessTag(final int tagOffset, final @NotNull Set processedIfdOffsets, - final int tiffHeaderOffset, final @NotNull RandomAccessReader reader, final int tagId, - final int byteCount) throws IOException + final int byteCount, + final boolean isBigTiff) throws IOException { assert(_currentDirectory != null); @@ -201,7 +213,7 @@ public boolean customProcessTag(final int tagOffset, // Custom processing for the Makernote tag if (tagId == ExifSubIFDDirectory.TAG_MAKERNOTE && _currentDirectory instanceof ExifSubIFDDirectory) { - return processMakernote(tagOffset, processedIfdOffsets, tiffHeaderOffset, reader); + return processMakernote(tagOffset, processedIfdOffsets, reader, isBigTiff); } // Custom processing for embedded IPTC data @@ -257,35 +269,35 @@ public boolean customProcessTag(final int tagOffset, switch (tagId) { case OlympusMakernoteDirectory.TAG_EQUIPMENT: pushDirectory(OlympusEquipmentMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS: pushDirectory(OlympusCameraSettingsMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT: pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2: pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING: pushDirectory(OlympusImageProcessingMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_FOCUS_INFO: pushDirectory(OlympusFocusInfoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_RAW_INFO: pushDirectory(OlympusRawInfoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; case OlympusMakernoteDirectory.TAG_MAIN_INFO: pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, isBigTiff); return true; } } @@ -425,8 +437,8 @@ private static String getReaderString(final @NotNull RandomAccessReader reader, private boolean processMakernote(final int makernoteOffset, final @NotNull Set processedIfdOffsets, - final int tiffHeaderOffset, - final @NotNull RandomAccessReader reader) throws IOException + final @NotNull RandomAccessReader reader, + final boolean isBigTiff) throws IOException { assert(_currentDirectory != null); @@ -453,24 +465,24 @@ private boolean processMakernote(final int makernoteOffset, // Olympus Makernote // Epson and Agfa use Olympus makernote standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/ pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); } else if ("OLYMPUS\0II".equals(firstTenChars)) { // Olympus Makernote (alternate) // Note that data is relative to the beginning of the makernote // http://exiv2.org/makernote.html pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 12, isBigTiff); } else if ("OM SYSTEM\0\0\0II".equals(firstFourteenChars)) { // Olympus Makernote (OM SYSTEM) // Note that data is relative to the beginning of the makernote // http://exiv2.org/makernote.html pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 14, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14, isBigTiff); } else if (cameraMake != null && cameraMake.toUpperCase().startsWith("MINOLTA")) { // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote // area that commences immediately. pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } else if (cameraMake != null && cameraMake.trim().toUpperCase().startsWith("NIKON")) { if ("Nikon".equals(firstFiveChars)) { /* There are two scenarios here: @@ -484,11 +496,11 @@ private boolean processMakernote(final int makernoteOffset, switch (reader.getUInt8(makernoteOffset + 6)) { case 1: pushDirectory(NikonType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); break; case 2: pushDirectory(NikonType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 18, makernoteOffset + 10); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset + 10), processedIfdOffsets, 8, isBigTiff); break; default: _currentDirectory.addError("Unsupported Nikon makernote data ignored."); @@ -497,26 +509,26 @@ private boolean processMakernote(final int makernoteOffset, } else { // The IFD begins with the first Makernote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models. pushDirectory(NikonType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) { pushDirectory(SonyType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, isBigTiff); // Do this check LAST after most other Sony checks } else if (cameraMake != null && cameraMake.startsWith("SONY") && !Arrays.equals(reader.getBytes(makernoteOffset, 2), new byte[]{ 0x01, 0x00 }) ) { // The IFD begins with the first Makernote byte (no ASCII name). Used in SR2 and ARW images pushDirectory(SonyType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) { // force MM for this directory reader.setMotorolaByteOrder(true); // skip 12 byte header + 2 for "MM" + 6 pushDirectory(SonyType6MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 20, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 20, isBigTiff); } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) { pushDirectory(SigmaMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 10, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 10, isBigTiff); } else if ("KDK".equals(firstThreeChars)) { reader.setMotorolaByteOrder(firstSevenChars.equals("KDK INFO")); KodakMakernoteDirectory directory = new KodakMakernoteDirectory(); @@ -524,14 +536,14 @@ private boolean processMakernote(final int makernoteOffset, processKodakMakernote(directory, makernoteOffset, reader); } else if ("Canon".equalsIgnoreCase(cameraMake)) { pushDirectory(CanonMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } else if (cameraMake != null && cameraMake.toUpperCase().startsWith("CASIO")) { if ("QVC\u0000\u0000\u0000".equals(firstSixChars)) { pushDirectory(CasioType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6, isBigTiff); } else { pushDirectory(CasioType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraMake)) { // Note that this also applies to certain Leica cameras, such as the Digilux-4.3 @@ -539,13 +551,14 @@ private boolean processMakernote(final int makernoteOffset, // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote // IFD, though the offset is relative to the start of the makernote, not the TIFF // header (like everywhere else) - int ifdStart = makernoteOffset + reader.getInt32(makernoteOffset + 8); + RandomAccessReader makernoteReader = reader.withShiftedBaseOffset(makernoteOffset); + int ifdStart = makernoteReader.getInt32(8); pushDirectory(FujifilmMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, ifdStart, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, ifdStart, isBigTiff); } else if ("KYOCERA".equals(firstSevenChars)) { // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html pushDirectory(KyoceraMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 22, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 22, isBigTiff); } else if ("LEICA".equals(firstFiveChars)) { reader.setMotorolaByteOrder(false); @@ -563,14 +576,14 @@ private boolean processMakernote(final int makernoteOffset, "LEICA\0\u0007\0".equals(firstEightChars)) { pushDirectory(LeicaType5MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); } else if ("Leica Camera AG".equals(cameraMake)) { pushDirectory(LeicaMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); } else if ("LEICA".equals(cameraMake)) { // Some Leica cameras use Panasonic makernote tags pushDirectory(PanasonicMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, isBigTiff); } else { return false; } @@ -579,7 +592,7 @@ private boolean processMakernote(final int makernoteOffset, // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html pushDirectory(PanasonicMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 12, isBigTiff); } else if ("AOC\u0000".equals(firstFourChars)) { // NON-Standard TIFF IFD Data using Casio Type 2 Tags // IFD has no Next-IFD pointer at end of IFD, and @@ -587,7 +600,7 @@ private boolean processMakernote(final int makernoteOffset, // Observed for: // - Pentax ist D pushDirectory(CasioType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 6, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 6, isBigTiff); } else if (cameraMake != null && (cameraMake.toUpperCase().startsWith("PENTAX") || cameraMake.toUpperCase().startsWith("ASAHI"))) { // NON-Standard TIFF IFD Data using Pentax Tags // IFD has no Next-IFD pointer at end of IFD, and @@ -596,7 +609,7 @@ private boolean processMakernote(final int makernoteOffset, // - PENTAX Optio 330 // - PENTAX Optio 430 pushDirectory(PentaxMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 0, isBigTiff); // } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) { // // This Konica data is not understood. Header identified in accordance with information at this site: // // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html @@ -604,7 +617,7 @@ private boolean processMakernote(final int makernoteOffset, // exifDirectory.addError("Unsupported Konica/Minolta data ignored."); } else if ("SANYO\0\1\0".equals(firstEightChars)) { pushDirectory(SanyoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); } else if (cameraMake != null && cameraMake.toLowerCase().startsWith("ricoh")) { if (firstTwoChars.equals("Rv") || firstThreeChars.equals("Rev")) { // This is a textual format, where the makernote bytes look like: @@ -617,14 +630,14 @@ private boolean processMakernote(final int makernoteOffset, // Always in Motorola byte order reader.setMotorolaByteOrder(true); pushDirectory(RicohMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8, isBigTiff); } } else if (firstTenChars.equals("Apple iOS\0")) { // Always in Motorola byte order boolean orderBefore = reader.isMotorolaByteOrder(); reader.setMotorolaByteOrder(true); pushDirectory(AppleMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 14, makernoteOffset); + TiffReader.processIfd(this, reader.withShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14, isBigTiff); reader.setMotorolaByteOrder(orderBefore); } else if (reader.getUInt16(makernoteOffset) == ReconyxHyperFireMakernoteDirectory.MAKERNOTE_VERSION) { ReconyxHyperFireMakernoteDirectory directory = new ReconyxHyperFireMakernoteDirectory(); @@ -641,7 +654,7 @@ private boolean processMakernote(final int makernoteOffset, } else if ("SAMSUNG".equalsIgnoreCase(cameraMake)) { // Only handles Type2 notes correctly. Others aren't implemented, and it's complex to determine which ones to use pushDirectory(SamsungType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, isBigTiff); } else { // The makernote is not comprehended by this library. // If you are reading this and believe a particular camera's image should be processed, get in touch. diff --git a/Source/com/drew/metadata/heif/HeifPictureHandler.java b/Source/com/drew/metadata/heif/HeifPictureHandler.java index 088a9d636..e60ee9370 100644 --- a/Source/com/drew/metadata/heif/HeifPictureHandler.java +++ b/Source/com/drew/metadata/heif/HeifPictureHandler.java @@ -151,7 +151,7 @@ private void handleItem(@NotNull ItemInfoBox.ItemInfoEntry entry, } payloadReader.skip(tiffHeaderOffset); ByteArrayInputStream tiffStream = new ByteArrayInputStream(payloadReader.getBytes(payloadReader.available())); - new ExifReader().extract(new RandomAccessStreamReader(tiffStream), metadata); + new ExifReader().extract(new RandomAccessStreamReader(tiffStream), metadata, 0); } } diff --git a/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java b/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java index b3dcdc1ec..4ed68c28d 100644 --- a/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java +++ b/Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -114,7 +114,7 @@ public Mp4Handler processBox(@NotNull String type, byte[] payload, long boxSi switch (uuidType) { case Exif: - new ExifReader().extract(new ByteArrayReader(payload, 16), metadata, 0, directory); + new ExifReader().extract(new ByteArrayReader(payload, 16), metadata, directory, 0); break; case IptcIim: new IptcReader().extract(new SequentialByteArrayReader(payload, 16), metadata, payload.length - 16, directory); diff --git a/Source/com/drew/metadata/photoshop/PhotoshopReader.java b/Source/com/drew/metadata/photoshop/PhotoshopReader.java index 90ef156f7..79488cb17 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopReader.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,12 +52,14 @@ public class PhotoshopReader implements JpegSegmentMetadataReader @NotNull private static final String JPEG_SEGMENT_PREAMBLE = "Photoshop 3.0"; + @Override @NotNull public Iterable getSegmentTypes() { return Collections.singletonList(JpegSegmentType.APPD); } + @Override public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) { final int preambleLength = JPEG_SEGMENT_PREAMBLE.length(); @@ -149,7 +151,7 @@ public void extract(@NotNull final SequentialReader reader, int length, @NotNull else if (tagType == PhotoshopDirectory.TAG_ICC_PROFILE_BYTES) new IccReader().extract(new ByteArrayReader(tagBytes), metadata, directory); else if (tagType == PhotoshopDirectory.TAG_EXIF_DATA_1 || tagType == PhotoshopDirectory.TAG_EXIF_DATA_3) - new ExifReader().extract(new ByteArrayReader(tagBytes), metadata, 0, directory); + new ExifReader().extract(new ByteArrayReader(tagBytes), metadata, directory, 0); else if (tagType == PhotoshopDirectory.TAG_XMP_DATA) new XmpReader().extract(tagBytes, metadata, directory); else if (tagType >= 0x07D0 && tagType <= 0x0BB6) { diff --git a/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java index a90e52827..26522493e 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java @@ -41,7 +41,8 @@ public boolean customProcessTag(final int tagOffset, final int tiffHeaderOffset, final @NotNull RandomAccessReader reader, final int tagId, - final int byteCount) throws IOException + final int byteCount, + final boolean isBigTiff) throws IOException { switch (tagId) { case TAG_XMP: @@ -55,7 +56,6 @@ public boolean customProcessTag(final int tagOffset, return true; } - - return super.customProcessTag(tagOffset, processedIfdOffsets, tiffHeaderOffset, reader, tagId, byteCount); + return super.customProcessTag(tagOffset, processedIfdOffsets, reader, tagId, byteCount, isBigTiff); } } diff --git a/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java b/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java index 21ed52917..3e06b2537 100644 --- a/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java +++ b/Source/com/drew/metadata/tiff/DirectoryTiffHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ protected DirectoryTiffHandler(Metadata metadata, @Nullable Directory parentDire _rootParentDirectory = parentDirectory; } + @Override public void endingIFD() { _currentDirectory = _directoryStack.empty() ? null : _directoryStack.pop(); @@ -90,11 +91,13 @@ protected void pushDirectory(@NotNull Directory directory) _metadata.addDirectory(_currentDirectory); } + @Override public void warn(@NotNull String message) { getCurrentOrErrorDirectory().addError(message); } + @Override public void error(@NotNull String message) { getCurrentOrErrorDirectory().addError(message); @@ -112,112 +115,156 @@ private Directory getCurrentOrErrorDirectory() return _currentDirectory; } + @Override public void setByteArray(int tagId, @NotNull byte[] bytes) { _currentDirectory.setByteArray(tagId, bytes); } + @Override public void setString(int tagId, @NotNull StringValue string) { _currentDirectory.setStringValue(tagId, string); } + @Override public void setRational(int tagId, @NotNull Rational rational) { _currentDirectory.setRational(tagId, rational); } + @Override public void setRationalArray(int tagId, @NotNull Rational[] array) { _currentDirectory.setRationalArray(tagId, array); } + @Override public void setFloat(int tagId, float float32) { _currentDirectory.setFloat(tagId, float32); } + @Override public void setFloatArray(int tagId, @NotNull float[] array) { _currentDirectory.setFloatArray(tagId, array); } + @Override public void setDouble(int tagId, double double64) { _currentDirectory.setDouble(tagId, double64); } + @Override public void setDoubleArray(int tagId, @NotNull double[] array) { _currentDirectory.setDoubleArray(tagId, array); } + @Override public void setInt8s(int tagId, byte int8s) { // NOTE Directory stores all integral types as int32s, except for int32u and long _currentDirectory.setInt(tagId, int8s); } + @Override public void setInt8sArray(int tagId, @NotNull byte[] array) { // NOTE Directory stores all integral types as int32s, except for int32u and long _currentDirectory.setByteArray(tagId, array); } + @Override public void setInt8u(int tagId, short int8u) { // NOTE Directory stores all integral types as int32s, except for int32u and long _currentDirectory.setInt(tagId, int8u); } + @Override public void setInt8uArray(int tagId, @NotNull short[] array) { // TODO create and use a proper setter for short[] _currentDirectory.setObjectArray(tagId, array); } + @Override public void setInt16s(int tagId, int int16s) { // TODO create and use a proper setter for int16u? _currentDirectory.setInt(tagId, int16s); } + @Override public void setInt16sArray(int tagId, @NotNull short[] array) { // TODO create and use a proper setter for short[] _currentDirectory.setObjectArray(tagId, array); } + @Override public void setInt16u(int tagId, int int16u) { // TODO create and use a proper setter for _currentDirectory.setInt(tagId, int16u); } + @Override public void setInt16uArray(int tagId, @NotNull int[] array) { // TODO create and use a proper setter for short[] _currentDirectory.setObjectArray(tagId, array); } + @Override public void setInt32s(int tagId, int int32s) { _currentDirectory.setInt(tagId, int32s); } + @Override public void setInt32sArray(int tagId, @NotNull int[] array) { _currentDirectory.setIntArray(tagId, array); } + @Override public void setInt32u(int tagId, long int32u) { _currentDirectory.setLong(tagId, int32u); } + @Override public void setInt32uArray(int tagId, @NotNull long[] array) { // TODO create and use a proper setter for short[] _currentDirectory.setObjectArray(tagId, array); } + + @Override + public void setInt64S(int tagId, long int64S) + { + _currentDirectory.setLong(tagId, int64S); + } + + @Override + public void setInt64SArray(int tagId, @NotNull long[] array) + { + _currentDirectory.setObjectArray(tagId, array); + } + + @Override + public void setInt64U(int tagId, long int64U) + { + _currentDirectory.setLong(tagId, int64U); + } + + @Override + public void setInt64UArray(int tagId, @NotNull long[] array) + { + _currentDirectory.setObjectArray(tagId, array); + } } diff --git a/Source/com/drew/metadata/webp/WebpRiffHandler.java b/Source/com/drew/metadata/webp/WebpRiffHandler.java index d1ac57c16..510834af2 100644 --- a/Source/com/drew/metadata/webp/WebpRiffHandler.java +++ b/Source/com/drew/metadata/webp/WebpRiffHandler.java @@ -83,7 +83,7 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) ByteArrayReader reader = ExifReader.startsWithJpegExifPreamble(payload) ? new ByteArrayReader(payload, ExifReader.JPEG_SEGMENT_PREAMBLE.length()) : new ByteArrayReader(payload); - new ExifReader().extract(reader, _metadata); + new ExifReader().extract(reader, _metadata, 0); } else if (fourCC.equals(WebpDirectory.CHUNK_ICCP)) { new IccReader().extract(new ByteArrayReader(payload), _metadata); } else if (fourCC.equals(WebpDirectory.CHUNK_XMP)) { diff --git a/Tests/com/drew/lang/RandomAccessTestBase.java b/Tests/com/drew/lang/RandomAccessTestBase.java index 7d9ac418f..68ee674a7 100644 --- a/Tests/com/drew/lang/RandomAccessTestBase.java +++ b/Tests/com/drew/lang/RandomAccessTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,9 @@ import java.io.IOException; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -336,4 +338,35 @@ public void testGetInt8EOF() throws Exception fail("Expecting exception"); } catch (IOException ignored) {} } + + @Test + public void testWithShiftedBaseOffset() throws Exception + { + RandomAccessReader reader = createReader(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + reader.setMotorolaByteOrder(false); + + assertEquals(10, reader.getLength()); + assertEquals(0, reader.getByte(0)); + assertEquals(1, reader.getByte(1)); + assertArrayEquals(new byte[] { 0, 1 }, reader.getBytes(0, 2)); + assertEquals(4, reader.toUnshiftedOffset(4)); + + reader = reader.withShiftedBaseOffset(2); + + assertFalse(reader.isMotorolaByteOrder()); + assertEquals(8, reader.getLength()); + assertEquals(2, reader.getByte(0)); + assertEquals(3, reader.getByte(1)); + assertArrayEquals(new byte[] { 2, 3 }, reader.getBytes(0, 2)); + assertEquals(6, reader.toUnshiftedOffset(4)); + + reader = reader.withShiftedBaseOffset(2); + + assertFalse(reader.isMotorolaByteOrder()); + assertEquals(6, reader.getLength()); + assertEquals(4, reader.getByte(0)); + assertEquals(5, reader.getByte(1)); + assertArrayEquals(new byte[] { 4, 5 }, reader.getBytes(0, 2)); + assertEquals(8, reader.toUnshiftedOffset(4)); + } } diff --git a/Tests/com/drew/metadata/exif/ExifReaderTest.java b/Tests/com/drew/metadata/exif/ExifReaderTest.java index 3cd95547b..8cc4c7012 100644 --- a/Tests/com/drew/metadata/exif/ExifReaderTest.java +++ b/Tests/com/drew/metadata/exif/ExifReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 Drew Noakes and contributors + * Copyright 2002-2022 Drew Noakes and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ public static Metadata processBytes(@NotNull String filePath) throws IOException { Metadata metadata = new Metadata(); byte[] bytes = FileUtil.readBytes(filePath); - new ExifReader().extract(new ByteArrayReader(bytes), metadata, ExifReader.JPEG_SEGMENT_PREAMBLE.length(), null); + new ExifReader().extract(new ByteArrayReader(bytes, ExifReader.JPEG_SEGMENT_PREAMBLE.length()), metadata, null, ExifReader.JPEG_SEGMENT_PREAMBLE.length()); return metadata; } diff --git a/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java b/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java index 302c27d6a..fcd842e8c 100644 --- a/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java +++ b/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java @@ -21,6 +21,7 @@ package com.drew.metadata.exif; import com.drew.lang.Rational; +import com.drew.metadata.ErrorDirectory; import com.drew.metadata.Metadata; import com.drew.metadata.exif.makernotes.NikonType1MakernoteDirectory; import org.junit.Before; @@ -28,6 +29,8 @@ import static org.junit.Assert.*; +import java.util.Objects; + /** * @author Drew Noakes https://drewnoakes.com */ @@ -55,6 +58,9 @@ public void setUp() throws Exception { Metadata metadata = ExifReaderTest.processBytes("Tests/Data/nikonMakernoteType1.jpg.app1"); + ErrorDirectory error = metadata.getFirstDirectoryOfType(ErrorDirectory.class); + assertNull(error != null ? Objects.toString(error.getErrors()) : "", error); + _nikonDirectory = metadata.getFirstDirectoryOfType(NikonType1MakernoteDirectory.class); _exifSubIFDDirectory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); _exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);