diff --git a/Source/com/drew/imaging/FileType.java b/Source/com/drew/imaging/FileType.java index db7a1118e..310785c6e 100644 --- a/Source/com/drew/imaging/FileType.java +++ b/Source/com/drew/imaging/FileType.java @@ -43,6 +43,7 @@ public enum FileType Riff("RIFF", "Resource Interchange File Format", null), Wav("WAV", "Waveform Audio File Format", "audio/vnd.wave", "wav", "wave"), Avi("AVI", "Audio Video Interleaved", "video/vnd.avi", "avi"), + Netpbm("Netpbm", "Netpbm", "image/x-portable-graymap", "pbm", "ppm"), WebP("WebP", "WebP", "image/webp", "webp"), Mov("MOV", "QuickTime Movie", "video/quicktime", "mov", "qt"), Mp4("MP4", "MPEG-4 Part 14", "video/mp4", "mp4", "m4a", "m4p", "m4b", "m4r", "m4v"), diff --git a/Source/com/drew/imaging/FileTypeDetector.java b/Source/com/drew/imaging/FileTypeDetector.java index b33dd0d07..f749b49e7 100644 --- a/Source/com/drew/imaging/FileTypeDetector.java +++ b/Source/com/drew/imaging/FileTypeDetector.java @@ -22,13 +22,13 @@ import com.drew.lang.ByteTrie; import com.drew.lang.annotations.NotNull; +import com.drew.lang.ReaderInfo; -import java.io.BufferedInputStream; import java.io.IOException; import java.util.HashMap; /** - * Examines the a file's first bytes and estimates the file's type. + * Examines a file's first bytes and estimates the file's type. */ public class FileTypeDetector { @@ -56,6 +56,13 @@ public class FileTypeDetector _root.addPath(FileType.Gif, "GIF87a".getBytes()); _root.addPath(FileType.Gif, "GIF89a".getBytes()); _root.addPath(FileType.Ico, new byte[]{0x00, 0x00, 0x01, 0x00}); + _root.addPath(FileType.Netpbm, "P1".getBytes()); // ASCII B + _root.addPath(FileType.Netpbm, "P2".getBytes()); // ASCII greysca + _root.addPath(FileType.Netpbm, "P3".getBytes()); // ASCII R + _root.addPath(FileType.Netpbm, "P4".getBytes()); // RAW B + _root.addPath(FileType.Netpbm, "P5".getBytes()); // RAW greysca + _root.addPath(FileType.Netpbm, "P6".getBytes()); // RAW R + _root.addPath(FileType.Netpbm, "P7".getBytes()); // P _root.addPath(FileType.Pcx, new byte[]{0x0A, 0x00, 0x01}); // multiple PCX versions, explicitly listed _root.addPath(FileType.Pcx, new byte[]{0x0A, 0x02, 0x01}); _root.addPath(FileType.Pcx, new byte[]{0x0A, 0x03, 0x01}); @@ -97,6 +104,8 @@ public class FileTypeDetector _ftypMap.put("ftypM4VH", FileType.Mp4); _ftypMap.put("ftypM4VP", FileType.Mp4); _ftypMap.put("ftypmmp4", FileType.Mp4); + _ftypMap.put("ftyp3g2a", FileType.Mp4); + _ftypMap.put("ftyp3gp5", FileType.Mp4); _ftypMap.put("ftypmp41", FileType.Mp4); _ftypMap.put("ftypmp42", FileType.Mp4); _ftypMap.put("ftypmp71", FileType.Mp4); @@ -151,7 +160,7 @@ private FileTypeDetector() throws Exception /** * Examines the file's bytes and estimates the file's type. *

- * Requires a {@link BufferedInputStream} in order to mark and reset the stream to the position + * Requires a {@link ReaderInfo} in order to mark and reset the stream to the position * at which it was provided to this method once completed. *

* Requires the stream to contain at least eight bytes. @@ -159,22 +168,17 @@ private FileTypeDetector() throws Exception * @throws IOException if an IO error occurred or the input stream ended unexpectedly. */ @NotNull - public static FileType detectFileType(@NotNull final BufferedInputStream inputStream) throws IOException + public static FileType detectFileType(@NotNull final ReaderInfo rdrInfo) throws IOException { - if (!inputStream.markSupported()) - throw new IOException("Stream must support mark/reset"); - int maxByteCount = Math.max(16, _root.getMaxDepth()); - inputStream.mark(maxByteCount); - byte[] bytes = new byte[maxByteCount]; - int bytesRead = inputStream.read(bytes); + int bytesRead = rdrInfo.read(bytes, 0, bytes.length); if (bytesRead == -1) throw new IOException("Stream ended before file's magic number could be determined."); - inputStream.reset(); + rdrInfo.skip(-bytesRead); FileType fileType = _root.find(bytes); diff --git a/Source/com/drew/imaging/ImageMetadataReader.java b/Source/com/drew/imaging/ImageMetadataReader.java index 9a3dd0bd0..5dec8872a 100644 --- a/Source/com/drew/imaging/ImageMetadataReader.java +++ b/Source/com/drew/imaging/ImageMetadataReader.java @@ -29,6 +29,7 @@ import com.drew.imaging.jpeg.JpegMetadataReader; import com.drew.imaging.mp3.Mp3MetadataReader; import com.drew.imaging.mp4.Mp4MetadataReader; +import com.drew.imaging.netpbm.NetpbmMetadataReader; import com.drew.imaging.quicktime.QuickTimeMetadataReader; import com.drew.imaging.pcx.PcxMetadataReader; import com.drew.imaging.png.PngMetadataReader; @@ -37,7 +38,8 @@ import com.drew.imaging.tiff.TiffMetadataReader; import com.drew.imaging.wav.WavMetadataReader; import com.drew.imaging.webp.WebpMetadataReader; -import com.drew.lang.RandomAccessStreamReader; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.lang.StringUtil; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Directory; @@ -68,6 +70,7 @@ *

  • {@link FileSystemMetadataReader} for metadata from the file system when a {@link File} is provided
  • *
  • {@link GifMetadataReader} for GIF files
  • *
  • {@link IcoMetadataReader} for ICO files
  • + *
  • {@link NetpbmMetadataReader} for Netpbm files (PPM, PGM, PBM, PPM)
  • *
  • {@link JpegMetadataReader} for JPEG files
  • *
  • {@link Mp4MetadataReader} for MPEG-4 files
  • *
  • {@link PcxMetadataReader} for PCX files
  • @@ -95,93 +98,104 @@ public class ImageMetadataReader * * @param inputStream a stream from which the file data may be read. The stream must be positioned at the * beginning of the file's data. + * @param streamLength the length of the stream, if known, otherwise -1. * @return a populated {@link Metadata} object containing directories of tags with values and any processing errors. * @throws ImageProcessingException if the file type is unknown, or for general processing errors. */ @NotNull - public static Metadata readMetadata(@NotNull final InputStream inputStream) throws ImageProcessingException, IOException + public static Metadata readMetadata(@NotNull final InputStream inputStream, long streamLength) throws ImageProcessingException, IOException { - return readMetadata(inputStream, -1); + return readMetadata(new RandomAccessStream(inputStream, streamLength)); } /** - * Reads metadata from an {@link InputStream} of known length. + * Reads {@link Metadata} from the file at the given path * - * @param inputStream a stream from which the file data may be read. The stream must be positioned at the - * beginning of the file's data. - * @param streamLength the length of the stream, if known, otherwise -1. + * @param filePath a file path from which the image data may be read. + * @return a populated {@link Metadata} object containing directories of tags with values and any processing errors. + * @throws ImageProcessingException for general processing errors. + */ + @NotNull + public static Metadata readMetadata(@NotNull final String filePath) throws ImageProcessingException, IOException + { + return readMetadata(new RandomAccessStream(new RandomAccessFile(filePath, "r"))); + } + + /** + * Reads {@link Metadata} from an {@link RandomAccessStream} of known length. + * + * @param rastream a {@link RandomAccessStream} from which the file data may be read. * @return a populated {@link Metadata} object containing directories of tags with values and any processing errors. * @throws ImageProcessingException if the file type is unknown, or for general processing errors. */ @NotNull - public static Metadata readMetadata(@NotNull final InputStream inputStream, final long streamLength) throws ImageProcessingException, IOException + public static Metadata readMetadata(@NotNull final RandomAccessStream rastream) throws ImageProcessingException, IOException { - BufferedInputStream bufferedInputStream = inputStream instanceof BufferedInputStream - ? (BufferedInputStream)inputStream - : new BufferedInputStream(inputStream); - - FileType fileType = FileTypeDetector.detectFileType(bufferedInputStream); - - Metadata metadata = readMetadata(bufferedInputStream, streamLength, fileType); - + ReaderInfo fileTypeReader = rastream.createReader(); + FileType fileType = FileTypeDetector.detectFileType(fileTypeReader); + + ReaderInfo readerClone = fileTypeReader.Clone(); + + Metadata metadata = readMetadata(readerClone, fileType); metadata.addDirectory(new FileTypeDirectory(fileType)); return metadata; } /** - * Reads metadata from an {@link InputStream} of known length and file type. + * Reads {@link Metadata} from an {@link ReaderInfo} of known length and file type. * - * @param inputStream a stream from which the file data may be read. The stream must be positioned at the + * @param reader a {@link ReaderInfo} from which the file data may be read. The stream must be positioned at the * beginning of the file's data. - * @param streamLength the length of the stream, if known, otherwise -1. * @param fileType the file type of the data stream. * @return a populated {@link Metadata} object containing directories of tags with values and any processing errors. * @throws ImageProcessingException if the file type is unknown, or for general processing errors. */ @NotNull - public static Metadata readMetadata(@NotNull final InputStream inputStream, final long streamLength, final FileType fileType) throws IOException, ImageProcessingException + public static Metadata readMetadata(@NotNull final ReaderInfo reader, final FileType fileType) throws IOException, ImageProcessingException { switch (fileType) { case Jpeg: - return JpegMetadataReader.readMetadata(inputStream); + return JpegMetadataReader.readMetadata(reader); case Tiff: case Arw: case Cr2: case Nef: case Orf: case Rw2: - return TiffMetadataReader.readMetadata(new RandomAccessStreamReader(inputStream, RandomAccessStreamReader.DEFAULT_CHUNK_LENGTH, streamLength)); + return TiffMetadataReader.readMetadata(reader); case Psd: - return PsdMetadataReader.readMetadata(inputStream); + return PsdMetadataReader.readMetadata(reader); case Png: - return PngMetadataReader.readMetadata(inputStream); + return PngMetadataReader.readMetadata(reader); case Bmp: - return BmpMetadataReader.readMetadata(inputStream); + return BmpMetadataReader.readMetadata(reader); case Gif: - return GifMetadataReader.readMetadata(inputStream); + return GifMetadataReader.readMetadata(reader); case Ico: - return IcoMetadataReader.readMetadata(inputStream); + return IcoMetadataReader.readMetadata(reader); case Pcx: - return PcxMetadataReader.readMetadata(inputStream); + return PcxMetadataReader.readMetadata(reader); case WebP: - return WebpMetadataReader.readMetadata(inputStream); + return WebpMetadataReader.readMetadata(reader); case Raf: - return RafMetadataReader.readMetadata(inputStream); + return RafMetadataReader.readMetadata(reader); case Avi: - return AviMetadataReader.readMetadata(inputStream); + return AviMetadataReader.readMetadata(reader); + case Netpbm: + return NetpbmMetadataReader.readMetadata(reader); case Wav: - return WavMetadataReader.readMetadata(inputStream); + return WavMetadataReader.readMetadata(reader); case Mov: - return QuickTimeMetadataReader.readMetadata(inputStream); + return QuickTimeMetadataReader.readMetadata(reader); case Mp4: - return Mp4MetadataReader.readMetadata(inputStream); + return Mp4MetadataReader.readMetadata(reader); case Mp3: - return Mp3MetadataReader.readMetadata(inputStream); + return Mp3MetadataReader.readMetadata(reader); case Eps: - return EpsMetadataReader.readMetadata(inputStream); + return EpsMetadataReader.readMetadata(reader); case Heif: - return HeifMetadataReader.readMetadata(inputStream); + return HeifMetadataReader.readMetadata(reader); case Unknown: throw new ImageProcessingException("File format could not be determined"); default: diff --git a/Source/com/drew/imaging/avi/AviMetadataReader.java b/Source/com/drew/imaging/avi/AviMetadataReader.java index a6f943865..b83eaf6eb 100644 --- a/Source/com/drew/imaging/avi/AviMetadataReader.java +++ b/Source/com/drew/imaging/avi/AviMetadataReader.java @@ -22,7 +22,8 @@ import com.drew.imaging.riff.RiffProcessingException; import com.drew.imaging.riff.RiffReader; -import com.drew.lang.StreamReader; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.avi.AviRiffHandler; @@ -32,6 +33,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.RandomAccessFile; /** * Obtains metadata from AVI files. @@ -43,22 +45,34 @@ public class AviMetadataReader @NotNull public static Metadata readMetadata(@NotNull File file) throws IOException, RiffProcessingException { - InputStream inputStream = new FileInputStream(file); Metadata metadata; - try { - metadata = readMetadata(inputStream); - } finally { - inputStream.close(); + if(file.isFile()) + { + RandomAccessFile raFile = new RandomAccessFile(file, "r"); + try { + metadata = readMetadata(new RandomAccessStream(raFile).createReader()); + } finally { + raFile.close(); + } + } + else + { + InputStream inputStream = new FileInputStream(file); + try { + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); + } finally { + inputStream.close(); + } } new FileSystemMetadataReader().read(file, metadata); return metadata; } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) throws IOException, RiffProcessingException + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws IOException, RiffProcessingException { Metadata metadata = new Metadata(); - new RiffReader().processRiff(new StreamReader(inputStream), new AviRiffHandler(metadata)); + new RiffReader().processRiff(reader, new AviRiffHandler(metadata)); return metadata; } } diff --git a/Source/com/drew/imaging/bmp/BmpMetadataReader.java b/Source/com/drew/imaging/bmp/BmpMetadataReader.java index b2e0066a9..994b3bc35 100644 --- a/Source/com/drew/imaging/bmp/BmpMetadataReader.java +++ b/Source/com/drew/imaging/bmp/BmpMetadataReader.java @@ -21,7 +21,8 @@ package com.drew.imaging.bmp; -import com.drew.lang.StreamReader; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.bmp.BmpReader; @@ -42,7 +43,7 @@ public static Metadata readMetadata(@NotNull File file) throws IOException InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -51,10 +52,10 @@ public static Metadata readMetadata(@NotNull File file) throws IOException } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) + public static Metadata readMetadata(@NotNull ReaderInfo reader) { Metadata metadata = new Metadata(); - new BmpReader().extract(new StreamReader(inputStream), metadata); + new BmpReader().extract(reader, metadata); return metadata; } } diff --git a/Source/com/drew/imaging/eps/EpsMetadataReader.java b/Source/com/drew/imaging/eps/EpsMetadataReader.java index 71681f217..e54527c5c 100644 --- a/Source/com/drew/imaging/eps/EpsMetadataReader.java +++ b/Source/com/drew/imaging/eps/EpsMetadataReader.java @@ -1,6 +1,8 @@ package com.drew.imaging.eps; import com.drew.lang.annotations.NotNull; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.eps.EpsReader; import com.drew.metadata.file.FileSystemMetadataReader; @@ -19,19 +21,22 @@ public class EpsMetadataReader { @NotNull public static Metadata readMetadata(@NotNull File file) throws IOException { - Metadata metadata = new Metadata(); - - new EpsReader().extract(new FileInputStream(file), metadata); - + InputStream inputStream = new FileInputStream(file); + Metadata metadata; + try { + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); + } finally { + inputStream.close(); + } new FileSystemMetadataReader().read(file, metadata); return metadata; } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) throws IOException + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws IOException { Metadata metadata = new Metadata(); - new EpsReader().extract(inputStream, metadata); + new EpsReader().extract(reader, metadata); return metadata; } } diff --git a/Source/com/drew/imaging/gif/GifMetadataReader.java b/Source/com/drew/imaging/gif/GifMetadataReader.java index c684f271c..d9f1923d7 100644 --- a/Source/com/drew/imaging/gif/GifMetadataReader.java +++ b/Source/com/drew/imaging/gif/GifMetadataReader.java @@ -21,7 +21,8 @@ package com.drew.imaging.gif; -import com.drew.lang.StreamReader; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.file.FileSystemMetadataReader; @@ -29,8 +30,8 @@ import java.io.File; import java.io.FileInputStream; -import java.io.IOException; import java.io.InputStream; +import java.io.IOException; /** * Obtains metadata from GIF files. @@ -45,7 +46,7 @@ public static Metadata readMetadata(@NotNull File file) throws IOException InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -54,10 +55,10 @@ public static Metadata readMetadata(@NotNull File file) throws IOException } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws IOException { Metadata metadata = new Metadata(); - new GifReader().extract(new StreamReader(inputStream), metadata); + new GifReader().extract(reader, metadata); return metadata; } } diff --git a/Source/com/drew/imaging/heif/HeifHandler.java b/Source/com/drew/imaging/heif/HeifHandler.java index 0dd544da1..50d70dd05 100644 --- a/Source/com/drew/imaging/heif/HeifHandler.java +++ b/Source/com/drew/imaging/heif/HeifHandler.java @@ -20,7 +20,7 @@ */ package com.drew.imaging.heif; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.heif.HeifDirectory; @@ -46,11 +46,11 @@ public HeifHandler(Metadata metadata) protected abstract boolean shouldAcceptContainer(@NotNull Box box); - protected abstract HeifHandler processBox(@NotNull Box box, @NotNull byte[] payload) throws IOException; + protected abstract HeifHandler processBox(@NotNull Box box, @NotNull ReaderInfo payloadReader) throws IOException; /** * There is potential for a box to both contain other boxes and contain information, so this method will - * handle those occurences. + * handle those occurrences. */ - protected abstract void processContainer(@NotNull Box box, @NotNull SequentialReader reader) throws IOException; + protected abstract void processContainer(@NotNull Box box, @NotNull ReaderInfo reader) throws IOException; } diff --git a/Source/com/drew/imaging/heif/HeifMetadataReader.java b/Source/com/drew/imaging/heif/HeifMetadataReader.java index 3684762fd..f9b68d170 100644 --- a/Source/com/drew/imaging/heif/HeifMetadataReader.java +++ b/Source/com/drew/imaging/heif/HeifMetadataReader.java @@ -22,6 +22,7 @@ import com.drew.imaging.mp4.Mp4Reader; import com.drew.lang.annotations.NotNull; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.heif.HeifBoxHandler; import com.drew.metadata.mp4.Mp4BoxHandler; @@ -33,11 +34,11 @@ public class HeifMetadataReader { @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) throws IOException + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws IOException { try { Metadata metadata = new Metadata(); - new HeifReader().extract(metadata, inputStream, new HeifBoxHandler(metadata)); + new HeifReader().extract(metadata, reader, new HeifBoxHandler(metadata)); return metadata; } catch (DataFormatException e) { e.printStackTrace(); diff --git a/Source/com/drew/imaging/heif/HeifReader.java b/Source/com/drew/imaging/heif/HeifReader.java index 72b2c1a8b..7ac5f06b1 100644 --- a/Source/com/drew/imaging/heif/HeifReader.java +++ b/Source/com/drew/imaging/heif/HeifReader.java @@ -20,28 +20,27 @@ */ package com.drew.imaging.heif; -import com.drew.lang.StreamReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.heif.boxes.Box; import java.io.IOException; -import java.io.InputStream; import java.util.zip.DataFormatException; public class HeifReader { - public void extract(Metadata metadata, InputStream inputStream, HeifHandler handler) throws IOException, DataFormatException + public void extract(Metadata metadata, ReaderInfo reader, HeifHandler handler) throws IOException, DataFormatException { - StreamReader reader = new StreamReader(inputStream); - reader.setMotorolaByteOrder(true); + if(!reader.isMotorolaByteOrder()) + reader = reader.Clone(false); processBoxes(reader, -1, handler); } - private void processBoxes(StreamReader reader, long atomEnd, HeifHandler handler) + private void processBoxes(ReaderInfo reader, long atomEnd, HeifHandler handler) { try { - while ((atomEnd == -1) ? true : reader.getPosition() < atomEnd) { + while ((atomEnd == -1) ? true : reader.getLocalPosition() < atomEnd) { Box box = new Box(reader); @@ -50,9 +49,9 @@ private void processBoxes(StreamReader reader, long atomEnd, HeifHandler handler if (handler.shouldAcceptContainer(box)) { handler.processContainer(box, reader); - processBoxes(reader, box.size + reader.getPosition() - 8, handler); + processBoxes(reader, box.size + reader.getLocalPosition() - 8, handler); } else if (handler.shouldAcceptBox(box)) { - handler = handler.processBox(box, reader.getBytes((int)box.size - 8)); + handler = handler.processBox(box, reader.Clone((int)box.size - 8)); } else if (box.size > 1) { reader.skip(box.size - 8); } else if (box.size == -1) { diff --git a/Source/com/drew/imaging/ico/IcoMetadataReader.java b/Source/com/drew/imaging/ico/IcoMetadataReader.java index f5a295daf..ec9ba19ab 100644 --- a/Source/com/drew/imaging/ico/IcoMetadataReader.java +++ b/Source/com/drew/imaging/ico/IcoMetadataReader.java @@ -20,7 +20,8 @@ */ package com.drew.imaging.ico; -import com.drew.lang.StreamReader; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.file.FileSystemMetadataReader; @@ -41,7 +42,7 @@ public static Metadata readMetadata(@NotNull File file) throws IOException InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -50,10 +51,10 @@ public static Metadata readMetadata(@NotNull File file) throws IOException } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) + public static Metadata readMetadata(@NotNull ReaderInfo reader) { Metadata metadata = new Metadata(); - new IcoReader().extract(new StreamReader(inputStream), metadata); + new IcoReader().extract(reader, metadata); return metadata; } } diff --git a/Source/com/drew/imaging/jpeg/JpegMetadataReader.java b/Source/com/drew/imaging/jpeg/JpegMetadataReader.java index e90b2ba66..52f001bb1 100644 --- a/Source/com/drew/imaging/jpeg/JpegMetadataReader.java +++ b/Source/com/drew/imaging/jpeg/JpegMetadataReader.java @@ -20,9 +20,10 @@ */ package com.drew.imaging.jpeg; -import com.drew.lang.StreamReader; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.adobe.AdobeJpegReader; import com.drew.metadata.exif.ExifReader; @@ -43,6 +44,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.RandomAccessFile; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -69,69 +71,96 @@ public class JpegMetadataReader new JpegDhtReader(), new JpegDnlReader() ); - - @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream, @Nullable Iterable readers) throws JpegProcessingException, IOException - { - Metadata metadata = new Metadata(); - process(metadata, inputStream, readers); - return metadata; - } - + @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) throws JpegProcessingException, IOException + public static Metadata readMetadata(@NotNull File file) throws JpegProcessingException, IOException { - return readMetadata(inputStream, null); + return readMetadata(file, null); } - + @NotNull public static Metadata readMetadata(@NotNull File file, @Nullable Iterable readers) throws JpegProcessingException, IOException - { - InputStream inputStream = new FileInputStream(file); + { Metadata metadata; - try { - metadata = readMetadata(inputStream, readers); - } finally { - inputStream.close(); + if(file.isFile()) + { + RandomAccessFile raFile = new RandomAccessFile(file, "r"); + + try { + metadata = readMetadata(new RandomAccessStream(raFile).createReader()); + } finally { + raFile.close(); + } + } + else + { + InputStream inputStream = new FileInputStream(file); + + try { + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader(), readers); + } finally { + inputStream.close(); + } } + new FileSystemMetadataReader().read(file, metadata); return metadata; } + + @NotNull + public static Metadata readMetadata(@NotNull InputStream inputStream, long streamLength) throws JpegProcessingException, IOException + { + return readMetadata(inputStream, streamLength, null); + } @NotNull - public static Metadata readMetadata(@NotNull File file) throws JpegProcessingException, IOException + public static Metadata readMetadata(@NotNull InputStream inputStream, long streamLength, @Nullable Iterable readers) throws JpegProcessingException, IOException { - return readMetadata(file, null); + return readMetadata(new RandomAccessStream(inputStream, streamLength).createReader(), readers); + } + + @NotNull + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws JpegProcessingException, IOException + { + return readMetadata(reader, null); } - public static void process(@NotNull Metadata metadata, @NotNull InputStream inputStream) throws JpegProcessingException, IOException + @NotNull + public static Metadata readMetadata(@NotNull ReaderInfo reader, @Nullable Iterable readers) throws JpegProcessingException, IOException + { + Metadata metadata = new Metadata(); + process(metadata, reader, readers); + return metadata; + } + + public static void process(@NotNull Metadata metadata, @NotNull ReaderInfo reader) throws JpegProcessingException, IOException { - process(metadata, inputStream, null); + process(metadata, reader, null); } - public static void process(@NotNull Metadata metadata, @NotNull InputStream inputStream, @Nullable Iterable readers) throws JpegProcessingException, IOException + public static void process(@NotNull Metadata metadata, @NotNull ReaderInfo reader, @Nullable Iterable readers) throws JpegProcessingException, IOException { if (readers == null) readers = ALL_READERS; Set segmentTypes = new HashSet(); - for (JpegSegmentMetadataReader reader : readers) { - for (JpegSegmentType type : reader.getSegmentTypes()) { + for (JpegSegmentMetadataReader rdr : readers) { + for (JpegSegmentType type : rdr.getSegmentTypes()) { segmentTypes.add(type); } } - JpegSegmentData segmentData = JpegSegmentReader.readSegments(new StreamReader(inputStream), segmentTypes); + JpegSegmentData segmentData = JpegSegmentReader.readSegments(reader, segmentTypes); 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. + // Pass the appropriate readerinfos to each reader. for (JpegSegmentMetadataReader reader : readers) { for (JpegSegmentType segmentType : reader.getSegmentTypes()) { - reader.readJpegSegments(segmentData.getSegments(segmentType), metadata, segmentType); + reader.readJpegSegments(segmentData.getSegments(segmentType), metadata); } } } diff --git a/Source/com/drew/imaging/jpeg/JpegSegment.java b/Source/com/drew/imaging/jpeg/JpegSegment.java new file mode 100644 index 000000000..5ea97f521 --- /dev/null +++ b/Source/com/drew/imaging/jpeg/JpegSegment.java @@ -0,0 +1,85 @@ +/* + * Copyright 2018 kevinm. + * + * 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. + */ +package com.drew.imaging.jpeg; + +import com.drew.lang.annotations.NotNull; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; + +import java.io.IOException; +/** + * + * @author Kevin Mott https://github.com/kwhopper + */ +public class JpegSegment +{ + private JpegSegmentType _type; + private String _preamble; + private ReaderInfo _reader; + private byte _byteMarker; + + @NotNull + public JpegSegmentType getType() + { + return _type; + } + + @NotNull + public String getPreamble() + { + return _preamble; + } + + @NotNull + public ReaderInfo getReader() + { + return _reader; + } + + @NotNull + public byte getByteMarker() + { + return _byteMarker; + } + + public JpegSegment(JpegSegmentType type, @NotNull ReaderInfo segmentReader) + { + this(type, segmentReader, ""); + } + + public JpegSegment(JpegSegmentType type, @NotNull ReaderInfo segmentReader, @NotNull String preamble) + { + this(type, segmentReader, preamble, (byte)0x00); + } + + public JpegSegment(JpegSegmentType type, @NotNull ReaderInfo segmentReader, @NotNull String preamble, @NotNull byte byteMarker) + { + _type = type; + _reader = segmentReader; + _preamble = preamble; + _byteMarker = byteMarker; + } + + private byte[] asByteArray = null; + public void holdAsBytes() throws IOException + { + if(asByteArray == null) + { + asByteArray = getReader().toArray(); + _reader = new ReaderInfo(new RandomAccessStream(asByteArray)); + } + } +} diff --git a/Source/com/drew/imaging/jpeg/JpegSegmentData.java b/Source/com/drew/imaging/jpeg/JpegSegmentData.java index 11c367e18..da91556f2 100644 --- a/Source/com/drew/imaging/jpeg/JpegSegmentData.java +++ b/Source/com/drew/imaging/jpeg/JpegSegmentData.java @@ -41,20 +41,18 @@ */ public class JpegSegmentData { - // TODO key this on JpegSegmentType rather than Byte, and hopefully lose much of the use of 'byte' with this class @NotNull - private final HashMap> _segmentDataMap = new HashMap>(10); + private final HashMap> _segmentDataMap = new HashMap>(10); /** * Adds segment bytes to the collection. * - * @param segmentType the type of the segment being added - * @param segmentBytes the byte array holding data for the segment being added + * @param segment the specific segment being added */ @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) - public void addSegment(byte segmentType, @NotNull byte[] segmentBytes) + public void addSegment(@NotNull JpegSegment segment) { - getOrCreateSegmentList(segmentType).add(segmentBytes); + getOrCreateSegmentList(segment.getType()).add(segment); } /** @@ -64,12 +62,8 @@ public Iterable getSegmentTypes() { Set segmentTypes = new HashSet(); - for (Byte segmentTypeByte : _segmentDataMap.keySet()) + for (JpegSegmentType segmentType : _segmentDataMap.keySet()) { - JpegSegmentType segmentType = JpegSegmentType.fromByte(segmentTypeByte); - if (segmentType == null) { - throw new IllegalStateException("Should not have a segmentTypeByte that is not in the enum: " + Integer.toHexString(segmentTypeByte)); - } segmentTypes.add(segmentType); } @@ -83,23 +77,11 @@ public Iterable getSegmentTypes() * @return a byte[] containing segment data or null if no data exists for that segment */ @Nullable - public byte[] getSegment(byte segmentType) + public JpegSegment getSegment(@NotNull JpegSegmentType segmentType) { return getSegment(segmentType, 0); } - /** - * Gets the first JPEG segment data for the specified type. - * - * @param segmentType the JpegSegmentType for the desired segment - * @return a byte[] containing segment data or null if no data exists for that segment - */ - @Nullable - public byte[] getSegment(@NotNull JpegSegmentType segmentType) - { - return getSegment(segmentType.byteValue, 0); - } - /** * Gets segment data for a specific occurrence and type. Use this method when more than one occurrence * of segment data for a given type exists. @@ -109,23 +91,9 @@ public byte[] getSegment(@NotNull JpegSegmentType segmentType) * @return the segment data as a byte[], or null if no segment exists for the type & occurrence */ @Nullable - public byte[] getSegment(@NotNull JpegSegmentType segmentType, int occurrence) + public JpegSegment getSegment(@NotNull JpegSegmentType segmentType, int occurrence) { - return getSegment(segmentType.byteValue, occurrence); - } - - /** - * Gets segment data for a specific occurrence and type. Use this method when more than one occurrence - * of segment data for a given type exists. - * - * @param segmentType identifies the required segment - * @param occurrence the zero-based index of the occurrence - * @return the segment data as a byte[], or null if no segment exists for the type & occurrence - */ - @Nullable - public byte[] getSegment(byte segmentType, int occurrence) - { - final List segmentList = getSegmentList(segmentType); + final List segmentList = getSegmentList(segmentType); return segmentList != null && segmentList.size() > occurrence ? segmentList.get(occurrence) @@ -139,38 +107,26 @@ public byte[] getSegment(byte segmentType, int occurrence) * @return zero or more byte arrays, each holding the data of a JPEG segment */ @NotNull - public Iterable getSegments(@NotNull JpegSegmentType segmentType) + public Iterable getSegments(JpegSegmentType segmentType) { - return getSegments(segmentType.byteValue); - } - - /** - * Returns all instances of a given JPEG segment. If no instances exist, an empty sequence is returned. - * - * @param segmentType a number which identifies the type of JPEG segment being queried - * @return zero or more byte arrays, each holding the data of a JPEG segment - */ - @NotNull - public Iterable getSegments(byte segmentType) - { - final List segmentList = getSegmentList(segmentType); - return segmentList == null ? new ArrayList() : segmentList; + final List segmentList = getSegmentList(segmentType); + return segmentList == null ? new ArrayList() : segmentList; } @Nullable - private List getSegmentList(byte segmentType) + private List getSegmentList(JpegSegmentType segmentType) { return _segmentDataMap.get(segmentType); } @NotNull - private List getOrCreateSegmentList(byte segmentType) + private List getOrCreateSegmentList(JpegSegmentType segmentType) { - List segmentList; + List segmentList; if (_segmentDataMap.containsKey(segmentType)) { segmentList = _segmentDataMap.get(segmentType); } else { - segmentList = new ArrayList(); + segmentList = new ArrayList(); _segmentDataMap.put(segmentType, segmentList); } return segmentList; @@ -184,18 +140,7 @@ private List getOrCreateSegmentList(byte segmentType) */ public int getSegmentCount(@NotNull JpegSegmentType segmentType) { - return getSegmentCount(segmentType.byteValue); - } - - /** - * Returns the count of segment data byte arrays stored for a given segment type. - * - * @param segmentType identifies the required segment - * @return the segment count (zero if no segments exist). - */ - public int getSegmentCount(byte segmentType) - { - final List segmentList = getSegmentList(segmentType); + final List segmentList = getSegmentList(segmentType); return segmentList == null ? 0 : segmentList.size(); } @@ -209,20 +154,7 @@ public int getSegmentCount(byte segmentType) @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) public void removeSegmentOccurrence(@NotNull JpegSegmentType segmentType, int occurrence) { - removeSegmentOccurrence(segmentType.byteValue, occurrence); - } - - /** - * Removes a specified instance of a segment's data from the collection. Use this method when more than one - * occurrence of segment data exists for a given type exists. - * - * @param segmentType identifies the required segment - * @param occurrence the zero-based index of the segment occurrence to remove. - */ - @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) - public void removeSegmentOccurrence(byte segmentType, int occurrence) - { - final List segmentList = _segmentDataMap.get(segmentType); + final List segmentList = _segmentDataMap.get(segmentType); segmentList.remove(occurrence); } @@ -232,16 +164,6 @@ public void removeSegmentOccurrence(byte segmentType, int occurrence) * @param segmentType identifies the required segment */ public void removeSegment(@NotNull JpegSegmentType segmentType) - { - removeSegment(segmentType.byteValue); - } - - /** - * Removes all segments from the collection having the specified type. - * - * @param segmentType identifies the required segment - */ - public void removeSegment(byte segmentType) { _segmentDataMap.remove(segmentType); } @@ -253,17 +175,6 @@ public void removeSegment(byte segmentType) * @return true if data exists, otherwise false */ public boolean containsSegment(@NotNull JpegSegmentType segmentType) - { - return containsSegment(segmentType.byteValue); - } - - /** - * Determines whether data is present for a given segment type. - * - * @param segmentType identifies the required segment - * @return true if data exists, otherwise false - */ - public boolean containsSegment(byte segmentType) { return _segmentDataMap.containsKey(segmentType); } diff --git a/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java b/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java index e1d2aef34..9ca5c5501 100644 --- a/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java +++ b/Source/com/drew/imaging/jpeg/JpegSegmentMetadataReader.java @@ -3,6 +3,8 @@ import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; +import java.io.IOException; + /** * Defines an object that extracts metadata from in JPEG segments. */ @@ -20,7 +22,6 @@ public interface JpegSegmentMetadataReader * @param segments A sequence of byte arrays from which the metadata should be extracted. These are in the order * 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. */ - void readJpegSegments(@NotNull final Iterable segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType); + void readJpegSegments(@NotNull final Iterable segments, @NotNull final Metadata metadata) throws IOException; } diff --git a/Source/com/drew/imaging/jpeg/JpegSegmentReader.java b/Source/com/drew/imaging/jpeg/JpegSegmentReader.java index 55e6bd01b..56077a638 100644 --- a/Source/com/drew/imaging/jpeg/JpegSegmentReader.java +++ b/Source/com/drew/imaging/jpeg/JpegSegmentReader.java @@ -20,22 +20,35 @@ */ package com.drew.imaging.jpeg; -import com.drew.lang.SequentialReader; -import com.drew.lang.StreamReader; +import com.drew.lang.ByteTrie; +import com.drew.lang.Charsets; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; +import com.drew.metadata.exif.ExifReader; +import com.drew.metadata.adobe.AdobeJpegReader; +import com.drew.metadata.photoshop.DuckyReader; +import com.drew.metadata.photoshop.PhotoshopReader; +import com.drew.metadata.jfif.JfifReader; +import com.drew.metadata.jfxx.JfxxReader; +import com.drew.metadata.icc.IccReader; +import com.drew.metadata.xmp.XmpReader; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.RandomAccessFile; import java.util.HashSet; import java.util.Set; +import java.util.Collection; /** * Performs read functions of JPEG files, returning specific file segments. *

    * JPEG files are composed of a sequence of consecutive JPEG 'segments'. Each is identified by one of a set of byte - * values, modelled in the {@link JpegSegmentType} enumeration. Use readSegments to read out the some + * values, modeled in the {@link JpegSegmentType} enumeration. Use readSegments to read out the some * or all segments into a {@link JpegSegmentData} object, from which the raw JPEG segment byte arrays may be accessed. * * @author Drew Noakes https://drewnoakes.com @@ -47,15 +60,26 @@ public class JpegSegmentReader */ private static final byte SEGMENT_IDENTIFIER = (byte) 0xFF; - /** - * Private, because this segment crashes my algorithm, and searching for it doesn't work (yet). - */ - private static final byte SEGMENT_SOS = (byte) 0xDA; - - /** - * Private, because one wouldn't search for it. - */ - private static final byte MARKER_EOI = (byte) 0xD9; + private static final ByteTrie _appSegmentByPreambleBytes; + private static final HashSet _segmentMarkerBytes; + + static + { + _appSegmentByPreambleBytes = new ByteTrie(); + _appSegmentByPreambleBytes.addPath(AdobeJpegReader.JPEG_SEGMENT_ID, AdobeJpegReader.JPEG_SEGMENT_PREAMBLE.getBytes(Charsets.UTF_8)); + _appSegmentByPreambleBytes.addPath(DuckyReader.JPEG_SEGMENT_ID, DuckyReader.JPEG_SEGMENT_PREAMBLE.getBytes(Charsets.UTF_8)); + _appSegmentByPreambleBytes.addPath(ExifReader.JPEG_SEGMENT_ID, ExifReader.JPEG_SEGMENT_PREAMBLE.getBytes(Charsets.UTF_8)); + _appSegmentByPreambleBytes.addPath(IccReader.JPEG_SEGMENT_ID, IccReader.JPEG_SEGMENT_PREAMBLE.getBytes(Charsets.UTF_8)); + _appSegmentByPreambleBytes.addPath(JfifReader.JPEG_SEGMENT_ID, JfifReader.JPEG_SEGMENT_PREAMBLE.getBytes(Charsets.UTF_8)); + _appSegmentByPreambleBytes.addPath(JfxxReader.JPEG_SEGMENT_ID, JfxxReader.JPEG_SEGMENT_PREAMBLE.getBytes(Charsets.UTF_8)); + _appSegmentByPreambleBytes.addPath(PhotoshopReader.JPEG_SEGMENT_ID, PhotoshopReader.JPEG_SEGMENT_PREAMBLE.getBytes(Charsets.UTF_8)); + _appSegmentByPreambleBytes.addPath(XmpReader.JPEG_SEGMENT_ID, XmpReader.JPEG_SEGMENT_PREAMBLE.getBytes(Charsets.UTF_8)); + _appSegmentByPreambleBytes.addPath(XmpReader.JPEG_SEGMENT_EXTENSION_ID, XmpReader.JPEG_SEGMENT_PREAMBLE_EXTENSION.getBytes(Charsets.UTF_8)); + + _segmentMarkerBytes = new HashSet(); + _segmentMarkerBytes.add((byte)0x1C); + + } /** * Processes the provided JPEG data, and extracts the specified JPEG segments into a {@link JpegSegmentData} object. @@ -66,32 +90,59 @@ public class JpegSegmentReader * @param segmentTypes the set of JPEG segments types that are to be returned. If this argument is null * then all found segment types are returned. */ + @NotNull - public static JpegSegmentData readSegments(@NotNull File file, @Nullable Iterable segmentTypes) throws JpegProcessingException, IOException + public static JpegSegmentData readSegments(@NotNull File file, @Nullable Collection segmentTypes) throws JpegProcessingException, IOException { - FileInputStream stream = null; - try { - stream = new FileInputStream(file); - return readSegments(new StreamReader(stream), segmentTypes); - } finally { - if (stream != null) { - stream.close(); + return readSegments(file, segmentTypes, false); + } + + @NotNull + public static JpegSegmentData readSegments(@NotNull File file, @Nullable Collection segmentTypes, boolean holdBytes) throws JpegProcessingException, IOException + { + if(file.isFile()) + { + RandomAccessFile raFile = null; + try { + raFile = new RandomAccessFile(file, "r"); + return readSegments(new RandomAccessStream(raFile).createReader(), segmentTypes, holdBytes); + } finally { + if(raFile != null) + raFile.close(); + } + } + else + { + FileInputStream stream = null; + try { + stream = new FileInputStream(file); + return readSegments(new RandomAccessStream(stream, file.length()).createReader(), segmentTypes, holdBytes); + } finally { + if (stream != null) { + stream.close(); + } } } } + @NotNull + public static JpegSegmentData readSegments(@NotNull final ReaderInfo reader, @Nullable Collection segmentTypes) throws JpegProcessingException, IOException + { + return readSegments(reader, segmentTypes, false); + } + /** * Processes the provided JPEG data, and extracts the specified JPEG segments into a {@link JpegSegmentData} object. *

    * Will not return SOS (start of scan) or EOI (end of image) segments. * - * @param reader a {@link SequentialReader} from which the JPEG data will be read. It must be positioned at the + * @param reader a {@link ReaderInfo} from which the JPEG data will be read. It must be positioned at the * beginning of the JPEG data stream. * @param segmentTypes the set of JPEG segments types that are to be returned. If this argument is null * then all found segment types are returned. */ @NotNull - public static JpegSegmentData readSegments(@NotNull final SequentialReader reader, @Nullable Iterable segmentTypes) throws JpegProcessingException, IOException + public static JpegSegmentData readSegments(@NotNull final ReaderInfo reader, @Nullable Collection segmentTypes, boolean holdBytes) throws JpegProcessingException, IOException { // Must be big-endian assert (reader.isMotorolaByteOrder()); @@ -110,56 +161,125 @@ public static JpegSegmentData readSegments(@NotNull final SequentialReader reade } } - JpegSegmentData segmentData = new JpegSegmentData(); + JpegSegmentData segments = new JpegSegmentData(); + + boolean dobreak = false; + + while (true) + { + int padding = 0; + dobreak = false; - do { // Find the segment marker. Markers are zero or more 0xFF bytes, followed // by a 0xFF and then a byte not equal to 0x00 or 0xFF. - + if (reader.isCloserToEnd(2)) + break; byte segmentIdentifier = reader.getInt8(); - byte segmentType = reader.getInt8(); + byte segmentTypeByte = reader.getInt8(); // Read until we have a 0xFF byte followed by a byte that is not 0xFF or 0x00 - while (segmentIdentifier != SEGMENT_IDENTIFIER || segmentType == SEGMENT_IDENTIFIER || segmentType == 0) { - segmentIdentifier = segmentType; - segmentType = reader.getInt8(); + while (segmentIdentifier != SEGMENT_IDENTIFIER || segmentTypeByte == SEGMENT_IDENTIFIER || segmentTypeByte == 0) + { + padding++; + if (reader.isCloserToEnd(2)) + { + dobreak = true; + break; + } + + segmentIdentifier = segmentTypeByte; + segmentTypeByte = reader.getInt8(); } + if(dobreak) break; - if (segmentType == SEGMENT_SOS) { - // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would - // have to search for the two bytes: 0xFF 0xD9 (EOI). - // It comes last so simply return at this point - return segmentData; - } + JpegSegmentType segmentType = JpegSegmentType.fromByte(segmentTypeByte); - if (segmentType == MARKER_EOI) { - // the 'End-Of-Image' segment -- this should never be found in this fashion - return segmentData; - } + // decide whether this JPEG segment type's marker is followed by a length indicator + if (JpegSegmentType.containsPayload(segmentType)) + { + // Need two more bytes for the segment length. If closer than two bytes to the end, yield + if (reader.isCloserToEnd(2)) + break; + + // Read the 2-byte big-endian segment length + // The length includes the two bytes for the length, but not the two bytes for the marker + int segmentLength = reader.getUInt16(); + + // A length of less than two would be an error + if (segmentLength < 2) + break; + + // get position after id and type bytes (beginning of payload) + //offset += 2; + + // TODO: would you rather break here or throw an exception? + /*if (segmentLength > (reader.Length - offset + 1)) + yield break; // throw new JpegProcessingException($"Segment {segmentType} is truncated. Processing cannot proceed."); + else + {*/ + // segment length includes size bytes, so subtract two + segmentLength -= 2; + //} + + // Check whether we are interested in this segment + if (segmentTypes == null || segmentTypes.contains(segmentType)) + { + byte[] preambleBytes = new byte[Math.min(segmentLength, _appSegmentByPreambleBytes.getMaxDepth())]; + long read = reader.read(preambleBytes, 0, preambleBytes.length); + if (read != preambleBytes.length) + break; + String preamble = _appSegmentByPreambleBytes.find(preambleBytes); //?? ""; - // next 2-bytes are : [high-byte] [low-byte] - int segmentLength = reader.getUInt16(); - - // segment length includes size bytes, so subtract two - segmentLength -= 2; - - if (segmentLength < 0) - throw new JpegProcessingException("JPEG segment size would be less than zero"); - - // Check whether we are interested in this segment - if (segmentTypeBytes == null || segmentTypeBytes.contains(segmentType)) { - byte[] segmentBytes = reader.getBytes(segmentLength); - assert (segmentLength == segmentBytes.length); - segmentData.addSegment(segmentType, segmentBytes); - } else { - // Skip this segment - if (!reader.trySkip(segmentLength)) { - // If skipping failed, just return the segments we found so far - return segmentData; + // Preamble wasn't found in the list. Since preamble is used in string comparisons, set it to blank. + // (TODO: this might be a good place to record unknown preambles and report back somehow) + if (preamble == null) + preamble = ""; + + reader.skip(-read); + + byte bytemarker = 0x00; + if(preamble.length() == 0) + { + bytemarker = reader.getInt8(); + if (!_segmentMarkerBytes.contains(bytemarker)) + bytemarker = 0x00; + reader.skip(-1); + } + JpegSegment segment = new JpegSegment(segmentType, reader.Clone(segmentLength), preamble, bytemarker); + if(holdBytes) + segment.holdAsBytes(); + segments.addSegment(segment); + } + + // seek to the end of the segment + reader.skip(segmentLength); + + // Sos means entropy encoded data immediately follows, ending with Eoi or another indicator + // We already did a seek to the end of the SOS segment. A byte-by-byte scan follows to find the next indicator + if (segmentType == JpegSegmentType.SOS) + { + // yielding here makes Sos processing work the old way (ending at the first one) + break; } } + else + { + // Check whether we are interested in this non-payload segment + if (segmentTypes == null || segmentTypes.contains(segmentType)) + { + JpegSegment segment = new JpegSegment(segmentType, reader.Clone(0)); + if(holdBytes) + segment.holdAsBytes(); + segments.addSegment(segment); + } + + if (segmentType == JpegSegmentType.EOI) + break; + } - } while (true); + } + + return segments; } private JpegSegmentReader() throws Exception diff --git a/Source/com/drew/imaging/jpeg/JpegSegmentType.java b/Source/com/drew/imaging/jpeg/JpegSegmentType.java index 521e47854..d679a392b 100644 --- a/Source/com/drew/imaging/jpeg/JpegSegmentType.java +++ b/Source/com/drew/imaging/jpeg/JpegSegmentType.java @@ -38,148 +38,195 @@ */ public enum JpegSegmentType { + ///

    For temporary use in arithmetic coding. + /// No length or parameter sequence follows this marker. + TEM((byte)0x01), //, false), + /** APP0 JPEG segment identifier. Commonly contains JFIF, JFXX. */ - APP0((byte)0xE0, true), + APP0((byte)0xE0), //, true), /** APP1 JPEG segment identifier. Commonly contains Exif. XMP data is also kept in here, though usually in a second instance. */ - APP1((byte)0xE1, true), + APP1((byte)0xE1), //, true), /** APP2 JPEG segment identifier. Commonly contains ICC. */ - APP2((byte)0xE2, true), + APP2((byte)0xE2), //, true), /** APP3 JPEG segment identifier. */ - APP3((byte)0xE3, true), + APP3((byte)0xE3), //, true), /** APP4 JPEG segment identifier. */ - APP4((byte)0xE4, true), + APP4((byte)0xE4), //, true), /** APP5 JPEG segment identifier. */ - APP5((byte)0xE5, true), + APP5((byte)0xE5), //, true), /** APP6 JPEG segment identifier. */ - APP6((byte)0xE6, true), + APP6((byte)0xE6), //, true), /** APP7 JPEG segment identifier. */ - APP7((byte)0xE7, true), + APP7((byte)0xE7), //, true), /** APP8 JPEG segment identifier. */ - APP8((byte)0xE8, true), + APP8((byte)0xE8), //, true), /** APP9 JPEG segment identifier. */ - APP9((byte)0xE9, true), + APP9((byte)0xE9), //, true), /** APPA (App10) JPEG segment identifier. Can contain Unicode comments, though {@link JpegSegmentType#COM} is more commonly used for comments. */ - APPA((byte)0xEA, true), + APPA((byte)0xEA), //, true), /** APPB (App11) JPEG segment identifier. */ - APPB((byte)0xEB, true), + APPB((byte)0xEB), //, true), /** APPC (App12) JPEG segment identifier. */ - APPC((byte)0xEC, true), + APPC((byte)0xEC), //, true), /** APPD (App13) JPEG segment identifier. Commonly contains IPTC, Photoshop data. */ - APPD((byte)0xED, true), + APPD((byte)0xED), //, true), /** APPE (App14) JPEG segment identifier. Commonly contains Adobe data. */ - APPE((byte)0xEE, true), + APPE((byte)0xEE), //, true), /** APPF (App15) JPEG segment identifier. */ - APPF((byte)0xEF, true), + APPF((byte)0xEF), //, true), /** Start Of Image segment identifier. */ - SOI((byte)0xD8, false), + SOI((byte)0xD8), //, false), /** Define Quantization Table segment identifier. */ - DQT((byte)0xDB, false), + DQT((byte)0xDB), //, false), /** Define Number of Lines segment identifier. */ - DNL((byte)0xDC, false), + DNL((byte)0xDC), //, false), /** Define Restart Interval segment identifier. */ - DRI((byte)0xDD, false), + DRI((byte)0xDD), //, false), /** Define Hierarchical Progression segment identifier. */ - DHP((byte)0xDE, false), + DHP((byte)0xDE), //, false), /** EXPand reference component(s) segment identifier. */ - EXP((byte)0xDF, false), + EXP((byte)0xDF), //, false), /** Define Huffman Table segment identifier. */ - DHT((byte)0xC4, false), + DHT((byte)0xC4), //, false), /** Define Arithmetic Coding conditioning segment identifier. */ - DAC((byte)0xCC, false), + DAC((byte)0xCC), //, false), /** Start-of-Frame (0) segment identifier for Baseline DCT. */ - SOF0((byte)0xC0, true), + SOF0((byte)0xC0), //, true), /** Start-of-Frame (1) segment identifier for Extended sequential DCT. */ - SOF1((byte)0xC1, true), + SOF1((byte)0xC1), //, true), /** Start-of-Frame (2) segment identifier for Progressive DCT. */ - SOF2((byte)0xC2, true), + SOF2((byte)0xC2), //, true), /** Start-of-Frame (3) segment identifier for Lossless (sequential). */ - SOF3((byte)0xC3, true), + SOF3((byte)0xC3), //, true), // /** Start-of-Frame (4) segment identifier. */ // SOF4((byte)0xC4, true), /** Start-of-Frame (5) segment identifier for Differential sequential DCT. */ - SOF5((byte)0xC5, true), + SOF5((byte)0xC5), //, true), /** Start-of-Frame (6) segment identifier for Differential progressive DCT. */ - SOF6((byte)0xC6, true), + SOF6((byte)0xC6), //, true), /** Start-of-Frame (7) segment identifier for Differential lossless (sequential). */ - SOF7((byte)0xC7, true), + SOF7((byte)0xC7), //, true), /** Reserved for JPEG extensions. */ - JPG((byte)0xC8, true), + JPG((byte)0xC8), //, true), /** Start-of-Frame (9) segment identifier for Extended sequential DCT. */ - SOF9((byte)0xC9, true), + SOF9((byte)0xC9), //, true), /** Start-of-Frame (10) segment identifier for Progressive DCT. */ - SOF10((byte)0xCA, true), + SOF10((byte)0xCA), //, true), /** Start-of-Frame (11) segment identifier for Lossless (sequential). */ - SOF11((byte)0xCB, true), + SOF11((byte)0xCB), //, true), // /** Start-of-Frame (12) segment identifier. */ // SOF12((byte)0xCC, true), /** Start-of-Frame (13) segment identifier for Differential sequential DCT. */ - SOF13((byte)0xCD, true), + SOF13((byte)0xCD), //, true), /** Start-of-Frame (14) segment identifier for Differential progressive DCT. */ - SOF14((byte)0xCE, true), + SOF14((byte)0xCE), //, true), /** Start-of-Frame (15) segment identifier for Differential lossless (sequential). */ - SOF15((byte)0xCF, true), - + SOF15((byte)0xCF), //, true), + + /** Restart. */ + RST0((byte)0xD0), //, false), + + /** Restart. */ + RST1((byte)0xD1), //, false), + + /** Restart. */ + RST2((byte)0xD2), //, false), + + /** Restart. */ + RST3((byte)0xD3), //, false), + + /** Restart. */ + RST4((byte)0xD4), //, false), + + /** Restart. */ + RST5((byte)0xD5), //, false), + + /** Restart. */ + RST6((byte)0xD6), //, false), + + /** Restart. */ + RST7((byte)0xD7), //, false), + + /** End-of-Image. Terminates the JPEG compressed data stream that started at Soi. */ + EOI((byte)0xD9), //, false), + + /** Start-of-scan. */ + SOS((byte)0xDA), //, false), + /** JPEG comment segment identifier for comments. */ - COM((byte)0xFE, true); + COM((byte)0xFE); //, true); public static final Collection canContainMetadataTypes; static { List segmentTypes = new ArrayList(); for (JpegSegmentType segmentType : JpegSegmentType.class.getEnumConstants()) { - if (segmentType.canContainMetadata) { + if (canContainMetadata(segmentType)) { segmentTypes.add(segmentType); } } canContainMetadataTypes = segmentTypes; } + /// Gets whether this JPEG segment type might contain metadata. + /// Used to exclude large image-data-only segment from certain types of processing. + public static boolean canContainMetadata(JpegSegmentType type) + { + switch (type) + { + case SOI: + case DQT: + case DHT: + return false; + default: + return true; + } + } + public final byte byteValue; - public final boolean canContainMetadata; - JpegSegmentType(byte byteValue, boolean canContainMetadata) + JpegSegmentType(byte byteValue) { this.byteValue = byteValue; - this.canContainMetadata = canContainMetadata; } @Nullable @@ -191,4 +238,25 @@ public static JpegSegmentType fromByte(byte segmentTypeByte) } return null; } + + public static boolean containsPayload(JpegSegmentType type) + { + switch (type) + { + case SOI: + case EOI: + case RST0: + case RST1: + case RST2: + case RST3: + case RST4: + case RST5: + case RST6: + case RST7: + case TEM: + return false; + default: + return true; + } + } } diff --git a/Source/com/drew/imaging/mp3/Mp3MetadataReader.java b/Source/com/drew/imaging/mp3/Mp3MetadataReader.java index 148c04f0d..bdfec6c3e 100644 --- a/Source/com/drew/imaging/mp3/Mp3MetadataReader.java +++ b/Source/com/drew/imaging/mp3/Mp3MetadataReader.java @@ -21,6 +21,8 @@ package com.drew.imaging.mp3; import com.drew.lang.annotations.NotNull; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.file.FileSystemMetadataReader; import com.drew.metadata.mp3.Mp3Reader; @@ -43,7 +45,7 @@ public static Metadata readMetadata(@NotNull File file) throws IOException InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -52,10 +54,10 @@ public static Metadata readMetadata(@NotNull File file) throws IOException } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) + public static Metadata readMetadata(@NotNull ReaderInfo reader) { Metadata metadata = new Metadata(); - new Mp3Reader().extract(inputStream, metadata); + new Mp3Reader().extract(reader, metadata); return metadata; } } diff --git a/Source/com/drew/imaging/mp4/Mp4MetadataReader.java b/Source/com/drew/imaging/mp4/Mp4MetadataReader.java index 00c1a402a..b29335cf2 100644 --- a/Source/com/drew/imaging/mp4/Mp4MetadataReader.java +++ b/Source/com/drew/imaging/mp4/Mp4MetadataReader.java @@ -22,6 +22,8 @@ import com.drew.imaging.ImageProcessingException; import com.drew.lang.annotations.NotNull; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.file.FileSystemMetadataReader; import com.drew.metadata.mp4.Mp4BoxHandler; @@ -42,7 +44,7 @@ public static Metadata readMetadata(@NotNull final File file) throws ImageProces InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -51,10 +53,10 @@ public static Metadata readMetadata(@NotNull final File file) throws ImageProces } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) throws IOException + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws IOException { Metadata metadata = new Metadata(); - Mp4Reader.extract(inputStream, new Mp4BoxHandler(metadata)); + Mp4Reader.extract(reader, new Mp4BoxHandler(metadata)); return metadata; } } diff --git a/Source/com/drew/imaging/mp4/Mp4Reader.java b/Source/com/drew/imaging/mp4/Mp4Reader.java index 5a62ab0b3..7f4c63723 100644 --- a/Source/com/drew/imaging/mp4/Mp4Reader.java +++ b/Source/com/drew/imaging/mp4/Mp4Reader.java @@ -20,12 +20,11 @@ */ package com.drew.imaging.mp4; -import com.drew.lang.StreamReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.mp4.boxes.Box; import java.io.IOException; -import java.io.InputStream; /** * @author Payton Garland @@ -34,18 +33,21 @@ public class Mp4Reader { private Mp4Reader() {} - public static void extract(@NotNull InputStream inputStream, @NotNull Mp4Handler handler) + public static void extract(@NotNull ReaderInfo reader, @NotNull Mp4Handler handler) throws IOException { - StreamReader reader = new StreamReader(inputStream); - reader.setMotorolaByteOrder(true); + if(!reader.isMotorolaByteOrder()) + { + reader = reader.Clone(); + reader.setMotorolaByteOrder(true); + } processBoxes(reader, -1, handler); } - private static void processBoxes(StreamReader reader, long atomEnd, Mp4Handler handler) + private static void processBoxes(ReaderInfo reader, long atomEnd, Mp4Handler handler) { try { - while (atomEnd == -1 || reader.getPosition() < atomEnd) { + while (atomEnd == -1 || reader.getLocalPosition() < atomEnd) { Box box = new Box(reader); @@ -53,7 +55,7 @@ private static void processBoxes(StreamReader reader, long atomEnd, Mp4Handler h // Unknown atoms will be skipped if (handler.shouldAcceptContainer(box)) { - processBoxes(reader, box.size + reader.getPosition() - 8, handler.processContainer(box)); + processBoxes(reader, box.size + reader.getLocalPosition() - 8, handler.processContainer(box)); } else if (handler.shouldAcceptBox(box)) { handler = handler.processBox(box, reader.getBytes((int)box.size - 8)); } else if (box.usertype != null) { diff --git a/Source/com/drew/imaging/netpbm/NetpbmMetadataReader.java b/Source/com/drew/imaging/netpbm/NetpbmMetadataReader.java new file mode 100644 index 000000000..f5235f7c5 --- /dev/null +++ b/Source/com/drew/imaging/netpbm/NetpbmMetadataReader.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2017 Drew Noakes + * + * 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.netpbm; + +import com.drew.imaging.ImageProcessingException; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.annotations.NotNull; +import com.drew.lang.ReaderInfo; +import com.drew.metadata.Metadata; +import com.drew.metadata.file.FileSystemMetadataReader; +import com.drew.metadata.netpbm.NetpbmReader; +import java.io.File; +import java.io.FileInputStream; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Obtains metadata from Netpbm files. + * + * @author Drew Noakes https://drewnoakes.com + * @author Kevin Mott https://github.com/kwhopper + */ +public class NetpbmMetadataReader +{ + @NotNull + public static Metadata readMetadata(@NotNull File file) throws IOException, ImageProcessingException + { + InputStream inputStream = new FileInputStream(file); + Metadata metadata; + try { + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); + } finally { + inputStream.close(); + } + new FileSystemMetadataReader().read(file, metadata); + return metadata; + } + + @NotNull + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws IOException, ImageProcessingException + { + Metadata metadata = new Metadata(); + new NetpbmReader().extract(reader, metadata); + return metadata; + } +} diff --git a/Source/com/drew/imaging/pcx/PcxMetadataReader.java b/Source/com/drew/imaging/pcx/PcxMetadataReader.java index 15caabe79..b50b147e5 100644 --- a/Source/com/drew/imaging/pcx/PcxMetadataReader.java +++ b/Source/com/drew/imaging/pcx/PcxMetadataReader.java @@ -20,10 +20,11 @@ */ package com.drew.imaging.pcx; -import com.drew.lang.StreamReader; import com.drew.lang.annotations.NotNull; -import com.drew.metadata.Metadata; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.metadata.file.FileSystemMetadataReader; +import com.drew.metadata.Metadata; import com.drew.metadata.pcx.PcxReader; import java.io.*; @@ -41,7 +42,7 @@ public static Metadata readMetadata(@NotNull File file) throws IOException InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -50,10 +51,10 @@ public static Metadata readMetadata(@NotNull File file) throws IOException } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) + public static Metadata readMetadata(@NotNull ReaderInfo reader) { Metadata metadata = new Metadata(); - new PcxReader().extract(new StreamReader(inputStream), metadata); + new PcxReader().extract(reader, metadata); return metadata; } } diff --git a/Source/com/drew/imaging/png/PngChromaticities.java b/Source/com/drew/imaging/png/PngChromaticities.java index 094a89e2e..60489d4e8 100644 --- a/Source/com/drew/imaging/png/PngChromaticities.java +++ b/Source/com/drew/imaging/png/PngChromaticities.java @@ -20,8 +20,8 @@ */ package com.drew.imaging.png; -import com.drew.lang.SequentialByteArrayReader; import com.drew.lang.annotations.NotNull; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -39,13 +39,12 @@ public class PngChromaticities private final int _blueX; private final int _blueY; - public PngChromaticities(@NotNull byte[] bytes) throws PngProcessingException + public PngChromaticities(@NotNull ReaderInfo reader) throws PngProcessingException, IOException { - if (bytes.length != 8 * 4) { + if (reader.getLength() != 8 * 4) { throw new PngProcessingException("Invalid number of bytes"); } - SequentialByteArrayReader reader = new SequentialByteArrayReader(bytes); try { _whitePointX = reader.getInt32(); _whitePointY = reader.getInt32(); diff --git a/Source/com/drew/imaging/png/PngChunk.java b/Source/com/drew/imaging/png/PngChunk.java index cbc4bb3c9..3d1eb9a8f 100644 --- a/Source/com/drew/imaging/png/PngChunk.java +++ b/Source/com/drew/imaging/png/PngChunk.java @@ -21,6 +21,7 @@ package com.drew.imaging.png; import com.drew.lang.annotations.NotNull; +import com.drew.lang.ReaderInfo; /** * @author Drew Noakes https://drewnoakes.com @@ -30,12 +31,12 @@ public class PngChunk @NotNull private final PngChunkType _chunkType; @NotNull - private final byte[] _bytes; + private ReaderInfo _reader; - public PngChunk(@NotNull PngChunkType chunkType, @NotNull byte[] bytes) + public PngChunk(@NotNull PngChunkType chunkType, @NotNull ReaderInfo chunkReader) { _chunkType = chunkType; - _bytes = bytes; + _reader = chunkReader; } @NotNull @@ -45,8 +46,8 @@ public PngChunkType getType() } @NotNull - public byte[] getBytes() + public ReaderInfo getReader() { - return _bytes; + return _reader; } } diff --git a/Source/com/drew/imaging/png/PngChunkReader.java b/Source/com/drew/imaging/png/PngChunkReader.java index 16e25defa..5a38ae247 100644 --- a/Source/com/drew/imaging/png/PngChunkReader.java +++ b/Source/com/drew/imaging/png/PngChunkReader.java @@ -20,9 +20,9 @@ */ package com.drew.imaging.png; -import com.drew.lang.SequentialReader; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; +import com.drew.lang.ReaderInfo; import java.io.IOException; import java.util.*; @@ -34,7 +34,7 @@ public class PngChunkReader { private static final byte[] PNG_SIGNATURE_BYTES = {(byte)0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; - public Iterable extract(@NotNull final SequentialReader reader, @Nullable final Set desiredChunkTypes) throws PngProcessingException, IOException + public Iterable extract(@NotNull ReaderInfo reader, @Nullable final Set desiredChunkTypes) throws PngProcessingException, IOException { // // PNG DATA STREAM @@ -70,6 +70,7 @@ public Iterable extract(@NotNull final SequentialReader reader, @Nulla // Time information: tIME // + reader = reader.Clone(); reader.setMotorolaByteOrder(true); // network byte order if (!Arrays.equals(PNG_SIGNATURE_BYTES, reader.getBytes(PNG_SIGNATURE_BYTES.length))) { @@ -93,11 +94,12 @@ public Iterable extract(@NotNull final SequentialReader reader, @Nulla boolean willStoreChunk = desiredChunkTypes == null || desiredChunkTypes.contains(chunkType); - byte[] chunkData = reader.getBytes(chunkDataLength); + ReaderInfo chunkReader = reader.Clone(chunkDataLength); // Skip the CRC bytes at the end of the chunk // TODO consider verifying the CRC value to determine if we're processing bad data - reader.skip(4); + //reader.skip(4); + reader.skip(chunkDataLength); if (willStoreChunk && seenChunkTypes.contains(chunkType) && !chunkType.areMultipleAllowed()) { throw new PngProcessingException(String.format("Observed multiple instances of PNG chunk '%s', for which multiples are not allowed", chunkType)); @@ -114,9 +116,13 @@ public Iterable extract(@NotNull final SequentialReader reader, @Nulla } if (willStoreChunk) { - chunks.add(new PngChunk(chunkType, chunkData)); + chunks.add(new PngChunk(chunkType, chunkReader)); } + // Skip the CRC bytes at the end of the chunk + // TODO consider verifying the CRC value to determine if we're processing bad data + reader.skip(4); + seenChunkTypes.add(chunkType); } diff --git a/Source/com/drew/imaging/png/PngHeader.java b/Source/com/drew/imaging/png/PngHeader.java index 81fef4f57..97903e8bb 100644 --- a/Source/com/drew/imaging/png/PngHeader.java +++ b/Source/com/drew/imaging/png/PngHeader.java @@ -20,9 +20,8 @@ */ package com.drew.imaging.png; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; import com.drew.lang.annotations.NotNull; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -40,12 +39,11 @@ public class PngHeader private byte _filterMethod; private byte _interlaceMethod; - public PngHeader(@NotNull byte[] bytes) throws PngProcessingException + public PngHeader(@NotNull ReaderInfo reader) throws PngProcessingException, IOException { - if (bytes.length != 13) { + if (reader.getLength() != 13) { throw new PngProcessingException("PNG header chunk must have 13 data bytes"); } - SequentialReader reader = new SequentialByteArrayReader(bytes); try { _imageWidth = reader.getInt32(); _imageHeight = reader.getInt32(); diff --git a/Source/com/drew/imaging/png/PngMetadataReader.java b/Source/com/drew/imaging/png/PngMetadataReader.java index a30012b62..8c8e8fe96 100644 --- a/Source/com/drew/imaging/png/PngMetadataReader.java +++ b/Source/com/drew/imaging/png/PngMetadataReader.java @@ -83,7 +83,7 @@ public static Metadata readMetadata(@NotNull File file) throws PngProcessingExce InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -92,9 +92,9 @@ public static Metadata readMetadata(@NotNull File file) throws PngProcessingExce } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) throws PngProcessingException, IOException + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws PngProcessingException, IOException { - Iterable chunks = new PngChunkReader().extract(new StreamReader(inputStream), _desiredChunkTypes); + Iterable chunks = new PngChunkReader().extract(reader, _desiredChunkTypes); Metadata metadata = new Metadata(); @@ -112,10 +112,9 @@ public static Metadata readMetadata(@NotNull InputStream inputStream) throws Png private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk chunk) throws PngProcessingException, IOException { PngChunkType chunkType = chunk.getType(); - byte[] bytes = chunk.getBytes(); if (chunkType.equals(PngChunkType.IHDR)) { - PngHeader header = new PngHeader(bytes); + PngHeader header = new PngHeader(chunk.getReader()); PngDirectory directory = new PngDirectory(PngChunkType.IHDR); directory.setInt(PngDirectory.TAG_IMAGE_WIDTH, header.getImageWidth()); directory.setInt(PngDirectory.TAG_IMAGE_HEIGHT, header.getImageHeight()); @@ -127,19 +126,19 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.PLTE)) { PngDirectory directory = new PngDirectory(PngChunkType.PLTE); - directory.setInt(PngDirectory.TAG_PALETTE_SIZE, bytes.length / 3); + directory.setInt(PngDirectory.TAG_PALETTE_SIZE, (int)chunk.getReader().getLength() / 3); metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.tRNS)) { PngDirectory directory = new PngDirectory(PngChunkType.tRNS); directory.setInt(PngDirectory.TAG_PALETTE_HAS_TRANSPARENCY, 1); metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.sRGB)) { - int srgbRenderingIntent = bytes[0]; + int srgbRenderingIntent = chunk.getReader().getInt8(); // bytes[0]; PngDirectory directory = new PngDirectory(PngChunkType.sRGB); directory.setInt(PngDirectory.TAG_SRGB_RENDERING_INTENT, srgbRenderingIntent); metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.cHRM)) { - PngChromaticities chromaticities = new PngChromaticities(bytes); + PngChromaticities chromaticities = new PngChromaticities(chunk.getReader()); PngChromaticitiesDirectory directory = new PngChromaticitiesDirectory(); directory.setInt(PngChromaticitiesDirectory.TAG_WHITE_POINT_X, chromaticities.getWhitePointX()); directory.setInt(PngChromaticitiesDirectory.TAG_WHITE_POINT_Y, chromaticities.getWhitePointY()); @@ -151,29 +150,30 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c directory.setInt(PngChromaticitiesDirectory.TAG_BLUE_Y, chromaticities.getBlueY()); metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.gAMA)) { - int gammaInt = ByteConvert.toInt32BigEndian(bytes); - new SequentialByteArrayReader(bytes).getInt32(); + int gammaInt = ByteConvert.toInt32BigEndian(chunk.getReader().toArray()); PngDirectory directory = new PngDirectory(PngChunkType.gAMA); directory.setDouble(PngDirectory.TAG_GAMMA, gammaInt / 100000.0); metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.iCCP)) { - SequentialReader reader = new SequentialByteArrayReader(bytes); + ReaderInfo reader = chunk.getReader(); // Profile Name is 1-79 bytes, followed by the 1 byte null character - byte[] profileNameBytes = reader.getNullTerminatedBytes(79 + 1); + StringValue profileName = reader.getNullTerminatedStringValue(79 + 1, _latin1Encoding); PngDirectory directory = new PngDirectory(PngChunkType.iCCP); - directory.setStringValue(PngDirectory.TAG_ICC_PROFILE_NAME, new StringValue(profileNameBytes, _latin1Encoding)); + directory.setStringValue(PngDirectory.TAG_ICC_PROFILE_NAME, profileName); byte compressionMethod = reader.getInt8(); // Only compression method allowed by the spec is zero: deflate if (compressionMethod == 0) { // bytes left for compressed text is: // total bytes length - (profilenamebytes length + null byte + compression method byte) - int bytesLeft = bytes.length - (profileNameBytes.length + 1 + 1); + int bytesLeft = (int)reader.getLength() - (profileName.getBytes().length + 1 + 1); + byte[] compressedProfile = reader.getBytes(bytesLeft); try { InflaterInputStream inflateStream = new InflaterInputStream(new ByteArrayInputStream(compressedProfile)); - new IccReader().extract(new RandomAccessStreamReader(inflateStream), metadata, directory); + // the inflate stream is compressed so the length is unknown. Set to Integer max... + new IccReader().extract(new RandomAccessStream(inflateStream, Integer.MAX_VALUE).createReader(), metadata, directory); inflateStream.close(); } catch(java.util.zip.ZipException zex) { directory.addError(String.format("Exception decompressing PNG iCCP chunk : %s", zex.getMessage())); @@ -184,27 +184,29 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c } metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.bKGD)) { + byte[] bytes = chunk.getReader().toArray(); PngDirectory directory = new PngDirectory(PngChunkType.bKGD); directory.setByteArray(PngDirectory.TAG_BACKGROUND_COLOR, bytes); metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.tEXt)) { - SequentialReader reader = new SequentialByteArrayReader(bytes); - + ReaderInfo reader = chunk.getReader(); + // Keyword is 1-79 bytes, followed by the 1 byte null character StringValue keywordsv = reader.getNullTerminatedStringValue(79 + 1, _latin1Encoding); String keyword = keywordsv.toString(); // bytes left for text is: // total bytes length - (Keyword length + null byte) - int bytesLeft = bytes.length - (keywordsv.getBytes().length + 1); + int bytesLeft = (int)reader.getLength() - (keywordsv.getBytes().length + 1); StringValue value = reader.getNullTerminatedStringValue(bytesLeft, _latin1Encoding); + List textPairs = new ArrayList(); textPairs.add(new KeyValuePair(keyword, value)); PngDirectory directory = new PngDirectory(PngChunkType.tEXt); directory.setObject(PngDirectory.TAG_TEXTUAL_DATA, textPairs); metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.zTXt)) { - SequentialReader reader = new SequentialByteArrayReader(bytes); + ReaderInfo reader = chunk.getReader(); // Keyword is 1-79 bytes, followed by the 1 byte null character StringValue keywordsv = reader.getNullTerminatedStringValue(79 + 1, _latin1Encoding); @@ -213,11 +215,14 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c // bytes left for compressed text is: // total bytes length - (Keyword length + null byte + compression method byte) - int bytesLeft = bytes.length - (keywordsv.getBytes().length + 1 + 1); + int bytesLeft = (int)reader.getLength() - (keywordsv.getBytes().length + 1 + 1); byte[] textBytes = null; if (compressionMethod == 0) { try { - textBytes = StreamUtil.readAllBytes(new InflaterInputStream(new ByteArrayInputStream(bytes, bytes.length - bytesLeft, bytesLeft))); + byte[] bytes = chunk.getReader().getBytes((int)chunk.getReader().getLength() - bytesLeft, bytesLeft); + InflaterInputStream inflateStream = new InflaterInputStream(new ByteArrayInputStream(bytes)); + textBytes = StreamUtil.readAllBytes(inflateStream); + inflateStream.close(); } catch(java.util.zip.ZipException zex) { textBytes = null; PngDirectory directory = new PngDirectory(PngChunkType.zTXt); @@ -242,27 +247,30 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c } } } else if (chunkType.equals(PngChunkType.iTXt)) { - SequentialReader reader = new SequentialByteArrayReader(bytes); + ReaderInfo reader = chunk.getReader(); // Keyword is 1-79 bytes, followed by the 1 byte null character - StringValue keywordsv = reader.getNullTerminatedStringValue(79 + 1, _latin1Encoding); + StringValue keywordsv = reader.getNullTerminatedStringValue(79, _latin1Encoding); String keyword = keywordsv.toString(); byte compressionFlag = reader.getInt8(); byte compressionMethod = reader.getInt8(); // TODO we currently ignore languageTagBytes and translatedKeywordBytes - byte[] languageTagBytes = reader.getNullTerminatedBytes(bytes.length); - byte[] translatedKeywordBytes = reader.getNullTerminatedBytes(bytes.length); + byte[] languageTagBytes = reader.getNullTerminatedBytes((int)reader.getLength()); + byte[] translatedKeywordBytes = reader.getNullTerminatedBytes((int)reader.getLength()); // bytes left for compressed text is: // total bytes length - (Keyword length + null byte + comp flag byte + comp method byte + lang length + null byte + translated length + null byte) - int bytesLeft = bytes.length - (keywordsv.getBytes().length + 1 + 1 + 1 + languageTagBytes.length + 1 + translatedKeywordBytes.length + 1); + int bytesLeft = (int)reader.getLength() - (keywordsv.getBytes().length + 1 + 1 + 1 + languageTagBytes.length + 1 + translatedKeywordBytes.length + 1); byte[] textBytes = null; if (compressionFlag == 0) { textBytes = reader.getNullTerminatedBytes(bytesLeft); } else if (compressionFlag == 1) { if (compressionMethod == 0) { try { - textBytes = StreamUtil.readAllBytes(new InflaterInputStream(new ByteArrayInputStream(bytes, bytes.length - bytesLeft, bytesLeft))); + byte[] bytes = chunk.getReader().getBytes((int)chunk.getReader().getLength() - bytesLeft, bytesLeft); + InflaterInputStream inflateStream = new InflaterInputStream(new ByteArrayInputStream(bytes)); + textBytes = StreamUtil.readAllBytes(inflateStream); + inflateStream.close(); } catch(java.util.zip.ZipException zex) { textBytes = null; PngDirectory directory = new PngDirectory(PngChunkType.iTXt); @@ -293,7 +301,7 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c } } } else if (chunkType.equals(PngChunkType.tIME)) { - SequentialByteArrayReader reader = new SequentialByteArrayReader(bytes); + ReaderInfo reader = chunk.getReader(); int year = reader.getUInt16(); int month = reader.getUInt8(); int day = reader.getUInt8(); @@ -311,7 +319,7 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c } metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.pHYs)) { - SequentialByteArrayReader reader = new SequentialByteArrayReader(bytes); + ReaderInfo reader = chunk.getReader(); int pixelsPerUnitX = reader.getInt32(); int pixelsPerUnitY = reader.getInt32(); byte unitSpecifier = reader.getInt8(); @@ -321,6 +329,7 @@ private static void processChunk(@NotNull Metadata metadata, @NotNull PngChunk c directory.setInt(PngDirectory.TAG_UNIT_SPECIFIER, unitSpecifier); metadata.addDirectory(directory); } else if (chunkType.equals(PngChunkType.sBIT)) { + byte[] bytes = chunk.getReader().toArray(); PngDirectory directory = new PngDirectory(PngChunkType.sBIT); directory.setByteArray(PngDirectory.TAG_SIGNIFICANT_BITS, bytes); metadata.addDirectory(directory); diff --git a/Source/com/drew/imaging/psd/PsdMetadataReader.java b/Source/com/drew/imaging/psd/PsdMetadataReader.java index 8f2001ea7..8ea417e21 100644 --- a/Source/com/drew/imaging/psd/PsdMetadataReader.java +++ b/Source/com/drew/imaging/psd/PsdMetadataReader.java @@ -21,8 +21,9 @@ package com.drew.imaging.psd; -import com.drew.lang.StreamReader; import com.drew.lang.annotations.NotNull; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.file.FileSystemMetadataReader; import com.drew.metadata.photoshop.PsdReader; @@ -42,7 +43,7 @@ public static Metadata readMetadata(@NotNull File file) throws IOException InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -51,10 +52,10 @@ public static Metadata readMetadata(@NotNull File file) throws IOException } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) + public static Metadata readMetadata(@NotNull ReaderInfo reader) { Metadata metadata = new Metadata(); - new PsdReader().extract(new StreamReader(inputStream), metadata); + new PsdReader().extract(reader, metadata); return metadata; } } diff --git a/Source/com/drew/imaging/quicktime/QuickTimeMetadataReader.java b/Source/com/drew/imaging/quicktime/QuickTimeMetadataReader.java index b1fee85ce..2eb4ea513 100644 --- a/Source/com/drew/imaging/quicktime/QuickTimeMetadataReader.java +++ b/Source/com/drew/imaging/quicktime/QuickTimeMetadataReader.java @@ -22,6 +22,8 @@ import com.drew.imaging.ImageProcessingException; import com.drew.lang.annotations.NotNull; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.file.FileSystemMetadataReader; import com.drew.metadata.mov.QuickTimeAtomHandler; @@ -39,7 +41,7 @@ public static Metadata readMetadata(@NotNull final File file) throws ImageProces InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -48,10 +50,10 @@ public static Metadata readMetadata(@NotNull final File file) throws ImageProces } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) + public static Metadata readMetadata(@NotNull ReaderInfo reader) { Metadata metadata = new Metadata(); - QuickTimeReader.extract(inputStream, new QuickTimeAtomHandler(metadata)); + QuickTimeReader.extract(reader, new QuickTimeAtomHandler(metadata)); return metadata; } } diff --git a/Source/com/drew/imaging/quicktime/QuickTimeReader.java b/Source/com/drew/imaging/quicktime/QuickTimeReader.java index b011b2e8f..747bdeb93 100644 --- a/Source/com/drew/imaging/quicktime/QuickTimeReader.java +++ b/Source/com/drew/imaging/quicktime/QuickTimeReader.java @@ -20,14 +20,13 @@ */ package com.drew.imaging.quicktime; -import com.drew.lang.StreamReader; import com.drew.lang.annotations.NotNull; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.mov.QuickTimeDirectory; import com.drew.metadata.mov.atoms.Atom; import java.io.IOException; -import java.io.InputStream; /** * @author Payton Garland @@ -36,18 +35,17 @@ public class QuickTimeReader { private QuickTimeReader() {} - public static void extract(@NotNull InputStream inputStream, @NotNull QuickTimeHandler handler) + public static void extract(@NotNull ReaderInfo reader, @NotNull QuickTimeHandler handler) { - StreamReader reader = new StreamReader(inputStream); reader.setMotorolaByteOrder(true); processAtoms(reader, -1, handler); } - private static void processAtoms(StreamReader reader, long atomEnd, QuickTimeHandler handler) + private static void processAtoms(ReaderInfo reader, long atomEnd, QuickTimeHandler handler) { try { - while (atomEnd == -1 || reader.getPosition() < atomEnd) { + while (atomEnd == -1 || reader.getLocalPosition() < atomEnd) { Atom atom = new Atom(reader); @@ -55,7 +53,7 @@ private static void processAtoms(StreamReader reader, long atomEnd, QuickTimeHan // Unknown atoms will be skipped if (handler.shouldAcceptContainer(atom)) { - processAtoms(reader, atom.size + reader.getPosition() - 8, handler.processContainer(atom)); + processAtoms(reader, atom.size + reader.getLocalPosition() - 8, handler.processContainer(atom)); } else if (handler.shouldAcceptAtom(atom)) { handler = handler.processAtom(atom, reader.getBytes((int)atom.size - 8)); } else if (atom.size > 1) { diff --git a/Source/com/drew/imaging/raf/RafMetadataReader.java b/Source/com/drew/imaging/raf/RafMetadataReader.java index c3e6842b7..48c7fc68e 100644 --- a/Source/com/drew/imaging/raf/RafMetadataReader.java +++ b/Source/com/drew/imaging/raf/RafMetadataReader.java @@ -22,7 +22,9 @@ import com.drew.imaging.jpeg.JpegMetadataReader; import com.drew.imaging.jpeg.JpegProcessingException; +import com.drew.lang.RandomAccessStream; import com.drew.lang.annotations.NotNull; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.file.FileSystemMetadataReader; @@ -45,7 +47,7 @@ public static Metadata readMetadata(@NotNull File file) throws JpegProcessingExc InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -54,32 +56,25 @@ public static Metadata readMetadata(@NotNull File file) throws JpegProcessingExc } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) throws JpegProcessingException, IOException + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws JpegProcessingException, IOException { - if (!inputStream.markSupported()) - throw new IOException("Stream must support mark/reset"); - - inputStream.mark(512); - byte[] data = new byte[512]; - int bytesRead = inputStream.read(data); + int bytesRead = reader.read(data, 0, 512); if (bytesRead == -1) throw new IOException("Stream is empty"); - inputStream.reset(); + reader.skip(-bytesRead); for (int i = 0; i < bytesRead - 2; i++) { // Look for the first three bytes of a JPEG encoded file if (data[i] == (byte) 0xff && data[i + 1] == (byte) 0xd8 && data[i + 2] == (byte) 0xff) { - long bytesSkipped = inputStream.skip(i); - if (bytesSkipped != i) - throw new IOException("Skipping stream bytes failed"); + reader.skip(i); break; } } - return JpegMetadataReader.readMetadata(inputStream); + return JpegMetadataReader.readMetadata(reader); } private RafMetadataReader() throws Exception diff --git a/Source/com/drew/imaging/riff/RiffHandler.java b/Source/com/drew/imaging/riff/RiffHandler.java index 465816fd3..f788b161e 100644 --- a/Source/com/drew/imaging/riff/RiffHandler.java +++ b/Source/com/drew/imaging/riff/RiffHandler.java @@ -20,8 +20,11 @@ */ package com.drew.imaging.riff; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; +import java.io.IOException; + /** * Interface of an class capable of handling events raised during the reading of a RIFF file * via {@link RiffReader}. @@ -43,11 +46,11 @@ public interface RiffHandler /** * Gets whether this handler is interested in the specific chunk type. * Returns true if the data should be copied into an array and passed - * to {@link RiffHandler#processChunk(String, byte[])}, or false to avoid + * to {@link RiffHandler#processChunk(String, ReaderInfo)}, or false to avoid * the copy and skip to the next chunk in the file, if any. * * @param fourCC the four character code of this chunk - * @return true if {@link RiffHandler#processChunk(String, byte[])} should be called, otherwise false + * @return true if {@link RiffHandler#processChunk(String, ReaderInfo)} should be called, otherwise false */ boolean shouldAcceptChunk(@NotNull String fourCC); @@ -57,7 +60,7 @@ public interface RiffHandler * or false to avoid any unknown chunks within the list. * * @param fourCC the four character code of this chunk - * @return true if {@link RiffHandler#processChunk(String, byte[])} should be called, otherwise false + * @return true if {@link RiffHandler#processChunk(String, ReaderInfo)} should be called, otherwise false */ boolean shouldAcceptList(@NotNull String fourCC); @@ -71,5 +74,5 @@ public interface RiffHandler * @param fourCC the four character code of the chunk * @param payload they payload of the chunk as a byte array */ - void processChunk(@NotNull String fourCC, @NotNull byte[] payload); + void processChunk(@NotNull String fourCC, @NotNull ReaderInfo payload) throws IOException; } diff --git a/Source/com/drew/imaging/riff/RiffReader.java b/Source/com/drew/imaging/riff/RiffReader.java index 43ca1a4da..d2661f21b 100644 --- a/Source/com/drew/imaging/riff/RiffReader.java +++ b/Source/com/drew/imaging/riff/RiffReader.java @@ -20,7 +20,8 @@ */ package com.drew.imaging.riff; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import java.io.IOException; @@ -42,13 +43,13 @@ public class RiffReader /** * Processes a RIFF data sequence. * - * @param reader the {@link SequentialReader} from which the data should be read + * @param reader the {@link ReaderInfo} from which the data should be read * @param handler the {@link RiffHandler} that will coordinate processing and accept read values * @throws RiffProcessingException if an error occurred during the processing of RIFF data that could not be * ignored or recovered from * @throws IOException an error occurred while accessing the required data */ - public void processRiff(@NotNull final SequentialReader reader, + public void processRiff(@NotNull ReaderInfo reader, @NotNull final RiffHandler handler) throws RiffProcessingException, IOException { // RIFF files are always little-endian @@ -56,7 +57,7 @@ public void processRiff(@NotNull final SequentialReader reader, // PROCESS FILE HEADER - final String fileFourCC = reader.getString(4); + final String fileFourCC = reader.getString(4, Charsets.ASCII); if (!fileFourCC.equals("RIFF")) throw new RiffProcessingException("Invalid RIFF header: " + fileFourCC); @@ -65,7 +66,7 @@ public void processRiff(@NotNull final SequentialReader reader, final int fileSize = reader.getInt32(); int sizeLeft = fileSize; - final String identifier = reader.getString(4); + final String identifier = reader.getString(4, Charsets.ASCII); sizeLeft -= 4; if (!handler.shouldAcceptRiffIdentifier(identifier)) @@ -75,27 +76,41 @@ public void processRiff(@NotNull final SequentialReader reader, processChunks(reader, sizeLeft, handler); } - public void processChunks(SequentialReader reader, int sectionSize, RiffHandler handler) throws IOException + public void processChunks(ReaderInfo reader, int sizeLeft, RiffHandler handler) throws IOException, RiffProcessingException { - while (reader.getPosition() < sectionSize) { - String fourCC = new String(reader.getBytes(4)); - int size = reader.getInt32(); - if (fourCC.equals("LIST") || fourCC.equals("RIFF")) { + // Processing chunks. Each chunk is 8 bytes header (4 bytes CC code + 4 bytes length of chunk) + data of the chunk + while (reader.getLocalPosition() < sizeLeft) { + // Check if end of the file is closer then 8 bytes + if (reader.isCloserToEnd(8)) return; + + String chunkFourCC = new String(reader.getBytes(4)); + int chunkSize = reader.getInt32(); + + // NOTE we fail a negative chunk size here (greater than 0x7FFFFFFF) as we cannot allocate arrays larger than this + if (chunkSize < 0 || sizeLeft < chunkSize) + throw new RiffProcessingException("Invalid RIFF chunk size"); + + // Check if end of the file is closer then chunkSize bytes + if (reader.isCloserToEnd(chunkSize)) return; + + if (chunkFourCC.equals("LIST") || chunkFourCC.equals("RIFF")) { String listName = new String(reader.getBytes(4)); if (handler.shouldAcceptList(listName)) { - processChunks(reader, size - 4, handler); + processChunks(reader, sizeLeft - 4, handler); } else { - reader.skip(size - 4); + reader.skip(sizeLeft - 4); } } else { - if (handler.shouldAcceptChunk(fourCC)) { + if (handler.shouldAcceptChunk(chunkFourCC)) { // TODO is it feasible to avoid copying the chunk here, and to pass the sequential reader to the handler? - handler.processChunk(fourCC, reader.getBytes(size)); - } else { - reader.skip(size); + // Update: solved with ReaderInfo + handler.processChunk(chunkFourCC, reader.Clone(chunkSize)); } + + reader.skip(chunkSize); + // Bytes read must be even - skip one if not - if (size % 2 == 1) { + if (chunkSize % 2 == 1) { reader.skip(1); } } diff --git a/Source/com/drew/imaging/tiff/TiffHandler.java b/Source/com/drew/imaging/tiff/TiffHandler.java index fd24d06a3..1254934d4 100644 --- a/Source/com/drew/imaging/tiff/TiffHandler.java +++ b/Source/com/drew/imaging/tiff/TiffHandler.java @@ -20,7 +20,7 @@ */ package com.drew.imaging.tiff; -import com.drew.lang.RandomAccessReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.Rational; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; @@ -56,9 +56,8 @@ public interface TiffHandler Long tryCustomProcessFormat(int tagId, int formatCode, long componentCount); boolean customProcessTag(int tagOffset, - @NotNull Set processedIfdOffsets, - int tiffHeaderOffset, - @NotNull RandomAccessReader reader, + @NotNull Set processedIfdOffsets, + @NotNull ReaderInfo reader, int tagId, int byteCount) throws IOException; diff --git a/Source/com/drew/imaging/tiff/TiffMetadataReader.java b/Source/com/drew/imaging/tiff/TiffMetadataReader.java index 1c0a7a53b..6734994d5 100644 --- a/Source/com/drew/imaging/tiff/TiffMetadataReader.java +++ b/Source/com/drew/imaging/tiff/TiffMetadataReader.java @@ -20,9 +20,8 @@ */ package com.drew.imaging.tiff; -import com.drew.lang.RandomAccessFileReader; -import com.drew.lang.RandomAccessReader; -import com.drew.lang.RandomAccessStreamReader; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.exif.ExifTiffHandler; @@ -45,7 +44,7 @@ public static Metadata readMetadata(@NotNull File file) throws IOException, Tiff RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r"); Metadata metadata; try { - metadata = readMetadata(new RandomAccessFileReader(randomAccessFile)); + metadata = readMetadata(new RandomAccessStream(randomAccessFile).createReader()); } finally { randomAccessFile.close(); } @@ -54,21 +53,17 @@ public static Metadata readMetadata(@NotNull File file) throws IOException, Tiff } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) throws IOException, TiffProcessingException + public static Metadata readMetadata(@NotNull InputStream inputStream, long streamLength) throws IOException, TiffProcessingException { - // TIFF processing requires random access, as directories can be scattered throughout the byte sequence. - // InputStream does not support seeking backwards, so we wrap it with RandomAccessStreamReader, which - // buffers data from the stream as we seek forward. - - return readMetadata(new RandomAccessStreamReader(inputStream)); + return readMetadata(new RandomAccessStream(inputStream, streamLength).createReader()); } @NotNull - public static Metadata readMetadata(@NotNull RandomAccessReader reader) throws IOException, TiffProcessingException + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws IOException, TiffProcessingException { Metadata metadata = new Metadata(); ExifTiffHandler handler = new ExifTiffHandler(metadata, null); - 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 dd4aefe50..14b4c5072 100644 --- a/Source/com/drew/imaging/tiff/TiffReader.java +++ b/Source/com/drew/imaging/tiff/TiffReader.java @@ -20,7 +20,7 @@ */ package com.drew.imaging.tiff; -import com.drew.lang.RandomAccessReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.Rational; import com.drew.lang.annotations.NotNull; @@ -38,44 +38,45 @@ public class TiffReader /** * Processes a TIFF data sequence. * - * @param reader the {@link RandomAccessReader} from which the data should be read + * @param reader the {@link ReaderInfo} 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 + public void processTiff(@NotNull ReaderInfo reader, + @NotNull final TiffHandler handler) throws TiffProcessingException, IOException { // This must be either "MM" or "II". - short byteOrderIdentifier = reader.getInt16(tiffHeaderOffset); + short byteOrderIdentifier = reader.getInt16(0); if (byteOrderIdentifier == 0x4d4d) { // "MM" - reader.setMotorolaByteOrder(true); + if(!reader.isMotorolaByteOrder()) + reader = reader.Clone(false); } else if (byteOrderIdentifier == 0x4949) { // "II" - reader.setMotorolaByteOrder(false); + if(reader.isMotorolaByteOrder()) + reader = reader.Clone(false); } else { throw new TiffProcessingException("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier); } // Check the next two values for correctness. - final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset); + final int tiffMarker = reader.getUInt16(2); handler.setTiffMarker(tiffMarker); - int firstIfdOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset; + int firstIfdOffset = reader.getInt32(4); // 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 + // Update: solved with RandomAccessReader and ReaderInfo implementation 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; + firstIfdOffset = 2 + 2 + 4; } - Set processedIfdOffsets = new HashSet(); - processIfd(handler, reader, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset); + Set processedIfdOffsets = new HashSet(); + processIfd(handler, reader, processedIfdOffsets, firstIfdOffset); } /** @@ -95,27 +96,25 @@ public void processTiff(@NotNull final RandomAccessReader reader, * * * @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 reader the {@link ReaderInfo} 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 * @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 + @NotNull ReaderInfo reader, + @NotNull final Set processedIfdOffsets, + final int ifdOffset) throws IOException { - 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))) { + long globalIfdOffset = reader.getStartPosition() + ifdOffset; + if (processedIfdOffsets.contains(globalIfdOffset)) { return; } // remember that we've visited this directory so that we don't visit it again later - processedIfdOffsets.add(ifdOffset); + processedIfdOffsets.add(globalIfdOffset); if (ifdOffset >= reader.getLength() || ifdOffset < 0) { handler.error("Ignored IFD marked to start outside data segment"); @@ -130,9 +129,8 @@ public static void processIfd(@NotNull final TiffHandler handler, // 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) { - resetByteOrder = reader.isMotorolaByteOrder(); dirTagCount >>= 8; - reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder()); + reader = reader.Clone(false); } int dirLength = (2 + (12 * dirTagCount) + 4); @@ -147,7 +145,7 @@ public static void processIfd(@NotNull final TiffHandler handler, int invalidTiffFormatCodeCount = 0; for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) { final int tagOffset = calculateTagOffset(ifdOffset, tagNumber); - + // 2 bytes for the tag id final int tagId = reader.getUInt16(tagOffset); @@ -180,13 +178,12 @@ 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()) { + tagValueOffset = 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; @@ -210,14 +207,14 @@ public static void processIfd(@NotNull final TiffHandler handler, 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); + int subDirOffset = reader.getInt32((int) (tagValueOffset + i * 4)); + processIfd(handler, reader, processedIfdOffsets, subDirOffset); } } } // 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)) { // If no custom processing occurred, process the tag in the standard fashion processTag(handler, tagId, (int) tagValueOffset, (int) componentCount, formatCode, reader); } @@ -227,7 +224,6 @@ public static void processIfd(@NotNull final TiffHandler handler, final int finalTagOffset = calculateTagOffset(ifdOffset, dirTagCount); int nextIfdOffset = reader.getInt32(finalTagOffset); if (nextIfdOffset != 0) { - nextIfdOffset += tiffHeaderOffset; 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 @@ -239,13 +235,11 @@ public static void processIfd(@NotNull final TiffHandler handler, } if (handler.hasFollowerIfd()) { - processIfd(handler, reader, processedIfdOffsets, nextIfdOffset, tiffHeaderOffset); + processIfd(handler, reader, processedIfdOffsets, nextIfdOffset); } } } finally { handler.endingIFD(); - if (resetByteOrder != null) - reader.setMotorolaByteOrder(resetByteOrder); } } @@ -254,7 +248,7 @@ private static void processTag(@NotNull final TiffHandler handler, final int tagValueOffset, final int componentCount, final int formatCode, - @NotNull final RandomAccessReader reader) throws IOException + @NotNull final ReaderInfo reader) throws IOException { switch (formatCode) { case TiffDataFormat.CODE_UNDEFINED: diff --git a/Source/com/drew/imaging/wav/WavMetadataReader.java b/Source/com/drew/imaging/wav/WavMetadataReader.java index d90a68a1b..d257d2c5b 100644 --- a/Source/com/drew/imaging/wav/WavMetadataReader.java +++ b/Source/com/drew/imaging/wav/WavMetadataReader.java @@ -22,7 +22,8 @@ import com.drew.imaging.riff.RiffProcessingException; import com.drew.imaging.riff.RiffReader; -import com.drew.lang.StreamReader; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.file.FileSystemMetadataReader; @@ -46,7 +47,7 @@ public static Metadata readMetadata(@NotNull File file) throws IOException, Riff InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -55,10 +56,10 @@ public static Metadata readMetadata(@NotNull File file) throws IOException, Riff } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) throws IOException, RiffProcessingException + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws IOException, RiffProcessingException { Metadata metadata = new Metadata(); - new RiffReader().processRiff(new StreamReader(inputStream), new WavRiffHandler(metadata)); + new RiffReader().processRiff(reader, new WavRiffHandler(metadata)); return metadata; } } diff --git a/Source/com/drew/imaging/webp/WebpMetadataReader.java b/Source/com/drew/imaging/webp/WebpMetadataReader.java index 5c150ae2b..d4b443d50 100644 --- a/Source/com/drew/imaging/webp/WebpMetadataReader.java +++ b/Source/com/drew/imaging/webp/WebpMetadataReader.java @@ -22,7 +22,8 @@ import com.drew.imaging.riff.RiffProcessingException; import com.drew.imaging.riff.RiffReader; -import com.drew.lang.StreamReader; +import com.drew.lang.RandomAccessStream; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.file.FileSystemMetadataReader; @@ -43,7 +44,7 @@ public static Metadata readMetadata(@NotNull File file) throws IOException, Riff InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + metadata = readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { inputStream.close(); } @@ -52,10 +53,10 @@ public static Metadata readMetadata(@NotNull File file) throws IOException, Riff } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) throws IOException, RiffProcessingException + public static Metadata readMetadata(@NotNull ReaderInfo reader) throws IOException, RiffProcessingException { Metadata metadata = new Metadata(); - new RiffReader().processRiff(new StreamReader(inputStream), new WebpRiffHandler(metadata)); + new RiffReader().processRiff(reader, new WebpRiffHandler(metadata)); return metadata; } } diff --git a/Source/com/drew/lang/BufferBoundsException.java b/Source/com/drew/lang/BufferBoundsException.java index 6045a0ac1..44b6d6827 100644 --- a/Source/com/drew/lang/BufferBoundsException.java +++ b/Source/com/drew/lang/BufferBoundsException.java @@ -24,7 +24,7 @@ import java.io.IOException; /** - * A checked replacement for {@link IndexOutOfBoundsException}. Used by {@link RandomAccessReader}. + * A checked replacement for {@link IndexOutOfBoundsException}. Used by {@link RandomAccessStream}. * * @author Drew Noakes https://drewnoakes.com */ diff --git a/Source/com/drew/lang/ByteArrayReader.java b/Source/com/drew/lang/ByteArrayReader.java deleted file mode 100644 index f5e58f071..000000000 --- a/Source/com/drew/lang/ByteArrayReader.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2002-2017 Drew Noakes - * - * 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.lang; - -import com.drew.lang.annotations.NotNull; - -import java.io.IOException; - -/** - * Provides methods to read specific values from a byte array, with a consistent, checked exception structure for - * issues. - *

    - * By default, the reader operates with Motorola byte order (big endianness). This can be changed by calling - * setMotorolaByteOrder(boolean). - * - * @author Drew Noakes https://drewnoakes.com - * */ -public class ByteArrayReader extends RandomAccessReader -{ - @NotNull - private final byte[] _buffer; - private final int _baseOffset; - - @SuppressWarnings({ "ConstantConditions" }) - @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent") - public ByteArrayReader(@NotNull byte[] buffer) - { - this(buffer, 0); - } - - @SuppressWarnings({ "ConstantConditions" }) - @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent") - public ByteArrayReader(@NotNull byte[] buffer, int baseOffset) - { - if (buffer == null) - throw new NullPointerException(); - if (baseOffset < 0) - throw new IllegalArgumentException("Must be zero or greater"); - - _buffer = buffer; - _baseOffset = baseOffset; - } - - @Override - public int toUnshiftedOffset(int localOffset) - { - return localOffset + _baseOffset; - } - - @Override - public long getLength() - { - return _buffer.length - _baseOffset; - } - - @Override - public byte getByte(int index) throws IOException - { - validateIndex(index, 1); - return _buffer[index + _baseOffset]; - } - - @Override - protected void validateIndex(int index, int bytesRequested) throws IOException - { - if (!isValidIndex(index, bytesRequested)) - throw new BufferBoundsException(toUnshiftedOffset(index), bytesRequested, _buffer.length); - } - - @Override - protected boolean isValidIndex(int index, int bytesRequested) throws IOException - { - return bytesRequested >= 0 - && index >= 0 - && (long)index + (long)bytesRequested - 1L < getLength(); - } - - @Override - @NotNull - public byte[] getBytes(int index, int count) throws IOException - { - validateIndex(index, count); - - byte[] bytes = new byte[count]; - System.arraycopy(_buffer, index + _baseOffset, bytes, 0, count); - return bytes; - } -} diff --git a/Source/com/drew/lang/IterableWordReader.java b/Source/com/drew/lang/IterableWordReader.java new file mode 100644 index 000000000..e6c6bdfc0 --- /dev/null +++ b/Source/com/drew/lang/IterableWordReader.java @@ -0,0 +1,106 @@ +/* + * Copyright 2002-2017 Drew Noakes + * + * 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.lang; + +import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * Reads lines and words from a reader sequentially + * + * @author Kevin Mott https://github.com/kwhopper + * @author Drew Noakes https://drewnoakes.com + */ +public class IterableWordReader implements Iterable, Iterator +{ + private final ReaderInfo _reader; + private final LinkedList _stringLinkedList = new LinkedList(); + + public IterableWordReader(ReaderInfo reader) + { + _reader = reader; + } + + // the next three methods implement Iterator + @Override + public boolean hasNext() + { + if(_stringLinkedList.isEmpty()) + { + while(true) + { + String line = null; + try + { + line = _reader.readLine(); + } + catch(IOException ignore) + { + return false; + } + + if (line != null) + { + int commentFromIndex = line.indexOf('#'); + if (commentFromIndex != -1) + line = line.substring(0, commentFromIndex); + + String[] words = line.split("\\s+"); + for (String word : words) + { + String wordtrim = word.trim(); + if(wordtrim.length() > 0) + _stringLinkedList.add(wordtrim); + } + } + + // it's possible for line to be a zero-length string instead + // of null. In that case, keep reading lines until the list + // already contains a string or finally get a null line + if(line == null || !_stringLinkedList.isEmpty()) + break; + } + } + + return !_stringLinkedList.isEmpty(); + } + + @Override + public String next() + { + if(hasNext()) + return _stringLinkedList.pop(); + return ""; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + + // this method implements Iterable + @Override + public Iterator iterator() { + return this; + } +} diff --git a/Source/com/drew/lang/RandomAccessFileReader.java b/Source/com/drew/lang/RandomAccessFileReader.java deleted file mode 100644 index 5f23fbe13..000000000 --- a/Source/com/drew/lang/RandomAccessFileReader.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2002-2017 Drew Noakes - * - * 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.lang; - -import com.drew.lang.annotations.NotNull; - -import java.io.IOException; -import java.io.RandomAccessFile; - -/** - * Provides methods to read specific values from a {@link RandomAccessFile}, with a consistent, checked exception structure for - * issues. - * - * @author Drew Noakes https://drewnoakes.com - * */ -public class RandomAccessFileReader extends RandomAccessReader -{ - @NotNull - private final RandomAccessFile _file; - private final long _length; - private int _currentIndex; - - private final int _baseOffset; - - @SuppressWarnings({ "ConstantConditions" }) - @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent") - public RandomAccessFileReader(@NotNull RandomAccessFile file) throws IOException - { - this(file, 0); - } - - @SuppressWarnings({ "ConstantConditions" }) - @com.drew.lang.annotations.SuppressWarnings(value = "EI_EXPOSE_REP2", justification = "Design intent") - public RandomAccessFileReader(@NotNull RandomAccessFile file, int baseOffset) throws IOException - { - if (file == null) - throw new NullPointerException(); - - _file = file; - _baseOffset = baseOffset; - _length = _file.length(); - } - - @Override - public int toUnshiftedOffset(int localOffset) - { - return localOffset + _baseOffset; - } - - @Override - public long getLength() - { - return _length; - } - - @Override - public byte getByte(int index) throws IOException - { - if (index != _currentIndex) - seek(index); - - final int b = _file.read(); - if (b < 0) - throw new BufferBoundsException("Unexpected end of file encountered."); - assert (b <= 0xff); - _currentIndex++; - return (byte)b; - } - - @Override - @NotNull - public byte[] getBytes(int index, int count) throws IOException - { - validateIndex(index, count); - - if (index != _currentIndex) - seek(index); - - byte[] bytes = new byte[count]; - final int bytesRead = _file.read(bytes); - _currentIndex += bytesRead; - if (bytesRead != count) - throw new BufferBoundsException("Unexpected end of file encountered."); - return bytes; - } - - private void seek(final int index) throws IOException - { - if (index == _currentIndex) - return; - - _file.seek(index); - _currentIndex = index; - } - - @Override - protected boolean isValidIndex(int index, int bytesRequested) throws IOException - { - return bytesRequested >= 0 - && index >= 0 - && (long)index + (long)bytesRequested - 1L < _length; - } - - @Override - protected void validateIndex(final int index, final int bytesRequested) throws IOException - { - if (!isValidIndex(index, bytesRequested)) - throw new BufferBoundsException(index, bytesRequested, _length); - } -} diff --git a/Source/com/drew/lang/RandomAccessReader.java b/Source/com/drew/lang/RandomAccessReader.java deleted file mode 100644 index 7e5987a42..000000000 --- a/Source/com/drew/lang/RandomAccessReader.java +++ /dev/null @@ -1,445 +0,0 @@ -/* - * Copyright 2002-2017 Drew Noakes - * - * 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.lang; - -import com.drew.lang.annotations.NotNull; -import com.drew.lang.annotations.Nullable; -import com.drew.metadata.StringValue; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; - -/** - * Base class for random access data reading operations of common data types. - *

    - * By default, the reader operates with Motorola byte order (big endianness). This can be changed by calling - * {@link com.drew.lang.RandomAccessReader#setMotorolaByteOrder(boolean)}. - *

    - * Concrete implementations include: - *

      - *
    • {@link ByteArrayReader}
    • - *
    • {@link RandomAccessStreamReader}
    • - *
    - * - * @author Drew Noakes https://drewnoakes.com - */ -public abstract class RandomAccessReader -{ - private boolean _isMotorolaByteOrder = true; - - public abstract int toUnshiftedOffset(int localOffset); - - /** - * Gets the byte value at the specified byte index. - *

    - * Implementations should not perform any bounds checking in this method. That should be performed - * in validateIndex and isValidIndex. - * - * @param index The index from which to read the byte - * @return The read byte value - * @throws IllegalArgumentException index is negative - * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source - * @throws IOException if the byte is unable to be read - */ - public abstract byte getByte(int index) throws IOException; - - /** - * Returns the required number of bytes from the specified index from the underlying source. - * - * @param index The index from which the bytes begins in the underlying source - * @param count The number of bytes to be returned - * @return The requested bytes - * @throws IllegalArgumentException index or count are negative - * @throws BufferBoundsException if the requested bytes extend beyond the end of the underlying data source - * @throws IOException if the byte is unable to be read - */ - @NotNull - public abstract byte[] getBytes(int index, int count) throws IOException; - - /** - * Ensures that the buffered bytes extend to cover the specified index. If not, an attempt is made - * to read to that point. - *

    - * If the stream ends before the point is reached, a {@link BufferBoundsException} is raised. - * - * @param index the index from which the required bytes start - * @param bytesRequested the number of bytes which are required - * @throws IOException if the stream ends before the required number of bytes are acquired - */ - protected abstract void validateIndex(int index, int bytesRequested) throws IOException; - - protected abstract boolean isValidIndex(int index, int bytesRequested) throws IOException; - - /** - * Returns the length of the data source in bytes. - *

    - * This is a simple operation for implementations (such as {@link RandomAccessFileReader} and - * {@link ByteArrayReader}) that have the entire data source available. - *

    - * Users of this method must be aware that sequentially accessed implementations such as - * {@link RandomAccessStreamReader} will have to read and buffer the entire data source in - * order to determine the length. - * - * @return the length of the data source, in bytes. - */ - public abstract long getLength() throws IOException; - - /** - * Sets the endianness of this reader. - *

      - *
    • true for Motorola (or big) endianness (also known as network byte order), with MSB before LSB.
    • - *
    • false for Intel (or little) endianness, with LSB before MSB.
    • - *
    - * - * @param motorolaByteOrder true for Motorola/big endian, false for Intel/little endian - */ - public void setMotorolaByteOrder(boolean motorolaByteOrder) - { - _isMotorolaByteOrder = motorolaByteOrder; - } - - /** - * Gets the endianness of this reader. - *
      - *
    • true for Motorola (or big) endianness (also known as network byte order), with MSB before LSB.
    • - *
    • false for Intel (or little) endianness, with LSB before MSB.
    • - *
    - */ - public boolean isMotorolaByteOrder() - { - return _isMotorolaByteOrder; - } - - /** - * Gets whether a bit at a specific index is set or not. - * - * @param index the number of bits at which to test - * @return true if the bit is set, otherwise false - * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative - */ - public boolean getBit(int index) throws IOException - { - int byteIndex = index / 8; - int bitIndex = index % 8; - - validateIndex(byteIndex, 1); - - byte b = getByte(byteIndex); - return ((b >> bitIndex) & 1) == 1; - } - - /** - * Returns an unsigned 8-bit int calculated from one byte of data at the specified index. - * - * @param index position within the data buffer to read byte - * @return the 8 bit int value, between 0 and 255 - * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative - */ - public short getUInt8(int index) throws IOException - { - validateIndex(index, 1); - - return (short) (getByte(index) & 0xFF); - } - - /** - * Returns a signed 8-bit int calculated from one byte of data at the specified index. - * - * @param index position within the data buffer to read byte - * @return the 8 bit int value, between 0x00 and 0xFF - * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative - */ - public byte getInt8(int index) throws IOException - { - validateIndex(index, 1); - - return getByte(index); - } - - /** - * Returns an unsigned 16-bit int calculated from two bytes of data at the specified index. - * - * @param index position within the data buffer to read first byte - * @return the 16 bit int value, between 0x0000 and 0xFFFF - * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative - */ - public int getUInt16(int index) throws IOException - { - validateIndex(index, 2); - - if (_isMotorolaByteOrder) { - // Motorola - MSB first - return (getByte(index ) << 8 & 0xFF00) | - (getByte(index + 1) & 0xFF); - } else { - // Intel ordering - LSB first - return (getByte(index + 1) << 8 & 0xFF00) | - (getByte(index ) & 0xFF); - } - } - - /** - * Returns a signed 16-bit int calculated from two bytes of data at the specified index (MSB, LSB). - * - * @param index position within the data buffer to read first byte - * @return the 16 bit int value, between 0x0000 and 0xFFFF - * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative - */ - public short getInt16(int index) throws IOException - { - validateIndex(index, 2); - - if (_isMotorolaByteOrder) { - // Motorola - MSB first - return (short) (((short)getByte(index ) << 8 & (short)0xFF00) | - ((short)getByte(index + 1) & (short)0xFF)); - } else { - // Intel ordering - LSB first - return (short) (((short)getByte(index + 1) << 8 & (short)0xFF00) | - ((short)getByte(index ) & (short)0xFF)); - } - } - - /** - * Get a 24-bit unsigned integer from the buffer, returning it as an int. - * - * @param index position within the data buffer to read first byte - * @return the unsigned 24-bit int value as a long, between 0x00000000 and 0x00FFFFFF - * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative - */ - public int getInt24(int index) throws IOException - { - validateIndex(index, 3); - - if (_isMotorolaByteOrder) { - // Motorola - MSB first (big endian) - return (((int)getByte(index )) << 16 & 0xFF0000) | - (((int)getByte(index + 1)) << 8 & 0xFF00) | - (((int)getByte(index + 2)) & 0xFF); - } else { - // Intel ordering - LSB first (little endian) - return (((int)getByte(index + 2)) << 16 & 0xFF0000) | - (((int)getByte(index + 1)) << 8 & 0xFF00) | - (((int)getByte(index )) & 0xFF); - } - } - - /** - * Get a 32-bit unsigned integer from the buffer, returning it as a long. - * - * @param index position within the data buffer to read first byte - * @return the unsigned 32-bit int value as a long, between 0x00000000 and 0xFFFFFFFF - * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative - */ - public long getUInt32(int index) throws IOException - { - validateIndex(index, 4); - - if (_isMotorolaByteOrder) { - // Motorola - MSB first (big endian) - return (((long)getByte(index )) << 24 & 0xFF000000L) | - (((long)getByte(index + 1)) << 16 & 0xFF0000L) | - (((long)getByte(index + 2)) << 8 & 0xFF00L) | - (((long)getByte(index + 3)) & 0xFFL); - } else { - // Intel ordering - LSB first (little endian) - return (((long)getByte(index + 3)) << 24 & 0xFF000000L) | - (((long)getByte(index + 2)) << 16 & 0xFF0000L) | - (((long)getByte(index + 1)) << 8 & 0xFF00L) | - (((long)getByte(index )) & 0xFFL); - } - } - - /** - * Returns a signed 32-bit integer from four bytes of data at the specified index the buffer. - * - * @param index position within the data buffer to read first byte - * @return the signed 32 bit int value, between 0x00000000 and 0xFFFFFFFF - * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative - */ - public int getInt32(int index) throws IOException - { - validateIndex(index, 4); - - if (_isMotorolaByteOrder) { - // Motorola - MSB first (big endian) - return (getByte(index ) << 24 & 0xFF000000) | - (getByte(index + 1) << 16 & 0xFF0000) | - (getByte(index + 2) << 8 & 0xFF00) | - (getByte(index + 3) & 0xFF); - } else { - // Intel ordering - LSB first (little endian) - return (getByte(index + 3) << 24 & 0xFF000000) | - (getByte(index + 2) << 16 & 0xFF0000) | - (getByte(index + 1) << 8 & 0xFF00) | - (getByte(index ) & 0xFF); - } - } - - /** - * Get a signed 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 getInt64(int index) throws IOException - { - validateIndex(index, 8); - - if (_isMotorolaByteOrder) { - // Motorola - MSB first - return ((long)getByte(index ) << 56 & 0xFF00000000000000L) | - ((long)getByte(index + 1) << 48 & 0xFF000000000000L) | - ((long)getByte(index + 2) << 40 & 0xFF0000000000L) | - ((long)getByte(index + 3) << 32 & 0xFF00000000L) | - ((long)getByte(index + 4) << 24 & 0xFF000000L) | - ((long)getByte(index + 5) << 16 & 0xFF0000L) | - ((long)getByte(index + 6) << 8 & 0xFF00L) | - ((long)getByte(index + 7) & 0xFFL); - } else { - // Intel ordering - LSB first - return ((long)getByte(index + 7) << 56 & 0xFF00000000000000L) | - ((long)getByte(index + 6) << 48 & 0xFF000000000000L) | - ((long)getByte(index + 5) << 40 & 0xFF0000000000L) | - ((long)getByte(index + 4) << 32 & 0xFF00000000L) | - ((long)getByte(index + 3) << 24 & 0xFF000000L) | - ((long)getByte(index + 2) << 16 & 0xFF0000L) | - ((long)getByte(index + 1) << 8 & 0xFF00L) | - ((long)getByte(index ) & 0xFFL); - } - } - - /** - * Gets a s15.16 fixed point float from the buffer. - *

    - * This particular fixed point encoding has one sign bit, 15 numerator bits and 16 denominator bits. - * - * @return the floating point value - * @throws IOException the buffer does not contain enough bytes to service the request, or index is negative - */ - public float getS15Fixed16(int index) throws IOException - { - validateIndex(index, 4); - - if (_isMotorolaByteOrder) { - float res = (getByte(index ) & 0xFF) << 8 | - (getByte(index + 1) & 0xFF); - int d = (getByte(index + 2) & 0xFF) << 8 | - (getByte(index + 3) & 0xFF); - return (float)(res + d/65536.0); - } else { - // this particular branch is untested - float res = (getByte(index + 3) & 0xFF) << 8 | - (getByte(index + 2) & 0xFF); - int d = (getByte(index + 1) & 0xFF) << 8 | - (getByte(index ) & 0xFF); - return (float)(res + d/65536.0); - } - } - - public float getFloat32(int index) throws IOException - { - return Float.intBitsToFloat(getInt32(index)); - } - - public double getDouble64(int index) throws IOException - { - return Double.longBitsToDouble(getInt64(index)); - } - - @NotNull - public StringValue getStringValue(int index, int bytesRequested, @Nullable Charset charset) throws IOException - { - return new StringValue(getBytes(index, bytesRequested), charset); - } - - @NotNull - public String getString(int index, int bytesRequested, @NotNull Charset charset) throws IOException - { - return new String(getBytes(index, bytesRequested), charset.name()); - } - - @NotNull - public String getString(int index, int bytesRequested, @NotNull String charset) throws IOException - { - byte[] bytes = getBytes(index, bytesRequested); - try { - return new String(bytes, charset); - } catch (UnsupportedEncodingException e) { - return new String(bytes); - } - } - - /** - * Creates a String from the _data buffer starting at the specified index, - * and ending where byte=='\0' or where length==maxLength. - * - * @param index The index within the buffer at which to start reading the string. - * @param maxLengthBytes The maximum number of bytes to read. If a zero-byte is not reached within this limit, - * reading will stop and the string will be truncated to this length. - * @return The read string. - * @throws IOException The buffer does not contain enough bytes to satisfy this request. - */ - @NotNull - public String getNullTerminatedString(int index, int maxLengthBytes, @NotNull Charset charset) throws IOException - { - return new String(getNullTerminatedBytes(index, maxLengthBytes), charset.name()); - } - - @NotNull - public StringValue getNullTerminatedStringValue(int index, int maxLengthBytes, @Nullable Charset charset) throws IOException - { - byte[] bytes = getNullTerminatedBytes(index, maxLengthBytes); - - return new StringValue(bytes, charset); - } - - /** - * Returns the sequence of bytes punctuated by a \0 value. - * - * @param index The index within the buffer at which to start reading the string. - * @param maxLengthBytes The maximum number of bytes to read. If a \0 byte is not reached within this limit, - * the returned array will be maxLengthBytes long. - * @return The read byte array, excluding the null terminator. - * @throws IOException The buffer does not contain enough bytes to satisfy this request. - */ - @NotNull - public byte[] getNullTerminatedBytes(int index, int maxLengthBytes) throws IOException - { - byte[] buffer = getBytes(index, maxLengthBytes); - - // Count the number of non-null bytes - int length = 0; - while (length < buffer.length && buffer[length] != 0) - length++; - - if (length == maxLengthBytes) - return buffer; - - byte[] bytes = new byte[length]; - if (length > 0) - System.arraycopy(buffer, 0, bytes, 0, length); - return bytes; - } -} diff --git a/Source/com/drew/lang/RandomAccessStream.java b/Source/com/drew/lang/RandomAccessStream.java new file mode 100644 index 000000000..580b2de16 --- /dev/null +++ b/Source/com/drew/lang/RandomAccessStream.java @@ -0,0 +1,629 @@ +/* + * Copyright 2002-2017 Drew Noakes + * Copyright 2018 Kevin Mott + * + * 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.lang; + +import com.drew.lang.annotations.NotNull; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.Collections; +import java.util.HashMap; +/** + * + * @author Kevin Mott https://github.com/kwhopper + * @author Drew Noakes https://drewnoakes.com + */ +public class RandomAccessStream +{ + private final RandomAccessFile _raFile; + private final InputStream _inputStream; + + private long _streamLength = -1; + + private boolean _canSeek = false; + private boolean _isStreamFinished; + + public final static int DEFAULT_CHUNK_LENGTH = 2 * 1024; + private final int _chunkLength; + + @NotNull + public final HashMap _chunks = new HashMap(); + + private long _totalBytesRead = 0; + + + public RandomAccessStream(@NotNull RandomAccessFile raFile) throws IOException + { + if (raFile == null) + throw new NullPointerException(); + + _raFile = raFile; + _canSeek = true; + _chunkLength = DEFAULT_CHUNK_LENGTH; + + _streamLength = raFile.length(); + + _inputStream = null; + } + + public RandomAccessStream(@NotNull InputStream stream, long streamLength) + { + if (stream == null) + throw new NullPointerException(); + if (streamLength <= 0L) + throw new IllegalArgumentException("streamLength must be greater than zero"); + + _inputStream = stream; + _canSeek = false; + _streamLength = streamLength; + + _chunkLength = DEFAULT_CHUNK_LENGTH; + + _raFile = null; + } + + public RandomAccessStream(@NotNull byte[] bytes) + { + if (bytes == null) + throw new NullPointerException(); + + _canSeek = true; + + _chunks.put(0L, bytes); + _chunkLength = bytes.length; + + _streamLength = bytes.length; + _isStreamFinished = true; + + _raFile = null; + _inputStream = null; + } + + public boolean canSeek() + { + return _canSeek; + } + + public long getLength() + { + return _streamLength; + } + + public ReaderInfo createReader() + { + return createReader(-1L, -1L, true); + } + public ReaderInfo createReader(boolean isMotorolaByteOrder) + { + return createReader(-1L, -1L, isMotorolaByteOrder); + } + public ReaderInfo createReader(long startPosition, long length, boolean isMotorolaByteOrder) + { + long pos = startPosition >= 0 ? startPosition : 0; + + //var rdrInfo = new ReaderInfo(this, pos, 0, length, isMotorolaByteOrder); + //rdrList.Add(rdrInfo); + //return rdrInfo; + return new ReaderInfo(this, pos, 0L, length, isMotorolaByteOrder); + } + + /** + * Retrieves bytes, writing them into a caller-provided buffer. + * SequentialFlag as index indicates this call should read sequentially + * + * @param index position within the data buffer to read bytes + * @param buffer array to write bytes to + * @param offset starting position with buffer to write to + * @param count The number of bytes to be written + * @param isSequential flag indicating if caller is using sequential access + * @return The requested bytes + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int read(long index, byte[] buffer, int offset, int count, boolean isSequential) throws IOException, EOFException, BufferBoundsException + { + return read(index, buffer, offset, count, isSequential, true); + } + + /** + * Retrieves bytes, writing them into a caller-provided buffer. + * SequentialFlag as index indicates this call should read sequentially + * + * @param index position within the data buffer to read bytes + * @param buffer array to write bytes to + * @param offset starting position with buffer to write to + * @param count The number of bytes to be written + * @param isSequential flag indicating if caller is using sequential access + * @param allowPartial flag indicating if fewer than count bytes can be returned + * @return The requested bytes + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int read(long index, byte[] buffer, int offset, int count, boolean isSequential, boolean allowPartial) throws IOException, EOFException, BufferBoundsException + { + count = (int)validateIndex(index, count, isSequential, allowPartial); + + // This bypasses a lot of checks particularly when the input was a byte[] + if (_isStreamFinished && _chunks.size() == 1) + { + System.arraycopy(_chunks.get(0L), (int)index, buffer, 0, count); + return count; + } + + int remaining = count; // how many bytes are requested + int fromOffset = (int)index; + int toIndex = offset > 0 ? offset : 0; + while (remaining != 0) + { + int fromChunkIndex = fromOffset / _chunkLength; // chunk integer key + int fromInnerIndex = fromOffset % _chunkLength; // index inside the chunk to start reading + int length = Math.min(remaining, _chunkLength - fromInnerIndex); + byte[] chunk = _chunks.get((long)fromChunkIndex); + System.arraycopy(chunk, fromInnerIndex, buffer, toIndex, length); + remaining -= length; + fromOffset += length; + toIndex += length; + } + + return toIndex - offset; + } + + /** + * Gets the byte value at the specified byte index. + *

    + * Implementations should not perform any bounds checking in this method. That should be performed + * in validateIndex and bytesAvailable. + * + * @param index The index from which to read the byte + * @param isSequential flag indicating if caller is using sequential access + * @return The read byte value + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public byte getByte(long index, boolean isSequential) throws IOException, EOFException, BufferBoundsException + { + validateIndex(index, 1, isSequential); + + // This bypasses a lot of checks particularly when the input was a byte[] + if (_isStreamFinished && _chunks.size() == 1) + return _chunks.get(0L)[(int)index]; + + long chunkIndex = index / _chunkLength; + long innerIndex = index % _chunkLength; + + if (_chunks.containsKey(chunkIndex)) + return (byte)_chunks.get(chunkIndex)[(int)innerIndex]; + else + return (byte)255; // unchecked((byte)-1); + } + + /** + * Returns an unsigned 16-bit int calculated from two bytes of data at the specified index. + * + * @param index position within the data buffer to read first byte + * @param isMotorolaByteOrder true for Motorola/big endian, false for Intel/little endian + * @param isSequential flag indicating if caller is using sequential access + * @return the 16 bit int value, between 0x0000 and 0xFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int getUInt16(long index, boolean isMotorolaByteOrder, boolean isSequential) throws IOException, EOFException, BufferBoundsException + { + byte[] bytes = new byte[2]; + read(index, bytes, 0, bytes.length, isSequential, false); + + if (isMotorolaByteOrder) { + // Motorola - MSB first + return (bytes[0] << 8 & 0xFF00) | + (bytes[1] & 0xFF); + } else { + // Intel ordering - LSB first + return (bytes[1] << 8 & 0xFF00) | + (bytes[0] & 0xFF); + } + } + + /** + * Returns a signed 16-bit int calculated from two bytes of data at the specified index. + * + * @param index position within the data buffer to read first byte + * @param isMotorolaByteOrder true for Motorola/big endian, false for Intel/little endian + * @param isSequential flag indicating if caller is using sequential access + * @return the 16 bit int value, between 0x0000 and 0xFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public short getInt16(long index, boolean isMotorolaByteOrder, boolean isSequential) throws IOException, EOFException, BufferBoundsException + { + byte[] bytes = new byte[2]; + read(index, bytes, 0, bytes.length, isSequential, false); + + if (isMotorolaByteOrder) { + // Motorola - MSB first + return (short) (((short)bytes[0] << 8 & (short)0xFF00) | + ((short)bytes[1] & (short)0xFF)); + } else { + // Intel ordering - LSB first + return (short) (((short)bytes[1] << 8 & (short)0xFF00) | + ((short)bytes[0] & (short)0xFF)); + } + } + + /** + * Get a 24-bit unsigned integer from the buffer, returning it as an int. + * + * @param index position within the data buffer to read first byte + * @param isMotorolaByteOrder true for Motorola/big endian, false for Intel/little endian + * @param isSequential flag indicating if caller is using sequential access + * @return the unsigned 24-bit int value as a long, between 0x00000000 and 0x00FFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int getInt24(long index, boolean isMotorolaByteOrder, boolean isSequential) throws IOException, EOFException, BufferBoundsException + { + byte[] bytes = new byte[3]; + read(index, bytes, 0, bytes.length, isSequential, false); + + if (isMotorolaByteOrder) { + // Motorola - MSB first (big endian) + return (((int)bytes[0]) << 16 & 0xFF0000) | + (((int)bytes[1]) << 8 & 0xFF00) | + (((int)bytes[2]) & 0xFF); + } else { + // Intel ordering - LSB first (little endian) + return (((int)bytes[2]) << 16 & 0xFF0000) | + (((int)bytes[1]) << 8 & 0xFF00) | + (((int)bytes[0]) & 0xFF); + } + } + + /** + * Get a 32-bit unsigned integer from the buffer, returning it as a long. + * + * @param index position within the data buffer to read first byte + * @param isMotorolaByteOrder true for Motorola/big endian, false for Intel/little endian + * @param isSequential flag indicating if caller is using sequential access + * @return the unsigned 32-bit int value as a long, between 0x00000000 and 0xFFFFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public long getUInt32(long index, boolean isMotorolaByteOrder, boolean isSequential) throws IOException, EOFException, BufferBoundsException + { + byte[] bytes = new byte[4]; + read(index, bytes, 0, bytes.length, isSequential, false); + + if (isMotorolaByteOrder) { + // Motorola - MSB first (big endian) + return (((long)bytes[0]) << 24 & 0xFF000000L) | + (((long)bytes[1]) << 16 & 0xFF0000L) | + (((long)bytes[2]) << 8 & 0xFF00L) | + (((long)bytes[3]) & 0xFFL); + } else { + // Intel ordering - LSB first (little endian) + return (((long)bytes[3]) << 24 & 0xFF000000L) | + (((long)bytes[2]) << 16 & 0xFF0000L) | + (((long)bytes[1]) << 8 & 0xFF00L) | + (((long)bytes[0]) & 0xFFL); + } + } + + /** + * Get a 32-bit signed integer from the buffer, returning it as a long. + * + * @param index position within the data buffer to read first byte + * @param isMotorolaByteOrder true for Motorola/big endian, false for Intel/little endian + * @param isSequential flag indicating if caller is using sequential access + * @return the signed 32-bit int value as a long, between 0x00000000 and 0xFFFFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int getInt32(long index, boolean isMotorolaByteOrder, boolean isSequential) throws IOException, EOFException, BufferBoundsException + { + byte[] bytes = new byte[4]; + read(index, bytes, 0, bytes.length, isSequential, false); + + if (isMotorolaByteOrder) { + // Motorola - MSB first (big endian) + return (bytes[0] << 24 & 0xFF000000) | + (bytes[1] << 16 & 0xFF0000) | + (bytes[2] << 8 & 0xFF00) | + (bytes[3] & 0xFF); + } else { + // Intel ordering - LSB first (little endian) + return (bytes[3] << 24 & 0xFF000000) | + (bytes[2] << 16 & 0xFF0000) | + (bytes[1] << 8 & 0xFF00) | + (bytes[0] & 0xFF); + } + } + + /** + * Get a signed 64-bit integer from the buffer. + * + * @param index position within the data buffer to read first byte + * @param isMotorolaByteOrder true for Motorola/big endian, false for Intel/little endian + * @param isSequential flag indicating if caller is using sequential access + * @return the 64 bit int value, between 0x0000000000000000 and 0xFFFFFFFFFFFFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public long getInt64(long index, boolean isMotorolaByteOrder, boolean isSequential) throws IOException, EOFException, BufferBoundsException + { + byte[] bytes = new byte[8]; + read(index, bytes, 0, bytes.length, isSequential, false); + + if (isMotorolaByteOrder) { + // Motorola - MSB first + return ((long)bytes[0] << 56 & 0xFF00000000000000L) | + ((long)bytes[1] << 48 & 0xFF000000000000L) | + ((long)bytes[2] << 40 & 0xFF0000000000L) | + ((long)bytes[3] << 32 & 0xFF00000000L) | + ((long)bytes[4] << 24 & 0xFF000000L) | + ((long)bytes[5] << 16 & 0xFF0000L) | + ((long)bytes[6] << 8 & 0xFF00L) | + ((long)bytes[7] & 0xFFL); + } else { + // Intel ordering - LSB first + return ((long)bytes[7] << 56 & 0xFF00000000000000L) | + ((long)bytes[6] << 48 & 0xFF000000000000L) | + ((long)bytes[5] << 40 & 0xFF0000000000L) | + ((long)bytes[4] << 32 & 0xFF00000000L) | + ((long)bytes[3] << 24 & 0xFF000000L) | + ((long)bytes[2] << 16 & 0xFF0000L) | + ((long)bytes[1] << 8 & 0xFF00L) | + ((long)bytes[0] & 0xFFL); + } + } + + /** + * Gets a s15.16 fixed point float from the buffer. + *

    + * This particular fixed point encoding has one sign bit, 15 numerator bits and 16 denominator bits. + * @param index position within the data buffer to read first byte + * @param isMotorolaByteOrder true for Motorola/big endian, false for Intel/little endian + * @param isSequential flag indicating if caller is using sequential access + * @return the floating point value + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public float getS15Fixed16(long index, boolean isMotorolaByteOrder, boolean isSequential) throws IOException, EOFException, BufferBoundsException + { + byte[] bytes = new byte[4]; + read(index, bytes, 0, bytes.length, isSequential, false); + + if (isMotorolaByteOrder) { + float res = (bytes[0] & 0xFF) << 8 | + (bytes[1] & 0xFF); + int d = (bytes[2] & 0xFF) << 8 | + (bytes[3] & 0xFF); + return (float)(res + d/65536.0); + } else { + // this particular branch is untested + float res = (bytes[3] & 0xFF) << 8 | + (bytes[2] & 0xFF); + int d = (bytes[1] & 0xFF) << 8 | + (bytes[0] & 0xFF); + return (float)(res + d/65536.0); + } + } + + /** + * Skips to a specific index in the sequence. If the sequence ends, an {@link EOFException} is thrown. + * + * @param index the number of bytes to skip. + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public void seek(final long index) throws IOException, EOFException, BufferBoundsException + { + validateIndex((index == 0) ? 0 : (index - 1), 1L, false); + } + + /** + * Ensures that the buffered bytes extend to cover the specified index. If not, an attempt is made + * to read to that point. + *

    + * If the stream ends before the point is reached, a {@link BufferBoundsException} is raised. + * + * @param index the index from which the required bytes start + * @param bytesRequested the number of bytes which are required + * @param isSequential flag indicating if caller is using sequential access + * @return count of bytes available at the given index + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public long validateIndex(long index, long bytesRequested, boolean isSequential) throws IOException, EOFException, BufferBoundsException + { + return validateIndex(index, bytesRequested, isSequential, false); + } + + /** + * Ensures that the buffered bytes extend to cover the specified index. If not, an attempt is made + * to read to that point. + *

    + * If the stream ends before the point is reached, a {@link BufferBoundsException} might be raised. + * + * @param index the index from which the required bytes start + * @param bytesRequested the number of bytes which are required + * @param isSequential flag indicating if caller is using sequential access + * @param allowPartial flag indicating if fewer than count bytes can be returned + * @return count of bytes available at the given index + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public long validateIndex(long index, long bytesRequested, boolean isSequential, boolean allowPartial) throws IOException, EOFException, BufferBoundsException + { + long available = bytesAvailable(index, bytesRequested); + if(available != bytesRequested && !allowPartial) + { + if(index < 0) + throw new BufferBoundsException("Attempt to read from buffer using a negative index (" + ((Long)index).toString() + ")"); + if (bytesRequested < 0) + throw new BufferBoundsException("Number of requested bytes must be zero or greater"); + if (index + bytesRequested - 1 > Integer.MAX_VALUE) + throw new BufferBoundsException("Number of requested bytes summed with starting index exceed maximum range of signed 32 bit integers (requested index: " + ((Long)index).toString() + ", requested count: " + ((Long)bytesRequested).toString() + ")"); + if (index + bytesRequested >= _streamLength && isSequential) + throw new EOFException("End of data reached."); + + // TODO test that can continue using an instance of this type after this exception + throw new BufferBoundsException((int)index, (int)bytesRequested, _streamLength); + } + + return available; + } + + /** + * Determines how many bytes of bytesRequested are available at index + * @param index the index from which the required bytes start + * @param bytesRequested the number of bytes which are required + * @return number of bytes available on and after the given index + * @throws IOException + */ + private long bytesAvailable(long index, long bytesRequested) throws IOException + { + if (index < 0 || bytesRequested < 0) + return 0; + + // if there's only one chunk, there's no need to calculate anything. + // This bypasses a lot of checks particularly when the input was a byte[] + if (_isStreamFinished && _chunks.size() == 1) + { + if ((index + bytesRequested) < _streamLength) + return bytesRequested; + else if (index > _streamLength) + return 0; + else + return _streamLength - index; + } + + long endIndex = index + bytesRequested - 1; + if (endIndex < 0) endIndex = 0; + + // Maybe don't check this? + if (endIndex > Integer.MAX_VALUE) + return 0; + + // zero-based + long chunkstart = index / _chunkLength; + long chunkend = ((index + bytesRequested) / _chunkLength) + 1; + + + if (!_chunks.containsKey(chunkstart)) + { + if(!_canSeek) + chunkstart = _chunks.isEmpty() ? 0 : Collections.max(_chunks.keySet()) + 1; + } + + for (long i = chunkstart; i < chunkend; i++) + { + if (!_chunks.containsKey(i)) + { + _isStreamFinished = false; + + + // chunkstart can be anywhere. Try to seek + if (_canSeek && _raFile != null) + _raFile.seek(i * _chunkLength); + + byte[] chunk = new byte[_chunkLength]; + + int totalBytesRead = 0; + while (!_isStreamFinished && totalBytesRead != _chunkLength) + { + int bytesRead; + if(_canSeek && _raFile != null) + bytesRead = _raFile.read(chunk, totalBytesRead, _chunkLength - totalBytesRead); + else + bytesRead = _inputStream.read(chunk, totalBytesRead, _chunkLength - totalBytesRead); + + if (bytesRead == -1) + { + // the stream has ended, which may be ok + _isStreamFinished = true; + _streamLength = i * _chunkLength + totalBytesRead; + + // check we have enough bytes for the requested index + if (endIndex >= _streamLength) + { + _totalBytesRead += totalBytesRead; + _chunks.put(i, chunk); + return (index + bytesRequested) <= _streamLength ? bytesRequested : _streamLength - index; + } + } + else + { + totalBytesRead += bytesRead; + } + } + + _totalBytesRead += totalBytesRead; + _chunks.put(i, chunk); + } + } + + if (_isStreamFinished) + return (index + bytesRequested) <= _streamLength ? bytesRequested : 0; + else + return bytesRequested; + } + + public long getTotalBytesRead() + { + return _totalBytesRead; + } + + public byte[] toArray(long index, int count) throws IOException + { + byte[] buffer = null; + // if this was a byte array and asking for the whole thing... + if (_isStreamFinished && + _chunks.size() == 1 && + index == 0 && + count == getLength()) + { + buffer = _chunks.get(0L); + } + else + { + buffer = new byte[count]; + read(index, buffer, 0, count, false); + } + + return buffer; + } +} diff --git a/Source/com/drew/lang/RandomAccessStreamReader.java b/Source/com/drew/lang/RandomAccessStreamReader.java deleted file mode 100644 index e9c4056fb..000000000 --- a/Source/com/drew/lang/RandomAccessStreamReader.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2002-2017 Drew Noakes - * - * 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.lang; - -import com.drew.lang.annotations.NotNull; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; - -/** - * @author Drew Noakes https://drewnoakes.com - */ -public class RandomAccessStreamReader extends RandomAccessReader -{ - public final static int DEFAULT_CHUNK_LENGTH = 2 * 1024; - - @NotNull - private final InputStream _stream; - private final int _chunkLength; - - private final ArrayList _chunks = new ArrayList(); - - private boolean _isStreamFinished; - private long _streamLength; - - public RandomAccessStreamReader(@NotNull InputStream stream) - { - this(stream, DEFAULT_CHUNK_LENGTH, -1); - } - - public RandomAccessStreamReader(@NotNull InputStream stream, int chunkLength) - { - this(stream, chunkLength, -1); - } - - public RandomAccessStreamReader(@NotNull InputStream stream, int chunkLength, long streamLength) - { - if (stream == null) - throw new NullPointerException(); - if (chunkLength <= 0) - throw new IllegalArgumentException("chunkLength must be greater than zero"); - - _chunkLength = chunkLength; - _stream = stream; - _streamLength = streamLength; - } - - /** - * Reads to the end of the stream, in order to determine the total number of bytes. - * In general, this is not a good idea for this implementation of {@link RandomAccessReader}. - * - * @return the length of the data source, in bytes. - */ - @Override - public long getLength() throws IOException - { - if (_streamLength != -1) { - return _streamLength; - } - - isValidIndex(Integer.MAX_VALUE, 1); - assert(_isStreamFinished); - return _streamLength; - } - - /** - * Ensures that the buffered bytes extend to cover the specified index. If not, an attempt is made - * to read to that point. - *

    - * If the stream ends before the point is reached, a {@link BufferBoundsException} is raised. - * - * @param index the index from which the required bytes start - * @param bytesRequested the number of bytes which are required - * @throws BufferBoundsException if the stream ends before the required number of bytes are acquired - */ - @Override - protected void validateIndex(int index, int bytesRequested) throws IOException - { - if (index < 0) { - throw new BufferBoundsException(String.format("Attempt to read from buffer using a negative index (%d)", index)); - } else if (bytesRequested < 0) { - throw new BufferBoundsException("Number of requested bytes must be zero or greater"); - } else if ((long)index + bytesRequested - 1 > Integer.MAX_VALUE) { - throw new BufferBoundsException(String.format("Number of requested bytes summed with starting index exceed maximum range of signed 32 bit integers (requested index: %d, requested count: %d)", index, bytesRequested)); - } - - 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); - } - } - - @Override - protected boolean isValidIndex(int index, int bytesRequested) throws IOException - { - if (index < 0 || bytesRequested < 0) { - return false; - } - - long endIndexLong = (long)index + bytesRequested - 1; - - if (endIndexLong > Integer.MAX_VALUE) { - return false; - } - - int endIndex = (int)endIndexLong; - - if (_isStreamFinished) { - return endIndex < _streamLength; - } - - int chunkIndex = endIndex / _chunkLength; - - // TODO test loading several chunks for a single request - while (chunkIndex >= _chunks.size()) { - assert (!_isStreamFinished); - - byte[] chunk = new byte[_chunkLength]; - int totalBytesRead = 0; - while (!_isStreamFinished && totalBytesRead != _chunkLength) { - int bytesRead = _stream.read(chunk, totalBytesRead, _chunkLength - totalBytesRead); - if (bytesRead == -1) { - // the stream has ended, which may be ok - _isStreamFinished = true; - int observedStreamLength = _chunks.size() * _chunkLength + totalBytesRead; - if (_streamLength == -1) { - _streamLength = observedStreamLength; - } else if (_streamLength != observedStreamLength) { - assert(false); - } - - // check we have enough bytes for the requested index - if (endIndex >= _streamLength) { - _chunks.add(chunk); - return false; - } - } else { - totalBytesRead += bytesRead; - } - } - - _chunks.add(chunk); - } - - return true; - } - - @Override - public int toUnshiftedOffset(int localOffset) - { - return localOffset; - } - - @Override - public byte getByte(int index) throws IOException - { - assert(index >= 0); - - final int chunkIndex = index / _chunkLength; - final int innerIndex = index % _chunkLength; - final byte[] chunk = _chunks.get(chunkIndex); - - return chunk[innerIndex]; - } - - @NotNull - @Override - public byte[] getBytes(int index, int count) throws IOException - { - validateIndex(index, count); - - byte[] bytes = new byte[count]; - - int remaining = count; - int fromIndex = index; - int toIndex = 0; - - while (remaining != 0) { - int fromChunkIndex = fromIndex / _chunkLength; - int fromInnerIndex = fromIndex % _chunkLength; - int length = Math.min(remaining, _chunkLength - fromInnerIndex); - - byte[] chunk = _chunks.get(fromChunkIndex); - - System.arraycopy(chunk, fromInnerIndex, bytes, toIndex, length); - - remaining -= length; - fromIndex += length; - toIndex += length; - } - - return bytes; - } -} diff --git a/Source/com/drew/lang/ReaderInfo.java b/Source/com/drew/lang/ReaderInfo.java new file mode 100644 index 000000000..a9edd8e59 --- /dev/null +++ b/Source/com/drew/lang/ReaderInfo.java @@ -0,0 +1,943 @@ +/* + * Copyright 2002-2017 Drew Noakes + * Copyright 2018 Kevin Mott + * + * 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.lang; + +import com.drew.lang.annotations.NotNull; +import com.drew.lang.annotations.Nullable; +import com.drew.metadata.StringValue; + +import java.io.EOFException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +/** + * + * @author Kevin Mott https://github.com/kwhopper + * @author Drew Noakes https://drewnoakes.com + */ +public class ReaderInfo +{ + // this flag is compared to index inputs and indicates sequential access + private final int SequentialFlag = Integer.MIN_VALUE; + + private RandomAccessStream _ras = null; + private long _length = -1; + + private long _startPosition; + private long _localPosition; + private boolean _isMotorolaByteOrder; + + public ReaderInfo(RandomAccessStream parent) + { + this(parent, 0L); + } + + public ReaderInfo(RandomAccessStream parent, long startPosition) + { + this(parent, startPosition, 0L); + } + + public ReaderInfo(RandomAccessStream parent, long startPosition, long localPosition) + { + this(parent, startPosition, localPosition, -1L); + } + + public ReaderInfo(RandomAccessStream parent, long startPosition, long localPosition, long length) + { + this(parent, startPosition, localPosition, length, true); + } + + public ReaderInfo(RandomAccessStream parent, long startPosition, long localPosition, long length, boolean isMotorolaByteOrder) + { + _ras = parent; + _startPosition = startPosition; + _localPosition = localPosition; + _length = length; + + _isMotorolaByteOrder = isMotorolaByteOrder; + } + + public static ReaderInfo createFromArray(@NotNull byte[] bytes) throws NullPointerException + { + if(bytes == null) + throw new NullPointerException(); + + return new RandomAccessStream(bytes).createReader(); + } + + private long getGlobalPosition() + { + return getStartPosition() + getLocalPosition(); + } + + public long getStartPosition() + { + return _startPosition; + } + + public long getLocalPosition() + { + return _localPosition; + } + + public long getLength() throws IOException + { + return (_length != -1) ? _length : (_ras.getLength() - getStartPosition()); + } + + /** + * Gets the endianness of this reader. + *

      + *
    • true for Motorola (or big) endianness (also known as network byte order), with MSB before LSB.
    • + *
    • false for Intel (or little) endianness, with LSB before MSB.
    • + *
    + */ + public boolean isMotorolaByteOrder() + { + return _isMotorolaByteOrder; + } + + /** + * Sets the endianness of this reader. + *
      + *
    • true for Motorola (or big) endianness (also known as network byte order), with MSB before LSB.
    • + *
    • false for Intel (or little) endianness, with LSB before MSB.
    • + *
    + * + * @param value true for Motorola/big endian, false for Intel/little endian + */ + public void setMotorolaByteOrder(boolean value) + { + _isMotorolaByteOrder = value; + } + + /** + * Creates a new ReaderInfo with the current properties of this reader + * @return a clones ReaderInfo + * @throws IOException + */ + public ReaderInfo Clone() throws IOException + { + return Clone(0L, -1L, true); + } + public ReaderInfo Clone(boolean useByteOrder) throws IOException + { + return Clone(0L, useByteOrder); + } + public ReaderInfo Clone(long length) throws IOException + { + return Clone(0L, length, true); + } + public ReaderInfo Clone(long offset, long length) throws IOException + { + return Clone(offset, length, true); + } + public ReaderInfo Clone(long offset, boolean useByteOrder) throws IOException + { + return Clone(offset, -1L, useByteOrder); + } + public ReaderInfo Clone(long offset, long length, boolean useByteOrder) throws IOException + { + return _ras.createReader(getGlobalPosition() + offset, (length > -1 ? length : getLength() - offset), useByteOrder ? isMotorolaByteOrder() : !isMotorolaByteOrder()); + } + + /** + * Skips forward or backward in the sequence. If the sequence ends, an {@link EOFException} is thrown. + * + * @param offset the number of bytes to skip. + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public void skip(long offset) throws IOException, EOFException, BufferBoundsException + { + if (offset + getLocalPosition() < 0) + offset = -getLocalPosition(); + + _ras.seek(getLocalPosition() + offset); + + _localPosition += offset; + } + + /** + * Skips forward or backward in the sequence, returning a boolean indicating whether the skip succeeded, or whether the sequence ended. + * + * @param n the number of byte to skip. Must be zero or greater. + * @return a boolean indicating whether the skip succeeded, or whether the sequence ended. + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public boolean trySkip(long n) throws IOException, EOFException, BufferBoundsException + { + try + { + skip(n); + return true; + } + catch (IOException ex) + { + // Stream ended, or error reading from underlying source + return false; + } + } + + /** + * Retrieves bytes, writing them into a caller-provided buffer. + * + * @param buffer array to write bytes to + * @param offset starting position with buffer to write to + * @param count The number of bytes to be written + * @return The requested bytes + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int read(byte[] buffer, int offset, int count) throws IOException, EOFException, BufferBoundsException + { + return read(buffer, offset, SequentialFlag, count); + } + + /** + * Retrieves bytes, writing them into a caller-provided buffer. + * SequentialFlag as index indicates this call should read sequentially + * + * @param buffer array to write bytes to + * @param offset starting position with buffer to write to + * @param index position within the data buffer to read bytes + * @param count The number of bytes to be written + * @return The requested bytes + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int read(byte[] buffer, int offset, long index, int count) throws IOException, EOFException, BufferBoundsException + { + boolean isSeq = (index == SequentialFlag); + long readat = isSeq ? getGlobalPosition() : (getStartPosition() + index); + + return readAtGlobal(readat, buffer, offset, count, isSeq, true); + } + + private int readAtGlobal(long readat, byte[] buffer, int offset, int count, boolean isSequential, boolean allowPartial) throws IOException, EOFException, BufferBoundsException + { + int read = _ras.read(readat, buffer, offset, count, isSequential, allowPartial); + + if (isSequential && read > 0) + _localPosition += read; // advance the sequential position + + return read; + } + + /** + * Determine if the next bytes match the input pattern. Internal sequential variables are unaffected + * + * @param pattern the byte pattern to match + * @return true if this pattern is found or false if any part of it is not found + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public boolean startsWith(byte[] pattern) throws IOException, EOFException, BufferBoundsException + { + if (getLength() < pattern.length) + return false; + + boolean ret = true; + int i = 0; + for (i = 0; i < pattern.length; i++) + { + if (getByte(i) != pattern[i]) + { + ret = false; + break; + } + } + + return ret; + } + + /** + * Gets the byte value at the current sequential index + *

    + * Implementations should not perform any bounds checking in this method. That should be performed + * in validateIndex and bytesAvailable. + * + * @return The read byte value + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public byte getByte() throws IOException, EOFException, BufferBoundsException + { + return getByte(SequentialFlag); + } + + /** + * Gets the byte value at the specified byte index. + *

    + * Implementations should not perform any bounds checking in this method. That should be performed + * in validateIndex and bytesAvailable. + * + * @param index The index from which to read the byte + * @return The read byte value + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public byte getByte(long index) throws IOException, EOFException, BufferBoundsException + { + boolean isSeq = (index == SequentialFlag); + long readat = isSeq ? getGlobalPosition() : (getStartPosition() + index); + + byte read = _ras.getByte(readat, isSeq); + + if (isSeq) + _localPosition++; // advance the sequential position + + return read; + } + + /** + * Returns the required number of bytes sequentially from the underlying source. + * + * @param count The number of bytes to be returned + * @return The requested bytes + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public byte[] getBytes(int count) throws IOException, EOFException, BufferBoundsException + { + return getBytes(SequentialFlag, count); + } + + /** + * Returns the required number of bytes from the specified index from the underlying source. + * + * @param index The index from which the bytes begins in the underlying source + * @param count The number of bytes to be returned + * @return The requested bytes + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public byte[] getBytes(long index, int count) throws IOException, EOFException, BufferBoundsException + { + // validate the index now to avoid creating a byte array that could cause a heap overflow + boolean isSeq = (index == SequentialFlag); + long readat = isSeq ? getGlobalPosition() : (getStartPosition() + index); + long available = _ras.validateIndex(readat, count, isSeq, false); + + if(available <= 0) + return new byte[0]; + + byte[] bytes = new byte[count]; + readAtGlobal(readat, bytes, 0, count, isSeq, false); + + return bytes; + } + + /** + * Gets whether a bit at the current sequential index is set or not. + * + * @return true if the bit is set, otherwise false + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public boolean getBit() throws IOException, EOFException, BufferBoundsException + { + return getBit(SequentialFlag); + } + + /** + * Gets whether a bit at a specific index is set or not. + * + * @param index the number of bits at which to test + * @return true if the bit is set, otherwise false + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public boolean getBit(int index) throws IOException, EOFException, BufferBoundsException + { + int byteIndex = index / 8; + int bitIndex = index % 8; + + byte b = getByte(byteIndex); + return ((b >> bitIndex) & 1) == 1; + } + + /** + * Returns an unsigned 8-bit int calculated from one byte of data at the current sequential index. + * + * @return the 8 bit int value, between 0 and 255 + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public short getUInt8() throws IOException, EOFException, BufferBoundsException + { + return getUInt8(SequentialFlag); + } + + /** + * Returns an unsigned 8-bit int calculated from one byte of data at the specified index. + * + * @param index position within the data buffer to read byte + * @return the 8 bit int value, between 0 and 255 + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public short getUInt8(int index) throws IOException, EOFException, BufferBoundsException + { + return (short) (getByte(index) & 0xFF); + } + + /** + * Returns a signed 8-bit int calculated from one byte of data at the current sequential index. + * + * @return the 8 bit int value, between 0x00 and 0xFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public byte getInt8() throws IOException, EOFException, BufferBoundsException + { + return getInt8(SequentialFlag); + } + + /** + * Returns a signed 8-bit int calculated from one byte of data at the specified index. + * + * @param index position within the data buffer to read byte + * @return the 8 bit int value, between 0x00 and 0xFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public byte getInt8(int index) throws IOException, EOFException, BufferBoundsException + { + return getByte(index); + } + + /** + * Returns an unsigned 16-bit int calculated from two bytes of data at the current sequential index. + * + * @return the 16 bit int value, between 0x0000 and 0xFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int getUInt16() throws IOException, EOFException, BufferBoundsException + { + return getUInt16(SequentialFlag); + } + + /** + * Returns an unsigned 16-bit int calculated from two bytes of data at the specified index. + * + * @param index position within the data buffer to read first byte + * @return the 16 bit int value, between 0x0000 and 0xFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int getUInt16(int index) throws IOException, EOFException, BufferBoundsException + { + boolean isSeq = (index == SequentialFlag); + long readat = isSeq ? getGlobalPosition() : (getStartPosition() + index); + + int read = _ras.getUInt16(readat, isMotorolaByteOrder(), isSeq); + + if (isSeq) + _localPosition += 2; // advance the sequential position + + return read; + } + + /** + * Returns a signed 16-bit int calculated from two bytes of data at the current sequential index (MSB, LSB). + * + * @return the 16 bit int value, between 0x0000 and 0xFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public short getInt16() throws IOException, EOFException, BufferBoundsException + { + return getInt16(SequentialFlag); + } + + /** + * Returns a signed 16-bit int calculated from two bytes of data at the specified index (MSB, LSB). + * + * @param index position within the data buffer to read first byte + * @return the 16 bit int value, between 0x0000 and 0xFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public short getInt16(int index) throws IOException, EOFException, BufferBoundsException + { + boolean isSeq = (index == SequentialFlag); + long readat = isSeq ? getGlobalPosition() : (getStartPosition() + index); + + short read = _ras.getInt16(readat, isMotorolaByteOrder(), isSeq); + + if (isSeq) + _localPosition += 2; // advance the sequential position + + return read; + } + + /** + * Get a 24-bit unsigned integer from the current sequential index, returning it as an int. + * + * @return the unsigned 24-bit int value as a long, between 0x00000000 and 0x00FFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int getInt24() throws IOException, EOFException, BufferBoundsException + { + return getInt24(SequentialFlag); + } + + /** + * Get a 24-bit unsigned integer from the buffer, returning it as an int. + * + * @param index position within the data buffer to read first byte + * @return the unsigned 24-bit int value as a long, between 0x00000000 and 0x00FFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int getInt24(int index) throws IOException, EOFException, BufferBoundsException + { + boolean isSeq = (index == SequentialFlag); + long readat = isSeq ? getGlobalPosition() : (getStartPosition() + index); + + int read = _ras.getInt24(readat, isMotorolaByteOrder(), isSeq); + + if (isSeq) + _localPosition += 3; // advance the sequential position + + return read; + } + + /** + * Get a 32-bit unsigned integer from the current sequential index, returning it as a long. + * + * @return the unsigned 32-bit int value as a long, between 0x00000000 and 0xFFFFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public long getUInt32() throws IOException, EOFException, BufferBoundsException + { + return getUInt32(SequentialFlag); + } + + /** + * Get a 32-bit unsigned integer from the buffer, returning it as a long. + * + * @param index position within the data buffer to read first byte + * @return the unsigned 32-bit int value as a long, between 0x00000000 and 0xFFFFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public long getUInt32(int index) throws IOException, EOFException, BufferBoundsException + { + boolean isSeq = (index == SequentialFlag); + long readat = isSeq ? getGlobalPosition() : (getStartPosition() + index); + + long read = _ras.getUInt32(readat, isMotorolaByteOrder(), isSeq); + + if (isSeq) + _localPosition += 4; // advance the sequential position + + return read; + } + + /** + * Returns a signed 32-bit integer from four bytes of data at the current sequential index of the buffer. + * + * @return the signed 32 bit int value, between 0x00000000 and 0xFFFFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int getInt32() throws IOException, EOFException, BufferBoundsException + { + return getInt32(SequentialFlag); + } + + /** + * Returns a signed 32-bit integer from four bytes of data at the specified index of the buffer. + * + * @param index position within the data buffer to read first byte + * @return the signed 32 bit int value, between 0x00000000 and 0xFFFFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public int getInt32(int index) throws IOException, EOFException, BufferBoundsException + { + boolean isSeq = (index == SequentialFlag); + long readat = isSeq ? getGlobalPosition() : (getStartPosition() + index); + + int read = _ras.getInt32(readat, isMotorolaByteOrder(), isSeq); + + if (isSeq) + _localPosition += 4; // advance the sequential position + + return read; + } + + /** + * Get a signed 64-bit integer from the current sequential index of the buffer. + * + * @return the 64 bit int value, between 0x0000000000000000 and 0xFFFFFFFFFFFFFFFF + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public long getInt64() throws IOException, EOFException, BufferBoundsException + { + return getInt64(SequentialFlag); + } + + /** + * Get a signed 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 if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public long getInt64(int index) throws IOException, EOFException, BufferBoundsException + { + boolean isSeq = (index == SequentialFlag); + long readat = isSeq ? getGlobalPosition() : (getStartPosition() + index); + + long read = _ras.getInt64(readat, isMotorolaByteOrder(), isSeq); + + if (isSeq) + _localPosition += 8; // advance the sequential position + + return read; + } + + /** + * Gets a s15.16 fixed point float from the buffer sequentially. + *

    + * This particular fixed point encoding has one sign bit, 15 numerator bits and 16 denominator bits. + * + * @return the floating point value + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public float getS15Fixed16() throws IOException, EOFException, BufferBoundsException + { + return getS15Fixed16(SequentialFlag); + } + + /** + * Gets a s15.16 fixed point float from the buffer. + *

    + * This particular fixed point encoding has one sign bit, 15 numerator bits and 16 denominator bits. + * + * @param index position within the data buffer to read first byte + * @return the floating point value + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public float getS15Fixed16(int index) throws IOException, EOFException, BufferBoundsException + { + boolean isSeq = (index == SequentialFlag); + long readat = isSeq ? getGlobalPosition() : (getStartPosition() + index); + + float read = _ras.getS15Fixed16(readat, isMotorolaByteOrder(), isSeq); + + if (isSeq) + _localPosition += 4; // advance the sequential position + + return read; + } + + public float getFloat32() throws IOException, EOFException, BufferBoundsException + { + return getFloat32(SequentialFlag); + } + + public float getFloat32(int index) throws IOException, EOFException, BufferBoundsException + { + return Float.intBitsToFloat(getInt32(index)); + } + + public double getDouble64() throws IOException, EOFException, BufferBoundsException + { + return getDouble64(SequentialFlag); + } + + public double getDouble64(int index) throws IOException, EOFException, BufferBoundsException + { + return Double.longBitsToDouble(getInt64(index)); + } + + @NotNull + public StringValue getStringValue(int bytesRequested, @Nullable Charset charset) throws IOException, EOFException, BufferBoundsException + { + return getStringValue(SequentialFlag, bytesRequested, charset); + } + + @NotNull + public StringValue getStringValue(int index, int bytesRequested, @Nullable Charset charset) throws IOException, EOFException, BufferBoundsException + { + return new StringValue(getBytes(index, bytesRequested), charset); + } + + @NotNull + public String getString(int bytesRequested, @NotNull Charset charset) throws IOException, EOFException, BufferBoundsException + { + return getString(SequentialFlag, bytesRequested, charset); + } + + @NotNull + public String getString(int index, int bytesRequested, @NotNull Charset charset) throws IOException, EOFException, BufferBoundsException + { + return new String(getBytes(index, bytesRequested), charset.name()); + } + + @NotNull + public String getString(int index, int bytesRequested, @NotNull String charset) throws IOException, EOFException, BufferBoundsException + { + byte[] bytes = getBytes(index, bytesRequested); + try { + return new String(bytes, charset); + } catch (UnsupportedEncodingException e) { + return new String(bytes); + } + } + + /** + * Creates a String from the _data buffer starting at the current sequential index, + * and ending where byte=='\0' or where length==maxLength. + * + * @param maxLengthBytes The maximum number of bytes to read. If a zero-byte is not reached within this limit, + * reading will stop and the string will be truncated to this length. + * @param charset The Charset to register with the returned StringValue, or null if the encoding + * is unknown + * @return The read string. + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + @NotNull + public String getNullTerminatedString(int maxLengthBytes, @NotNull Charset charset) throws IOException, EOFException, BufferBoundsException + { + return getNullTerminatedString(SequentialFlag, maxLengthBytes, charset); + } + + /** + * Creates a String from the _data buffer starting at the specified index, + * and ending where byte=='\0' or where length==maxLength. + * + * @param index The index within the buffer at which to start reading the string. + * @param maxLengthBytes The maximum number of bytes to read. If a zero-byte is not reached within this limit, + * reading will stop and the string will be truncated to this length. + * @param charset The Charset to register with the returned StringValue, or null if the encoding + * is unknown + * @return The read string. + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + @NotNull + public String getNullTerminatedString(int index, int maxLengthBytes, @NotNull Charset charset) throws IOException, EOFException, BufferBoundsException + { + return new String(getNullTerminatedBytes(index, maxLengthBytes), charset.name()); + } + + /** + * Creates a String from the _data buffer starting at the current sequential index, + * and ending where byte=='\0' or where length==maxLength. + * + * @param maxLengthBytes The maximum number of bytes to read. If a zero-byte is not reached within this limit, + * reading will stop and the string will be truncated to this length. + * @param charset The Charset to register with the returned StringValue, or null if the encoding + * is unknown + * @return The read string. + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + @NotNull + public StringValue getNullTerminatedStringValue(int maxLengthBytes, @Nullable Charset charset) throws IOException, EOFException, BufferBoundsException + { + return getNullTerminatedStringValue(SequentialFlag, maxLengthBytes, charset); + } + + /** + * Creates a String from the _data buffer starting at the specified index, + * and ending where byte=='\0' or where length==maxLength. + * + * @param index The index within the buffer at which to start reading the string. + * @param maxLengthBytes The maximum number of bytes to read. If a zero-byte is not reached within this limit, + * reading will stop and the string will be truncated to this length. + * @param charset The Charset to register with the returned StringValue, or null if the encoding + * is unknown + * @return The read string. + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + @NotNull + public StringValue getNullTerminatedStringValue(int index, int maxLengthBytes, @Nullable Charset charset) throws IOException, EOFException, BufferBoundsException + { + byte[] bytes = getNullTerminatedBytes(index, maxLengthBytes); + + return new StringValue(bytes, charset); + } + + /** + * Returns the sequence of bytes punctuated by a \0 value. + * + * @param maxLengthBytes The maximum number of bytes to read. If a \0 byte is not reached within this limit, + * the returned array will be maxLengthBytes long. + * @return The read byte array, excluding the null terminator. + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + @NotNull + public byte[] getNullTerminatedBytes(int maxLengthBytes) throws IOException, EOFException, BufferBoundsException + { + return getNullTerminatedBytes(SequentialFlag, maxLengthBytes); + } + + /** + * Returns the sequence of bytes punctuated by a \0 value. + * + * @param index The index within the buffer at which to start reading the string. + * @param maxLengthBytes The maximum number of bytes to read. If a \0 byte is not reached within this limit, + * the returned array will be maxLengthBytes long. + * @return The read byte array, excluding the null terminator. + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + @NotNull + public byte[] getNullTerminatedBytes(int index, int maxLengthBytes) throws IOException, EOFException, BufferBoundsException + { + boolean isSeq = (index == SequentialFlag); + byte[] buffer = !isSeq ? getBytes(index, maxLengthBytes) : new byte[maxLengthBytes]; + + // Count the number of non-null bytes + int length = 0; + if(!isSeq) + { + while (length < buffer.length && buffer[length] != 0) + length++; + } + else + { + byte getB = getByte(); + while(length < buffer.length && getB != 0) + { + buffer[length] = getB; + getB = getByte(); + length++; + } + } + + if (length == maxLengthBytes) + return buffer; + + byte[] bytes = new byte[length]; + if (length > 0) + System.arraycopy(buffer, 0, bytes, 0, length); + return bytes; + } + + ///

    Returns the bytes described by this particular reader + /// + /** + * Returns the bytes described by this particular reader + * @return byte array + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public byte[] toArray() throws IOException, EOFException, BufferBoundsException + { + return _ras.toArray(getStartPosition(), (int)getLength()); + } + + public String readLine() throws IOException + { + StringBuilder sb = new StringBuilder(); + while (true) + { + if (getLocalPosition() == getLength()) + break; + + int ch = getByte(); + if (ch == -1) break; + if (ch == '\r' || ch == '\n') + { + byte nextbyte = 0; + if(getGlobalPosition() + 1 < getLength()) + nextbyte = getByte(); + if (!(ch == '\r' && nextbyte == '\n')) + skip(-1); + + return sb.toString(); + } + sb.append((char)ch); + } + if (sb.length() > 0) return sb.toString(); + return null; + } + + /** + * Returns true in case the sequence supports length checking and distance to the end of the stream is less then number of bytes in parameter. + * Otherwise false. + * @param numberOfBytes count of bytes to try and read + * @return True if we are going to have an exception while reading next numberOfBytes bytes from the stream + * @throws IOException if the byte is unable to be read + * @throws EOFException if the requested bytes extend beyond the end of the underlying data source + * @throws BufferBoundsException if the requested byte is beyond the end of the underlying data source + */ + public boolean isCloserToEnd(long numberOfBytes) throws IOException, EOFException, BufferBoundsException + { + return (getLocalPosition() + numberOfBytes) > getLength(); + } +} diff --git a/Source/com/drew/lang/SequentialByteArrayReader.java b/Source/com/drew/lang/SequentialByteArrayReader.java deleted file mode 100644 index 846efeb6f..000000000 --- a/Source/com/drew/lang/SequentialByteArrayReader.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2002-2017 Drew Noakes - * - * 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.lang; - -import com.drew.lang.annotations.NotNull; - -import java.io.EOFException; -import java.io.IOException; - -/** - * - * @author Drew Noakes https://drewnoakes.com - */ -public class SequentialByteArrayReader extends SequentialReader -{ - @NotNull - private final byte[] _bytes; - private int _index; - - @Override - public long getPosition() - { - return _index; - } - - public SequentialByteArrayReader(@NotNull byte[] bytes) - { - this(bytes, 0); - } - - @SuppressWarnings("ConstantConditions") - public SequentialByteArrayReader(@NotNull byte[] bytes, int baseIndex) - { - if (bytes == null) - throw new NullPointerException(); - - _bytes = bytes; - _index = baseIndex; - } - - @Override - public byte getByte() throws IOException - { - if (_index >= _bytes.length) { - throw new EOFException("End of data reached."); - } - return _bytes[_index++]; - } - - @NotNull - @Override - public byte[] getBytes(int count) throws IOException - { - if (_index + count > _bytes.length) { - throw new EOFException("End of data reached."); - } - - byte[] bytes = new byte[count]; - System.arraycopy(_bytes, _index, bytes, 0, count); - _index += count; - - return bytes; - } - - @Override - public void getBytes(@NotNull byte[] buffer, int offset, int count) throws IOException - { - if (_index + count > _bytes.length) { - throw new EOFException("End of data reached."); - } - - System.arraycopy(_bytes, _index, buffer, offset, count); - _index += count; - } - - @Override - public void skip(long n) throws IOException - { - if (n < 0) { - throw new IllegalArgumentException("n must be zero or greater."); - } - - if (_index + n > _bytes.length) { - throw new EOFException("End of data reached."); - } - - _index += n; - } - - @Override - public boolean trySkip(long n) throws IOException - { - if (n < 0) { - throw new IllegalArgumentException("n must be zero or greater."); - } - - _index += n; - - if (_index > _bytes.length) { - _index = _bytes.length; - return false; - } - - return true; - } - - @Override - public int available() { - return _bytes.length - _index; - } -} diff --git a/Source/com/drew/lang/SequentialReader.java b/Source/com/drew/lang/SequentialReader.java deleted file mode 100644 index d172aa944..000000000 --- a/Source/com/drew/lang/SequentialReader.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright 2002-2017 Drew Noakes - * - * 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.lang; - -import com.drew.lang.annotations.NotNull; -import com.drew.lang.annotations.Nullable; -import com.drew.metadata.StringValue; - -import java.io.EOFException; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; - -/** - * @author Drew Noakes https://drewnoakes.com - */ -@SuppressWarnings("WeakerAccess") -public abstract class SequentialReader -{ - // TODO review whether the masks are needed (in both this and RandomAccessReader) - - private boolean _isMotorolaByteOrder = true; - - public abstract long getPosition() throws IOException; - - /** - * Gets the next byte in the sequence. - * - * @return The read byte value - */ - public abstract byte getByte() throws IOException; - - /** - * Returns the required number of bytes from the sequence. - * - * @param count The number of bytes to be returned - * @return The requested bytes - */ - @NotNull - public abstract byte[] getBytes(int count) throws IOException; - - /** - * Retrieves bytes, writing them into a caller-provided buffer. - * @param buffer The array to write bytes to. - * @param offset The starting position within buffer to write to. - * @param count The number of bytes to be written. - */ - public abstract void getBytes(@NotNull byte[] buffer, int offset, int count) throws IOException; - - /** - * Skips forward in the sequence. If the sequence ends, an {@link EOFException} is thrown. - * - * @param n the number of byte to skip. Must be zero or greater. - * @throws EOFException the end of the sequence is reached. - * @throws IOException an error occurred reading from the underlying source. - */ - public abstract void skip(long n) throws IOException; - - /** - * Skips forward in the sequence, returning a boolean indicating whether the skip succeeded, or whether the sequence ended. - * - * @param n the number of byte to skip. Must be zero or greater. - * @return a boolean indicating whether the skip succeeded, or whether the sequence ended. - * @throws IOException an error occurred reading from the underlying source. - */ - public abstract boolean trySkip(long n) throws IOException; - - /** - * Returns an estimate of the number of bytes that can be read (or skipped - * over) from this {@link SequentialReader} without blocking by the next - * invocation of a method for this input stream. A single read or skip of - * this many bytes will not block, but may read or skip fewer bytes. - *

    - * Note that while some implementations of {@link SequentialReader} like - * {@link SequentialByteArrayReader} will return the total remaining number - * of bytes in the stream, others will not. It is never correct to use the - * return value of this method to allocate a buffer intended to hold all - * data in this stream. - * - * @return an estimate of the number of bytes that can be read (or skipped - * over) from this {@link SequentialReader} without blocking or - * {@code 0} when it reaches the end of the input stream. - */ - public abstract int available(); - - /** - * Sets the endianness of this reader. - *

      - *
    • true for Motorola (or big) endianness (also known as network byte order), with MSB before LSB.
    • - *
    • false for Intel (or little) endianness, with LSB before MSB.
    • - *
    - * - * @param motorolaByteOrder true for Motorola/big endian, false for Intel/little endian - */ - public void setMotorolaByteOrder(boolean motorolaByteOrder) - { - _isMotorolaByteOrder = motorolaByteOrder; - } - - /** - * Gets the endianness of this reader. - *
      - *
    • true for Motorola (or big) endianness (also known as network byte order), with MSB before LSB.
    • - *
    • false for Intel (or little) endianness, with LSB before MSB.
    • - *
    - */ - public boolean isMotorolaByteOrder() - { - return _isMotorolaByteOrder; - } - - /** - * Returns an unsigned 8-bit int calculated from the next byte of the sequence. - * - * @return the 8 bit int value, between 0 and 255 - */ - public short getUInt8() throws IOException - { - return (short) (getByte() & 0xFF); - } - - /** - * Returns a signed 8-bit int calculated from the next byte the sequence. - * - * @return the 8 bit int value, between 0x00 and 0xFF - */ - public byte getInt8() throws IOException - { - return getByte(); - } - - /** - * Returns an unsigned 16-bit int calculated from the next two bytes of the sequence. - * - * @return the 16 bit int value, between 0x0000 and 0xFFFF - */ - public int getUInt16() throws IOException - { - if (_isMotorolaByteOrder) { - // Motorola - MSB first - return (getByte() << 8 & 0xFF00) | - (getByte() & 0xFF); - } else { - // Intel ordering - LSB first - return (getByte() & 0xFF) | - (getByte() << 8 & 0xFF00); - } - } - - /** - * Returns a signed 16-bit int calculated from two bytes of data (MSB, LSB). - * - * @return the 16 bit int value, between 0x0000 and 0xFFFF - * @throws IOException the buffer does not contain enough bytes to service the request - */ - public short getInt16() throws IOException - { - if (_isMotorolaByteOrder) { - // Motorola - MSB first - return (short) (((short)getByte() << 8 & (short)0xFF00) | - ((short)getByte() & (short)0xFF)); - } else { - // Intel ordering - LSB first - return (short) (((short)getByte() & (short)0xFF) | - ((short)getByte() << 8 & (short)0xFF00)); - } - } - - /** - * Get a 32-bit unsigned integer from the buffer, returning it as a long. - * - * @return the unsigned 32-bit int value as a long, between 0x00000000 and 0xFFFFFFFF - * @throws IOException the buffer does not contain enough bytes to service the request - */ - public long getUInt32() throws IOException - { - if (_isMotorolaByteOrder) { - // Motorola - MSB first (big endian) - return (((long)getByte()) << 24 & 0xFF000000L) | - (((long)getByte()) << 16 & 0xFF0000L) | - (((long)getByte()) << 8 & 0xFF00L) | - (((long)getByte()) & 0xFFL); - } else { - // Intel ordering - LSB first (little endian) - return (((long)getByte()) & 0xFFL) | - (((long)getByte()) << 8 & 0xFF00L) | - (((long)getByte()) << 16 & 0xFF0000L) | - (((long)getByte()) << 24 & 0xFF000000L); - } - } - - /** - * Returns a signed 32-bit integer from four bytes of data. - * - * @return the signed 32 bit int value, between 0x00000000 and 0xFFFFFFFF - * @throws IOException the buffer does not contain enough bytes to service the request - */ - public int getInt32() throws IOException - { - if (_isMotorolaByteOrder) { - // Motorola - MSB first (big endian) - return (getByte() << 24 & 0xFF000000) | - (getByte() << 16 & 0xFF0000) | - (getByte() << 8 & 0xFF00) | - (getByte() & 0xFF); - } else { - // Intel ordering - LSB first (little endian) - return (getByte() & 0xFF) | - (getByte() << 8 & 0xFF00) | - (getByte() << 16 & 0xFF0000) | - (getByte() << 24 & 0xFF000000); - } - } - - /** - * Get a signed 64-bit integer from the buffer. - * - * @return the 64 bit int value, between 0x0000000000000000 and 0xFFFFFFFFFFFFFFFF - * @throws IOException the buffer does not contain enough bytes to service the request - */ - public long getInt64() throws IOException - { - if (_isMotorolaByteOrder) { - // Motorola - MSB first - return ((long)getByte() << 56 & 0xFF00000000000000L) | - ((long)getByte() << 48 & 0xFF000000000000L) | - ((long)getByte() << 40 & 0xFF0000000000L) | - ((long)getByte() << 32 & 0xFF00000000L) | - ((long)getByte() << 24 & 0xFF000000L) | - ((long)getByte() << 16 & 0xFF0000L) | - ((long)getByte() << 8 & 0xFF00L) | - ((long)getByte() & 0xFFL); - } else { - // Intel ordering - LSB first - return ((long)getByte() & 0xFFL) | - ((long)getByte() << 8 & 0xFF00L) | - ((long)getByte() << 16 & 0xFF0000L) | - ((long)getByte() << 24 & 0xFF000000L) | - ((long)getByte() << 32 & 0xFF00000000L) | - ((long)getByte() << 40 & 0xFF0000000000L) | - ((long)getByte() << 48 & 0xFF000000000000L) | - ((long)getByte() << 56 & 0xFF00000000000000L); - } - } - - /** - * Gets a s15.16 fixed point float from the buffer. - *

    - * This particular fixed point encoding has one sign bit, 15 numerator bits and 16 denominator bits. - * - * @return the floating point value - * @throws IOException the buffer does not contain enough bytes to service the request - */ - public float getS15Fixed16() throws IOException - { - if (_isMotorolaByteOrder) { - float res = (getByte() & 0xFF) << 8 | - (getByte() & 0xFF); - int d = (getByte() & 0xFF) << 8 | - (getByte() & 0xFF); - return (float)(res + d/65536.0); - } else { - // this particular branch is untested - int d = (getByte() & 0xFF) | - (getByte() & 0xFF) << 8; - float res = (getByte() & 0xFF) | - (getByte() & 0xFF) << 8; - return (float)(res + d/65536.0); - } - } - - public float getFloat32() throws IOException - { - return Float.intBitsToFloat(getInt32()); - } - - public double getDouble64() throws IOException - { - return Double.longBitsToDouble(getInt64()); - } - - @NotNull - public String getString(int bytesRequested) throws IOException - { - return new String(getBytes(bytesRequested)); - } - - @NotNull - public String getString(int bytesRequested, String charset) throws IOException - { - byte[] bytes = getBytes(bytesRequested); - try { - return new String(bytes, charset); - } catch (UnsupportedEncodingException e) { - return new String(bytes); - } - } - - @NotNull - public String getString(int bytesRequested, @NotNull Charset charset) throws IOException - { - byte[] bytes = getBytes(bytesRequested); - return new String(bytes, charset); - } - - @NotNull - public StringValue getStringValue(int bytesRequested, @Nullable Charset charset) throws IOException - { - return new StringValue(getBytes(bytesRequested), charset); - } - - /** - * Creates a String from the stream, ending where byte=='\0' or where length==maxLength. - * - * @param maxLengthBytes The maximum number of bytes to read. If a zero-byte is not reached within this limit, - * reading will stop and the string will be truncated to this length. - * @return The read string. - * @throws IOException The buffer does not contain enough bytes to satisfy this request. - */ - @NotNull - public String getNullTerminatedString(int maxLengthBytes, Charset charset) throws IOException - { - return getNullTerminatedStringValue(maxLengthBytes, charset).toString(); - } - - /** - * Creates a String from the stream, ending where byte=='\0' or where length==maxLength. - * - * @param maxLengthBytes The maximum number of bytes to read. If a \0 byte is not reached within this limit, - * reading will stop and the string will be truncated to this length. - * @param charset The Charset to register with the returned StringValue, or null if the encoding - * is unknown - * @return The read string. - * @throws IOException The buffer does not contain enough bytes to satisfy this request. - */ - @NotNull - public StringValue getNullTerminatedStringValue(int maxLengthBytes, Charset charset) throws IOException - { - byte[] bytes = getNullTerminatedBytes(maxLengthBytes); - - return new StringValue(bytes, charset); - } - - /** - * Returns the sequence of bytes punctuated by a \0 value. - * - * @param maxLengthBytes The maximum number of bytes to read. If a \0 byte is not reached within this limit, - * the returned array will be maxLengthBytes long. - * @return The read byte array, excluding the null terminator. - * @throws IOException The buffer does not contain enough bytes to satisfy this request. - */ - @NotNull - public byte[] getNullTerminatedBytes(int maxLengthBytes) throws IOException - { - byte[] buffer = new byte[maxLengthBytes]; - - // Count the number of non-null bytes - int length = 0; - while (length < buffer.length && (buffer[length] = getByte()) != 0) - length++; - - if (length == maxLengthBytes) - return buffer; - - byte[] bytes = new byte[length]; - if (length > 0) - System.arraycopy(buffer, 0, bytes, 0, length); - return bytes; - } -} diff --git a/Source/com/drew/lang/StreamReader.java b/Source/com/drew/lang/StreamReader.java deleted file mode 100644 index 1e30b12ef..000000000 --- a/Source/com/drew/lang/StreamReader.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2002-2017 Drew Noakes - * - * 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.lang; - -import com.drew.lang.annotations.NotNull; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; - -/** - * - * @author Drew Noakes https://drewnoakes.com - */ -public class StreamReader extends SequentialReader -{ - @NotNull - private final InputStream _stream; - - private long _pos; - - @Override - public long getPosition() - { - return _pos; - } - - @SuppressWarnings("ConstantConditions") - public StreamReader(@NotNull InputStream stream) - { - if (stream == null) - throw new NullPointerException(); - - _stream = stream; - _pos = 0; - } - - @Override - public byte getByte() throws IOException - { - int value = _stream.read(); - if (value == -1) - throw new EOFException("End of data reached."); - _pos++; - return (byte)value; - } - - @NotNull - @Override - public byte[] getBytes(int count) throws IOException - { - byte[] bytes = new byte[count]; - getBytes(bytes, 0, count); - return bytes; - } - - @Override - public void getBytes(@NotNull byte[] buffer, int offset, int count) throws IOException - { - int totalBytesRead = 0; - while (totalBytesRead != count) - { - final int bytesRead = _stream.read(buffer, offset + totalBytesRead, count - totalBytesRead); - if (bytesRead == -1) - throw new EOFException("End of data reached."); - totalBytesRead += bytesRead; - assert(totalBytesRead <= count); - } - _pos += totalBytesRead; - } - - @Override - public void skip(long n) throws IOException - { - if (n < 0) - throw new IllegalArgumentException("n must be zero or greater."); - - long skippedCount = skipInternal(n); - - if (skippedCount != n) - throw new EOFException(String.format("Unable to skip. Requested %d bytes but skipped %d.", n, skippedCount)); - } - - @Override - public boolean trySkip(long n) throws IOException - { - if (n < 0) - throw new IllegalArgumentException("n must be zero or greater."); - - return skipInternal(n) == n; - } - - @Override - public int available() { - try { - return _stream.available(); - } catch (IOException e) { - return 0; - } - } - - private long skipInternal(long n) throws IOException - { - // It seems that for some streams, such as BufferedInputStream, that skip can return - // some smaller number than was requested. So loop until we either skip enough, or - // InputStream.skip returns zero. - // - // See http://stackoverflow.com/questions/14057720/robust-skipping-of-data-in-a-java-io-inputstream-and-its-subtypes - // - long skippedTotal = 0; - while (skippedTotal != n) { - long skipped = _stream.skip(n - skippedTotal); - assert(skipped >= 0); - skippedTotal += skipped; - if (skipped == 0) - break; - } - _pos += skippedTotal; - return skippedTotal; - } -} diff --git a/Source/com/drew/metadata/MetadataReader.java b/Source/com/drew/metadata/MetadataReader.java deleted file mode 100644 index 9459fd351..000000000 --- a/Source/com/drew/metadata/MetadataReader.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-2017 Drew Noakes - * - * 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.metadata; - -import com.drew.lang.RandomAccessReader; -import com.drew.lang.annotations.NotNull; - -/** - * Defines an object capable of processing a particular type of metadata from a {@link RandomAccessReader}. - *

    - * Instances of this interface must be thread-safe and reusable. - * - * @author Drew Noakes https://drewnoakes.com - */ -public interface MetadataReader -{ - /** - * Extracts metadata from reader and merges it into the specified {@link Metadata} object. - * - * @param reader The {@link RandomAccessReader} from which the metadata should be extracted. - * @param metadata The {@link Metadata} object into which extracted values should be merged. - */ - void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata); -} diff --git a/Source/com/drew/metadata/adobe/AdobeJpegReader.java b/Source/com/drew/metadata/adobe/AdobeJpegReader.java index d5d910150..a2dc4f83f 100644 --- a/Source/com/drew/metadata/adobe/AdobeJpegReader.java +++ b/Source/com/drew/metadata/adobe/AdobeJpegReader.java @@ -23,8 +23,9 @@ import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.imaging.jpeg.JpegSegment; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; @@ -41,7 +42,8 @@ @SuppressWarnings("WeakerAccess") public class AdobeJpegReader implements JpegSegmentMetadataReader { - public static final String PREAMBLE = "Adobe"; + public static final String JPEG_SEGMENT_ID = "Adobe"; + public static final String JPEG_SEGMENT_PREAMBLE = "Adobe"; @NotNull public Iterable getSegmentTypes() @@ -49,15 +51,15 @@ public Iterable getSegmentTypes() return Collections.singletonList(JpegSegmentType.APPE); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) throws IOException { - for (byte[] bytes : segments) { - if (bytes.length == 12 && PREAMBLE.equalsIgnoreCase(new String(bytes, 0, PREAMBLE.length()))) - extract(new SequentialByteArrayReader(bytes), metadata); + for (JpegSegment segment : segments) { + if (segment.getReader().getLength() == 12 && JPEG_SEGMENT_ID.equals(segment.getPreamble())) + extract(segment.getReader().Clone(), metadata); } } - public void extract(@NotNull SequentialReader reader, @NotNull Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull Metadata metadata) { Directory directory = new AdobeJpegDirectory(); metadata.addDirectory(directory); @@ -65,7 +67,7 @@ public void extract(@NotNull SequentialReader reader, @NotNull Metadata metadata try { reader.setMotorolaByteOrder(false); - if (!reader.getString(PREAMBLE.length()).equals(PREAMBLE)) { + if (!reader.getString(JPEG_SEGMENT_PREAMBLE.length(), Charsets.UTF_8).equals(JPEG_SEGMENT_PREAMBLE)) { directory.addError("Invalid Adobe JPEG data header."); return; } diff --git a/Source/com/drew/metadata/avi/AviRiffHandler.java b/Source/com/drew/metadata/avi/AviRiffHandler.java index b528e51fc..b55ad0111 100644 --- a/Source/com/drew/metadata/avi/AviRiffHandler.java +++ b/Source/com/drew/metadata/avi/AviRiffHandler.java @@ -21,7 +21,7 @@ package com.drew.metadata.avi; import com.drew.imaging.riff.RiffHandler; -import com.drew.lang.ByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; @@ -48,9 +48,6 @@ public class AviRiffHandler implements RiffHandler @NotNull private final AviDirectory _directory; -// @NotNull -// private String _currentList = ""; - public AviRiffHandler(@NotNull Metadata metadata) { _directory = new AviDirectory(); @@ -80,11 +77,11 @@ public boolean shouldAcceptList(@NotNull String fourCC) || fourCC.equals(AviDirectory.FORMAT); } - public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) + public void processChunk(@NotNull String fourCC, @NotNull ReaderInfo chunkReader) { try { if (fourCC.equals(AviDirectory.CHUNK_STREAM_HEADER)) { - ByteArrayReader reader = new ByteArrayReader(payload); + ReaderInfo reader = chunkReader.Clone(); reader.setMotorolaByteOrder(false); String fccType = new String(reader.getBytes(0, 4)); @@ -121,7 +118,7 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) } } } else if (fourCC.equals(AviDirectory.CHUNK_MAIN_HEADER)) { - ByteArrayReader reader = new ByteArrayReader(payload); + ReaderInfo reader = chunkReader.Clone(); reader.setMotorolaByteOrder(false); // int dwMicroSecPerFrame = reader.getInt32(0); diff --git a/Source/com/drew/metadata/bmp/BmpReader.java b/Source/com/drew/metadata/bmp/BmpReader.java index 019f2f7a1..030e30590 100644 --- a/Source/com/drew/metadata/bmp/BmpReader.java +++ b/Source/com/drew/metadata/bmp/BmpReader.java @@ -20,9 +20,8 @@ */ package com.drew.metadata.bmp; -import com.drew.lang.ByteArrayReader; import com.drew.lang.Charsets; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Directory; import com.drew.metadata.ErrorDirectory; @@ -76,7 +75,7 @@ public class BmpReader */ public static final int OS2_POINTER = 0x5450; - public void extract(@NotNull final SequentialReader reader, final @NotNull Metadata metadata) + public void extract(@NotNull final ReaderInfo reader, final @NotNull Metadata metadata) { reader.setMotorolaByteOrder(false); @@ -88,7 +87,7 @@ public void extract(@NotNull final SequentialReader reader, final @NotNull Metad readFileHeader(reader, metadata, true); } - protected void readFileHeader(@NotNull final SequentialReader reader, final @NotNull Metadata metadata, boolean allowArray) { + protected void readFileHeader(@NotNull final ReaderInfo reader, final @NotNull Metadata metadata, boolean allowArray) { /* * There are two possible headers a file can start with. If the magic * number is OS/2 Bitmap Array (0x4142) the OS/2 Bitmap Array Header @@ -136,11 +135,11 @@ protected void readFileHeader(@NotNull final SequentialReader reader, final @Not if (nextHeaderOffset == 0) { return; // No more bitmaps } - if (reader.getPosition() > nextHeaderOffset) { + if (reader.getLocalPosition() > nextHeaderOffset) { addError("Invalid next header offset", metadata); return; } - reader.skip(nextHeaderOffset - reader.getPosition()); + reader.skip(nextHeaderOffset - reader.getLocalPosition()); readFileHeader(reader, metadata, true); break; case BITMAP: @@ -153,7 +152,7 @@ protected void readFileHeader(@NotNull final SequentialReader reader, final @Not directory.setInt(BmpHeaderDirectory.TAG_BITMAP_TYPE, magicNumber); // skip past the rest of the file header reader.skip(4 + 2 + 2 + 4); - readBitmapHeader(reader, (BmpHeaderDirectory) directory, metadata); + readBitmapHeader(reader.Clone(), (BmpHeaderDirectory) directory, metadata); break; default: metadata.addDirectory(new ErrorDirectory("Invalid BMP magic number 0x" + Integer.toHexString(magicNumber))); @@ -168,7 +167,7 @@ protected void readFileHeader(@NotNull final SequentialReader reader, final @Not } } - protected void readBitmapHeader(@NotNull final SequentialReader reader, final @NotNull BmpHeaderDirectory directory, final @NotNull Metadata metadata) { + protected void readBitmapHeader(@NotNull final ReaderInfo reader, final @NotNull BmpHeaderDirectory directory, final @NotNull Metadata metadata) { /* * BITMAPCOREHEADER (12 bytes): * @@ -263,7 +262,7 @@ protected void readBitmapHeader(@NotNull final SequentialReader reader, final @N try { int bitmapType = directory.getInt(BmpHeaderDirectory.TAG_BITMAP_TYPE); - long headerOffset = reader.getPosition(); + long headerOffset = reader.getLocalPosition(); int headerSize = reader.getInt32(); directory.setInt(BmpHeaderDirectory.TAG_HEADER_SIZE, headerSize); @@ -362,16 +361,17 @@ protected void readBitmapHeader(@NotNull final SequentialReader reader, final @N if (csType == ColorSpaceType.PROFILE_EMBEDDED.getValue() || csType == ColorSpaceType.PROFILE_LINKED.getValue()) { long profileOffset = reader.getUInt32(); int profileSize = reader.getInt32(); - if (reader.getPosition() > headerOffset + profileOffset) { + if (reader.getLocalPosition() > headerOffset + profileOffset) { directory.addError("Invalid profile data offset 0x" + Long.toHexString(headerOffset + profileOffset)); return; } - reader.skip(headerOffset + profileOffset - reader.getPosition()); + reader.skip(headerOffset + profileOffset - reader.getLocalPosition()); if (csType == ColorSpaceType.PROFILE_LINKED.getValue()) { directory.setString(BmpHeaderDirectory.TAG_LINKED_PROFILE, reader.getNullTerminatedString(profileSize, Charsets.WINDOWS_1252)); } else { - ByteArrayReader randomAccessReader = new ByteArrayReader(reader.getBytes(profileSize)); - new IccReader().extract(randomAccessReader, metadata, directory); + //ByteArrayReader randomAccessReader = new ByteArrayReader(reader.getBytes(profileSize)); + ReaderInfo iccReader = reader.Clone(profileSize); + new IccReader().extract(iccReader, metadata, directory); } } else { reader.skip( diff --git a/Source/com/drew/metadata/eps/EpsReader.java b/Source/com/drew/metadata/eps/EpsReader.java index 24d8233c5..031a57192 100644 --- a/Source/com/drew/metadata/eps/EpsReader.java +++ b/Source/com/drew/metadata/eps/EpsReader.java @@ -13,7 +13,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; /** * Reads file passed in through SequentialReader and parses encountered data: @@ -48,12 +47,11 @@ public class EpsReader * data and then set the position to the beginning of the PostScript data. If it does not, the position will not * be changed. After both scenarios, the main extract method is called. * - * @param inputStream InputStream containing file + * @param reader ReaderInfo containing file * @param metadata Metadata to add directory to and extracted data */ - public void extract(@NotNull final InputStream inputStream, @NotNull final Metadata metadata) throws IOException + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) throws IOException { - RandomAccessStreamReader reader = new RandomAccessStreamReader(inputStream); EpsDirectory directory = new EpsDirectory(); metadata.addDirectory(directory); @@ -62,6 +60,8 @@ public void extract(@NotNull final InputStream inputStream, @NotNull final Metad * * 0x25215053 (%!PS) signifies an EPS File and leads straight into the PostScript */ + ReaderInfo origreader = reader.Clone(); + switch (reader.getInt32(0)) { case 0xC5D0D3C6: reader.setMotorolaByteOrder(false); @@ -79,8 +79,7 @@ public void extract(@NotNull final InputStream inputStream, @NotNull final Metad directory.setInt(EpsDirectory.TAG_TIFF_PREVIEW_OFFSET, tifOffset); // Get Tiff metadata try { - ByteArrayReader byteArrayReader = new ByteArrayReader(reader.getBytes(tifOffset, tifSize)); - new TiffReader().processTiff(byteArrayReader, new PhotoshopTiffHandler(metadata, null), 0); + new TiffReader().processTiff(reader.Clone(tifOffset, tifSize), new PhotoshopTiffHandler(metadata, null)); } catch (TiffProcessingException ex) { directory.addError("Unable to process TIFF data: " + ex.getMessage()); } @@ -90,11 +89,10 @@ public void extract(@NotNull final InputStream inputStream, @NotNull final Metad } // TODO avoid allocating byte array here -- read directly from InputStream - extract(directory, metadata, new SequentialByteArrayReader(reader.getBytes(postScriptOffset, postScriptLength))); + extract(directory, metadata, reader.Clone(postScriptOffset, postScriptLength)); break; case 0x25215053: - inputStream.reset(); - extract(directory, metadata, new StreamReader(inputStream)); + extract(directory, metadata, origreader); break; default: directory.addError("File type not supported."); @@ -110,7 +108,8 @@ public void extract(@NotNull final InputStream inputStream, @NotNull final Metad * * @param metadata Metadata to add directory to and extracted data */ - private void extract(@NotNull final EpsDirectory directory, @NotNull Metadata metadata, @NotNull SequentialReader reader) throws IOException + //private void extract(@NotNull final EpsDirectory directory, @NotNull Metadata metadata, @NotNull SequentialReader reader) throws IOException + private void extract(@NotNull final EpsDirectory directory, @NotNull Metadata metadata, @NotNull ReaderInfo reader) throws IOException { StringBuilder line = new StringBuilder(); @@ -229,29 +228,29 @@ else if (colorType == 4) /** * Decodes a commented hex section, and uses {@link PhotoshopReader} to decode the resulting data. */ - private static void extractPhotoshopData(@NotNull final Metadata metadata, @NotNull SequentialReader reader) throws IOException + private static void extractPhotoshopData(@NotNull final Metadata metadata, @NotNull ReaderInfo reader) throws IOException { byte[] buffer = decodeHexCommentBlock(reader); if (buffer != null) - new PhotoshopReader().extract(new SequentialByteArrayReader(buffer), buffer.length, metadata); + new PhotoshopReader().extract(ReaderInfo.createFromArray(buffer), metadata); } /** * Decodes a commented hex section, and uses {@link IccReader} to decode the resulting data. */ - private static void extractIccData(@NotNull final Metadata metadata, @NotNull SequentialReader reader) throws IOException + private static void extractIccData(@NotNull final Metadata metadata, @NotNull ReaderInfo reader) throws IOException { byte[] buffer = decodeHexCommentBlock(reader); if (buffer != null) - new IccReader().extract(new ByteArrayReader(buffer), metadata); + new IccReader().extract(ReaderInfo.createFromArray(buffer), metadata); } /** * Extracts an XMP xpacket, and uses {@link XmpReader} to decode the resulting data. */ - private static void extractXmpData(@NotNull final Metadata metadata, @NotNull SequentialReader reader) throws IOException + private static void extractXmpData(@NotNull final Metadata metadata, @NotNull ReaderInfo reader) throws IOException { byte[] bytes = readUntil(reader, "".getBytes()); String xmp = new String(bytes, Charsets.UTF_8); @@ -262,7 +261,7 @@ private static void extractXmpData(@NotNull final Metadata metadata, @NotNull Se * Reads all bytes until the given sentinel is observed. * The sentinel will be included in the returned bytes. */ - private static byte[] readUntil(@NotNull SequentialReader reader, @NotNull byte[] sentinel) throws IOException + private static byte[] readUntil(@NotNull ReaderInfo reader, @NotNull byte[] sentinel) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); @@ -301,7 +300,7 @@ private static byte[] readUntil(@NotNull SequentialReader reader, @NotNull byte[ * @return The decoded bytes, or null if decoding failed. */ @Nullable - private static byte[] decodeHexCommentBlock(@NotNull SequentialReader reader) throws IOException + private static byte[] decodeHexCommentBlock(@NotNull ReaderInfo reader) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); diff --git a/Source/com/drew/metadata/exif/ExifDescriptorBase.java b/Source/com/drew/metadata/exif/ExifDescriptorBase.java index 45d2e2d33..4f2243966 100644 --- a/Source/com/drew/metadata/exif/ExifDescriptorBase.java +++ b/Source/com/drew/metadata/exif/ExifDescriptorBase.java @@ -25,7 +25,7 @@ import com.drew.lang.Rational; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; -import com.drew.lang.ByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Directory; import com.drew.metadata.TagDescriptor; @@ -1139,7 +1139,7 @@ private int[] decodeCfaPattern(int tagType) ret = new int[values.length - 2]; try { - ByteArrayReader reader = new ByteArrayReader(values); + ReaderInfo reader = ReaderInfo.createFromArray(values); // first two values should be read as 16-bits (2 bytes) short item0 = reader.getInt16(0); diff --git a/Source/com/drew/metadata/exif/ExifReader.java b/Source/com/drew/metadata/exif/ExifReader.java index ffc77c67c..3c143bc71 100644 --- a/Source/com/drew/metadata/exif/ExifReader.java +++ b/Source/com/drew/metadata/exif/ExifReader.java @@ -22,10 +22,10 @@ import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; +import com.drew.imaging.jpeg.JpegSegment; import com.drew.imaging.tiff.TiffProcessingException; import com.drew.imaging.tiff.TiffReader; -import com.drew.lang.ByteArrayReader; -import com.drew.lang.RandomAccessReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Directory; @@ -44,6 +44,7 @@ @SuppressWarnings("WeakerAccess") public class ExifReader implements JpegSegmentMetadataReader { + public static final String JPEG_SEGMENT_ID = "Exif"; /** Exif data stored in JPEG files' APP1 segment are preceded by this six character preamble. */ public static final String JPEG_SEGMENT_PREAMBLE = "Exif\0\0"; @@ -53,41 +54,33 @@ public Iterable getSegmentTypes() return Collections.singletonList(JpegSegmentType.APP1); } - public void readJpegSegments(@NotNull final Iterable segments, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) + @Override + public void readJpegSegments(@NotNull final Iterable segments, @NotNull final Metadata metadata) throws IOException { - assert(segmentType == JpegSegmentType.APP1); - - for (byte[] segmentBytes : segments) { + //assert(segmentType == JpegSegmentType.APP1); + for (JpegSegment segment : segments) { // Filter any segments containing unexpected preambles - if (segmentBytes.length < JPEG_SEGMENT_PREAMBLE.length() || !new String(segmentBytes, 0, JPEG_SEGMENT_PREAMBLE.length()).equals(JPEG_SEGMENT_PREAMBLE)) - continue; - extract(new ByteArrayReader(segmentBytes), metadata, JPEG_SEGMENT_PREAMBLE.length()); + if (segment.getReader().getLength() >= JPEG_SEGMENT_PREAMBLE.length() && JPEG_SEGMENT_ID.equals(segment.getPreamble())) + extract(segment.getReader().Clone(JPEG_SEGMENT_PREAMBLE.length(), segment.getReader().getLength() - JPEG_SEGMENT_PREAMBLE.length()), metadata); } } - /** 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) + /** Reads TIFF formatted Exif data within a {@link ReaderInfo}. */ + public void extract(@NotNull final ReaderInfo reader, @NotNull final Metadata metadata) { - extract(reader, metadata, readerOffset, null); + extract(reader, metadata, null); } - /** 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) + /** Reads TIFF formatted Exif data within a {@link ReaderInfo}. */ + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata, @Nullable Directory parentDirectory) { ExifTiffHandler exifTiffHandler = new ExifTiffHandler(metadata, parentDirectory); try { // Read the TIFF-formatted Exif data new TiffReader().processTiff( - reader, - exifTiffHandler, - readerOffset + reader, + 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 04f66ec95..623dea443 100644 --- a/Source/com/drew/metadata/exif/ExifTiffHandler.java +++ b/Source/com/drew/metadata/exif/ExifTiffHandler.java @@ -25,10 +25,8 @@ import com.drew.imaging.jpeg.JpegMetadataReader; import com.drew.imaging.jpeg.JpegProcessingException; import com.drew.lang.BufferBoundsException; -import com.drew.lang.ByteArrayReader; import com.drew.lang.Charsets; -import com.drew.lang.RandomAccessReader; -import com.drew.lang.SequentialByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Directory; @@ -41,10 +39,10 @@ import com.drew.metadata.tiff.DirectoryTiffHandler; import com.drew.metadata.xmp.XmpReader; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; import java.util.Set; +//import javax.xml.parsers.DocumentBuilderFactory; /** * Implementation of {@link com.drew.imaging.tiff.TiffHandler} used for handling TIFF tags according to the Exif @@ -61,6 +59,7 @@ public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDir super(metadata, parentDirectory); } + @Override public void setTiffMarker(int marker) throws TiffProcessingException { final int standardTiffMarker = 0x002A; @@ -82,6 +81,7 @@ public void setTiffMarker(int marker) throws TiffProcessingException } } + @Override public boolean tryEnterSubIfd(int tagId) { if (tagId == ExifDirectoryBase.TAG_SUB_IFD_OFFSET) { @@ -142,6 +142,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. @@ -166,6 +167,7 @@ public boolean hasFollowerIfd() } @Nullable + @Override public Long tryCustomProcessFormat(final int tagId, final int formatCode, final long componentCount) { if (formatCode == 13) @@ -178,10 +180,10 @@ 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 @NotNull Set processedIfdOffsets, + @NotNull ReaderInfo reader, final int tagId, final int byteCount) throws IOException { @@ -201,15 +203,14 @@ 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.Clone(tagOffset, -1)); } // Custom processing for embedded IPTC data if (tagId == ExifSubIFDDirectory.TAG_IPTC_NAA && _currentDirectory instanceof ExifIFD0Directory) { // NOTE Adobe sets type 4 for IPTC instead of 7 if (reader.getInt8(tagOffset) == 0x1c) { - final byte[] iptcBytes = reader.getBytes(tagOffset, byteCount); - new IptcReader().extract(new SequentialByteArrayReader(iptcBytes), _metadata, iptcBytes.length, _currentDirectory); + new IptcReader().extract(reader.Clone(tagOffset, byteCount), _metadata, _currentDirectory); return true; } return false; @@ -217,20 +218,18 @@ public boolean customProcessTag(final int tagOffset, // Custom processing for ICC Profile data if (tagId == ExifSubIFDDirectory.TAG_INTER_COLOR_PROFILE) { - final byte[] iccBytes = reader.getBytes(tagOffset, byteCount); - new IccReader().extract(new ByteArrayReader(iccBytes), _metadata); + new IccReader().extract(reader.Clone(tagOffset, byteCount), _metadata); return true; } // Custom processing for Photoshop data if (tagId == ExifSubIFDDirectory.TAG_PHOTOSHOP_SETTINGS && _currentDirectory instanceof ExifIFD0Directory) { - final byte[] photoshopBytes = reader.getBytes(tagOffset, byteCount); - new PhotoshopReader().extract(new SequentialByteArrayReader(photoshopBytes), byteCount, _metadata); + new PhotoshopReader().extract(reader.Clone(tagOffset, byteCount), _metadata); return true; } // Custom processing for embedded XMP data - if (tagId == ExifSubIFDDirectory.TAG_APPLICATION_NOTES && _currentDirectory instanceof ExifIFD0Directory) { + if (tagId == ExifSubIFDDirectory.TAG_APPLICATION_NOTES && _currentDirectory instanceof ExifIFD0Directory) { new XmpReader().extract(reader.getNullTerminatedBytes(tagOffset, byteCount), _metadata, _currentDirectory); return true; } @@ -250,35 +249,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); return true; case OlympusMakernoteDirectory.TAG_CAMERA_SETTINGS: pushDirectory(OlympusCameraSettingsMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT: pushDirectory(OlympusRawDevelopmentMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_RAW_DEVELOPMENT_2: pushDirectory(OlympusRawDevelopment2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_IMAGE_PROCESSING: pushDirectory(OlympusImageProcessingMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_FOCUS_INFO: pushDirectory(OlympusFocusInfoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_RAW_INFO: pushDirectory(OlympusRawInfoMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; case OlympusMakernoteDirectory.TAG_MAIN_INFO: pushDirectory(OlympusMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, tagOffset); return true; } } @@ -310,12 +309,13 @@ public boolean customProcessTag(final int tagOffset, // Panasonic RAW sometimes contains an embedded version of the data as a JPG file. if (tagId == PanasonicRawIFD0Directory.TagJpgFromRaw && _currentDirectory instanceof PanasonicRawIFD0Directory) { - byte[] jpegrawbytes = reader.getBytes(tagOffset, byteCount); + ReaderInfo jpgreader = reader.Clone(tagOffset, byteCount); + jpgreader.setMotorolaByteOrder(true); // Extract information from embedded image since it is metadata-rich - ByteArrayInputStream jpegmem = new ByteArrayInputStream(jpegrawbytes); + try { - Metadata jpegDirectory = JpegMetadataReader.readMetadata(jpegmem); + Metadata jpegDirectory = JpegMetadataReader.readMetadata(jpgreader); //jpegmem); for (Directory directory : jpegDirectory.getDirectories()) { directory.setParent(_currentDirectory); _metadata.addDirectory(directory); @@ -331,7 +331,7 @@ public boolean customProcessTag(final int tagOffset, return false; } - private static void processBinary(@NotNull final Directory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount, final Boolean isSigned, final int arrayLength) throws IOException + private static void processBinary(@NotNull final Directory directory, final int tagValueOffset, @NotNull final ReaderInfo reader, final int byteCount, final Boolean isSigned, final int arrayLength) throws IOException { // expects signed/unsigned int16 (for now) //int byteSize = isSigned ? sizeof(short) : sizeof(ushort); @@ -377,19 +377,18 @@ private static void processBinary(@NotNull final Directory directory, final int * a full-on failure. */ @NotNull - private static String getReaderString(final @NotNull RandomAccessReader reader, final int makernoteOffset, final int bytesRequested) throws IOException + private static String getReaderString(final @NotNull ReaderInfo reader, final int bytesRequested) throws IOException { try { - return reader.getString(makernoteOffset, bytesRequested, Charsets.UTF_8); + return reader.getString(0, bytesRequested, Charsets.UTF_8); } catch(BufferBoundsException e) { return ""; } } private boolean processMakernote(final int makernoteOffset, - final @NotNull Set processedIfdOffsets, - final int tiffHeaderOffset, - final @NotNull RandomAccessReader reader) throws IOException + final @NotNull Set processedIfdOffsets, + @NotNull ReaderInfo reader) throws IOException { assert(_currentDirectory != null); @@ -398,35 +397,33 @@ private boolean processMakernote(final int makernoteOffset, String cameraMake = ifd0Directory == null ? null : ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE); - final String firstTwoChars = getReaderString(reader, makernoteOffset, 2); - final String firstThreeChars = getReaderString(reader, makernoteOffset, 3); - final String firstFourChars = getReaderString(reader, makernoteOffset, 4); - final String firstFiveChars = getReaderString(reader, makernoteOffset, 5); - final String firstSixChars = getReaderString(reader, makernoteOffset, 6); - final String firstSevenChars = getReaderString(reader, makernoteOffset, 7); - final String firstEightChars = getReaderString(reader, makernoteOffset, 8); - final String firstNineChars = getReaderString(reader, makernoteOffset, 9); - final String firstTenChars = getReaderString(reader, makernoteOffset, 10); - final String firstTwelveChars = getReaderString(reader, makernoteOffset, 12); - - boolean byteOrderBefore = reader.isMotorolaByteOrder(); + final String firstTwoChars = getReaderString(reader, 2); + final String firstThreeChars = getReaderString(reader, 3); + final String firstFourChars = getReaderString(reader, 4); + final String firstFiveChars = getReaderString(reader, 5); + final String firstSixChars = getReaderString(reader, 6); + final String firstSevenChars = getReaderString(reader, 7); + final String firstEightChars = getReaderString(reader, 8); + final String firstNineChars = getReaderString(reader, 9); + final String firstTenChars = getReaderString(reader, 10); + final String firstTwelveChars = getReaderString(reader, 12); if ("OLYMP\0".equals(firstSixChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) { // 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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset + 8); } 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, processedIfdOffsets, 12); } 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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset); } else if (cameraMake != null && cameraMake.trim().toUpperCase().startsWith("NIKON")) { if ("Nikon".equals(firstFiveChars)) { /* There are two scenarios here: @@ -437,14 +434,14 @@ private boolean processMakernote(final int makernoteOffset, * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*... * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200 */ - switch (reader.getUInt8(makernoteOffset + 6)) { + switch (reader.getUInt8(6)) { case 1: pushDirectory(NikonType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); + TiffReader.processIfd(this, reader.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset + 8); break; case 2: pushDirectory(NikonType2MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 18, makernoteOffset + 10); + TiffReader.processIfd(this, reader.Clone(10, -1), processedIfdOffsets, 8); break; default: _currentDirectory.addError("Unsupported Nikon makernote data ignored."); @@ -453,41 +450,41 @@ 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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset); } } 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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset + 12); // 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 }) ) { + !Arrays.equals(reader.getBytes(0, 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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset); } 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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset + 20); } 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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset + 10); } else if ("KDK".equals(firstThreeChars)) { reader.setMotorolaByteOrder(firstSevenChars.equals("KDK INFO")); KodakMakernoteDirectory directory = new KodakMakernoteDirectory(); _metadata.addDirectory(directory); - processKodakMakernote(directory, makernoteOffset, reader); + processKodakMakernote(directory, makernoteOffset, reader.Clone(-makernoteOffset, -1)); } else if ("Canon".equalsIgnoreCase(cameraMake)) { pushDirectory(CanonMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset); } 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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset + 6); } else { pushDirectory(CasioType1MakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset, tiffHeaderOffset); + TiffReader.processIfd(this, reader.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset); } } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraMake)) { // Note that this also applies to certain Leica cameras, such as the Digilux-4.3 @@ -495,13 +492,13 @@ 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); + int ifdStart = reader.getInt32(8); pushDirectory(FujifilmMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, ifdStart, makernoteOffset); + TiffReader.processIfd(this, reader, processedIfdOffsets, ifdStart); } 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, 22); } else if ("LEICA".equals(firstFiveChars)) { reader.setMotorolaByteOrder(false); @@ -519,14 +516,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, processedIfdOffsets, 8); } else if ("Leica Camera AG".equals(cameraMake)) { pushDirectory(LeicaMakernoteDirectory.class); - TiffReader.processIfd(this, reader, processedIfdOffsets, makernoteOffset + 8, tiffHeaderOffset); + TiffReader.processIfd(this, reader.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset + 8); } 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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset + 8); } else { return false; } @@ -535,7 +532,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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset + 12); } 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 @@ -543,7 +540,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, processedIfdOffsets, 6); } 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 @@ -552,7 +549,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, processedIfdOffsets, 0); // } 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 @@ -560,7 +557,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, processedIfdOffsets, 8); } 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: @@ -573,34 +570,33 @@ 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, processedIfdOffsets, 8); } } 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, processedIfdOffsets, 14); reader.setMotorolaByteOrder(orderBefore); - } else if (reader.getUInt16(makernoteOffset) == ReconyxHyperFireMakernoteDirectory.MAKERNOTE_VERSION) { + } else if (reader.getUInt16(0) == ReconyxHyperFireMakernoteDirectory.MAKERNOTE_VERSION) { ReconyxHyperFireMakernoteDirectory directory = new ReconyxHyperFireMakernoteDirectory(); _metadata.addDirectory(directory); - processReconyxHyperFireMakernote(directory, makernoteOffset, reader); + processReconyxHyperFireMakernote(directory, makernoteOffset, reader.Clone(-makernoteOffset, -1)); } else if (firstNineChars.equalsIgnoreCase("RECONYXUF")) { ReconyxUltraFireMakernoteDirectory directory = new ReconyxUltraFireMakernoteDirectory(); _metadata.addDirectory(directory); - processReconyxUltraFireMakernote(directory, makernoteOffset, reader); + processReconyxUltraFireMakernote(directory, makernoteOffset, reader.Clone(-makernoteOffset, -1)); } else if ("SAMSUNG".equals(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.Clone(-makernoteOffset, -1), processedIfdOffsets, makernoteOffset); } 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. return false; } - reader.setMotorolaByteOrder(byteOrderBefore); return true; } @@ -634,9 +630,9 @@ private static boolean handlePrintIM(@NotNull final Directory directory, final i /// http://www.sno.phy.queensu.ca/~phil/exiftool/ /// lib\Image\ExifTool\PrintIM.pm /// - private static void processPrintIM(@NotNull final PrintIMDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount) throws IOException + private static void processPrintIM(@NotNull final PrintIMDirectory directory, final int tagValueOffset, @NotNull final ReaderInfo reader, final int byteCount) throws IOException { - Boolean resetByteOrder = null; + //Boolean resetByteOrder = null; if (byteCount == 0) { directory.addError("Empty PrintIM data"); @@ -655,14 +651,14 @@ private static void processPrintIM(@NotNull final PrintIMDirectory directory, fi return; } + ReaderInfo localReader = reader; // check size of PrintIM block - int num = reader.getUInt16(tagValueOffset + 14); - + int num = localReader.getUInt16(tagValueOffset + 14); if (byteCount < 16 + num * 6) { // size is too big, maybe byte ordering is wrong - resetByteOrder = reader.isMotorolaByteOrder(); - reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder()); - num = reader.getUInt16(tagValueOffset + 14); + localReader = reader.Clone(false); + + num = localReader.getUInt16(tagValueOffset + 14); if (byteCount < 16 + num * 6) { directory.addError("Bad PrintIM size"); return; @@ -673,17 +669,14 @@ private static void processPrintIM(@NotNull final PrintIMDirectory directory, fi for (int n = 0; n < num; n++) { int pos = tagValueOffset + 16 + n * 6; - int tag = reader.getUInt16(pos); - long val = reader.getUInt32(pos + 2); + int tag = localReader.getUInt16(pos); + long val = localReader.getUInt32(pos + 2); directory.setObject(tag, val); } - - if (resetByteOrder != null) - reader.setMotorolaByteOrder(resetByteOrder); } - private static void processKodakMakernote(@NotNull final KodakMakernoteDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader) + private static void processKodakMakernote(@NotNull final KodakMakernoteDirectory directory, final int tagValueOffset, @NotNull final ReaderInfo reader) { // Kodak's makernote is not in IFD format. It has values at fixed offsets. int dataOffset = tagValueOffset + 8; @@ -719,7 +712,7 @@ private static void processKodakMakernote(@NotNull final KodakMakernoteDirectory } } - private static void processReconyxHyperFireMakernote(@NotNull final ReconyxHyperFireMakernoteDirectory directory, final int makernoteOffset, @NotNull final RandomAccessReader reader) throws IOException + private static void processReconyxHyperFireMakernote(@NotNull final ReconyxHyperFireMakernoteDirectory directory, final int makernoteOffset, @NotNull ReaderInfo reader) throws IOException { directory.setObject(ReconyxHyperFireMakernoteDirectory.TAG_MAKERNOTE_VERSION, reader.getUInt16(makernoteOffset)); @@ -791,7 +784,7 @@ private static void processReconyxHyperFireMakernote(@NotNull final ReconyxHyper directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_USER_LABEL, reader.getNullTerminatedString(makernoteOffset + ReconyxHyperFireMakernoteDirectory.TAG_USER_LABEL, 44, Charsets.UTF_8)); } - private static void processReconyxUltraFireMakernote(@NotNull final ReconyxUltraFireMakernoteDirectory directory, final int makernoteOffset, @NotNull final RandomAccessReader reader) throws IOException + private static void processReconyxUltraFireMakernote(@NotNull final ReconyxUltraFireMakernoteDirectory directory, final int makernoteOffset, @NotNull ReaderInfo reader) throws IOException { directory.setString(ReconyxUltraFireMakernoteDirectory.TAG_LABEL, reader.getString(makernoteOffset, 9, Charsets.UTF_8)); /*uint makernoteID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernoteID)); diff --git a/Source/com/drew/metadata/exif/makernotes/NikonType2MakernoteDescriptor.java b/Source/com/drew/metadata/exif/makernotes/NikonType2MakernoteDescriptor.java index d105d0f46..740e23c01 100644 --- a/Source/com/drew/metadata/exif/makernotes/NikonType2MakernoteDescriptor.java +++ b/Source/com/drew/metadata/exif/makernotes/NikonType2MakernoteDescriptor.java @@ -25,6 +25,7 @@ import com.drew.lang.annotations.Nullable; import com.drew.metadata.TagDescriptor; +import java.nio.ByteBuffer; import java.text.DecimalFormat; import static com.drew.metadata.exif.makernotes.NikonType2MakernoteDirectory.*; @@ -104,7 +105,15 @@ public String getDescription(int tagType) @Nullable public String getPowerUpTimeDescription() { - return getEpochTimeDescription(TAG_POWER_UP_TIME); + // this is generally a byte[] of length 8 directly representing a date and time. + // the format is : first 2 bytes together are the year, and then each byte after + // is month, day, hour, minute, second with the eighth byte unused + // e.g., 2011:04:25 01:54:58 + + byte[] values = _directory.getByteArray(TAG_POWER_UP_TIME); + short year = ByteBuffer.wrap(new byte[]{values[0], values[1]}).getShort(); + return String.format("%04d:%02d:%02d %02d:%02d:%02d", year, values[2], values[3], + values[4], values[5], values[6]); } @Nullable diff --git a/Source/com/drew/metadata/exif/makernotes/OlympusMakernoteDirectory.java b/Source/com/drew/metadata/exif/makernotes/OlympusMakernoteDirectory.java index 2856a6603..ae6d5deec 100644 --- a/Source/com/drew/metadata/exif/makernotes/OlympusMakernoteDirectory.java +++ b/Source/com/drew/metadata/exif/makernotes/OlympusMakernoteDirectory.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.exif.makernotes; -import com.drew.lang.SequentialByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Directory; @@ -476,7 +476,7 @@ public void setByteArray(int tagType, @NotNull byte[] bytes) private void processCameraSettings(byte[] bytes) { - SequentialByteArrayReader reader = new SequentialByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); reader.setMotorolaByteOrder(true); int count = bytes.length / 4; diff --git a/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDescriptor.java b/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDescriptor.java index d5e4943fb..5426a2221 100644 --- a/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDescriptor.java +++ b/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDescriptor.java @@ -20,9 +20,8 @@ */ package com.drew.metadata.exif.makernotes; -import com.drew.lang.ByteArrayReader; import com.drew.lang.Charsets; -import com.drew.lang.RandomAccessReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Age; @@ -275,7 +274,7 @@ private String getTransformDescription(int tag) if (values == null) return null; - RandomAccessReader reader = new ByteArrayReader(values); + ReaderInfo reader = ReaderInfo.createFromArray(values); try { diff --git a/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java b/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java index b2b6c25d0..277d4cfba 100644 --- a/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java +++ b/Source/com/drew/metadata/exif/makernotes/PanasonicMakernoteDirectory.java @@ -20,8 +20,7 @@ */ package com.drew.metadata.exif.makernotes; -import com.drew.lang.ByteArrayReader; -import com.drew.lang.RandomAccessReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Age; @@ -637,7 +636,7 @@ public Face[] getDetectedFaces() if (bytes==null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); reader.setMotorolaByteOrder(false); try { @@ -668,7 +667,7 @@ public Face[] getRecognizedFaces() if (bytes == null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); reader.setMotorolaByteOrder(false); try { diff --git a/Source/com/drew/metadata/file/FileSystemMetadataReader.java b/Source/com/drew/metadata/file/FileSystemMetadataReader.java index ce58f4e93..9bc0b68ee 100644 --- a/Source/com/drew/metadata/file/FileSystemMetadataReader.java +++ b/Source/com/drew/metadata/file/FileSystemMetadataReader.java @@ -27,7 +27,7 @@ import java.util.Date; public class FileSystemMetadataReader -{ +{ public void read(@NotNull File file, @NotNull Metadata metadata) throws IOException { if (!file.isFile()) diff --git a/Source/com/drew/metadata/gif/GifReader.java b/Source/com/drew/metadata/gif/GifReader.java index 289e6b0db..e4e9360d2 100644 --- a/Source/com/drew/metadata/gif/GifReader.java +++ b/Source/com/drew/metadata/gif/GifReader.java @@ -20,9 +20,8 @@ */ package com.drew.metadata.gif; -import com.drew.lang.ByteArrayReader; import com.drew.lang.Charsets; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; @@ -31,7 +30,6 @@ import com.drew.metadata.icc.IccReader; import com.drew.metadata.xmp.XmpReader; -import java.io.ByteArrayOutputStream; import java.io.IOException; /** @@ -52,9 +50,10 @@ public class GifReader private static final String GIF_87A_VERSION_IDENTIFIER = "87a"; private static final String GIF_89A_VERSION_IDENTIFIER = "89a"; - public void extract(@NotNull final SequentialReader reader, final @NotNull Metadata metadata) + public void extract(@NotNull ReaderInfo reader, final @NotNull Metadata metadata) throws IOException { - reader.setMotorolaByteOrder(false); + if(reader.isMotorolaByteOrder()) + reader = reader.Clone(false); GifHeaderDirectory header; try { @@ -131,7 +130,7 @@ public void extract(@NotNull final SequentialReader reader, final @NotNull Metad } } - private static GifHeaderDirectory readGifHeader(@NotNull final SequentialReader reader) throws IOException + private static GifHeaderDirectory readGifHeader(@NotNull ReaderInfo reader) throws IOException { // FILE HEADER // @@ -152,7 +151,7 @@ private static GifHeaderDirectory readGifHeader(@NotNull final SequentialReader GifHeaderDirectory headerDirectory = new GifHeaderDirectory(); - String signature = reader.getString(3); + String signature = reader.getString(3, Charsets.UTF_8); if (!signature.equals("GIF")) { @@ -160,7 +159,7 @@ private static GifHeaderDirectory readGifHeader(@NotNull final SequentialReader return headerDirectory; } - String version = reader.getString(3); + String version = reader.getString(3, Charsets.UTF_8); if (!version.equals(GIF_87A_VERSION_IDENTIFIER) && !version.equals(GIF_89A_VERSION_IDENTIFIER)) { headerDirectory.addError("Unexpected GIF version"); @@ -202,11 +201,11 @@ private static GifHeaderDirectory readGifHeader(@NotNull final SequentialReader return headerDirectory; } - private static void readGifExtensionBlock(SequentialReader reader, Metadata metadata) throws IOException + private static void readGifExtensionBlock(ReaderInfo reader, Metadata metadata) throws IOException { byte extensionLabel = reader.getInt8(); short blockSizeBytes = reader.getUInt8(); - long blockStartPos = reader.getPosition(); + long blockStartPos = reader.getLocalPosition(); switch (extensionLabel) { @@ -229,13 +228,13 @@ private static void readGifExtensionBlock(SequentialReader reader, Metadata meta break; } - long skipCount = blockStartPos + blockSizeBytes - reader.getPosition(); + long skipCount = blockStartPos + blockSizeBytes - reader.getLocalPosition(); if (skipCount > 0) reader.skip(skipCount); } @Nullable - private static Directory readPlainTextBlock(SequentialReader reader, int blockSizeBytes) throws IOException + private static Directory readPlainTextBlock(ReaderInfo reader, int blockSizeBytes) throws IOException { // It seems this extension is deprecated. If somebody finds an image with this in it, could implement here. // Just skip the entire block for now. @@ -252,13 +251,13 @@ private static Directory readPlainTextBlock(SequentialReader reader, int blockSi return null; } - private static GifCommentDirectory readCommentBlock(SequentialReader reader, int blockSizeBytes) throws IOException + private static GifCommentDirectory readCommentBlock(ReaderInfo reader, int blockSizeBytes) throws IOException { - byte[] buffer = gatherBytes(reader, blockSizeBytes); - return new GifCommentDirectory(new StringValue(buffer, Charsets.ASCII)); + ReaderInfo buffer = gatherBytes(reader, blockSizeBytes); + return new GifCommentDirectory(new StringValue(buffer.toArray(), Charsets.ASCII)); } - private static void readApplicationExtensionBlock(SequentialReader reader, int blockSizeBytes, Metadata metadata) throws IOException + private static void readApplicationExtensionBlock(ReaderInfo reader, int blockSizeBytes, Metadata metadata) throws IOException { if (blockSizeBytes != 11) { @@ -271,15 +270,15 @@ private static void readApplicationExtensionBlock(SequentialReader reader, int b if (extensionType.equals("XMP DataXMP")) { // XMP data extension - byte[] xmpBytes = gatherBytes(reader); - new XmpReader().extract(xmpBytes, 0, xmpBytes.length - 257, metadata, null); + ReaderInfo xmpBytes = gatherBytes(reader); + new XmpReader().extract(xmpBytes.Clone(xmpBytes.getLength() - 257), metadata); } else if (extensionType.equals("ICCRGBG1012")) { // ICC profile extension - byte[] iccBytes = gatherBytes(reader, ((int) reader.getByte()) & 0xff); - if (iccBytes.length != 0) - new IccReader().extract(new ByteArrayReader(iccBytes), metadata); + ReaderInfo iccBytes = gatherBytes(reader, ((int)reader.getByte()) & 0xff); + if (iccBytes.getLength() != 0) + new IccReader().extract(iccBytes, metadata); } else if (extensionType.equals("NETSCAPE2.0")) { @@ -299,7 +298,7 @@ else if (extensionType.equals("NETSCAPE2.0")) } } - private static GifControlDirectory readControlBlock(SequentialReader reader, int blockSizeBytes) throws IOException + private static GifControlDirectory readControlBlock(ReaderInfo reader, int blockSizeBytes) throws IOException { if (blockSizeBytes < 4) blockSizeBytes = 4; @@ -319,7 +318,7 @@ private static GifControlDirectory readControlBlock(SequentialReader reader, int return directory; } - private static GifImageDirectory readImageBlock(SequentialReader reader) throws IOException + private static GifImageDirectory readImageBlock(ReaderInfo reader) throws IOException { GifImageDirectory imageDirectory = new GifImageDirectory(); @@ -353,42 +352,41 @@ private static GifImageDirectory readImageBlock(SequentialReader reader) throws return imageDirectory; } - private static byte[] gatherBytes(SequentialReader reader) throws IOException + private static ReaderInfo gatherBytes(ReaderInfo reader) throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - byte[] buffer = new byte[257]; + int length = 0; while (true) { byte b = reader.getByte(); if (b == 0) - return bytes.toByteArray(); + break; int bInt = b & 0xFF; + reader.skip(bInt); - buffer[0] = b; - reader.getBytes(buffer, 1, bInt); - bytes.write(buffer, 0, bInt + 1); + length += bInt + 1; } + + return reader.Clone(-length - 1, length); } - private static byte[] gatherBytes(SequentialReader reader, int firstLength) throws IOException + private static ReaderInfo gatherBytes(ReaderInfo reader, int firstLength) throws IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int length = firstLength; - + int readerLength = 0; + while (length > 0) { - buffer.write(reader.getBytes(length), 0, length); - + reader.skip(length); + readerLength += length; length = reader.getByte() & 0xff; } - return buffer.toByteArray(); + return reader.Clone(-readerLength - 1, readerLength); } - private static void skipBlocks(SequentialReader reader) throws IOException + private static void skipBlocks(ReaderInfo reader) throws IOException { while (true) { diff --git a/Source/com/drew/metadata/heif/HeifBoxHandler.java b/Source/com/drew/metadata/heif/HeifBoxHandler.java index f40ee2984..d0aa23741 100644 --- a/Source/com/drew/metadata/heif/HeifBoxHandler.java +++ b/Source/com/drew/metadata/heif/HeifBoxHandler.java @@ -21,8 +21,7 @@ package com.drew.metadata.heif; import com.drew.imaging.heif.HeifHandler; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.heif.boxes.*; @@ -70,14 +69,13 @@ public boolean shouldAcceptContainer(Box box) } @Override - public HeifHandler processBox(@NotNull Box box, @NotNull byte[] payload) throws IOException + public HeifHandler processBox(@NotNull Box box, @NotNull ReaderInfo payloadReader) throws IOException { - if (payload != null) { - SequentialReader reader = new SequentialByteArrayReader(payload); + if (payloadReader != null) { if (box.type.equals(HeifBoxTypes.BOX_FILE_TYPE)) { - processFileType(reader, box); + processFileType(payloadReader, box); }else if (box.type.equals(HeifBoxTypes.BOX_HANDLER)) { - handlerBox = new HandlerBox(reader, box); + handlerBox = new HandlerBox(payloadReader, box); return handlerFactory.getHandler(handlerBox, metadata); } } @@ -85,14 +83,14 @@ public HeifHandler processBox(@NotNull Box box, @NotNull byte[] payload) throws } @Override - public void processContainer(@NotNull Box box, @NotNull SequentialReader reader) throws IOException + public void processContainer(@NotNull Box box, @NotNull ReaderInfo reader) throws IOException { if (box.type.equals(HeifContainerTypes.BOX_METADATA)) { new FullBox(reader, box); } } - private void processFileType(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + private void processFileType(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { FileTypeBox fileTypeBox = new FileTypeBox(reader, box); fileTypeBox.addMetadata(directory); diff --git a/Source/com/drew/metadata/heif/HeifPictureHandler.java b/Source/com/drew/metadata/heif/HeifPictureHandler.java index 6cec834cd..eb8fa4107 100644 --- a/Source/com/drew/metadata/heif/HeifPictureHandler.java +++ b/Source/com/drew/metadata/heif/HeifPictureHandler.java @@ -21,8 +21,7 @@ package com.drew.metadata.heif; import com.drew.imaging.heif.HeifHandler; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.heif.boxes.*; @@ -71,29 +70,29 @@ protected boolean shouldAcceptContainer(Box box) } @Override - protected HeifHandler processBox(Box box, byte[] payload) throws IOException + //protected HeifHandler processBox(Box box, byte[] payload) throws IOException + protected HeifHandler processBox(Box box, ReaderInfo payloadReader) throws IOException { - SequentialReader reader = new SequentialByteArrayReader(payload); if (box.type.equals(HeifBoxTypes.BOX_ITEM_PROTECTION)) { - itemProtectionBox = new ItemProtectionBox(reader, box); + itemProtectionBox = new ItemProtectionBox(payloadReader, box); } else if (box.type.equals(HeifBoxTypes.BOX_PRIMARY_ITEM)) { - primaryItemBox = new PrimaryItemBox(reader, box); + primaryItemBox = new PrimaryItemBox(payloadReader, box); } else if (box.type.equals(HeifBoxTypes.BOX_ITEM_INFO)) { - itemInfoBox = new ItemInfoBox(reader, box); + itemInfoBox = new ItemInfoBox(payloadReader, box); itemInfoBox.addMetadata(directory); } else if (box.type.equals(HeifBoxTypes.BOX_ITEM_LOCATION)) { - itemLocationBox = new ItemLocationBox(reader, box); + itemLocationBox = new ItemLocationBox(payloadReader, box); } else if (box.type.equals(HeifBoxTypes.BOX_IMAGE_SPATIAL_EXTENTS)) { - ImageSpatialExtentsProperty imageSpatialExtentsProperty = new ImageSpatialExtentsProperty(reader, box); + ImageSpatialExtentsProperty imageSpatialExtentsProperty = new ImageSpatialExtentsProperty(payloadReader, box); imageSpatialExtentsProperty.addMetadata(directory); } else if (box.type.equals(HeifBoxTypes.BOX_AUXILIARY_TYPE_PROPERTY)) { - AuxiliaryTypeProperty auxiliaryTypeProperty = new AuxiliaryTypeProperty(reader, box); + AuxiliaryTypeProperty auxiliaryTypeProperty = new AuxiliaryTypeProperty(payloadReader, box); } return this; } @Override - protected void processContainer(Box box, SequentialReader reader) throws IOException + protected void processContainer(Box box, ReaderInfo reader) throws IOException { } diff --git a/Source/com/drew/metadata/heif/boxes/AuxiliaryTypeProperty.java b/Source/com/drew/metadata/heif/boxes/AuxiliaryTypeProperty.java index 04f19cb5e..6ed6d1a6e 100644 --- a/Source/com/drew/metadata/heif/boxes/AuxiliaryTypeProperty.java +++ b/Source/com/drew/metadata/heif/boxes/AuxiliaryTypeProperty.java @@ -20,10 +20,9 @@ */ package com.drew.metadata.heif.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import java.io.IOException; -import java.nio.charset.Charset; /** * ISO/IEC 23008-12:2017 pg.14 @@ -33,7 +32,7 @@ public class AuxiliaryTypeProperty extends FullBox String auxType; int[] auxSubtype; - public AuxiliaryTypeProperty(SequentialReader reader, Box box) throws IOException + public AuxiliaryTypeProperty(ReaderInfo reader, Box box) throws IOException { super(reader, box); @@ -41,7 +40,7 @@ public AuxiliaryTypeProperty(SequentialReader reader, Box box) throws IOExceptio // auxSubtype } - private String getZeroTerminatedString(int maxLengthBytes, SequentialReader reader) throws IOException + private String getZeroTerminatedString(int maxLengthBytes, ReaderInfo reader) throws IOException { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < maxLengthBytes; i++) { diff --git a/Source/com/drew/metadata/heif/boxes/Box.java b/Source/com/drew/metadata/heif/boxes/Box.java index b436df8f7..33e9b2592 100644 --- a/Source/com/drew/metadata/heif/boxes/Box.java +++ b/Source/com/drew/metadata/heif/boxes/Box.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.heif.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; +import com.drew.lang.Charsets; import java.io.IOException; @@ -33,17 +34,17 @@ public class Box public String type; String usertype; - public Box(SequentialReader reader) throws IOException + public Box(ReaderInfo reader) throws IOException { this.size = reader.getUInt32(); - this.type = reader.getString(4); + this.type = reader.getString(4, Charsets.UTF_8); if (size == 1) { size = reader.getInt64(); } else if (size == 0) { size = -1; } if (type.equals("uuid")) { - usertype = reader.getString(16); + usertype = reader.getString(16, Charsets.UTF_8); } } diff --git a/Source/com/drew/metadata/heif/boxes/FileTypeBox.java b/Source/com/drew/metadata/heif/boxes/FileTypeBox.java index eaa1fc4e0..c9c399f6a 100644 --- a/Source/com/drew/metadata/heif/boxes/FileTypeBox.java +++ b/Source/com/drew/metadata/heif/boxes/FileTypeBox.java @@ -20,9 +20,11 @@ */ package com.drew.metadata.heif.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.heif.HeifDirectory; +import com.drew.lang.Charsets; + import java.io.IOException; import java.util.ArrayList; @@ -35,15 +37,15 @@ public class FileTypeBox extends Box long minorVersion; ArrayList compatibleBrands; - public FileTypeBox(SequentialReader reader, Box box) throws IOException + public FileTypeBox(ReaderInfo reader, Box box) throws IOException { super(box); - majorBrand = reader.getString(4); + majorBrand = reader.getString(4, Charsets.UTF_8); minorVersion = reader.getUInt32(); compatibleBrands = new ArrayList(); for (int i = 16; i < size; i += 4) { - compatibleBrands.add(reader.getString(4)); + compatibleBrands.add(reader.getString(4, Charsets.UTF_8)); } } diff --git a/Source/com/drew/metadata/heif/boxes/FullBox.java b/Source/com/drew/metadata/heif/boxes/FullBox.java index 382ca1483..b4bd40a93 100644 --- a/Source/com/drew/metadata/heif/boxes/FullBox.java +++ b/Source/com/drew/metadata/heif/boxes/FullBox.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.heif.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -32,7 +32,7 @@ public class FullBox extends Box byte[] flags; int version; - public FullBox(SequentialReader reader, Box box) throws IOException + public FullBox(ReaderInfo reader, Box box) throws IOException { super(box); diff --git a/Source/com/drew/metadata/heif/boxes/HandlerBox.java b/Source/com/drew/metadata/heif/boxes/HandlerBox.java index 673c69fa8..f5670ca7f 100644 --- a/Source/com/drew/metadata/heif/boxes/HandlerBox.java +++ b/Source/com/drew/metadata/heif/boxes/HandlerBox.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.heif.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; +import com.drew.lang.Charsets; import java.io.IOException; import java.nio.charset.Charset; @@ -34,12 +35,12 @@ public class HandlerBox extends FullBox String handlerType; String name; - public HandlerBox(SequentialReader reader, Box box) throws IOException + public HandlerBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); reader.skip(4); // Pre-defined - handlerType = reader.getString(4); + handlerType = reader.getString(4, Charsets.UTF_8); reader.skip(12); // Reserved name = reader.getNullTerminatedString((int)box.size - 32, Charset.defaultCharset()); } diff --git a/Source/com/drew/metadata/heif/boxes/ImageSpatialExtentsProperty.java b/Source/com/drew/metadata/heif/boxes/ImageSpatialExtentsProperty.java index d786c1786..c1c864326 100644 --- a/Source/com/drew/metadata/heif/boxes/ImageSpatialExtentsProperty.java +++ b/Source/com/drew/metadata/heif/boxes/ImageSpatialExtentsProperty.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.heif.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.heif.HeifDirectory; import java.io.IOException; @@ -33,7 +33,7 @@ public class ImageSpatialExtentsProperty extends FullBox long width; long height; - public ImageSpatialExtentsProperty(SequentialReader reader, Box box) throws IOException + public ImageSpatialExtentsProperty(ReaderInfo reader, Box box) throws IOException { super(reader, box); diff --git a/Source/com/drew/metadata/heif/boxes/ItemInfoBox.java b/Source/com/drew/metadata/heif/boxes/ItemInfoBox.java index 4c89207a9..09e3e00d9 100644 --- a/Source/com/drew/metadata/heif/boxes/ItemInfoBox.java +++ b/Source/com/drew/metadata/heif/boxes/ItemInfoBox.java @@ -20,12 +20,11 @@ */ package com.drew.metadata.heif.boxes; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.metadata.heif.HeifDirectory; import java.io.IOException; -import java.nio.charset.Charset; import java.util.ArrayList; /** @@ -36,7 +35,7 @@ public class ItemInfoBox extends FullBox long entryCount; ArrayList entries; - public ItemInfoBox(SequentialReader reader, Box box) throws IOException + public ItemInfoBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); @@ -49,7 +48,7 @@ public ItemInfoBox(SequentialReader reader, Box box) throws IOException for (int i = 1; i <= entryCount; i++) { Box entryBox = new Box(reader); - SequentialByteArrayReader byteReader = new SequentialByteArrayReader(reader.getBytes((int)entryBox.size - 8)); + ReaderInfo byteReader = reader.Clone((int)entryBox.size - 8); entries.add(new ItemInfoEntry(byteReader, entryBox)); } } @@ -65,22 +64,22 @@ class ItemInfoEntry extends FullBox String itemType; String itemUriType; - public ItemInfoEntry(SequentialReader reader, Box box) throws IOException + public ItemInfoEntry(ReaderInfo reader, Box box) throws IOException { super(reader, box); if ((version == 0) || (version == 1)) { itemID = reader.getUInt16(); itemProtectionIndex = reader.getUInt16(); - itemName = reader.getString(4); - contentType = reader.getString(4); + itemName = reader.getString(4, Charsets.UTF_8); + contentType = reader.getString(4, Charsets.UTF_8); if (box.size - 28 > 0) { - extensionType = reader.getString((int)box.size - 28); + extensionType = reader.getString((int)box.size - 28, Charsets.UTF_8); } } if (version == 1) { if (box.size - 28 >= 4) { - contentEncoding = reader.getString(4); + contentEncoding = reader.getString(4, Charsets.UTF_8); } } if (version >= 2) { @@ -90,13 +89,13 @@ public ItemInfoEntry(SequentialReader reader, Box box) throws IOException itemID = reader.getUInt32(); } itemProtectionIndex = reader.getUInt16(); - itemType = reader.getString(4); + itemType = reader.getString(4, Charsets.UTF_8); - itemName = reader.getString(4); + itemName = reader.getString(4, Charsets.UTF_8); if (itemType.equals("mime")) { - contentType = reader.getString(4); + contentType = reader.getString(4, Charsets.UTF_8); } else if (itemType.equals("uri ")) { - itemUriType = reader.getString(4); + itemUriType = reader.getString(4, Charsets.UTF_8); } } } diff --git a/Source/com/drew/metadata/heif/boxes/ItemLocationBox.java b/Source/com/drew/metadata/heif/boxes/ItemLocationBox.java index f6ee812fc..26f01be29 100644 --- a/Source/com/drew/metadata/heif/boxes/ItemLocationBox.java +++ b/Source/com/drew/metadata/heif/boxes/ItemLocationBox.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.heif.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -41,7 +41,7 @@ public class ItemLocationBox extends FullBox int extentCount; Extent[] extents; - public ItemLocationBox(SequentialReader reader, Box box) throws IOException + public ItemLocationBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); @@ -90,7 +90,7 @@ public ItemLocationBox(SequentialReader reader, Box box) throws IOException } } - public Long getIntFromUnknownByte(int variable, SequentialReader reader) throws IOException + public Long getIntFromUnknownByte(int variable, ReaderInfo reader) throws IOException { switch(variable) { case (1): diff --git a/Source/com/drew/metadata/heif/boxes/ItemProtectionBox.java b/Source/com/drew/metadata/heif/boxes/ItemProtectionBox.java index 4dfd42c94..210e0ebe3 100644 --- a/Source/com/drew/metadata/heif/boxes/ItemProtectionBox.java +++ b/Source/com/drew/metadata/heif/boxes/ItemProtectionBox.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.heif.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; +import com.drew.lang.Charsets; import java.io.IOException; import java.util.ArrayList; @@ -33,7 +34,7 @@ public class ItemProtectionBox extends FullBox int protectionCount; ArrayList protectionSchemes; - public ItemProtectionBox(SequentialReader reader, Box box) throws IOException + public ItemProtectionBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); @@ -46,7 +47,7 @@ public ItemProtectionBox(SequentialReader reader, Box box) throws IOException class ProtectionSchemeInfoBox extends Box { - public ProtectionSchemeInfoBox(SequentialReader reader, Box box) throws IOException + public ProtectionSchemeInfoBox(ReaderInfo reader, Box box) throws IOException { super(box); } @@ -55,11 +56,11 @@ class OriginalFormatBox extends Box { String dataFormat; - public OriginalFormatBox(SequentialReader reader, Box box) throws IOException + public OriginalFormatBox(ReaderInfo reader, Box box) throws IOException { super(reader); - dataFormat = reader.getString(4); + dataFormat = reader.getString(4, Charsets.UTF_8); } } } diff --git a/Source/com/drew/metadata/heif/boxes/PrimaryItemBox.java b/Source/com/drew/metadata/heif/boxes/PrimaryItemBox.java index 8c41c4ce8..09feb0033 100644 --- a/Source/com/drew/metadata/heif/boxes/PrimaryItemBox.java +++ b/Source/com/drew/metadata/heif/boxes/PrimaryItemBox.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.heif.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -31,7 +31,7 @@ public class PrimaryItemBox extends FullBox { long itemID; - public PrimaryItemBox(SequentialReader reader, Box box) throws IOException + public PrimaryItemBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); diff --git a/Source/com/drew/metadata/icc/IccDescriptor.java b/Source/com/drew/metadata/icc/IccDescriptor.java index 13363a650..2e6d50e00 100644 --- a/Source/com/drew/metadata/icc/IccDescriptor.java +++ b/Source/com/drew/metadata/icc/IccDescriptor.java @@ -21,8 +21,7 @@ package com.drew.metadata.icc; -import com.drew.lang.ByteArrayReader; -import com.drew.lang.RandomAccessReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.TagDescriptor; @@ -80,7 +79,7 @@ private String getTagDataString(int tagType) byte[] bytes = _directory.getByteArray(tagType); if (bytes == null) return _directory.getString(tagType); - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); int iccTagType = reader.getInt32(0); switch (iccTagType) { case ICC_TAG_TYPE_TEXT: @@ -337,6 +336,6 @@ private String getProfileVersionDescription() private static int getInt32FromString(@NotNull String string) throws IOException { byte[] bytes = string.getBytes(); - return new ByteArrayReader(bytes).getInt32(0); + return ReaderInfo.createFromArray(bytes).getInt32(0); } } diff --git a/Source/com/drew/metadata/icc/IccReader.java b/Source/com/drew/metadata/icc/IccReader.java index 090c9b482..ae55f7448 100644 --- a/Source/com/drew/metadata/icc/IccReader.java +++ b/Source/com/drew/metadata/icc/IccReader.java @@ -22,14 +22,13 @@ import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.ByteArrayReader; +import com.drew.imaging.jpeg.JpegSegment; +import com.drew.lang.ReaderInfo; import com.drew.lang.DateUtil; -import com.drew.lang.RandomAccessReader; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; -import com.drew.metadata.MetadataReader; import java.io.IOException; import java.util.Collections; @@ -47,55 +46,61 @@ * @author Yuri Binev * @author Drew Noakes https://drewnoakes.com */ -public class IccReader implements JpegSegmentMetadataReader, MetadataReader +public class IccReader implements JpegSegmentMetadataReader { + public static final String JPEG_SEGMENT_ID = "ICC"; public static final String JPEG_SEGMENT_PREAMBLE = "ICC_PROFILE"; - + @NotNull public Iterable getSegmentTypes() { return Collections.singletonList(JpegSegmentType.APP2); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) throws IOException { - final int preambleLength = JPEG_SEGMENT_PREAMBLE.length(); + // NOTE the header is 14 bytes, while "ICC_PROFILE" is 11 + final int preambleLength = 14; // ICC data can be spread across multiple JPEG segments. // We concat them together in this buffer for later processing. byte[] buffer = null; - for (byte[] segmentBytes : segments) { + for (JpegSegment segment : segments) { // Skip any segments that do not contain the required preamble - if (segmentBytes.length < preambleLength || !JPEG_SEGMENT_PREAMBLE.equalsIgnoreCase(new String(segmentBytes, 0, preambleLength))) + if (segment.getReader().getLength() < preambleLength || !JPEG_SEGMENT_ID.equalsIgnoreCase(segment.getPreamble())) continue; // NOTE we ignore three bytes here -- are they useful for anything? + ReaderInfo segmentReader = segment.getReader(); // Grow the buffer if (buffer == null) { - buffer = new byte[segmentBytes.length - 14]; + buffer = new byte[(int)segmentReader.getLength() - preambleLength]; // skip the first 14 bytes - System.arraycopy(segmentBytes, 14, buffer, 0, segmentBytes.length - 14); + System.arraycopy(segmentReader.toArray(), preambleLength, buffer, 0, (int)segmentReader.getLength() - preambleLength); } else { - byte[] newBuffer = new byte[buffer.length + segmentBytes.length - 14]; + byte[] newBuffer = new byte[buffer.length + (int)segmentReader.getLength() - preambleLength]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); - System.arraycopy(segmentBytes, 14, newBuffer, buffer.length, segmentBytes.length - 14); + System.arraycopy(segmentReader.toArray(), preambleLength, newBuffer, buffer.length, (int)segmentReader.getLength() - preambleLength); buffer = newBuffer; } } if (buffer != null) - extract(new ByteArrayReader(buffer), metadata); + extract(ReaderInfo.createFromArray(buffer), metadata); } - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) throws IOException { extract(reader, metadata, null); } - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata, @Nullable Directory parentDirectory) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata, @Nullable Directory parentDirectory) throws IOException { + if (!reader.isMotorolaByteOrder()) + reader = reader.Clone(false); + // TODO review whether the 'tagPtr' values below really do require RandomAccessReader or whether SequentialReader may be used instead IccDirectory directory = new IccDirectory(); @@ -157,14 +162,14 @@ public void extract(@NotNull final RandomAccessReader reader, @NotNull final Met metadata.addDirectory(directory); } - private void set4ByteString(@NotNull Directory directory, int tagType, @NotNull RandomAccessReader reader) throws IOException + private void set4ByteString(@NotNull Directory directory, int tagType, @NotNull ReaderInfo reader) throws IOException { int i = reader.getInt32(tagType); if (i != 0) directory.setString(tagType, getStringFromInt32(i)); } - private void setInt32(@NotNull Directory directory, int tagType, @NotNull RandomAccessReader reader) throws IOException + private void setInt32(@NotNull Directory directory, int tagType, @NotNull ReaderInfo reader) throws IOException { int i = reader.getInt32(tagType); if (i != 0) @@ -172,7 +177,7 @@ private void setInt32(@NotNull Directory directory, int tagType, @NotNull Random } @SuppressWarnings({"SameParameterValue"}) - private void setInt64(@NotNull Directory directory, int tagType, @NotNull RandomAccessReader reader) throws IOException + private void setInt64(@NotNull Directory directory, int tagType, @NotNull ReaderInfo reader) throws IOException { long l = reader.getInt64(tagType); if (l != 0) @@ -180,7 +185,7 @@ private void setInt64(@NotNull Directory directory, int tagType, @NotNull Random } @SuppressWarnings({"SameParameterValue", "MagicConstant"}) - private void setDate(@NotNull final IccDirectory directory, final int tagType, @NotNull RandomAccessReader reader) throws IOException + private void setDate(@NotNull final IccDirectory directory, final int tagType, @NotNull ReaderInfo reader) throws IOException { final int y = reader.getUInt16(tagType); final int m = reader.getUInt16(tagType + 2); diff --git a/Source/com/drew/metadata/ico/IcoReader.java b/Source/com/drew/metadata/ico/IcoReader.java index 49df0f2d1..7bb7b6323 100644 --- a/Source/com/drew/metadata/ico/IcoReader.java +++ b/Source/com/drew/metadata/ico/IcoReader.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.ico; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; @@ -36,7 +36,7 @@ */ public class IcoReader { - public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) { reader.setMotorolaByteOrder(false); diff --git a/Source/com/drew/metadata/iptc/IptcReader.java b/Source/com/drew/metadata/iptc/IptcReader.java index 7a21c0c2a..59d5d0524 100644 --- a/Source/com/drew/metadata/iptc/IptcReader.java +++ b/Source/com/drew/metadata/iptc/IptcReader.java @@ -22,8 +22,8 @@ import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.imaging.jpeg.JpegSegment; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Directory; @@ -65,12 +65,13 @@ public Iterable getSegmentTypes() return Collections.singletonList(JpegSegmentType.APPD); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + @Override + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) throws IOException { - for (byte[] segmentBytes : segments) { + for (JpegSegment segment : segments) { // Ensure data starts with the IPTC marker byte - if (segmentBytes.length != 0 && segmentBytes[0] == IptcMarkerByte) { - extract(new SequentialByteArrayReader(segmentBytes), metadata, segmentBytes.length); + if (segment.getReader().getLength() != 0 && segment.getByteMarker() == IptcMarkerByte) { + extract(segment.getReader(), metadata); } } } @@ -78,16 +79,19 @@ public void readJpegSegments(@NotNull Iterable segments, @NotNull Metada /** * Performs the IPTC data extraction, adding found values to the specified instance of {@link Metadata}. */ - public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata, long length) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) throws IOException { - extract(reader, metadata, length, null); + extract(reader, metadata, null); } /** * Performs the IPTC data extraction, adding found values to the specified instance of {@link Metadata}. */ - public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata, long length, @Nullable Directory parentDirectory) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata, @Nullable Directory parentDirectory) throws IOException { + if(!reader.isMotorolaByteOrder()) + reader = reader.Clone(false); + IptcDirectory directory = new IptcDirectory(); metadata.addDirectory(directory); @@ -97,7 +101,7 @@ public void extract(@NotNull final SequentialReader reader, @NotNull final Metad int offset = 0; // for each tag - while (offset < length) { + while (offset < reader.getLength()) { // identifies start of a tag short startByte; @@ -112,13 +116,13 @@ public void extract(@NotNull final SequentialReader reader, @NotNull final Metad if (startByte != IptcMarkerByte) { // NOTE have seen images where there was one extra byte at the end, giving // offset==length at this point, which is not worth logging as an error. - if (offset != length) + if (offset != reader.getLength()) directory.addError("Invalid IPTC tag marker at offset " + (offset - 1) + ". Expected '0x" + Integer.toHexString(IptcMarkerByte) + "' but got '0x" + Integer.toHexString(startByte) + "'."); return; } // we need at least four bytes left to read a tag - if (offset + 4 > length) { + if (offset + 4 > reader.getLength()) { directory.addError("Too few bytes remain for a valid IPTC tag"); return; } @@ -141,7 +145,7 @@ public void extract(@NotNull final SequentialReader reader, @NotNull final Metad return; } - if (offset + tagByteCount > length) { + if (offset + tagByteCount > reader.getLength()) { directory.addError("Data for tag extends beyond end of IPTC segment"); return; } @@ -157,7 +161,7 @@ public void extract(@NotNull final SequentialReader reader, @NotNull final Metad } } - private void processTag(@NotNull SequentialReader reader, @NotNull Directory directory, int directoryType, int tagType, int tagByteCount) throws IOException + private void processTag(@NotNull ReaderInfo reader, @NotNull Directory directory, int directoryType, int tagType, int tagByteCount) throws IOException { int tagIdentifier = tagType | (directoryType << 8); diff --git a/Source/com/drew/metadata/jfif/JfifReader.java b/Source/com/drew/metadata/jfif/JfifReader.java index 55db301a1..5d357c689 100644 --- a/Source/com/drew/metadata/jfif/JfifReader.java +++ b/Source/com/drew/metadata/jfif/JfifReader.java @@ -20,13 +20,12 @@ */ package com.drew.metadata.jfif; +import com.drew.imaging.jpeg.JpegSegment; import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.ByteArrayReader; -import com.drew.lang.RandomAccessReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; -import com.drew.metadata.MetadataReader; import java.io.IOException; import java.util.Collections; @@ -41,9 +40,10 @@ * * @author Yuri Binev, Drew Noakes, Markus Meyer */ -public class JfifReader implements JpegSegmentMetadataReader, MetadataReader +public class JfifReader implements JpegSegmentMetadataReader //, MetadataReader { - public static final String PREAMBLE = "JFIF"; + public static final String JPEG_SEGMENT_ID = "JFIF"; + public static final String JPEG_SEGMENT_PREAMBLE = "JFIF"; @NotNull public Iterable getSegmentTypes() @@ -51,12 +51,13 @@ public Iterable getSegmentTypes() return Collections.singletonList(JpegSegmentType.APP0); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + @Override + public void readJpegSegments(@NotNull final Iterable segments, @NotNull Metadata metadata) throws IOException { - for (byte[] segmentBytes : segments) { + for (JpegSegment segment : segments) { // Skip segments not starting with the required header - if (segmentBytes.length >= PREAMBLE.length() && PREAMBLE.equals(new String(segmentBytes, 0, PREAMBLE.length()))) - extract(new ByteArrayReader(segmentBytes), metadata); + if (segment.getReader().getLength() >= JPEG_SEGMENT_PREAMBLE.length() && JPEG_SEGMENT_ID.equals(segment.getPreamble())) + extract(segment.getReader().Clone(segment.getReader().getLocalPosition(), -1), metadata); } } @@ -64,7 +65,7 @@ public void readJpegSegments(@NotNull Iterable segments, @NotNull Metada * Performs the Jfif data extraction, adding found values to the specified * instance of {@link Metadata}. */ - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) { JfifDirectory directory = new JfifDirectory(); metadata.addDirectory(directory); diff --git a/Source/com/drew/metadata/jfxx/JfxxReader.java b/Source/com/drew/metadata/jfxx/JfxxReader.java index ca68ad9c7..770d51eca 100644 --- a/Source/com/drew/metadata/jfxx/JfxxReader.java +++ b/Source/com/drew/metadata/jfxx/JfxxReader.java @@ -22,11 +22,10 @@ import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.ByteArrayReader; -import com.drew.lang.RandomAccessReader; +import com.drew.imaging.jpeg.JpegSegment; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; -import com.drew.metadata.MetadataReader; import java.io.IOException; import java.util.Collections; @@ -41,9 +40,10 @@ * * @author Drew Noakes */ -public class JfxxReader implements JpegSegmentMetadataReader, MetadataReader +public class JfxxReader implements JpegSegmentMetadataReader //, MetadataReader { - public static final String PREAMBLE = "JFXX"; + public static final String JPEG_SEGMENT_ID = "JFXX"; + public static final String JPEG_SEGMENT_PREAMBLE = "JFXX"; @NotNull public Iterable getSegmentTypes() @@ -51,12 +51,12 @@ public Iterable getSegmentTypes() return Collections.singletonList(JpegSegmentType.APP0); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) throws IOException { - for (byte[] segmentBytes : segments) { + for (JpegSegment segment : segments) { // Skip segments not starting with the required header - if (segmentBytes.length >= PREAMBLE.length() && PREAMBLE.equals(new String(segmentBytes, 0, PREAMBLE.length()))) - extract(new ByteArrayReader(segmentBytes), metadata); + if (segment.getReader().getLength() >= JPEG_SEGMENT_PREAMBLE.length() && segment.getPreamble() == JPEG_SEGMENT_ID) + extract(segment.getReader(), metadata); } } @@ -64,7 +64,7 @@ public void readJpegSegments(@NotNull Iterable segments, @NotNull Metada * Performs the JFXX data extraction, adding found values to the specified * instance of {@link Metadata}. */ - public void extract(@NotNull final RandomAccessReader reader, @NotNull final Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) { JfxxDirectory directory = new JfxxDirectory(); metadata.addDirectory(directory); diff --git a/Source/com/drew/metadata/jpeg/JpegCommentReader.java b/Source/com/drew/metadata/jpeg/JpegCommentReader.java index 8170ba5f2..5abb7766a 100644 --- a/Source/com/drew/metadata/jpeg/JpegCommentReader.java +++ b/Source/com/drew/metadata/jpeg/JpegCommentReader.java @@ -22,11 +22,13 @@ import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; +import com.drew.imaging.jpeg.JpegSegment; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.StringValue; import java.util.Collections; +import java.io.IOException; /** * Decodes the comment stored within JPEG files, populating a {@link Metadata} object with tag values in a @@ -37,19 +39,21 @@ public class JpegCommentReader implements JpegSegmentMetadataReader { @NotNull + @Override public Iterable getSegmentTypes() { return Collections.singletonList(JpegSegmentType.COM); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + @Override + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) throws IOException { - for (byte[] segmentBytes : segments) { + for (JpegSegment segment : segments) { JpegCommentDirectory directory = new JpegCommentDirectory(); metadata.addDirectory(directory); // The entire contents of the directory are the comment - directory.setStringValue(JpegCommentDirectory.TAG_COMMENT, new StringValue(segmentBytes, null)); + directory.setStringValue(JpegCommentDirectory.TAG_COMMENT, new StringValue(segment.getReader().toArray(), null)); } } } diff --git a/Source/com/drew/metadata/jpeg/JpegDhtReader.java b/Source/com/drew/metadata/jpeg/JpegDhtReader.java index f8f769151..61b8ade5f 100644 --- a/Source/com/drew/metadata/jpeg/JpegDhtReader.java +++ b/Source/com/drew/metadata/jpeg/JpegDhtReader.java @@ -22,8 +22,8 @@ import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.imaging.jpeg.JpegSegment; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.jpeg.HuffmanTablesDirectory.HuffmanTable; @@ -39,15 +39,17 @@ public class JpegDhtReader implements JpegSegmentMetadataReader { @NotNull + @Override public Iterable getSegmentTypes() { return Collections.singletonList(JpegSegmentType.DHT); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + @Override + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) { - for (byte[] segmentBytes : segments) { - extract(new SequentialByteArrayReader(segmentBytes), metadata); + for (JpegSegment segment : segments) { + extract(segment.getReader(), metadata); } } @@ -55,7 +57,7 @@ public void readJpegSegments(@NotNull Iterable segments, @NotNull Metada * Performs the DHT tables extraction, adding found tables to the specified * instance of {@link Metadata}. */ - public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) { HuffmanTablesDirectory directory = metadata.getFirstDirectoryOfType(HuffmanTablesDirectory.class); if (directory == null) { @@ -64,7 +66,7 @@ public void extract(@NotNull final SequentialReader reader, @NotNull final Metad } try { - while (reader.available() > 0) { + while (!reader.isCloserToEnd(1)) { byte header = reader.getByte(); HuffmanTableClass tableClass = HuffmanTableClass.typeOf((header & 0xF0) >> 4); int tableDestinationId = header & 0xF; @@ -84,7 +86,7 @@ public void extract(@NotNull final SequentialReader reader, @NotNull final Metad directory.setInt(HuffmanTablesDirectory.TAG_NUMBER_OF_TABLES, directory.getTables().size()); } - private byte[] getBytes(@NotNull final SequentialReader reader, int count) throws IOException { + private byte[] getBytes(@NotNull ReaderInfo reader, int count) throws IOException { byte[] bytes = new byte[count]; for (int i = 0; i < count; i++) { byte b = reader.getByte(); diff --git a/Source/com/drew/metadata/jpeg/JpegDnlReader.java b/Source/com/drew/metadata/jpeg/JpegDnlReader.java index 6fc348747..3a855b7d7 100644 --- a/Source/com/drew/metadata/jpeg/JpegDnlReader.java +++ b/Source/com/drew/metadata/jpeg/JpegDnlReader.java @@ -22,8 +22,8 @@ import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.imaging.jpeg.JpegSegment; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.ErrorDirectory; import com.drew.metadata.Metadata; @@ -39,19 +39,21 @@ public class JpegDnlReader implements JpegSegmentMetadataReader { @NotNull + @Override public Iterable getSegmentTypes() { return Collections.singletonList(JpegSegmentType.DNL); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + @Override + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) throws IOException { - for (byte[] segmentBytes : segments) { - extract(segmentBytes, metadata, segmentType); + for (JpegSegment segment : segments) { + extract(segment.getReader().Clone(), metadata); } } - public void extract(byte[] segmentBytes, Metadata metadata, JpegSegmentType segmentType) + public void extract(ReaderInfo reader, Metadata metadata) { JpegDirectory directory = metadata.getFirstDirectoryOfType(JpegDirectory.class); if (directory == null) { @@ -61,8 +63,6 @@ public void extract(byte[] segmentBytes, Metadata metadata, JpegSegmentType segm return; } - SequentialReader reader = new SequentialByteArrayReader(segmentBytes); - try { // Only set height from DNL if it's not already defined Integer i = directory.getInteger(JpegDirectory.TAG_IMAGE_HEIGHT); diff --git a/Source/com/drew/metadata/jpeg/JpegReader.java b/Source/com/drew/metadata/jpeg/JpegReader.java index 9e6bf6619..58c56e811 100644 --- a/Source/com/drew/metadata/jpeg/JpegReader.java +++ b/Source/com/drew/metadata/jpeg/JpegReader.java @@ -20,10 +20,10 @@ */ package com.drew.metadata.jpeg; +import com.drew.imaging.jpeg.JpegSegment; import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; @@ -62,23 +62,24 @@ public Iterable getSegmentTypes() ); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + @Override + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) //, @NotNull JpegSegmentType segmentType) { - for (byte[] segmentBytes : segments) { - extract(segmentBytes, metadata, segmentType); + for (JpegSegment segment : segments) { + extract(segment, metadata); //, segmentType); } } - public void extract(byte[] segmentBytes, Metadata metadata, JpegSegmentType segmentType) + public void extract(JpegSegment segment, Metadata metadata) //, JpegSegmentType segmentType) { JpegDirectory directory = new JpegDirectory(); metadata.addDirectory(directory); // The value of TAG_COMPRESSION_TYPE is determined by the segment type found - directory.setInt(JpegDirectory.TAG_COMPRESSION_TYPE, segmentType.byteValue - JpegSegmentType.SOF0.byteValue); - - SequentialReader reader = new SequentialByteArrayReader(segmentBytes); + directory.setInt(JpegDirectory.TAG_COMPRESSION_TYPE, segment.getType().byteValue - JpegSegmentType.SOF0.byteValue); + ReaderInfo reader = segment.getReader(); + try { directory.setInt(JpegDirectory.TAG_DATA_PRECISION, reader.getUInt8()); directory.setInt(JpegDirectory.TAG_IMAGE_HEIGHT, reader.getUInt16()); diff --git a/Source/com/drew/metadata/mov/QuickTimeAtomHandler.java b/Source/com/drew/metadata/mov/QuickTimeAtomHandler.java index c5dbac2b7..bbd3adf72 100644 --- a/Source/com/drew/metadata/mov/QuickTimeAtomHandler.java +++ b/Source/com/drew/metadata/mov/QuickTimeAtomHandler.java @@ -21,8 +21,7 @@ package com.drew.metadata.mov; import com.drew.imaging.quicktime.QuickTimeHandler; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Metadata; @@ -72,7 +71,7 @@ public boolean shouldAcceptContainer(@NotNull Atom atom) public QuickTimeHandler processAtom(@NotNull Atom atom, @Nullable byte[] payload) throws IOException { if (payload != null) { - SequentialReader reader = new SequentialByteArrayReader(payload); + ReaderInfo reader = ReaderInfo.createFromArray(payload); if (atom.type.equals(QuickTimeAtomTypes.ATOM_MOVIE_HEADER)) { MovieHeaderAtom movieHeaderAtom = new MovieHeaderAtom(reader, atom); diff --git a/Source/com/drew/metadata/mov/QuickTimeMediaHandler.java b/Source/com/drew/metadata/mov/QuickTimeMediaHandler.java index 8dfc53dc0..7fa38436e 100644 --- a/Source/com/drew/metadata/mov/QuickTimeMediaHandler.java +++ b/Source/com/drew/metadata/mov/QuickTimeMediaHandler.java @@ -21,8 +21,7 @@ package com.drew.metadata.mov; import com.drew.imaging.quicktime.QuickTimeHandler; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Metadata; @@ -78,7 +77,7 @@ public boolean shouldAcceptContainer(@NotNull Atom atom) public QuickTimeMediaHandler processAtom(@NotNull Atom atom, @Nullable byte[] payload) throws IOException { if (payload != null) { - SequentialReader reader = new SequentialByteArrayReader(payload); + ReaderInfo reader = ReaderInfo.createFromArray(payload); if (atom.type.equals(getMediaInformation())) { processMediaInformation(reader, atom); } else if (atom.type.equals(QuickTimeAtomTypes.ATOM_SAMPLE_DESCRIPTION)) { @@ -92,9 +91,9 @@ public QuickTimeMediaHandler processAtom(@NotNull Atom atom, @Nullable byte[] pa protected abstract String getMediaInformation(); - protected abstract void processSampleDescription(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException; + protected abstract void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException; - protected abstract void processMediaInformation(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException; + protected abstract void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException; - protected abstract void processTimeToSample(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException; + protected abstract void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException; } diff --git a/Source/com/drew/metadata/mov/QuickTimeMetadataHandler.java b/Source/com/drew/metadata/mov/QuickTimeMetadataHandler.java index 55d4da4eb..dab78f6ad 100644 --- a/Source/com/drew/metadata/mov/QuickTimeMetadataHandler.java +++ b/Source/com/drew/metadata/mov/QuickTimeMetadataHandler.java @@ -21,7 +21,7 @@ package com.drew.metadata.mov; import com.drew.imaging.quicktime.QuickTimeHandler; -import com.drew.lang.SequentialByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Metadata; @@ -65,7 +65,7 @@ protected boolean shouldAcceptContainer(@NotNull Atom atom) protected QuickTimeHandler processAtom(@NotNull Atom atom, @Nullable byte[] payload) throws IOException { if (payload != null) { - SequentialByteArrayReader reader = new SequentialByteArrayReader(payload); + ReaderInfo reader = ReaderInfo.createFromArray(payload); if (atom.type.equals(QuickTimeAtomTypes.ATOM_KEYS)) { processKeys(reader); } else if (atom.type.equals(QuickTimeAtomTypes.ATOM_DATA)) { @@ -75,7 +75,7 @@ protected QuickTimeHandler processAtom(@NotNull Atom atom, @Nullable byte[] payl return this; } - protected abstract void processKeys(@NotNull SequentialByteArrayReader reader) throws IOException; + protected abstract void processKeys(@NotNull ReaderInfo reader) throws IOException; - protected abstract void processData(@NotNull byte[] payload, @NotNull SequentialByteArrayReader reader) throws IOException; + protected abstract void processData(@NotNull byte[] payload, @NotNull ReaderInfo reader) throws IOException; } diff --git a/Source/com/drew/metadata/mov/atoms/Atom.java b/Source/com/drew/metadata/mov/atoms/Atom.java index cc0b1b631..982784d79 100644 --- a/Source/com/drew/metadata/mov/atoms/Atom.java +++ b/Source/com/drew/metadata/mov/atoms/Atom.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -34,10 +35,10 @@ public class Atom public long size; public String type; - public Atom(SequentialReader reader) throws IOException + public Atom(ReaderInfo reader) throws IOException { this.size = reader.getUInt32(); - this.type = reader.getString(4); + this.type = reader.getString(4, Charsets.ASCII); if (size == 1) { size = reader.getInt64(); } else if (size == 0) { diff --git a/Source/com/drew/metadata/mov/atoms/FileTypeCompatibilityAtom.java b/Source/com/drew/metadata/mov/atoms/FileTypeCompatibilityAtom.java index e0dcefaaf..4447c4010 100644 --- a/Source/com/drew/metadata/mov/atoms/FileTypeCompatibilityAtom.java +++ b/Source/com/drew/metadata/mov/atoms/FileTypeCompatibilityAtom.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.QuickTimeDirectory; import java.io.IOException; @@ -37,15 +38,15 @@ public class FileTypeCompatibilityAtom extends Atom long minorVersion; ArrayList compatibleBrands; - public FileTypeCompatibilityAtom(SequentialReader reader, Atom atom) throws IOException + public FileTypeCompatibilityAtom(ReaderInfo reader, Atom atom) throws IOException { super(atom); - majorBrand = reader.getString(4); + majorBrand = reader.getString(4, Charsets.UTF_8); minorVersion = reader.getUInt32(); compatibleBrands = new ArrayList((int) ((size/16)>>2)); for (int i = 16; i < size; i += 4) { - compatibleBrands.add(reader.getString(4)); + compatibleBrands.add(reader.getString(4, Charsets.UTF_8)); } } diff --git a/Source/com/drew/metadata/mov/atoms/FullAtom.java b/Source/com/drew/metadata/mov/atoms/FullAtom.java index e2383485f..fd177601e 100644 --- a/Source/com/drew/metadata/mov/atoms/FullAtom.java +++ b/Source/com/drew/metadata/mov/atoms/FullAtom.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -34,7 +34,7 @@ public class FullAtom extends Atom int version; byte[] flags; - public FullAtom(SequentialReader reader, Atom atom) throws IOException + public FullAtom(ReaderInfo reader, Atom atom) throws IOException { super(atom); diff --git a/Source/com/drew/metadata/mov/atoms/HandlerReferenceAtom.java b/Source/com/drew/metadata/mov/atoms/HandlerReferenceAtom.java index 4f0bf83c4..b5cd7d85e 100644 --- a/Source/com/drew/metadata/mov/atoms/HandlerReferenceAtom.java +++ b/Source/com/drew/metadata/mov/atoms/HandlerReferenceAtom.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -40,15 +41,15 @@ public String getComponentType() String componentSubtype; String componentName; - public HandlerReferenceAtom(SequentialReader reader, Atom atom) throws IOException + public HandlerReferenceAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); - componentType = reader.getString(4); - componentSubtype = reader.getString(4); + componentType = reader.getString(4, Charsets.UTF_8); + componentSubtype = reader.getString(4, Charsets.UTF_8); reader.skip(4); // Reserved reader.skip(4); // Reserved reader.skip(4); // Reserved - componentName = reader.getString(reader.getUInt8()); + componentName = reader.getString(reader.getUInt8(), Charsets.UTF_8); } } diff --git a/Source/com/drew/metadata/mov/atoms/MediaHeaderAtom.java b/Source/com/drew/metadata/mov/atoms/MediaHeaderAtom.java index 28343653d..4e7c7888c 100644 --- a/Source/com/drew/metadata/mov/atoms/MediaHeaderAtom.java +++ b/Source/com/drew/metadata/mov/atoms/MediaHeaderAtom.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.QuickTimeHandlerFactory; import java.io.IOException; @@ -39,7 +39,7 @@ public class MediaHeaderAtom extends FullAtom int language; int quality; - public MediaHeaderAtom(SequentialReader reader, Atom atom) throws IOException + public MediaHeaderAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); diff --git a/Source/com/drew/metadata/mov/atoms/MovieHeaderAtom.java b/Source/com/drew/metadata/mov/atoms/MovieHeaderAtom.java index 1cfd2aecc..315afccdb 100644 --- a/Source/com/drew/metadata/mov/atoms/MovieHeaderAtom.java +++ b/Source/com/drew/metadata/mov/atoms/MovieHeaderAtom.java @@ -21,7 +21,7 @@ package com.drew.metadata.mov.atoms; import com.drew.lang.Rational; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.QuickTimeDirectory; import java.io.IOException; @@ -52,7 +52,7 @@ public class MovieHeaderAtom extends FullAtom long currentTime; long nextTrackID; - public MovieHeaderAtom(SequentialReader reader, Atom atom) throws IOException + public MovieHeaderAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); diff --git a/Source/com/drew/metadata/mov/atoms/MusicSampleDescriptionAtom.java b/Source/com/drew/metadata/mov/atoms/MusicSampleDescriptionAtom.java index 1369fc6e0..4560e37d9 100644 --- a/Source/com/drew/metadata/mov/atoms/MusicSampleDescriptionAtom.java +++ b/Source/com/drew/metadata/mov/atoms/MusicSampleDescriptionAtom.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.media.QuickTimeMusicDirectory; import java.io.IOException; @@ -32,13 +32,13 @@ */ public class MusicSampleDescriptionAtom extends SampleDescriptionAtom { - public MusicSampleDescriptionAtom(SequentialReader reader, Atom atom) throws IOException + public MusicSampleDescriptionAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); } @Override - MusicSampleDescription getSampleDescription(SequentialReader reader) throws IOException + MusicSampleDescription getSampleDescription(ReaderInfo reader) throws IOException { return new MusicSampleDescription(reader); } @@ -52,7 +52,7 @@ class MusicSampleDescription extends SampleDescription { long flags; - public MusicSampleDescription(SequentialReader reader) throws IOException + public MusicSampleDescription(ReaderInfo reader) throws IOException { super(reader); diff --git a/Source/com/drew/metadata/mov/atoms/SampleDescription.java b/Source/com/drew/metadata/mov/atoms/SampleDescription.java index 9fe4188fa..f59b04432 100644 --- a/Source/com/drew/metadata/mov/atoms/SampleDescription.java +++ b/Source/com/drew/metadata/mov/atoms/SampleDescription.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -35,10 +36,10 @@ public class SampleDescription String dataFormat; int dataReferenceIndex; - public SampleDescription(SequentialReader reader) throws IOException + public SampleDescription(ReaderInfo reader) throws IOException { sampleDescriptionSize = reader.getUInt32(); - dataFormat = reader.getString(4); + dataFormat = reader.getString(4, Charsets.UTF_8); reader.skip(6); // Reserved dataReferenceIndex = reader.getUInt16(); } diff --git a/Source/com/drew/metadata/mov/atoms/SampleDescriptionAtom.java b/Source/com/drew/metadata/mov/atoms/SampleDescriptionAtom.java index a1b2e27d2..28e33ee19 100644 --- a/Source/com/drew/metadata/mov/atoms/SampleDescriptionAtom.java +++ b/Source/com/drew/metadata/mov/atoms/SampleDescriptionAtom.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo;; import com.drew.lang.annotations.Nullable; import java.io.IOException; @@ -36,7 +36,7 @@ public abstract class SampleDescriptionAtom extends long numberOfEntries; ArrayList sampleDescriptions; - public SampleDescriptionAtom(SequentialReader reader, Atom atom) throws IOException + public SampleDescriptionAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); @@ -48,5 +48,5 @@ public SampleDescriptionAtom(SequentialReader reader, Atom atom) throws IOExcept } @Nullable - abstract T getSampleDescription(SequentialReader reader) throws IOException; + abstract T getSampleDescription(ReaderInfo reader) throws IOException; } diff --git a/Source/com/drew/metadata/mov/atoms/SoundInformationMediaHeaderAtom.java b/Source/com/drew/metadata/mov/atoms/SoundInformationMediaHeaderAtom.java index 4566db16b..da283bfbd 100644 --- a/Source/com/drew/metadata/mov/atoms/SoundInformationMediaHeaderAtom.java +++ b/Source/com/drew/metadata/mov/atoms/SoundInformationMediaHeaderAtom.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.media.QuickTimeSoundDirectory; import java.io.IOException; @@ -34,7 +34,7 @@ public class SoundInformationMediaHeaderAtom extends FullAtom { int balance; - public SoundInformationMediaHeaderAtom(SequentialReader reader, Atom atom) throws IOException + public SoundInformationMediaHeaderAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); diff --git a/Source/com/drew/metadata/mov/atoms/SoundSampleDescriptionAtom.java b/Source/com/drew/metadata/mov/atoms/SoundSampleDescriptionAtom.java index 0112753e7..08bd9da51 100644 --- a/Source/com/drew/metadata/mov/atoms/SoundSampleDescriptionAtom.java +++ b/Source/com/drew/metadata/mov/atoms/SoundSampleDescriptionAtom.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.QuickTimeDictionary; import com.drew.metadata.mov.media.QuickTimeSoundDirectory; @@ -33,13 +33,13 @@ */ public class SoundSampleDescriptionAtom extends SampleDescriptionAtom { - public SoundSampleDescriptionAtom(SequentialReader reader, Atom atom) throws IOException + public SoundSampleDescriptionAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); } @Override - SoundSampleDescription getSampleDescription(SequentialReader reader) throws IOException + SoundSampleDescription getSampleDescription(ReaderInfo reader) throws IOException { return new SoundSampleDescription(reader); } @@ -64,7 +64,7 @@ class SoundSampleDescription extends SampleDescription int packetSize; long sampleRate; - public SoundSampleDescription(SequentialReader reader) throws IOException + public SoundSampleDescription(ReaderInfo reader) throws IOException { super(reader); diff --git a/Source/com/drew/metadata/mov/atoms/SubtitleSampleDescriptionAtom.java b/Source/com/drew/metadata/mov/atoms/SubtitleSampleDescriptionAtom.java index 805417e58..ff5639d81 100644 --- a/Source/com/drew/metadata/mov/atoms/SubtitleSampleDescriptionAtom.java +++ b/Source/com/drew/metadata/mov/atoms/SubtitleSampleDescriptionAtom.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.media.QuickTimeSubtitleDirectory; import java.io.IOException; @@ -32,13 +32,13 @@ */ public class SubtitleSampleDescriptionAtom extends SampleDescriptionAtom { - public SubtitleSampleDescriptionAtom(SequentialReader reader, Atom atom) throws IOException + public SubtitleSampleDescriptionAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); } @Override - SubtitleSampleDescription getSampleDescription(SequentialReader reader) throws IOException + SubtitleSampleDescription getSampleDescription(ReaderInfo reader) throws IOException { return null; } @@ -52,7 +52,7 @@ class SubtitleSampleDescription extends SampleDescription int fontSize; int[] foregroundColor; - public SubtitleSampleDescription(SequentialReader reader) throws IOException + public SubtitleSampleDescription(ReaderInfo reader) throws IOException { super(reader); diff --git a/Source/com/drew/metadata/mov/atoms/TextSampleDescriptionAtom.java b/Source/com/drew/metadata/mov/atoms/TextSampleDescriptionAtom.java index afdbcf6a9..c6a5084ec 100644 --- a/Source/com/drew/metadata/mov/atoms/TextSampleDescriptionAtom.java +++ b/Source/com/drew/metadata/mov/atoms/TextSampleDescriptionAtom.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.media.QuickTimeTextDirectory; import java.io.IOException; @@ -32,13 +33,13 @@ */ public class TextSampleDescriptionAtom extends SampleDescriptionAtom { - public TextSampleDescriptionAtom(SequentialReader reader, Atom atom) throws IOException + public TextSampleDescriptionAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); } @Override - TextSampleDescription getSampleDescription(SequentialReader reader) throws IOException + TextSampleDescription getSampleDescription(ReaderInfo reader) throws IOException { return new TextSampleDescription(reader); } @@ -120,7 +121,7 @@ class TextSampleDescription extends SampleDescription int[] foregroundColor; String textName; - public TextSampleDescription(SequentialReader reader) throws IOException + public TextSampleDescription(ReaderInfo reader) throws IOException { super(reader); @@ -134,7 +135,7 @@ public TextSampleDescription(SequentialReader reader) throws IOException reader.skip(1); // 8-bits of reserved space set to 0 reader.skip(2); // 16-bits of reserved space set to 0 foregroundColor = new int[]{reader.getUInt16(), reader.getUInt16(), reader.getUInt16()}; - textName = reader.getString(reader.getUInt8()); + textName = reader.getString(reader.getUInt8(), Charsets.UTF_8); } } } diff --git a/Source/com/drew/metadata/mov/atoms/TimeToSampleAtom.java b/Source/com/drew/metadata/mov/atoms/TimeToSampleAtom.java index f06294c33..73905cab6 100644 --- a/Source/com/drew/metadata/mov/atoms/TimeToSampleAtom.java +++ b/Source/com/drew/metadata/mov/atoms/TimeToSampleAtom.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.QuickTimeHandlerFactory; import com.drew.metadata.mov.media.QuickTimeVideoDirectory; @@ -39,7 +39,7 @@ public class TimeToSampleAtom extends FullAtom long sampleCount; long sampleDuration; - public TimeToSampleAtom(SequentialReader reader, Atom atom) throws IOException + public TimeToSampleAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); @@ -55,7 +55,7 @@ class Entry long sampleCount; long sampleDuration; - public Entry(SequentialReader reader) throws IOException + public Entry(ReaderInfo reader) throws IOException { sampleCount = reader.getUInt32(); sampleDuration = reader.getUInt32(); diff --git a/Source/com/drew/metadata/mov/atoms/TimecodeInformationMediaAtom.java b/Source/com/drew/metadata/mov/atoms/TimecodeInformationMediaAtom.java index d12a42ec7..a72160819 100644 --- a/Source/com/drew/metadata/mov/atoms/TimecodeInformationMediaAtom.java +++ b/Source/com/drew/metadata/mov/atoms/TimecodeInformationMediaAtom.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.media.QuickTimeTimecodeDirectory; import java.io.IOException; @@ -39,7 +40,7 @@ public class TimecodeInformationMediaAtom extends FullAtom int[] backgroundColor; String fontName; - public TimecodeInformationMediaAtom(SequentialReader reader, Atom atom) throws IOException + public TimecodeInformationMediaAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); @@ -49,7 +50,7 @@ public TimecodeInformationMediaAtom(SequentialReader reader, Atom atom) throws I reader.skip(2); // Reserved textColor = new int[]{reader.getUInt16(), reader.getUInt16(), reader.getUInt16()}; backgroundColor = new int[]{reader.getUInt16(), reader.getUInt16(), reader.getUInt16()}; - fontName = reader.getString(reader.getUInt8()); + fontName = reader.getString(reader.getUInt8(), Charsets.UTF_8); } public void addMetadata(QuickTimeTimecodeDirectory directory) diff --git a/Source/com/drew/metadata/mov/atoms/TimecodeSampleDescriptionAtom.java b/Source/com/drew/metadata/mov/atoms/TimecodeSampleDescriptionAtom.java index e0feefa77..15f8a8e79 100644 --- a/Source/com/drew/metadata/mov/atoms/TimecodeSampleDescriptionAtom.java +++ b/Source/com/drew/metadata/mov/atoms/TimecodeSampleDescriptionAtom.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.media.QuickTimeTimecodeDirectory; import java.io.IOException; @@ -32,13 +32,13 @@ */ public class TimecodeSampleDescriptionAtom extends SampleDescriptionAtom { - public TimecodeSampleDescriptionAtom(SequentialReader reader, Atom atom) throws IOException + public TimecodeSampleDescriptionAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); } @Override - TimecodeSampleDescription getSampleDescription(SequentialReader reader) throws IOException + TimecodeSampleDescription getSampleDescription(ReaderInfo reader) throws IOException { return new TimecodeSampleDescription(reader); } @@ -60,7 +60,7 @@ class TimecodeSampleDescription extends SampleDescription int frameDuration; int numberOfFrames; - public TimecodeSampleDescription(SequentialReader reader) throws IOException + public TimecodeSampleDescription(ReaderInfo reader) throws IOException { super(reader); diff --git a/Source/com/drew/metadata/mov/atoms/VideoInformationMediaHeaderAtom.java b/Source/com/drew/metadata/mov/atoms/VideoInformationMediaHeaderAtom.java index 6727c8920..49f6454f3 100644 --- a/Source/com/drew/metadata/mov/atoms/VideoInformationMediaHeaderAtom.java +++ b/Source/com/drew/metadata/mov/atoms/VideoInformationMediaHeaderAtom.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.media.QuickTimeVideoDirectory; import java.io.IOException; @@ -35,7 +35,7 @@ public class VideoInformationMediaHeaderAtom extends FullAtom int graphicsMode; int[] opcolor; - public VideoInformationMediaHeaderAtom(SequentialReader reader, Atom atom) throws IOException + public VideoInformationMediaHeaderAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); diff --git a/Source/com/drew/metadata/mov/atoms/VideoSampleDescriptionAtom.java b/Source/com/drew/metadata/mov/atoms/VideoSampleDescriptionAtom.java index 503b9012c..7d95bbb37 100644 --- a/Source/com/drew/metadata/mov/atoms/VideoSampleDescriptionAtom.java +++ b/Source/com/drew/metadata/mov/atoms/VideoSampleDescriptionAtom.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mov.atoms; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mov.QuickTimeDictionary; import com.drew.metadata.mov.media.QuickTimeVideoDirectory; @@ -33,13 +34,13 @@ */ public class VideoSampleDescriptionAtom extends SampleDescriptionAtom { - public VideoSampleDescriptionAtom(SequentialReader reader, Atom atom) throws IOException + public VideoSampleDescriptionAtom(ReaderInfo reader, Atom atom) throws IOException { super(reader, atom); } @Override - VideoSampleDescription getSampleDescription(SequentialReader reader) throws IOException + VideoSampleDescription getSampleDescription(ReaderInfo reader) throws IOException { return new VideoSampleDescription(reader); } @@ -90,13 +91,13 @@ class VideoSampleDescription extends SampleDescription int depth; int colorTableID; - public VideoSampleDescription(SequentialReader reader) throws IOException + public VideoSampleDescription(ReaderInfo reader) throws IOException { super(reader); version = reader.getUInt16(); revisionLevel = reader.getUInt16(); - vendor = reader.getString(4); + vendor = reader.getString(4, Charsets.UTF_8); temporalQuality = reader.getUInt32(); spatialQuality = reader.getUInt32(); width = reader.getUInt16(); @@ -105,7 +106,7 @@ public VideoSampleDescription(SequentialReader reader) throws IOException verticalResolution = reader.getUInt32(); dataSize = reader.getUInt32(); frameCount = reader.getUInt16(); - compressorName = reader.getString(32); + compressorName = reader.getString(32, Charsets.UTF_8); depth = reader.getUInt16(); colorTableID = reader.getInt16(); } diff --git a/Source/com/drew/metadata/mov/media/QuickTimeMusicHandler.java b/Source/com/drew/metadata/mov/media/QuickTimeMusicHandler.java index 6a3c2b8a4..8dd609707 100644 --- a/Source/com/drew/metadata/mov/media/QuickTimeMusicHandler.java +++ b/Source/com/drew/metadata/mov/media/QuickTimeMusicHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mov.QuickTimeMediaHandler; @@ -53,20 +53,20 @@ protected String getMediaInformation() } @Override - protected void processSampleDescription(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { MusicSampleDescriptionAtom musicSampleDescriptionAtom = new MusicSampleDescriptionAtom(reader, atom); musicSampleDescriptionAtom.addMetadata(directory); } @Override - protected void processMediaInformation(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { // Not yet implemented } @Override - protected void processTimeToSample(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { // Not yet implemented } diff --git a/Source/com/drew/metadata/mov/media/QuickTimeSoundHandler.java b/Source/com/drew/metadata/mov/media/QuickTimeSoundHandler.java index 0f3787ff0..8669fbd81 100644 --- a/Source/com/drew/metadata/mov/media/QuickTimeSoundHandler.java +++ b/Source/com/drew/metadata/mov/media/QuickTimeSoundHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mov.*; @@ -54,21 +54,21 @@ protected String getMediaInformation() } @Override - public void processSampleDescription(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + public void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { SoundSampleDescriptionAtom soundSampleDescriptionAtom = new SoundSampleDescriptionAtom(reader, atom); soundSampleDescriptionAtom.addMetadata(directory); } @Override - public void processMediaInformation(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + public void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { SoundInformationMediaHeaderAtom soundInformationMediaHeaderAtom = new SoundInformationMediaHeaderAtom(reader, atom); soundInformationMediaHeaderAtom.addMetadata(directory); } @Override - protected void processTimeToSample(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { directory.setDouble(QuickTimeSoundDirectory.TAG_AUDIO_SAMPLE_RATE, QuickTimeHandlerFactory.HANDLER_PARAM_TIME_SCALE); } diff --git a/Source/com/drew/metadata/mov/media/QuickTimeSubtitleHandler.java b/Source/com/drew/metadata/mov/media/QuickTimeSubtitleHandler.java index 819383c17..d41bc6863 100644 --- a/Source/com/drew/metadata/mov/media/QuickTimeSubtitleHandler.java +++ b/Source/com/drew/metadata/mov/media/QuickTimeSubtitleHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mov.QuickTimeMediaHandler; @@ -54,20 +54,20 @@ protected String getMediaInformation() } @Override - protected void processSampleDescription(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { SubtitleSampleDescriptionAtom subtitleSampleDescriptionAtom = new SubtitleSampleDescriptionAtom(reader, atom); subtitleSampleDescriptionAtom.addMetadata(directory); } @Override - protected void processMediaInformation(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { // Not yet implemented } @Override - protected void processTimeToSample(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { // Not yet implemented } diff --git a/Source/com/drew/metadata/mov/media/QuickTimeTextHandler.java b/Source/com/drew/metadata/mov/media/QuickTimeTextHandler.java index c136f2173..7b6feddd6 100644 --- a/Source/com/drew/metadata/mov/media/QuickTimeTextHandler.java +++ b/Source/com/drew/metadata/mov/media/QuickTimeTextHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mov.QuickTimeAtomTypes; @@ -54,20 +54,20 @@ protected String getMediaInformation() } @Override - protected void processSampleDescription(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { TextSampleDescriptionAtom textSampleDescriptionAtom = new TextSampleDescriptionAtom(reader, atom); textSampleDescriptionAtom.addMetadata(directory); } @Override - protected void processMediaInformation(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { // Not yet implemented } @Override - protected void processTimeToSample(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { // Not yet implemented } diff --git a/Source/com/drew/metadata/mov/media/QuickTimeTimecodeHandler.java b/Source/com/drew/metadata/mov/media/QuickTimeTimecodeHandler.java index 72fb7bf09..6d4bad51d 100644 --- a/Source/com/drew/metadata/mov/media/QuickTimeTimecodeHandler.java +++ b/Source/com/drew/metadata/mov/media/QuickTimeTimecodeHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mov.QuickTimeAtomTypes; @@ -55,21 +55,21 @@ protected String getMediaInformation() } @Override - public void processSampleDescription(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + public void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { TimecodeSampleDescriptionAtom timecodeSampleDescriptionAtom = new TimecodeSampleDescriptionAtom(reader, atom); timecodeSampleDescriptionAtom.addMetadata(directory); } @Override - public void processMediaInformation(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + public void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { TimecodeInformationMediaAtom timecodeInformationMediaAtom = new TimecodeInformationMediaAtom(reader, atom); timecodeInformationMediaAtom.addMetadata(directory); } @Override - protected void processTimeToSample(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + protected void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { // Do nothing } diff --git a/Source/com/drew/metadata/mov/media/QuickTimeVideoHandler.java b/Source/com/drew/metadata/mov/media/QuickTimeVideoHandler.java index ba34266e7..048ee3203 100644 --- a/Source/com/drew/metadata/mov/media/QuickTimeVideoHandler.java +++ b/Source/com/drew/metadata/mov/media/QuickTimeVideoHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mov.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mov.QuickTimeAtomTypes; @@ -56,21 +56,21 @@ protected QuickTimeVideoDirectory getDirectory() } @Override - public void processSampleDescription(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + public void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { VideoSampleDescriptionAtom videoSampleDescriptionAtom = new VideoSampleDescriptionAtom(reader, atom); videoSampleDescriptionAtom.addMetadata(directory); } @Override - public void processMediaInformation(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + public void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { VideoInformationMediaHeaderAtom videoInformationMediaHeaderAtom = new VideoInformationMediaHeaderAtom(reader, atom); videoInformationMediaHeaderAtom.addMetadata(directory); } @Override - public void processTimeToSample(@NotNull SequentialReader reader, @NotNull Atom atom) throws IOException + public void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Atom atom) throws IOException { TimeToSampleAtom timeToSampleAtom = new TimeToSampleAtom(reader, atom); timeToSampleAtom.addMetadata(directory); diff --git a/Source/com/drew/metadata/mov/metadata/QuickTimeDataHandler.java b/Source/com/drew/metadata/mov/metadata/QuickTimeDataHandler.java index 1729883b5..a8cddd3b0 100644 --- a/Source/com/drew/metadata/mov/metadata/QuickTimeDataHandler.java +++ b/Source/com/drew/metadata/mov/metadata/QuickTimeDataHandler.java @@ -22,7 +22,8 @@ import com.drew.imaging.quicktime.QuickTimeHandler; import com.drew.lang.ByteUtil; -import com.drew.lang.SequentialByteArrayReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Metadata; @@ -66,7 +67,8 @@ protected boolean shouldAcceptContainer(@NotNull Atom atom) protected QuickTimeHandler processAtom(@NotNull Atom atom, @Nullable byte[] payload) throws IOException { if (payload != null) { - SequentialByteArrayReader reader = new SequentialByteArrayReader(payload); + //SequentialByteArrayReader reader = new SequentialByteArrayReader(payload); + ReaderInfo reader = ReaderInfo.createFromArray(payload); if (atom.type.equals(QuickTimeAtomTypes.ATOM_KEYS)) { processKeys(reader); } else if (atom.type.equals(QuickTimeAtomTypes.ATOM_DATA)) { @@ -82,7 +84,7 @@ protected QuickTimeHandler processAtom(@NotNull Atom atom, @Nullable byte[] payl } @Override - protected void processKeys(@NotNull SequentialByteArrayReader reader) throws IOException + protected void processKeys(@NotNull ReaderInfo reader) throws IOException { // Version 1-byte and Flags 3-bytes reader.skip(4); @@ -96,7 +98,7 @@ protected void processKeys(@NotNull SequentialByteArrayReader reader) throws IOE } @Override - protected void processData(@NotNull byte[] payload, @NotNull SequentialByteArrayReader reader) throws IOException + protected void processData(@NotNull byte[] payload, @NotNull ReaderInfo reader) throws IOException { int type = reader.getInt32(); // 4 bytes: locale indicator @@ -106,7 +108,7 @@ protected void processData(@NotNull byte[] payload, @NotNull SequentialByteArray int length = payload.length - 8; switch (type) { case 1: - directory.setString(tag, reader.getString(length, "UTF-8")); + directory.setString(tag, reader.getString(length, Charsets.UTF_8)); break; case 13: case 14: @@ -114,9 +116,8 @@ protected void processData(@NotNull byte[] payload, @NotNull SequentialByteArray directory.setByteArray(tag, reader.getBytes(length)); break; case 22: - byte[] buf = new byte[4]; - reader.getBytes(buf, 4 - length, length); - directory.setInt(tag, new SequentialByteArrayReader(buf).getInt32()); + byte[] buf = reader.getBytes(4 - length, length); + directory.setInt(tag, ReaderInfo.createFromArray(buf).getInt32()); break; case 23: directory.setFloat(tag, reader.getFloat32()); diff --git a/Source/com/drew/metadata/mov/metadata/QuickTimeDirectoryHandler.java b/Source/com/drew/metadata/mov/metadata/QuickTimeDirectoryHandler.java index e0a624ef0..1e7285dda 100644 --- a/Source/com/drew/metadata/mov/metadata/QuickTimeDirectoryHandler.java +++ b/Source/com/drew/metadata/mov/metadata/QuickTimeDirectoryHandler.java @@ -21,7 +21,7 @@ package com.drew.metadata.mov.metadata; import com.drew.imaging.quicktime.QuickTimeHandler; -import com.drew.lang.SequentialByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Metadata; @@ -61,7 +61,7 @@ protected boolean shouldAcceptContainer(@NotNull Atom atom) protected QuickTimeHandler processAtom(@NotNull Atom atom, @Nullable byte[] payload) throws IOException { if (payload != null) { - SequentialByteArrayReader reader = new SequentialByteArrayReader(payload); + ReaderInfo reader = ReaderInfo.createFromArray(payload); if (atom.type.equals(QuickTimeAtomTypes.ATOM_DATA) && currentData != null) { processData(payload, reader); } else { @@ -78,7 +78,7 @@ protected QuickTimeHandler processAtom(@NotNull Atom atom, @Nullable byte[] payl } @Override - protected void processData(@NotNull byte[] payload, @NotNull SequentialByteArrayReader reader) throws IOException + protected void processData(@NotNull byte[] payload, @NotNull ReaderInfo reader) throws IOException { // 4 bytes: type indicator // 4 bytes: locale indicator @@ -88,7 +88,7 @@ protected void processData(@NotNull byte[] payload, @NotNull SequentialByteArray } @Override - protected void processKeys(@NotNull SequentialByteArrayReader reader) throws IOException + protected void processKeys(@NotNull ReaderInfo reader) throws IOException { // Do nothing } diff --git a/Source/com/drew/metadata/mp3/Mp3Reader.java b/Source/com/drew/metadata/mp3/Mp3Reader.java index 3af21af27..e3b6f2b94 100644 --- a/Source/com/drew/metadata/mp3/Mp3Reader.java +++ b/Source/com/drew/metadata/mp3/Mp3Reader.java @@ -21,13 +21,11 @@ package com.drew.metadata.mp3; import com.drew.imaging.ImageProcessingException; -import com.drew.lang.SequentialReader; -import com.drew.lang.StreamReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import java.io.IOException; -import java.io.InputStream; /** * Sources: http://id3.org/mp3Frame @@ -38,15 +36,12 @@ public class Mp3Reader { - public void extract(@NotNull final InputStream inputStream, @NotNull final Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) { Mp3Directory directory = new Mp3Directory(); metadata.addDirectory(directory); try { - inputStream.reset(); - SequentialReader reader = new StreamReader(inputStream); - int header = reader.getInt32(); // ID: MPEG-2.5, MPEG-2, or MPEG-1 diff --git a/Source/com/drew/metadata/mp4/Mp4BoxHandler.java b/Source/com/drew/metadata/mp4/Mp4BoxHandler.java index ed2af4464..22f97e7f1 100644 --- a/Source/com/drew/metadata/mp4/Mp4BoxHandler.java +++ b/Source/com/drew/metadata/mp4/Mp4BoxHandler.java @@ -21,8 +21,7 @@ package com.drew.metadata.mp4; import com.drew.imaging.mp4.Mp4Handler; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Metadata; @@ -72,7 +71,7 @@ public boolean shouldAcceptContainer(@NotNull Box box) public Mp4Handler processBox(@NotNull Box box, @Nullable byte[] payload) throws IOException { if (payload != null) { - SequentialReader reader = new SequentialByteArrayReader(payload); + ReaderInfo reader = ReaderInfo.createFromArray(payload); if (box.type.equals(Mp4BoxTypes.BOX_MOVIE_HEADER)) { processMovieHeader(reader, box); } else if (box.type.equals(Mp4BoxTypes.BOX_FILE_TYPE)) { @@ -93,24 +92,24 @@ public Mp4Handler processBox(@NotNull Box box, @Nullable byte[] payload) throws return this; } - private void processFileType(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + private void processFileType(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { FileTypeBox fileTypeBox = new FileTypeBox(reader, box); fileTypeBox.addMetadata(directory); } - private void processMovieHeader(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + private void processMovieHeader(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { MovieHeaderBox movieHeaderBox = new MovieHeaderBox(reader, box); movieHeaderBox.addMetadata(directory); } - private void processMediaHeader(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + private void processMediaHeader(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { MediaHeaderBox mediaHeaderBox = new MediaHeaderBox(reader, box); } - private void processTrackHeader(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + private void processTrackHeader(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { TrackHeaderBox trackHeaderBox = new TrackHeaderBox(reader, box); trackHeaderBox.addMetadata(directory); diff --git a/Source/com/drew/metadata/mp4/Mp4MediaHandler.java b/Source/com/drew/metadata/mp4/Mp4MediaHandler.java index c757a9fe7..3d14cdc08 100644 --- a/Source/com/drew/metadata/mp4/Mp4MediaHandler.java +++ b/Source/com/drew/metadata/mp4/Mp4MediaHandler.java @@ -21,8 +21,7 @@ package com.drew.metadata.mp4; import com.drew.imaging.mp4.Mp4Handler; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Metadata; @@ -72,7 +71,7 @@ public boolean shouldAcceptContainer(@NotNull Box box) public Mp4Handler processBox(@NotNull Box box, @Nullable byte[] payload) throws IOException { if (payload != null) { - SequentialReader reader = new SequentialByteArrayReader(payload); + ReaderInfo reader = ReaderInfo.createFromArray(payload); if (box.type.equals(getMediaInformation())) { processMediaInformation(reader, box); } else if (box.type.equals(Mp4BoxTypes.BOX_SAMPLE_DESCRIPTION)) { @@ -86,9 +85,9 @@ public Mp4Handler processBox(@NotNull Box box, @Nullable byte[] payload) throws protected abstract String getMediaInformation(); - protected abstract void processSampleDescription(@NotNull SequentialReader reader, @NotNull Box box) throws IOException; + protected abstract void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException; - protected abstract void processMediaInformation(@NotNull SequentialReader reader, @NotNull Box box) throws IOException; + protected abstract void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException; - protected abstract void processTimeToSample(@NotNull SequentialReader reader, @NotNull Box box) throws IOException; + protected abstract void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException; } diff --git a/Source/com/drew/metadata/mp4/boxes/AudioSampleEntry.java b/Source/com/drew/metadata/mp4/boxes/AudioSampleEntry.java index 78de479ec..eab77cf8b 100644 --- a/Source/com/drew/metadata/mp4/boxes/AudioSampleEntry.java +++ b/Source/com/drew/metadata/mp4/boxes/AudioSampleEntry.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mp4.Mp4Dictionary; import com.drew.metadata.mp4.media.Mp4SoundDirectory; @@ -35,7 +35,7 @@ public class AudioSampleEntry extends SampleEntry int samplesize; long samplerate; - public AudioSampleEntry(SequentialReader reader, Box box) throws IOException + public AudioSampleEntry(ReaderInfo reader, Box box) throws IOException { super(reader, box); diff --git a/Source/com/drew/metadata/mp4/boxes/Box.java b/Source/com/drew/metadata/mp4/boxes/Box.java index 578f017d9..1938b0105 100644 --- a/Source/com/drew/metadata/mp4/boxes/Box.java +++ b/Source/com/drew/metadata/mp4/boxes/Box.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; +import com.drew.lang.Charsets; import java.io.IOException; @@ -33,17 +34,17 @@ public class Box public String type; public String usertype; - public Box(SequentialReader reader) throws IOException + public Box(ReaderInfo reader) throws IOException { this.size = reader.getUInt32(); - this.type = reader.getString(4); + this.type = reader.getString(4, Charsets.UTF_8); if (size == 1) { size = reader.getInt64(); } else if (size == 0) { size = -1; } if (type.equals("uuid")) { - usertype = reader.getString(16); + usertype = reader.getString(16, Charsets.UTF_8); } } diff --git a/Source/com/drew/metadata/mp4/boxes/FileTypeBox.java b/Source/com/drew/metadata/mp4/boxes/FileTypeBox.java index 569d1153a..363a640fb 100644 --- a/Source/com/drew/metadata/mp4/boxes/FileTypeBox.java +++ b/Source/com/drew/metadata/mp4/boxes/FileTypeBox.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mp4.Mp4Directory; import java.io.IOException; @@ -35,15 +36,15 @@ public class FileTypeBox extends Box long minorVersion; ArrayList compatibleBrands; - public FileTypeBox(SequentialReader reader, Box box) throws IOException + public FileTypeBox(ReaderInfo reader, Box box) throws IOException { super(box); - majorBrand = reader.getString(4); + majorBrand = reader.getString(4, Charsets.UTF_8); minorVersion = reader.getUInt32(); compatibleBrands = new ArrayList(); for (int i = 16; i < size; i += 4) { - compatibleBrands.add(reader.getString(4)); + compatibleBrands.add(reader.getString(4, Charsets.UTF_8)); } } diff --git a/Source/com/drew/metadata/mp4/boxes/FullBox.java b/Source/com/drew/metadata/mp4/boxes/FullBox.java index aad0e7d68..3622d7e47 100644 --- a/Source/com/drew/metadata/mp4/boxes/FullBox.java +++ b/Source/com/drew/metadata/mp4/boxes/FullBox.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -32,7 +32,7 @@ public class FullBox extends Box protected int version; protected byte[] flags; - public FullBox(SequentialReader reader, Box box) throws IOException + public FullBox(ReaderInfo reader, Box box) throws IOException { super(box); diff --git a/Source/com/drew/metadata/mp4/boxes/HandlerBox.java b/Source/com/drew/metadata/mp4/boxes/HandlerBox.java index bd061e5d9..be865f9de 100644 --- a/Source/com/drew/metadata/mp4/boxes/HandlerBox.java +++ b/Source/com/drew/metadata/mp4/boxes/HandlerBox.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import java.io.IOException; import java.nio.charset.Charset; @@ -39,12 +40,12 @@ public String getHandlerType() String name; - public HandlerBox(SequentialReader reader, Box box) throws IOException + public HandlerBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); reader.skip(4); // Pre-defined - handlerType = reader.getString(4); + handlerType = reader.getString(4, Charsets.UTF_8); reader.skip(12); // Reserved name = reader.getNullTerminatedString((int)size - 32, Charset.defaultCharset()); } diff --git a/Source/com/drew/metadata/mp4/boxes/HintMediaHeaderBox.java b/Source/com/drew/metadata/mp4/boxes/HintMediaHeaderBox.java index 843d0ca83..cb3c9e86b 100644 --- a/Source/com/drew/metadata/mp4/boxes/HintMediaHeaderBox.java +++ b/Source/com/drew/metadata/mp4/boxes/HintMediaHeaderBox.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mp4.media.Mp4HintDirectory; import java.io.IOException; @@ -35,7 +35,7 @@ public class HintMediaHeaderBox extends FullBox long maxbitrate; long avgbitrate; - public HintMediaHeaderBox(SequentialReader reader, Box box) throws IOException + public HintMediaHeaderBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); diff --git a/Source/com/drew/metadata/mp4/boxes/MediaHeaderBox.java b/Source/com/drew/metadata/mp4/boxes/MediaHeaderBox.java index b3c665dd1..e85b88eee 100644 --- a/Source/com/drew/metadata/mp4/boxes/MediaHeaderBox.java +++ b/Source/com/drew/metadata/mp4/boxes/MediaHeaderBox.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mp4.Mp4HandlerFactory; import java.io.IOException; @@ -36,7 +36,7 @@ public class MediaHeaderBox extends FullBox long duration; String language; - public MediaHeaderBox(SequentialReader reader, Box box) throws IOException + public MediaHeaderBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); diff --git a/Source/com/drew/metadata/mp4/boxes/MovieHeaderBox.java b/Source/com/drew/metadata/mp4/boxes/MovieHeaderBox.java index 6b1cc9b4e..841a1536d 100644 --- a/Source/com/drew/metadata/mp4/boxes/MovieHeaderBox.java +++ b/Source/com/drew/metadata/mp4/boxes/MovieHeaderBox.java @@ -21,7 +21,7 @@ package com.drew.metadata.mp4.boxes; import com.drew.lang.Rational; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mp4.Mp4Directory; import java.io.IOException; @@ -42,7 +42,7 @@ public class MovieHeaderBox extends FullBox protected int[] matrix; protected long nextTrackID; - public MovieHeaderBox(SequentialReader reader, Box box) throws IOException + public MovieHeaderBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); if (version == 1) { diff --git a/Source/com/drew/metadata/mp4/boxes/SampleEntry.java b/Source/com/drew/metadata/mp4/boxes/SampleEntry.java index b76eba551..ace8279f6 100644 --- a/Source/com/drew/metadata/mp4/boxes/SampleEntry.java +++ b/Source/com/drew/metadata/mp4/boxes/SampleEntry.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import java.io.IOException; @@ -34,13 +35,13 @@ public class SampleEntry extends FullBox String format; int dataReferenceIndex; - public SampleEntry(SequentialReader reader, Box box) throws IOException + public SampleEntry(ReaderInfo reader, Box box) throws IOException { super(reader, box); numberOfEntries = reader.getUInt32(); sampleDescriptionSize = reader.getUInt32(); - format = reader.getString(4); + format = reader.getString(4, Charsets.UTF_8); reader.skip(6); // Reserved dataReferenceIndex = reader.getUInt16(); } diff --git a/Source/com/drew/metadata/mp4/boxes/SoundMediaHeaderBox.java b/Source/com/drew/metadata/mp4/boxes/SoundMediaHeaderBox.java index df6957083..9c8ba0962 100644 --- a/Source/com/drew/metadata/mp4/boxes/SoundMediaHeaderBox.java +++ b/Source/com/drew/metadata/mp4/boxes/SoundMediaHeaderBox.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mp4.media.Mp4SoundDirectory; import java.io.IOException; @@ -32,7 +32,7 @@ public class SoundMediaHeaderBox extends FullBox { int balance; - public SoundMediaHeaderBox(SequentialReader reader, Box box) throws IOException + public SoundMediaHeaderBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); diff --git a/Source/com/drew/metadata/mp4/boxes/TimeToSampleBox.java b/Source/com/drew/metadata/mp4/boxes/TimeToSampleBox.java index 56fcb67cb..f029bf8f9 100644 --- a/Source/com/drew/metadata/mp4/boxes/TimeToSampleBox.java +++ b/Source/com/drew/metadata/mp4/boxes/TimeToSampleBox.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mp4.Mp4HandlerFactory; import com.drew.metadata.mp4.media.Mp4SoundDirectory; import com.drew.metadata.mp4.media.Mp4VideoDirectory; @@ -36,7 +36,7 @@ public class TimeToSampleBox extends FullBox long entryCount; ArrayList entries; - public TimeToSampleBox(SequentialReader reader, Box box) throws IOException + public TimeToSampleBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); diff --git a/Source/com/drew/metadata/mp4/boxes/TrackHeaderBox.java b/Source/com/drew/metadata/mp4/boxes/TrackHeaderBox.java index 6b228ca50..a16d2d223 100644 --- a/Source/com/drew/metadata/mp4/boxes/TrackHeaderBox.java +++ b/Source/com/drew/metadata/mp4/boxes/TrackHeaderBox.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mp4.Mp4Directory; import java.awt.*; @@ -42,7 +42,7 @@ public class TrackHeaderBox extends FullBox long width; long height; - public TrackHeaderBox(SequentialReader reader, Box box) throws IOException + public TrackHeaderBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); diff --git a/Source/com/drew/metadata/mp4/boxes/VideoMediaHeaderBox.java b/Source/com/drew/metadata/mp4/boxes/VideoMediaHeaderBox.java index dada8f535..d12382ce1 100644 --- a/Source/com/drew/metadata/mp4/boxes/VideoMediaHeaderBox.java +++ b/Source/com/drew/metadata/mp4/boxes/VideoMediaHeaderBox.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mp4.media.Mp4VideoDirectory; import java.io.IOException; @@ -33,7 +33,7 @@ public class VideoMediaHeaderBox extends FullBox int graphicsMode; int[] opcolor; - public VideoMediaHeaderBox(SequentialReader reader, Box box) throws IOException + public VideoMediaHeaderBox(ReaderInfo reader, Box box) throws IOException { super(reader, box); diff --git a/Source/com/drew/metadata/mp4/boxes/VisualSampleEntry.java b/Source/com/drew/metadata/mp4/boxes/VisualSampleEntry.java index c3c009d37..1240c8a4c 100644 --- a/Source/com/drew/metadata/mp4/boxes/VisualSampleEntry.java +++ b/Source/com/drew/metadata/mp4/boxes/VisualSampleEntry.java @@ -20,7 +20,8 @@ */ package com.drew.metadata.mp4.boxes; -import com.drew.lang.SequentialReader; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.metadata.mp4.Mp4Dictionary; import com.drew.metadata.mp4.media.Mp4VideoDirectory; @@ -44,13 +45,13 @@ public class VisualSampleEntry extends SampleEntry String compressorname; int depth; - public VisualSampleEntry(SequentialReader reader, Box box) throws IOException + public VisualSampleEntry(ReaderInfo reader, Box box) throws IOException { super(reader, box); version = reader.getInt16(); revisionLevel = reader.getInt16(); - vendor = reader.getString(4); + vendor = reader.getString(4, Charsets.UTF_8); temporalQuality = reader.getInt32(); spatialQuality = reader.getInt32(); width = reader.getUInt16(); @@ -59,7 +60,7 @@ public VisualSampleEntry(SequentialReader reader, Box box) throws IOException vertresolution = reader.getUInt32(); reader.skip(4); // Reserved frameCount = reader.getUInt16(); - compressorname = reader.getString(32); + compressorname = reader.getString(32, Charsets.UTF_8); depth = reader.getUInt16(); reader.skip(2); // Pre-defined } diff --git a/Source/com/drew/metadata/mp4/media/Mp4HintHandler.java b/Source/com/drew/metadata/mp4/media/Mp4HintHandler.java index 48c0343e9..0815691fb 100644 --- a/Source/com/drew/metadata/mp4/media/Mp4HintHandler.java +++ b/Source/com/drew/metadata/mp4/media/Mp4HintHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mp4.Mp4BoxTypes; @@ -51,20 +51,20 @@ protected String getMediaInformation() } @Override - protected void processSampleDescription(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + protected void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { } @Override - protected void processMediaInformation(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + protected void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { HintMediaHeaderBox hintMediaHeaderBox = new HintMediaHeaderBox(reader, box); hintMediaHeaderBox.addMetadata(directory); } @Override - protected void processTimeToSample(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + protected void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { } diff --git a/Source/com/drew/metadata/mp4/media/Mp4MetaHandler.java b/Source/com/drew/metadata/mp4/media/Mp4MetaHandler.java index de7644331..30aeb5735 100644 --- a/Source/com/drew/metadata/mp4/media/Mp4MetaHandler.java +++ b/Source/com/drew/metadata/mp4/media/Mp4MetaHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mp4.Mp4ContainerTypes; @@ -50,19 +50,19 @@ protected String getMediaInformation() } @Override - protected void processSampleDescription(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + protected void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { } @Override - protected void processMediaInformation(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + protected void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { } @Override - protected void processTimeToSample(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + protected void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { } diff --git a/Source/com/drew/metadata/mp4/media/Mp4SoundHandler.java b/Source/com/drew/metadata/mp4/media/Mp4SoundHandler.java index 0739751c5..b51caf49e 100644 --- a/Source/com/drew/metadata/mp4/media/Mp4SoundHandler.java +++ b/Source/com/drew/metadata/mp4/media/Mp4SoundHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mp4.Mp4BoxTypes; @@ -53,21 +53,21 @@ protected String getMediaInformation() } @Override - public void processSampleDescription(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + public void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { AudioSampleEntry audioSampleEntry = new AudioSampleEntry(reader, box); audioSampleEntry.addMetadata(directory); } @Override - public void processMediaInformation(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + public void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { SoundMediaHeaderBox soundMediaHeaderBox = new SoundMediaHeaderBox(reader, box); soundMediaHeaderBox.addMetadata(directory); } @Override - protected void processTimeToSample(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + protected void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { TimeToSampleBox timeToSampleBox = new TimeToSampleBox(reader, box); timeToSampleBox.addMetadata(directory); diff --git a/Source/com/drew/metadata/mp4/media/Mp4TextHandler.java b/Source/com/drew/metadata/mp4/media/Mp4TextHandler.java index e4835e2bf..377fd29d1 100644 --- a/Source/com/drew/metadata/mp4/media/Mp4TextHandler.java +++ b/Source/com/drew/metadata/mp4/media/Mp4TextHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mp4.Mp4ContainerTypes; @@ -50,19 +50,19 @@ protected String getMediaInformation() } @Override - protected void processSampleDescription(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + protected void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { } @Override - protected void processMediaInformation(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + protected void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { } @Override - protected void processTimeToSample(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + protected void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { } diff --git a/Source/com/drew/metadata/mp4/media/Mp4VideoHandler.java b/Source/com/drew/metadata/mp4/media/Mp4VideoHandler.java index c938117d3..30baa60ad 100644 --- a/Source/com/drew/metadata/mp4/media/Mp4VideoHandler.java +++ b/Source/com/drew/metadata/mp4/media/Mp4VideoHandler.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.mp4.media; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.mp4.Mp4BoxTypes; @@ -53,21 +53,21 @@ protected Mp4VideoDirectory getDirectory() } @Override - public void processSampleDescription(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + public void processSampleDescription(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { VisualSampleEntry visualSampleEntry = new VisualSampleEntry(reader, box); visualSampleEntry.addMetadata(directory); } @Override - public void processMediaInformation(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + public void processMediaInformation(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { VideoMediaHeaderBox videoMediaHeaderBox = new VideoMediaHeaderBox(reader, box); videoMediaHeaderBox.addMetadata(directory); } @Override - public void processTimeToSample(@NotNull SequentialReader reader, @NotNull Box box) throws IOException + public void processTimeToSample(@NotNull ReaderInfo reader, @NotNull Box box) throws IOException { TimeToSampleBox timeToSampleBox = new TimeToSampleBox(reader, box); timeToSampleBox.addMetadata(directory); diff --git a/Source/com/drew/metadata/netpbm/NetpbmHeaderDescriptor.java b/Source/com/drew/metadata/netpbm/NetpbmHeaderDescriptor.java new file mode 100644 index 000000000..334b419e3 --- /dev/null +++ b/Source/com/drew/metadata/netpbm/NetpbmHeaderDescriptor.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2017 Drew Noakes + * + * 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.metadata.netpbm; + +import com.drew.lang.annotations.Nullable; +import com.drew.lang.annotations.NotNull; +import com.drew.metadata.TagDescriptor; + +import static com.drew.metadata.netpbm.NetpbmHeaderDirectory.*; + +/** + * @author Drew Noakes https://drewnoakes.com + * @author Kevin Mott https://github.com/kwhopper + */ +@SuppressWarnings("WeakerAccess") +public class NetpbmHeaderDescriptor extends TagDescriptor +{ + public NetpbmHeaderDescriptor(@NotNull NetpbmHeaderDirectory directory) + { + super(directory); + } + + @Override + public String getDescription(int tagType) + { + switch (tagType) { + case TAG_FORMAT_TYPE: + return getFormatTypeDescription(); + default: + return super.getDescription(tagType); + } + } + + @Nullable + private String getFormatTypeDescription() + { + return getIndexedDescription(TAG_FORMAT_TYPE, 1, + "Portable BitMap (ASCII, B&W)", + "Portable GrayMap (ASCII, B&W)", + "Portable PixMap (ASCII, B&W)", + "Portable BitMap (RAW, B&W)", + "Portable GrayMap (RAW, B&W)", + "Portable PixMap (RAW, B&W)", + "Portable Arbitrary Map" + ); + } +} diff --git a/Source/com/drew/metadata/netpbm/NetpbmHeaderDirectory.java b/Source/com/drew/metadata/netpbm/NetpbmHeaderDirectory.java new file mode 100644 index 000000000..e9d4bbcc8 --- /dev/null +++ b/Source/com/drew/metadata/netpbm/NetpbmHeaderDirectory.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2017 Drew Noakes + * + * 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.metadata.netpbm; + +import com.drew.lang.annotations.NotNull; +import com.drew.metadata.Directory; + +import java.util.HashMap; +/** + * + * @author Drew Noakes https://drewnoakes.com + * @author Kevin Mott https://github.com/kwhopper + */ +public class NetpbmHeaderDirectory extends Directory +{ + public static final int TAG_FORMAT_TYPE = 1; + public static final int TAG_WIDTH = 2; + public static final int TAG_HEIGHT = 3; + public static final int TAG_MAXIMUM_VALUE = 4; + + @NotNull + protected static final HashMap _tagNameMap = new HashMap(); + + static { + _tagNameMap.put(TAG_FORMAT_TYPE, "Format Type"); + _tagNameMap.put(TAG_WIDTH, "Width"); + _tagNameMap.put(TAG_HEIGHT, "Height"); + _tagNameMap.put(TAG_MAXIMUM_VALUE, "Maximum Value"); + } + + public NetpbmHeaderDirectory() + { + this.setDescriptor(new NetpbmHeaderDescriptor(this)); + } + + @Override + @NotNull + public String getName() + { + return "Netpbm"; + } + + @Override + @NotNull + protected HashMap getTagNameMap() + { + return _tagNameMap; + } +} diff --git a/Source/com/drew/metadata/netpbm/NetpbmReader.java b/Source/com/drew/metadata/netpbm/NetpbmReader.java new file mode 100644 index 000000000..35bacd0b1 --- /dev/null +++ b/Source/com/drew/metadata/netpbm/NetpbmReader.java @@ -0,0 +1,106 @@ +/* + * Copyright 2002-2017 Drew Noakes + * + * 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.metadata.netpbm; + +import com.drew.imaging.ImageProcessingException; +import com.drew.lang.IterableWordReader; +import com.drew.lang.ReaderInfo; +import com.drew.lang.annotations.NotNull; +import com.drew.metadata.Metadata; +import java.io.IOException; +import java.util.*; + +/** + * Reads metadata from Netpbm files. + * + * @author Drew Noakes https://drewnoakes.com + * @author Kevin Mott https://github.com/kwhopper + */ +public class NetpbmReader +{ + public void extract(ReaderInfo reader, final @NotNull Metadata metadata) throws IOException, ImageProcessingException + { + NetpbmHeaderDirectory directory = new NetpbmHeaderDirectory(); + + Iterator words = new IterableWordReader(reader); + String current = ""; + + if (!words.hasNext()) + throw new IOException("Unexpected EOF."); + String magic = words.next(); + + if (magic.charAt(0) != 'P') + throw new ImageProcessingException("Invalid Netpbm magic number"); + int magicNum = magic.charAt(1) - '0'; + if (magicNum < 1 || magicNum > 7) + throw new ImageProcessingException("Invalid Netpbm magic number"); + + directory.setInt(NetpbmHeaderDirectory.TAG_FORMAT_TYPE, magicNum); + + if (!words.hasNext()) + throw new IOException("Unexpected EOF."); + + current = words.next(); + int width; + try { + width = Integer.parseInt(current); + } + catch(NumberFormatException parseError) { + throw new IOException("Width is not parseable as an integer."); + } + + directory.setInt(NetpbmHeaderDirectory.TAG_WIDTH, width); + + if (!words.hasNext()) + throw new IOException("Unexpected EOF."); + + current = words.next(); + int height; + try { + height = Integer.parseInt(current); + } + catch(NumberFormatException parseError) { + throw new IOException("Height is not parseable as an integer."); + } + + directory.setInt(NetpbmHeaderDirectory.TAG_HEIGHT, height); + + if (!words.hasNext()) + throw new IOException("Unexpected EOF."); + + current = words.next(); + if (magicNum != 1 && magicNum != 6) + { + int maxValue; + try { + maxValue = Integer.parseInt(current); + } + catch(NumberFormatException parseError) { + throw new IOException("MaxValue is not parseable as an integer."); + } + + directory.setInt(NetpbmHeaderDirectory.TAG_MAXIMUM_VALUE, maxValue); + } + + metadata.addDirectory(directory); + } +} diff --git a/Source/com/drew/metadata/pcx/PcxReader.java b/Source/com/drew/metadata/pcx/PcxReader.java index 4acf82de0..1d26e1cf3 100644 --- a/Source/com/drew/metadata/pcx/PcxReader.java +++ b/Source/com/drew/metadata/pcx/PcxReader.java @@ -21,7 +21,7 @@ package com.drew.metadata.pcx; import com.drew.imaging.ImageProcessingException; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; @@ -38,7 +38,7 @@ */ public class PcxReader { - public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) { reader.setMotorolaByteOrder(false); diff --git a/Source/com/drew/metadata/photoshop/DuckyReader.java b/Source/com/drew/metadata/photoshop/DuckyReader.java index f740acaf7..c096c6797 100644 --- a/Source/com/drew/metadata/photoshop/DuckyReader.java +++ b/Source/com/drew/metadata/photoshop/DuckyReader.java @@ -22,9 +22,9 @@ import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; +import com.drew.imaging.jpeg.JpegSegment; import com.drew.lang.Charsets; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; @@ -40,7 +40,9 @@ public class DuckyReader implements JpegSegmentMetadataReader { @NotNull - private static final String JPEG_SEGMENT_PREAMBLE = "Ducky"; + public static final String JPEG_SEGMENT_ID = "Ducky"; + @NotNull + public static final String JPEG_SEGMENT_PREAMBLE = "Ducky"; @NotNull public Iterable getSegmentTypes() @@ -48,22 +50,18 @@ public Iterable getSegmentTypes() return Collections.singletonList(JpegSegmentType.APPC); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) throws IOException { final int preambleLength = JPEG_SEGMENT_PREAMBLE.length(); - for (byte[] segmentBytes : segments) { + for (JpegSegment segment : segments) { // Ensure data starts with the necessary preamble - if (segmentBytes.length < preambleLength || !JPEG_SEGMENT_PREAMBLE.equals(new String(segmentBytes, 0, preambleLength))) - continue; - - extract( - new SequentialByteArrayReader(segmentBytes, preambleLength), - metadata); + if (segment.getReader().getLength() >= preambleLength && JPEG_SEGMENT_ID.equals(segment.getPreamble())) + extract(segment.getReader().Clone(preambleLength, segment.getReader().getLength() - preambleLength), metadata); } } - public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) { DuckyDirectory directory = new DuckyDirectory(); metadata.addDirectory(directory); diff --git a/Source/com/drew/metadata/photoshop/PhotoshopDescriptor.java b/Source/com/drew/metadata/photoshop/PhotoshopDescriptor.java index a43672f5c..03127a290 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopDescriptor.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopDescriptor.java @@ -20,9 +20,8 @@ */ package com.drew.metadata.photoshop; -import com.drew.lang.ByteArrayReader; import com.drew.lang.Charsets; -import com.drew.lang.RandomAccessReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.TagDescriptor; @@ -95,7 +94,7 @@ public String getJpegQualityString() if (b == null) return _directory.getString(TAG_JPEG_QUALITY); - RandomAccessReader reader = new ByteArrayReader(b); + ReaderInfo reader = ReaderInfo.createFromArray(b); int q = reader.getUInt16(0); // & 0xFFFF; int f = reader.getUInt16(2); // & 0xFFFF; int s = reader.getUInt16(4); @@ -164,7 +163,7 @@ public String getPixelAspectRatioString() byte[] bytes = _directory.getByteArray(TAG_PIXEL_ASPECT_RATIO); if (bytes == null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); double d = reader.getDouble64(4); return Double.toString(d); } catch (Exception e) { @@ -179,7 +178,7 @@ public String getPrintScaleDescription() byte bytes[] = _directory.getByteArray(TAG_PRINT_SCALE); if (bytes == null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); int style = reader.getInt32(0); float locX = reader.getFloat32(2); float locY = reader.getFloat32(6); @@ -206,7 +205,7 @@ public String getResolutionInfoDescription() byte[] bytes = _directory.getByteArray(TAG_RESOLUTION_INFO); if (bytes == null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); float resX = reader.getS15Fixed16(0); float resY = reader.getS15Fixed16(8); // is this the correct offset? it's only reading 4 bytes each time DecimalFormat format = new DecimalFormat("0.##"); @@ -223,7 +222,7 @@ public String getVersionDescription() final byte[] bytes = _directory.getByteArray(TAG_VERSION); if (bytes == null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); int pos = 0; int ver = reader.getInt32(0); pos += 4; @@ -250,7 +249,7 @@ public String getSlicesDescription() final byte bytes[] = _directory.getByteArray(TAG_SLICES); if (bytes == null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); int nameLength = reader.getInt32(20); String name = reader.getString(24, nameLength * 2, "UTF-16"); int pos = 24 + nameLength * 2; @@ -269,7 +268,7 @@ public String getThumbnailDescription(int tagType) byte[] v = _directory.getByteArray(tagType); if (v == null) return null; - RandomAccessReader reader = new ByteArrayReader(v); + ReaderInfo reader = ReaderInfo.createFromArray(v); int format = reader.getInt32(0); int width = reader.getInt32(4); int height = reader.getInt32(8); @@ -301,7 +300,7 @@ private String get32BitNumberString(int tag) byte[] bytes = _directory.getByteArray(tag); if (bytes == null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); try { return String.format("%d", reader.getInt32(0)); } catch (IOException e) { @@ -334,7 +333,7 @@ public String getClippingPathNameString(int tagType) byte[] bytes = _directory.getByteArray(tagType); if (bytes == null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); int length = reader.getByte(0); return new String(reader.getBytes(1, length), "UTF-8"); } catch (Exception e) { @@ -349,7 +348,7 @@ public String getPathString(int tagType) byte[] bytes = _directory.getByteArray(tagType); if (bytes == null) return null; - RandomAccessReader reader = new ByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); int length = (int) (reader.getLength() - reader.getByte((int)reader.getLength() - 1) - 1) / 26; String fillRecord = null; diff --git a/Source/com/drew/metadata/photoshop/PhotoshopReader.java b/Source/com/drew/metadata/photoshop/PhotoshopReader.java index dd06b8638..f972586c2 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopReader.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopReader.java @@ -23,9 +23,9 @@ import com.drew.imaging.ImageProcessingException; import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.ByteArrayReader; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.imaging.jpeg.JpegSegment; +import com.drew.lang.ReaderInfo; +import com.drew.lang.Charsets; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.exif.ExifReader; @@ -33,6 +33,7 @@ import com.drew.metadata.iptc.IptcReader; import com.drew.metadata.xmp.XmpReader; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; @@ -48,7 +49,9 @@ public class PhotoshopReader implements JpegSegmentMetadataReader { @NotNull - private static final String JPEG_SEGMENT_PREAMBLE = "Photoshop 3.0"; + public static final String JPEG_SEGMENT_ID = "Photoshop"; + @NotNull + public static final String JPEG_SEGMENT_PREAMBLE = "Photoshop 3.0"; @NotNull public Iterable getSegmentTypes() @@ -56,24 +59,22 @@ public Iterable getSegmentTypes() return Collections.singletonList(JpegSegmentType.APPD); } - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) throws IOException { final int preambleLength = JPEG_SEGMENT_PREAMBLE.length(); - for (byte[] segmentBytes : segments) { + for (JpegSegment segment : segments) { // Ensure data starts with the necessary preamble - if (segmentBytes.length < preambleLength + 1 || !JPEG_SEGMENT_PREAMBLE.equals(new String(segmentBytes, 0, preambleLength))) - continue; - - extract( - new SequentialByteArrayReader(segmentBytes, preambleLength + 1), - segmentBytes.length - preambleLength - 1, - metadata); + if (segment.getReader().getLength() >= preambleLength + 1 && JPEG_SEGMENT_ID.equals(segment.getPreamble())) + extract(segment.getReader().Clone(preambleLength + 1, segment.getReader().getLength() - preambleLength - 1), metadata); } } - public void extract(@NotNull final SequentialReader reader, int length, @NotNull final Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) throws IOException { + if (!reader.isMotorolaByteOrder()) + reader = reader.Clone(false); + PhotoshopDirectory directory = new PhotoshopDirectory(); metadata.addDirectory(directory); @@ -89,10 +90,11 @@ public void extract(@NotNull final SequentialReader reader, int length, @NotNull int pos = 0; int clippingPathCount = 0; + int length = (int)reader.getLength(); while (pos < length) { try { // 4 bytes for the signature ("8BIM", "PHUT", etc.) - String signature = reader.getString(4); + String signature = reader.getString(4, Charsets.UTF_8); pos += 4; // 2 bytes for the resource identifier (tag type). @@ -102,6 +104,7 @@ public void extract(@NotNull final SequentialReader reader, int length, @NotNull // A variable number of bytes holding a pascal string (two leading bytes for length). short descriptionLength = reader.getUInt8(); pos += 1; + // Some basic bounds checking if (descriptionLength < 0 || descriptionLength + pos > length) throw new ImageProcessingException("Invalid string length"); @@ -117,6 +120,7 @@ public void extract(@NotNull final SequentialReader reader, int length, @NotNull // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { + //reader.skip(1); reader.skip(1); pos++; } @@ -124,9 +128,12 @@ public void extract(@NotNull final SequentialReader reader, int length, @NotNull // 4 bytes for the size of the resource data that follows. int byteCount = reader.getInt32(); pos += 4; + // The resource data. - byte[] tagBytes = reader.getBytes(byteCount); + ReaderInfo tagReader = reader.Clone(byteCount); + reader.skip(byteCount); pos += byteCount; + // The number of bytes is padded with a trailing zero, if needed, to make the size even. if (pos % 2 != 0) { reader.skip(1); @@ -135,16 +142,16 @@ public void extract(@NotNull final SequentialReader reader, int length, @NotNull if (signature.equals("8BIM")) { if (tagType == PhotoshopDirectory.TAG_IPTC) - new IptcReader().extract(new SequentialByteArrayReader(tagBytes), metadata, tagBytes.length, directory); + new IptcReader().extract(tagReader, metadata, directory); else if (tagType == PhotoshopDirectory.TAG_ICC_PROFILE_BYTES) - new IccReader().extract(new ByteArrayReader(tagBytes), metadata, directory); + new IccReader().extract(tagReader, 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(tagReader, metadata, directory); else if (tagType == PhotoshopDirectory.TAG_XMP_DATA) - new XmpReader().extract(tagBytes, metadata, directory); + new XmpReader().extract(tagReader, metadata, directory); else if (tagType >= 0x07D0 && tagType <= 0x0BB6) { clippingPathCount++; - tagBytes = Arrays.copyOf(tagBytes, tagBytes.length + description.length() + 1); + byte[] tagBytes = Arrays.copyOf(tagReader.toArray(), (int)tagReader.getLength() + description.length() + 1); // Append description(name) to end of byte array with 1 byte before the description representing the length for (int i = tagBytes.length - description.length() - 1; i < tagBytes.length; i++) { if (i % (tagBytes.length - description.length() - 1 + description.length()) == 0) @@ -156,7 +163,7 @@ else if (tagType >= 0x07D0 && tagType <= 0x0BB6) { directory.setByteArray(0x07CF + clippingPathCount, tagBytes); } else - directory.setByteArray(tagType, tagBytes); + directory.setByteArray(tagType, tagReader.toArray()); if (tagType >= 0x0fa0 && tagType <= 0x1387) PhotoshopDirectory._tagNameMap.put(tagType, String.format("Plug-in %d Data", tagType - 0x0fa0 + 1)); diff --git a/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java index b5de35ea8..4352282c0 100644 --- a/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java +++ b/Source/com/drew/metadata/photoshop/PhotoshopTiffHandler.java @@ -1,8 +1,6 @@ package com.drew.metadata.photoshop; -import com.drew.lang.ByteArrayReader; -import com.drew.lang.RandomAccessReader; -import com.drew.lang.SequentialByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; @@ -36,10 +34,12 @@ public PhotoshopTiffHandler(Metadata metadata, Directory parentDirectory) super(metadata, parentDirectory); } + @Override public boolean customProcessTag(final int tagOffset, - final @NotNull Set processedIfdOffsets, - final int tiffHeaderOffset, - final @NotNull RandomAccessReader reader, + final @NotNull Set processedIfdOffsets, + //final int tiffHeaderOffset, + //final @NotNull RandomAccessReader reader, + @NotNull ReaderInfo reader, final int tagId, final int byteCount) throws IOException { @@ -48,14 +48,14 @@ public boolean customProcessTag(final int tagOffset, new XmpReader().extract(reader.getBytes(tagOffset, byteCount), _metadata); return true; case TAG_PHOTOSHOP_IMAGE_RESOURCES: - new PhotoshopReader().extract(new SequentialByteArrayReader(reader.getBytes(tagOffset, byteCount)), byteCount, _metadata); + new PhotoshopReader().extract(reader.Clone(tagOffset, byteCount), _metadata); return true; case TAG_ICC_PROFILES: - new IccReader().extract(new ByteArrayReader(reader.getBytes(tagOffset, byteCount)), _metadata); + new IccReader().extract(reader.Clone(tagOffset, byteCount), _metadata); return true; } - return super.customProcessTag(tagOffset, processedIfdOffsets, tiffHeaderOffset, reader, tagId, byteCount); + return super.customProcessTag(tagOffset, processedIfdOffsets, reader, tagId, byteCount); } } diff --git a/Source/com/drew/metadata/photoshop/PsdReader.java b/Source/com/drew/metadata/photoshop/PsdReader.java index 55542d79c..60db5d4c1 100644 --- a/Source/com/drew/metadata/photoshop/PsdReader.java +++ b/Source/com/drew/metadata/photoshop/PsdReader.java @@ -21,7 +21,7 @@ package com.drew.metadata.photoshop; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; @@ -34,7 +34,7 @@ */ public class PsdReader { - public void extract(@NotNull final SequentialReader reader, @NotNull final Metadata metadata) + public void extract(@NotNull ReaderInfo reader, @NotNull final Metadata metadata) { PsdHeaderDirectory directory = new PsdHeaderDirectory(); metadata.addDirectory(directory); @@ -109,7 +109,7 @@ public void extract(@NotNull final SequentialReader reader, @NotNull final Metad assert(sectionLength <= Integer.MAX_VALUE); - new PhotoshopReader().extract(reader, (int)sectionLength, metadata); + new PhotoshopReader().extract(reader.Clone(0, (int)sectionLength), metadata); } catch (IOException e) { // ignore } diff --git a/Source/com/drew/metadata/png/PngDescriptor.java b/Source/com/drew/metadata/png/PngDescriptor.java index b59d4d8cc..447613106 100644 --- a/Source/com/drew/metadata/png/PngDescriptor.java +++ b/Source/com/drew/metadata/png/PngDescriptor.java @@ -22,8 +22,7 @@ import com.drew.imaging.png.PngColorType; import com.drew.lang.KeyValuePair; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.TagDescriptor; @@ -155,7 +154,7 @@ public String getBackgroundColorDescription() if (bytes == null) { return null; } - SequentialReader reader = new SequentialByteArrayReader(bytes); + ReaderInfo reader = ReaderInfo.createFromArray(bytes); try { // TODO do we need to normalise these based upon the bit depth? switch (bytes.length) { diff --git a/Source/com/drew/metadata/wav/WavRiffHandler.java b/Source/com/drew/metadata/wav/WavRiffHandler.java index ed7e4f059..f7e067d1f 100644 --- a/Source/com/drew/metadata/wav/WavRiffHandler.java +++ b/Source/com/drew/metadata/wav/WavRiffHandler.java @@ -1,7 +1,7 @@ package com.drew.metadata.wav; import com.drew.imaging.riff.RiffHandler; -import com.drew.lang.ByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.MetadataException; @@ -39,11 +39,13 @@ public WavRiffHandler(@NotNull Metadata metadata) metadata.addDirectory(_directory); } + @Override public boolean shouldAcceptRiffIdentifier(@NotNull String identifier) { return identifier.equals(WavDirectory.FORMAT); } + @Override public boolean shouldAcceptChunk(@NotNull String fourCC) { return fourCC.equals(WavDirectory.CHUNK_FORMAT) @@ -63,11 +65,12 @@ public boolean shouldAcceptList(@NotNull String fourCC) } } - public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) + @Override + public void processChunk(@NotNull String fourCC, @NotNull ReaderInfo chunkReader) // @NotNull byte[] payload) { try { if (fourCC.equals(WavDirectory.CHUNK_FORMAT)) { - ByteArrayReader reader = new ByteArrayReader(payload); + ReaderInfo reader = chunkReader.Clone(); reader.setMotorolaByteOrder(false); int wFormatTag = reader.getInt16(0); int wChannels = reader.getInt16(2); @@ -97,7 +100,7 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) } else if (fourCC.equals(WavDirectory.CHUNK_DATA)) { try { if (_directory.containsTag(WavDirectory.TAG_BYTES_PER_SEC)) { - double duration = (double)payload.length / _directory.getDouble(WavDirectory.TAG_BYTES_PER_SEC); + double duration = (double)chunkReader.getLength() / _directory.getDouble(WavDirectory.TAG_BYTES_PER_SEC); Integer hours = (int)duration / (int)(Math.pow(60, 2)); Integer minutes = ((int)duration / (int)(Math.pow(60, 1))) - (hours * 60); Integer seconds = (int)Math.round((duration / (Math.pow(60, 0))) - (minutes * 60)); @@ -108,6 +111,7 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) _directory.addError("Error calculating duration: bytes per second not found"); } }else if (WavDirectory._tagIntegerMap.containsKey(fourCC)) { + byte[] payload = chunkReader.toArray(); _directory.setString(WavDirectory._tagIntegerMap.get(fourCC), new String(payload).substring(0, payload.length - 1)); } } catch (IOException ex) { diff --git a/Source/com/drew/metadata/webp/WebpRiffHandler.java b/Source/com/drew/metadata/webp/WebpRiffHandler.java index eb27b66af..702535137 100644 --- a/Source/com/drew/metadata/webp/WebpRiffHandler.java +++ b/Source/com/drew/metadata/webp/WebpRiffHandler.java @@ -21,8 +21,7 @@ package com.drew.metadata.webp; import com.drew.imaging.riff.RiffHandler; -import com.drew.lang.ByteArrayReader; -import com.drew.lang.RandomAccessReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.exif.ExifReader; @@ -53,11 +52,13 @@ public WebpRiffHandler(@NotNull Metadata metadata) _metadata = metadata; } + @Override public boolean shouldAcceptRiffIdentifier(@NotNull String identifier) { return identifier.equals(WebpDirectory.FORMAT); } + @Override public boolean shouldAcceptChunk(@NotNull String fourCC) { return fourCC.equals(WebpDirectory.CHUNK_VP8X) @@ -74,18 +75,18 @@ public boolean shouldAcceptList(@NotNull String fourCC) return false; } - public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) + @Override + public void processChunk(@NotNull String fourCC, @NotNull ReaderInfo chunkReader) throws IOException // @NotNull byte[] payload) { -// System.out.println("Chunk " + fourCC + " " + payload.length + " bytes"); WebpDirectory directory = new WebpDirectory(); if (fourCC.equals(WebpDirectory.CHUNK_EXIF)) { - new ExifReader().extract(new ByteArrayReader(payload), _metadata); + new ExifReader().extract(chunkReader, _metadata); } else if (fourCC.equals(WebpDirectory.CHUNK_ICCP)) { - new IccReader().extract(new ByteArrayReader(payload), _metadata); + new IccReader().extract(chunkReader, _metadata); } else if (fourCC.equals(WebpDirectory.CHUNK_XMP)) { - new XmpReader().extract(payload, _metadata); - } else if (fourCC.equals(WebpDirectory.CHUNK_VP8X) && payload.length == 10) { - RandomAccessReader reader = new ByteArrayReader(payload); + new XmpReader().extract(chunkReader, _metadata); + } else if (fourCC.equals(WebpDirectory.CHUNK_VP8X) && chunkReader.getLength() == 10) { + ReaderInfo reader = chunkReader.Clone(); reader.setMotorolaByteOrder(false); try { @@ -111,8 +112,8 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) } catch (IOException e) { e.printStackTrace(System.err); } - } else if (fourCC.equals(WebpDirectory.CHUNK_VP8L) && payload.length > 4) { - RandomAccessReader reader = new ByteArrayReader(payload); + } else if (fourCC.equals(WebpDirectory.CHUNK_VP8L) && chunkReader.getLength() > 4) { + ReaderInfo reader = chunkReader.Clone(); reader.setMotorolaByteOrder(false); try { @@ -138,8 +139,8 @@ public void processChunk(@NotNull String fourCC, @NotNull byte[] payload) } catch (IOException e) { e.printStackTrace(System.err); } - } else if (fourCC.equals(WebpDirectory.CHUNK_VP8) && payload.length > 9) { - RandomAccessReader reader = new ByteArrayReader(payload); + } else if (fourCC.equals(WebpDirectory.CHUNK_VP8) && chunkReader.getLength() > 9) { + ReaderInfo reader = chunkReader.Clone(); reader.setMotorolaByteOrder(false); try { diff --git a/Source/com/drew/metadata/xmp/XmpReader.java b/Source/com/drew/metadata/xmp/XmpReader.java index bfcb2bb94..80292a501 100644 --- a/Source/com/drew/metadata/xmp/XmpReader.java +++ b/Source/com/drew/metadata/xmp/XmpReader.java @@ -28,8 +28,9 @@ import com.adobe.xmp.properties.XMPPropertyInfo; import com.drew.imaging.jpeg.JpegSegmentMetadataReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.SequentialByteArrayReader; -import com.drew.lang.SequentialReader; +import com.drew.imaging.jpeg.JpegSegment; +import com.drew.lang.Charsets; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Directory; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; @@ -40,6 +41,8 @@ import java.util.Collection; import java.util.Collections; +import java.io.ByteArrayInputStream; + /** * Extracts XMP data from JPEG APP1 segments. *

    @@ -57,9 +60,15 @@ public class XmpReader implements JpegSegmentMetadataReader { @NotNull - private static final String XMP_JPEG_PREAMBLE = "http://ns.adobe.com/xap/1.0/\0"; + public static final String JPEG_SEGMENT_ID = "XMP"; + @NotNull + public static final String JPEG_SEGMENT_PREAMBLE = "http://ns.adobe.com/xap/1.0/\0"; + + @NotNull + public static final String JPEG_SEGMENT_EXTENSION_ID = "XMP (Extended)"; @NotNull - private static final String XMP_EXTENSION_JPEG_PREAMBLE = "http://ns.adobe.com/xmp/extension/\0"; + public static final String JPEG_SEGMENT_PREAMBLE_EXTENSION = "http://ns.adobe.com/xmp/extension/\0"; + @NotNull private static final String SCHEMA_XMP_NOTES = "http://ns.adobe.com/xmp/note/"; @NotNull @@ -70,8 +79,14 @@ public class XmpReader implements JpegSegmentMetadataReader */ private static final int EXTENDED_XMP_GUID_LENGTH = 32; private static final int EXTENDED_XMP_INT_LENGTH = 4; + + private static final byte[] jpegSegmentPreambleBytes = JPEG_SEGMENT_PREAMBLE.getBytes(); + private static final byte[] jpegSegmentPreambleExtensionBytes = JPEG_SEGMENT_PREAMBLE_EXTENSION.getBytes(); + + private static final byte[] xmpStringBytes = "XMP".getBytes(); @NotNull + @Override public Iterable getSegmentTypes() { return Collections.singletonList(JpegSegmentType.APP1); @@ -83,27 +98,24 @@ public Iterable getSegmentTypes() * * @param segments The byte array from which the metadata should be extracted. * @param metadata The {@link Metadata} object into which extracted values should be merged. - * @param segmentType The {@link JpegSegmentType} being read. */ - public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata, @NotNull JpegSegmentType segmentType) + @Override + public void readJpegSegments(@NotNull Iterable segments, @NotNull Metadata metadata) throws IOException { - final int preambleLength = XMP_JPEG_PREAMBLE.length(); - final int extensionPreambleLength = XMP_EXTENSION_JPEG_PREAMBLE.length(); + final int preambleLength = JPEG_SEGMENT_PREAMBLE.length(); + final int extensionPreambleLength = JPEG_SEGMENT_PREAMBLE_EXTENSION.length(); String extendedXMPGUID = null; byte[] extendedXMPBuffer = null; - for (byte[] segmentBytes : segments) { + for (JpegSegment segment : segments) { // XMP in a JPEG file has an identifying preamble which is not valid XML - if (segmentBytes.length >= preambleLength) { + if (segment.getReader().getLength() >= preambleLength) { // NOTE we expect the full preamble here, but some images (such as that reported on GitHub #102) // start with "XMP\0://ns.adobe.com/xap/1.0/" which appears to be an error but is easily recovered // from. In such cases, the actual XMP data begins at the same offset. - if (XMP_JPEG_PREAMBLE.equalsIgnoreCase(new String(segmentBytes, 0, preambleLength)) || - "XMP".equalsIgnoreCase(new String(segmentBytes, 0, 3))) { + if (isXmpSegment(segment)) { + extract(segment.getReader().Clone(preambleLength, segment.getReader().getLength() - preambleLength), metadata); - byte[] xmlBytes = new byte[segmentBytes.length - preambleLength]; - System.arraycopy(segmentBytes, preambleLength, xmlBytes, 0, xmlBytes.length); - extract(xmlBytes, metadata); // Check in the Standard XMP if there should be a Extended XMP part in other chunks. extendedXMPGUID = getExtendedXMPGUID(metadata); continue; @@ -112,10 +124,10 @@ public void readJpegSegments(@NotNull Iterable segments, @NotNull Metada // If we know that there's Extended XMP chunks, look for them. if (extendedXMPGUID != null && - segmentBytes.length >= extensionPreambleLength && - XMP_EXTENSION_JPEG_PREAMBLE.equalsIgnoreCase(new String(segmentBytes, 0, extensionPreambleLength))) { + segment.getReader().getLength() >= extensionPreambleLength && + isExtendedXmpSegment(segment)) { - extendedXMPBuffer = processExtendedXMPChunk(metadata, segmentBytes, extendedXMPGUID, extendedXMPBuffer); + extendedXMPBuffer = processExtendedXMPChunk(metadata, segment.getReader().toArray(), extendedXMPGUID, extendedXMPBuffer); } } @@ -125,6 +137,35 @@ public void readJpegSegments(@NotNull Iterable segments, @NotNull Metada } } + private static boolean isXmpSegment(JpegSegment segment) throws IOException + { + // NOTE we expect the full preamble here, but some images (such as that reported on GitHub #102) + // start with "XMP\0://ns.adobe.com/xap/1.0/" which appears to be an error but is easily recovered + // from. In such cases, the actual XMP data begins at the same offset. + return segment.getReader().startsWith(jpegSegmentPreambleBytes) || + segment.getReader().startsWith(xmpStringBytes); + } + + private static boolean isExtendedXmpSegment(JpegSegment segment) throws IOException + { + return segment.getReader().startsWith(jpegSegmentPreambleExtensionBytes); + } + + /** + * Performs the XMP data extraction, adding found values to the specified instance of {@link Metadata}. + *

    + * The extraction is done with Adobe's XMPCore library. + */ + public void extract(@NotNull ReaderInfo reader, @NotNull Metadata metadata) throws IOException + { + extract(reader.Clone().toArray(), metadata); + } + + public void extract(@NotNull ReaderInfo reader, @NotNull Metadata metadata, @Nullable Directory parentDirectory) throws IOException + { + extract(reader.Clone().toArray(), metadata, parentDirectory); + } + /** * Performs the XMP data extraction, adding found values to the specified instance of {@link Metadata}. *

    @@ -168,13 +209,13 @@ public void extract(@NotNull final byte[] xmpBytes, int offset, int length, @Not xmpMeta = XMPMetaFactory.parse(buffer.getByteStream()); } - directory.setXMPMeta(xmpMeta); + directory.setXMPMeta(xmpMeta); } catch (XMPException e) { directory.addError("Error processing XMP data: " + e.getMessage()); } if (!directory.isEmpty()) - metadata.addDirectory(directory); + metadata.addDirectory(directory); } /** @@ -261,7 +302,7 @@ private static String getExtendedXMPGUID(@NotNull Metadata metadata) @Nullable private static byte[] processExtendedXMPChunk(@NotNull Metadata metadata, @NotNull byte[] segmentBytes, @NotNull String extendedXMPGUID, @Nullable byte[] extendedXMPBuffer) { - final int extensionPreambleLength = XMP_EXTENSION_JPEG_PREAMBLE.length(); + final int extensionPreambleLength = JPEG_SEGMENT_PREAMBLE_EXTENSION.length(); final int segmentLength = segmentBytes.length; final int totalOffset = extensionPreambleLength + EXTENDED_XMP_GUID_LENGTH + EXTENDED_XMP_INT_LENGTH + EXTENDED_XMP_INT_LENGTH; @@ -276,9 +317,9 @@ private static byte[] processExtendedXMPChunk(@NotNull Metadata metadata, @NotNu * - The offset of this portion as a 32-bit unsigned integer * - The portion of the ExtendedXMP */ - final SequentialReader reader = new SequentialByteArrayReader(segmentBytes); + ReaderInfo reader = ReaderInfo.createFromArray(segmentBytes); reader.skip(extensionPreambleLength); - final String segmentGUID = reader.getString(EXTENDED_XMP_GUID_LENGTH); + final String segmentGUID = reader.getString(EXTENDED_XMP_GUID_LENGTH, Charsets.UTF_8); if (extendedXMPGUID.equals(segmentGUID)) { final int fullLength = (int)reader.getUInt32(); diff --git a/Source/com/drew/tools/ExtractJpegSegmentTool.java b/Source/com/drew/tools/ExtractJpegSegmentTool.java index e62c8763e..c430fd541 100644 --- a/Source/com/drew/tools/ExtractJpegSegmentTool.java +++ b/Source/com/drew/tools/ExtractJpegSegmentTool.java @@ -24,6 +24,7 @@ import com.drew.imaging.jpeg.JpegSegmentData; import com.drew.imaging.jpeg.JpegSegmentReader; import com.drew.imaging.jpeg.JpegSegmentType; +import com.drew.imaging.jpeg.JpegSegment; import com.drew.lang.Iterables; import com.drew.lang.annotations.NotNull; @@ -67,7 +68,7 @@ public static void main(String[] args) throws IOException, JpegProcessingExcepti for (int i = 1; i < args.length; i++) { JpegSegmentType segmentType = JpegSegmentType.valueOf(args[i].toUpperCase()); - if (!segmentType.canContainMetadata) { + if (!JpegSegmentType.canContainMetadata(segmentType)) { System.err.printf("WARNING: Segment type %s cannot contain metadata so it may not be necessary to extract it%n", segmentType); } segmentTypes.add(segmentType); @@ -80,7 +81,7 @@ public static void main(String[] args) throws IOException, JpegProcessingExcepti System.out.println("Reading: " + filePath); - JpegSegmentData segmentData = JpegSegmentReader.readSegments(new File(filePath), segmentTypes); + JpegSegmentData segmentData = JpegSegmentReader.readSegments(new File(filePath), segmentTypes, true); saveSegmentFiles(filePath, segmentData); } @@ -88,7 +89,7 @@ public static void main(String[] args) throws IOException, JpegProcessingExcepti public static void saveSegmentFiles(@NotNull String jpegFilePath, @NotNull JpegSegmentData segmentData) throws IOException { for (JpegSegmentType segmentType : segmentData.getSegmentTypes()) { - List segments = Iterables.toList(segmentData.getSegments(segmentType)); + List segments = Iterables.toList(segmentData.getSegments(segmentType)); if (segments.size() == 0) { continue; } @@ -97,12 +98,12 @@ public static void saveSegmentFiles(@NotNull String jpegFilePath, @NotNull JpegS for (int i = 0; i < segments.size(); i++) { String outputFilePath = String.format("%s.%s.%d", jpegFilePath, segmentType.toString().toLowerCase(), i); System.out.println("Writing: " + outputFilePath); - FileUtil.saveBytes(new File(outputFilePath), segments.get(i)); + FileUtil.saveBytes(new File(outputFilePath), segments.get(i).getReader().toArray()); } } else { String outputFilePath = String.format("%s.%s", jpegFilePath, segmentType.toString().toLowerCase()); System.out.println("Writing: " + outputFilePath); - FileUtil.saveBytes(new File(outputFilePath), segments.get(0)); + FileUtil.saveBytes(new File(outputFilePath), segments.get(0).getReader().toArray()); } } } @@ -114,7 +115,7 @@ private static void printUsage() System.out.print("Where is zero or more of:"); for (JpegSegmentType segmentType : JpegSegmentType.class.getEnumConstants()) { - if (segmentType.canContainMetadata) { + if (JpegSegmentType.canContainMetadata(segmentType)) { System.out.print(" " + segmentType.toString()); } } diff --git a/Source/com/drew/tools/ProcessAllImagesInFolderUtility.java b/Source/com/drew/tools/ProcessAllImagesInFolderUtility.java index 613b2df15..7e3df977e 100644 --- a/Source/com/drew/tools/ProcessAllImagesInFolderUtility.java +++ b/Source/com/drew/tools/ProcessAllImagesInFolderUtility.java @@ -29,6 +29,7 @@ import com.drew.imaging.FileTypeDetector; import com.drew.imaging.ImageMetadataReader; import com.drew.imaging.jpeg.JpegProcessingException; +import com.drew.lang.RandomAccessStream; import com.drew.lang.StringUtil; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; @@ -439,10 +440,15 @@ private static PrintWriter openWriter(@NotNull File file) throws IOException writer.write("FILE: " + file.getName() + NEW_LINE); // Detect file type - BufferedInputStream stream = null; + //BufferedInputStream stream = null; + //FileInputStream stream = null; + RandomAccessFile stream = null; try { - stream = new BufferedInputStream(new FileInputStream(file)); - FileType fileType = FileTypeDetector.detectFileType(stream); + //stream = new BufferedInputStream(new FileInputStream(file)); + //stream = new FileInputStream(file); + stream = new RandomAccessFile(file, "r"); + //FileType fileType = FileTypeDetector.detectFileType(new RandomAccessStream(stream, file.length()).createReader()); + FileType fileType = FileTypeDetector.detectFileType(new RandomAccessStream(stream).createReader()); writer.write(String.format("TYPE: %s" + NEW_LINE, fileType.toString().toUpperCase())); writer.write(NEW_LINE); } finally { diff --git a/Source/com/drew/tools/ProcessUrlUtility.java b/Source/com/drew/tools/ProcessUrlUtility.java index 8308e7ac7..970730b56 100644 --- a/Source/com/drew/tools/ProcessUrlUtility.java +++ b/Source/com/drew/tools/ProcessUrlUtility.java @@ -56,6 +56,8 @@ public static void main(String[] args) throws IOException, JpegProcessingExcepti private static void processUrl(URL url) throws IOException { URLConnection con = url.openConnection(); + long contentLength = con.getContentLengthLong(); + // con.setConnectTimeout(connectTimeout); // con.setReadTimeout(readTimeout); InputStream in = con.getInputStream(); @@ -63,7 +65,7 @@ private static void processUrl(URL url) throws IOException // Read metadata final Metadata metadata; try { - metadata = ImageMetadataReader.readMetadata(in); + metadata = ImageMetadataReader.readMetadata(in, contentLength); } catch (ImageProcessingException e) { // this is an error in the Jpeg segment structure. we're looking for bad handling of // metadata segments. in this case, we didn't even get a segment. diff --git a/Tests/com/drew/imaging/jpeg/JpegMetadataReaderTest.java b/Tests/com/drew/imaging/jpeg/JpegMetadataReaderTest.java index cda6d5447..dfac0494d 100644 --- a/Tests/com/drew/imaging/jpeg/JpegMetadataReaderTest.java +++ b/Tests/com/drew/imaging/jpeg/JpegMetadataReaderTest.java @@ -50,7 +50,8 @@ public void testExtractMetadata() throws Exception @Test public void testExtractMetadataUsingInputStream() throws Exception { - validate(JpegMetadataReader.readMetadata(new FileInputStream((new File("Tests/Data/withExif.jpg"))))); + File file = new File("Tests/Data/withExif.jpg"); + validate(JpegMetadataReader.readMetadata(new FileInputStream(file), file.length())); } @Test diff --git a/Tests/com/drew/imaging/jpeg/JpegSegmentDataTest.java b/Tests/com/drew/imaging/jpeg/JpegSegmentDataTest.java index 7474b2b2c..2fc26db40 100644 --- a/Tests/com/drew/imaging/jpeg/JpegSegmentDataTest.java +++ b/Tests/com/drew/imaging/jpeg/JpegSegmentDataTest.java @@ -20,6 +20,7 @@ */ package com.drew.imaging.jpeg; +import com.drew.lang.ReaderInfo; import org.junit.Test; import static org.junit.Assert.*; @@ -35,12 +36,13 @@ public void testAddAndGetSegment() throws Exception { JpegSegmentData segmentData = new JpegSegmentData(); - byte segmentMarker = (byte)12; + JpegSegmentType segmentType = JpegSegmentType.APP0; byte[] segmentBytes = new byte[] { 1,2,3 }; - segmentData.addSegment(segmentMarker, segmentBytes); - assertEquals(1, segmentData.getSegmentCount(segmentMarker)); - assertArrayEquals(segmentBytes, segmentData.getSegment(segmentMarker)); + JpegSegment segment = new JpegSegment(segmentType, ReaderInfo.createFromArray(segmentBytes)); + segmentData.addSegment(segment); + assertEquals(1, segmentData.getSegmentCount(segmentType)); + assertArrayEquals(segmentBytes, segmentData.getSegment(segmentType).getReader().toArray()); } @Test @@ -48,14 +50,15 @@ public void testContainsSegment() throws Exception { JpegSegmentData segmentData = new JpegSegmentData(); - byte segmentMarker = (byte)12; + JpegSegmentType segmentType = JpegSegmentType.APP0; byte[] segmentBytes = new byte[] { 1,2,3 }; - assertTrue(!segmentData.containsSegment(segmentMarker)); + assertTrue(!segmentData.containsSegment(segmentType)); - segmentData.addSegment(segmentMarker, segmentBytes); + JpegSegment segment = new JpegSegment(segmentType, ReaderInfo.createFromArray(segmentBytes)); + segmentData.addSegment(segment); - assertTrue(segmentData.containsSegment(segmentMarker)); + assertTrue(segmentData.containsSegment(segmentType)); } @Test @@ -63,17 +66,20 @@ public void testAddingMultipleSegments() throws Exception { JpegSegmentData segmentData = new JpegSegmentData(); - byte segmentMarker1 = (byte)12; - byte segmentMarker2 = (byte)21; + JpegSegmentType segmentType1 = JpegSegmentType.APP0; + JpegSegmentType segmentType2 = JpegSegmentType.APP1; byte[] segmentBytes1 = new byte[] { 1,2,3 }; byte[] segmentBytes2 = new byte[] { 3,2,1 }; - segmentData.addSegment(segmentMarker1, segmentBytes1); - segmentData.addSegment(segmentMarker2, segmentBytes2); - assertEquals(1, segmentData.getSegmentCount(segmentMarker1)); - assertEquals(1, segmentData.getSegmentCount(segmentMarker2)); - assertArrayEquals(segmentBytes1, segmentData.getSegment(segmentMarker1)); - assertArrayEquals(segmentBytes2, segmentData.getSegment(segmentMarker2)); + JpegSegment segment1 = new JpegSegment(segmentType1, ReaderInfo.createFromArray(segmentBytes1)); + JpegSegment segment2 = new JpegSegment(segmentType2, ReaderInfo.createFromArray(segmentBytes2)); + + segmentData.addSegment(segment1); + segmentData.addSegment(segment2); + assertEquals(1, segmentData.getSegmentCount(segmentType1)); + assertEquals(1, segmentData.getSegmentCount(segmentType2)); + assertArrayEquals(segmentBytes1, segmentData.getSegment(segmentType1).getReader().toArray()); + assertArrayEquals(segmentBytes2, segmentData.getSegment(segmentType2).getReader().toArray()); } @Test @@ -81,16 +87,19 @@ public void testSegmentWithMultipleOccurrences() throws Exception { JpegSegmentData segmentData = new JpegSegmentData(); - byte segmentMarker = (byte)12; + JpegSegmentType segmentType = JpegSegmentType.APP0; byte[] segmentBytes1 = new byte[] { 1,2,3 }; byte[] segmentBytes2 = new byte[] { 3,2,1 }; - segmentData.addSegment(segmentMarker, segmentBytes1); - segmentData.addSegment(segmentMarker, segmentBytes2); - assertEquals(2, segmentData.getSegmentCount(segmentMarker)); - assertArrayEquals(segmentBytes1, segmentData.getSegment(segmentMarker)); - assertArrayEquals(segmentBytes1, segmentData.getSegment(segmentMarker, 0)); - assertArrayEquals(segmentBytes2, segmentData.getSegment(segmentMarker, 1)); + JpegSegment segment1 = new JpegSegment(segmentType, ReaderInfo.createFromArray(segmentBytes1)); + JpegSegment segment2 = new JpegSegment(segmentType, ReaderInfo.createFromArray(segmentBytes2)); + + segmentData.addSegment(segment1); + segmentData.addSegment(segment2); + assertEquals(2, segmentData.getSegmentCount(segmentType)); + assertArrayEquals(segmentBytes1, segmentData.getSegment(segmentType).getReader().toArray()); + assertArrayEquals(segmentBytes1, segmentData.getSegment(segmentType, 0).getReader().toArray()); + assertArrayEquals(segmentBytes2, segmentData.getSegment(segmentType, 1).getReader().toArray()); } @Test @@ -98,20 +107,23 @@ public void testRemoveSegmentOccurrence() throws Exception { JpegSegmentData segmentData = new JpegSegmentData(); - byte segmentMarker = (byte)12; + JpegSegmentType segmentType = JpegSegmentType.APP0; byte[] segmentBytes1 = new byte[] { 1,2,3 }; byte[] segmentBytes2 = new byte[] { 3,2,1 }; - segmentData.addSegment(segmentMarker, segmentBytes1); - segmentData.addSegment(segmentMarker, segmentBytes2); + JpegSegment segment1 = new JpegSegment(segmentType, ReaderInfo.createFromArray(segmentBytes1)); + JpegSegment segment2 = new JpegSegment(segmentType, ReaderInfo.createFromArray(segmentBytes2)); + + segmentData.addSegment(segment1); + segmentData.addSegment(segment2); - assertEquals(2, segmentData.getSegmentCount(segmentMarker)); + assertEquals(2, segmentData.getSegmentCount(segmentType)); - assertArrayEquals(segmentBytes1, segmentData.getSegment(segmentMarker, 0)); + assertArrayEquals(segmentBytes1, segmentData.getSegment(segmentType, 0).getReader().toArray()); - segmentData.removeSegmentOccurrence(segmentMarker, 0); + segmentData.removeSegmentOccurrence(segmentType, 0); - assertArrayEquals(segmentBytes2, segmentData.getSegment(segmentMarker, 0)); + assertArrayEquals(segmentBytes2, segmentData.getSegment(segmentType, 0).getReader().toArray()); } @Test @@ -119,21 +131,24 @@ public void testRemoveSegment() throws Exception { JpegSegmentData segmentData = new JpegSegmentData(); - byte segmentMarker = (byte)12; + JpegSegmentType segmentType = JpegSegmentType.APP0; byte[] segmentBytes1 = new byte[] { 1,2,3 }; byte[] segmentBytes2 = new byte[] { 3,2,1 }; - segmentData.addSegment(segmentMarker, segmentBytes1); - segmentData.addSegment(segmentMarker, segmentBytes2); + JpegSegment segment1 = new JpegSegment(segmentType, ReaderInfo.createFromArray(segmentBytes1)); + JpegSegment segment2 = new JpegSegment(segmentType, ReaderInfo.createFromArray(segmentBytes2)); + + segmentData.addSegment(segment1); + segmentData.addSegment(segment2); - assertEquals(2, segmentData.getSegmentCount(segmentMarker)); - assertTrue(segmentData.containsSegment(segmentMarker)); + assertEquals(2, segmentData.getSegmentCount(segmentType)); + assertTrue(segmentData.containsSegment(segmentType)); - assertArrayEquals(segmentBytes1, segmentData.getSegment(segmentMarker, 0)); + assertArrayEquals(segmentBytes1, segmentData.getSegment(segmentType, 0).getReader().toArray()); - segmentData.removeSegment(segmentMarker); + segmentData.removeSegment(segmentType); - assertTrue(!segmentData.containsSegment(segmentMarker)); - assertEquals(0, segmentData.getSegmentCount(segmentMarker)); + assertTrue(!segmentData.containsSegment(segmentType)); + assertEquals(0, segmentData.getSegmentCount(segmentType)); } } diff --git a/Tests/com/drew/imaging/jpeg/JpegSegmentReaderTest.java b/Tests/com/drew/imaging/jpeg/JpegSegmentReaderTest.java index edea9ab1a..f4601ca1a 100644 --- a/Tests/com/drew/imaging/jpeg/JpegSegmentReaderTest.java +++ b/Tests/com/drew/imaging/jpeg/JpegSegmentReaderTest.java @@ -39,39 +39,39 @@ public class JpegSegmentReaderTest @Test public void testReadAllSegments() throws Exception { - JpegSegmentData segmentData = JpegSegmentReader.readSegments(new File("Tests/Data/withExifAndIptc.jpg"), null); + JpegSegmentData segmentData = JpegSegmentReader.readSegments(new File("Tests/Data/withExifAndIptc.jpg"), null, true); assertEquals(1, segmentData.getSegmentCount(JpegSegmentType.APP0)); assertArrayEquals( FileUtil.readBytes("Tests/Data/withExifAndIptc.jpg.app0"), - segmentData.getSegment(JpegSegmentType.APP0)); + segmentData.getSegment(JpegSegmentType.APP0).getReader().toArray()); assertNull(segmentData.getSegment(JpegSegmentType.APP0, 1)); assertEquals(2, segmentData.getSegmentCount(JpegSegmentType.APP1)); assertArrayEquals( FileUtil.readBytes("Tests/Data/withExifAndIptc.jpg.app1.0"), - segmentData.getSegment(JpegSegmentType.APP1, 0)); + segmentData.getSegment(JpegSegmentType.APP1, 0).getReader().toArray()); assertArrayEquals( FileUtil.readBytes("Tests/Data/withExifAndIptc.jpg.app1.1"), - segmentData.getSegment(JpegSegmentType.APP1, 1)); + segmentData.getSegment(JpegSegmentType.APP1, 1).getReader().toArray()); assertNull(segmentData.getSegment(JpegSegmentType.APP1, 2)); assertEquals(1, segmentData.getSegmentCount(JpegSegmentType.APP2)); assertArrayEquals( FileUtil.readBytes("Tests/Data/withExifAndIptc.jpg.app2"), - segmentData.getSegment(JpegSegmentType.APP2)); + segmentData.getSegment(JpegSegmentType.APP2).getReader().toArray()); assertNull(segmentData.getSegment(JpegSegmentType.APP2, 1)); assertEquals(1, segmentData.getSegmentCount(JpegSegmentType.APPD)); assertArrayEquals( FileUtil.readBytes("Tests/Data/withExifAndIptc.jpg.appd"), - segmentData.getSegment(JpegSegmentType.APPD)); + segmentData.getSegment(JpegSegmentType.APPD).getReader().toArray()); assertNull(segmentData.getSegment(JpegSegmentType.APPD, 1)); assertEquals(1, segmentData.getSegmentCount(JpegSegmentType.APPE)); assertArrayEquals( FileUtil.readBytes("Tests/Data/withExifAndIptc.jpg.appe"), - segmentData.getSegment(JpegSegmentType.APPE)); + segmentData.getSegment(JpegSegmentType.APPE).getReader().toArray()); assertNull(segmentData.getSegment(JpegSegmentType.APPE, 1)); assertEquals(0, segmentData.getSegmentCount(JpegSegmentType.APP3)); @@ -99,7 +99,8 @@ public void testReadSpecificSegments() throws Exception { JpegSegmentData segmentData = JpegSegmentReader.readSegments( new File("Tests/Data/withExifAndIptc.jpg"), - Arrays.asList(JpegSegmentType.APP0, JpegSegmentType.APP2)); + Arrays.asList(JpegSegmentType.APP0, JpegSegmentType.APP2), + true); assertEquals(1, segmentData.getSegmentCount(JpegSegmentType.APP0)); assertEquals(0, segmentData.getSegmentCount(JpegSegmentType.APP1)); @@ -123,10 +124,10 @@ public void testReadSpecificSegments() throws Exception assertArrayEquals( FileUtil.readBytes("Tests/Data/withExifAndIptc.jpg.app0"), - segmentData.getSegment(JpegSegmentType.APP0)); + segmentData.getSegment(JpegSegmentType.APP0).getReader().toArray()); assertArrayEquals( FileUtil.readBytes("Tests/Data/withExifAndIptc.jpg.app2"), - segmentData.getSegment(JpegSegmentType.APP2)); + segmentData.getSegment(JpegSegmentType.APP2).getReader().toArray()); } @Test diff --git a/Tests/com/drew/imaging/png/PngChunkReaderTest.java b/Tests/com/drew/imaging/png/PngChunkReaderTest.java index d9ba01b1c..bdb3a1de4 100644 --- a/Tests/com/drew/imaging/png/PngChunkReaderTest.java +++ b/Tests/com/drew/imaging/png/PngChunkReaderTest.java @@ -21,9 +21,10 @@ package com.drew.imaging.png; import com.drew.lang.Iterables; -import com.drew.lang.StreamReader; +import com.drew.lang.RandomAccessStream; import org.junit.Test; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.List; @@ -37,10 +38,12 @@ public class PngChunkReaderTest { public static List processFile(String filePath) throws PngProcessingException, IOException { + File file = new File(filePath); + FileInputStream inputStream = null; try { - inputStream = new FileInputStream(filePath); - return Iterables.toList(new PngChunkReader().extract(new StreamReader(inputStream), null)); + inputStream = new FileInputStream(file); + return Iterables.toList(new PngChunkReader().extract(new RandomAccessStream(inputStream, file.length()).createReader(), null)); } finally { if (inputStream != null) { inputStream.close(); @@ -56,22 +59,22 @@ public void testExtractMspaint() throws Exception assertEquals(6, chunks.size()); assertEquals(PngChunkType.IHDR, chunks.get(0).getType()); - assertEquals(13, chunks.get(0).getBytes().length); + assertEquals(13, chunks.get(0).getReader().getLength()); //.getBytes().length); assertEquals(PngChunkType.sRGB, chunks.get(1).getType()); - assertEquals(1, chunks.get(1).getBytes().length); + assertEquals(1, chunks.get(1).getReader().getLength()); assertEquals(PngChunkType.gAMA, chunks.get(2).getType()); - assertEquals(4, chunks.get(2).getBytes().length); + assertEquals(4, chunks.get(2).getReader().getLength()); assertEquals(PngChunkType.pHYs, chunks.get(3).getType()); - assertEquals(9, chunks.get(3).getBytes().length); + assertEquals(9, chunks.get(3).getReader().getLength()); assertEquals(PngChunkType.IDAT, chunks.get(4).getType()); - assertEquals(17, chunks.get(4).getBytes().length); + assertEquals(17, chunks.get(4).getReader().getLength()); assertEquals(PngChunkType.IEND, chunks.get(5).getType()); - assertEquals(0, chunks.get(5).getBytes().length); + assertEquals(0, chunks.get(5).getReader().getLength()); } @Test @@ -82,18 +85,18 @@ public void testExtractPhotoshop() throws Exception assertEquals(5, chunks.size()); assertEquals(PngChunkType.IHDR, chunks.get(0).getType()); - assertEquals(13, chunks.get(0).getBytes().length); + assertEquals(13, chunks.get(0).getReader().getLength()); assertEquals(PngChunkType.tEXt, chunks.get(1).getType()); - assertEquals(25, chunks.get(1).getBytes().length); + assertEquals(25, chunks.get(1).getReader().getLength()); assertEquals(PngChunkType.iTXt, chunks.get(2).getType()); - assertEquals(802, chunks.get(2).getBytes().length); + assertEquals(802, chunks.get(2).getReader().getLength()); assertEquals(PngChunkType.IDAT, chunks.get(3).getType()); - assertEquals(130, chunks.get(3).getBytes().length); + assertEquals(130, chunks.get(3).getReader().getLength()); assertEquals(PngChunkType.IEND, chunks.get(4).getType()); - assertEquals(0, chunks.get(4).getBytes().length); + assertEquals(0, chunks.get(4).getReader().getLength()); } } diff --git a/Tests/com/drew/imaging/png/PngMetadataReaderTest.java b/Tests/com/drew/imaging/png/PngMetadataReaderTest.java index 6bf880932..2efe92ac9 100644 --- a/Tests/com/drew/imaging/png/PngMetadataReaderTest.java +++ b/Tests/com/drew/imaging/png/PngMetadataReaderTest.java @@ -22,10 +22,12 @@ import com.drew.lang.KeyValuePair; import com.drew.lang.annotations.NotNull; +import com.drew.lang.RandomAccessStream; import com.drew.metadata.Metadata; import com.drew.metadata.png.PngDirectory; import org.junit.Test; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.text.SimpleDateFormat; @@ -44,10 +46,11 @@ public class PngMetadataReaderTest @NotNull private static Metadata processFile(@NotNull String filePath) throws PngProcessingException, IOException { + File file = new File(filePath); FileInputStream inputStream = null; try { - inputStream = new FileInputStream(filePath); - return PngMetadataReader.readMetadata(inputStream); + inputStream = new FileInputStream(file); + return PngMetadataReader.readMetadata(new RandomAccessStream(inputStream, file.length()).createReader()); } finally { if (inputStream != null) { inputStream.close(); diff --git a/Tests/com/drew/lang/ByteArrayReaderTest.java b/Tests/com/drew/lang/ByteArrayReaderTest.java index 01403c97e..d41a8ea73 100644 --- a/Tests/com/drew/lang/ByteArrayReaderTest.java +++ b/Tests/com/drew/lang/ByteArrayReaderTest.java @@ -29,15 +29,15 @@ public class ByteArrayReaderTest extends RandomAccessTestBase { @Override - protected RandomAccessReader createReader(byte[] bytes) + protected ReaderInfo createReader(byte[] bytes) { - return new ByteArrayReader(bytes); + return ReaderInfo.createFromArray(bytes); } @SuppressWarnings({ "ConstantConditions" }) @Test(expected = NullPointerException.class) public void testConstructWithNullBufferThrows() { - new ByteArrayReader(null); + ReaderInfo.createFromArray(null); } } diff --git a/Tests/com/drew/lang/RandomAccessFileReaderTest.java b/Tests/com/drew/lang/RandomAccessFileReaderTest.java index 173cdeea1..995839167 100644 --- a/Tests/com/drew/lang/RandomAccessFileReaderTest.java +++ b/Tests/com/drew/lang/RandomAccessFileReaderTest.java @@ -41,7 +41,7 @@ public class RandomAccessFileReaderTest extends RandomAccessTestBase private RandomAccessFile _randomAccessFile; @Override - protected RandomAccessReader createReader(byte[] bytes) + protected ReaderInfo createReader(byte[] bytes) { try { // Unit tests can create multiple readers in the same test, as long as they're used one after the other @@ -50,7 +50,7 @@ protected RandomAccessReader createReader(byte[] bytes) _tempFile = File.createTempFile("metadata-extractor-test-", ".tmp"); FileUtil.saveBytes(_tempFile, bytes); _randomAccessFile = new RandomAccessFile(_tempFile, "r"); - return new RandomAccessFileReader(_randomAccessFile); + return new RandomAccessStream(_randomAccessFile).createReader(); } catch (IOException e) { fail("Unable to create temp file"); return null; @@ -80,6 +80,6 @@ public void deleteTempFile() throws IOException @Test(expected = NullPointerException.class) public void testConstructWithNullBufferThrows() throws IOException { - new RandomAccessFileReader(null); + ReaderInfo.createFromArray(null); } } diff --git a/Tests/com/drew/lang/RandomAccessStreamReaderTest.java b/Tests/com/drew/lang/RandomAccessStreamReaderTest.java index 8295965c7..25b9f3bb8 100644 --- a/Tests/com/drew/lang/RandomAccessStreamReaderTest.java +++ b/Tests/com/drew/lang/RandomAccessStreamReaderTest.java @@ -23,8 +23,6 @@ import org.junit.Test; -import java.io.ByteArrayInputStream; - /** * @author Drew Noakes https://drewnoakes.com */ @@ -34,12 +32,12 @@ public class RandomAccessStreamReaderTest extends RandomAccessTestBase @Test(expected = NullPointerException.class) public void testConstructWithNullBufferThrows() { - new RandomAccessStreamReader(null); + ReaderInfo.createFromArray(null); } @Override - protected RandomAccessReader createReader(byte[] bytes) + protected ReaderInfo createReader(byte[] bytes) { - return new RandomAccessStreamReader(new ByteArrayInputStream(bytes)); + return ReaderInfo.createFromArray(bytes); } } diff --git a/Tests/com/drew/lang/RandomAccessTestBase.java b/Tests/com/drew/lang/RandomAccessTestBase.java index 809c2e2a4..2bc4674d9 100644 --- a/Tests/com/drew/lang/RandomAccessTestBase.java +++ b/Tests/com/drew/lang/RandomAccessTestBase.java @@ -36,7 +36,7 @@ */ public abstract class RandomAccessTestBase { - protected abstract RandomAccessReader createReader(byte[] bytes); + protected abstract ReaderInfo createReader(byte[] bytes); @Test public void testDefaultEndianness() @@ -48,7 +48,7 @@ public void testDefaultEndianness() public void testGetInt8() throws Exception { byte[] buffer = new byte[]{0x00, 0x01, (byte)0x7F, (byte)0xFF}; - RandomAccessReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals((byte)0, reader.getInt8(0)); assertEquals((byte)1, reader.getInt8(1)); @@ -60,7 +60,7 @@ public void testGetInt8() throws Exception public void testGetUInt8() throws Exception { byte[] buffer = new byte[]{0x00, 0x01, (byte)0x7F, (byte)0xFF}; - RandomAccessReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals(0, reader.getUInt8(0)); assertEquals(1, reader.getUInt8(1)); @@ -72,7 +72,7 @@ public void testGetUInt8() throws Exception public void testGetUInt8_OutOfBounds() { try { - RandomAccessReader reader = createReader(new byte[2]); + ReaderInfo reader = createReader(new byte[2]); reader.getUInt8(2); fail("Exception expected"); } catch (IOException ex) { @@ -86,7 +86,7 @@ public void testGetInt16() throws Exception assertEquals(-1, createReader(new byte[]{(byte)0xff, (byte)0xff}).getInt16(0)); byte[] buffer = new byte[]{0x00, 0x01, (byte)0x7F, (byte)0xFF}; - RandomAccessReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals((short)0x0001, reader.getInt16(0)); assertEquals((short)0x017F, reader.getInt16(1)); @@ -103,7 +103,7 @@ public void testGetInt16() throws Exception public void testGetUInt16() throws Exception { byte[] buffer = new byte[]{0x00, 0x01, (byte)0x7F, (byte)0xFF}; - RandomAccessReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals(0x0001, reader.getUInt16(0)); assertEquals(0x017F, reader.getUInt16(1)); @@ -120,7 +120,7 @@ public void testGetUInt16() throws Exception public void testGetUInt16_OutOfBounds() { try { - RandomAccessReader reader = createReader(new byte[2]); + ReaderInfo reader = createReader(new byte[2]); reader.getUInt16(1); fail("Exception expected"); } catch (IOException ex) { @@ -134,7 +134,7 @@ public void testGetInt32() throws Exception assertEquals(-1, createReader(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff}).getInt32(0)); byte[] buffer = new byte[]{0x00, 0x01, (byte)0x7F, (byte)0xFF, 0x02, 0x03, 0x04}; - RandomAccessReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals(0x00017FFF, reader.getInt32(0)); assertEquals(0x017FFF02, reader.getInt32(1)); @@ -155,7 +155,7 @@ public void testGetUInt32() throws Exception assertEquals(4294967295L, createReader(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff}).getUInt32(0)); byte[] buffer = new byte[]{0x00, 0x01, (byte)0x7F, (byte)0xFF, 0x02, 0x03, 0x04}; - RandomAccessReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals(0x00017FFFL, reader.getUInt32(0)); assertEquals(0x017FFF02L, reader.getUInt32(1)); @@ -174,7 +174,7 @@ public void testGetUInt32() throws Exception public void testGetInt32_OutOfBounds() { try { - RandomAccessReader reader = createReader(new byte[3]); + ReaderInfo reader = createReader(new byte[3]); reader.getInt32(0); fail("Exception expected"); } catch (IOException ex) { @@ -186,7 +186,7 @@ public void testGetInt32_OutOfBounds() public void testGetInt64() throws IOException { byte[] buffer = new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, (byte)0xFF}; - RandomAccessReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals(0x0001020304050607L, reader.getInt64(0)); assertEquals(0x01020304050607FFL, reader.getInt64(1)); @@ -201,14 +201,14 @@ public void testGetInt64() throws IOException public void testGetInt64_OutOfBounds() throws Exception { try { - RandomAccessReader reader = createReader(new byte[7]); + ReaderInfo reader = createReader(new byte[7]); reader.getInt64(0); fail("Exception expected"); } catch (IOException ex) { assertEquals("Attempt to read from beyond end of underlying data source (requested index: 0, requested count: 8, max index: 6)", ex.getMessage()); } try { - RandomAccessReader reader = createReader(new byte[7]); + ReaderInfo reader = createReader(new byte[7]); reader.getInt64(-1); fail("Exception expected"); } catch (IOException ex) { @@ -223,7 +223,7 @@ public void testGetFloat32() throws Exception assertTrue(Float.isNaN(Float.intBitsToFloat(nanBits))); byte[] buffer = new byte[]{0x7f, (byte)0xc0, 0x00, 0x00}; - RandomAccessReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertTrue(Float.isNaN(reader.getFloat32(0))); } @@ -235,7 +235,7 @@ public void testGetFloat64() throws Exception assertTrue(Double.isNaN(Double.longBitsToDouble(nanBits))); byte[] buffer = new byte[]{(byte)0xff, (byte)0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; - RandomAccessReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertTrue(Double.isNaN(reader.getDouble64(0))); } @@ -244,7 +244,7 @@ public void testGetFloat64() throws Exception public void testGetNullTerminatedString() throws Exception { byte[] bytes = new byte[]{0x41, 0x42, 0x43, 0x44, 0x00, 0x45, 0x46, 0x47}; - RandomAccessReader reader = createReader(bytes); + ReaderInfo reader = createReader(bytes); assertEquals("", reader.getNullTerminatedString(0, 0, Charsets.UTF_8)); assertEquals("A", reader.getNullTerminatedString(0, 1, Charsets.UTF_8)); @@ -265,7 +265,7 @@ public void testGetNullTerminatedString() throws Exception public void testGetString() throws Exception { byte[] bytes = new byte[]{0x41, 0x42, 0x43, 0x44, 0x00, 0x45, 0x46, 0x47}; - RandomAccessReader reader = createReader(bytes); + ReaderInfo reader = createReader(bytes); assertEquals("", reader.getString(0, 0, Charsets.UTF_8)); assertEquals("A", reader.getString(0, 1, Charsets.UTF_8)); @@ -285,7 +285,7 @@ public void testGetString() throws Exception @Test public void testIndexPlusCountExceedsIntMaxValue() { - RandomAccessReader reader = createReader(new byte[10]); + ReaderInfo reader = createReader(new byte[10]); try { reader.getBytes(0x6FFFFFFF, 0x6FFFFFFF); @@ -297,7 +297,7 @@ public void testIndexPlusCountExceedsIntMaxValue() @Test public void testOverflowBoundsCalculation() { - RandomAccessReader reader = createReader(new byte[10]); + ReaderInfo reader = createReader(new byte[10]); try { reader.getBytes(5, 10); @@ -311,7 +311,7 @@ public void testGetBytesEOF() throws Exception { createReader(new byte[50]).getBytes(0, 50); - RandomAccessReader reader = createReader(new byte[50]); + ReaderInfo reader = createReader(new byte[50]); reader.getBytes(25, 25); try { @@ -325,7 +325,7 @@ public void testGetInt8EOF() throws Exception { createReader(new byte[1]).getInt8(0); - RandomAccessReader reader = createReader(new byte[2]); + ReaderInfo reader = createReader(new byte[2]); reader.getInt8(0); reader.getInt8(1); diff --git a/Tests/com/drew/lang/SequentialAccessTestBase.java b/Tests/com/drew/lang/SequentialAccessTestBase.java index 57e4b5e95..152188d3c 100644 --- a/Tests/com/drew/lang/SequentialAccessTestBase.java +++ b/Tests/com/drew/lang/SequentialAccessTestBase.java @@ -35,7 +35,7 @@ */ public abstract class SequentialAccessTestBase { - protected abstract SequentialReader createReader(byte[] bytes); + protected abstract ReaderInfo createReader(byte[] bytes); @Test public void testDefaultEndianness() @@ -47,7 +47,7 @@ public void testDefaultEndianness() public void testGetInt8() throws IOException { byte[] buffer = new byte[]{0x00, 0x01, (byte)0x7F, (byte)0xFF}; - SequentialReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals((byte)0, reader.getInt8()); assertEquals((byte)1, reader.getInt8()); @@ -59,7 +59,7 @@ public void testGetInt8() throws IOException public void testGetUInt8() throws IOException { byte[] buffer = new byte[]{0x00, 0x01, (byte)0x7F, (byte)0xFF}; - SequentialReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals(0, reader.getUInt8()); assertEquals(1, reader.getUInt8()); @@ -71,7 +71,7 @@ public void testGetUInt8() throws IOException public void testGetUInt8_OutOfBounds() { try { - SequentialReader reader = createReader(new byte[1]); + ReaderInfo reader = createReader(new byte[1]); reader.getUInt8(); reader.getUInt8(); fail("Exception expected"); @@ -86,7 +86,7 @@ public void testGetInt16() throws IOException assertEquals(-1, createReader(new byte[]{(byte)0xff, (byte)0xff}).getInt16()); byte[] buffer = new byte[]{0x00, 0x01, (byte)0x7F, (byte)0xFF}; - SequentialReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals((short)0x0001, reader.getInt16()); assertEquals((short)0x7FFF, reader.getInt16()); @@ -102,7 +102,7 @@ public void testGetInt16() throws IOException public void testGetUInt16() throws IOException { byte[] buffer = new byte[]{0x00, 0x01, (byte)0x7F, (byte)0xFF}; - SequentialReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals(0x0001, reader.getUInt16()); assertEquals(0x7FFF, reader.getUInt16()); @@ -118,7 +118,7 @@ public void testGetUInt16() throws IOException public void testGetUInt16_OutOfBounds() { try { - SequentialReader reader = createReader(new byte[1]); + ReaderInfo reader = createReader(new byte[1]); reader.getUInt16(); fail("Exception expected"); } catch (IOException ex) { @@ -132,7 +132,7 @@ public void testGetInt32() throws IOException assertEquals(-1, createReader(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff}).getInt32()); byte[] buffer = new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; - SequentialReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals(0x00010203, reader.getInt32()); assertEquals(0x04050607, reader.getInt32()); @@ -150,7 +150,7 @@ public void testGetUInt32() throws IOException assertEquals(4294967295L, createReader(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff}).getUInt32()); byte[] buffer = new byte[]{(byte)0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; - SequentialReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals(0xFF000102L, reader.getUInt32()); assertEquals(0x03040506L, reader.getUInt32()); @@ -166,7 +166,7 @@ public void testGetUInt32() throws IOException public void testGetInt32_OutOfBounds() { try { - SequentialReader reader = createReader(new byte[3]); + ReaderInfo reader = createReader(new byte[3]); reader.getInt32(); fail("Exception expected"); } catch (IOException ex) { @@ -178,7 +178,7 @@ public void testGetInt32_OutOfBounds() public void testGetInt64() throws IOException { byte[] buffer = new byte[]{(byte)0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; - SequentialReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertEquals(0xFF00010203040506L, reader.getInt64()); @@ -192,7 +192,7 @@ public void testGetInt64() throws IOException public void testGetInt64_OutOfBounds() { try { - SequentialReader reader = createReader(new byte[7]); + ReaderInfo reader = createReader(new byte[7]); reader.getInt64(); fail("Exception expected"); } catch (IOException ex) { @@ -207,7 +207,7 @@ public void testGetFloat32() throws IOException assertTrue(Float.isNaN(Float.intBitsToFloat(nanBits))); byte[] buffer = new byte[]{0x7f, (byte)0xc0, 0x00, 0x00}; - SequentialReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertTrue(Float.isNaN(reader.getFloat32())); } @@ -219,7 +219,7 @@ public void testGetFloat64() throws IOException assertTrue(Double.isNaN(Double.longBitsToDouble(nanBits))); byte[] buffer = new byte[]{(byte)0xff, (byte)0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}; - SequentialReader reader = createReader(buffer); + ReaderInfo reader = createReader(buffer); assertTrue(Double.isNaN(reader.getDouble64())); } @@ -248,7 +248,8 @@ public void testGetString() throws IOException assertEquals(bytes.length, expected.length()); for (int i = 0; i < bytes.length; i++) { - assertEquals("ABCDEFG".substring(0, i), createReader(bytes).getString(i)); + //assertEquals("ABCDEFG".substring(0, i), createReader(bytes).getString(i)); + assertEquals("ABCDEFG".substring(0, i), createReader(bytes).getString(i, Charsets.UTF_8)); } } @@ -258,7 +259,7 @@ public void testGetBytes() throws IOException byte[] bytes = {0, 1, 2, 3, 4, 5}; for (int i = 0; i < bytes.length; i++) { - SequentialReader reader = createReader(bytes); + ReaderInfo reader = createReader(bytes); byte[] readBytes = reader.getBytes(i); for (int j = 0; j < i; j++) { assertEquals(bytes[j], readBytes[j]); @@ -269,7 +270,7 @@ public void testGetBytes() throws IOException @Test public void testOverflowBoundsCalculation() { - SequentialReader reader = createReader(new byte[10]); + ReaderInfo reader = createReader(new byte[10]); try { reader.getBytes(15); @@ -283,7 +284,7 @@ public void testGetBytesEOF() throws Exception { createReader(new byte[50]).getBytes(50); - SequentialReader reader = createReader(new byte[50]); + ReaderInfo reader = createReader(new byte[50]); reader.getBytes(25); reader.getBytes(25); @@ -298,7 +299,7 @@ public void testGetInt8EOF() throws Exception { createReader(new byte[1]).getInt8(); - SequentialReader reader = createReader(new byte[2]); + ReaderInfo reader = createReader(new byte[2]); reader.getInt8(); reader.getInt8(); @@ -315,7 +316,7 @@ public void testSkipEOF() throws Exception { createReader(new byte[1]).skip(1); - SequentialReader reader = createReader(new byte[2]); + ReaderInfo reader = createReader(new byte[2]); reader.skip(1); reader.skip(1); @@ -324,7 +325,8 @@ public void testSkipEOF() throws Exception reader.skip(1); reader.skip(1); fail("Expecting exception"); - } catch (EOFException ignored) {} + } catch (BufferBoundsException ignored) {} + //} catch (EOFException ignored) {} } @Test @@ -332,7 +334,7 @@ public void testTrySkipEOF() throws Exception { assertTrue(createReader(new byte[1]).trySkip(1)); - SequentialReader reader = createReader(new byte[2]); + ReaderInfo reader = createReader(new byte[2]); assertTrue(reader.trySkip(1)); assertTrue(reader.trySkip(1)); assertFalse(reader.trySkip(1)); diff --git a/Tests/com/drew/lang/SequentialByteArrayReaderTest.java b/Tests/com/drew/lang/SequentialByteArrayReaderTest.java index 44ab50490..d05a0cccf 100644 --- a/Tests/com/drew/lang/SequentialByteArrayReaderTest.java +++ b/Tests/com/drew/lang/SequentialByteArrayReaderTest.java @@ -31,12 +31,12 @@ public class SequentialByteArrayReaderTest extends SequentialAccessTestBase @Test(expected = NullPointerException.class) public void testConstructWithNullStreamThrows() { - new SequentialByteArrayReader(null); + ReaderInfo.createFromArray(null); } @Override - protected SequentialReader createReader(byte[] bytes) + protected ReaderInfo createReader(byte[] bytes) { - return new SequentialByteArrayReader(bytes); + return ReaderInfo.createFromArray(bytes); } } diff --git a/Tests/com/drew/lang/StreamReaderTest.java b/Tests/com/drew/lang/StreamReaderTest.java index 537a88834..f1d6f28ee 100644 --- a/Tests/com/drew/lang/StreamReaderTest.java +++ b/Tests/com/drew/lang/StreamReaderTest.java @@ -22,8 +22,6 @@ import org.junit.Test; -import java.io.ByteArrayInputStream; - /** * @author Drew Noakes https://drewnoakes.com */ @@ -33,12 +31,12 @@ public class StreamReaderTest extends SequentialAccessTestBase @Test(expected = NullPointerException.class) public void testConstructWithNullStreamThrows() { - new StreamReader(null); + ReaderInfo.createFromArray(null); } @Override - protected SequentialReader createReader(byte[] bytes) + protected ReaderInfo createReader(byte[] bytes) { - return new StreamReader(new ByteArrayInputStream(bytes)); + return ReaderInfo.createFromArray(bytes); } } diff --git a/Tests/com/drew/metadata/adobe/AdobeJpegReaderTest.java b/Tests/com/drew/metadata/adobe/AdobeJpegReaderTest.java index d3b1dfe23..29d03d8cc 100644 --- a/Tests/com/drew/metadata/adobe/AdobeJpegReaderTest.java +++ b/Tests/com/drew/metadata/adobe/AdobeJpegReaderTest.java @@ -22,7 +22,6 @@ package com.drew.metadata.adobe; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.SequentialByteArrayReader; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.tools.FileUtil; @@ -31,6 +30,7 @@ import java.io.IOException; import static com.drew.lang.Iterables.toList; +import com.drew.lang.ReaderInfo; import static org.junit.Assert.*; /** @@ -42,7 +42,7 @@ public class AdobeJpegReaderTest public static AdobeJpegDirectory processBytes(@NotNull String filePath) throws IOException { Metadata metadata = new Metadata(); - new AdobeJpegReader().extract(new SequentialByteArrayReader(FileUtil.readBytes(filePath)), metadata); + new AdobeJpegReader().extract(ReaderInfo.createFromArray(FileUtil.readBytes(filePath)), metadata); AdobeJpegDirectory directory = metadata.getFirstDirectoryOfType(AdobeJpegDirectory.class); assertNotNull(directory); diff --git a/Tests/com/drew/metadata/bmp/BmpReaderTest.java b/Tests/com/drew/metadata/bmp/BmpReaderTest.java index 10fc40e4a..020a2218c 100644 --- a/Tests/com/drew/metadata/bmp/BmpReaderTest.java +++ b/Tests/com/drew/metadata/bmp/BmpReaderTest.java @@ -21,13 +21,12 @@ package com.drew.metadata.bmp; -import com.drew.lang.StreamReader; +import com.drew.lang.RandomAccessStream; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import org.junit.Test; -import java.io.FileInputStream; -import java.io.InputStream; +import java.io.RandomAccessFile; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -42,9 +41,9 @@ public class BmpReaderTest public static BmpHeaderDirectory processBytes(@NotNull String file) throws Exception { Metadata metadata = new Metadata(); - InputStream stream = new FileInputStream(file); - new BmpReader().extract(new StreamReader(stream), metadata); - stream.close(); + RandomAccessFile raf = new RandomAccessFile(file, "r"); + new BmpReader().extract(new RandomAccessStream(raf).createReader(), metadata); + raf.close(); BmpHeaderDirectory directory = metadata.getFirstDirectoryOfType(BmpHeaderDirectory.class); assertNotNull(directory); diff --git a/Tests/com/drew/metadata/eps/EpsReaderTest.java b/Tests/com/drew/metadata/eps/EpsReaderTest.java index 6c3d9aa53..641b52e9a 100644 --- a/Tests/com/drew/metadata/eps/EpsReaderTest.java +++ b/Tests/com/drew/metadata/eps/EpsReaderTest.java @@ -1,6 +1,7 @@ package com.drew.metadata.eps; import com.drew.lang.annotations.NotNull; +import com.drew.lang.RandomAccessStream; import com.drew.metadata.Metadata; import org.junit.Test; @@ -17,12 +18,13 @@ public class EpsReaderTest { @NotNull - public static EpsDirectory processBytes(@NotNull String file) throws Exception + public static EpsDirectory processBytes(@NotNull String filePath) throws Exception { Metadata metadata = new Metadata(); - InputStream stream = new FileInputStream(new File(file)); + File file = new File(filePath); + InputStream stream = new FileInputStream(file); try { - new EpsReader().extract(stream, metadata); + new EpsReader().extract(new RandomAccessStream(stream, file.length()).createReader(), metadata); } catch (Exception e) { stream.close(); throw e; diff --git a/Tests/com/drew/metadata/exif/ExifDirectoryTest.java b/Tests/com/drew/metadata/exif/ExifDirectoryTest.java index 2e80651a9..b1482210c 100644 --- a/Tests/com/drew/metadata/exif/ExifDirectoryTest.java +++ b/Tests/com/drew/metadata/exif/ExifDirectoryTest.java @@ -21,6 +21,7 @@ package com.drew.metadata.exif; import com.drew.imaging.jpeg.JpegProcessingException; +import com.drew.imaging.jpeg.JpegSegmentType; import com.drew.lang.GeoLocation; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; @@ -63,7 +64,7 @@ public void testGetDirectoryName() throws Exception @Test public void testDateTime() throws JpegProcessingException, IOException, MetadataException { - Metadata metadata = ExifReaderTest.processBytes("Tests/Data/nikonMakernoteType2a.jpg.app1"); + Metadata metadata = ExifReaderTest.processSegmentBytes("Tests/Data/nikonMakernoteType2a.jpg.app1", JpegSegmentType.APP1); ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); ExifSubIFDDirectory exifSubIFDDirectory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); @@ -100,7 +101,7 @@ public void testDateTime() throws JpegProcessingException, IOException, Metadata @Test public void testResolution() throws JpegProcessingException, IOException, MetadataException { - Metadata metadata = ExifReaderTest.processBytes("Tests/Data/withUncompressedRGBThumbnail.jpg.app1"); + Metadata metadata = ExifReaderTest.processSegmentBytes("Tests/Data/withUncompressedRGBThumbnail.jpg.app1", JpegSegmentType.APP1); ExifThumbnailDirectory thumbnailDirectory = metadata.getFirstDirectoryOfType(ExifThumbnailDirectory.class); assertNotNull(thumbnailDirectory); @@ -114,7 +115,7 @@ public void testResolution() throws JpegProcessingException, IOException, Metada @Test public void testGeoLocation() throws IOException, MetadataException { - Metadata metadata = ExifReaderTest.processBytes("Tests/Data/withExifAndIptc.jpg.app1.0"); + Metadata metadata = ExifReaderTest.processSegmentBytes("Tests/Data/withExifAndIptc.jpg.app1.0", JpegSegmentType.APP1); GpsDirectory gpsDirectory = metadata.getFirstDirectoryOfType(GpsDirectory.class); assertNotNull(gpsDirectory); @@ -126,7 +127,7 @@ public void testGeoLocation() throws IOException, MetadataException @Test public void testGpsDate() throws IOException, MetadataException { - Metadata metadata = ExifReaderTest.processBytes("Tests/Data/withPanasonicFaces.jpg.app1"); + Metadata metadata = ExifReaderTest.processSegmentBytes("Tests/Data/withPanasonicFaces.jpg.app1", JpegSegmentType.APP1); GpsDirectory gpsDirectory = metadata.getFirstDirectoryOfType(GpsDirectory.class); assertNotNull(gpsDirectory); diff --git a/Tests/com/drew/metadata/exif/ExifIFD0DescriptorTest.java b/Tests/com/drew/metadata/exif/ExifIFD0DescriptorTest.java index 8b1ca9a19..3b5cd0ac2 100644 --- a/Tests/com/drew/metadata/exif/ExifIFD0DescriptorTest.java +++ b/Tests/com/drew/metadata/exif/ExifIFD0DescriptorTest.java @@ -21,6 +21,7 @@ package com.drew.metadata.exif; +import com.drew.imaging.jpeg.JpegSegmentType; import com.drew.lang.Rational; import org.junit.Test; @@ -59,7 +60,7 @@ public void testYResolutionDescription() throws Exception @Test public void testWindowsXpFields() throws Exception { - ExifIFD0Directory directory = ExifReaderTest.processBytes("Tests/Data/windowsXpFields.jpg.app1", ExifIFD0Directory.class); + ExifIFD0Directory directory = ExifReaderTest.processSegmentBytes("Tests/Data/windowsXpFields.jpg.app1", JpegSegmentType.APP1, ExifIFD0Directory.class); assertEquals("Testing artist\0", directory.getString(TAG_WIN_AUTHOR, "UTF-16LE")); assertEquals("Testing comments\0", directory.getString(TAG_WIN_COMMENT, "UTF-16LE")); diff --git a/Tests/com/drew/metadata/exif/ExifReaderTest.java b/Tests/com/drew/metadata/exif/ExifReaderTest.java index 1bf4cebfd..804eba1bb 100644 --- a/Tests/com/drew/metadata/exif/ExifReaderTest.java +++ b/Tests/com/drew/metadata/exif/ExifReaderTest.java @@ -21,8 +21,9 @@ package com.drew.metadata.exif; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.ByteArrayReader; +import com.drew.imaging.jpeg.JpegSegment; import com.drew.lang.Rational; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; @@ -42,18 +43,22 @@ public class ExifReaderTest { @NotNull - public static Metadata processBytes(@NotNull String filePath) throws IOException + public static Metadata processSegmentBytes(@NotNull String filePath, JpegSegmentType type) throws IOException { Metadata metadata = new Metadata(); byte[] bytes = FileUtil.readBytes(filePath); - new ExifReader().extract(new ByteArrayReader(bytes), metadata, ExifReader.JPEG_SEGMENT_PREAMBLE.length(), null); + JpegSegment segment = new JpegSegment(type, ReaderInfo.createFromArray(bytes), ExifReader.JPEG_SEGMENT_ID); + ArrayList segments = new ArrayList(); + segments.add(segment); + + new ExifReader().readJpegSegments(segments, metadata); return metadata; } @NotNull - public static T processBytes(@NotNull String filePath, @NotNull Class directoryClass) throws IOException + public static T processSegmentBytes(@NotNull String filePath, JpegSegmentType type, @NotNull Class directoryClass) throws IOException { - T directory = processBytes(filePath).getFirstDirectoryOfType(directoryClass); + T directory = processSegmentBytes(filePath, type).getFirstDirectoryOfType(directoryClass); assertNotNull(directory); return directory; } @@ -63,7 +68,7 @@ public static T processBytes(@NotNull String filePath, @No public void testExtractWithNullDataThrows() throws Exception { try{ - new ExifReader().readJpegSegments(null, new Metadata(), JpegSegmentType.APP1); + new ExifReader().readJpegSegments(null, null); fail("Exception expected"); } catch (NullPointerException npe) { // passed @@ -73,7 +78,7 @@ public void testExtractWithNullDataThrows() throws Exception @Test public void testLoadFujifilmJpeg() throws Exception { - ExifSubIFDDirectory directory = ExifReaderTest.processBytes("Tests/Data/withExif.jpg.app1", ExifSubIFDDirectory.class); + ExifSubIFDDirectory directory = ExifReaderTest.processSegmentBytes("Tests/Data/withExif.jpg.app1", JpegSegmentType.APP1, ExifSubIFDDirectory.class); final String description = directory.getDescription(ExifSubIFDDirectory.TAG_ISO_EQUIVALENT); assertNotNull(description); @@ -87,9 +92,10 @@ public void testReadJpegSegmentWithNoExifData() throws Exception { byte[] badExifData = new byte[]{ 1,2,3,4,5,6,7,8,9,10 }; Metadata metadata = new Metadata(); - ArrayList segments = new ArrayList(); - segments.add(badExifData); - new ExifReader().readJpegSegments(segments, metadata, JpegSegmentType.APP1); + JpegSegment badExifSegment = new JpegSegment(JpegSegmentType.APP1, ReaderInfo.createFromArray(badExifData)); + ArrayList segments = new ArrayList(); + segments.add(badExifSegment); + new ExifReader().readJpegSegments(segments, metadata); //, JpegSegmentType.APP1); assertEquals(0, metadata.getDirectoryCount()); assertFalse(metadata.hasErrors()); } @@ -100,7 +106,7 @@ public void testCrashRegressionTest() throws Exception // This image was created via a resize in ACDSee. // It seems to have a reference to an IFD starting outside the data segment. // I've noticed that ACDSee reports a Comment for this image, yet ExifReader doesn't report one. - ExifSubIFDDirectory directory = ExifReaderTest.processBytes("Tests/Data/crash01.jpg.app1", ExifSubIFDDirectory.class); + ExifSubIFDDirectory directory = ExifReaderTest.processSegmentBytes("Tests/Data/crash01.jpg.app1", JpegSegmentType.APP1, ExifSubIFDDirectory.class); assertTrue(directory.getTagCount() > 0); } @@ -108,7 +114,7 @@ public void testCrashRegressionTest() throws Exception @Test public void testDateTime() throws Exception { - ExifIFD0Directory directory = ExifReaderTest.processBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", ExifIFD0Directory.class); + ExifIFD0Directory directory = ExifReaderTest.processSegmentBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", JpegSegmentType.APP1, ExifIFD0Directory.class); assertEquals("2002:11:27 18:00:35", directory.getString(ExifIFD0Directory.TAG_DATETIME)); } @@ -116,7 +122,7 @@ public void testDateTime() throws Exception @Test public void testThumbnailXResolution() throws Exception { - ExifThumbnailDirectory directory = ExifReaderTest.processBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", ExifThumbnailDirectory.class); + ExifThumbnailDirectory directory = ExifReaderTest.processSegmentBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", JpegSegmentType.APP1, ExifThumbnailDirectory.class); Rational rational = directory.getRational(ExifThumbnailDirectory.TAG_X_RESOLUTION); assertNotNull(rational); @@ -127,7 +133,7 @@ public void testThumbnailXResolution() throws Exception @Test public void testThumbnailYResolution() throws Exception { - ExifThumbnailDirectory directory = ExifReaderTest.processBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", ExifThumbnailDirectory.class); + ExifThumbnailDirectory directory = ExifReaderTest.processSegmentBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", JpegSegmentType.APP1, ExifThumbnailDirectory.class); Rational rational = directory.getRational(ExifThumbnailDirectory.TAG_Y_RESOLUTION); assertNotNull(rational); @@ -138,7 +144,7 @@ public void testThumbnailYResolution() throws Exception @Test public void testThumbnailOffset() throws Exception { - ExifThumbnailDirectory directory = ExifReaderTest.processBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", ExifThumbnailDirectory.class); + ExifThumbnailDirectory directory = ExifReaderTest.processSegmentBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", JpegSegmentType.APP1, ExifThumbnailDirectory.class); assertEquals(192, directory.getInt(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET)); } @@ -146,7 +152,7 @@ public void testThumbnailOffset() throws Exception @Test public void testThumbnailLength() throws Exception { - ExifThumbnailDirectory directory = ExifReaderTest.processBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", ExifThumbnailDirectory.class); + ExifThumbnailDirectory directory = ExifReaderTest.processSegmentBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", JpegSegmentType.APP1, ExifThumbnailDirectory.class); assertEquals(2970, directory.getInt(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH)); } @@ -154,7 +160,7 @@ public void testThumbnailLength() throws Exception @Test public void testCompression() throws Exception { - ExifThumbnailDirectory directory = ExifReaderTest.processBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", ExifThumbnailDirectory.class); + ExifThumbnailDirectory directory = ExifReaderTest.processSegmentBytes("Tests/Data/manuallyAddedThumbnail.jpg.app1", JpegSegmentType.APP1, ExifThumbnailDirectory.class); // 6 means JPEG compression assertEquals(6, directory.getInt(ExifThumbnailDirectory.TAG_COMPRESSION)); @@ -167,7 +173,7 @@ public void testStackOverflowOnRevisitationOfSameDirectory() throws Exception // repeatedly. Thanks to Alistair Dickie for providing the sample data used in this // unit test. - Metadata metadata = processBytes("Tests/Data/recursiveDirectories.jpg.app1"); + Metadata metadata = processSegmentBytes("Tests/Data/recursiveDirectories.jpg.app1", JpegSegmentType.APP1); // Mostly we're just happy at this point that we didn't get stuck in an infinite loop. @@ -180,7 +186,7 @@ public void testDifferenceImageAndThumbnailOrientations() throws Exception // This metadata contains different orientations for the thumbnail and the main image. // These values used to be merged into a single directory, causing errors. // This unit test demonstrates correct behaviour. - Metadata metadata = processBytes("Tests/Data/repeatedOrientationTagWithDifferentValues.jpg.app1"); + Metadata metadata = processSegmentBytes("Tests/Data/repeatedOrientationTagWithDifferentValues.jpg.app1", JpegSegmentType.APP1); ExifIFD0Directory ifd0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); ExifThumbnailDirectory thumbnailDirectory = metadata.getFirstDirectoryOfType(ExifThumbnailDirectory.class); diff --git a/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java b/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java index 94e99cdd4..a1d88e08a 100644 --- a/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java +++ b/Tests/com/drew/metadata/exif/NikonType1MakernoteTest.java @@ -20,6 +20,7 @@ */ package com.drew.metadata.exif; +import com.drew.imaging.jpeg.JpegSegmentType; import com.drew.lang.Rational; import com.drew.metadata.Metadata; import com.drew.metadata.exif.makernotes.NikonType1MakernoteDirectory; @@ -53,7 +54,7 @@ public class NikonType1MakernoteTest @Before public void setUp() throws Exception { - Metadata metadata = ExifReaderTest.processBytes("Tests/Data/nikonMakernoteType1.jpg.app1"); + Metadata metadata = ExifReaderTest.processSegmentBytes("Tests/Data/nikonMakernoteType1.jpg.app1", JpegSegmentType.APP1); _nikonDirectory = metadata.getFirstDirectoryOfType(NikonType1MakernoteDirectory.class); _exifSubIFDDirectory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); diff --git a/Tests/com/drew/metadata/exif/NikonType2MakernoteTest1.java b/Tests/com/drew/metadata/exif/NikonType2MakernoteTest1.java index a8685efde..f5ad74034 100644 --- a/Tests/com/drew/metadata/exif/NikonType2MakernoteTest1.java +++ b/Tests/com/drew/metadata/exif/NikonType2MakernoteTest1.java @@ -20,6 +20,7 @@ */ package com.drew.metadata.exif; +import com.drew.imaging.jpeg.JpegSegmentType; import com.drew.metadata.MetadataException; import com.drew.metadata.exif.makernotes.NikonType2MakernoteDescriptor; import com.drew.metadata.exif.makernotes.NikonType2MakernoteDirectory; @@ -43,7 +44,7 @@ public void setUp() throws Exception { Locale.setDefault(new Locale("en", "GB")); - _nikonDirectory = ExifReaderTest.processBytes("Tests/Data/nikonMakernoteType2a.jpg.app1", NikonType2MakernoteDirectory.class); + _nikonDirectory = ExifReaderTest.processSegmentBytes("Tests/Data/nikonMakernoteType2a.jpg.app1", JpegSegmentType.APP1, NikonType2MakernoteDirectory.class); assertNotNull(_nikonDirectory); diff --git a/Tests/com/drew/metadata/exif/NikonType2MakernoteTest2.java b/Tests/com/drew/metadata/exif/NikonType2MakernoteTest2.java index a77c3992a..b7c06b5c2 100644 --- a/Tests/com/drew/metadata/exif/NikonType2MakernoteTest2.java +++ b/Tests/com/drew/metadata/exif/NikonType2MakernoteTest2.java @@ -20,6 +20,7 @@ */ package com.drew.metadata.exif; +import com.drew.imaging.jpeg.JpegSegmentType; import com.drew.lang.Rational; import com.drew.metadata.Metadata; import com.drew.metadata.exif.makernotes.NikonType2MakernoteDirectory; @@ -45,7 +46,7 @@ public class NikonType2MakernoteTest2 @Before public void setUp() throws Exception { - _metadata = ExifReaderTest.processBytes("Tests/Data/nikonMakernoteType2b.jpg.app1"); + _metadata = ExifReaderTest.processSegmentBytes("Tests/Data/nikonMakernoteType2b.jpg.app1", JpegSegmentType.APP1); _nikonDirectory = _metadata.getFirstDirectoryOfType(NikonType2MakernoteDirectory.class); _exifIFD0Directory = _metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); diff --git a/Tests/com/drew/metadata/exif/PanasonicMakernoteDescriptorTest.java b/Tests/com/drew/metadata/exif/PanasonicMakernoteDescriptorTest.java index e71f941c4..2639b6642 100644 --- a/Tests/com/drew/metadata/exif/PanasonicMakernoteDescriptorTest.java +++ b/Tests/com/drew/metadata/exif/PanasonicMakernoteDescriptorTest.java @@ -21,6 +21,7 @@ package com.drew.metadata.exif; +import com.drew.imaging.jpeg.JpegSegmentType; import com.drew.metadata.Age; import com.drew.metadata.Face; import com.drew.metadata.exif.makernotes.PanasonicMakernoteDirectory; @@ -40,7 +41,7 @@ public class PanasonicMakernoteDescriptorTest @Before public void setUp() throws Exception { - _panasonicDirectory = ExifReaderTest.processBytes("Tests/Data/withPanasonicFaces.jpg.app1", PanasonicMakernoteDirectory.class); + _panasonicDirectory = ExifReaderTest.processSegmentBytes("Tests/Data/withPanasonicFaces.jpg.app1", JpegSegmentType.APP1, PanasonicMakernoteDirectory.class); } @Test diff --git a/Tests/com/drew/metadata/exif/SonyType1MakernoteTest.java b/Tests/com/drew/metadata/exif/SonyType1MakernoteTest.java index 38466a954..f470cae6d 100644 --- a/Tests/com/drew/metadata/exif/SonyType1MakernoteTest.java +++ b/Tests/com/drew/metadata/exif/SonyType1MakernoteTest.java @@ -21,6 +21,7 @@ package com.drew.metadata.exif; +import com.drew.imaging.jpeg.JpegSegmentType; import com.drew.metadata.exif.makernotes.SonyType1MakernoteDescriptor; import com.drew.metadata.exif.makernotes.SonyType1MakernoteDirectory; import org.junit.Test; @@ -34,7 +35,7 @@ public class SonyType1MakernoteTest { @Test public void testSonyType1Makernote() throws Exception { - SonyType1MakernoteDirectory directory = ExifReaderTest.processBytes("Tests/Data/sonyType1.jpg.app1", SonyType1MakernoteDirectory.class); + SonyType1MakernoteDirectory directory = ExifReaderTest.processSegmentBytes("Tests/Data/sonyType1.jpg.app1", JpegSegmentType.APP1, SonyType1MakernoteDirectory.class); assertNotNull(directory); assertFalse(directory.hasErrors()); diff --git a/Tests/com/drew/metadata/exif/SonyType6MakernoteTest.java b/Tests/com/drew/metadata/exif/SonyType6MakernoteTest.java index eca2ee19c..118e1fa38 100644 --- a/Tests/com/drew/metadata/exif/SonyType6MakernoteTest.java +++ b/Tests/com/drew/metadata/exif/SonyType6MakernoteTest.java @@ -21,6 +21,7 @@ package com.drew.metadata.exif; +import com.drew.imaging.jpeg.JpegSegmentType; import com.drew.metadata.exif.makernotes.SonyType6MakernoteDescriptor; import com.drew.metadata.exif.makernotes.SonyType6MakernoteDirectory; import org.junit.Test; @@ -34,7 +35,7 @@ public class SonyType6MakernoteTest { @Test public void testSonyType6Makernote() throws Exception { - SonyType6MakernoteDirectory directory = ExifReaderTest.processBytes("Tests/Data/sonyType6.jpg.app1.0", SonyType6MakernoteDirectory.class); + SonyType6MakernoteDirectory directory = ExifReaderTest.processSegmentBytes("Tests/Data/sonyType6.jpg.app1.0", JpegSegmentType.APP1, SonyType6MakernoteDirectory.class); assertNotNull(directory); assertFalse(directory.hasErrors()); diff --git a/Tests/com/drew/metadata/gif/GifReaderTest.java b/Tests/com/drew/metadata/gif/GifReaderTest.java index 08fbd481f..218c76c62 100644 --- a/Tests/com/drew/metadata/gif/GifReaderTest.java +++ b/Tests/com/drew/metadata/gif/GifReaderTest.java @@ -21,11 +21,12 @@ package com.drew.metadata.gif; -import com.drew.lang.StreamReader; import com.drew.lang.annotations.NotNull; +import com.drew.lang.RandomAccessStream; import com.drew.metadata.Metadata; import org.junit.Test; +import java.io.File; import java.io.FileInputStream; import java.io.InputStream; @@ -37,11 +38,12 @@ public class GifReaderTest { @NotNull - public static GifHeaderDirectory processBytes(@NotNull String file) throws Exception + public static GifHeaderDirectory processBytes(@NotNull String filePath) throws Exception { Metadata metadata = new Metadata(); + File file = new File(filePath); InputStream stream = new FileInputStream(file); - new GifReader().extract(new StreamReader(stream), metadata); + new GifReader().extract(new RandomAccessStream(stream, file.length()).createReader(), metadata); stream.close(); GifHeaderDirectory directory = metadata.getFirstDirectoryOfType(GifHeaderDirectory.class); diff --git a/Tests/com/drew/metadata/icc/IccReaderTest.java b/Tests/com/drew/metadata/icc/IccReaderTest.java index 04dd6bcc6..55d8cb692 100644 --- a/Tests/com/drew/metadata/icc/IccReaderTest.java +++ b/Tests/com/drew/metadata/icc/IccReaderTest.java @@ -21,14 +21,17 @@ package com.drew.metadata.icc; +import com.drew.imaging.jpeg.JpegSegment; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.ByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.testing.TestHelper; import com.drew.tools.FileUtil; +import java.util.ArrayList; import org.junit.Test; import java.util.Arrays; +import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -48,7 +51,7 @@ public void testExtract_InvalidData() throws Exception byte[] icc = TestHelper.skipBytes(app2Bytes, 14); Metadata metadata = new Metadata(); - new IccReader().extract(new ByteArrayReader(icc), metadata); + new IccReader().extract(ReaderInfo.createFromArray(icc), metadata); IccDirectory directory = metadata.getFirstDirectoryOfType(IccDirectory.class); @@ -59,10 +62,10 @@ public void testExtract_InvalidData() throws Exception @Test public void testReadJpegSegments_InvalidData() throws Exception { - byte[] app2Bytes = FileUtil.readBytes("Tests/Data/iccDataInvalid1.jpg.app2"); - Metadata metadata = new Metadata(); - new IccReader().readJpegSegments(Arrays.asList(app2Bytes), metadata, JpegSegmentType.APP2); + List jpegSegments = new ArrayList(); + jpegSegments.add(new JpegSegment(JpegSegmentType.APP2, ReaderInfo.createFromArray(FileUtil.readBytes("Tests/Data/iccDataInvalid1.jpg.app2")), IccReader.JPEG_SEGMENT_ID)); + new IccReader().readJpegSegments(jpegSegments, metadata); IccDirectory directory = metadata.getFirstDirectoryOfType(IccDirectory.class); @@ -73,10 +76,10 @@ public void testReadJpegSegments_InvalidData() throws Exception @Test public void testExtract_ProfileDateTime() throws Exception { - byte[] app2Bytes = FileUtil.readBytes("Tests/Data/withExifAndIptc.jpg.app2"); - Metadata metadata = new Metadata(); - new IccReader().readJpegSegments(Arrays.asList(app2Bytes), metadata, JpegSegmentType.APP2); + List jpegSegments = new ArrayList(); + jpegSegments.add(new JpegSegment(JpegSegmentType.APP2, ReaderInfo.createFromArray(FileUtil.readBytes("Tests/Data/withExifAndIptc.jpg.app2")), IccReader.JPEG_SEGMENT_ID)); + new IccReader().readJpegSegments(jpegSegments, metadata); IccDirectory directory = metadata.getFirstDirectoryOfType(IccDirectory.class); diff --git a/Tests/com/drew/metadata/iptc/IptcReaderTest.java b/Tests/com/drew/metadata/iptc/IptcReaderTest.java index 23e058130..d31402464 100644 --- a/Tests/com/drew/metadata/iptc/IptcReaderTest.java +++ b/Tests/com/drew/metadata/iptc/IptcReaderTest.java @@ -20,7 +20,7 @@ */ package com.drew.metadata.iptc; -import com.drew.lang.SequentialByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.Tag; @@ -44,7 +44,7 @@ public static IptcDirectory processBytes(@NotNull String filePath) throws IOExce { Metadata metadata = new Metadata(); byte[] bytes = FileUtil.readBytes(filePath); - new IptcReader().extract(new SequentialByteArrayReader(bytes), metadata, bytes.length); + new IptcReader().extract(ReaderInfo.createFromArray(bytes), metadata); IptcDirectory directory = metadata.getFirstDirectoryOfType(IptcDirectory.class); assertNotNull(directory); return directory; diff --git a/Tests/com/drew/metadata/jfif/JfifReaderTest.java b/Tests/com/drew/metadata/jfif/JfifReaderTest.java index 3fb4b4928..822ea55df 100644 --- a/Tests/com/drew/metadata/jfif/JfifReaderTest.java +++ b/Tests/com/drew/metadata/jfif/JfifReaderTest.java @@ -21,7 +21,7 @@ package com.drew.metadata.jfif; -import com.drew.lang.ByteArrayReader; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.metadata.Tag; import org.junit.Test; @@ -46,7 +46,7 @@ public class JfifReaderTest final Metadata metadata = new Metadata(); final JfifReader reader = new JfifReader(); - reader.extract(new ByteArrayReader(jfifData), metadata); + reader.extract(ReaderInfo.createFromArray(jfifData), metadata); assertEquals(1, metadata.getDirectoryCount()); JfifDirectory directory = metadata.getFirstDirectoryOfType(JfifDirectory.class); diff --git a/Tests/com/drew/metadata/jpeg/JpegDhtReaderTest.java b/Tests/com/drew/metadata/jpeg/JpegDhtReaderTest.java index 1bda3fe5a..7d76b82a6 100644 --- a/Tests/com/drew/metadata/jpeg/JpegDhtReaderTest.java +++ b/Tests/com/drew/metadata/jpeg/JpegDhtReaderTest.java @@ -23,7 +23,7 @@ import com.drew.imaging.jpeg.JpegSegmentData; import com.drew.imaging.jpeg.JpegSegmentReader; import com.drew.imaging.jpeg.JpegSegmentType; -import com.drew.lang.SequentialByteArrayReader; +import com.drew.imaging.jpeg.JpegSegment; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; import com.drew.metadata.jpeg.HuffmanTablesDirectory.HuffmanTable.HuffmanTableClass; @@ -47,9 +47,9 @@ public static HuffmanTablesDirectory processBytes(String filePath) throws Except new File(filePath), Collections.singletonList(JpegSegmentType.DHT)); - Iterable segments = segmentData.getSegments(JpegSegmentType.DHT); - for (byte[] segment : segments) { - new JpegDhtReader().extract(new SequentialByteArrayReader(segment), metadata); + Iterable segments = segmentData.getSegments(JpegSegmentType.DHT); + for (JpegSegment segment : segments) { + new JpegDhtReader().extract(segment.getReader(), metadata); } diff --git a/Tests/com/drew/metadata/jpeg/JpegReaderTest.java b/Tests/com/drew/metadata/jpeg/JpegReaderTest.java index 766de5f2f..6104cf2a7 100644 --- a/Tests/com/drew/metadata/jpeg/JpegReaderTest.java +++ b/Tests/com/drew/metadata/jpeg/JpegReaderTest.java @@ -22,7 +22,9 @@ import com.drew.imaging.jpeg.JpegProcessingException; import com.drew.imaging.jpeg.JpegSegmentType; +import com.drew.imaging.jpeg.JpegSegment; import com.drew.lang.annotations.NotNull; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.tools.FileUtil; import org.junit.Before; @@ -42,7 +44,8 @@ public class JpegReaderTest public static JpegDirectory processBytes(String filePath) throws IOException { Metadata metadata = new Metadata(); - new JpegReader().extract(FileUtil.readBytes(filePath), metadata, JpegSegmentType.SOF0); + JpegSegment sof0 = new JpegSegment(JpegSegmentType.SOF0, ReaderInfo.createFromArray(FileUtil.readBytes(filePath))); + new JpegReader().extract(sof0, metadata); JpegDirectory directory = metadata.getFirstDirectoryOfType(JpegDirectory.class); assertNotNull(directory); diff --git a/Tests/com/drew/metadata/photoshop/PsdReaderTest.java b/Tests/com/drew/metadata/photoshop/PsdReaderTest.java index 8d77df008..17bfe746b 100644 --- a/Tests/com/drew/metadata/photoshop/PsdReaderTest.java +++ b/Tests/com/drew/metadata/photoshop/PsdReaderTest.java @@ -21,8 +21,8 @@ package com.drew.metadata.photoshop; -import com.drew.lang.StreamReader; import com.drew.lang.annotations.NotNull; +import com.drew.lang.RandomAccessStream; import com.drew.metadata.Metadata; import org.junit.Test; @@ -39,12 +39,13 @@ public class PsdReaderTest { @NotNull - public static PsdHeaderDirectory processBytes(@NotNull String file) throws Exception + public static PsdHeaderDirectory processBytes(@NotNull String filePath) throws Exception { Metadata metadata = new Metadata(); - InputStream stream = new FileInputStream(new File(file)); + File file = new File(filePath); + InputStream stream = new FileInputStream(file); try { - new PsdReader().extract(new StreamReader(stream), metadata); + new PsdReader().extract(new RandomAccessStream(stream, file.length()).createReader(), metadata); } catch (Exception e) { stream.close(); throw e; diff --git a/Tests/com/drew/metadata/xmp/XmpReaderTest.java b/Tests/com/drew/metadata/xmp/XmpReaderTest.java index f83d31636..9fb70f61a 100644 --- a/Tests/com/drew/metadata/xmp/XmpReaderTest.java +++ b/Tests/com/drew/metadata/xmp/XmpReaderTest.java @@ -21,6 +21,8 @@ package com.drew.metadata.xmp; import com.drew.imaging.jpeg.JpegSegmentType; +import com.drew.imaging.jpeg.JpegSegment; +import com.drew.lang.ReaderInfo; import com.drew.metadata.Metadata; import com.drew.tools.FileUtil; import org.junit.Before; @@ -41,9 +43,10 @@ public class XmpReaderTest public void setUp() throws Exception { Metadata metadata = new Metadata(); - List jpegSegments = new ArrayList(); - jpegSegments.add(FileUtil.readBytes("Tests/Data/withXmpAndIptc.jpg.app1.1")); - new XmpReader().readJpegSegments(jpegSegments, metadata, JpegSegmentType.APP1); + + List jpegSegments = new ArrayList(); + jpegSegments.add(new JpegSegment(JpegSegmentType.APP1, ReaderInfo.createFromArray(FileUtil.readBytes("Tests/Data/withXmpAndIptc.jpg.app1.1")), "Exif")); + new XmpReader().readJpegSegments(jpegSegments, metadata); Collection xmpDirectories = metadata.getDirectoriesOfType(XmpDirectory.class);