diff --git a/Source/com/drew/imaging/ImageMetadataReader.java b/Source/com/drew/imaging/ImageMetadataReader.java index 8e3106fac..6e2e0b538 100644 --- a/Source/com/drew/imaging/ImageMetadataReader.java +++ b/Source/com/drew/imaging/ImageMetadataReader.java @@ -151,6 +151,8 @@ public static Metadata readMetadata(@NotNull final InputStream inputStream, fina case Orf: case Rw2: return TiffMetadataReader.readMetadata(new RandomAccessStreamReader(inputStream, RandomAccessStreamReader.DEFAULT_CHUNK_LENGTH, streamLength)); + case Crx: + return QuickTimeMetadataReader.readMetadata(inputStream, fileType); case Psd: return PsdMetadataReader.readMetadata(inputStream); case Png: @@ -172,7 +174,7 @@ public static Metadata readMetadata(@NotNull final InputStream inputStream, fina case Wav: return WavMetadataReader.readMetadata(inputStream); case QuickTime: - return QuickTimeMetadataReader.readMetadata(inputStream); + return QuickTimeMetadataReader.readMetadata(inputStream, fileType); case Mp4: return Mp4MetadataReader.readMetadata(inputStream); case Mp3: diff --git a/Source/com/drew/imaging/quicktime/QuickTimeMetadataReader.java b/Source/com/drew/imaging/quicktime/QuickTimeMetadataReader.java index 67cdad153..758004581 100644 --- a/Source/com/drew/imaging/quicktime/QuickTimeMetadataReader.java +++ b/Source/com/drew/imaging/quicktime/QuickTimeMetadataReader.java @@ -20,6 +20,8 @@ */ package com.drew.imaging.quicktime; +import com.drew.imaging.FileType; +import com.drew.imaging.FileTypeDetector; import com.drew.imaging.ImageProcessingException; import com.drew.lang.annotations.NotNull; import com.drew.metadata.Metadata; @@ -39,7 +41,8 @@ public static Metadata readMetadata(@NotNull final File file) throws ImageProces InputStream inputStream = new FileInputStream(file); Metadata metadata; try { - metadata = readMetadata(inputStream); + FileType fileType = FileTypeDetector.detectFileType(inputStream); + metadata = readMetadata(inputStream, fileType); } finally { inputStream.close(); } @@ -48,10 +51,10 @@ public static Metadata readMetadata(@NotNull final File file) throws ImageProces } @NotNull - public static Metadata readMetadata(@NotNull InputStream inputStream) + public static Metadata readMetadata(@NotNull InputStream inputStream, FileType fileType) { Metadata metadata = new Metadata(); - QuickTimeReader.extract(inputStream, new QuickTimeAtomHandler(metadata)); + QuickTimeReader.extract(inputStream, new QuickTimeAtomHandler(metadata), fileType); return metadata; } } diff --git a/Source/com/drew/imaging/quicktime/QuickTimeReader.java b/Source/com/drew/imaging/quicktime/QuickTimeReader.java index 065a4a286..22f685084 100644 --- a/Source/com/drew/imaging/quicktime/QuickTimeReader.java +++ b/Source/com/drew/imaging/quicktime/QuickTimeReader.java @@ -20,32 +20,45 @@ */ package com.drew.imaging.quicktime; +import com.drew.imaging.FileType; +import com.drew.imaging.tiff.TiffProcessingException; +import com.drew.imaging.tiff.TiffReader; import com.drew.lang.StreamReader; import com.drew.lang.annotations.NotNull; +import com.drew.metadata.exif.ExifIFD0Directory; +import com.drew.metadata.exif.ExifSubIFDDirectory; +import com.drew.metadata.exif.GpsDirectory; +import com.drew.metadata.exif.makernotes.CanonMakernoteDirectory; +import com.drew.metadata.mov.QuickTimeContainerTypes; import com.drew.metadata.mov.QuickTimeContext; import com.drew.metadata.mov.atoms.Atom; +import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; /** * @author Payton Garland */ public class QuickTimeReader { + // 8 bytes length for an atom header + private static final long ATOM_HEADER_LENGTH = 8; + private QuickTimeReader() {} - public static void extract(@NotNull InputStream inputStream, @NotNull QuickTimeHandler handler) + public static void extract(@NotNull InputStream inputStream, @NotNull QuickTimeHandler handler, FileType fileType) { StreamReader reader = new StreamReader(inputStream); reader.setMotorolaByteOrder(true); QuickTimeContext context = new QuickTimeContext(); - processAtoms(reader, -1, handler, context); + processAtoms(reader, -1, handler, context, fileType); } - private static void processAtoms(StreamReader reader, long atomEnd, QuickTimeHandler handler, QuickTimeContext context) + private static void processAtoms(StreamReader reader, long atomEnd, QuickTimeHandler handler, QuickTimeContext context, FileType fileType) { try { while (atomEnd == -1 || reader.getPosition() < atomEnd) { @@ -65,12 +78,22 @@ private static void processAtoms(StreamReader reader, long atomEnd, QuickTimeHan break; } - if (handler.shouldAcceptContainer(atom)) { - processAtoms(reader, atom.size + reader.getPosition() - 8, handler.processContainer(atom, context), context); + if (FileType.Crx.equals(fileType) && atom.type.equals(QuickTimeContainerTypes.ATOM_UUID)) { + byte[] cr3 = new byte[]{(byte) 0x85, (byte) 0xc0, (byte) 0xb6, (byte) 0x87, (byte) 0x82, 0x0f, 0x11, (byte) 0xe0, (byte) 0x81, 0x11, (byte) 0xf4, (byte) 0xce, 0x46, 0x2b, 0x6a, 0x48}; + try { + byte[] uuid = reader.getBytes(cr3.length); + if (Arrays.equals(cr3, uuid)) { + processUuidAtoms(reader, atom.size + reader.getPosition() - ATOM_HEADER_LENGTH, handler.processContainer(atom, context)); + } + } catch (IOException ex) { + handler.addError("IOException at crx uuid header: " + ex.getMessage()); + } + } else if (handler.shouldAcceptContainer(atom)) { + processAtoms(reader, atom.size + reader.getPosition() - ATOM_HEADER_LENGTH, handler.processContainer(atom, context), context, fileType); } else if (handler.shouldAcceptAtom(atom)) { - handler = handler.processAtom(atom, reader.getBytes((int)atom.size - 8), context); + handler = handler.processAtom(atom, reader.getBytes((int) (atom.size - ATOM_HEADER_LENGTH)), context); } else if (atom.size > 8) { - reader.skip(atom.size - 8); + reader.skip(atom.size - ATOM_HEADER_LENGTH); } else if (atom.size == -1) { break; } @@ -79,4 +102,55 @@ private static void processAtoms(StreamReader reader, long atomEnd, QuickTimeHan handler.addError(e.getMessage()); } } + + private static void processUuidAtoms(StreamReader reader, long atomEnd, QuickTimeHandler handler) { + try { + while (atomEnd == -1 || reader.getPosition() < atomEnd) { + + Atom atom = new Atom(reader); + // Unknown atoms will be skipped + + if (atom.size > Integer.MAX_VALUE) { + handler.addError("Atom size too large."); + continue; + } + + if (atom.size < 8) { + handler.addError("Atom size too small."); + continue; + } + switch (atom.type) { + case "CMT1": + { + QuickTimeTiffHandler tiffHandler = new QuickTimeTiffHandler(ExifIFD0Directory.class, handler.metadata, handler.directory, 0); + new TiffReader().processTiff(reader.asByteArrayReader((int) (atom.size - ATOM_HEADER_LENGTH)), tiffHandler, 0); + break; + } + case "CMT2": + { + QuickTimeTiffHandler tiffHandler = new QuickTimeTiffHandler(ExifSubIFDDirectory.class, handler.metadata, handler.directory, 0); + new TiffReader().processTiff(reader.asByteArrayReader((int) (atom.size - ATOM_HEADER_LENGTH)), tiffHandler, 0); + break; + } + case "CMT3": + { + QuickTimeTiffHandler tiffHandler = new QuickTimeTiffHandler(CanonMakernoteDirectory.class, handler.metadata, handler.directory, 0); + new TiffReader().processTiff(reader.asByteArrayReader((int) (atom.size - ATOM_HEADER_LENGTH)), tiffHandler, 0); + break; + } + case "CMT4": + { + QuickTimeTiffHandler tiffHandler = new QuickTimeTiffHandler(GpsDirectory.class, handler.metadata, handler.directory, 0); + new TiffReader().processTiff(reader.asByteArrayReader((int) atom.size - 8), tiffHandler, 0); + break; + } + default: + reader.skip(atom.size - ATOM_HEADER_LENGTH); + + } + } + } catch (IOException | TiffProcessingException e) { + handler.addError(e.getMessage()); + } + } } diff --git a/Source/com/drew/imaging/quicktime/QuickTimeTiffHandler.java b/Source/com/drew/imaging/quicktime/QuickTimeTiffHandler.java new file mode 100644 index 000000000..35f124ce0 --- /dev/null +++ b/Source/com/drew/imaging/quicktime/QuickTimeTiffHandler.java @@ -0,0 +1,32 @@ +package com.drew.imaging.quicktime; + +import com.drew.imaging.tiff.TiffProcessingException; +import com.drew.metadata.Directory; +import com.drew.metadata.Metadata; +import com.drew.metadata.exif.ExifIFD0Directory; +import com.drew.metadata.exif.ExifTiffHandler; + +import java.lang.reflect.InvocationTargetException; + +class QuickTimeTiffHandler extends ExifTiffHandler { + + private final Class _clazz; + + public QuickTimeTiffHandler(Class clazz, Metadata metadata, Directory parentDirectory, int exifStartOffset) { + super(metadata, parentDirectory, exifStartOffset); + this._clazz = clazz; + } + @Override + public void setTiffMarker(int marker) throws TiffProcessingException { + int standardTiffMarker = 0x002A; + if (marker != standardTiffMarker) + { + throw new TiffProcessingException("Unexpected TIFF marker: 0x{marker:X}"); + } + try { + pushDirectory(this._clazz.getConstructor().newInstance()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/Source/com/drew/lang/StreamReader.java b/Source/com/drew/lang/StreamReader.java index 78761d866..f11f0cacc 100644 --- a/Source/com/drew/lang/StreamReader.java +++ b/Source/com/drew/lang/StreamReader.java @@ -141,4 +141,12 @@ private long skipInternal(long n) throws IOException _pos += skippedTotal; return skippedTotal; } + + public ByteArrayReader asByteArrayReader(int size) { + try { + return new ByteArrayReader(this.getBytes(size)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/Source/com/drew/metadata/mov/QuickTimeAtomHandler.java b/Source/com/drew/metadata/mov/QuickTimeAtomHandler.java index 368860c85..5f13fdbb4 100644 --- a/Source/com/drew/metadata/mov/QuickTimeAtomHandler.java +++ b/Source/com/drew/metadata/mov/QuickTimeAtomHandler.java @@ -98,6 +98,9 @@ public QuickTimeHandler processAtom(@NotNull Atom atom, @Nullable byte[] payl } else if (atom.type.equals(QuickTimeAtomTypes.ATOM_TRACK_HEADER)) { TrackHeaderAtom trackHeaderAtom = new TrackHeaderAtom(reader, atom); trackHeaderAtom.addMetadata(directory); + } else if (atom.type.equals(QuickTimeAtomTypes.ATOM_UUID)) { + TrackHeaderAtom trackHeaderAtom = new TrackHeaderAtom(reader, atom); + trackHeaderAtom.addMetadata(directory); } } else { if (atom.type.equals(QuickTimeContainerTypes.ATOM_COMPRESSED_MOVIE)) { diff --git a/Source/com/drew/metadata/mov/QuickTimeAtomTypes.java b/Source/com/drew/metadata/mov/QuickTimeAtomTypes.java index d39cb1a9f..6b67db167 100644 --- a/Source/com/drew/metadata/mov/QuickTimeAtomTypes.java +++ b/Source/com/drew/metadata/mov/QuickTimeAtomTypes.java @@ -42,6 +42,7 @@ public class QuickTimeAtomTypes public static final String ATOM_CANON_THUMBNAIL = "CNTH"; public static final String ATOM_ADOBE_XMP = "XMP_"; public static final String ATOM_TRACK_HEADER = "tkhd"; + public static final String ATOM_UUID = "uuid"; private static final ArrayList _atomList = new ArrayList(); diff --git a/Source/com/drew/metadata/mov/QuickTimeContainerTypes.java b/Source/com/drew/metadata/mov/QuickTimeContainerTypes.java index 760fc9ee0..73ebbdb57 100644 --- a/Source/com/drew/metadata/mov/QuickTimeContainerTypes.java +++ b/Source/com/drew/metadata/mov/QuickTimeContainerTypes.java @@ -29,6 +29,7 @@ public class QuickTimeContainerTypes { public static final String ATOM_MOVIE = "moov"; public static final String ATOM_USER_DATA = "udta"; + public static final String ATOM_UUID = "uuid"; public static final String ATOM_TRACK = "trak"; public static final String ATOM_MEDIA = "mdia"; public static final String ATOM_MEDIA_INFORMATION = "minf";