public static DirectoryList ReadMetadata(Stream stream) { var directories = new List <Directory>(); var metaDataKeys = new List <string>(); QuickTimeMetadataHeaderDirectory?metaHeaderDirectory = null; QuickTimeReader.ProcessAtoms(stream, Handler); return(directories); QuickTimeMetadataHeaderDirectory GetMetaHeaderDirectory() { if (metaHeaderDirectory == null) { metaHeaderDirectory = new QuickTimeMetadataHeaderDirectory(); directories.Add(metaHeaderDirectory); } return(metaHeaderDirectory); } void TrakHandler(AtomCallbackArgs a) { switch (a.TypeString) { case "tkhd": { var directory = new QuickTimeTrackHeaderDirectory(); directory.Set(QuickTimeTrackHeaderDirectory.TagVersion, a.Reader.GetByte()); directory.Set(QuickTimeTrackHeaderDirectory.TagFlags, a.Reader.GetBytes(3)); directory.Set(QuickTimeTrackHeaderDirectory.TagCreated, _epoch.AddTicks(TimeSpan.TicksPerSecond * a.Reader.GetUInt32())); directory.Set(QuickTimeTrackHeaderDirectory.TagModified, _epoch.AddTicks(TimeSpan.TicksPerSecond * a.Reader.GetUInt32())); directory.Set(QuickTimeTrackHeaderDirectory.TagTrackId, a.Reader.GetUInt32()); a.Reader.Skip(4L); directory.Set(QuickTimeTrackHeaderDirectory.TagDuration, a.Reader.GetUInt32()); a.Reader.Skip(8L); directory.Set(QuickTimeTrackHeaderDirectory.TagLayer, a.Reader.GetUInt16()); directory.Set(QuickTimeTrackHeaderDirectory.TagAlternateGroup, a.Reader.GetUInt16()); directory.Set(QuickTimeTrackHeaderDirectory.TagVolume, a.Reader.Get16BitFixedPoint()); a.Reader.Skip(2L); directory.Set(QuickTimeTrackHeaderDirectory.TagMatrix, a.Reader.GetMatrix()); directory.Set(QuickTimeTrackHeaderDirectory.TagWidth, a.Reader.Get32BitFixedPoint()); directory.Set(QuickTimeTrackHeaderDirectory.TagHeight, a.Reader.Get32BitFixedPoint()); SetRotation(directory); directories.Add(directory); break; } } }
void MetaDataHandler(AtomCallbackArgs a) { // see https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html switch (a.TypeString) { case "keys": { var version = a.Reader.GetByte(); var flags = a.Reader.GetBytes(3); var entryCount = a.Reader.GetUInt32(); for (int i = 1; i <= entryCount; i++) { var keySize = a.Reader.GetUInt32(); var keyValueSize = (int)keySize - 8; var keyNamespace = a.Reader.GetUInt32(); var keyValue = a.Reader.GetBytes(keyValueSize); metaDataKeys.Add(Encoding.UTF8.GetString(keyValue)); } break; } case "ilst": { var directory = new QuickTimeMetadataHeaderDirectory(); // Iterate over the list of Metadata Item Atoms. for (int i = 0; i < metaDataKeys.Count; i++) { long atomSize = a.Reader.GetUInt32(); if (atomSize < 24) { directory.AddError("Invalid ilist atom type"); a.Reader.Skip(atomSize - 4); continue; } var atomType = a.Reader.GetUInt32(); // 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) { directory.AddError("Invalid ilist atom type"); a.Reader.Skip(atomSize - 8); continue; } var key = metaDataKeys[(int)atomType - 1]; // Value Atom var typeIndicator = a.Reader.GetUInt32(); var localeIndicator = a.Reader.GetUInt32(); // Data Atom var dataTypeIndicator = a.Reader.GetUInt32(); if (!_supportedAtomValueTypes.Contains((int)dataTypeIndicator)) { directory.AddError($"Unsupported type indicator \"{dataTypeIndicator}\" for key \"{key}\""); a.Reader.Skip(atomSize - 20); continue; } // Currently only the Default Country/Locale is supported var dataLocaleIndicator = a.Reader.GetUInt32(); if (dataLocaleIndicator != 0) { directory.AddError($"Unsupported locale indicator \"{dataLocaleIndicator}\" for key \"{key}\""); a.Reader.Skip(atomSize - 24); continue; } var data = a.Reader.GetBytes((int)atomSize - 24); if (directory.TryGetTag(key, out int tag)) { DecodeData(data, (int)dataTypeIndicator, tag, directory); } else { directory.AddError($"Unsupported ilist key \"{key}\""); } } directories.Add(directory); break; } } }
void MetaDataHandler(AtomCallbackArgs a) { // see https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html switch (a.TypeString) { case "keys": { a.Reader.Skip(4); // 1 byte version, 3 bytes flags var entryCount = a.Reader.GetUInt32(); for (int i = 1; i <= entryCount; i++) { var keySize = a.Reader.GetUInt32(); var keyValueSize = (int)keySize - 8; a.Reader.Skip(4); // uint32: key namespace var keyValue = a.Reader.GetBytes(keyValueSize); metaDataKeys.Add(Encoding.UTF8.GetString(keyValue)); } break; } case "ilst": { // Iterate over the list of Metadata Item Atoms. for (int i = 0; i < metaDataKeys.Count; i++) { long atomSize = a.Reader.GetUInt32(); if (atomSize < 24) { GetMetaHeaderDirectory().AddError("Invalid ilist atom type"); a.Reader.Skip(atomSize - 4); continue; } var atomType = a.Reader.GetUInt32(); // 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 ilist atom type"); a.Reader.Skip(atomSize - 8); continue; } var key = metaDataKeys[(int)atomType - 1]; // 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; } // 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 }; GetMetaHeaderDirectory().Set(tag, value); } else { GetMetaHeaderDirectory().AddError($"Unsupported ilist key \"{key}\""); } } break; } } }