diff --git a/MetadataExtractor/Formats/QuickTime/QuickTimeMetadataHeaderDirectory.cs b/MetadataExtractor/Formats/QuickTime/QuickTimeMetadataHeaderDirectory.cs index 2180903f..c0d526bb 100644 --- a/MetadataExtractor/Formats/QuickTime/QuickTimeMetadataHeaderDirectory.cs +++ b/MetadataExtractor/Formats/QuickTime/QuickTimeMetadataHeaderDirectory.cs @@ -121,6 +121,29 @@ public sealed class QuickTimeMetadataHeaderDirectory : Directory { "com.android.version", TagAndroidVersion }, { "com.android.manufacturer", TagAndroidManufacturer }, { "com.android.model", TagAndroidModel }, + { "alb", TagAlbum }, + { "albm", TagAlbum }, + { "ART", TagArtist }, + { "aut", TagAuthor }, + { "auth", TagAuthor }, + { "cmt", TagComment }, + { "cpy", TagCopyright }, + { "cprt", TagCopyright }, + { "day", TagCreationDate }, + { "des", TagDescription }, + { "desc", TagDescription }, + { "dscp", TagDescription }, + { "dir", TagDirector }, + { "nam", TagTitle }, + { "titl", TagTitle }, + { "gen", TagGenre }, + { "gnre", TagGenre }, + { "keyw", TagKeywords }, + { "xyz", TagGpsLocation }, + { "prd", TagProducer }, + { "pub", TagPublisher }, + { "yrrc", TagYear }, + { "rtng", TagRating }, }; public QuickTimeMetadataHeaderDirectory() : base(_tagNameMap) diff --git a/MetadataExtractor/Formats/QuickTime/QuickTimeMetadataReader.cs b/MetadataExtractor/Formats/QuickTime/QuickTimeMetadataReader.cs index 35bfd758..26e6a587 100644 --- a/MetadataExtractor/Formats/QuickTime/QuickTimeMetadataReader.cs +++ b/MetadataExtractor/Formats/QuickTime/QuickTimeMetadataReader.cs @@ -2,6 +2,7 @@ using MetadataExtractor.Formats.Exif; using MetadataExtractor.Formats.Exif.Makernotes; +using MetadataExtractor.Formats.Iso14496; using MetadataExtractor.Formats.Tiff; using MetadataExtractor.Formats.Xmp; @@ -19,6 +20,7 @@ public static IReadOnlyList ReadMetadata(Stream stream) { var directories = new List(); var metaDataKeys = new List(); + var metaDataHandlerType = string.Empty; QuickTimeMetadataHeaderDirectory? metaHeaderDirectory = null; QuickTimeReader.ProcessAtoms(stream, Handler); @@ -134,6 +136,12 @@ void UserDataHandler(AtomCallbackArgs a) QuickTimeMetadataHeaderDirectory.TagGpsLocation, new StringValue(stringBytes, Encoding.UTF8)); break; + case "meta": + { + a.Reader.Skip(4); + QuickTimeReader.ProcessAtoms(stream, MetaDataHandler, a.BytesLeft); + break; + } } } @@ -142,8 +150,22 @@ void MetaDataHandler(AtomCallbackArgs a) // see https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html switch (a.TypeString) { + case "hdlr": + //QuickTime Handler Tags + a.Reader.Skip(8); + + var handlerType = a.Reader.GetUInt32(); + metaDataHandlerType = TypeStringConverter.ToTypeString(handlerType); + + //metaDataHandlerType: + //mdir => Metadata Item List Tags + //mdta => Metadata Keys Tags + + break; case "keys": { + //This directory contains a list of key names which are used to decode tags written by the "mdta" handler. + a.Reader.Skip(4); // 1 byte version, 3 bytes flags var entryCount = a.Reader.GetUInt32(); for (int i = 1; i <= entryCount; i++) @@ -158,80 +180,109 @@ void MetaDataHandler(AtomCallbackArgs a) } case "ilst": { - // Iterate over the list of Metadata Item Atoms. - for (int i = 0; i < metaDataKeys.Count; i++) + //ilst is both used by both mdir (ItemList Tags) and mdta (Keys Tags) handlers + + if (metaDataHandlerType == "mdir") { - long atomSize = a.Reader.GetUInt32(); - if (atomSize < 24) - { - GetMetaHeaderDirectory().AddError("Invalid ilst atom type"); - a.Reader.Skip(atomSize - 4); - continue; - } - var atomType = a.Reader.GetUInt32(); + //ItemList Tags + QuickTimeReader.ProcessAtoms(stream, MetaDataItemListTagsHandler, a.BytesLeft); + } + else if (metaDataHandlerType == "mdta") + { + // Iterate over the list of Metadata Item Atoms. + QuickTimeReader.ProcessAtoms(stream, MetaDataKeysTagsHandler, a.BytesLeft); + } - // Indexes into the metadata item keys atom are 1-based (1…entry_count). - // atom type for each metadata item atom is the index of the key - if (atomType < 1 || atomType > metaDataKeys.Count) - { - GetMetaHeaderDirectory().AddError("Invalid ilst atom type"); - a.Reader.Skip(atomSize - 8); - continue; - } - var key = metaDataKeys[(int)atomType - 1]; + break; + } + } + } - // Value Atom - a.Reader.Skip(8); // uint32 type indicator, uint32 locale indicator + void MetaDataTagHandler(AtomCallbackArgs a, string key) + { + // Value Atom + a.Reader.Skip(8); // uint32 type indicator, uint32 locale indicator - // Data Atom - var dataTypeIndicator = a.Reader.GetUInt32(); - if (!_supportedAtomValueTypes.Contains((int)dataTypeIndicator)) - { - GetMetaHeaderDirectory().AddError($"Unsupported type indicator \"{dataTypeIndicator}\" for key \"{key}\""); - a.Reader.Skip(atomSize - 20); - continue; - } + // Data Atom + var dataTypeIndicator = a.Reader.GetUInt32(); + if (!_supportedAtomValueTypes.Contains((int)dataTypeIndicator)) + { + GetMetaHeaderDirectory().AddError($"Unsupported type indicator \"{dataTypeIndicator}\" for key \"{key}\""); + return; + } - // locale not supported yet. - a.Reader.Skip(4); + // locale not supported yet. + a.Reader.Skip(4); - var data = a.Reader.GetBytes((int)atomSize - 24); - if (QuickTimeMetadataHeaderDirectory.TryGetTag(key, out int tag)) - { - object value = dataTypeIndicator switch - { - // UTF-8 - 1 => new StringValue(data, Encoding.UTF8), - - // BE Float32 (used for User Rating) - 23 => BitConverter.ToSingle(BitConverter.IsLittleEndian ? data.Reverse().ToArray() : data, 0), - - // 13 JPEG - // 14 PNG - // 27 BMP - _ => data - }; - - value = tag switch - { - QuickTimeMetadataHeaderDirectory.TagCreationDate => DateTime.Parse(((StringValue)value).ToString()), - QuickTimeMetadataHeaderDirectory.TagLocationDate => DateTime.Parse(((StringValue)value).ToString()), - _ => value, - }; - - GetMetaHeaderDirectory().Set(tag, value); - } - else - { - GetMetaHeaderDirectory().AddError($"Unsupported ilst key \"{key}\""); - } - } + var data = a.Reader.GetBytes((int)a.Size - 24); + if (QuickTimeMetadataHeaderDirectory.TryGetTag(key, out int tag)) + { + object value = dataTypeIndicator switch + { + // UTF-8 + 1 => new StringValue(data, Encoding.UTF8), - break; - } + // BE Float32 (used for User Rating) + 23 => BitConverter.ToSingle(BitConverter.IsLittleEndian ? data.Reverse().ToArray() : data, 0), + + // 13 JPEG + // 14 PNG + // 27 BMP + _ => data + }; + + value = tag switch + { + QuickTimeMetadataHeaderDirectory.TagCreationDate => DateTime.Parse(((StringValue)value).ToString()), + QuickTimeMetadataHeaderDirectory.TagLocationDate => DateTime.Parse(((StringValue)value).ToString()), + _ => value, + }; + + GetMetaHeaderDirectory().Set(tag, value); + } + else + { + GetMetaHeaderDirectory().AddError($"Unsupported ilst key \"{key}\""); } } + void MetaDataKeysTagsHandler(AtomCallbackArgs a) + { + // Indexes into the metadata item keys atom are 1-based (1…entry_count). + // atom type for each metadata item atom is the index of the key + if (a.Type < 1 || a.Type > metaDataKeys.Count) + { + GetMetaHeaderDirectory().AddError("Invalid ilst atom type"); + return; + } + + var key = metaDataKeys[(int)a.Type - 1]; + + MetaDataTagHandler(a, key); + } + + void MetaDataItemListTagsHandler(AtomCallbackArgs a) + { + var key = a.TypeString; + if (key.Length < 1) + { + return; + } + if (key[0] == 0xa9 || key[0] == 0x40) + { + //Tag ID's beginning with the copyright symbol (hex 0xa9) are multi-language text + //Alternate language tags are accessed by adding a dash followed by a 3-character ISO 639-2 language code to the tag name. + + //some stupid Ricoh programmer used the '@' symbol instead of the copyright symbol in these tag ID's for the Ricoh Theta Z1 and maybe other models + + //For now we don't support those, we will strip the copyright and locale info + key = key.Substring(1); + key = key.Split('-')[0]; + } + + MetaDataTagHandler(a, key); + } + void MoovHandler(AtomCallbackArgs a) { switch (a.TypeString)