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");
}
}
}