diff --git a/Source/com/drew/metadata/mp4/Mp4BoxHandler.java b/Source/com/drew/metadata/mp4/Mp4BoxHandler.java index 17e56e642..29861f6a3 100644 --- a/Source/com/drew/metadata/mp4/Mp4BoxHandler.java +++ b/Source/com/drew/metadata/mp4/Mp4BoxHandler.java @@ -20,24 +20,37 @@ */ package com.drew.metadata.mp4; +import static com.drew.metadata.mp4.Mp4Directory.TAG_CATEGORY; +import static com.drew.metadata.mp4.Mp4Directory.TAG_COMMENT; +import static com.drew.metadata.mp4.Mp4Directory.TAG_LATITUDE; +import static com.drew.metadata.mp4.Mp4Directory.TAG_LONGITUDE; +import static com.drew.metadata.mp4.Mp4Directory.TAG_MOOD; +import static com.drew.metadata.mp4.Mp4Directory.TAG_SUBTITLE; +import static com.drew.metadata.mp4.Mp4Directory.TAG_TITLE; +import static com.drew.metadata.mp4.Mp4Directory.TAG_USER_RATING; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import com.drew.imaging.mp4.Mp4Handler; import com.drew.lang.DateUtil; import com.drew.lang.Rational; import com.drew.lang.SequentialByteArrayReader; import com.drew.lang.SequentialReader; +import com.drew.lang.StringUtil; import com.drew.lang.annotations.NotNull; import com.drew.lang.annotations.Nullable; import com.drew.metadata.Metadata; -import com.drew.metadata.mp4.media.*; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.drew.metadata.mp4.Mp4Directory.TAG_LATITUDE; -import static com.drew.metadata.mp4.Mp4Directory.TAG_LONGITUDE; +import com.drew.metadata.mp4.media.Mp4HintHandler; +import com.drew.metadata.mp4.media.Mp4MetaHandler; +import com.drew.metadata.mp4.media.Mp4SoundHandler; +import com.drew.metadata.mp4.media.Mp4TextHandler; +import com.drew.metadata.mp4.media.Mp4UuidBoxHandler; +import com.drew.metadata.mp4.media.Mp4VideoHandler; /** * @author Payton Garland @@ -140,6 +153,8 @@ public Mp4Handler processBox(@NotNull String type, @Nullable byte[] payload, private void processUserData(@NotNull SequentialReader reader, int length) throws IOException { final int LOCATION_CODE = 0xA978797A; // "©xyz" + final int META_TYPE = 0x6D657461; // "meta" + final int XTRA_TYPE = 0x58747261; // "Xtra" String coordinateString = null; @@ -152,11 +167,16 @@ private void processUserData(@NotNull SequentialReader reader, int length) throw int xyzLength = reader.getUInt16(); reader.skip(2); coordinateString = reader.getString(xyzLength, "UTF-8"); - } else if (size >= 8) { - reader.skip(size - 8); - } else { - return; - } + } else if (kind == META_TYPE && size > 16) { + reader.skip(4); + processUserDataMeta(reader, length, size - 12); + } else if (kind == XTRA_TYPE && size > 16) { + processUserDataMetaXtra(reader, length, size - 8); + } else if (size >= 8) { + reader.skip(size - 8); + } else { + return; + } } if (coordinateString != null) { @@ -170,6 +190,91 @@ private void processUserData(@NotNull SequentialReader reader, int length) throw } } } + + private void processUserDataMeta(@NotNull SequentialReader reader, int length, long blockSize) throws IOException { + final int HDLR_TYPE = 0x68646C72; // "hdlr" + final int ILST_TYPE = 0x696C7374; // "ilst" + + long initialPosition = reader.getPosition(); + + while (reader.getPosition() < length && (reader.getPosition() - initialPosition) < blockSize) { + long size = reader.getUInt32(); + if (size <= 4) + break; + int kind = reader.getInt32(); + if (kind == HDLR_TYPE) { + // nothing + reader.skip(size - 8); + } else if (kind == ILST_TYPE && size > 16) { + processUserDataMetaIList(reader, length, size - 8); + } + } + } + + private void processUserDataMetaIList(@NotNull SequentialReader reader, int length, long blockSize) + throws IOException { + final int CNAM_TYPE = 0xA96E616D; // "©nam" + final int CCMT_TYPE = 0xA9636D74; // "©cmt" + long initialPosition = reader.getPosition(); + + while (reader.getPosition() < length && (reader.getPosition() - initialPosition) < blockSize) { + long size = reader.getUInt32(); + if (size <= 4) + break; + int kind = reader.getInt32(); + if (kind == CNAM_TYPE) { + long cnamSize = reader.getUInt32(); + if (cnamSize > 16) { + reader.skip(12); + directory.setString(TAG_TITLE, reader.getString((int) cnamSize - 16, "UTF-8")); + } + } else if (kind == CCMT_TYPE) { + long ccmtSize = reader.getUInt32(); + if (ccmtSize > 16) { + reader.skip(12); + directory.setString(TAG_COMMENT, reader.getString((int) ccmtSize - 16, "UTF-8")); + } + } else { + // nothing + } + } + } + + private void processUserDataMetaXtra(@NotNull SequentialReader reader, int length, long blockSize) + throws IOException { + long initialPosition = reader.getPosition(); + while (reader.getPosition() < length && (reader.getPosition() - initialPosition) < blockSize) { + long key_size = reader.getUInt32(); + long key_name_size = reader.getUInt32(); + String key_name = reader.getString((int) key_name_size, "UTF-8"); + long entry_count = reader.getUInt32(); + if (key_name.equals("WM/SubTitle")) { + String value = getProcessUserDataMetaXtraValue(reader, entry_count); + directory.setString(TAG_SUBTITLE, value); + } else if (key_name.equals("WM/SharedUserRating")) { + long value_size = reader.getUInt32(); + int value_type = reader.getUInt16(); + directory.setLong(TAG_USER_RATING, reader.getInt64()); + } else if (key_name.equals("WM/Category")) { + String value = getProcessUserDataMetaXtraValue(reader, entry_count); + directory.setString(TAG_CATEGORY, value); + } else if (key_name.equals("WM/Mood")) { + String value = getProcessUserDataMetaXtraValue(reader, entry_count); + directory.setString(TAG_MOOD, value); + } + } + } + + private String getProcessUserDataMetaXtraValue(@NotNull SequentialReader reader, long entry_count) + throws IOException { + List result = new ArrayList<>(); + for (long i = 0; i < entry_count; ++i) { + long value_size = reader.getUInt32(); + int val_type = reader.getUInt16(); + result.add(reader.getString((int) value_size - 6,"UTF-16LE").replace("\0", "")); + } + return StringUtil.join(result, " | "); + } private void processFileType(@NotNull SequentialReader reader, long boxSize) throws IOException { diff --git a/Source/com/drew/metadata/mp4/Mp4Directory.java b/Source/com/drew/metadata/mp4/Mp4Directory.java index 1355f370f..8369fc805 100644 --- a/Source/com/drew/metadata/mp4/Mp4Directory.java +++ b/Source/com/drew/metadata/mp4/Mp4Directory.java @@ -20,11 +20,11 @@ */ package com.drew.metadata.mp4; +import java.util.HashMap; + import com.drew.lang.annotations.NotNull; import com.drew.metadata.Directory; -import java.util.HashMap; - public class Mp4Directory extends Directory { public static final int TAG_CREATION_TIME = 0x0100; @@ -46,6 +46,12 @@ public class Mp4Directory extends Directory { public static final int TAG_LATITUDE = 0x2001; public static final int TAG_LONGITUDE = 0x2002; public static final int TAG_MEDIA_TIME_SCALE = 0x0306; + public static final int TAG_TITLE = 0x3000; + public static final int TAG_COMMENT = 0x3001; + public static final int TAG_SUBTITLE = 0x3002; + public static final int TAG_USER_RATING = 0x3003; + public static final int TAG_CATEGORY = 0x3004; + public static final int TAG_MOOD = 0x3005; public static final int TAG_MAJOR_BRAND = 1; public static final int TAG_MINOR_VERSION = 2; @@ -78,6 +84,12 @@ public class Mp4Directory extends Directory { _tagNameMap.put(TAG_ROTATION, "Rotation"); _tagNameMap.put(TAG_LATITUDE, "Latitude"); _tagNameMap.put(TAG_LONGITUDE, "Longitude"); + _tagNameMap.put(TAG_TITLE, "Title"); + _tagNameMap.put(TAG_COMMENT, "Comment"); + _tagNameMap.put(TAG_SUBTITLE, "Subtitle"); + _tagNameMap.put(TAG_USER_RATING, "User rating"); + _tagNameMap.put(TAG_CATEGORY, "Tags"); + _tagNameMap.put(TAG_MOOD, "Mood"); _tagNameMap.put(TAG_MEDIA_TIME_SCALE, "Media Time Scale"); }