diff --git a/Source/com/drew/imaging/ImageMetadataReader.java b/Source/com/drew/imaging/ImageMetadataReader.java index 5eba8059f..3bc3b2607 100644 --- a/Source/com/drew/imaging/ImageMetadataReader.java +++ b/Source/com/drew/imaging/ImageMetadataReader.java @@ -22,6 +22,7 @@ import com.drew.imaging.avi.AviMetadataReader; import com.drew.imaging.bmp.BmpMetadataReader; +import com.drew.imaging.cr3.Cr3MetadataReader; import com.drew.imaging.eps.EpsMetadataReader; import com.drew.imaging.gif.GifMetadataReader; import com.drew.imaging.heif.HeifMetadataReader; @@ -194,6 +195,8 @@ public static Metadata readMetadata(@NotNull final InputStream inputStream, fina return AviMetadataReader.readMetadata(inputStream); case Wav: return WavMetadataReader.readMetadata(inputStream); + case Crx: + return Cr3MetadataReader.readMetadata(inputStream); case QuickTime: return QuickTimeMetadataReader.readMetadata(inputStream); case Mp4: diff --git a/Source/com/drew/imaging/cr3/Cr3CanonUuidHandler.java b/Source/com/drew/imaging/cr3/Cr3CanonUuidHandler.java new file mode 100644 index 000000000..55bf95c3c --- /dev/null +++ b/Source/com/drew/imaging/cr3/Cr3CanonUuidHandler.java @@ -0,0 +1,202 @@ +/* + * Copyright 2002-2019 Drew Noakes and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More information about this project is available at: + * + * https://drewnoakes.com/code/exif/ + * https://github.com/drewnoakes/metadata-extractor + */ +package com.drew.imaging.cr3; + +import com.drew.imaging.isobmff.IsoBox; +import com.drew.imaging.isobmff.IsoBoxVisitor; +import com.drew.imaging.tiff.TiffProcessingException; +import com.drew.imaging.tiff.TiffReader; +import com.drew.lang.ByteArrayReader; +import com.drew.lang.SequentialByteArrayReader; +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.cr3.Cr3BoxTypes; +import com.drew.metadata.cr3.Cr3Directory; +import com.drew.metadata.exif.ExifIFD0Directory; +import com.drew.metadata.exif.ExifSubIFDDirectory; +import com.drew.metadata.exif.ExifTiffHandler; +import com.drew.metadata.exif.GpsDirectory; +import com.drew.metadata.exif.makernotes.CanonMakernoteDirectory; + +import java.io.IOException; + +/** + * Visits the sub-boxes of the Canon main metadata UUID + * ({@code 85c0b687-820f-11e0-8111-f4ce462b6a48}) inside the {@code moov} container. + *
+ * Decodes: + *
+ * THMB payload layout (big-endian): + *
+ * uint32 version/flags + * uint16 width + * uint16 height + * uint32 jpeg_size + * uint32 unknown + * byte[] jpeg_data + *+ */ + private void processThumbnail(@NotNull byte[] payload) + { + try { + SequentialByteArrayReader reader = new SequentialByteArrayReader(payload); + reader.skip(4); // version/flags uint32 + int width = reader.getUInt16(); + int height = reader.getUInt16(); + directory.setInt(Cr3Directory.TAG_THUMBNAIL_WIDTH, width); + directory.setInt(Cr3Directory.TAG_THUMBNAIL_HEIGHT, height); + } catch (IOException e) { + directory.addError("Error reading THMB dimensions: " + e.getMessage()); + } + } + + /** + * An {@link ExifTiffHandler} variant that starts IFD processing at a caller-specified + * directory class rather than the default {@link ExifIFD0Directory}. + * Used for CMT2 (ExifSubIFD), CMT3 (Canon makernote), and CMT4 (GPS). + */ + private static final class FixedRootExifTiffHandler extends ExifTiffHandler + { + private final Class extends Directory> _rootClass; + + FixedRootExifTiffHandler(@NotNull Metadata metadata, + @NotNull Class extends Directory> rootClass) + { + super(metadata, null, 0); + _rootClass = rootClass; + } + + @Override + public void setTiffMarker(int marker) throws TiffProcessingException + { + pushDirectory(_rootClass); + } + } +} diff --git a/Source/com/drew/imaging/cr3/Cr3Handler.java b/Source/com/drew/imaging/cr3/Cr3Handler.java new file mode 100644 index 000000000..b51243ada --- /dev/null +++ b/Source/com/drew/imaging/cr3/Cr3Handler.java @@ -0,0 +1,146 @@ +/* + * Copyright 2002-2019 Drew Noakes and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * More information about this project is available at: + * + * https://drewnoakes.com/code/exif/ + * https://github.com/drewnoakes/metadata-extractor + */ +package com.drew.imaging.cr3; + +import com.drew.imaging.isobmff.IsoBox; +import com.drew.imaging.isobmff.IsoBoxVisitor; +import com.drew.lang.SequentialByteArrayReader; +import com.drew.lang.annotations.NotNull; +import com.drew.metadata.Metadata; +import com.drew.metadata.cr3.Cr3BoxTypes; +import com.drew.metadata.cr3.Cr3ContainerTypes; +import com.drew.metadata.cr3.Cr3Directory; +import com.drew.metadata.xmp.XmpReader; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * Root ISOBMFF visitor for Canon CR3 files. + *
+ * Handles the top-level box sequence ({@code ftyp}, {@code moov}, {@code uuid}).
+ * Delegates Canon-metadata UUID content to {@link Cr3CanonUuidHandler} and
+ * preview UUID content to {@link Cr3PreviewUuidHandler}.
+ */
+class Cr3Handler extends IsoBoxVisitor
+{
+ /**
+ * Canon main-metadata UUID: {@code 85c0b687-820f-11e0-8111-f4ce462b6a48}.
+ * Resides inside {@code moov}; contains CMT1–CMT4, THMB, CNCV, …
+ */
+ static final byte[] UUID_CANON_MAIN = {
+ (byte)0x85, (byte)0xc0, (byte)0xb6, (byte)0x87,
+ (byte)0x82, (byte)0x0f, (byte)0x11, (byte)0xe0,
+ (byte)0x81, (byte)0x11, (byte)0xf4, (byte)0xce,
+ (byte)0x46, (byte)0x2b, (byte)0x6a, (byte)0x48
+ };
+
+ /**
+ * Canon preview UUID: {@code eaf42b5e-1c98-4b88-b9fb-b7dc406e4d16}.
+ * Top-level; contains the PRVW sub-box.
+ */
+ static final byte[] UUID_CANON_PREVIEW = {
+ (byte)0xea, (byte)0xf4, (byte)0x2b, (byte)0x5e,
+ (byte)0x1c, (byte)0x98, (byte)0x4b, (byte)0x88,
+ (byte)0xb9, (byte)0xfb, (byte)0xb7, (byte)0xdc,
+ (byte)0x40, (byte)0x6e, (byte)0x4d, (byte)0x16
+ };
+
+ /**
+ * Canon XMP UUID: {@code be7acfcb-97a9-42e8-9c71-999491e3afac}.
+ * Top-level; payload is a raw XMP packet.
+ */
+ static final byte[] UUID_CANON_XMP = {
+ (byte)0xbe, (byte)0x7a, (byte)0xcf, (byte)0xcb,
+ (byte)0x97, (byte)0xa9, (byte)0x42, (byte)0xe8,
+ (byte)0x9c, (byte)0x71, (byte)0x99, (byte)0x94,
+ (byte)0x91, (byte)0xe3, (byte)0xaf, (byte)0xac
+ };
+
+ final Metadata metadata;
+ final Cr3Directory directory;
+
+ Cr3Handler(@NotNull Metadata metadata, @NotNull Cr3Directory directory)
+ {
+ this.metadata = metadata;
+ this.directory = directory;
+ }
+
+ @Override
+ protected boolean shouldRecurse(@NotNull IsoBox box)
+ {
+ return Cr3ContainerTypes.BOX_MOVIE.equals(box.type)
+ || box.usertypeMatches(UUID_CANON_MAIN);
+ }
+
+ @Override
+ @NotNull
+ protected IsoBoxVisitor processContainer(@NotNull IsoBox box)
+ {
+ if (box.usertypeMatches(UUID_CANON_MAIN))
+ return new Cr3CanonUuidHandler(metadata, directory);
+ return this;
+ }
+
+ @Override
+ protected boolean shouldVisit(@NotNull IsoBox box)
+ {
+ return Cr3BoxTypes.BOX_FILE_TYPE.equals(box.type)
+ || box.usertypeMatches(UUID_CANON_XMP)
+ || box.usertypeMatches(UUID_CANON_PREVIEW);
+ }
+
+ @Override
+ protected void visit(@NotNull IsoBox box, @NotNull byte[] payload) throws IOException
+ {
+ if (Cr3BoxTypes.BOX_FILE_TYPE.equals(box.type)) {
+ processFtyp(payload, box.size);
+ } else if (box.usertypeMatches(UUID_CANON_XMP)) {
+ new XmpReader().extract(payload, metadata);
+ } else if (box.usertypeMatches(UUID_CANON_PREVIEW)) {
+ new Cr3PreviewUuidHandler(directory).parsePayload(payload);
+ }
+ }
+
+ @Override
+ public void addError(@NotNull String message)
+ {
+ directory.addError(message);
+ }
+
+ private void processFtyp(byte[] payload, long boxSize) throws IOException
+ {
+ SequentialByteArrayReader reader = new SequentialByteArrayReader(payload);
+ String majorBrand = reader.getString(4);
+ long minorVersion = reader.getUInt32();
+
+ ArrayList
+ * CR3 uses the ISO Base Media File Format (ISOBMFF) as its container, with Canon-specific
+ * {@code uuid} boxes that embed TIFF-formatted EXIF, makernote, and GPS data (CMT1–CMT4),
+ * an XMP packet, and JPEG thumbnail/preview images.
+ *
+ * Camera metadata is populated into the standard directories
+ * ({@link com.drew.metadata.exif.ExifIFD0Directory},
+ * {@link com.drew.metadata.exif.ExifSubIFDDirectory},
+ * {@link com.drew.metadata.exif.makernotes.CanonMakernoteDirectory},
+ * {@link com.drew.metadata.exif.GpsDirectory}) by reusing the existing
+ * {@link com.drew.metadata.exif.ExifTiffHandler} pipeline.
+ *
+ * @see lclevy/canon_cr3 format specification
+ */
+public class Cr3MetadataReader
+{
+ @NotNull
+ public static Metadata readMetadata(@NotNull final File file) throws IOException
+ {
+ InputStream inputStream = new FileInputStream(file);
+ Metadata metadata;
+ try {
+ metadata = readMetadata(inputStream);
+ } finally {
+ inputStream.close();
+ }
+ new FileSystemMetadataReader().read(file, metadata);
+ return metadata;
+ }
+
+ @NotNull
+ public static Metadata readMetadata(@NotNull InputStream inputStream)
+ {
+ Metadata metadata = new Metadata();
+ extract(inputStream, metadata);
+ return metadata;
+ }
+
+ public static void extract(@NotNull InputStream inputStream, @NotNull Metadata metadata)
+ {
+ Cr3Directory directory = new Cr3Directory();
+ metadata.addDirectory(directory);
+ IsoBoxWalker.walk(inputStream, new Cr3Handler(metadata, directory));
+ }
+}
diff --git a/Source/com/drew/imaging/cr3/Cr3PreviewUuidHandler.java b/Source/com/drew/imaging/cr3/Cr3PreviewUuidHandler.java
new file mode 100644
index 000000000..a3b78272a
--- /dev/null
+++ b/Source/com/drew/imaging/cr3/Cr3PreviewUuidHandler.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2019 Drew Noakes and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ * https://drewnoakes.com/code/exif/
+ * https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.imaging.cr3;
+
+import com.drew.lang.SequentialByteArrayReader;
+import com.drew.lang.annotations.NotNull;
+import com.drew.metadata.cr3.Cr3Directory;
+
+import java.io.IOException;
+
+/**
+ * Parses the Canon preview UUID ({@code eaf42b5e-1c98-4b88-b9fb-b7dc406e4d16}).
+ *
+ * The UUID payload has an 8-byte preamble followed by a standard {@code PRVW} box.
+ * Decodes the {@code PRVW} header to extract preview image dimensions and stores
+ * them on {@link Cr3Directory}. The JPEG payload is discarded.
+ */
+class Cr3PreviewUuidHandler
+{
+ private static final int PREAMBLE_SIZE = 8;
+ private static final int BOX_HEADER_SIZE = 8; // size(4) + type(4)
+
+ private final Cr3Directory directory;
+
+ Cr3PreviewUuidHandler(@NotNull Cr3Directory directory)
+ {
+ this.directory = directory;
+ }
+
+ /**
+ * Parses the full preview UUID payload, skipping the preamble and PRVW box
+ * header to reach the PRVW dimension fields.
+ *
+ * PRVW payload layout (big-endian, after preamble + box header):
+ *
+ * ISO/IEC 14496-12 section 4.2 defines the box structure:
+ *
+ * For each box encountered the walker first asks {@link #shouldRecurse} — if {@code true} it
+ * calls {@link #processContainer} to obtain the visitor for the nested level, then recurses.
+ * Otherwise it asks {@link #shouldVisit} — if {@code true} it reads the payload bytes and calls
+ * {@link #visit}. If neither, the box is skipped.
+ */
+public abstract class IsoBoxVisitor
+{
+ /**
+ * Returns {@code true} if the walker should recurse into this box's payload as a nested box sequence.
+ * When {@code true}, {@link #processContainer} is called next.
+ */
+ protected abstract boolean shouldRecurse(@NotNull IsoBox box);
+
+ /**
+ * Called before recursing into a container box. Returns the visitor to use for the nested level.
+ * Returning {@code this} continues with the same visitor; returning a new instance delegates to
+ * a format-specific sub-handler.
+ */
+ @NotNull
+ protected abstract IsoBoxVisitor processContainer(@NotNull IsoBox box);
+
+ /**
+ * Returns {@code true} if the walker should read the payload and call {@link #visit}.
+ * Only consulted when {@link #shouldRecurse} returned {@code false}.
+ */
+ protected abstract boolean shouldVisit(@NotNull IsoBox box);
+
+ /**
+ * Called with the complete payload bytes of a visited box.
+ *
+ * @param box the box header (type, size, usertype)
+ * @param payload bytes of the box payload (everything after the full box header)
+ */
+ protected abstract void visit(@NotNull IsoBox box, @NotNull byte[] payload) throws IOException;
+
+ /** Records an error encountered during parsing. */
+ public abstract void addError(@NotNull String message);
+}
diff --git a/Source/com/drew/imaging/isobmff/IsoBoxWalker.java b/Source/com/drew/imaging/isobmff/IsoBoxWalker.java
new file mode 100644
index 000000000..d178134f3
--- /dev/null
+++ b/Source/com/drew/imaging/isobmff/IsoBoxWalker.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2002-2019 Drew Noakes and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ * https://drewnoakes.com/code/exif/
+ * https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.imaging.isobmff;
+
+import com.drew.lang.SequentialReader;
+import com.drew.lang.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Walks an ISO Base Media File Format (ISOBMFF) box sequence, dispatching each box
+ * to an {@link IsoBoxVisitor}.
+ *
+ * Handles standard 32-bit sizes, 64-bit large sizes ({@code size == 1}), EOF-terminated
+ * boxes ({@code size == 0}), and {@code uuid} boxes whose 16-byte usertype is parsed into
+ * {@link IsoBox#usertype} before any payload is read.
+ */
+public final class IsoBoxWalker
+{
+ private IsoBoxWalker() {}
+
+ /**
+ * Starts a walk over the boxes in {@code inputStream} using the given visitor.
+ * The stream is read with Motorola (big-endian) byte order, which is mandatory for ISOBMFF.
+ */
+ public static void walk(@NotNull InputStream inputStream, @NotNull IsoBoxVisitor visitor)
+ {
+ com.drew.lang.StreamReader reader = new com.drew.lang.StreamReader(inputStream);
+ reader.setMotorolaByteOrder(true);
+ walk(reader, -1L, visitor);
+ }
+
+ /**
+ * Recursively walks boxes from the current position in {@code reader} until
+ * {@code containerEnd} bytes have been consumed (or EOF when {@code containerEnd == -1}).
+ */
+ static void walk(@NotNull SequentialReader reader, long containerEnd, @NotNull IsoBoxVisitor visitor)
+ {
+ try {
+ while (containerEnd == -1 || reader.getPosition() < containerEnd) {
+ IsoBox box;
+ try {
+ box = new IsoBox(reader);
+ } catch (IOException e) {
+ // Clean EOF when reading the next box header: normal for open-ended containers.
+ if (containerEnd == -1) break;
+ visitor.addError(e.getMessage());
+ break;
+ }
+
+ if (box.payloadSize < 0 && box.size != -1) {
+ visitor.addError("Box payload size is negative for type: " + box.type);
+ break;
+ }
+
+ long payloadEnd = (box.payloadSize == -1) ? -1L : reader.getPosition() + box.payloadSize;
+
+ if (visitor.shouldRecurse(box)) {
+ walk(reader, payloadEnd, visitor.processContainer(box));
+ // After recursion, ensure the reader is positioned at the end of this box.
+ // The inner walk may have stopped early (e.g. on an error), which would leave
+ // the reader in the middle of the container and corrupt all subsequent sibling
+ // box reads (e.g. the top-level XMP UUID after moov would be missed).
+ if (payloadEnd != -1) {
+ long remaining = payloadEnd - reader.getPosition();
+ if (remaining > 0)
+ reader.skip(remaining);
+ }
+ } else if (visitor.shouldVisit(box)) {
+ if (box.payloadSize == -1) {
+ visitor.addError("Cannot visit EOF-terminated box: " + box.type);
+ break;
+ } else if (box.payloadSize > Integer.MAX_VALUE) {
+ visitor.addError("Box payload too large to read into memory: " + box.type);
+ reader.skip(box.payloadSize);
+ } else {
+ byte[] payload = reader.getBytes((int) box.payloadSize);
+ try {
+ visitor.visit(box, payload);
+ } catch (IOException e) {
+ // A single bad box must not abort the entire walk.
+ visitor.addError("Error visiting box '" + box.type + "': " + e.getMessage());
+ }
+ }
+ } else {
+ if (box.payloadSize > 0) {
+ reader.skip(box.payloadSize);
+ } else if (box.payloadSize == -1) {
+ break; // EOF-terminated box, nothing more to do
+ }
+ }
+ }
+ } catch (IOException e) {
+ visitor.addError(e.getMessage());
+ }
+ }
+}
diff --git a/Source/com/drew/metadata/cr3/Cr3BoxTypes.java b/Source/com/drew/metadata/cr3/Cr3BoxTypes.java
new file mode 100644
index 000000000..6e385c7a0
--- /dev/null
+++ b/Source/com/drew/metadata/cr3/Cr3BoxTypes.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2002-2019 Drew Noakes and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ * https://drewnoakes.com/code/exif/
+ * https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.metadata.cr3;
+
+/**
+ * FourCC box type constants for Canon CR3 files.
+ *
+ * See https://github.com/lclevy/canon_cr3 for the CR3 format specification.
+ */
+public final class Cr3BoxTypes
+{
+ /** File type box (standard ISOBMFF). */
+ public static final String BOX_FILE_TYPE = "ftyp";
+
+ /** Canon Compressor Version — 30-byte ASCII string identifying the encoder version. */
+ public static final String BOX_CANON_COMPRESSOR_VERSION = "CNCV";
+
+ /** Canon Compressor Table Pointers — track layout metadata. Not decoded; skipped by the walker. */
+ public static final String BOX_CANON_COMPRESSOR_TABLE = "CCTP";
+
+ /** Canon Compressor Data — per-track records within CCTP. Not decoded; skipped by the walker. */
+ public static final String BOX_CANON_COMPRESSOR_DATA = "CCDT";
+
+ /** Canon Track Base Offsets — file offsets for each track. Not decoded; skipped by the walker. */
+ public static final String BOX_CANON_TRACK_BASE_OFFSETS = "CTBO";
+
+ /** Canon Metadata in TIFF format — IFD0 (Make, Model, DateTime, …). */
+ public static final String BOX_CANON_TIFF_IFD0 = "CMT1";
+
+ /** Canon Metadata in TIFF format — Exif sub-IFD (exposure, focal length, …). */
+ public static final String BOX_CANON_TIFF_EXIF = "CMT2";
+
+ /** Canon Metadata in TIFF format — Canon makernote IFD. */
+ public static final String BOX_CANON_TIFF_MAKERNOTE = "CMT3";
+
+ /** Canon Metadata in TIFF format — GPS IFD. */
+ public static final String BOX_CANON_TIFF_GPS = "CMT4";
+
+ /** Thumbnail box — 160×120 JPEG inside the Canon metadata UUID. */
+ public static final String BOX_THUMBNAIL = "THMB";
+
+ private Cr3BoxTypes() {}
+}
diff --git a/Source/com/drew/metadata/cr3/Cr3ContainerTypes.java b/Source/com/drew/metadata/cr3/Cr3ContainerTypes.java
new file mode 100644
index 000000000..26ccd6a3f
--- /dev/null
+++ b/Source/com/drew/metadata/cr3/Cr3ContainerTypes.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2019 Drew Noakes and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ * https://drewnoakes.com/code/exif/
+ * https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.metadata.cr3;
+
+/**
+ * ISOBMFF container (box-of-boxes) type constants used in Canon CR3 files.
+ */
+public final class Cr3ContainerTypes
+{
+ /** Movie container — holds tracks, header, and Canon metadata UUID. */
+ public static final String BOX_MOVIE = "moov";
+
+ private Cr3ContainerTypes() {}
+}
diff --git a/Source/com/drew/metadata/cr3/Cr3Descriptor.java b/Source/com/drew/metadata/cr3/Cr3Descriptor.java
new file mode 100644
index 000000000..e269ac4b1
--- /dev/null
+++ b/Source/com/drew/metadata/cr3/Cr3Descriptor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2019 Drew Noakes and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ * https://drewnoakes.com/code/exif/
+ * https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.metadata.cr3;
+
+import com.drew.lang.annotations.NotNull;
+import com.drew.metadata.TagDescriptor;
+
+/**
+ * Provides human-readable descriptions for {@link Cr3Directory} tags.
+ */
+public class Cr3Descriptor extends TagDescriptor
+ * Camera data (EXIF, makernote, GPS) is stored in the standard directories
+ * ({@code ExifIFD0Directory}, {@code ExifSubIFDDirectory}, {@code CanonMakernoteDirectory},
+ * {@code GpsDirectory}) that are populated via the CMT1–CMT4 TIFF blobs inside the file.
+ */
+public class Cr3Directory extends Directory
+{
+ public static final int TAG_MAJOR_BRAND = 1;
+ public static final int TAG_MINOR_VERSION = 2;
+ public static final int TAG_COMPATIBLE_BRANDS = 3;
+ public static final int TAG_COMPRESSOR_VERSION = 4;
+ public static final int TAG_THUMBNAIL_WIDTH = 5;
+ public static final int TAG_THUMBNAIL_HEIGHT = 6;
+ public static final int TAG_PREVIEW_WIDTH = 7;
+ public static final int TAG_PREVIEW_HEIGHT = 8;
+
+ @NotNull
+ private static final HashMap
+ * uint32 unknown/flags
+ * uint16 unknown
+ * uint16 width
+ * uint16 height
+ * ...
+ * byte[] jpeg_data
+ *
+ */
+ void parsePayload(@NotNull byte[] uuidPayload)
+ {
+ try {
+ SequentialByteArrayReader reader = new SequentialByteArrayReader(uuidPayload);
+ reader.skip(PREAMBLE_SIZE + BOX_HEADER_SIZE + 6); // preamble + PRVW box header + uint32 flags + uint16 unknown
+ int width = reader.getUInt16();
+ int height = reader.getUInt16();
+ directory.setInt(Cr3Directory.TAG_PREVIEW_WIDTH, width);
+ directory.setInt(Cr3Directory.TAG_PREVIEW_HEIGHT, height);
+ } catch (IOException e) {
+ directory.addError("Error reading PRVW dimensions: " + e.getMessage());
+ }
+ }
+}
diff --git a/Source/com/drew/imaging/isobmff/IsoBox.java b/Source/com/drew/imaging/isobmff/IsoBox.java
new file mode 100644
index 000000000..c1244686a
--- /dev/null
+++ b/Source/com/drew/imaging/isobmff/IsoBox.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2019 Drew Noakes and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ * https://drewnoakes.com/code/exif/
+ * https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.imaging.isobmff;
+
+import com.drew.lang.SequentialReader;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Represents an ISO Base Media File Format (ISOBMFF) box header.
+ *
+ *
+ */
+public final class IsoBox
+{
+ /** Total size of the box in bytes including the header, or -1 if the box extends to EOF. */
+ public final long size;
+
+ /** Four-character box type code. */
+ public final String type;
+
+ /** 16-byte user-defined type for {@code uuid} boxes; {@code null} otherwise. */
+ public final byte[] usertype;
+
+ /** Byte count of the payload (data after the full header). */
+ final long payloadSize;
+
+ IsoBox(SequentialReader reader) throws IOException
+ {
+ int headerSize = 8; // minimum: 4 (size field) + 4 (type field)
+
+ long rawSize = reader.getUInt32();
+ this.type = reader.getString(4);
+
+ long resolvedSize;
+ if (rawSize == 1) {
+ resolvedSize = reader.getInt64();
+ headerSize += 8;
+ } else if (rawSize == 0) {
+ resolvedSize = -1; // extends to EOF
+ } else {
+ resolvedSize = rawSize;
+ }
+ this.size = resolvedSize;
+
+ if ("uuid".equals(this.type)) {
+ this.usertype = reader.getBytes(16);
+ headerSize += 16;
+ } else {
+ this.usertype = null;
+ }
+
+ this.payloadSize = (resolvedSize == -1) ? -1 : resolvedSize - headerSize;
+ }
+
+ /** Returns {@code true} if this is a {@code uuid} box whose usertype matches the given 16 bytes. */
+ public boolean usertypeMatches(byte[] expected)
+ {
+ return usertype != null && Arrays.equals(usertype, expected);
+ }
+}
diff --git a/Source/com/drew/imaging/isobmff/IsoBoxVisitor.java b/Source/com/drew/imaging/isobmff/IsoBoxVisitor.java
new file mode 100644
index 000000000..bc0e26ee8
--- /dev/null
+++ b/Source/com/drew/imaging/isobmff/IsoBoxVisitor.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2019 Drew Noakes and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ * https://drewnoakes.com/code/exif/
+ * https://github.com/drewnoakes/metadata-extractor
+ */
+package com.drew.imaging.isobmff;
+
+import com.drew.lang.annotations.NotNull;
+
+import java.io.IOException;
+
+/**
+ * Visitor interface for {@link IsoBoxWalker}.
+ *