void Handler(AtomCallbackArgs a)
            {
                switch (a.TypeString)
                {
                case "moov":
                {
                    QuickTimeReader.ProcessAtoms(stream, MoovHandler, a.BytesLeft);
                    break;
                }

                case "ftyp":
                {
                    var directory = new QuickTimeFileTypeDirectory();
                    directory.Set(QuickTimeFileTypeDirectory.TagMajorBrand, a.Reader.Get4ccString());
                    directory.Set(QuickTimeFileTypeDirectory.TagMinorVersion, a.Reader.GetUInt32());
                    var compatibleBrands = new List <string>();
                    while (a.BytesLeft >= 4)
                    {
                        compatibleBrands.Add(a.Reader.Get4ccString());
                    }
                    directory.Set(QuickTimeFileTypeDirectory.TagCompatibleBrands, String.Join(", ", compatibleBrands));
                    directories.Add(directory);
                    break;
                }
                }
            }
        /// <summary>
        /// Reads atom data from <paramref name="stream"/>, invoking <paramref name="handler"/> for each atom encountered.
        /// </summary>
        /// <param name="stream">The stream to read atoms from.</param>
        /// <param name="handler">A callback function to handle each atom.</param>
        /// <param name="stopByBytes">The maximum number of bytes to process before discontinuing.</param>
        public static void ProcessAtoms([NotNull] Stream stream, [NotNull] Action <AtomCallbackArgs> handler, long stopByBytes = -1)
        {
            var reader = new SequentialStreamReader(stream);

            var seriesStartPos = stream.Position;

            while (stopByBytes == -1 || stream.Position < seriesStartPos + stopByBytes)
            {
                var atomStartPos = stream.Position;

                // Length of the atom's data, in bytes, including size bytes
                long atomSize;
                try
                {
                    atomSize = reader.GetUInt32();
                }
                catch (IOException)
                {
                    // TODO don't use exception to trap end of stream
                    return;
                }

                // Typically four ASCII characters, but may be non-printable.
                // By convention, lowercase 4CCs are reserved by Apple.
                var atomType = reader.GetUInt32();

                if (atomSize == 1)
                {
                    // Size doesn't fit in 32 bits so read the 64 bit size here
                    atomSize = checked ((long)reader.GetUInt64());
                }
                else
                {
                    Debug.Assert(atomSize >= 8, "Atom should be at least 8 bytes long");
                }

                var args = new AtomCallbackArgs(atomType, atomSize, stream, atomStartPos, reader);

                handler(args);

                if (args.Cancel)
                {
                    return;
                }

                if (atomSize == 0)
                {
                    return;
                }

                var toSkip = atomStartPos + atomSize - stream.Position;

                if (toSkip < 0)
                {
                    throw new Exception("Handler moved stream beyond end of atom");
                }

                reader.TrySkip(toSkip);
            }
        }
