Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Canon Raw V3 support via MP4 handlers #644

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Source/com/drew/imaging/ImageMetadataReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ public static Metadata readMetadata(@NotNull final InputStream inputStream, fina
return EpsMetadataReader.readMetadata(inputStream);
case Heif:
return HeifMetadataReader.readMetadata(inputStream);
case Crx:
return Mp4MetadataReader.readMetadata(inputStream);
case Unknown:
throw new ImageProcessingException("File format could not be determined");
default:
Expand Down
135 changes: 125 additions & 10 deletions Source/com/drew/metadata/mp4/media/Mp4UuidBoxHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,44 @@
*/
package com.drew.metadata.mp4.media;

import static com.drew.metadata.mp4.media.Mp4UuidBoxDirectory.TAG_USER_DATA;
import static com.drew.metadata.mp4.media.Mp4UuidBoxDirectory.TAG_UUID;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

import com.drew.imaging.mp4.Mp4Handler;
import com.drew.imaging.tiff.TiffProcessingException;
import com.drew.imaging.tiff.TiffReader;
import com.drew.lang.ByteArrayReader;
import com.drew.lang.ByteTrie;
import com.drew.lang.RandomAccessStreamReader;
import com.drew.lang.SequentialByteArrayReader;
import com.drew.lang.SequentialReader;
import com.drew.lang.annotations.NotNull;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.exif.ExifReader;
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 com.drew.metadata.iptc.IptcReader;
import com.drew.metadata.mp4.Mp4BoxTypes;
import com.drew.metadata.mp4.Mp4Context;
import com.drew.metadata.photoshop.PhotoshopReader;
import com.drew.metadata.xmp.XmpReader;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.UUID;

import static com.drew.metadata.mp4.media.Mp4UuidBoxDirectory.TAG_USER_DATA;
import static com.drew.metadata.mp4.media.Mp4UuidBoxDirectory.TAG_UUID;

public class Mp4UuidBoxHandler extends Mp4Handler<Mp4UuidBoxDirectory>
{
// http://xhelmboyx.tripod.com/formats/mp4-layout.txt
// http://fileformats.archiveteam.org/wiki/Boxes/atoms_format#UUID_boxes
// https://github.com/lclevy/canon_cr3

private enum UuidType
{
Expand All @@ -58,6 +71,7 @@ private enum UuidType
GeoJp2GeoTiffBox, // b14bf8bd-083d-4b43-a5ae-8cd7d5a6ce03
Xmp, // be7acfcb-97a9-42e8-9c71-999491e3afac
PiffProtectionSystemSpecificHeaderBox, // d08a4f18-10f3-4a82-b6c8-32d8aba183d3
CanonCr3Box // 85c0b687-820f-11e0-8111-f4ce462b6a48
}

private final static ByteTrie<UuidType> _uuidLookup;
Expand All @@ -76,8 +90,16 @@ private enum UuidType
_uuidLookup.addPath(UuidType.GeoJp2GeoTiffBox, new byte[] { (byte)0xb1, 0x4b, (byte)0xf8, (byte)0xbd, 0x08, 0x3d, 0x4b, 0x43, (byte)0xa5, (byte)0xae, (byte)0x8c, (byte)0xd7, (byte)0xd5, (byte)0xa6, (byte)0xce, 0x03 });
_uuidLookup.addPath(UuidType.Xmp, new byte[] { (byte)0xbe, 0x7a, (byte)0xcf, (byte)0xcb, (byte)0x97, (byte)0xa9, 0x42, (byte)0xe8, (byte)0x9c, 0x71, (byte)0x99, (byte)0x94, (byte)0x91, (byte)0xe3, (byte)0xaf, (byte)0xac });
_uuidLookup.addPath(UuidType.PiffProtectionSystemSpecificHeaderBox, new byte[] { (byte)0xd0, (byte)0x8a, 0x4f, 0x18, 0x10, (byte)0xf3, 0x4a, (byte)0x82, (byte)0xb6, (byte)0xc8, 0x32, (byte)0xd8, (byte)0xab, (byte)0xa1, (byte)0x83, (byte)0xd3 });
_uuidLookup.addPath(UuidType.CanonCr3Box, 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 });
}


private static final String CANON_METADATA_IFD0 = "CMT1"; // Canon Metadata in TIFF format, Exif IFD0
private static final String CANON_METADATA_SUBIFD = "CMT2"; // Canon Metadata in TIFF format, Exif ExifIFD
private static final String CANON_METADATA_MAKERNOTE = "CMT3"; // Canon Metadata in TIFF format, Canon Makernotes
private static final String CANON_METADATA_GPS = "CMT4"; // Canon Metadata in TIFF format, Exif GPS IFD

