using System; using Jellyfin.MediaEncoding.Keyframes.Matroska.Models; using NEbml.Core; namespace Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions { /// /// Extension methods for the class. /// internal static class EbmlReaderExtensions { /// /// Traverses the current container to find the element with identifier. /// /// An instance of . /// The element identifier. /// A value indicating whether the element was found. internal static bool FindElement(this EbmlReader reader, ulong identifier) { while (reader.ReadNext()) { if (reader.ElementId.EncodedValue == identifier) { return true; } } return false; } /// /// Reads the current position in the file as an unsigned integer converted from binary. /// /// An instance of . /// The unsigned integer. internal static uint ReadUIntFromBinary(this EbmlReader reader) { var buffer = new byte[4]; reader.ReadBinary(buffer, 0, 4); if (BitConverter.IsLittleEndian) { Array.Reverse(buffer); } return BitConverter.ToUInt32(buffer); } /// /// Reads from the start of the file to retrieve the SeekHead segment. /// /// An instance of . /// Instance of internal static SeekHead ReadSeekHead(this EbmlReader reader) { reader = reader ?? throw new ArgumentNullException(nameof(reader)); if (reader.ElementPosition != 0) { throw new InvalidOperationException("File position must be at 0"); } // Skip the header if (!reader.FindElement(MatroskaConstants.SegmentContainer)) { throw new InvalidOperationException("Expected a segment container"); } reader.EnterContainer(); long? tracksPosition = null; long? cuesPosition = null; long? infoPosition = null; // The first element should be a SeekHead otherwise we'll have to search manually if (!reader.FindElement(MatroskaConstants.SeekHead)) { throw new InvalidOperationException("Expected a SeekHead"); } reader.EnterContainer(); while (reader.FindElement(MatroskaConstants.Seek)) { reader.EnterContainer(); reader.ReadNext(); var type = (ulong)reader.ReadUIntFromBinary(); switch (type) { case MatroskaConstants.Tracks: reader.ReadNext(); tracksPosition = (long)reader.ReadUInt(); break; case MatroskaConstants.Cues: reader.ReadNext(); cuesPosition = (long)reader.ReadUInt(); break; case MatroskaConstants.Info: reader.ReadNext(); infoPosition = (long)reader.ReadUInt(); break; } reader.LeaveContainer(); if (tracksPosition.HasValue && cuesPosition.HasValue && infoPosition.HasValue) { break; } } reader.LeaveContainer(); if (!tracksPosition.HasValue || !cuesPosition.HasValue || !infoPosition.HasValue) { throw new InvalidOperationException("SeekHead is missing or does not contain Info, Tracks and Cues positions"); } return new SeekHead(infoPosition.Value, tracksPosition.Value, cuesPosition.Value); } /// /// Reads from SegmentContainer to retrieve the Info segment. /// /// An instance of . /// Instance of internal static Info ReadInfo(this EbmlReader reader, long position) { reader.ReadAt(position); double? duration = null; reader.EnterContainer(); // Mandatory element reader.FindElement(MatroskaConstants.TimestampScale); var timestampScale = reader.ReadUInt(); if (reader.FindElement(MatroskaConstants.Duration)) { duration = reader.ReadFloat(); } reader.LeaveContainer(); return new Info((long)timestampScale, duration); } /// /// Enters the Tracks segment and reads all tracks to find the specified type. /// /// Instance of . /// The relative position of the tracks segment. /// The track type identifier. /// The first track number with the specified type. /// Stream type is not found. internal static ulong FindFirstTrackNumberByType(this EbmlReader reader, long tracksPosition, ulong type) { reader.ReadAt(tracksPosition); reader.EnterContainer(); while (reader.FindElement(MatroskaConstants.TrackEntry)) { reader.EnterContainer(); // Mandatory element reader.FindElement(MatroskaConstants.TrackNumber); var trackNumber = reader.ReadUInt(); // Mandatory element reader.FindElement(MatroskaConstants.TrackType); var trackType = reader.ReadUInt(); reader.LeaveContainer(); if (trackType == MatroskaConstants.TrackTypeVideo) { reader.LeaveContainer(); return trackNumber; } } reader.LeaveContainer(); throw new InvalidOperationException($"No stream with type {type} found"); } } }