aboutsummaryrefslogtreecommitdiff
path: root/src/Jellyfin.MediaEncoding.Keyframes/Matroska
diff options
context:
space:
mode:
Diffstat (limited to 'src/Jellyfin.MediaEncoding.Keyframes/Matroska')
-rw-r--r--src/Jellyfin.MediaEncoding.Keyframes/Matroska/Extensions/EbmlReaderExtensions.cs257
-rw-r--r--src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaConstants.cs47
-rw-r--r--src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaKeyframeExtractor.cs101
-rw-r--r--src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/Info.cs43
-rw-r--r--src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/SeekHead.cs55
5 files changed, 249 insertions, 254 deletions
diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Extensions/EbmlReaderExtensions.cs b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Extensions/EbmlReaderExtensions.cs
index 75d5aafe0..e068cac84 100644
--- a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Extensions/EbmlReaderExtensions.cs
+++ b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Extensions/EbmlReaderExtensions.cs
@@ -3,176 +3,175 @@ using System.Buffers.Binary;
using Jellyfin.MediaEncoding.Keyframes.Matroska.Models;
using NEbml.Core;
-namespace Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions
+namespace Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions;
+
+/// <summary>
+/// Extension methods for the <see cref="EbmlReader"/> class.
+/// </summary>
+internal static class EbmlReaderExtensions
{
/// <summary>
- /// Extension methods for the <see cref="EbmlReader"/> class.
+ /// Traverses the current container to find the element with <paramref name="identifier"/> identifier.
/// </summary>
- internal static class EbmlReaderExtensions
+ /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param>
+ /// <param name="identifier">The element identifier.</param>
+ /// <returns>A value indicating whether the element was found.</returns>
+ internal static bool FindElement(this EbmlReader reader, ulong identifier)
{
- /// <summary>
- /// Traverses the current container to find the element with <paramref name="identifier"/> identifier.
- /// </summary>
- /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param>
- /// <param name="identifier">The element identifier.</param>
- /// <returns>A value indicating whether the element was found.</returns>
- internal static bool FindElement(this EbmlReader reader, ulong identifier)
+ while (reader.ReadNext())
{
- while (reader.ReadNext())
+ if (reader.ElementId.EncodedValue == identifier)
{
- if (reader.ElementId.EncodedValue == identifier)
- {
- return true;
- }
+ return true;
}
-
- return false;
}
- /// <summary>
- /// Reads the current position in the file as an unsigned integer converted from binary.
- /// </summary>
- /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param>
- /// <returns>The unsigned integer.</returns>
- internal static uint ReadUIntFromBinary(this EbmlReader reader)
+ return false;
+ }
+
+ /// <summary>
+ /// Reads the current position in the file as an unsigned integer converted from binary.
+ /// </summary>
+ /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param>
+ /// <returns>The unsigned integer.</returns>
+ internal static uint ReadUIntFromBinary(this EbmlReader reader)
+ {
+ var buffer = new byte[4];
+ reader.ReadBinary(buffer, 0, 4);
+ return BinaryPrimitives.ReadUInt32BigEndian(buffer);
+ }
+
+ /// <summary>
+ /// Reads from the start of the file to retrieve the SeekHead segment.
+ /// </summary>
+ /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param>
+ /// <returns>Instance of <see cref="SeekHead"/>.</returns>
+ internal static SeekHead ReadSeekHead(this EbmlReader reader)
+ {
+ reader = reader ?? throw new ArgumentNullException(nameof(reader));
+
+ if (reader.ElementPosition != 0)
{
- var buffer = new byte[4];
- reader.ReadBinary(buffer, 0, 4);
- return BinaryPrimitives.ReadUInt32BigEndian(buffer);
+ throw new InvalidOperationException("File position must be at 0");
}
- /// <summary>
- /// Reads from the start of the file to retrieve the SeekHead segment.
- /// </summary>
- /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param>
- /// <returns>Instance of <see cref="SeekHead"/>.</returns>
- internal static SeekHead ReadSeekHead(this EbmlReader reader)
+ // Skip the header
+ if (!reader.FindElement(MatroskaConstants.SegmentContainer))
{
- 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");
- }
+ throw new InvalidOperationException("Expected a segment container");
+ }
- reader.EnterContainer();
+ 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");
- }
+ 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();
- while (reader.FindElement(MatroskaConstants.Seek))
+ reader.ReadNext();
+ var type = (ulong)reader.ReadUIntFromBinary();
+ switch (type)
{
- 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)
- {
+ 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)
+ if (tracksPosition.HasValue && cuesPosition.HasValue && infoPosition.HasValue)
{
- throw new InvalidOperationException("SeekHead is missing or does not contain Info, Tracks and Cues positions");
+ break;
}
-
- return new SeekHead(infoPosition.Value, tracksPosition.Value, cuesPosition.Value);
}
- /// <summary>
- /// Reads from SegmentContainer to retrieve the Info segment.
- /// </summary>
- /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param>
- /// <param name="position">The position of the info segment relative to the Segment container.</param>
- /// <returns>Instance of <see cref="Info"/>.</returns>
- internal static Info ReadInfo(this EbmlReader reader, long position)
+ reader.LeaveContainer();
+
+ if (!tracksPosition.HasValue || !cuesPosition.HasValue || !infoPosition.HasValue)
{
- reader.ReadAt(position);
+ throw new InvalidOperationException("SeekHead is missing or does not contain Info, Tracks and Cues positions");
+ }
- double? duration = null;
- reader.EnterContainer();
- // Mandatory element
- reader.FindElement(MatroskaConstants.TimestampScale);
- var timestampScale = reader.ReadUInt();
+ return new SeekHead(infoPosition.Value, tracksPosition.Value, cuesPosition.Value);
+ }
- if (reader.FindElement(MatroskaConstants.Duration))
- {
- duration = reader.ReadFloat();
- }
+ /// <summary>
+ /// Reads from SegmentContainer to retrieve the Info segment.
+ /// </summary>
+ /// <param name="reader">An instance of <see cref="EbmlReader"/>.</param>
+ /// <param name="position">The position of the info segment relative to the Segment container.</param>
+ /// <returns>Instance of <see cref="Info"/>.</returns>
+ internal static Info ReadInfo(this EbmlReader reader, long position)
+ {
+ reader.ReadAt(position);
- reader.LeaveContainer();
+ double? duration = null;
+ reader.EnterContainer();
+ // Mandatory element
+ reader.FindElement(MatroskaConstants.TimestampScale);
+ var timestampScale = reader.ReadUInt();
- return new Info((long)timestampScale, duration);
+ if (reader.FindElement(MatroskaConstants.Duration))
+ {
+ duration = reader.ReadFloat();
}
- /// <summary>
- /// Enters the Tracks segment and reads all tracks to find the specified type.
- /// </summary>
- /// <param name="reader">Instance of <see cref="EbmlReader"/>.</param>
- /// <param name="tracksPosition">The relative position of the tracks segment.</param>
- /// <param name="type">The track type identifier.</param>
- /// <returns>The first track number with the specified type.</returns>
- /// <exception cref="InvalidOperationException">Stream type is not found.</exception>
- internal static ulong FindFirstTrackNumberByType(this EbmlReader reader, long tracksPosition, ulong type)
- {
- reader.ReadAt(tracksPosition);
+ reader.LeaveContainer();
+ return new Info((long)timestampScale, duration);
+ }
+
+ /// <summary>
+ /// Enters the Tracks segment and reads all tracks to find the specified type.
+ /// </summary>
+ /// <param name="reader">Instance of <see cref="EbmlReader"/>.</param>
+ /// <param name="tracksPosition">The relative position of the tracks segment.</param>
+ /// <param name="type">The track type identifier.</param>
+ /// <returns>The first track number with the specified type.</returns>
+ /// <exception cref="InvalidOperationException">Stream type is not found.</exception>
+ internal static ulong FindFirstTrackNumberByType(this EbmlReader reader, long tracksPosition, ulong type)
+ {
+ reader.ReadAt(tracksPosition);
+
+ reader.EnterContainer();
+ while (reader.FindElement(MatroskaConstants.TrackEntry))
+ {
reader.EnterContainer();
- while (reader.FindElement(MatroskaConstants.TrackEntry))
- {
- reader.EnterContainer();
- // Mandatory element
- reader.FindElement(MatroskaConstants.TrackNumber);
- var trackNumber = reader.ReadUInt();
+ // Mandatory element
+ reader.FindElement(MatroskaConstants.TrackNumber);
+ var trackNumber = reader.ReadUInt();
- // Mandatory element
- reader.FindElement(MatroskaConstants.TrackType);
- var trackType = reader.ReadUInt();
+ // Mandatory element
+ reader.FindElement(MatroskaConstants.TrackType);
+ var trackType = reader.ReadUInt();
+ reader.LeaveContainer();
+ if (trackType == MatroskaConstants.TrackTypeVideo)
+ {
reader.LeaveContainer();
- if (trackType == MatroskaConstants.TrackTypeVideo)
- {
- reader.LeaveContainer();
- return trackNumber;
- }
+ return trackNumber;
}
+ }
- reader.LeaveContainer();
+ reader.LeaveContainer();
- throw new InvalidOperationException($"No stream with type {type} found");
- }
+ throw new InvalidOperationException($"No stream with type {type} found");
}
}
diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaConstants.cs b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaConstants.cs
index d18418d45..0d5c2f34f 100644
--- a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaConstants.cs
+++ b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaConstants.cs
@@ -1,31 +1,30 @@
-namespace Jellyfin.MediaEncoding.Keyframes.Matroska
+namespace Jellyfin.MediaEncoding.Keyframes.Matroska;
+
+/// <summary>
+/// Constants for the Matroska identifiers.
+/// </summary>
+public static class MatroskaConstants
{
- /// <summary>
- /// Constants for the Matroska identifiers.
- /// </summary>
- public static class MatroskaConstants
- {
- internal const ulong SegmentContainer = 0x18538067;
+ internal const ulong SegmentContainer = 0x18538067;
- internal const ulong SeekHead = 0x114D9B74;
- internal const ulong Seek = 0x4DBB;
+ internal const ulong SeekHead = 0x114D9B74;
+ internal const ulong Seek = 0x4DBB;
- internal const ulong Info = 0x1549A966;
- internal const ulong TimestampScale = 0x2AD7B1;
- internal const ulong Duration = 0x4489;
+ internal const ulong Info = 0x1549A966;
+ internal const ulong TimestampScale = 0x2AD7B1;
+ internal const ulong Duration = 0x4489;
- internal const ulong Tracks = 0x1654AE6B;
- internal const ulong TrackEntry = 0xAE;
- internal const ulong TrackNumber = 0xD7;
- internal const ulong TrackType = 0x83;
+ internal const ulong Tracks = 0x1654AE6B;
+ internal const ulong TrackEntry = 0xAE;
+ internal const ulong TrackNumber = 0xD7;
+ internal const ulong TrackType = 0x83;
- internal const ulong TrackTypeVideo = 0x1;
- internal const ulong TrackTypeSubtitle = 0x11;
+ internal const ulong TrackTypeVideo = 0x1;
+ internal const ulong TrackTypeSubtitle = 0x11;
- internal const ulong Cues = 0x1C53BB6B;
- internal const ulong CueTime = 0xB3;
- internal const ulong CuePoint = 0xBB;
- internal const ulong CueTrackPositions = 0xB7;
- internal const ulong CuePointTrackNumber = 0xF7;
- }
+ internal const ulong Cues = 0x1C53BB6B;
+ internal const ulong CueTime = 0xB3;
+ internal const ulong CuePoint = 0xBB;
+ internal const ulong CueTrackPositions = 0xB7;
+ internal const ulong CuePointTrackNumber = 0xF7;
}
diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaKeyframeExtractor.cs
index 6a8a55643..8bb1ff00d 100644
--- a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaKeyframeExtractor.cs
+++ b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaKeyframeExtractor.cs
@@ -4,73 +4,72 @@ using System.IO;
using Jellyfin.MediaEncoding.Keyframes.Matroska.Extensions;
using NEbml.Core;
-namespace Jellyfin.MediaEncoding.Keyframes.Matroska
+namespace Jellyfin.MediaEncoding.Keyframes.Matroska;
+
+/// <summary>
+/// The keyframe extractor for the matroska container.
+/// </summary>
+public static class MatroskaKeyframeExtractor
{
/// <summary>
- /// The keyframe extractor for the matroska container.
+ /// Extracts the keyframes in ticks (scaled using the container timestamp scale) from the matroska container.
/// </summary>
- public static class MatroskaKeyframeExtractor
+ /// <param name="filePath">The file path.</param>
+ /// <returns>An instance of <see cref="KeyframeData"/>.</returns>
+ public static KeyframeData GetKeyframeData(string filePath)
{
- /// <summary>
- /// Extracts the keyframes in ticks (scaled using the container timestamp scale) from the matroska container.
- /// </summary>
- /// <param name="filePath">The file path.</param>
- /// <returns>An instance of <see cref="KeyframeData"/>.</returns>
- public static KeyframeData GetKeyframeData(string filePath)
- {
- using var stream = File.OpenRead(filePath);
- using var reader = new EbmlReader(stream);
+ using var stream = File.OpenRead(filePath);
+ using var reader = new EbmlReader(stream);
+
+ var seekHead = reader.ReadSeekHead();
+ var info = reader.ReadInfo(seekHead.InfoPosition);
+ var videoTrackNumber = reader.FindFirstTrackNumberByType(seekHead.TracksPosition, MatroskaConstants.TrackTypeVideo);
- var seekHead = reader.ReadSeekHead();
- var info = reader.ReadInfo(seekHead.InfoPosition);
- var videoTrackNumber = reader.FindFirstTrackNumberByType(seekHead.TracksPosition, MatroskaConstants.TrackTypeVideo);
+ var keyframes = new List<long>();
+ reader.ReadAt(seekHead.CuesPosition);
+ reader.EnterContainer();
- var keyframes = new List<long>();
- reader.ReadAt(seekHead.CuesPosition);
+ while (reader.FindElement(MatroskaConstants.CuePoint))
+ {
reader.EnterContainer();
+ ulong? trackNumber = null;
+ // Mandatory element
+ reader.FindElement(MatroskaConstants.CueTime);
+ var cueTime = reader.ReadUInt();
- while (reader.FindElement(MatroskaConstants.CuePoint))
+ // Mandatory element
+ reader.FindElement(MatroskaConstants.CueTrackPositions);
+ reader.EnterContainer();
+ if (reader.FindElement(MatroskaConstants.CuePointTrackNumber))
{
- reader.EnterContainer();
- ulong? trackNumber = null;
- // Mandatory element
- reader.FindElement(MatroskaConstants.CueTime);
- var cueTime = reader.ReadUInt();
-
- // Mandatory element
- reader.FindElement(MatroskaConstants.CueTrackPositions);
- reader.EnterContainer();
- if (reader.FindElement(MatroskaConstants.CuePointTrackNumber))
- {
- trackNumber = reader.ReadUInt();
- }
-
- reader.LeaveContainer();
+ trackNumber = reader.ReadUInt();
+ }
- if (trackNumber == videoTrackNumber)
- {
- keyframes.Add(ScaleToTicks(cueTime, info.TimestampScale));
- }
+ reader.LeaveContainer();
- reader.LeaveContainer();
+ if (trackNumber == videoTrackNumber)
+ {
+ keyframes.Add(ScaleToTicks(cueTime, info.TimestampScale));
}
reader.LeaveContainer();
-
- var result = new KeyframeData(ScaleToTicks(info.Duration ?? 0, info.TimestampScale), keyframes);
- return result;
}
- private static long ScaleToTicks(ulong unscaledValue, long timestampScale)
- {
- // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns
- return (long)unscaledValue * timestampScale / 100;
- }
+ reader.LeaveContainer();
- private static long ScaleToTicks(double unscaledValue, long timestampScale)
- {
- // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns
- return Convert.ToInt64(unscaledValue * timestampScale / 100);
- }
+ var result = new KeyframeData(ScaleToTicks(info.Duration ?? 0, info.TimestampScale), keyframes);
+ return result;
+ }
+
+ private static long ScaleToTicks(ulong unscaledValue, long timestampScale)
+ {
+ // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns
+ return (long)unscaledValue * timestampScale / 100;
+ }
+
+ private static long ScaleToTicks(double unscaledValue, long timestampScale)
+ {
+ // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns
+ return Convert.ToInt64(unscaledValue * timestampScale / 100);
}
}
diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/Info.cs b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/Info.cs
index 02c6741ec..415d6da00 100644
--- a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/Info.cs
+++ b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/Info.cs
@@ -1,29 +1,28 @@
-namespace Jellyfin.MediaEncoding.Keyframes.Matroska.Models
+namespace Jellyfin.MediaEncoding.Keyframes.Matroska.Models;
+
+/// <summary>
+/// The matroska Info segment.
+/// </summary>
+internal class Info
{
/// <summary>
- /// The matroska Info segment.
+ /// Initializes a new instance of the <see cref="Info"/> class.
/// </summary>
- internal class Info
+ /// <param name="timestampScale">The timestamp scale in nanoseconds.</param>
+ /// <param name="duration">The duration of the entire file.</param>
+ public Info(long timestampScale, double? duration)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="Info"/> class.
- /// </summary>
- /// <param name="timestampScale">The timestamp scale in nanoseconds.</param>
- /// <param name="duration">The duration of the entire file.</param>
- public Info(long timestampScale, double? duration)
- {
- TimestampScale = timestampScale;
- Duration = duration;
- }
+ TimestampScale = timestampScale;
+ Duration = duration;
+ }
- /// <summary>
- /// Gets the timestamp scale in nanoseconds.
- /// </summary>
- public long TimestampScale { get; }
+ /// <summary>
+ /// Gets the timestamp scale in nanoseconds.
+ /// </summary>
+ public long TimestampScale { get; }
- /// <summary>
- /// Gets the total duration of the file.
- /// </summary>
- public double? Duration { get; }
- }
+ /// <summary>
+ /// Gets the total duration of the file.
+ /// </summary>
+ public double? Duration { get; }
}
diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/SeekHead.cs b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/SeekHead.cs
index d9e346c03..95e4fd882 100644
--- a/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/SeekHead.cs
+++ b/src/Jellyfin.MediaEncoding.Keyframes/Matroska/Models/SeekHead.cs
@@ -1,36 +1,35 @@
-namespace Jellyfin.MediaEncoding.Keyframes.Matroska.Models
+namespace Jellyfin.MediaEncoding.Keyframes.Matroska.Models;
+
+/// <summary>
+/// The matroska SeekHead segment. All positions are relative to the Segment container.
+/// </summary>
+internal class SeekHead
{
/// <summary>
- /// The matroska SeekHead segment. All positions are relative to the Segment container.
+ /// Initializes a new instance of the <see cref="SeekHead"/> class.
/// </summary>
- internal class SeekHead
+ /// <param name="infoPosition">The relative file position of the info segment.</param>
+ /// <param name="tracksPosition">The relative file position of the tracks segment.</param>
+ /// <param name="cuesPosition">The relative file position of the cues segment.</param>
+ public SeekHead(long infoPosition, long tracksPosition, long cuesPosition)
{
- /// <summary>
- /// Initializes a new instance of the <see cref="SeekHead"/> class.
- /// </summary>
- /// <param name="infoPosition">The relative file position of the info segment.</param>
- /// <param name="tracksPosition">The relative file position of the tracks segment.</param>
- /// <param name="cuesPosition">The relative file position of the cues segment.</param>
- public SeekHead(long infoPosition, long tracksPosition, long cuesPosition)
- {
- InfoPosition = infoPosition;
- TracksPosition = tracksPosition;
- CuesPosition = cuesPosition;
- }
+ InfoPosition = infoPosition;
+ TracksPosition = tracksPosition;
+ CuesPosition = cuesPosition;
+ }
- /// <summary>
- /// Gets relative file position of the info segment.
- /// </summary>
- public long InfoPosition { get; }
+ /// <summary>
+ /// Gets relative file position of the info segment.
+ /// </summary>
+ public long InfoPosition { get; }
- /// <summary>
- /// Gets the relative file position of the tracks segment.
- /// </summary>
- public long TracksPosition { get; }
+ /// <summary>
+ /// Gets the relative file position of the tracks segment.
+ /// </summary>
+ public long TracksPosition { get; }
- /// <summary>
- /// Gets the relative file position of the cues segment.
- /// </summary>
- public long CuesPosition { get; }
- }
+ /// <summary>
+ /// Gets the relative file position of the cues segment.
+ /// </summary>
+ public long CuesPosition { get; }
}