private List<String> CANON_METADATA_BOXES = Arrays.asList(CANON_METADATA_IFD0, CANON_METADATA_SUBIFD, CANON_METADATA_MAKERNOTE, CANON_METADATA_GPS);

public Mp4UuidBoxHandler(Metadata metadata)
{
super(metadata);
Expand All @@ -101,7 +123,7 @@ protected boolean shouldAcceptContainer(@NotNull String type)
{
return false;
}

@Override
public Mp4Handler<?> processBox(@NotNull String type, byte[] payload, long boxSize, Mp4Context context) throws IOException
{
Expand All @@ -111,7 +133,7 @@ public Mp4Handler<?> processBox(@NotNull String type, byte[] payload, long boxSi
if (uuidType == null) {
return this;
}

switch (uuidType) {
case Exif:
new ExifReader().extract(new ByteArrayReader(payload, 16), metadata, 0, directory);
Expand All @@ -125,6 +147,9 @@ public Mp4Handler<?> processBox(@NotNull String type, byte[] payload, long boxSi
case Xmp:
new XmpReader().extract(payload, 16, payload.length - 16, metadata, directory);
break;
case CanonCr3Box:
processCanonRawV3(new SequentialByteArrayReader(payload, 16), payload.length - 16);
break;
default:
SequentialReader reader = new SequentialByteArrayReader(payload);
String usertype = getUuid(reader.getBytes(16));
Expand All @@ -144,4 +169,94 @@ private static String getUuid(byte[] bytes) {

return uuid.toString();
}

private CanonCrxTiffHandler createCanonHandler(String type) {
switch (type) {
case CANON_METADATA_IFD0:
return new CanonCrxTiffHandler(new ExifIFD0Directory(), metadata, null, 0);
case CANON_METADATA_SUBIFD:
return new CanonCrxTiffHandler(new ExifSubIFDDirectory(), metadata, null, 0);
case CANON_METADATA_MAKERNOTE:
return new CanonCrxTiffHandler(new CanonMakernoteDirectory(), metadata, null, 0);
case CANON_METADATA_GPS:
return new CanonCrxTiffHandler(new GpsDirectory(), metadata, null, 0);
}

return null;
}

// When the UUID box's uuid == CanonCr3Box, it may contain several sub-boxes
// This will extract each box and attempt to process with the appropriate
// reader, installing/updating the appropriate Directory
private void processCanonRawV3(SequentialReader reader, long atomEnd)
{
try {
while (atomEnd == -1 || reader.getPosition() < atomEnd) {
long boxSize = reader.getUInt32();
String boxType = reader.getString(4);
boolean isLargeSize = boxSize == 1;

if (isLargeSize) {
boxSize = reader.getInt64();
}

if (boxSize > Integer.MAX_VALUE) {
this.addError("Box size too large.");
break;
}

if (boxSize < 8) {
this.addError("Box size too small.");
break;
}

// Determine if fourCC is container/atom and process accordingly.
// Unknown atoms will be skipped
long dataSize = (isLargeSize ? boxSize - 16 : boxSize - 8);

if (CANON_METADATA_BOXES.contains(boxType)) {
CanonCrxTiffHandler subHandler = createCanonHandler(boxType);
RandomAccessStreamReader subReader = new RandomAccessStreamReader(new ByteArrayInputStream(reader.getBytes((int) dataSize)));

try {
new TiffReader().processTiff(subReader, subHandler, 0);
}
catch (TiffProcessingException ex) {
this.addError(ex.getMessage());
}
}
else if (isLargeSize) {
if (boxSize < 16) {
// TODO capture this error in a directory
break;
}
reader.skip(dataSize);
} else {
reader.skip(dataSize);
}
}
} catch (IOException e) {
this.addError(e.getMessage());
}
}

private static class CanonCrxTiffHandler extends ExifTiffHandler {
private static final int STANDARD_TIFF_MARKER = 0x002A;
private Directory directory;

public CanonCrxTiffHandler(Directory directory, Metadata metadata, Directory parentDirectory, int exifStartOffset) {
super(metadata, parentDirectory, exifStartOffset);
this.directory = directory;
}

@Override
public void setTiffMarker(int marker) throws TiffProcessingException {
if (marker != STANDARD_TIFF_MARKER)
{
throw new TiffProcessingException("Unexpected TIFF marker: 0x{marker:X}");
}

pushDirectory(directory);
}
}
}
Loading