Пример #3
0
            void UserDataHandler(AtomCallbackArgs a)
            {
                switch (a.TypeString)
                {
                case "?xyz":
                    var stringSize = a.Reader.GetUInt16();
                    a.Reader.Skip(2);     // uint16 language code
                    var stringBytes = a.Reader.GetBytes(stringSize);

                    // TODO parse ISO 6709 string into GeoLocation? GeoLocation does not (currently) support altitude, where ISO 6709 does
                    GetMetaHeaderDirectory().Set(
                        QuickTimeMetadataHeaderDirectory.TagGpsLocation,
                        new StringValue(stringBytes, Encoding.UTF8));
                    break;
                }
            }
            void Handler(AtomCallbackArgs a)
            {
                switch (a.TypeString)
                {
                case "moov":
                {
                    QuickTimeReader.ProcessAtoms(stream, MoovHandler, a.BytesLeft);
                    break;
                }

                case "uuid":
                {
                    var XMP = new byte[] { 0xbe, 0x7a, 0xcf, 0xcb, 0x97, 0xa9, 0x42, 0xe8, 0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac };
                    if (a.BytesLeft >= XMP.Length)
                    {
                        var uuid = a.Reader.GetBytes(XMP.Length);
                        if (XMP.RegionEquals(0, XMP.Length, uuid))
                        {
                            var xmpBytes     = a.Reader.GetNullTerminatedBytes((int)a.BytesLeft);
                            var xmpDirectory = new XmpReader().Extract(xmpBytes);
                            directories.Add(xmpDirectory);
                        }
                    }
                    break;
                }

                case "ftyp":
                {
                    var directory = new QuickTimeFileTypeDirectory();
                    directory.Set(QuickTimeFileTypeDirectory.TagMajorBrand, a.Reader.Get4ccString());
                    directory.Set(QuickTimeFileTypeDirectory.TagMinorVersion, a.Reader.GetUInt32());
                    var compatibleBrands = new List <string>();
                    while (a.BytesLeft >= 4)
                    {
                        compatibleBrands.Add(a.Reader.Get4ccString());
                    }
#if NET35
                    directory.Set(QuickTimeFileTypeDirectory.TagCompatibleBrands, string.Join(", ", compatibleBrands.ToArray()));
#else
                    directory.Set(QuickTimeFileTypeDirectory.TagCompatibleBrands, string.Join(", ", compatibleBrands));
#endif
                    directories.Add(directory);
                    break;
                }
                }
            }
            void UuidHandler(AtomCallbackArgs a)
            {
                switch (a.TypeString)
                {
                case "CMT1":
                {
                    var handler = new QuickTimeTiffHandler <ExifIfd0Directory>(directories);
                    var reader  = new IndexedSeekingReader(a.Stream, (int)a.Reader.Position);
                    TiffReader.ProcessTiff(reader, handler);
                    break;
                }

                case "CMT2":
                {
                    var handler = new QuickTimeTiffHandler <ExifSubIfdDirectory>(directories);
                    var reader  = new IndexedSeekingReader(a.Stream, (int)a.Reader.Position);
                    TiffReader.ProcessTiff(reader, handler);
                    break;
                }

                case "CMT3":
                {
                    var handler = new QuickTimeTiffHandler <CanonMakernoteDirectory>(directories);
                    var reader  = new IndexedSeekingReader(a.Stream, (int)a.Reader.Position);
                    TiffReader.ProcessTiff(reader, handler);
                    break;
                }

                case "CMT4":
                {
                    var handler = new QuickTimeTiffHandler <GpsDirectory>(directories);
                    var reader  = new IndexedSeekingReader(a.Stream, (int)a.Reader.Position);
                    TiffReader.ProcessTiff(reader, handler);
                    break;
                }
                }
            }
        /// <summary>
        /// Reads atom data from <paramref name="stream"/>, invoking <paramref name="handler"/> for each atom encountered.
        /// </summary>
        /// <param name="stream">The stream to read atoms from.</param>
        /// <param name="handler">A callback function to handle each atom.</param>
        /// <param name="stopByBytes">The maximum number of bytes to process before discontinuing.</param>
        public static void ProcessAtoms([NotNull] Stream stream, [NotNull] Action <AtomCallbackArgs> handler, long stopByBytes = -1)
        {
            var reader = new SequentialStreamReader(stream);

            var seriesStartPos = stream.Position;

            while (stopByBytes == -1 || stream.Position < seriesStartPos + stopByBytes)
            {
                var atomStartPos = stream.Position;

                try
                {
                    // Check if the end of the stream is closer then 8 bytes to current position (Length of the atom's data + atom type)
                    if (reader.IsCloserToEnd(8))
                    {
                        return;
                    }

                    // Length of the atom's data, in bytes, including size bytes
                    long atomSize = reader.GetUInt32();

                    // Typically four ASCII characters, but may be non-printable.
                    // By convention, lowercase 4CCs are reserved by Apple.
                    var atomType = reader.GetUInt32();

                    if (atomSize == 1)
                    {
                        // Check if the end of the stream is closer then 8 bytes
                        if (reader.IsCloserToEnd(8))
                        {
                            return;
                        }

                        // Size doesn't fit in 32 bits so read the 64 bit size here
                        atomSize = checked ((long)reader.GetUInt64());
                    }
                    else if (atomSize < 8)
                    {
                        // Atom should be at least 8 bytes long
                        return;
                    }

                    var args = new AtomCallbackArgs(atomType, atomSize, stream, atomStartPos, reader);

                    handler(args);

                    if (args.Cancel)
                    {
                        return;
                    }

                    if (atomSize == 0)
                    {
                        return;
                    }

                    var toSkip = atomStartPos + atomSize - stream.Position;

                    if (toSkip < 0)
                    {
                        throw new Exception("Handler moved stream beyond end of atom");
                    }

                    // To avoid exception handling we can check if needed number of bytes are available
                    if (!reader.IsCloserToEnd(toSkip))
                    {
                        reader.TrySkip(toSkip);
                    }
                }
                catch (IOException)
                {
                    // Exception trapping is used when stream doesn't support stream length method only
                    return;
                }
            }
        }
            void MoovHandler(AtomCallbackArgs a)
            {
                switch (a.TypeString)
                {
                case "mvhd":
                {
                    var directory = new QuickTimeMovieHeaderDirectory();
                    directory.Set(QuickTimeMovieHeaderDirectory.TagVersion, a.Reader.GetByte());
                    directory.Set(QuickTimeMovieHeaderDirectory.TagFlags, a.Reader.GetBytes(3));
                    directory.Set(QuickTimeMovieHeaderDirectory.TagCreated, _epoch.AddTicks(TimeSpan.TicksPerSecond * a.Reader.GetUInt32()));
                    directory.Set(QuickTimeMovieHeaderDirectory.TagModified, _epoch.AddTicks(TimeSpan.TicksPerSecond * a.Reader.GetUInt32()));
                    var timeScale = a.Reader.GetUInt32();
                    directory.Set(QuickTimeMovieHeaderDirectory.TagTimeScale, timeScale);
                    directory.Set(QuickTimeMovieHeaderDirectory.TagDuration, TimeSpan.FromSeconds(a.Reader.GetUInt32() / (double)timeScale));
                    directory.Set(QuickTimeMovieHeaderDirectory.TagPreferredRate, a.Reader.Get32BitFixedPoint());
                    directory.Set(QuickTimeMovieHeaderDirectory.TagPreferredVolume, a.Reader.Get16BitFixedPoint());
                    a.Reader.Skip(10);
                    directory.Set(QuickTimeMovieHeaderDirectory.TagMatrix, a.Reader.GetBytes(36));
                    directory.Set(QuickTimeMovieHeaderDirectory.TagPreviewTime, a.Reader.GetUInt32());
                    directory.Set(QuickTimeMovieHeaderDirectory.TagPreviewDuration, a.Reader.GetUInt32());
                    directory.Set(QuickTimeMovieHeaderDirectory.TagPosterTime, a.Reader.GetUInt32());
                    directory.Set(QuickTimeMovieHeaderDirectory.TagSelectionTime, a.Reader.GetUInt32());
                    directory.Set(QuickTimeMovieHeaderDirectory.TagSelectionDuration, a.Reader.GetUInt32());
                    directory.Set(QuickTimeMovieHeaderDirectory.TagCurrentTime, a.Reader.GetUInt32());
                    directory.Set(QuickTimeMovieHeaderDirectory.TagNextTrackId, a.Reader.GetUInt32());
                    directories.Add(directory);
                    break;
                }

                case "uuid":
                {
                    var CR3  = new byte[] { 0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0, 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, 0x48 };
                    var uuid = a.Reader.GetBytes(CR3.Length);
                    if (CR3.RegionEquals(0, CR3.Length, uuid))
                    {
                        QuickTimeReader.ProcessAtoms(stream, UuidHandler, a.BytesLeft);
                    }
                    break;
                }

                case "trak":
                {
                    QuickTimeReader.ProcessAtoms(stream, TrakHandler, a.BytesLeft);
                    break;
                }
//                    case "clip":
//                    {
//                        QuickTimeReader.ProcessAtoms(stream, clipHandler, a.BytesLeft);
//                        break;
//                    }
//                    case "prfl":
//                    {
//                        a.Reader.Skip(4L);
//                        var partId = a.Reader.GetUInt32();
//                        var featureCode = a.Reader.GetUInt32();
//                        var featureValue = string.Join(" ", a.Reader.GetBytes(4).Select(v => v.ToString("X2")).ToArray());
//                        Debug.WriteLine($"PartId={partId} FeatureCode={featureCode} FeatureValue={featureValue}");
//                        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;
                }
                }
            }
        /// <summary>
        /// Reads atom data from <paramref name="stream"/>, invoking <paramref name="handler"/> for each atom encountered.
        /// </summary>
        /// <param name="stream">The stream to read atoms from.</param>
        /// <param name="handler">A callback function to handle each atom.</param>
        /// <param name="stopByBytes">The maximum number of bytes to process before discontinuing.</param>
        public static void ProcessAtoms([NotNull] Stream stream, [NotNull] Action<AtomCallbackArgs> handler, long stopByBytes = -1)
        {
            var reader = new SequentialStreamReader(stream);

            var seriesStartPos = stream.Position;

            while (stopByBytes == -1 || stream.Position < seriesStartPos + stopByBytes)
            {
                var atomStartPos = stream.Position;

                // Length of the atom's data, in bytes, including size bytes
                long atomSize;
                try
                {
                    atomSize = reader.GetUInt32();
                }
                catch (IOException)
                {
                    // TODO don't use exception to trap end of stream
                    return;
                }

                // Typically four ASCII characters, but may be non-printable.
                // By convention, lowercase 4CCs are reserved by Apple.
                var atomType = reader.GetUInt32();

                if (atomSize == 1)
                {
                    // Size doesn't fit in 32 bits so read the 64 bit size here
                    // TODO GetUInt64 (i.e. unsigned)
                    atomSize = reader.GetInt64();
                }
                else
                {
                    Debug.Assert(atomSize >= 8, "Atom should be at least 8 bytes long");
                }

                var args = new AtomCallbackArgs(atomType, atomSize, stream, atomStartPos, reader);

                handler(args);

                if (args.Cancel)
                    return;

                if (atomSize == 0)
                    return;

                var toSkip = atomStartPos + atomSize - stream.Position;

                if (toSkip < 0)
                    throw new Exception("Handler moved stream beyond end of atom");

                reader.TrySkip(toSkip);
            }
        }
Пример #10
0
            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;
                }
                }
            }