/// <summary>
        /// Tries to add an entry created from the frame.
        /// </summary>
        /// <param name="managedFrame">The managed frame.</param>
        /// <returns>
        /// True if the index entry was created from the frame.
        /// False if the frame is of wrong picture type or if it already existed.
        /// </returns>
        public bool TryAdd(VideoFrame managedFrame)
        {
            // Update the Seek index
            if (managedFrame.PictureType != AVPictureType.AV_PICTURE_TYPE_I)
            {
                return(false);
            }

            // Create the seek entry
            var seekEntry = new VideoSeekIndexEntry(managedFrame);

            // Check if the entry already exists.
            if (Entries.BinarySearch(seekEntry, LookupComparer) >= 0)
            {
                return(false);
            }

            // Add the seek entry and ensure they are sorted.
            Entries.Add(seekEntry);
            Entries.Sort(LookupComparer);
            return(true);
        }
        /// <summary>
        /// Adds the monotonic entries up to a stream duration.
        /// </summary>
        /// <param name="streamDuration">Duration of the stream.</param>
        public void AddMonotonicEntries(TimeSpan streamDuration)
        {
            if (Entries.Count < 2)
            {
                return;
            }

            while (true)
            {
                var lastEntry = Entries[Entries.Count - 1];
                var prevEntry = Entries[Entries.Count - 2];

                var presentationTime = lastEntry.PresentationTime == ffmpeg.AV_NOPTS_VALUE ?
                                       ffmpeg.AV_NOPTS_VALUE :
                                       lastEntry.PresentationTime + (lastEntry.PresentationTime - prevEntry.PresentationTime);

                var decodingTime = lastEntry.DecodingTime == ffmpeg.AV_NOPTS_VALUE ?
                                   ffmpeg.AV_NOPTS_VALUE :
                                   lastEntry.DecodingTime + (lastEntry.DecodingTime - prevEntry.DecodingTime);

                var startTimeTicks = lastEntry.StartTime.Ticks + (lastEntry.StartTime.Ticks - prevEntry.StartTime.Ticks);

                var entry = new VideoSeekIndexEntry(
                    lastEntry.StreamIndex,
                    lastEntry.StreamTimeBase.num,
                    lastEntry.StreamTimeBase.den,
                    startTimeTicks,
                    presentationTime,
                    decodingTime);

                if (entry.StartTime.Ticks > streamDuration.Ticks)
                {
                    return;
                }

                Entries.Add(entry);
            }
        }
        /// <summary>
        /// Loads the specified stream in the CSV-like UTF8 format it was written by the <see cref="Save(Stream)"/> method.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>The loaded index from the specified stream.</returns>
        public static VideoSeekIndex Load(Stream stream)
        {
            var separator  = new[] { ',' };
            var trimQuotes = new[] { '"' };
            var result     = new VideoSeekIndex(null, -1);

            using (var reader = new StreamReader(stream, Encoding.UTF8, true))
            {
                var state = 0;

                while (reader.EndOfStream == false)
                {
                    var line = reader.ReadLine()?.Trim() ?? string.Empty;
                    if (state == 0 && line == SectionHeaderText)
                    {
                        state = 1;
                        continue;
                    }

                    if (state == 1 && line == SectionHeaderFields)
                    {
                        state = 2;
                        continue;
                    }

                    if (state == 2 && !string.IsNullOrWhiteSpace(line))
                    {
                        var parts = line.Split(separator, 2);
                        if (parts.Length >= 2)
                        {
                            if (int.TryParse(parts[0], out var index))
                            {
                                result.StreamIndex = index;
                            }

                            result.MediaSource = parts[1]
                                                 .Trim(trimQuotes)
                                                 .ReplaceOrdinal("\"\"", "\"");
                        }

                        state = 3;
                    }

                    if (state == 3 && line == SectionDataText)
                    {
                        state = 4;
                        continue;
                    }

                    if (state == 4 && line == SectionDataFields)
                    {
                        state = 5;
                        continue;
                    }

                    if (state == 5 && !string.IsNullOrWhiteSpace(line))
                    {
                        if (VideoSeekIndexEntry.FromCsvString(line) is VideoSeekIndexEntry entry)
                        {
                            result.Entries.Add(entry);
                        }
                    }
                }
            }

            return(result);
        }