Add project files.

alphons <alphons@heijden.com> 16 Jan 2026, 17:27
e3e99de22f5938897f7057362f02d552ce943604
80 files changed
  • Matroska Solution.sln
  • Matroska/Attributes/MatroskaElementDescriptorAttribute.cs
  • Matroska/Enumerations/Lacing.cs
  • Matroska/Enumerations/TrackType.cs
  • Matroska/Extensions/EbmlReaderExtensions.cs
  • Matroska/Extensions/VIntExtensions.cs
  • Matroska/Matroska.csproj
  • Matroska/MatroskaSerializer.cs
  • Matroska/MatroskaSpecification.cs
  • Matroska/MatroskaSpecification.tt
  • Matroska/Models/BaseModel.cs
  • Matroska/Models/Block.cs
  • Matroska/Models/BlockAdditional.cs
  • Matroska/Models/BlockVirtual.cs
  • Matroska/Models/EncryptedBlock.cs
  • Matroska/Models/IParseRawBinary.cs
  • Matroska/Models/MatroskaDocument.cs
  • Matroska/Models/MatroskaModels.cs
  • Matroska/Models/MatroskaModels.tt
  • Matroska/Models/SimpleBlock.cs
  • Matroska/Models/SimpleTag.cs
  • Matroska/NEbml/Core/DTDBase.cs
  • Matroska/NEbml/Core/EbmlDataFormatException.cs
  • Matroska/NEbml/Core/EbmlReader.cs
  • Matroska/NEbml/Core/EbmlWriter.cs
  • Matroska/NEbml/Core/ElementDescriptor.cs
  • Matroska/NEbml/Core/ElementType.cs
  • Matroska/NEbml/Core/MasterBlockWriter.cs
  • Matroska/NEbml/Core/StreamExtensions.cs
  • Matroska/NEbml/Core/Union.cs
  • Matroska/NEbml/Core/VInt.cs
  • Matroska/NEbml/Readme.md
  • Matroska/ebml.xml
  • Matroska/ebml_matroska.xml
  • MatroskaDemuxer/MatroskaDemuxer.csproj
  • MatroskaDemuxer/Program.cs
  • MediaContainers.Matroska/EBML/EBMLDocType.cs
  • MediaContainers.Matroska/EBML/EBMLDocTypeLookup.cs
  • MediaContainers.Matroska/EBML/EBMLDocumentReader.cs
  • MediaContainers.Matroska/EBML/EBMLDocumentWriter.cs
  • MediaContainers.Matroska/EBML/EBMLElement.cs
  • MediaContainers.Matroska/EBML/EBMLElementDefiniton.cs
  • MediaContainers.Matroska/EBML/EBMLElementStruct.cs
  • MediaContainers.Matroska/EBML/EBMLElementType.cs
  • MediaContainers.Matroska/EBML/EBMLHeader.cs
  • MediaContainers.Matroska/EBML/EBMLReader.cs
  • MediaContainers.Matroska/EBML/EBMLVInt.cs
  • MediaContainers.Matroska/EBML/EBMLWriter.cs
  • MediaContainers.Matroska/Matroska/MatroskaCuePoint.cs
  • MediaContainers.Matroska/Matroska/MatroskaCues.cs
  • MediaContainers.Matroska/Matroska/MatroskaFrame.cs
  • MediaContainers.Matroska/Matroska/MatroskaInfo.cs
  • MediaContainers.Matroska/Matroska/MatroskaReader.cs
  • MediaContainers.Matroska/Matroska/MatroskaSeekHead.cs
  • MediaContainers.Matroska/Matroska/MatroskaSimpleTag.cs
  • MediaContainers.Matroska/Matroska/MatroskaSpecification.cs
  • MediaContainers.Matroska/Matroska/MatroskaTag.cs
  • MediaContainers.Matroska/Matroska/MatroskaTags.cs
  • MediaContainers.Matroska/Matroska/MatroskaTrackEntry.cs
  • MediaContainers.Matroska/Matroska/MatroskaTrackType.cs
  • MediaContainers.Matroska/Matroska/MatroskaTracks.cs
  • MediaContainers.Matroska/Matroska/MatroskaWriter.cs
  • MediaContainers.Matroska/Matroska/XiphLacingSize.cs
  • MediaContainers.Matroska/MediaContainers.Matroska.csproj
  • Xtremegaida.DataStructures/DataBufferCache.cs
  • Xtremegaida.DataStructures/DataQueue/DataQueue.cs
  • Xtremegaida.DataStructures/DataQueue/IDataQueue.cs
  • Xtremegaida.DataStructures/DataQueue/Read/DataQueueLimitedReader.cs
  • Xtremegaida.DataStructures/DataQueue/Read/DataQueueLimitedReaderMutable.cs
  • Xtremegaida.DataStructures/DataQueue/Read/DataQueueMemoryReader.cs
  • Xtremegaida.DataStructures/DataQueue/Read/DataQueueMemoryReaderMutable.cs
  • Xtremegaida.DataStructures/DataQueue/Read/DataQueueReadStream.cs
  • Xtremegaida.DataStructures/DataQueue/Read/DataQueueStreamReader.cs
  • Xtremegaida.DataStructures/DataQueue/Read/IDataQueueReader.cs
  • Xtremegaida.DataStructures/DataQueue/Write/DataQueueStreamWriter.cs
  • Xtremegaida.DataStructures/DataQueue/Write/DataQueueWriteStream.cs
  • Xtremegaida.DataStructures/DataQueue/Write/IDataQueueWriter.cs
  • Xtremegaida.DataStructures/EventWaitLight.cs
  • Xtremegaida.DataStructures/PolyfillTaskExtensions.cs
  • Xtremegaida.DataStructures/Xtremegaida.DataStructures.csproj
diff --git a/Matroska Solution.sln b/Matroska Solution.sln
new file mode 100644
index 0000000..bd1dec0
--- /dev/null
+++ b/Matroska Solution.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 18
+VisualStudioVersion = 18.2.11408.102
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaContainers.Matroska", "MediaContainers.Matroska\MediaContainers.Matroska.csproj", "{61F51DE1-FE73-A298-B429-BF5B3D211546}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xtremegaida.DataStructures", "Xtremegaida.DataStructures\Xtremegaida.DataStructures.csproj", "{1C9B14BC-4BB4-25CE-DAEF-E4B6B336FEA1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatroskaDemuxer", "MatroskaDemuxer\MatroskaDemuxer.csproj", "{5538F9BB-BA24-9620-6EF8-CAE335EE3AB1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Matroska", "Matroska\Matroska.csproj", "{4A92ABA7-E734-80F9-0FFD-91C6E3A6BE20}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {61F51DE1-FE73-A298-B429-BF5B3D211546}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {61F51DE1-FE73-A298-B429-BF5B3D211546}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {61F51DE1-FE73-A298-B429-BF5B3D211546}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {61F51DE1-FE73-A298-B429-BF5B3D211546}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1C9B14BC-4BB4-25CE-DAEF-E4B6B336FEA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1C9B14BC-4BB4-25CE-DAEF-E4B6B336FEA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1C9B14BC-4BB4-25CE-DAEF-E4B6B336FEA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1C9B14BC-4BB4-25CE-DAEF-E4B6B336FEA1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5538F9BB-BA24-9620-6EF8-CAE335EE3AB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5538F9BB-BA24-9620-6EF8-CAE335EE3AB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5538F9BB-BA24-9620-6EF8-CAE335EE3AB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5538F9BB-BA24-9620-6EF8-CAE335EE3AB1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4A92ABA7-E734-80F9-0FFD-91C6E3A6BE20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4A92ABA7-E734-80F9-0FFD-91C6E3A6BE20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4A92ABA7-E734-80F9-0FFD-91C6E3A6BE20}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4A92ABA7-E734-80F9-0FFD-91C6E3A6BE20}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {64034C54-9707-4662-BDA6-B14E1539311F}
+ EndGlobalSection
+EndGlobal
diff --git a/Matroska/Attributes/MatroskaElementDescriptorAttribute.cs b/Matroska/Attributes/MatroskaElementDescriptorAttribute.cs
new file mode 100644
index 0000000..d0d72db
--- /dev/null
+++ b/Matroska/Attributes/MatroskaElementDescriptorAttribute.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Matroska.Attributes;
+
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
+public class MatroskaElementDescriptorAttribute : Attribute
+{
+ public ulong Identifier { get; }
+
+ public Type? ElementType { get; }
+
+ public MatroskaElementDescriptorAttribute(ulong identifier, Type? type = null)
+ {
+ Identifier = identifier;
+ ElementType = type;
+ }
+}
\ No newline at end of file
diff --git a/Matroska/Enumerations/Lacing.cs b/Matroska/Enumerations/Lacing.cs
new file mode 100644
index 0000000..f03503a
--- /dev/null
+++ b/Matroska/Enumerations/Lacing.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Matroska.Enumerations;
+
+[Flags]
+public enum Lacing : byte
+{
+ No = 0b0000000,
+ Xiph = 0b0000010,
+ EBML = 0b0000110,
+ FixedSize = 0b0000100
+}
\ No newline at end of file
diff --git a/Matroska/Enumerations/TrackType.cs b/Matroska/Enumerations/TrackType.cs
new file mode 100644
index 0000000..29109bd
--- /dev/null
+++ b/Matroska/Enumerations/TrackType.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Matroska.Enumerations;
+
+[Flags]
+public enum TrackType : ulong
+{
+ Video = 1,
+ Audio = 2,
+ Complex = 3,
+ Logo = 16,
+ SubTitle = 17,
+ Buttons = 18,
+ Control = 32,
+ MetaData = 33
+}
\ No newline at end of file
diff --git a/Matroska/Extensions/EbmlReaderExtensions.cs b/Matroska/Extensions/EbmlReaderExtensions.cs
new file mode 100644
index 0000000..02393dd
--- /dev/null
+++ b/Matroska/Extensions/EbmlReaderExtensions.cs
@@ -0,0 +1,59 @@
+using System.Globalization;
+using NEbml.Core;
+
+namespace Matroska.Extensions;
+
+internal static class EbmlReaderExtensions
+{
+ public static bool LocateElement(this EbmlReader reader, ElementDescriptor descriptor)
+ {
+ return reader.LocateElement(descriptor.Identifier.EncodedValue);
+ }
+
+ public static bool LocateElement(this EbmlReader reader, ulong identifier)
+ {
+ while (reader.ReadNext())
+ {
+ if (reader.ElementId == identifier)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool IsKnownMasterElement(this EbmlReader reader)
+ {
+ return MatroskaSpecification.ElementDescriptors.TryGetValue(reader.ElementId, out var descriptor) && descriptor.Type == ElementType.MasterElement;
+ }
+
+ public static string GetName(this EbmlReader r, bool dumpValue = false)
+ {
+ var name = "?";
+ var dump = "?";
+
+ if (MatroskaSpecification.ElementDescriptors.TryGetValue(r.ElementId, out var el))
+ {
+ name = el.Name;
+
+ if (dumpValue)
+ {
+ dump = el.Type switch
+ {
+ ElementType.AsciiString => r.ReadAscii(),
+ ElementType.Binary => "'Binary Data'",
+ ElementType.Date => r.ReadDate().ToString(CultureInfo.CurrentCulture),
+ ElementType.Float => r.ReadFloat().ToString(CultureInfo.CurrentCulture),
+ ElementType.SignedInteger => r.ReadInt().ToString(),
+ ElementType.UnsignedInteger => r.ReadUInt().ToString(),
+ ElementType.Utf8String => r.ReadUtf(),
+ ElementType.MasterElement => "'MasterElement'",
+ _ => $"unknown (id:{r})"
+ };
+ }
+ }
+
+ return $"0x{r.ElementId.Value:X8} {name} [{r.ElementSize} bytes]" + (dumpValue ? " Value: " + dump : "");
+ }
+}
\ No newline at end of file
diff --git a/Matroska/Extensions/VIntExtensions.cs b/Matroska/Extensions/VIntExtensions.cs
new file mode 100644
index 0000000..53fe0d7
--- /dev/null
+++ b/Matroska/Extensions/VIntExtensions.cs
@@ -0,0 +1,9 @@
+namespace Matroska.Extensions;
+
+public static class VIntExtensions
+{
+ public static string Info(this (ulong Value, int Length, ulong EncodedValue) variableInt)
+ {
+ return $"VInt, value = {variableInt.Value}, length = {variableInt.Length}, encoded = {variableInt.EncodedValue:X}";
+ }
+}
\ No newline at end of file
diff --git a/Matroska/Matroska.csproj b/Matroska/Matroska.csproj
new file mode 100644
index 0000000..d280f97
--- /dev/null
+++ b/Matroska/Matroska.csproj
@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFrameworks>net10.0</TargetFrameworks>
+ <Description>An Ebml based serializer to deserialize a Matroska file (.mkv or .webm)</Description>
+ <Nullable>enable</Nullable>
+ <WarningsAsErrors>nullable</WarningsAsErrors>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Span.ReaderWriter" Version="0.1.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <None Update="MatroskaSpecification.tt">
+ <LastGenOutput>MatroskaSpecification.cs</LastGenOutput>
+ <Generator>TextTemplatingFileGenerator</Generator>
+ </None>
+ <None Update="Models\MatroskaModels.tt">
+ <Generator>TextTemplatingFileGenerator</Generator>
+ <LastGenOutput>MatroskaModels.cs</LastGenOutput>
+ </None>
+ <Compile Update="MatroskaSpecification.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>MatroskaSpecification.tt</DependentUpon>
+ </Compile>
+ <Compile Update="Models\MatroskaModels.cs">
+ <DesignTime>True</DesignTime>
+ <AutoGen>True</AutoGen>
+ <DependentUpon>MatroskaModels.tt</DependentUpon>
+ </Compile>
+ </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/Matroska/MatroskaSerializer.cs b/Matroska/MatroskaSerializer.cs
new file mode 100644
index 0000000..1ce6dab
--- /dev/null
+++ b/Matroska/MatroskaSerializer.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Matroska.Attributes;
+using Matroska.Extensions;
+using Matroska.Models;
+using NEbml.Core;
+
+namespace Matroska;
+
+public static class MatroskaSerializer
+{
+ private static readonly IDictionary<string, Dictionary<ulong, MatroskaElementInfo>> cache = new Dictionary<string, Dictionary<ulong, MatroskaElementInfo>>();
+
+ public static MatroskaDocument Deserialize(Stream stream)
+ {
+ var reader = new EbmlReader(stream);
+
+ reader.ReadNext();
+ var ebml = Deserialize<EBML>(reader);
+
+ reader.ReadNext();
+ var segment = Deserialize<Segment>(reader);
+
+ return new MatroskaDocument
+ {
+ Ebml = ebml,
+ Segment = segment
+ };
+ }
+
+ public static T Deserialize<T>(EbmlReader reader) where T : class
+ {
+ return (T)Deserialize(typeof(T), reader);
+ }
+
+ public static object Deserialize(Type type, EbmlReader reader)
+ {
+ bool isMasterElement = reader.IsKnownMasterElement();
+
+ if (isMasterElement)
+ {
+ reader.EnterContainer();
+ }
+
+ var instance = Activator.CreateInstance(type)!;
+
+ try
+ {
+ while (reader.ReadNext())
+ {
+ if (TryGetInfoByIdentifier(type, reader.ElementId.EncodedValue, out var info))
+ {
+ SetPropertyValue(instance, info, reader);
+ }
+ else
+ {
+ Console.WriteLine($"WARNING: {instance.GetType().Name}: property {reader.GetName(true)} not mapped.");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"ERROR: {instance.GetType().Name} at position {reader.ElementPosition} not mapped. Exception: {ex}");
+ }
+
+ if (isMasterElement)
+ {
+ reader.LeaveContainer();
+ }
+
+ return instance;
+ }
+
+ private static object GetValue(MatroskaElementInfo info, EbmlReader reader)
+ {
+ switch (info.ElementDescriptor.Type)
+ {
+ case ElementType.AsciiString:
+ return reader.ReadAscii();
+
+ case ElementType.Binary:
+ int bufferLength = (int)reader.ElementSize;
+ var buffer = new byte[bufferLength];
+ reader.ReadBinary(buffer, 0, bufferLength); // TODO : EbmlReader does not yet support reading a Span<byte>
+
+ if (typeof(IParseRawBinary).IsAssignableFrom(info.ElementType))
+ {
+ var parsedRawBinary = (IParseRawBinary)Activator.CreateInstance(info.ElementType)!;
+ parsedRawBinary.Parse(buffer);
+
+ return parsedRawBinary;
+ }
+
+ return buffer;
+
+ case ElementType.Date:
+ return reader.ReadDate();
+
+ case ElementType.Float:
+ return reader.ReadFloat();
+
+ case ElementType.MasterElement:
+ return Deserialize(info.ElementType, reader);
+
+ case ElementType.SignedInteger:
+ return reader.ReadInt();
+
+ case ElementType.UnsignedInteger:
+ return reader.ReadUInt();
+
+ case ElementType.Utf8String:
+ return reader.ReadUtf();
+ }
+
+ throw new NotSupportedException();
+ }
+
+ private static void SetPropertyValue(object instance, MatroskaElementInfo info, EbmlReader reader)
+ {
+ var value = GetValue(info, reader);
+
+ if (value != null)
+ {
+ if (typeof(IList).IsAssignableFrom(info.PropertyInfo.PropertyType))
+ {
+ var genericTypeElement = info.PropertyInfo.PropertyType.GenericTypeArguments.FirstOrDefault();
+ if (genericTypeElement?.GetTypeInfo().IsClass == true)
+ {
+ var list = (info.PropertyInfo.GetValue(instance) as IList) ?? CreateList(genericTypeElement);
+ list.Add(value);
+
+ value = list;
+ }
+ }
+
+ info.PropertyInfo.SetValue(instance, value);
+ }
+ }
+
+ public static IList CreateList(Type genericType)
+ {
+ return (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(genericType))!;
+ }
+
+ private static bool TryGetInfoByIdentifier(Type type, ulong identifier, out MatroskaElementInfo info)
+ {
+ return GetInfoFromCache(type).TryGetValue(identifier, out info);
+ }
+
+ private static Dictionary<ulong, MatroskaElementInfo> GetInfoFromCache(Type type)
+ {
+ var key = type.FullName!;
+ if (!cache.ContainsKey(type.FullName!))
+ {
+ cache[key] = new Dictionary<ulong, MatroskaElementInfo>();
+ foreach (var property in type.GetProperties())
+ {
+ var attribute = property.GetCustomAttributes().OfType<MatroskaElementDescriptorAttribute>().FirstOrDefault();
+ if (attribute == null)
+ {
+ continue;
+ }
+
+ var info = new MatroskaElementInfo
+ {
+ PropertyInfo = property,
+ Identifier = attribute.Identifier,
+ ElementType = attribute.ElementType ?? property.PropertyType,
+ ElementDescriptor = MatroskaSpecification.ElementDescriptorsByIdentifier[attribute.Identifier]
+ };
+
+ cache[key].Add(attribute.Identifier, info);
+ }
+ }
+
+ return cache[key];
+ }
+
+ private struct MatroskaElementInfo
+ {
+ public PropertyInfo PropertyInfo { get; set; }
+
+ public ulong Identifier { get; set; }
+
+ public Type ElementType { get; set; }
+
+ public ElementDescriptor ElementDescriptor { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Matroska/MatroskaSpecification.cs b/Matroska/MatroskaSpecification.cs
new file mode 100644
index 0000000..c69d50b
--- /dev/null
+++ b/Matroska/MatroskaSpecification.cs
@@ -0,0 +1,1145 @@
+using System.Collections.Generic;
+using System.CodeDom.Compiler;
+using System.Linq;
+using System.Reflection;
+using NEbml.Core;
+
+//--------------------------------------------------------------------------------------------------------
+// <auto-generated>
+// This C# code class was auto-generated at 06/14/2025 08:18:24.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+// Based on "https://github.com/ietf-wg-cellar/matroska-specification/blob/master/ebml_matroska.xml".
+// </auto-generated>
+//--------------------------------------------------------------------------------------------------------
+namespace Matroska;
+
+/// <summary>
+/// Contains the EBML elements specified by the Matroska project (https://matroska.org/technical/specs/index.html)
+/// </summary>
+[GeneratedCode("MatroskaSpecification.tt", "1.0.0.0")]
+internal static class MatroskaSpecification
+{
+ #region Helpers
+ private static readonly Dictionary<VInt, ElementDescriptor> _elementDescriptorsByVInt = new Dictionary<VInt, ElementDescriptor>();
+ private static readonly Dictionary<ulong, ElementDescriptor> _elementDescriptorsByIdentifier = new Dictionary<ulong, ElementDescriptor>();
+
+ /// <summary>
+ /// Gets a dictionary of all Matroska elements.
+ /// </summary>
+ public static IReadOnlyDictionary<VInt, ElementDescriptor> ElementDescriptors => _elementDescriptorsByVInt;
+
+ /// <summary>
+ /// Gets a dictionary of all Matroska elements.
+ /// </summary>
+ public static IReadOnlyDictionary<ulong, ElementDescriptor> ElementDescriptorsByIdentifier => _elementDescriptorsByIdentifier;
+
+ static MatroskaSpecification()
+ {
+ var fields = typeof(MatroskaSpecification).GetFields(BindingFlags.Public | BindingFlags.Static);
+
+ foreach (FieldInfo field in fields.Where(ft => ft.FieldType == typeof(ElementDescriptor)))
+ {
+ var value = (ElementDescriptor)field.GetValue(null)!;
+ _elementDescriptorsByVInt.Add(value.Identifier, value);
+ }
+
+ foreach (FieldInfo field in fields.Where(ft => ft.FieldType == typeof(ulong)))
+ {
+ var identifier = (ulong)field.GetValue(null)!;
+ _elementDescriptorsByIdentifier.Add(identifier, _elementDescriptorsByVInt[VInt.FromEncoded(identifier)]);
+ }
+ }
+ #endregion
+
+ #region Definitions
+ /// <summary>Used to void damaged data, to avoid unexpected behaviors when using damaged data. The content is discarded. Also used to reserve space in a sub-element for later use.</summary>
+ public const ulong Void = 0xEC;
+ public static readonly ElementDescriptor VoidDescriptor = new ElementDescriptor(Void, nameof(Void), ElementType.Binary);
+
+ /// <summary>The CRC is computed on all the data of the Master element it's in. The CRC element should be the first in it's parent master for easier reading. All level 1 elements should include a CRC-32. The CRC in use is the IEEE CRC32 Little Endian.</summary>
+ public const ulong CRC32 = 0xBF;
+ public static readonly ElementDescriptor CRC32Descriptor = new ElementDescriptor(CRC32, nameof(CRC32), ElementType.Binary);
+
+ /// <summary>[Required] Set the EBML characteristics of the data to follow. Each EBML document has to start with this.</summary>
+ public const ulong EBML = 0x1A45DFA3;
+ public static readonly ElementDescriptor EBMLDescriptor = new ElementDescriptor(EBML, nameof(EBML), ElementType.MasterElement);
+
+ /// <summary>[Required] A string that describes the type of document that follows this EBML header, for example 'matroska' or 'webm'.</summary>
+ public const ulong DocType = 0x4282;
+ public static readonly ElementDescriptor DocTypeDescriptor = new ElementDescriptor(DocType, nameof(DocType), ElementType.AsciiString);
+
+ /// <summary>A DocTypeExtension adds extra Elements to the main DocType+DocTypeVersion tuple it's attached to. An EBML Reader **MAY** know these extra Elements and how to use them. A DocTypeExtension **MAY** be used to iterate between experimental Elements before they are integrated into a regular DocTypeVersion. Reading one DocTypeExtension version of a DocType+DocTypeVersion tuple doesn't imply one should be able to read upper versions of this DocTypeExtension.</summary>
+ public const ulong DocTypeExtension = 0x4281;
+ public static readonly ElementDescriptor DocTypeExtensionDescriptor = new ElementDescriptor(DocTypeExtension, nameof(DocTypeExtension), ElementType.MasterElement);
+
+ /// <summary>[Required] The name of the DocTypeExtension to differentiate it from other DocTypeExtensions of the same DocType+DocTypeVersion tuple. A DocTypeExtensionName value **MUST** be unique within the EBML Header.</summary>
+ public const ulong DocTypeExtensionName = 0x4283;
+ public static readonly ElementDescriptor DocTypeExtensionNameDescriptor = new ElementDescriptor(DocTypeExtensionName, nameof(DocTypeExtensionName), ElementType.AsciiString);
+
+ /// <summary>[Required] The version of the DocTypeExtension. Different DocTypeExtensionVersion values of the same DocType + DocTypeVersion + DocTypeExtensionName tuple **MAY** contain completely different sets of extra Elements. An EBML Reader **MAY** support multiple versions of the same tuple, only one version of the tuple, or not support the tuple at all.</summary>
+ public const ulong DocTypeExtensionVersion = 0x4284;
+ public static readonly ElementDescriptor DocTypeExtensionVersionDescriptor = new ElementDescriptor(DocTypeExtensionVersion, nameof(DocTypeExtensionVersion), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The minimum DocType version an interpreter has to support to read this file.</summary>
+ public const ulong DocTypeReadVersion = 0x4285;
+ public static readonly ElementDescriptor DocTypeReadVersionDescriptor = new ElementDescriptor(DocTypeReadVersion, nameof(DocTypeReadVersion), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The version of DocType interpreter used to create the file.</summary>
+ public const ulong DocTypeVersion = 0x4287;
+ public static readonly ElementDescriptor DocTypeVersionDescriptor = new ElementDescriptor(DocTypeVersion, nameof(DocTypeVersion), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The maximum length of the IDs you'll find in this file (4 or less in Matroska).</summary>
+ public const ulong EBMLMaxIDLength = 0x42F2;
+ public static readonly ElementDescriptor EBMLMaxIDLengthDescriptor = new ElementDescriptor(EBMLMaxIDLength, nameof(EBMLMaxIDLength), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The maximum length of the sizes you'll find in this file (8 or less in Matroska). This does not override the element size indicated at the beginning of an element. Elements that have an indicated size which is larger than what is allowed by EBMLMaxSizeLength shall be considered invalid.</summary>
+ public const ulong EBMLMaxSizeLength = 0x42F3;
+ public static readonly ElementDescriptor EBMLMaxSizeLengthDescriptor = new ElementDescriptor(EBMLMaxSizeLength, nameof(EBMLMaxSizeLength), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The minimum EBML version a parser has to support to read this file.</summary>
+ public const ulong EBMLReadVersion = 0x42F7;
+ public static readonly ElementDescriptor EBMLReadVersionDescriptor = new ElementDescriptor(EBMLReadVersion, nameof(EBMLReadVersion), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The version of EBML parser used to create the file.</summary>
+ public const ulong EBMLVersion = 0x4286;
+ public static readonly ElementDescriptor EBMLVersionDescriptor = new ElementDescriptor(EBMLVersion, nameof(EBMLVersion), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The `Root Element` that contains all other `Top-Level Elements`; see (#data-layout).</summary>
+ public const ulong Segment = 0x18538067;
+ public static readonly ElementDescriptor SegmentDescriptor = new ElementDescriptor(Segment, nameof(Segment), ElementType.MasterElement);
+
+ /// <summary>Contains attached files.</summary>
+ public const ulong Attachments = 0x1941A469;
+ public static readonly ElementDescriptor AttachmentsDescriptor = new ElementDescriptor(Attachments, nameof(Attachments), ElementType.MasterElement);
+
+ /// <summary>[Required] An attached file.</summary>
+ public const ulong AttachedFile = 0x61A7;
+ public static readonly ElementDescriptor AttachedFileDescriptor = new ElementDescriptor(AttachedFile, nameof(AttachedFile), ElementType.MasterElement);
+
+ /// <summary>[Required] The data of the file.</summary>
+ public const ulong FileData = 0x465C;
+ public static readonly ElementDescriptor FileDataDescriptor = new ElementDescriptor(FileData, nameof(FileData), ElementType.Binary);
+
+ /// <summary>A human-friendly name for the attached file.</summary>
+ public const ulong FileDescription = 0x467E;
+ public static readonly ElementDescriptor FileDescriptionDescriptor = new ElementDescriptor(FileDescription, nameof(FileDescription), ElementType.Utf8String);
+
+ /// <summary>[Required] Media type of the file following the format described in [@!RFC6838].</summary>
+ public const ulong FileMediaType = 0x4660;
+ public static readonly ElementDescriptor FileMediaTypeDescriptor = new ElementDescriptor(FileMediaType, nameof(FileMediaType), ElementType.AsciiString);
+
+ /// <summary>[Required] Filename of the attached file.</summary>
+ public const ulong FileName = 0x466E;
+ public static readonly ElementDescriptor FileNameDescriptor = new ElementDescriptor(FileName, nameof(FileName), ElementType.Utf8String);
+
+ /// <summary>A binary value that a track/codec can refer to when the attachment is needed.</summary>
+ public const ulong FileReferral = 0x4675;
+ public static readonly ElementDescriptor FileReferralDescriptor = new ElementDescriptor(FileReferral, nameof(FileReferral), ElementType.Binary);
+
+ /// <summary>[Required] UID representing the file, as random as possible.</summary>
+ public const ulong FileUID = 0x46AE;
+ public static readonly ElementDescriptor FileUIDDescriptor = new ElementDescriptor(FileUID, nameof(FileUID), ElementType.UnsignedInteger);
+
+ /// <summary>The timestamp at which this optimized font attachment goes out of context, expressed in Segment Ticks, which are based on`TimestampScale`. See [@?DivXWorldFonts].</summary>
+ public const ulong FileUsedEndTime = 0x4662;
+ public static readonly ElementDescriptor FileUsedEndTimeDescriptor = new ElementDescriptor(FileUsedEndTime, nameof(FileUsedEndTime), ElementType.UnsignedInteger);
+
+ /// <summary>The timestamp at which this optimized font attachment comes into context, expressed in Segment Ticks, which are based on`TimestampScale`. See [@?DivXWorldFonts].</summary>
+ public const ulong FileUsedStartTime = 0x4661;
+ public static readonly ElementDescriptor FileUsedStartTimeDescriptor = new ElementDescriptor(FileUsedStartTime, nameof(FileUsedStartTime), ElementType.UnsignedInteger);
+
+ /// <summary>A system to define basic menus and partition data.For more detailed information, see (#chapters).</summary>
+ public const ulong Chapters = 0x1043A770;
+ public static readonly ElementDescriptor ChaptersDescriptor = new ElementDescriptor(Chapters, nameof(Chapters), ElementType.MasterElement);
+
+ /// <summary>[Required] Contains all information about a `Segment` edition.</summary>
+ public const ulong EditionEntry = 0x45B9;
+ public static readonly ElementDescriptor EditionEntryDescriptor = new ElementDescriptor(EditionEntry, nameof(EditionEntry), ElementType.MasterElement);
+
+ /// <summary>[Required] Contains the atom information to use as the chapter atom (applies to all tracks).</summary>
+ public const ulong ChapterAtom = 0xB6;
+ public static readonly ElementDescriptor ChapterAtomDescriptor = new ElementDescriptor(ChapterAtom, nameof(ChapterAtom), ElementType.MasterElement);
+
+ /// <summary>Contains all the commands associated with the Atom.</summary>
+ public const ulong ChapProcess = 0x6944;
+ public static readonly ElementDescriptor ChapProcessDescriptor = new ElementDescriptor(ChapProcess, nameof(ChapProcess), ElementType.MasterElement);
+
+ /// <summary>[Required] Contains the type of the codec used for processing.</summary>
+ public const ulong ChapProcessCodecID = 0x6955;
+ public static readonly ElementDescriptor ChapProcessCodecIDDescriptor = new ElementDescriptor(ChapProcessCodecID, nameof(ChapProcessCodecID), ElementType.UnsignedInteger);
+
+ /// <summary>Contains all the commands associated with the Atom.</summary>
+ public const ulong ChapProcessCommand = 0x6911;
+ public static readonly ElementDescriptor ChapProcessCommandDescriptor = new ElementDescriptor(ChapProcessCommand, nameof(ChapProcessCommand), ElementType.MasterElement);
+
+ /// <summary>[Required] Contains the command information.The data **SHOULD** be interpreted depending on the `ChapProcessCodecID` value. For `ChapProcessCodecID` = 1,the data correspond to the binary DVD cell pre/post commands; see (#menu-features) on DVD menus.</summary>
+ public const ulong ChapProcessData = 0x6933;
+ public static readonly ElementDescriptor ChapProcessDataDescriptor = new ElementDescriptor(ChapProcessData, nameof(ChapProcessData), ElementType.Binary);
+
+ /// <summary>[Required] Defines when the process command **SHOULD** be handled.</summary>
+ public const ulong ChapProcessTime = 0x6922;
+ public static readonly ElementDescriptor ChapProcessTimeDescriptor = new ElementDescriptor(ChapProcessTime, nameof(ChapProcessTime), ElementType.UnsignedInteger);
+
+ /// <summary>Optional data attached to the `ChapProcessCodecID` information.For `ChapProcessCodecID` = 1, it is the "DVD level" equivalent; see (#menu-features) on DVD menus.</summary>
+ public const ulong ChapProcessPrivate = 0x450D;
+ public static readonly ElementDescriptor ChapProcessPrivateDescriptor = new ElementDescriptor(ChapProcessPrivate, nameof(ChapProcessPrivate), ElementType.Binary);
+
+ /// <summary>Contains all possible strings to use for the chapter display.</summary>
+ public const ulong ChapterDisplay = 0x80;
+ public static readonly ElementDescriptor ChapterDisplayDescriptor = new ElementDescriptor(ChapterDisplay, nameof(ChapterDisplay), ElementType.MasterElement);
+
+ /// <summary>A country corresponding to the string,in the Matroska countries form; see (#country-codes) on country codes.This element **MUST** be ignored if a `ChapLanguageBCP47` element is used within the same `ChapterDisplay` element.</summary>
+ public const ulong ChapCountry = 0x437E;
+ public static readonly ElementDescriptor ChapCountryDescriptor = new ElementDescriptor(ChapCountry, nameof(ChapCountry), ElementType.AsciiString);
+
+ /// <summary>[Required] A language corresponding to the string,in the Matroska languages form; see (#language-codes) on language codes.This element **MUST** be ignored if a `ChapLanguageBCP47` element is used within the same `ChapterDisplay` element.</summary>
+ public const ulong ChapLanguage = 0x437C;
+ public static readonly ElementDescriptor ChapLanguageDescriptor = new ElementDescriptor(ChapLanguage, nameof(ChapLanguage), ElementType.AsciiString);
+
+ /// <summary>A language corresponding to the `ChapString`,in the form defined in [@!RFC5646]; see (#language-codes) on language codes.If a `ChapLanguageBCP47` element is used, then any `ChapLanguage` and `ChapCountry` elements used in the same `ChapterDisplay` **MUST** be ignored.</summary>
+ public const ulong ChapLanguageBCP47 = 0x437D;
+ public static readonly ElementDescriptor ChapLanguageBCP47Descriptor = new ElementDescriptor(ChapLanguageBCP47, nameof(ChapLanguageBCP47), ElementType.AsciiString);
+
+ /// <summary>[Required] Contains the string to use as the chapter atom.</summary>
+ public const ulong ChapString = 0x85;
+ public static readonly ElementDescriptor ChapStringDescriptor = new ElementDescriptor(ChapString, nameof(ChapString), ElementType.Utf8String);
+
+ /// <summary>[Required] Set to 1 if the chapter is enabled. It can be enabled/disabled by a Control Track.When disabled, the movie **SHOULD** skip all the content between the TimeStart and TimeEnd of this chapter; see (#chapter-flags) on `Chapter` flags.</summary>
+ public const ulong ChapterFlagEnabled = 0x4598;
+ public static readonly ElementDescriptor ChapterFlagEnabledDescriptor = new ElementDescriptor(ChapterFlagEnabled, nameof(ChapterFlagEnabled), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Set to 1 if a chapter is hidden.Hidden chapters **SHOULD NOT** be available to the user interface(but still be available to Control Tracks; see (#chapterflaghidden) on `Chapter` flags).</summary>
+ public const ulong ChapterFlagHidden = 0x98;
+ public static readonly ElementDescriptor ChapterFlagHiddenDescriptor = new ElementDescriptor(ChapterFlagHidden, nameof(ChapterFlagHidden), ElementType.UnsignedInteger);
+
+ /// <summary>Specifies the physical equivalent of this `ChapterAtom`, e.g., "DVD" (60) or "SIDE" (50);see (#physical-types) for a complete list of values.</summary>
+ public const ulong ChapterPhysicalEquiv = 0x63C3;
+ public static readonly ElementDescriptor ChapterPhysicalEquivDescriptor = new ElementDescriptor(ChapterPhysicalEquiv, nameof(ChapterPhysicalEquiv), ElementType.UnsignedInteger);
+
+ /// <summary>The `EditionUID` to play from the `Segment` linked in `ChapterSegmentUUID`.If `ChapterSegmentEditionUID` is undeclared, then no `Edition` of the `Linked Segment` is used; see (#medium-linking) on Medium-Linking `Segments`.</summary>
+ public const ulong ChapterSegmentEditionUID = 0x6EBC;
+ public static readonly ElementDescriptor ChapterSegmentEditionUIDDescriptor = new ElementDescriptor(ChapterSegmentEditionUID, nameof(ChapterSegmentEditionUID), ElementType.UnsignedInteger);
+
+ /// <summary>The `SegmentUUID` of another `Segment` to play during this chapter.</summary>
+ public const ulong ChapterSegmentUUID = 0x6E67;
+ public static readonly ElementDescriptor ChapterSegmentUUIDDescriptor = new ElementDescriptor(ChapterSegmentUUID, nameof(ChapterSegmentUUID), ElementType.Binary);
+
+ /// <summary>Indicates what type of content the `ChapterAtom` contains and might be skipped.It can be used to automatically skip content based on the type.If a `ChapterAtom` is inside a `ChapterAtom` that has a `ChapterSkipType` set, it**MUST NOT** have a `ChapterSkipType` or have a `ChapterSkipType` with the same value as it's parent `ChapterAtom`.If the `ChapterAtom` doesn't contain a `ChapterTimeEnd`, the value of the `ChapterSkipType` is only valid until the next `ChapterAtom` with a `ChapterSkipType` value or the end of the file.</summary>
+ public const ulong ChapterSkipType = 0x4588;
+ public static readonly ElementDescriptor ChapterSkipTypeDescriptor = new ElementDescriptor(ChapterSkipType, nameof(ChapterSkipType), ElementType.UnsignedInteger);
+
+ /// <summary>A unique string ID that identifies the `Chapter`.For example, it is used as the storage for cue identifier values [@?WebVTT].</summary>
+ public const ulong ChapterStringUID = 0x5654;
+ public static readonly ElementDescriptor ChapterStringUIDDescriptor = new ElementDescriptor(ChapterStringUID, nameof(ChapterStringUID), ElementType.Utf8String);
+
+ /// <summary>Timestamp of the end of `Chapter` (timestamp excluded), expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).The value **MUST** be greater than or equal to the `ChapterTimeStart` of the same `ChapterAtom`.</summary>
+ public const ulong ChapterTimeEnd = 0x92;
+ public static readonly ElementDescriptor ChapterTimeEndDescriptor = new ElementDescriptor(ChapterTimeEnd, nameof(ChapterTimeEnd), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Timestamp of the start of `Chapter`, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).</summary>
+ public const ulong ChapterTimeStart = 0x91;
+ public static readonly ElementDescriptor ChapterTimeStartDescriptor = new ElementDescriptor(ChapterTimeStart, nameof(ChapterTimeStart), ElementType.UnsignedInteger);
+
+ /// <summary>List of tracks on which the chapter applies. If this element is not present, all tracks apply.</summary>
+ public const ulong ChapterTrack = 0x8F;
+ public static readonly ElementDescriptor ChapterTrackDescriptor = new ElementDescriptor(ChapterTrack, nameof(ChapterTrack), ElementType.MasterElement);
+
+ /// <summary>[Required] UID of the `Track` to apply this chapter to.In the absence of a control track, choosing this chapter will select the listed `Tracks` and deselect unlisted tracks.Absence of this element indicates that the `Chapter` **SHOULD** be applied to any currently used `Tracks`.</summary>
+ public const ulong ChapterTrackUID = 0x89;
+ public static readonly ElementDescriptor ChapterTrackUIDDescriptor = new ElementDescriptor(ChapterTrackUID, nameof(ChapterTrackUID), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] A UID that identifies the `Chapter`.</summary>
+ public const ulong ChapterUID = 0x73C4;
+ public static readonly ElementDescriptor ChapterUIDDescriptor = new ElementDescriptor(ChapterUID, nameof(ChapterUID), ElementType.UnsignedInteger);
+
+ /// <summary>Contains a possible string to use for the edition display for the given languages.</summary>
+ public const ulong EditionDisplay = 0x4520;
+ public static readonly ElementDescriptor EditionDisplayDescriptor = new ElementDescriptor(EditionDisplay, nameof(EditionDisplay), ElementType.MasterElement);
+
+ /// <summary>One language corresponding to the EditionString,in the form defined in [@!RFC5646]; see (#language-codes) on language codes.</summary>
+ public const ulong EditionLanguageIETF = 0x45E4;
+ public static readonly ElementDescriptor EditionLanguageIETFDescriptor = new ElementDescriptor(EditionLanguageIETF, nameof(EditionLanguageIETF), ElementType.AsciiString);
+
+ /// <summary>[Required] Contains the string to use as the edition name.</summary>
+ public const ulong EditionString = 0x4521;
+ public static readonly ElementDescriptor EditionStringDescriptor = new ElementDescriptor(EditionString, nameof(EditionString), ElementType.Utf8String);
+
+ /// <summary>[Required] Set to 1 if the edition **SHOULD** be used as the default one.</summary>
+ public const ulong EditionFlagDefault = 0x45DB;
+ public static readonly ElementDescriptor EditionFlagDefaultDescriptor = new ElementDescriptor(EditionFlagDefault, nameof(EditionFlagDefault), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Set to 1 if an edition is hidden. Hidden editions **SHOULD NOT** be available to the user interface(but still be available to Control Tracks; see (#chapter-flags) on `Chapter` flags).</summary>
+ public const ulong EditionFlagHidden = 0x45BD;
+ public static readonly ElementDescriptor EditionFlagHiddenDescriptor = new ElementDescriptor(EditionFlagHidden, nameof(EditionFlagHidden), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Set to 1 if the chapters can be defined multiple times and the order to play them is enforced; see (#editionflagordered).</summary>
+ public const ulong EditionFlagOrdered = 0x45DD;
+ public static readonly ElementDescriptor EditionFlagOrderedDescriptor = new ElementDescriptor(EditionFlagOrdered, nameof(EditionFlagOrdered), ElementType.UnsignedInteger);
+
+ /// <summary>A UID that identifies the edition. It's useful for tagging an edition.</summary>
+ public const ulong EditionUID = 0x45BC;
+ public static readonly ElementDescriptor EditionUIDDescriptor = new ElementDescriptor(EditionUID, nameof(EditionUID), ElementType.UnsignedInteger);
+
+ /// <summary>The `Top-Level Element` containing the (monolithic) `Block` structure.</summary>
+ public const ulong Cluster = 0x1F43B675;
+ public static readonly ElementDescriptor ClusterDescriptor = new ElementDescriptor(Cluster, nameof(Cluster), ElementType.MasterElement);
+
+ /// <summary>Basic container of information containing a single `Block` and information specific to that `Block`.</summary>
+ public const ulong BlockGroup = 0xA0;
+ public static readonly ElementDescriptor BlockGroupDescriptor = new ElementDescriptor(BlockGroup, nameof(BlockGroup), ElementType.MasterElement);
+
+ /// <summary>[Required] `Block` containing the actual data to be rendered and a timestamp relative to the `Cluster` Timestamp;see (#block-structure) on `Block` Structure.</summary>
+ public const ulong Block = 0xA1;
+ public static readonly ElementDescriptor BlockDescriptor = new ElementDescriptor(Block, nameof(Block), ElementType.Binary);
+
+ /// <summary>Contains additional binary data to complete the `Block` element; see [@?I-D.ietf-cellar-codec, section 4.1.5] for more information.An EBML parser that has no knowledge of the `Block` structure could still see and use/skip these data.</summary>
+ public const ulong BlockAdditions = 0x75A1;
+ public static readonly ElementDescriptor BlockAdditionsDescriptor = new ElementDescriptor(BlockAdditions, nameof(BlockAdditions), ElementType.MasterElement);
+
+ /// <summary>[Required] Contains the `BlockAdditional` and some parameters.</summary>
+ public const ulong BlockMore = 0xA6;
+ public static readonly ElementDescriptor BlockMoreDescriptor = new ElementDescriptor(BlockMore, nameof(BlockMore), ElementType.MasterElement);
+
+ /// <summary>[Required] An ID that identifies how to interpret the `BlockAdditional` data; see [@?I-D.ietf-cellar-codec, section 4.1.5] formore information. A value of 1 indicates that the `BlockAdditional` data isdefined by the codec. Any other value indicates that the `BlockAdditional` datashould be handled according to the `BlockAddIDType` that is located in the`TrackEntry`.</summary>
+ public const ulong BlockAddID = 0xEE;
+ public static readonly ElementDescriptor BlockAddIDDescriptor = new ElementDescriptor(BlockAddID, nameof(BlockAddID), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Interpreted by the codec as it wishes (using the `BlockAddID`).</summary>
+ public const ulong BlockAdditional = 0xA5;
+ public static readonly ElementDescriptor BlockAdditionalDescriptor = new ElementDescriptor(BlockAdditional, nameof(BlockAdditional), ElementType.Binary);
+
+ /// <summary>The duration of the `Block`, expressed in Track Ticks; see (#timestamp-ticks).The `BlockDuration` element can be usefulat the end of a `Track` to define the duration of the last frame (asthere is no subsequent `Block` available) or when there is a break in atrack like for subtitle tracks.</summary>
+ public const ulong BlockDuration = 0x9B;
+ public static readonly ElementDescriptor BlockDurationDescriptor = new ElementDescriptor(BlockDuration, nameof(BlockDuration), ElementType.UnsignedInteger);
+
+ /// <summary>A `Block` with no data. It must be stored in the stream at the place the real `Block` would be in display order.</summary>
+ public const ulong BlockVirtual = 0xA2;
+ public static readonly ElementDescriptor BlockVirtualDescriptor = new ElementDescriptor(BlockVirtual, nameof(BlockVirtual), ElementType.Binary);
+
+ /// <summary>The new codec state to use. Data interpretation is private to the codec.This information **SHOULD** always be referenced by a seek entry.</summary>
+ public const ulong CodecState = 0xA4;
+ public static readonly ElementDescriptor CodecStateDescriptor = new ElementDescriptor(CodecState, nameof(CodecState), ElementType.Binary);
+
+ /// <summary>Duration of the silent data added to the `Block`, expressed inMatroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks)(padding at the end of the `Block` for positive values and at thebeginning of the `Block` for negative values). The duration of`DiscardPadding` is not calculated in the duration of the`TrackEntry` and **SHOULD** be discarded duringplayback.</summary>
+ public const ulong DiscardPadding = 0x75A2;
+ public static readonly ElementDescriptor DiscardPaddingDescriptor = new ElementDescriptor(DiscardPadding, nameof(DiscardPadding), ElementType.SignedInteger);
+
+ /// <summary>A timestamp value, relative to the timestamp of the `Block` in this `BlockGroup`, expressed in Track Ticks; see (#timestamp-ticks).This is used to reference other frames necessary to decode this frame.The relative value **SHOULD** correspond to a valid `Block` that this `Block` depends on.Historically, `Matroska Writers` didn't write the actual `Block(s)` that this `Block` depends on, but they did write *some* `Block(s)` in the past.The value "0" **MAY** also be used to signify that this `Block` cannot be decoded on its own, but the necessary reference `Block(s)` is unknown. In this case, other `ReferenceBlock` elements **MUST NOT** be found in the same `BlockGroup`. If the `BlockGroup` doesn't have a `ReferenceBlock` element, then the `Block` it contains can be decoded without using any other `Block` data.</summary>
+ public const ulong ReferenceBlock = 0xFB;
+ public static readonly ElementDescriptor ReferenceBlockDescriptor = new ElementDescriptor(ReferenceBlock, nameof(ReferenceBlock), ElementType.SignedInteger);
+
+ /// <summary>Contains information about the last reference frame. See [@?DivXTrickTrack].</summary>
+ public const ulong ReferenceFrame = 0xC8;
+ public static readonly ElementDescriptor ReferenceFrameDescriptor = new ElementDescriptor(ReferenceFrame, nameof(ReferenceFrame), ElementType.MasterElement);
+
+ /// <summary>[Required] The relative offset, in bytes, from the previous `BlockGroup` element for this Smooth FF/RW video track to the containing `BlockGroup`element. See [@?DivXTrickTrack].</summary>
+ public const ulong ReferenceOffset = 0xC9;
+ public static readonly ElementDescriptor ReferenceOffsetDescriptor = new ElementDescriptor(ReferenceOffset, nameof(ReferenceOffset), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The timestamp of the `BlockGroup` pointed to by ReferenceOffset, expressed in Track Ticks; see (#timestamp-ticks). See [@?DivXTrickTrack].</summary>
+ public const ulong ReferenceTimestamp = 0xCA;
+ public static readonly ElementDescriptor ReferenceTimestampDescriptor = new ElementDescriptor(ReferenceTimestamp, nameof(ReferenceTimestamp), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] This frame is referenced and has the specified cache priority.In the cache, only a frame of the same or higher priority can replace this frame. A value of 0 means the frame is not referenced.</summary>
+ public const ulong ReferencePriority = 0xFA;
+ public static readonly ElementDescriptor ReferencePriorityDescriptor = new ElementDescriptor(ReferencePriority, nameof(ReferencePriority), ElementType.UnsignedInteger);
+
+ /// <summary>The `Segment Position` of the data that would otherwise be in position of the virtual block.</summary>
+ public const ulong ReferenceVirtual = 0xFD;
+ public static readonly ElementDescriptor ReferenceVirtualDescriptor = new ElementDescriptor(ReferenceVirtual, nameof(ReferenceVirtual), ElementType.SignedInteger);
+
+ /// <summary>Contains slices description.</summary>
+ public const ulong Slices = 0x8E;
+ public static readonly ElementDescriptor SlicesDescriptor = new ElementDescriptor(Slices, nameof(Slices), ElementType.MasterElement);
+
+ /// <summary>Contains extra time information about the data contained in the `Block`.Being able to interpret this element is not required for playback.</summary>
+ public const ulong TimeSlice = 0xE8;
+ public static readonly ElementDescriptor TimeSliceDescriptor = new ElementDescriptor(TimeSlice, nameof(TimeSlice), ElementType.MasterElement);
+
+ /// <summary>The ID of the `BlockAdditional` element (0 is the main `Block`).</summary>
+ public const ulong BlockAdditionID = 0xCB;
+ public static readonly ElementDescriptor BlockAdditionIDDescriptor = new ElementDescriptor(BlockAdditionID, nameof(BlockAdditionID), ElementType.UnsignedInteger);
+
+ /// <summary>The delay to apply to the element, expressed in Track Ticks; see (#timestamp-ticks).</summary>
+ public const ulong Delay = 0xCE;
+ public static readonly ElementDescriptor DelayDescriptor = new ElementDescriptor(Delay, nameof(Delay), ElementType.UnsignedInteger);
+
+ /// <summary>The number of the frame to generate from this lace with this delay(allows for the generation of many frames from the same Block/Frame).</summary>
+ public const ulong FrameNumber = 0xCD;
+ public static readonly ElementDescriptor FrameNumberDescriptor = new ElementDescriptor(FrameNumber, nameof(FrameNumber), ElementType.UnsignedInteger);
+
+ /// <summary>The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc.).Being able to interpret this element is not required for playback.</summary>
+ public const ulong LaceNumber = 0xCC;
+ public static readonly ElementDescriptor LaceNumberDescriptor = new ElementDescriptor(LaceNumber, nameof(LaceNumber), ElementType.UnsignedInteger);
+
+ /// <summary>The duration to apply to the element, expressed in Track Ticks; see (#timestamp-ticks).</summary>
+ public const ulong SliceDuration = 0xCF;
+ public static readonly ElementDescriptor SliceDurationDescriptor = new ElementDescriptor(SliceDuration, nameof(SliceDuration), ElementType.UnsignedInteger);
+
+ /// <summary>Similar to `SimpleBlock` (see (#simpleblock-structure)),but the data inside the `Block` are Transformed (encrypted and/or signed).</summary>
+ public const ulong EncryptedBlock = 0xAF;
+ public static readonly ElementDescriptor EncryptedBlockDescriptor = new ElementDescriptor(EncryptedBlock, nameof(EncryptedBlock), ElementType.Binary);
+
+ /// <summary>The `Segment Position` of the `Cluster` in the `Segment` (0 in live streams).It might help to resynchronize the offset on damaged streams.</summary>
+ public const ulong Position = 0xA7;
+ public static readonly ElementDescriptor PositionDescriptor = new ElementDescriptor(Position, nameof(Position), ElementType.UnsignedInteger);
+
+ /// <summary>Size of the previous `Cluster`, in octets. Can be useful for backward playing.</summary>
+ public const ulong PrevSize = 0xAB;
+ public static readonly ElementDescriptor PrevSizeDescriptor = new ElementDescriptor(PrevSize, nameof(PrevSize), ElementType.UnsignedInteger);
+
+ /// <summary>The list of tracks that are not used in that part of the stream.It is useful when using overlay tracks for seeking or deciding what track to use.</summary>
+ public const ulong SilentTracks = 0x5854;
+ public static readonly ElementDescriptor SilentTracksDescriptor = new ElementDescriptor(SilentTracks, nameof(SilentTracks), ElementType.MasterElement);
+
+ /// <summary>One of the track numbers that is not used from now on in the stream.It could change later if not specified as silent in a further `Cluster`.</summary>
+ public const ulong SilentTrackNumber = 0x58D7;
+ public static readonly ElementDescriptor SilentTrackNumberDescriptor = new ElementDescriptor(SilentTrackNumber, nameof(SilentTrackNumber), ElementType.UnsignedInteger);
+
+ /// <summary>Similar to `Block` (see (#block-structure)) but without all the extra information.Mostly used to reduce overhead when no extra feature is needed; see (#simpleblock-structure) on `SimpleBlock` Structure.</summary>
+ public const ulong SimpleBlock = 0xA3;
+ public static readonly ElementDescriptor SimpleBlockDescriptor = new ElementDescriptor(SimpleBlock, nameof(SimpleBlock), ElementType.Binary);
+
+ /// <summary>[Required] Absolute timestamp of the cluster, expressed in Segment Ticks, which are based on `TimestampScale`; see (#timestamp-ticks).</summary>
+ public const ulong Timestamp = 0xE7;
+ public static readonly ElementDescriptor TimestampDescriptor = new ElementDescriptor(Timestamp, nameof(Timestamp), ElementType.UnsignedInteger);
+
+ /// <summary>A `Top-Level Element` to speed seeking access. All entries arelocal to the `Segment`.</summary>
+ public const ulong Cues = 0x1C53BB6B;
+ public static readonly ElementDescriptor CuesDescriptor = new ElementDescriptor(Cues, nameof(Cues), ElementType.MasterElement);
+
+ /// <summary>[Required] Contains all information relative to a seek point in the `Segment`.</summary>
+ public const ulong CuePoint = 0xBB;
+ public static readonly ElementDescriptor CuePointDescriptor = new ElementDescriptor(CuePoint, nameof(CuePoint), ElementType.MasterElement);
+
+ /// <summary>[Required] Absolute timestamp of the seek point, expressed in Segment Ticks, which are based on `TimestampScale`; see (#timestamp-ticks).</summary>
+ public const ulong CueTime = 0xB3;
+ public static readonly ElementDescriptor CueTimeDescriptor = new ElementDescriptor(CueTime, nameof(CueTime), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Contains positions for different tracks corresponding to the timestamp.</summary>
+ public const ulong CueTrackPositions = 0xB7;
+ public static readonly ElementDescriptor CueTrackPositionsDescriptor = new ElementDescriptor(CueTrackPositions, nameof(CueTrackPositions), ElementType.MasterElement);
+
+ /// <summary>Number of the `Block` in the specified `Cluster`.</summary>
+ public const ulong CueBlockNumber = 0x5378;
+ public static readonly ElementDescriptor CueBlockNumberDescriptor = new ElementDescriptor(CueBlockNumber, nameof(CueBlockNumber), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The `Segment Position` ((#segment-position)) of the `Cluster` containing the associated `Block`.</summary>
+ public const ulong CueClusterPosition = 0xF1;
+ public static readonly ElementDescriptor CueClusterPositionDescriptor = new ElementDescriptor(CueClusterPosition, nameof(CueClusterPosition), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The `Segment Position` ((#segment-position)) of theCodec State corresponding to this `Cues` element. 0 means that thedata is taken from the initial `TrackEntry`.</summary>
+ public const ulong CueCodecState = 0xEA;
+ public static readonly ElementDescriptor CueCodecStateDescriptor = new ElementDescriptor(CueCodecState, nameof(CueCodecState), ElementType.UnsignedInteger);
+
+ /// <summary>The duration of the block, expressed in Segment Ticks, which are based on `TimestampScale`; see (#timestamp-ticks).If missing, the track's `DefaultDuration` does not apply and no duration information is available in terms of the cues.</summary>
+ public const ulong CueDuration = 0xB2;
+ public static readonly ElementDescriptor CueDurationDescriptor = new ElementDescriptor(CueDuration, nameof(CueDuration), ElementType.UnsignedInteger);
+
+ /// <summary>The `Clusters` containing the referenced `Blocks`.</summary>
+ public const ulong CueReference = 0xDB;
+ public static readonly ElementDescriptor CueReferenceDescriptor = new ElementDescriptor(CueReference, nameof(CueReference), ElementType.MasterElement);
+
+ /// <summary>[Required] The `Segment Position` of the `Cluster` containing the referenced `Block`.</summary>
+ public const ulong CueRefCluster = 0x97;
+ public static readonly ElementDescriptor CueRefClusterDescriptor = new ElementDescriptor(CueRefCluster, nameof(CueRefCluster), ElementType.UnsignedInteger);
+
+ /// <summary>The `Segment Position` of the Codec State corresponding to this referenced element.0 means that the data is taken from the initial `TrackEntry`.</summary>
+ public const ulong CueRefCodecState = 0xEB;
+ public static readonly ElementDescriptor CueRefCodecStateDescriptor = new ElementDescriptor(CueRefCodecState, nameof(CueRefCodecState), ElementType.UnsignedInteger);
+
+ /// <summary>Number of the referenced `Block` of Track X in the specified `Cluster`.</summary>
+ public const ulong CueRefNumber = 0x535F;
+ public static readonly ElementDescriptor CueRefNumberDescriptor = new ElementDescriptor(CueRefNumber, nameof(CueRefNumber), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Timestamp of the referenced `Block`, expressed in Segment Ticks which is based on `TimestampScale`; see (#timestamp-ticks).</summary>
+ public const ulong CueRefTime = 0x96;
+ public static readonly ElementDescriptor CueRefTimeDescriptor = new ElementDescriptor(CueRefTime, nameof(CueRefTime), ElementType.UnsignedInteger);
+
+ /// <summary>The relative position inside the `Cluster` of the referenced `SimpleBlock` or `BlockGroup`with 0 being the first possible position for an element inside that `Cluster`.</summary>
+ public const ulong CueRelativePosition = 0xF0;
+ public static readonly ElementDescriptor CueRelativePositionDescriptor = new ElementDescriptor(CueRelativePosition, nameof(CueRelativePosition), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The track for which a position is given.</summary>
+ public const ulong CueTrack = 0xF7;
+ public static readonly ElementDescriptor CueTrackDescriptor = new ElementDescriptor(CueTrack, nameof(CueTrack), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Contains general information about the `Segment`.</summary>
+ public const ulong Info = 0x1549A966;
+ public static readonly ElementDescriptor InfoDescriptor = new ElementDescriptor(Info, nameof(Info), ElementType.MasterElement);
+
+ /// <summary>The mapping between this `Segment` and asegment value in the given Chapter Codec.</summary>
+ public const ulong ChapterTranslate = 0x6924;
+ public static readonly ElementDescriptor ChapterTranslateDescriptor = new ElementDescriptor(ChapterTranslate, nameof(ChapterTranslate), ElementType.MasterElement);
+
+ /// <summary>[Required] Applies to the chapter codec of the given chapter edition(s); see (#chapprocesscodecid-element).</summary>
+ public const ulong ChapterTranslateCodec = 0x69BF;
+ public static readonly ElementDescriptor ChapterTranslateCodecDescriptor = new ElementDescriptor(ChapterTranslateCodec, nameof(ChapterTranslateCodec), ElementType.UnsignedInteger);
+
+ /// <summary>Specifies a chapter edition UID to which this `ChapterTranslate` applies.</summary>
+ public const ulong ChapterTranslateEditionUID = 0x69FC;
+ public static readonly ElementDescriptor ChapterTranslateEditionUIDDescriptor = new ElementDescriptor(ChapterTranslateEditionUID, nameof(ChapterTranslateEditionUID), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The binary value used to represent this `Segment` in the chapter codec data.The format depends on the `ChapProcessCodecID` used; see (#chapprocesscodecid-element).</summary>
+ public const ulong ChapterTranslateID = 0x69A5;
+ public static readonly ElementDescriptor ChapterTranslateIDDescriptor = new ElementDescriptor(ChapterTranslateID, nameof(ChapterTranslateID), ElementType.Binary);
+
+ /// <summary>The date and time that the `Segment` was created by the muxing application or library.</summary>
+ public const ulong DateUTC = 0x4461;
+ public static readonly ElementDescriptor DateUTCDescriptor = new ElementDescriptor(DateUTC, nameof(DateUTC), ElementType.Date);
+
+ /// <summary>Duration of the `Segment`, expressed in `Segment` Ticks, which are based on `TimestampScale`; see (#timestamp-ticks).</summary>
+ public const ulong Duration = 0x4489;
+ public static readonly ElementDescriptor DurationDescriptor = new ElementDescriptor(Duration, nameof(Duration), ElementType.Float);
+
+ /// <summary>[Required] Muxing application or library (example: "libmatroska-0.4.3").</summary>
+ public const ulong MuxingApp = 0x4D80;
+ public static readonly ElementDescriptor MuxingAppDescriptor = new ElementDescriptor(MuxingApp, nameof(MuxingApp), ElementType.Utf8String);
+
+ /// <summary>A filename corresponding to the file of the next `Linked Segment`.</summary>
+ public const ulong NextFilename = 0x3E83BB;
+ public static readonly ElementDescriptor NextFilenameDescriptor = new ElementDescriptor(NextFilename, nameof(NextFilename), ElementType.Utf8String);
+
+ /// <summary>An ID that identifies the next `Segment` of a `Linked Segment`.</summary>
+ public const ulong NextUUID = 0x3EB923;
+ public static readonly ElementDescriptor NextUUIDDescriptor = new ElementDescriptor(NextUUID, nameof(NextUUID), ElementType.Binary);
+
+ /// <summary>A filename corresponding to the file of the previous `Linked Segment`.</summary>
+ public const ulong PrevFilename = 0x3C83AB;
+ public static readonly ElementDescriptor PrevFilenameDescriptor = new ElementDescriptor(PrevFilename, nameof(PrevFilename), ElementType.Utf8String);
+
+ /// <summary>An ID that identifies the previous `Segment` of a `Linked Segment`.</summary>
+ public const ulong PrevUUID = 0x3CB923;
+ public static readonly ElementDescriptor PrevUUIDDescriptor = new ElementDescriptor(PrevUUID, nameof(PrevUUID), ElementType.Binary);
+
+ /// <summary>A UID that all `Segments` of a `Linked Segment` **MUST** share (128 bits). It is equivalent to a UUID v4 [@!RFC9562] with all bits randomly (or pseudorandomly) chosen. An actual UUID v4 value, where some bits are not random, **MAY** also be used.</summary>
+ public const ulong SegmentFamily = 0x4444;
+ public static readonly ElementDescriptor SegmentFamilyDescriptor = new ElementDescriptor(SegmentFamily, nameof(SegmentFamily), ElementType.Binary);
+
+ /// <summary>A filename corresponding to this `Segment`.</summary>
+ public const ulong SegmentFilename = 0x7384;
+ public static readonly ElementDescriptor SegmentFilenameDescriptor = new ElementDescriptor(SegmentFilename, nameof(SegmentFilename), ElementType.Utf8String);
+
+ /// <summary>A randomly generated UID that identifies the `Segment` amongst many others (128 bits). It is equivalent to a Universally Unique Identifier (UUID) v4 [@!RFC9562] with all bits randomly (or pseudorandomly) chosen. An actual UUID v4 value, where some bits are not random, **MAY** also be used.</summary>
+ public const ulong SegmentUUID = 0x73A4;
+ public static readonly ElementDescriptor SegmentUUIDDescriptor = new ElementDescriptor(SegmentUUID, nameof(SegmentUUID), ElementType.Binary);
+
+ /// <summary>[Required] Base unit for Segment Ticks and Track Ticks, in nanoseconds. A `TimestampScale` value of 1000000 means scaled timestamps in the `Segment` are expressed in milliseconds; see (#timestamps) on how to interpret timestamps.</summary>
+ public const ulong TimestampScale = 0x2AD7B1;
+ public static readonly ElementDescriptor TimestampScaleDescriptor = new ElementDescriptor(TimestampScale, nameof(TimestampScale), ElementType.UnsignedInteger);
+
+ /// <summary>General name of the `Segment`.</summary>
+ public const ulong Title = 0x7BA9;
+ public static readonly ElementDescriptor TitleDescriptor = new ElementDescriptor(Title, nameof(Title), ElementType.Utf8String);
+
+ /// <summary>[Required] Writing application (example: "mkvmerge-0.3.3").</summary>
+ public const ulong WritingApp = 0x5741;
+ public static readonly ElementDescriptor WritingAppDescriptor = new ElementDescriptor(WritingApp, nameof(WritingApp), ElementType.Utf8String);
+
+ /// <summary>Contains seeking information of `Top-Level Elements`; see (#data-layout).</summary>
+ public const ulong SeekHead = 0x114D9B74;
+ public static readonly ElementDescriptor SeekHeadDescriptor = new ElementDescriptor(SeekHead, nameof(SeekHead), ElementType.MasterElement);
+
+ /// <summary>[Required] Contains a single seek entry to an EBML Element.</summary>
+ public const ulong Seek = 0x4DBB;
+ public static readonly ElementDescriptor SeekDescriptor = new ElementDescriptor(Seek, nameof(Seek), ElementType.MasterElement);
+
+ /// <summary>[Required] The binary EBML ID of a `Top-Level Element`.</summary>
+ public const ulong SeekID = 0x53AB;
+ public static readonly ElementDescriptor SeekIDDescriptor = new ElementDescriptor(SeekID, nameof(SeekID), ElementType.Binary);
+
+ /// <summary>[Required] The `Segment Position` ((#segment-position)) of a `Top-Level Element`.</summary>
+ public const ulong SeekPosition = 0x53AC;
+ public static readonly ElementDescriptor SeekPositionDescriptor = new ElementDescriptor(SeekPosition, nameof(SeekPosition), ElementType.UnsignedInteger);
+
+ /// <summary>Element containing metadata describing `Tracks`, `Editions`, `Chapters`, `Attachments`, or the `Segment` as a whole.A list of valid tags can be found in [@?I-D.ietf-cellar-tags].</summary>
+ public const ulong Tags = 0x1254C367;
+ public static readonly ElementDescriptor TagsDescriptor = new ElementDescriptor(Tags, nameof(Tags), ElementType.MasterElement);
+
+ /// <summary>[Required] A single metadata descriptor.</summary>
+ public const ulong Tag = 0x7373;
+ public static readonly ElementDescriptor TagDescriptor = new ElementDescriptor(Tag, nameof(Tag), ElementType.MasterElement);
+
+ /// <summary>[Required] Contains general information about the target.</summary>
+ public const ulong SimpleTag = 0x67C8;
+ public static readonly ElementDescriptor SimpleTagDescriptor = new ElementDescriptor(SimpleTag, nameof(SimpleTag), ElementType.MasterElement);
+
+ /// <summary>The tag value if it is binary. Note that this cannot be used in the same `SimpleTag` as `TagString`.</summary>
+ public const ulong TagBinary = 0x4485;
+ public static readonly ElementDescriptor TagBinaryDescriptor = new ElementDescriptor(TagBinary, nameof(TagBinary), ElementType.Binary);
+
+ /// <summary>[Required] A boolean value to indicate if this is the default/original language to use for the given tag.</summary>
+ public const ulong TagDefault = 0x4484;
+ public static readonly ElementDescriptor TagDefaultDescriptor = new ElementDescriptor(TagDefault, nameof(TagDefault), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] A variant of the `TagDefault` element with a bogus element ID; see (#tagdefault-element).</summary>
+ public const ulong TagDefaultBogus = 0x44B4;
+ public static readonly ElementDescriptor TagDefaultBogusDescriptor = new ElementDescriptor(TagDefaultBogus, nameof(TagDefaultBogus), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Specifies the language of the specified tag in the Matroska languages form; see (#language-codes) on language codes.This element **MUST** be ignored if the `TagLanguageBCP47` element is used within the same `SimpleTag` element.</summary>
+ public const ulong TagLanguage = 0x447A;
+ public static readonly ElementDescriptor TagLanguageDescriptor = new ElementDescriptor(TagLanguage, nameof(TagLanguage), ElementType.AsciiString);
+
+ /// <summary>The language used in the `TagString`,in the form defined in [@!RFC5646]; see (#language-codes) on language codes.If this element is used, then any `TagLanguage` elements used in the same `SimpleTag` **MUST** be ignored.</summary>
+ public const ulong TagLanguageBCP47 = 0x447B;
+ public static readonly ElementDescriptor TagLanguageBCP47Descriptor = new ElementDescriptor(TagLanguageBCP47, nameof(TagLanguageBCP47), ElementType.AsciiString);
+
+ /// <summary>[Required] The name of the tag value that is going to be stored.</summary>
+ public const ulong TagName = 0x45A3;
+ public static readonly ElementDescriptor TagNameDescriptor = new ElementDescriptor(TagName, nameof(TagName), ElementType.Utf8String);
+
+ /// <summary>The tag value.</summary>
+ public const ulong TagString = 0x4487;
+ public static readonly ElementDescriptor TagStringDescriptor = new ElementDescriptor(TagString, nameof(TagString), ElementType.Utf8String);
+
+ /// <summary>[Required] Specifies which other elements the metadata represented by the tag value applies to.If empty or omitted, then the tag value describes everything in the `Segment`.</summary>
+ public const ulong Targets = 0x63C0;
+ public static readonly ElementDescriptor TargetsDescriptor = new ElementDescriptor(Targets, nameof(Targets), ElementType.MasterElement);
+
+ /// <summary>A UID that identifies the Attachment(s) that the tags belong to.</summary>
+ public const ulong TagAttachmentUID = 0x63C6;
+ public static readonly ElementDescriptor TagAttachmentUIDDescriptor = new ElementDescriptor(TagAttachmentUID, nameof(TagAttachmentUID), ElementType.UnsignedInteger);
+
+ /// <summary>A UID that identifies the `Chapter(s)` that the tags belong to.</summary>
+ public const ulong TagChapterUID = 0x63C4;
+ public static readonly ElementDescriptor TagChapterUIDDescriptor = new ElementDescriptor(TagChapterUID, nameof(TagChapterUID), ElementType.UnsignedInteger);
+
+ /// <summary>A UID that identifies the `EditionEntry(s)` that the tags belong to.</summary>
+ public const ulong TagEditionUID = 0x63C9;
+ public static readonly ElementDescriptor TagEditionUIDDescriptor = new ElementDescriptor(TagEditionUID, nameof(TagEditionUID), ElementType.UnsignedInteger);
+
+ /// <summary>A UID that identifies the `Track(s)` that the tags belong to.</summary>
+ public const ulong TagTrackUID = 0x63C5;
+ public static readonly ElementDescriptor TagTrackUIDDescriptor = new ElementDescriptor(TagTrackUID, nameof(TagTrackUID), ElementType.UnsignedInteger);
+
+ /// <summary>An informational string that can be used to display the logical level of the target, such as "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc.</summary>
+ public const ulong TargetType = 0x63CA;
+ public static readonly ElementDescriptor TargetTypeDescriptor = new ElementDescriptor(TargetType, nameof(TargetType), ElementType.AsciiString);
+
+ /// <summary>[Required] A number to indicate the logical level of the target.</summary>
+ public const ulong TargetTypeValue = 0x68CA;
+ public static readonly ElementDescriptor TargetTypeValueDescriptor = new ElementDescriptor(TargetTypeValue, nameof(TargetTypeValue), ElementType.UnsignedInteger);
+
+ /// <summary>A `Top-Level Element` of information with many tracks described.</summary>
+ public const ulong Tracks = 0x1654AE6B;
+ public static readonly ElementDescriptor TracksDescriptor = new ElementDescriptor(Tracks, nameof(Tracks), ElementType.MasterElement);
+
+ /// <summary>[Required] Describes a track with all elements.</summary>
+ public const ulong TrackEntry = 0xAE;
+ public static readonly ElementDescriptor TrackEntryDescriptor = new ElementDescriptor(TrackEntry, nameof(TrackEntry), ElementType.MasterElement);
+
+ /// <summary>The UID of an attachment that is used by this codec.</summary>
+ public const ulong AttachmentLink = 0x7446;
+ public static readonly ElementDescriptor AttachmentLinkDescriptor = new ElementDescriptor(AttachmentLink, nameof(AttachmentLink), ElementType.UnsignedInteger);
+
+ /// <summary>Audio settings.</summary>
+ public const ulong Audio = 0xE1;
+ public static readonly ElementDescriptor AudioDescriptor = new ElementDescriptor(Audio, nameof(Audio), ElementType.MasterElement);
+
+ /// <summary>Bits per sample, mostly used for PCM.</summary>
+ public const ulong BitDepth = 0x6264;
+ public static readonly ElementDescriptor BitDepthDescriptor = new ElementDescriptor(BitDepth, nameof(BitDepth), ElementType.UnsignedInteger);
+
+ /// <summary>Table of horizontal angles for each successive channel.</summary>
+ public const ulong ChannelPositions = 0x7D7B;
+ public static readonly ElementDescriptor ChannelPositionsDescriptor = new ElementDescriptor(ChannelPositions, nameof(ChannelPositions), ElementType.Binary);
+
+ /// <summary>[Required] Numbers of channels in the track.</summary>
+ public const ulong Channels = 0x9F;
+ public static readonly ElementDescriptor ChannelsDescriptor = new ElementDescriptor(Channels, nameof(Channels), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Audio emphasis applied on audio samples. The player **MUST** apply the inverse emphasis to get the proper audio samples.</summary>
+ public const ulong Emphasis = 0x52F1;
+ public static readonly ElementDescriptor EmphasisDescriptor = new ElementDescriptor(Emphasis, nameof(Emphasis), ElementType.UnsignedInteger);
+
+ /// <summary>Real output sampling frequency in Hz that is used for Spectral Band Replication (SBR) techniques.</summary>
+ public const ulong OutputSamplingFrequency = 0x78B5;
+ public static readonly ElementDescriptor OutputSamplingFrequencyDescriptor = new ElementDescriptor(OutputSamplingFrequency, nameof(OutputSamplingFrequency), ElementType.Float);
+
+ /// <summary>[Required] Sampling frequency in Hz.</summary>
+ public const ulong SamplingFrequency = 0xB5;
+ public static readonly ElementDescriptor SamplingFrequencyDescriptor = new ElementDescriptor(SamplingFrequency, nameof(SamplingFrequency), ElementType.Float);
+
+ /// <summary>Contains elements that extend the track format by adding content either to each frame,with `BlockAddID` ((#blockaddid-element)), or to the track as a wholewith `BlockAddIDExtraData`.</summary>
+ public const ulong BlockAdditionMapping = 0x41E4;
+ public static readonly ElementDescriptor BlockAdditionMappingDescriptor = new ElementDescriptor(BlockAdditionMapping, nameof(BlockAdditionMapping), ElementType.MasterElement);
+
+ /// <summary>Extra binary data that the `BlockAddIDType` can use to interpret the `BlockAdditional` data.The interpretation of the binary data depends on the `BlockAddIDType` value and the corresponding `Block Additional Mapping`.</summary>
+ public const ulong BlockAddIDExtraData = 0x41ED;
+ public static readonly ElementDescriptor BlockAddIDExtraDataDescriptor = new ElementDescriptor(BlockAddIDExtraData, nameof(BlockAddIDExtraData), ElementType.Binary);
+
+ /// <summary>A human-friendly name describing the type of `BlockAdditional` data,as defined by the associated `Block Additional Mapping`.</summary>
+ public const ulong BlockAddIDName = 0x41A4;
+ public static readonly ElementDescriptor BlockAddIDNameDescriptor = new ElementDescriptor(BlockAddIDName, nameof(BlockAddIDName), ElementType.AsciiString);
+
+ /// <summary>[Required] Stores the registered identifier of the `Block Additional Mapping`to define how the `BlockAdditional` data should be handled.</summary>
+ public const ulong BlockAddIDType = 0x41E7;
+ public static readonly ElementDescriptor BlockAddIDTypeDescriptor = new ElementDescriptor(BlockAddIDType, nameof(BlockAddIDType), ElementType.UnsignedInteger);
+
+ /// <summary>If the track format extension needs content beside frames,the value refers to the `BlockAddID` ((#blockaddid-element)) value being described.</summary>
+ public const ulong BlockAddIDValue = 0x41F0;
+ public static readonly ElementDescriptor BlockAddIDValueDescriptor = new ElementDescriptor(BlockAddIDValue, nameof(BlockAddIDValue), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Set to 1 if the codec can decode potentially damaged data.</summary>
+ public const ulong CodecDecodeAll = 0xAA;
+ public static readonly ElementDescriptor CodecDecodeAllDescriptor = new ElementDescriptor(CodecDecodeAll, nameof(CodecDecodeAll), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The built-in delay for the codec, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).It represents the number of codec samples that will be discarded by the decoder during playback.This timestamp value **MUST** be subtracted from each frame timestamp in order to get the timestamp that will be actually played.The value **SHOULD** be small so the muxing of tracks with the same actual timestamp are in the same `Cluster`.</summary>
+ public const ulong CodecDelay = 0x56AA;
+ public static readonly ElementDescriptor CodecDelayDescriptor = new ElementDescriptor(CodecDelay, nameof(CodecDelay), ElementType.UnsignedInteger);
+
+ /// <summary>A URL to download information about the codec used.</summary>
+ public const ulong CodecDownloadURL = 0x26B240;
+ public static readonly ElementDescriptor CodecDownloadURLDescriptor = new ElementDescriptor(CodecDownloadURL, nameof(CodecDownloadURL), ElementType.AsciiString);
+
+ /// <summary>[Required] An ID corresponding to the codec;see [@?I-D.ietf-cellar-codec] for more info.</summary>
+ public const ulong CodecID = 0x86;
+ public static readonly ElementDescriptor CodecIDDescriptor = new ElementDescriptor(CodecID, nameof(CodecID), ElementType.AsciiString);
+
+ /// <summary>A URL to find information about the codec used.</summary>
+ public const ulong CodecInfoURL = 0x3B4040;
+ public static readonly ElementDescriptor CodecInfoURLDescriptor = new ElementDescriptor(CodecInfoURL, nameof(CodecInfoURL), ElementType.AsciiString);
+
+ /// <summary>A human-readable string specifying the codec.</summary>
+ public const ulong CodecName = 0x258688;
+ public static readonly ElementDescriptor CodecNameDescriptor = new ElementDescriptor(CodecName, nameof(CodecName), ElementType.Utf8String);
+
+ /// <summary>Private data only known to the codec.</summary>
+ public const ulong CodecPrivate = 0x63A2;
+ public static readonly ElementDescriptor CodecPrivateDescriptor = new ElementDescriptor(CodecPrivate, nameof(CodecPrivate), ElementType.Binary);
+
+ /// <summary>A string describing the encoding setting used.</summary>
+ public const ulong CodecSettings = 0x3A9697;
+ public static readonly ElementDescriptor CodecSettingsDescriptor = new ElementDescriptor(CodecSettings, nameof(CodecSettings), ElementType.Utf8String);
+
+ /// <summary>Settings for several content encoding mechanisms like compression or encryption.</summary>
+ public const ulong ContentEncodings = 0x6D80;
+ public static readonly ElementDescriptor ContentEncodingsDescriptor = new ElementDescriptor(ContentEncodings, nameof(ContentEncodings), ElementType.MasterElement);
+
+ /// <summary>[Required] Settings for one content encoding like compression or encryption.</summary>
+ public const ulong ContentEncoding = 0x6240;
+ public static readonly ElementDescriptor ContentEncodingDescriptor = new ElementDescriptor(ContentEncoding, nameof(ContentEncoding), ElementType.MasterElement);
+
+ /// <summary>Settings describing the compression used.This element **MUST** be present if the value of `ContentEncodingType` is 0 and absent otherwise.Each block **MUST** be decompressable, even if no previous block is available in order to not prevent seeking.</summary>
+ public const ulong ContentCompression = 0x5034;
+ public static readonly ElementDescriptor ContentCompressionDescriptor = new ElementDescriptor(ContentCompression, nameof(ContentCompression), ElementType.MasterElement);
+
+ /// <summary>[Required] The compression algorithm used.</summary>
+ public const ulong ContentCompAlgo = 0x4254;
+ public static readonly ElementDescriptor ContentCompAlgoDescriptor = new ElementDescriptor(ContentCompAlgo, nameof(ContentCompAlgo), ElementType.UnsignedInteger);
+
+ /// <summary>Settings that might be needed by the decompressor. For Header Stripping (`ContentCompAlgo`=3),the bytes that were removed from the beginning of each frame of the track.</summary>
+ public const ulong ContentCompSettings = 0x4255;
+ public static readonly ElementDescriptor ContentCompSettingsDescriptor = new ElementDescriptor(ContentCompSettings, nameof(ContentCompSettings), ElementType.Binary);
+
+ /// <summary>[Required] Defines the order to apply each `ContentEncoding` of the `ContentEncodings`.The decoder/demuxer **MUST** start with the `ContentEncoding` with the highest `ContentEncodingOrder` and work its way down to the `ContentEncoding` with the lowest `ContentEncodingOrder`.This value **MUST** be unique for each `ContentEncoding` found in the `ContentEncodings` of this `TrackEntry`.</summary>
+ public const ulong ContentEncodingOrder = 0x5031;
+ public static readonly ElementDescriptor ContentEncodingOrderDescriptor = new ElementDescriptor(ContentEncodingOrder, nameof(ContentEncodingOrder), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] A bit field that describes which elements have been modified in this way. Values (big-endian) can be OR'ed.</summary>
+ public const ulong ContentEncodingScope = 0x5032;
+ public static readonly ElementDescriptor ContentEncodingScopeDescriptor = new ElementDescriptor(ContentEncodingScope, nameof(ContentEncodingScope), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] A value describing the kind of transformation that is applied.</summary>
+ public const ulong ContentEncodingType = 0x5033;
+ public static readonly ElementDescriptor ContentEncodingTypeDescriptor = new ElementDescriptor(ContentEncodingType, nameof(ContentEncodingType), ElementType.UnsignedInteger);
+
+ /// <summary>Settings describing the encryption used.This element **MUST** be present if the value of `ContentEncodingType` is 1 (encryption) and **MUST** be ignored otherwise.A `Matroska Player` **MAY** support encryption.</summary>
+ public const ulong ContentEncryption = 0x5035;
+ public static readonly ElementDescriptor ContentEncryptionDescriptor = new ElementDescriptor(ContentEncryption, nameof(ContentEncryption), ElementType.MasterElement);
+
+ /// <summary>Settings describing the encryption algorithm used.</summary>
+ public const ulong ContentEncAESSettings = 0x47E7;
+ public static readonly ElementDescriptor ContentEncAESSettingsDescriptor = new ElementDescriptor(ContentEncAESSettings, nameof(ContentEncAESSettings), ElementType.MasterElement);
+
+ /// <summary>[Required] The AES cipher mode used in the encryption.</summary>
+ public const ulong AESSettingsCipherMode = 0x47E8;
+ public static readonly ElementDescriptor AESSettingsCipherModeDescriptor = new ElementDescriptor(AESSettingsCipherMode, nameof(AESSettingsCipherMode), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The encryption algorithm used.</summary>
+ public const ulong ContentEncAlgo = 0x47E1;
+ public static readonly ElementDescriptor ContentEncAlgoDescriptor = new ElementDescriptor(ContentEncAlgo, nameof(ContentEncAlgo), ElementType.UnsignedInteger);
+
+ /// <summary>For public key algorithms, the ID of the public key that the data was encrypted with.</summary>
+ public const ulong ContentEncKeyID = 0x47E2;
+ public static readonly ElementDescriptor ContentEncKeyIDDescriptor = new ElementDescriptor(ContentEncKeyID, nameof(ContentEncKeyID), ElementType.Binary);
+
+ /// <summary>The algorithm used for the signature.</summary>
+ public const ulong ContentSigAlgo = 0x47E5;
+ public static readonly ElementDescriptor ContentSigAlgoDescriptor = new ElementDescriptor(ContentSigAlgo, nameof(ContentSigAlgo), ElementType.UnsignedInteger);
+
+ /// <summary>The hash algorithm used for the signature.</summary>
+ public const ulong ContentSigHashAlgo = 0x47E6;
+ public static readonly ElementDescriptor ContentSigHashAlgoDescriptor = new ElementDescriptor(ContentSigHashAlgo, nameof(ContentSigHashAlgo), ElementType.UnsignedInteger);
+
+ /// <summary>This is the ID of the private key that the data was signed with.</summary>
+ public const ulong ContentSigKeyID = 0x47E4;
+ public static readonly ElementDescriptor ContentSigKeyIDDescriptor = new ElementDescriptor(ContentSigKeyID, nameof(ContentSigKeyID), ElementType.Binary);
+
+ /// <summary>A cryptographic signature of the contents.</summary>
+ public const ulong ContentSignature = 0x47E3;
+ public static readonly ElementDescriptor ContentSignatureDescriptor = new ElementDescriptor(ContentSignature, nameof(ContentSignature), ElementType.Binary);
+
+ /// <summary>The period between two successive fields at the output of the decoding process, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).See (#defaultdecodedfieldduration) for more information.</summary>
+ public const ulong DefaultDecodedFieldDuration = 0x234E7A;
+ public static readonly ElementDescriptor DefaultDecodedFieldDurationDescriptor = new ElementDescriptor(DefaultDecodedFieldDuration, nameof(DefaultDecodedFieldDuration), ElementType.UnsignedInteger);
+
+ /// <summary>Number of nanoseconds per frame, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks)("frame" in the Matroska sense -- one element put into a (Simple)Block).</summary>
+ public const ulong DefaultDuration = 0x23E383;
+ public static readonly ElementDescriptor DefaultDurationDescriptor = new ElementDescriptor(DefaultDuration, nameof(DefaultDuration), ElementType.UnsignedInteger);
+
+ /// <summary>Set to 1 if and only if the track contains commentary.</summary>
+ public const ulong FlagCommentary = 0x55AF;
+ public static readonly ElementDescriptor FlagCommentaryDescriptor = new ElementDescriptor(FlagCommentary, nameof(FlagCommentary), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Set to 1 if the track (audio, video, or subtitles) is eligible for automatic selection by the player; see (#default-track-selection) for more details.</summary>
+ public const ulong FlagDefault = 0x88;
+ public static readonly ElementDescriptor FlagDefaultDescriptor = new ElementDescriptor(FlagDefault, nameof(FlagDefault), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Set to 1 if the track is usable. It is possible to turn a track that is not usable into a usable track using chapter codecs or control tracks.</summary>
+ public const ulong FlagEnabled = 0xB9;
+ public static readonly ElementDescriptor FlagEnabledDescriptor = new ElementDescriptor(FlagEnabled, nameof(FlagEnabled), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Applies only to subtitles. Set to 1 if the track is eligible for automatic selection by the player if it matches the user's language preference,even if the user's preferences would not normally enable subtitles with the selected audio track;this can be used for tracks containing only translations of audio in foreign languages or on-screen text.See (#default-track-selection) for more details.</summary>
+ public const ulong FlagForced = 0x55AA;
+ public static readonly ElementDescriptor FlagForcedDescriptor = new ElementDescriptor(FlagForced, nameof(FlagForced), ElementType.UnsignedInteger);
+
+ /// <summary>Set to 1 if and only if the track is suitable for users with hearing impairments.</summary>
+ public const ulong FlagHearingImpaired = 0x55AB;
+ public static readonly ElementDescriptor FlagHearingImpairedDescriptor = new ElementDescriptor(FlagHearingImpaired, nameof(FlagHearingImpaired), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Set to 1 if the track **MAY** contain blocks that use lacing.When set to 0, all blocks **MUST** have their lacing flags set to "no lacing"; see (#block-lacing) on 'Block' Lacing.</summary>
+ public const ulong FlagLacing = 0x9C;
+ public static readonly ElementDescriptor FlagLacingDescriptor = new ElementDescriptor(FlagLacing, nameof(FlagLacing), ElementType.UnsignedInteger);
+
+ /// <summary>Set to 1 if and only if the track is in the content's original language.</summary>
+ public const ulong FlagOriginal = 0x55AE;
+ public static readonly ElementDescriptor FlagOriginalDescriptor = new ElementDescriptor(FlagOriginal, nameof(FlagOriginal), ElementType.UnsignedInteger);
+
+ /// <summary>Set to 1 if and only if the track contains textual descriptions of video content.</summary>
+ public const ulong FlagTextDescriptions = 0x55AD;
+ public static readonly ElementDescriptor FlagTextDescriptionsDescriptor = new ElementDescriptor(FlagTextDescriptions, nameof(FlagTextDescriptions), ElementType.UnsignedInteger);
+
+ /// <summary>Set to 1 if and only if the track is suitable for users with visual impairments.</summary>
+ public const ulong FlagVisualImpaired = 0x55AC;
+ public static readonly ElementDescriptor FlagVisualImpairedDescriptor = new ElementDescriptor(FlagVisualImpaired, nameof(FlagVisualImpaired), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The language of the track,in the Matroska languages form; see (#language-codes) on language codes.This element **MUST** be ignored if the `LanguageBCP47` element is used in the same `TrackEntry`.</summary>
+ public const ulong Language = 0x22B59C;
+ public static readonly ElementDescriptor LanguageDescriptor = new ElementDescriptor(Language, nameof(Language), ElementType.AsciiString);
+
+ /// <summary>The language of the track,in the form defined in [@!RFC5646]; see (#language-codes) on language codes.If this element is used, then any `Language` elements used in the same `TrackEntry` **MUST** be ignored.</summary>
+ public const ulong LanguageBCP47 = 0x22B59D;
+ public static readonly ElementDescriptor LanguageBCP47Descriptor = new ElementDescriptor(LanguageBCP47, nameof(LanguageBCP47), ElementType.AsciiString);
+
+ /// <summary>[Required] The maximum value of `BlockAddID` ((#blockaddid-element)).A value of 0 means there is no `BlockAdditions` ((#blockadditions-element)) for this track.</summary>
+ public const ulong MaxBlockAdditionID = 0x55EE;
+ public static readonly ElementDescriptor MaxBlockAdditionIDDescriptor = new ElementDescriptor(MaxBlockAdditionID, nameof(MaxBlockAdditionID), ElementType.UnsignedInteger);
+
+ /// <summary>The maximum cache size necessary to store referenced frames in and the current frame.0 means no cache is needed.</summary>
+ public const ulong MaxCache = 0x6DF8;
+ public static readonly ElementDescriptor MaxCacheDescriptor = new ElementDescriptor(MaxCache, nameof(MaxCache), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The minimum number of frames a player should be able to cache during playback.If set to 0, the reference pseudo-cache system is not used.</summary>
+ public const ulong MinCache = 0x6DE7;
+ public static readonly ElementDescriptor MinCacheDescriptor = new ElementDescriptor(MinCache, nameof(MinCache), ElementType.UnsignedInteger);
+
+ /// <summary>A human-readable track name.</summary>
+ public const ulong Name = 0x536E;
+ public static readonly ElementDescriptor NameDescriptor = new ElementDescriptor(Name, nameof(Name), ElementType.Utf8String);
+
+ /// <summary>[Required] After a discontinuity, the duration of the datathat the decoder **MUST** decode before the decoded data is valid, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).</summary>
+ public const ulong SeekPreRoll = 0x56BB;
+ public static readonly ElementDescriptor SeekPreRollDescriptor = new ElementDescriptor(SeekPreRoll, nameof(SeekPreRoll), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The track number as used in the `Block` Header.</summary>
+ public const ulong TrackNumber = 0xD7;
+ public static readonly ElementDescriptor TrackNumberDescriptor = new ElementDescriptor(TrackNumber, nameof(TrackNumber), ElementType.UnsignedInteger);
+
+ /// <summary>A value to add to the `Block`'s Timestamp, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).This can be used to adjust the playback offset of a track.</summary>
+ public const ulong TrackOffset = 0x537F;
+ public static readonly ElementDescriptor TrackOffsetDescriptor = new ElementDescriptor(TrackOffset, nameof(TrackOffset), ElementType.SignedInteger);
+
+ /// <summary>Operation that needs to be applied on tracks to create this virtual track. For more details, see (#track-operation).</summary>
+ public const ulong TrackOperation = 0xE2;
+ public static readonly ElementDescriptor TrackOperationDescriptor = new ElementDescriptor(TrackOperation, nameof(TrackOperation), ElementType.MasterElement);
+
+ /// <summary>Contains the list of all video plane tracks that need to be combined to create this 3D track.</summary>
+ public const ulong TrackCombinePlanes = 0xE3;
+ public static readonly ElementDescriptor TrackCombinePlanesDescriptor = new ElementDescriptor(TrackCombinePlanes, nameof(TrackCombinePlanes), ElementType.MasterElement);
+
+ /// <summary>[Required] Contains a video plane track that needs to be combined to create this 3D track.</summary>
+ public const ulong TrackPlane = 0xE4;
+ public static readonly ElementDescriptor TrackPlaneDescriptor = new ElementDescriptor(TrackPlane, nameof(TrackPlane), ElementType.MasterElement);
+
+ /// <summary>[Required] The kind of plane this track corresponds to.</summary>
+ public const ulong TrackPlaneType = 0xE6;
+ public static readonly ElementDescriptor TrackPlaneTypeDescriptor = new ElementDescriptor(TrackPlaneType, nameof(TrackPlaneType), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The `TrackUID` number of the track representing the plane.</summary>
+ public const ulong TrackPlaneUID = 0xE5;
+ public static readonly ElementDescriptor TrackPlaneUIDDescriptor = new ElementDescriptor(TrackPlaneUID, nameof(TrackPlaneUID), ElementType.UnsignedInteger);
+
+ /// <summary>Contains the list of all tracks whose `Blocks` need to be combined to create this virtual track.</summary>
+ public const ulong TrackJoinBlocks = 0xE9;
+ public static readonly ElementDescriptor TrackJoinBlocksDescriptor = new ElementDescriptor(TrackJoinBlocks, nameof(TrackJoinBlocks), ElementType.MasterElement);
+
+ /// <summary>[Required] The `TrackUID` number of a track whose blocks are used to create this virtual track.</summary>
+ public const ulong TrackJoinUID = 0xED;
+ public static readonly ElementDescriptor TrackJoinUIDDescriptor = new ElementDescriptor(TrackJoinUID, nameof(TrackJoinUID), ElementType.UnsignedInteger);
+
+ /// <summary>Specify that this track is an overlay track for the `Track` specified (in the u-integer).This means that when this track has a gap on `SilentTracks`, the overlay track should be used instead. The order of multiple `TrackOverlay` matters; the first one is the one that should be used.If the first one is not found, it should be the second, etc.</summary>
+ public const ulong TrackOverlay = 0x6FAB;
+ public static readonly ElementDescriptor TrackOverlayDescriptor = new ElementDescriptor(TrackOverlay, nameof(TrackOverlay), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The scale to apply on this track to work at normal speed in relation with other tracks(mostly used to adjust video speed when the audio length differs).</summary>
+ public const ulong TrackTimestampScale = 0x23314F;
+ public static readonly ElementDescriptor TrackTimestampScaleDescriptor = new ElementDescriptor(TrackTimestampScale, nameof(TrackTimestampScale), ElementType.Float);
+
+ /// <summary>The mapping between this `TrackEntry` and a track value in the given Chapter Codec.</summary>
+ public const ulong TrackTranslate = 0x6624;
+ public static readonly ElementDescriptor TrackTranslateDescriptor = new ElementDescriptor(TrackTranslate, nameof(TrackTranslate), ElementType.MasterElement);
+
+ /// <summary>[Required] Applies to the chapter codec of the given chapter edition(s); see (#chapprocesscodecid-element).</summary>
+ public const ulong TrackTranslateCodec = 0x66BF;
+ public static readonly ElementDescriptor TrackTranslateCodecDescriptor = new ElementDescriptor(TrackTranslateCodec, nameof(TrackTranslateCodec), ElementType.UnsignedInteger);
+
+ /// <summary>Specifies a chapter edition UID to which this `TrackTranslate` applies.</summary>
+ public const ulong TrackTranslateEditionUID = 0x66FC;
+ public static readonly ElementDescriptor TrackTranslateEditionUIDDescriptor = new ElementDescriptor(TrackTranslateEditionUID, nameof(TrackTranslateEditionUID), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The binary value used to represent this `TrackEntry` in the chapter codec data.The format depends on the `ChapProcessCodecID` used; see (#chapprocesscodecid-element).</summary>
+ public const ulong TrackTranslateTrackID = 0x66A5;
+ public static readonly ElementDescriptor TrackTranslateTrackIDDescriptor = new ElementDescriptor(TrackTranslateTrackID, nameof(TrackTranslateTrackID), ElementType.Binary);
+
+ /// <summary>[Required] The `TrackType` defines the type of each frame found in the `Track`.The value **SHOULD** be stored on 1 octet.</summary>
+ public const ulong TrackType = 0x83;
+ public static readonly ElementDescriptor TrackTypeDescriptor = new ElementDescriptor(TrackType, nameof(TrackType), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] A UID that identifies the `Track`.</summary>
+ public const ulong TrackUID = 0x73C5;
+ public static readonly ElementDescriptor TrackUIDDescriptor = new ElementDescriptor(TrackUID, nameof(TrackUID), ElementType.UnsignedInteger);
+
+ /// <summary>The `SegmentUUID` of the `Segment` containing the track identified by MasterTrackUID. See [@?DivXTrickTrack].</summary>
+ public const ulong TrickMasterTrackSegmentUID = 0xC4;
+ public static readonly ElementDescriptor TrickMasterTrackSegmentUIDDescriptor = new ElementDescriptor(TrickMasterTrackSegmentUID, nameof(TrickMasterTrackSegmentUID), ElementType.Binary);
+
+ /// <summary>The `TrackUID` of the video track in the paired EBML structure that corresponds to this Smooth FF/RW track. See [@?DivXTrickTrack].</summary>
+ public const ulong TrickMasterTrackUID = 0xC7;
+ public static readonly ElementDescriptor TrickMasterTrackUIDDescriptor = new ElementDescriptor(TrickMasterTrackUID, nameof(TrickMasterTrackUID), ElementType.UnsignedInteger);
+
+ /// <summary>Set to 1 if this video track is a Smooth FF/RW track. If set to 1, `MasterTrackUID` and `MasterTrackSegUID` should be present, and`BlockGroups` for this track must contain ReferenceFrame structures.Otherwise, TrickTrackUID and TrickTrackSegUID must be present if this track has a corresponding Smooth FF/RW track. See [@?DivXTrickTrack].</summary>
+ public const ulong TrickTrackFlag = 0xC6;
+ public static readonly ElementDescriptor TrickTrackFlagDescriptor = new ElementDescriptor(TrickTrackFlag, nameof(TrickTrackFlag), ElementType.UnsignedInteger);
+
+ /// <summary>The `SegmentUUID` of the `Segment` containing the track identified by TrickTrackUID. See [@?DivXTrickTrack].</summary>
+ public const ulong TrickTrackSegmentUID = 0xC1;
+ public static readonly ElementDescriptor TrickTrackSegmentUIDDescriptor = new ElementDescriptor(TrickTrackSegmentUID, nameof(TrickTrackSegmentUID), ElementType.Binary);
+
+ /// <summary>The `TrackUID` of the Smooth FF/RW video in the paired EBML structure corresponding to this video track. See [@?DivXTrickTrack].</summary>
+ public const ulong TrickTrackUID = 0xC0;
+ public static readonly ElementDescriptor TrickTrackUIDDescriptor = new ElementDescriptor(TrickTrackUID, nameof(TrickTrackUID), ElementType.UnsignedInteger);
+
+ /// <summary>Video settings.</summary>
+ public const ulong Video = 0xE0;
+ public static readonly ElementDescriptor VideoDescriptor = new ElementDescriptor(Video, nameof(Video), ElementType.MasterElement);
+
+ /// <summary>[Required] Indicates whether the `BlockAdditional` element with `BlockAddID` of "1"contains Alpha data as defined by the Codec Mapping for the `CodecID`.Undefined values (i.e., values other than 0 or 1) **SHOULD NOT** be used, as thebehavior of known implementations is different.</summary>
+ public const ulong AlphaMode = 0x53C0;
+ public static readonly ElementDescriptor AlphaModeDescriptor = new ElementDescriptor(AlphaMode, nameof(AlphaMode), ElementType.UnsignedInteger);
+
+ /// <summary>Specifies the possible modifications to the aspect ratio.</summary>
+ public const ulong AspectRatioType = 0x54B3;
+ public static readonly ElementDescriptor AspectRatioTypeDescriptor = new ElementDescriptor(AspectRatioType, nameof(AspectRatioType), ElementType.UnsignedInteger);
+
+ /// <summary>Settings describing the color format.</summary>
+ public const ulong Colour = 0x55B0;
+ public static readonly ElementDescriptor ColourDescriptor = new ElementDescriptor(Colour, nameof(Colour), ElementType.MasterElement);
+
+ /// <summary>[Required] Number of decoded bits per channel. A value of 0 indicates that the `BitsPerChannel` is unspecified.</summary>
+ public const ulong BitsPerChannel = 0x55B2;
+ public static readonly ElementDescriptor BitsPerChannelDescriptor = new ElementDescriptor(BitsPerChannel, nameof(BitsPerChannel), ElementType.UnsignedInteger);
+
+ /// <summary>The number of pixels to remove in the Cb channel for every pixel not removed horizontally.This is additive with `ChromaSubsamplingHorz`.Example: For video with 4:2:1 chromasubsampling, the `ChromaSubsamplingHorz` **SHOULD** be set to 1, and `CbSubsamplingHorz` **SHOULD** be set to 1.</summary>
+ public const ulong CbSubsamplingHorz = 0x55B5;
+ public static readonly ElementDescriptor CbSubsamplingHorzDescriptor = new ElementDescriptor(CbSubsamplingHorz, nameof(CbSubsamplingHorz), ElementType.UnsignedInteger);
+
+ /// <summary>The number of pixels to remove in the Cb channel for every pixel not removed vertically.This is additive with `ChromaSubsamplingVert`.</summary>
+ public const ulong CbSubsamplingVert = 0x55B6;
+ public static readonly ElementDescriptor CbSubsamplingVertDescriptor = new ElementDescriptor(CbSubsamplingVert, nameof(CbSubsamplingVert), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] How chroma is subsampled horizontally.</summary>
+ public const ulong ChromaSitingHorz = 0x55B7;
+ public static readonly ElementDescriptor ChromaSitingHorzDescriptor = new ElementDescriptor(ChromaSitingHorz, nameof(ChromaSitingHorz), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] How chroma is subsampled vertically.</summary>
+ public const ulong ChromaSitingVert = 0x55B8;
+ public static readonly ElementDescriptor ChromaSitingVertDescriptor = new ElementDescriptor(ChromaSitingVert, nameof(ChromaSitingVert), ElementType.UnsignedInteger);
+
+ /// <summary>The number of pixels to remove in the Cr and Cb channels for every pixel not removed horizontally. Example: For video with 4:2:0 chroma subsampling, the `ChromaSubsamplingHorz`**SHOULD** be set to 1.</summary>
+ public const ulong ChromaSubsamplingHorz = 0x55B3;
+ public static readonly ElementDescriptor ChromaSubsamplingHorzDescriptor = new ElementDescriptor(ChromaSubsamplingHorz, nameof(ChromaSubsamplingHorz), ElementType.UnsignedInteger);
+
+ /// <summary>The number of pixels to remove in the Cr and Cb channels for every pixel not removed vertically.Example: For video with 4:2:0 chroma subsampling, the `ChromaSubsamplingVert`**SHOULD** be set to 1.</summary>
+ public const ulong ChromaSubsamplingVert = 0x55B4;
+ public static readonly ElementDescriptor ChromaSubsamplingVertDescriptor = new ElementDescriptor(ChromaSubsamplingVert, nameof(ChromaSubsamplingVert), ElementType.UnsignedInteger);
+
+ /// <summary>SMPTE 2086 mastering data.</summary>
+ public const ulong MasteringMetadata = 0x55D0;
+ public static readonly ElementDescriptor MasteringMetadataDescriptor = new ElementDescriptor(MasteringMetadata, nameof(MasteringMetadata), ElementType.MasterElement);
+
+ /// <summary>Maximum luminance. Represented in candelas per square meter (cd/m^2^).</summary>
+ public const ulong LuminanceMax = 0x55D9;
+ public static readonly ElementDescriptor LuminanceMaxDescriptor = new ElementDescriptor(LuminanceMax, nameof(LuminanceMax), ElementType.Float);
+
+ /// <summary>Minimum luminance. Represented in candelas per square meter (cd/m^2^).</summary>
+ public const ulong LuminanceMin = 0x55DA;
+ public static readonly ElementDescriptor LuminanceMinDescriptor = new ElementDescriptor(LuminanceMin, nameof(LuminanceMin), ElementType.Float);
+
+ /// <summary>Blue X chromaticity coordinate, as defined by [@!CIE-1931].</summary>
+ public const ulong PrimaryBChromaticityX = 0x55D5;
+ public static readonly ElementDescriptor PrimaryBChromaticityXDescriptor = new ElementDescriptor(PrimaryBChromaticityX, nameof(PrimaryBChromaticityX), ElementType.Float);
+
+ /// <summary>Blue Y chromaticity coordinate, as defined by [@!CIE-1931].</summary>
+ public const ulong PrimaryBChromaticityY = 0x55D6;
+ public static readonly ElementDescriptor PrimaryBChromaticityYDescriptor = new ElementDescriptor(PrimaryBChromaticityY, nameof(PrimaryBChromaticityY), ElementType.Float);
+
+ /// <summary>Green X chromaticity coordinate, as defined by [@!CIE-1931].</summary>
+ public const ulong PrimaryGChromaticityX = 0x55D3;
+ public static readonly ElementDescriptor PrimaryGChromaticityXDescriptor = new ElementDescriptor(PrimaryGChromaticityX, nameof(PrimaryGChromaticityX), ElementType.Float);
+
+ /// <summary>Green Y chromaticity coordinate, as defined by [@!CIE-1931].</summary>
+ public const ulong PrimaryGChromaticityY = 0x55D4;
+ public static readonly ElementDescriptor PrimaryGChromaticityYDescriptor = new ElementDescriptor(PrimaryGChromaticityY, nameof(PrimaryGChromaticityY), ElementType.Float);
+
+ /// <summary>Red X chromaticity coordinate, as defined by [@!CIE-1931].</summary>
+ public const ulong PrimaryRChromaticityX = 0x55D1;
+ public static readonly ElementDescriptor PrimaryRChromaticityXDescriptor = new ElementDescriptor(PrimaryRChromaticityX, nameof(PrimaryRChromaticityX), ElementType.Float);
+
+ /// <summary>Red Y chromaticity coordinate, as defined by [@!CIE-1931].</summary>
+ public const ulong PrimaryRChromaticityY = 0x55D2;
+ public static readonly ElementDescriptor PrimaryRChromaticityYDescriptor = new ElementDescriptor(PrimaryRChromaticityY, nameof(PrimaryRChromaticityY), ElementType.Float);
+
+ /// <summary>White X chromaticity coordinate, as defined by [@!CIE-1931].</summary>
+ public const ulong WhitePointChromaticityX = 0x55D7;
+ public static readonly ElementDescriptor WhitePointChromaticityXDescriptor = new ElementDescriptor(WhitePointChromaticityX, nameof(WhitePointChromaticityX), ElementType.Float);
+
+ /// <summary>White Y chromaticity coordinate, as defined by [@!CIE-1931].</summary>
+ public const ulong WhitePointChromaticityY = 0x55D8;
+ public static readonly ElementDescriptor WhitePointChromaticityYDescriptor = new ElementDescriptor(WhitePointChromaticityY, nameof(WhitePointChromaticityY), ElementType.Float);
+
+ /// <summary>[Required] The Matrix Coefficients of the video used to derive luma and chroma values from red, green, and blue color primaries.For clarity, the value and meanings for `MatrixCoefficients` are adopted from Table 4 of [@!ITU-H.273].</summary>
+ public const ulong MatrixCoefficients = 0x55B1;
+ public static readonly ElementDescriptor MatrixCoefficientsDescriptor = new ElementDescriptor(MatrixCoefficients, nameof(MatrixCoefficients), ElementType.UnsignedInteger);
+
+ /// <summary>Maximum brightness of a single pixel (Maximum Content Light Level)in candelas per square meter (cd/m^2^).</summary>
+ public const ulong MaxCLL = 0x55BC;
+ public static readonly ElementDescriptor MaxCLLDescriptor = new ElementDescriptor(MaxCLL, nameof(MaxCLL), ElementType.UnsignedInteger);
+
+ /// <summary>Maximum brightness of a single full frame (Maximum Frame-Average Light Level)in candelas per square meter (cd/m^2^).</summary>
+ public const ulong MaxFALL = 0x55BD;
+ public static readonly ElementDescriptor MaxFALLDescriptor = new ElementDescriptor(MaxFALL, nameof(MaxFALL), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The color primaries of the video. For clarity,the value and meanings for `Primaries` are adopted from Table 2 of [@!ITU-H.273].</summary>
+ public const ulong Primaries = 0x55BB;
+ public static readonly ElementDescriptor PrimariesDescriptor = new ElementDescriptor(Primaries, nameof(Primaries), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Clipping of the color ranges.</summary>
+ public const ulong Range = 0x55B9;
+ public static readonly ElementDescriptor RangeDescriptor = new ElementDescriptor(Range, nameof(Range), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The transfer characteristics of the video. For clarity,the value and meanings for `TransferCharacteristics` are adopted from Table 3 of [@!ITU-H.273].</summary>
+ public const ulong TransferCharacteristics = 0x55BA;
+ public static readonly ElementDescriptor TransferCharacteristicsDescriptor = new ElementDescriptor(TransferCharacteristics, nameof(TransferCharacteristics), ElementType.UnsignedInteger);
+
+ /// <summary>Height of the video frames to display. Applies to the video frame after cropping (PixelCrop* Elements).</summary>
+ public const ulong DisplayHeight = 0x54BA;
+ public static readonly ElementDescriptor DisplayHeightDescriptor = new ElementDescriptor(DisplayHeight, nameof(DisplayHeight), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] How `DisplayWidth` and `DisplayHeight` are interpreted.</summary>
+ public const ulong DisplayUnit = 0x54B2;
+ public static readonly ElementDescriptor DisplayUnitDescriptor = new ElementDescriptor(DisplayUnit, nameof(DisplayUnit), ElementType.UnsignedInteger);
+
+ /// <summary>Width of the video frames to display. Applies to the video frame after cropping (PixelCrop* Elements).</summary>
+ public const ulong DisplayWidth = 0x54B0;
+ public static readonly ElementDescriptor DisplayWidthDescriptor = new ElementDescriptor(DisplayWidth, nameof(DisplayWidth), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Specifies the field ordering of video frames in this track.</summary>
+ public const ulong FieldOrder = 0x9D;
+ public static readonly ElementDescriptor FieldOrderDescriptor = new ElementDescriptor(FieldOrder, nameof(FieldOrder), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Specifies whether the video frames in this track are interlaced.</summary>
+ public const ulong FlagInterlaced = 0x9A;
+ public static readonly ElementDescriptor FlagInterlacedDescriptor = new ElementDescriptor(FlagInterlaced, nameof(FlagInterlaced), ElementType.UnsignedInteger);
+
+ /// <summary>Number of frames per second. This value is informational only. It is intended for constant frame rate streams and should not beused for a variable frame rate `TrackEntry`.</summary>
+ public const ulong FrameRate = 0x2383E3;
+ public static readonly ElementDescriptor FrameRateDescriptor = new ElementDescriptor(FrameRate, nameof(FrameRate), ElementType.Float);
+
+ /// <summary>Gamma value.</summary>
+ public const ulong GammaValue = 0x2FB523;
+ public static readonly ElementDescriptor GammaValueDescriptor = new ElementDescriptor(GammaValue, nameof(GammaValue), ElementType.Float);
+
+ /// <summary>Bogus `StereoMode` value used in old versions of [@?libmatroska].</summary>
+ public const ulong OldStereoMode = 0x53B9;
+ public static readonly ElementDescriptor OldStereoModeDescriptor = new ElementDescriptor(OldStereoMode, nameof(OldStereoMode), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The number of video pixels to remove at the bottom of the image.</summary>
+ public const ulong PixelCropBottom = 0x54AA;
+ public static readonly ElementDescriptor PixelCropBottomDescriptor = new ElementDescriptor(PixelCropBottom, nameof(PixelCropBottom), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The number of video pixels to remove on the left of the image.</summary>
+ public const ulong PixelCropLeft = 0x54CC;
+ public static readonly ElementDescriptor PixelCropLeftDescriptor = new ElementDescriptor(PixelCropLeft, nameof(PixelCropLeft), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The number of video pixels to remove on the right of the image.</summary>
+ public const ulong PixelCropRight = 0x54DD;
+ public static readonly ElementDescriptor PixelCropRightDescriptor = new ElementDescriptor(PixelCropRight, nameof(PixelCropRight), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] The number of video pixels to remove at the top of the image.</summary>
+ public const ulong PixelCropTop = 0x54BB;
+ public static readonly ElementDescriptor PixelCropTopDescriptor = new ElementDescriptor(PixelCropTop, nameof(PixelCropTop), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Height of the encoded video frames in pixels.</summary>
+ public const ulong PixelHeight = 0xBA;
+ public static readonly ElementDescriptor PixelHeightDescriptor = new ElementDescriptor(PixelHeight, nameof(PixelHeight), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Width of the encoded video frames in pixels.</summary>
+ public const ulong PixelWidth = 0xB0;
+ public static readonly ElementDescriptor PixelWidthDescriptor = new ElementDescriptor(PixelWidth, nameof(PixelWidth), ElementType.UnsignedInteger);
+
+ /// <summary>Describes the video projection details. Used to render spherical or VR videos or to flip videos horizontally or vertically.</summary>
+ public const ulong Projection = 0x7670;
+ public static readonly ElementDescriptor ProjectionDescriptor = new ElementDescriptor(Projection, nameof(Projection), ElementType.MasterElement);
+
+ /// <summary>[Required] Specifies a pitch rotation to the projection. Value represents a counter-clockwise rotation, in degrees, around the right vector. This rotation must be appliedafter the `ProjectionPoseYaw` rotation and before the `ProjectionPoseRoll` rotation.The value of this element **MUST** be in the -90 to 90 degree range, both inclusive.</summary>
+ public const ulong ProjectionPosePitch = 0x7674;
+ public static readonly ElementDescriptor ProjectionPosePitchDescriptor = new ElementDescriptor(ProjectionPosePitch, nameof(ProjectionPosePitch), ElementType.Float);
+
+ /// <summary>[Required] Specifies a roll rotation to the projection. Value represents acounter-clockwise rotation, in degrees, around the forward vector. Thisrotation must be applied after the `ProjectionPoseYaw` and`ProjectionPosePitch` rotations. The value of this element**MUST** be in the -180 to 180 degree range, both inclusive. Setting `ProjectionPoseRoll` to 180 or -180 degrees and`ProjectionPoseYaw` to 180 or -180 degrees with`ProjectionPosePitch` set to 0 degrees flips the image vertically.Setting `ProjectionPoseRoll` to 180 or -180 degrees with`ProjectionPoseYaw` and `ProjectionPosePitch` set to 0 degreesflips the image horizontally and vertically.</summary>
+ public const ulong ProjectionPoseRoll = 0x7675;
+ public static readonly ElementDescriptor ProjectionPoseRollDescriptor = new ElementDescriptor(ProjectionPoseRoll, nameof(ProjectionPoseRoll), ElementType.Float);
+
+ /// <summary>[Required] Specifies a yaw rotation to the projection. Value represents a clockwise rotation, in degrees, around the up vector. This rotation must be appliedbefore any `ProjectionPosePitch` or `ProjectionPoseRoll` rotations.The value of this element **MUST** be in the -180 to 180 degree range, both inclusive.Setting `ProjectionPoseYaw` to 180 or -180 degrees with `ProjectionPoseRoll` and `ProjectionPosePitch` set to 0 degrees flips the image horizontally.</summary>
+ public const ulong ProjectionPoseYaw = 0x7673;
+ public static readonly ElementDescriptor ProjectionPoseYawDescriptor = new ElementDescriptor(ProjectionPoseYaw, nameof(ProjectionPoseYaw), ElementType.Float);
+
+ /// <summary>Private data that only applies to a specific projection.* If `ProjectionType` equals 0 (rectangular),then this element **MUST NOT** be present.* If `ProjectionType` equals 1 (equirectangular), then this element **MUST** be present and contain the same binary data that would be stored insidean ISOBMFF Equirectangular Projection Box ("equi").* If `ProjectionType` equals 2 (cubemap), then this element **MUST** be present and contain the same binary data that would be storedinside an ISOBMFF Cubemap Projection Box ("cbmp").* If `ProjectionType` equals 3 (mesh), then this element **MUST** be present and contain the same binary data that would be stored insidean ISOBMFF Mesh Projection Box ("mshp").</summary>
+ public const ulong ProjectionPrivate = 0x7672;
+ public static readonly ElementDescriptor ProjectionPrivateDescriptor = new ElementDescriptor(ProjectionPrivate, nameof(ProjectionPrivate), ElementType.Binary);
+
+ /// <summary>[Required] Describes the projection used for this video track.</summary>
+ public const ulong ProjectionType = 0x7671;
+ public static readonly ElementDescriptor ProjectionTypeDescriptor = new ElementDescriptor(ProjectionType, nameof(ProjectionType), ElementType.UnsignedInteger);
+
+ /// <summary>[Required] Stereo-3D video mode. See (#multi-planar-and-3d-videos) for more details.</summary>
+ public const ulong StereoMode = 0x53B8;
+ public static readonly ElementDescriptor StereoModeDescriptor = new ElementDescriptor(StereoMode, nameof(StereoMode), ElementType.UnsignedInteger);
+
+ /// <summary>Specifies the uncompressed pixel format used for the `Track`'s data as a FourCC.This value is similar in scope to the biCompression value of AVI's `BITMAPINFO` [@?AVIFormat]. There is neither a definitive list of FourCC values nor an official registry. Some common values for YUV pixel formats can be found at [@?MSYUV8], [@?MSYUV16], and [@?FourCC-YUV]. Some common values for uncompressed RGB pixel formats can be found at [@?MSRGB] and [@?FourCC-RGB].</summary>
+ public const ulong UncompressedFourCC = 0x2EB524;
+ public static readonly ElementDescriptor UncompressedFourCCDescriptor = new ElementDescriptor(UncompressedFourCC, nameof(UncompressedFourCC), ElementType.Binary);
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Matroska/MatroskaSpecification.tt b/Matroska/MatroskaSpecification.tt
new file mode 100644
index 0000000..451e655
--- /dev/null
+++ b/Matroska/MatroskaSpecification.tt
@@ -0,0 +1,157 @@
+<#@ template debug="false" hostspecific="true" language="C#" #>
+<#@ output extension=".cs" encoding="utf-8" #>
+<#@ Assembly Name="System.Core.dll" #>
+<#@ Assembly Name="System.Xml.dll" #>
+<#@ Assembly Name="System.Xml.Linq.dll" #>
+<#@ import namespace="System" #>
+<#@ import namespace="System.IO" #>
+<#@ import namespace="System.Diagnostics" #>
+<#@ import namespace="System.Reflection" #>
+<#@ import namespace="System.Linq" #>
+<#@ import namespace="System.Collections" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#@ import namespace="System.Xml.Linq" #>
+<#
+ string XmlFile = "ebml.xml";
+ string MatroskaXmlFile = "ebml_matroska.xml";
+ XNamespace ns = "urn:ietf:rfc:8794";
+#>
+using System.Collections.Generic;
+using System.CodeDom.Compiler;
+using System.Linq;
+using System.Reflection;
+using NEbml.Core;
+
+//--------------------------------------------------------------------------------------------------------
+// <auto-generated>
+// This C# code class was auto-generated at <#= DateTime.Now #>.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+// Based on "https://github.com/ietf-wg-cellar/matroska-specification/blob/master/ebml_matroska.xml".
+// </auto-generated>
+//--------------------------------------------------------------------------------------------------------
+namespace Matroska;
+
+/// <summary>
+/// Contains the EBML elements specified by the Matroska project (https://matroska.org/technical/specs/index.html)
+/// </summary>
+[GeneratedCode("MatroskaSpecification.tt", "1.0.0.0")]
+internal static class MatroskaSpecification
+{
+<#
+string FixDocumentation(string d)
+{
+ return d.Replace(" ", " ").Trim();
+}
+
+var elementsEbml = XElement.Load(Path.Combine(Path.GetDirectoryName(this.Host.TemplateFile), XmlFile)).Elements()
+ .Select(e => new
+ {
+ Name = e.Attribute("name").Value.Replace("CRC-32", "CRC32"),
+ Path = e.Attribute("path").Value,
+ Id = e.Attribute("id").Value,
+ ElementType = e.Attribute("type").Value,
+ Required = (e.Attribute("minOccurs") != null ? int.Parse(e.Attribute("minOccurs").Value) : 0) > 0,
+ Documentation = e.Element(ns + "documentation") != null ? FixDocumentation(e.Element(ns + "documentation").Value) : ""
+ });
+
+var matroskaElementsToSkip = new [] { "EBMLMaxIDLength", "EBMLMaxSizeLength" };
+var elementsMatroska = XElement.Load(Path.Combine(Path.GetDirectoryName(this.Host.TemplateFile), MatroskaXmlFile)).Elements()
+ .Select(e => new
+ {
+ Name = e.Attribute("name").Value,
+ Path = e.Attribute("path").Value,
+ Id = e.Attribute("id").Value,
+ ElementType = e.Attribute("type").Value,
+ Required = (e.Attribute("minOccurs") != null ? int.Parse(e.Attribute("minOccurs").Value) : 0) > 0,
+ Documentation = e.Element(ns + "documentation") != null ? FixDocumentation(e.Element(ns + "documentation").Value) : ""
+ })
+ .Where(e => !matroskaElementsToSkip.Contains(e.Name)).ToArray();
+
+var elements = elementsEbml.Union(elementsMatroska).OrderBy(e => e.Path).ToList();
+#>
+ #region Helpers
+ private static readonly Dictionary<VInt, ElementDescriptor> _elementDescriptorsByVInt = new Dictionary<VInt, ElementDescriptor>();
+ private static readonly Dictionary<ulong, ElementDescriptor> _elementDescriptorsByIdentifier = new Dictionary<ulong, ElementDescriptor>();
+
+ /// <summary>
+ /// Gets a dictionary of all Matroska elements.
+ /// </summary>
+ public static IReadOnlyDictionary<VInt, ElementDescriptor> ElementDescriptors => _elementDescriptorsByVInt;
+
+ /// <summary>
+ /// Gets a dictionary of all Matroska elements.
+ /// </summary>
+ public static IReadOnlyDictionary<ulong, ElementDescriptor> ElementDescriptorsByIdentifier => _elementDescriptorsByIdentifier;
+
+ static MatroskaSpecification()
+ {
+ var fields = typeof(MatroskaSpecification).GetFields(BindingFlags.Public | BindingFlags.Static);
+
+ foreach (FieldInfo field in fields.Where(ft => ft.FieldType == typeof(ElementDescriptor)))
+ {
+ var value = (ElementDescriptor)field.GetValue(null);
+ _elementDescriptorsByVInt.Add(value.Identifier, value);
+ }
+
+ foreach (FieldInfo field in fields.Where(ft => ft.FieldType == typeof(ulong)))
+ {
+ var identifier = (ulong)field.GetValue(null);
+ _elementDescriptorsByIdentifier.Add(identifier, _elementDescriptorsByVInt[VInt.FromEncoded(identifier)]);
+ }
+ }
+ #endregion
+
+ #region Definitions
+<#
+foreach (var element in elements)
+{
+ string elementType;
+ switch (element.ElementType)
+ {
+ case "master":
+ elementType = "MasterElement";
+ break;
+
+ case "utf-8":
+ elementType = "Utf8String";
+ break;
+
+ case "string":
+ elementType = "AsciiString";
+ break;
+
+ case "binary":
+ elementType = "Binary";
+ break;
+
+ case "date":
+ elementType = "Date";
+ break;
+
+ case "float":
+ elementType = "Float";
+ break;
+
+ case "uinteger":
+ elementType = "UnsignedInteger";
+ break;
+
+ case "integer":
+ elementType = "SignedInteger";
+ break;
+
+ default:
+ elementType = element.ElementType;
+ break;
+ }
+#>
+ /// <summary><#=(element.Required ? "[Required] " : "")#><#=element.Documentation.Replace("\n", "").Replace(" ", "")#></summary>
+ public const ulong <#=element.Name#> = <#=element.Id#>;
+ public static readonly ElementDescriptor <#=element.Name#>Descriptor = new ElementDescriptor(<#=element.Name#>, nameof(<#=element.Name#>), ElementType.<#=elementType#>);
+
+<#
+ }
+#>
+ #endregion
+}
\ No newline at end of file
diff --git a/Matroska/Models/BaseModel.cs b/Matroska/Models/BaseModel.cs
new file mode 100644
index 0000000..08c02c5
--- /dev/null
+++ b/Matroska/Models/BaseModel.cs
@@ -0,0 +1,12 @@
+using Matroska.Attributes;
+
+namespace Matroska.Models;
+
+public abstract class BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.Void)]
+ public byte[]? Void { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CRC32)]
+ public byte[]? CRC32 { get; set; }
+}
\ No newline at end of file
diff --git a/Matroska/Models/Block.cs b/Matroska/Models/Block.cs
new file mode 100644
index 0000000..6befc4c
--- /dev/null
+++ b/Matroska/Models/Block.cs
@@ -0,0 +1,73 @@
+using System;
+using System.IO;
+using Matroska.Enumerations;
+
+namespace Matroska.Models;
+
+/// <summary>
+/// http://matroska.sourceforge.net/technical/specs/index.html#block_structure
+/// </summary>
+public class Block : IParseRawBinary
+{
+ private const byte LacingBits = 0b0000110;
+ private const byte InvisibleBit = 0b00010000;
+
+ /// <summary>
+ /// Track Number (Track Entry)
+ /// </summary>
+ public ulong TrackNumber { get; private set; }
+
+ public byte Flags { get; private set; }
+
+ /// <summary>
+ /// Number of frames in the lace-1 (uint8)
+ /// </summary>
+ public int NumFrames { get; private set; }
+
+ /// <summary>
+ /// Lace-coded size of each frame of the lace, except for the last one (multiple uint8).
+ /// *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace).
+ /// </summary>
+ public byte LaceCodedSizeOfEachFrame { get; private set; }
+
+ /// <summary>
+ /// Timecode (relative to Cluster timecode, signed int16)
+ /// </summary>
+ public short TimeCode { get; private set; }
+
+ /// <summary>
+ /// Invisible, the codec should decode this frame but not display it
+ /// </summary>
+ public bool IsInvisible { get; private set; }
+
+ /// <summary>
+ /// Lacing
+ /// </summary>
+ public Lacing Lacing { get; private set; }
+
+ public byte[]? Data { get; private set; }
+
+ public virtual void Parse(Span<byte> span)
+ {
+ var spanReader = new SpanReader(span);
+
+ TrackNumber = spanReader.ReadVInt().Value;
+ TimeCode = spanReader.ReadShort(true); // EBML integer datatypes are big-endian
+ Flags = spanReader.ReadByte();
+
+ IsInvisible = (Flags & InvisibleBit) == InvisibleBit;
+ Lacing = (Lacing)(Flags & LacingBits);
+
+ if (Lacing != Lacing.No)
+ {
+ NumFrames = spanReader.ReadByte();
+
+ if (Lacing != Lacing.FixedSize)
+ {
+ LaceCodedSizeOfEachFrame = spanReader.ReadByte();
+ }
+ }
+
+ Data = span.Slice(spanReader.Position).ToArray();
+ }
+}
\ No newline at end of file
diff --git a/Matroska/Models/BlockAdditional.cs b/Matroska/Models/BlockAdditional.cs
new file mode 100644
index 0000000..12da452
--- /dev/null
+++ b/Matroska/Models/BlockAdditional.cs
@@ -0,0 +1,5 @@
+namespace Matroska.Models;
+
+public sealed class BlockAdditional : Block
+{
+}
\ No newline at end of file
diff --git a/Matroska/Models/BlockVirtual.cs b/Matroska/Models/BlockVirtual.cs
new file mode 100644
index 0000000..53cdecd
--- /dev/null
+++ b/Matroska/Models/BlockVirtual.cs
@@ -0,0 +1,5 @@
+namespace Matroska.Models;
+
+public sealed class BlockVirtual : Block
+{
+}
\ No newline at end of file
diff --git a/Matroska/Models/EncryptedBlock.cs b/Matroska/Models/EncryptedBlock.cs
new file mode 100644
index 0000000..f4303e0
--- /dev/null
+++ b/Matroska/Models/EncryptedBlock.cs
@@ -0,0 +1,5 @@
+namespace Matroska.Models;
+
+public sealed class EncryptedBlock : Block
+{
+}
\ No newline at end of file
diff --git a/Matroska/Models/IParseRawBinary.cs b/Matroska/Models/IParseRawBinary.cs
new file mode 100644
index 0000000..652b5e1
--- /dev/null
+++ b/Matroska/Models/IParseRawBinary.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace Matroska.Models;
+
+public interface IParseRawBinary
+{
+ void Parse(Span<byte> span);
+}
\ No newline at end of file
diff --git a/Matroska/Models/MatroskaDocument.cs b/Matroska/Models/MatroskaDocument.cs
new file mode 100644
index 0000000..77c5155
--- /dev/null
+++ b/Matroska/Models/MatroskaDocument.cs
@@ -0,0 +1,8 @@
+namespace Matroska.Models;
+
+public sealed class MatroskaDocument
+{
+ public EBML Ebml { get; set; } = null!;
+
+ public Segment Segment { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/Matroska/Models/MatroskaModels.cs b/Matroska/Models/MatroskaModels.cs
new file mode 100644
index 0000000..7d21423
--- /dev/null
+++ b/Matroska/Models/MatroskaModels.cs
@@ -0,0 +1,935 @@
+using System;
+using System.Collections.Generic;
+using System.CodeDom.Compiler;
+using Matroska.Attributes;
+
+//--------------------------------------------------------------------------------------------------------
+// <auto-generated>
+// This C# code class was auto-generated at 06/14/2025 08:18:21.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+// Based on "https://github.com/ietf-wg-cellar/matroska-specification/blob/master/ebml_matroska.xml".
+// </auto-generated>
+//--------------------------------------------------------------------------------------------------------
+namespace Matroska.Models;
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class EBML : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.DocType)]
+ public string DocType { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DocTypeExtension)]
+ public DocTypeExtension? DocTypeExtension { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DocTypeReadVersion)]
+ public ulong DocTypeReadVersion { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DocTypeVersion)]
+ public ulong DocTypeVersion { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EBMLMaxIDLength)]
+ public ulong EBMLMaxIDLength { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EBMLMaxSizeLength)]
+ public ulong EBMLMaxSizeLength { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EBMLReadVersion)]
+ public ulong EBMLReadVersion { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EBMLVersion)]
+ public ulong EBMLVersion { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class DocTypeExtension : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.DocTypeExtensionName)]
+ public string DocTypeExtensionName { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DocTypeExtensionVersion)]
+ public ulong DocTypeExtensionVersion { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Segment : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.Attachments)]
+ public Attachments? Attachments { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Chapters)]
+ public Chapters? Chapters { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Cluster, typeof(Cluster))]
+ public List<Cluster>? Clusters { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Cues)]
+ public Cues? Cues { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Info)]
+ public Info Info { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.SeekHead)]
+ public SeekHead? SeekHead { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Tags)]
+ public Tags? Tags { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Tracks)]
+ public Tracks? Tracks { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Attachments : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.AttachedFile, typeof(AttachedFile))]
+ public List<AttachedFile> AttachedFiles { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class AttachedFile : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.FileData)]
+ public byte[]? FileData { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FileDescription)]
+ public string? FileDescription { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FileMediaType)]
+ public string FileMediaType { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FileName)]
+ public string FileName { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FileReferral)]
+ public byte[]? FileReferral { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FileUID)]
+ public ulong FileUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FileUsedEndTime)]
+ public ulong? FileUsedEndTime { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FileUsedStartTime)]
+ public ulong? FileUsedStartTime { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Chapters : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.EditionEntry)]
+ public EditionEntry EditionEntry { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class EditionEntry : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.ChapterAtom)]
+ public ChapterAtom ChapterAtom { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EditionDisplay)]
+ public EditionDisplay? EditionDisplay { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EditionFlagDefault)]
+ public ulong EditionFlagDefault { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EditionFlagHidden)]
+ public ulong EditionFlagHidden { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EditionFlagOrdered)]
+ public ulong EditionFlagOrdered { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EditionUID)]
+ public ulong? EditionUID { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ChapterAtom : BaseModel
+{
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ChapProcess : BaseModel
+{
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ChapProcessCommand : BaseModel
+{
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ChapterDisplay : BaseModel
+{
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ChapterTrack : BaseModel
+{
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class EditionDisplay : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.EditionLanguageIETF)]
+ public string? EditionLanguageIETF { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EditionString)]
+ public string EditionString { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Cluster : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockGroup, typeof(BlockGroup))]
+ public List<BlockGroup>? BlockGroups { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.EncryptedBlock, typeof(EncryptedBlock))]
+ public List<EncryptedBlock>? EncryptedBlocks { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Position)]
+ public ulong? Position { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PrevSize)]
+ public ulong? PrevSize { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.SilentTracks)]
+ public SilentTracks? SilentTracks { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.SimpleBlock, typeof(SimpleBlock))]
+ public List<SimpleBlock>? SimpleBlocks { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Timestamp)]
+ public ulong Timestamp { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class BlockGroup : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.Block, typeof(Block))]
+ public List<Block> Blocks { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockAdditions)]
+ public BlockAdditions? BlockAdditions { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockDuration)]
+ public ulong? BlockDuration { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockVirtual, typeof(BlockVirtual))]
+ public List<BlockVirtual>? BlockVirtuals { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CodecState)]
+ public byte[]? CodecState { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DiscardPadding)]
+ public long? DiscardPadding { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ReferenceBlock)]
+ public long? ReferenceBlock { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ReferenceFrame)]
+ public ReferenceFrame? ReferenceFrame { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ReferencePriority)]
+ public ulong ReferencePriority { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ReferenceVirtual)]
+ public long? ReferenceVirtual { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Slices)]
+ public Slices? Slices { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class BlockAdditions : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockMore)]
+ public BlockMore BlockMore { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class BlockMore : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockAddID)]
+ public ulong BlockAddID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockAdditional, typeof(BlockAdditional))]
+ public List<BlockAdditional> BlockAdditionals { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ReferenceFrame : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.ReferenceOffset)]
+ public ulong ReferenceOffset { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ReferenceTimestamp)]
+ public ulong ReferenceTimestamp { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Slices : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.TimeSlice)]
+ public TimeSlice? TimeSlice { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class TimeSlice : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockAdditionID)]
+ public ulong? BlockAdditionID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Delay)]
+ public ulong? Delay { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FrameNumber)]
+ public ulong? FrameNumber { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.LaceNumber)]
+ public ulong? LaceNumber { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.SliceDuration)]
+ public ulong? SliceDuration { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class SilentTracks : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.SilentTrackNumber)]
+ public ulong? SilentTrackNumber { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Cues : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.CuePoint, typeof(CuePoint))]
+ public List<CuePoint> CuePoints { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class CuePoint : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.CueTime)]
+ public ulong CueTime { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CueTrackPositions)]
+ public CueTrackPositions CueTrackPositions { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class CueTrackPositions : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.CueBlockNumber)]
+ public ulong? CueBlockNumber { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CueClusterPosition)]
+ public ulong CueClusterPosition { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CueCodecState)]
+ public ulong CueCodecState { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CueDuration)]
+ public ulong? CueDuration { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CueReference, typeof(CueReference))]
+ public List<CueReference>? CueReferences { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CueRelativePosition)]
+ public ulong? CueRelativePosition { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CueTrack)]
+ public ulong CueTrack { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class CueReference : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.CueRefCluster)]
+ public ulong CueRefCluster { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CueRefCodecState)]
+ public ulong? CueRefCodecState { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CueRefNumber)]
+ public ulong? CueRefNumber { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CueRefTime)]
+ public ulong CueRefTime { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Info : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.ChapterTranslate)]
+ public ChapterTranslate? ChapterTranslate { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DateUTC)]
+ public DateTime? DateUTC { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Duration)]
+ public double? Duration { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.MuxingApp)]
+ public string MuxingApp { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.NextFilename)]
+ public string? NextFilename { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.NextUUID)]
+ public byte[]? NextUUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PrevFilename)]
+ public string? PrevFilename { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PrevUUID)]
+ public byte[]? PrevUUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.SegmentFamily)]
+ public byte[]? SegmentFamily { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.SegmentFilename)]
+ public string? SegmentFilename { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.SegmentUUID)]
+ public byte[]? SegmentUUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TimestampScale)]
+ public ulong TimestampScale { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Title)]
+ public string? Title { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.WritingApp)]
+ public string WritingApp { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ChapterTranslate : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.ChapterTranslateCodec)]
+ public ulong ChapterTranslateCodec { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ChapterTranslateEditionUID)]
+ public ulong? ChapterTranslateEditionUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ChapterTranslateID)]
+ public byte[]? ChapterTranslateID { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class SeekHead : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.Seek)]
+ public Seek Seek { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Seek : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.SeekID)]
+ public byte[]? SeekID { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.SeekPosition)]
+ public ulong SeekPosition { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Tags : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.Tag, typeof(TagItem))]
+ public List<TagItem> TagItems { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class TagItem : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.SimpleTag, typeof(SimpleTag))]
+ public List<SimpleTag> SimpleTags { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Targets)]
+ public Targets Targets { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Targets : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.TagAttachmentUID)]
+ public ulong? TagAttachmentUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TagChapterUID)]
+ public ulong? TagChapterUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TagEditionUID)]
+ public ulong? TagEditionUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TagTrackUID)]
+ public ulong? TagTrackUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TargetType)]
+ public string? TargetType { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TargetTypeValue)]
+ public ulong TargetTypeValue { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Tracks : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackEntry, typeof(TrackEntry))]
+ public List<TrackEntry> TrackEntries { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class TrackEntry : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.AttachmentLink)]
+ public ulong? AttachmentLink { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Audio)]
+ public Audio? Audio { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockAdditionMapping)]
+ public BlockAdditionMapping? BlockAdditionMapping { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CodecDecodeAll)]
+ public ulong CodecDecodeAll { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CodecDelay)]
+ public ulong CodecDelay { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CodecDownloadURL)]
+ public string? CodecDownloadURL { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CodecID)]
+ public string CodecID { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CodecInfoURL)]
+ public string? CodecInfoURL { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CodecName)]
+ public string? CodecName { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CodecPrivate)]
+ public byte[]? CodecPrivate { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CodecSettings)]
+ public string? CodecSettings { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentEncodings)]
+ public ContentEncodings? ContentEncodings { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DefaultDecodedFieldDuration)]
+ public ulong? DefaultDecodedFieldDuration { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DefaultDuration)]
+ public ulong? DefaultDuration { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FlagCommentary)]
+ public ulong? FlagCommentary { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FlagDefault)]
+ public ulong FlagDefault { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FlagEnabled)]
+ public ulong FlagEnabled { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FlagForced)]
+ public ulong FlagForced { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FlagHearingImpaired)]
+ public ulong? FlagHearingImpaired { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FlagLacing)]
+ public ulong FlagLacing { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FlagOriginal)]
+ public ulong? FlagOriginal { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FlagTextDescriptions)]
+ public ulong? FlagTextDescriptions { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FlagVisualImpaired)]
+ public ulong? FlagVisualImpaired { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Language)]
+ public string Language { get; set; } = null!;
+
+ [MatroskaElementDescriptor(MatroskaSpecification.LanguageBCP47)]
+ public string? LanguageBCP47 { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.MaxBlockAdditionID)]
+ public ulong MaxBlockAdditionID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.MaxCache)]
+ public ulong? MaxCache { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.MinCache)]
+ public ulong MinCache { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Name)]
+ public string? Name { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.SeekPreRoll)]
+ public ulong SeekPreRoll { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackNumber)]
+ public ulong TrackNumber { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackOffset)]
+ public long? TrackOffset { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackOperation)]
+ public TrackOperation? TrackOperation { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackOverlay)]
+ public ulong? TrackOverlay { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackTimestampScale)]
+ public double TrackTimestampScale { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackTranslate)]
+ public TrackTranslate? TrackTranslate { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackType)]
+ public ulong TrackType { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackUID)]
+ public ulong TrackUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrickMasterTrackSegmentUID)]
+ public byte[]? TrickMasterTrackSegmentUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrickMasterTrackUID)]
+ public ulong? TrickMasterTrackUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrickTrackFlag)]
+ public ulong? TrickTrackFlag { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrickTrackSegmentUID)]
+ public byte[]? TrickTrackSegmentUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrickTrackUID)]
+ public ulong? TrickTrackUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Video)]
+ public Video? Video { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Audio : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.BitDepth)]
+ public ulong? BitDepth { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ChannelPositions)]
+ public byte[]? ChannelPositions { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Channels)]
+ public ulong Channels { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Emphasis)]
+ public ulong Emphasis { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.OutputSamplingFrequency)]
+ public double? OutputSamplingFrequency { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.SamplingFrequency)]
+ public double SamplingFrequency { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class BlockAdditionMapping : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockAddIDExtraData)]
+ public byte[]? BlockAddIDExtraData { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockAddIDName)]
+ public string? BlockAddIDName { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockAddIDType)]
+ public ulong BlockAddIDType { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.BlockAddIDValue)]
+ public ulong? BlockAddIDValue { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ContentEncodings : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentEncoding, typeof(ContentEncodingItem))]
+ public List<ContentEncodingItem> ContentEncodingItems { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ContentEncodingItem : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentCompression, typeof(ContentCompression))]
+ public List<ContentCompression>? ContentCompressions { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentEncodingOrder)]
+ public ulong ContentEncodingOrder { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentEncodingScope)]
+ public ulong ContentEncodingScope { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentEncodingType)]
+ public ulong ContentEncodingType { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentEncryption)]
+ public ContentEncryption? ContentEncryption { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ContentCompression : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentCompAlgo)]
+ public ulong ContentCompAlgo { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentCompSettings)]
+ public byte[]? ContentCompSettings { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ContentEncryption : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentEncAESSettings)]
+ public ContentEncAESSettings? ContentEncAESSettings { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentEncAlgo)]
+ public ulong ContentEncAlgo { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentEncKeyID)]
+ public byte[]? ContentEncKeyID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentSigAlgo)]
+ public ulong? ContentSigAlgo { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentSigHashAlgo)]
+ public ulong? ContentSigHashAlgo { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentSigKeyID)]
+ public byte[]? ContentSigKeyID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ContentSignature)]
+ public byte[]? ContentSignature { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class ContentEncAESSettings : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.AESSettingsCipherMode)]
+ public ulong AESSettingsCipherMode { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class TrackOperation : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackCombinePlanes)]
+ public TrackCombinePlanes? TrackCombinePlanes { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackJoinBlocks)]
+ public TrackJoinBlocks? TrackJoinBlocks { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class TrackCombinePlanes : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackPlane)]
+ public TrackPlane TrackPlane { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class TrackPlane : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackPlaneType)]
+ public ulong TrackPlaneType { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackPlaneUID)]
+ public ulong TrackPlaneUID { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class TrackJoinBlocks : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackJoinUID)]
+ public ulong TrackJoinUID { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class TrackTranslate : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackTranslateCodec)]
+ public ulong TrackTranslateCodec { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackTranslateEditionUID)]
+ public ulong? TrackTranslateEditionUID { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TrackTranslateTrackID)]
+ public byte[]? TrackTranslateTrackID { get; set; } = null!;
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Video : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.AlphaMode)]
+ public ulong AlphaMode { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.AspectRatioType)]
+ public ulong? AspectRatioType { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Colour)]
+ public Colour? Colour { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DisplayHeight)]
+ public ulong? DisplayHeight { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DisplayUnit)]
+ public ulong DisplayUnit { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.DisplayWidth)]
+ public ulong? DisplayWidth { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FieldOrder)]
+ public ulong FieldOrder { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FlagInterlaced)]
+ public ulong FlagInterlaced { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.FrameRate)]
+ public double? FrameRate { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.GammaValue)]
+ public double? GammaValue { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.OldStereoMode)]
+ public ulong? OldStereoMode { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PixelCropBottom)]
+ public ulong PixelCropBottom { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PixelCropLeft)]
+ public ulong PixelCropLeft { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PixelCropRight)]
+ public ulong PixelCropRight { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PixelCropTop)]
+ public ulong PixelCropTop { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PixelHeight)]
+ public ulong PixelHeight { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PixelWidth)]
+ public ulong PixelWidth { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Projection)]
+ public Projection? Projection { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.StereoMode)]
+ public ulong StereoMode { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.UncompressedFourCC)]
+ public byte[]? UncompressedFourCC { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Colour : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.BitsPerChannel)]
+ public ulong BitsPerChannel { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CbSubsamplingHorz)]
+ public ulong? CbSubsamplingHorz { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.CbSubsamplingVert)]
+ public ulong? CbSubsamplingVert { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ChromaSitingHorz)]
+ public ulong ChromaSitingHorz { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ChromaSitingVert)]
+ public ulong ChromaSitingVert { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ChromaSubsamplingHorz)]
+ public ulong? ChromaSubsamplingHorz { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ChromaSubsamplingVert)]
+ public ulong? ChromaSubsamplingVert { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.MasteringMetadata)]
+ public MasteringMetadata? MasteringMetadata { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.MatrixCoefficients)]
+ public ulong MatrixCoefficients { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.MaxCLL)]
+ public ulong? MaxCLL { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.MaxFALL)]
+ public ulong? MaxFALL { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Primaries)]
+ public ulong Primaries { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.Range)]
+ public ulong Range { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TransferCharacteristics)]
+ public ulong TransferCharacteristics { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class MasteringMetadata : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.LuminanceMax)]
+ public double? LuminanceMax { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.LuminanceMin)]
+ public double? LuminanceMin { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PrimaryBChromaticityX)]
+ public double? PrimaryBChromaticityX { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PrimaryBChromaticityY)]
+ public double? PrimaryBChromaticityY { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PrimaryGChromaticityX)]
+ public double? PrimaryGChromaticityX { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PrimaryGChromaticityY)]
+ public double? PrimaryGChromaticityY { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PrimaryRChromaticityX)]
+ public double? PrimaryRChromaticityX { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.PrimaryRChromaticityY)]
+ public double? PrimaryRChromaticityY { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.WhitePointChromaticityX)]
+ public double? WhitePointChromaticityX { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.WhitePointChromaticityY)]
+ public double? WhitePointChromaticityY { get; set; }
+}
+
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class Projection : BaseModel
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.ProjectionPosePitch)]
+ public double ProjectionPosePitch { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ProjectionPoseRoll)]
+ public double ProjectionPoseRoll { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ProjectionPoseYaw)]
+ public double ProjectionPoseYaw { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ProjectionPrivate)]
+ public byte[]? ProjectionPrivate { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.ProjectionType)]
+ public ulong ProjectionType { get; set; }
+}
+
+
diff --git a/Matroska/Models/MatroskaModels.tt b/Matroska/Models/MatroskaModels.tt
new file mode 100644
index 0000000..4691021
--- /dev/null
+++ b/Matroska/Models/MatroskaModels.tt
@@ -0,0 +1,253 @@
+<#@ template debug="false" hostspecific="true" language="C#" #>
+<#@ output extension=".cs" encoding="utf-8" #>
+<#@ Assembly Name="System.Core.dll" #>
+<#@ Assembly Name="System.Xml.dll" #>
+<#@ Assembly Name="System.Xml.Linq.dll" #>
+<#@ import namespace="System" #>
+<#@ import namespace="System.IO" #>
+<#@ import namespace="System.Diagnostics" #>
+<#@ import namespace="System.Reflection" #>
+<#@ import namespace="System.Linq" #>
+<#@ import namespace="System.Collections" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#@ import namespace="System.Xml.Linq" #>
+<#@ import namespace="System.Text" #>
+<#
+ string XmlFile = "../ebml.xml";
+ string MatroskaXmlFile = "../ebml_matroska.xml";
+ XNamespace ns = "urn:ietf:rfc:8794";
+#>
+using System;
+using System.Collections.Generic;
+using System.CodeDom.Compiler;
+using Matroska.Attributes;
+
+//--------------------------------------------------------------------------------------------------------
+// <auto-generated>
+// This C# code class was auto-generated at <#= DateTime.Now #>.
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+// Based on "https://github.com/ietf-wg-cellar/matroska-specification/blob/master/ebml_matroska.xml".
+// </auto-generated>
+//--------------------------------------------------------------------------------------------------------
+namespace Matroska.Models;
+<#
+var isList = new List<string>
+{
+ { "AttachedFile" },
+ { "Block" },
+ { "BlockAdditional" },
+ { "BlockGroup" },
+ { "BlockVirtual" },
+ { "Cluster" },
+ { "ContentCompression" },
+ { "ContentEncoding" },
+ { "CuePoint" },
+ { "CueReference" },
+ { "CuetrackPosition" },
+ { "EncryptedBlock" },
+ { "SignatureElementList" },
+ { "SignatureSlot" },
+ { "SignedElement" },
+ { "SimpleBlock" },
+ { "SimpleTag" },
+ { "Tag" },
+ { "Target" },
+ { "TrackEntry" }
+};
+
+var elementsEbml = XElement.Load(Path.Combine(Path.GetDirectoryName(this.Host.TemplateFile), XmlFile)).Elements()
+ .Select(e => new Element
+ {
+ Name = e.Attribute("name").Value.Replace("CRC-32", "CRC32"),
+ UseNameInsteadOfBinary = UseNameInsteadOfBinary(e.Attribute("name").Value),
+ IsList = isList.Contains(e.Attribute("name").Value),
+ Path = e.Attribute("path").Value,
+ Level = e.Attribute("path").Value.Count(c => c == '\\'),
+ Id = e.Attribute("id").Value,
+ ElementType = e.Attribute("type").Value,
+ Required = (e.Attribute("minOccurs") != null ? int.Parse(e.Attribute("minOccurs").Value) : 0) > 0,
+ Documentation = e.Element(ns + "documentation") != null ? e.Element(ns + "documentation").Value : ""
+ });
+
+var matroskaElementsToSkip = new [] { "EBMLMaxIDLength", "EBMLMaxSizeLength" };
+var elementsMatroska = XElement.Load(Path.Combine(Path.GetDirectoryName(this.Host.TemplateFile), MatroskaXmlFile)).Elements()
+ .Select(e => new Element
+ {
+ Name = e.Attribute("name").Value,
+ UseNameInsteadOfBinary = UseNameInsteadOfBinary(e.Attribute("name").Value),
+ IsList = isList.Contains(e.Attribute("name").Value),
+ Path = e.Attribute("path").Value,
+ Level = e.Attribute("path").Value.Count(c => c == '\\'),
+ Id = e.Attribute("id").Value,
+ ElementType = e.Attribute("type").Value,
+ Required = (e.Attribute("minOccurs") != null ? int.Parse(e.Attribute("minOccurs").Value) : 0) > 0,
+ Documentation = e.Element(ns + "documentation") != null ? e.Element(ns + "documentation").Value : ""
+ })
+ .Where(e => !matroskaElementsToSkip.Contains(e.Name)).ToArray();
+
+var elements = elementsEbml.Union(elementsMatroska).OrderBy(e => e.Path).ToList();
+
+var ebml = elements.FirstOrDefault(e => e.Name == "EBML");
+Loop(elements, ebml, "");
+
+var segment = elements.FirstOrDefault(e => e.Name == "Segment");
+Loop(elements, segment, "");
+
+var skip = new List<string> { "Block", "SimpleBlock", "SimpleTag" };
+
+foreach (var master in elements.Where(e => e.ElementType == "master" && !skip.Contains(e.Name)).OrderBy(e => e.Path))
+{
+ string className = master.Name;
+ if (className == "Tag")
+ {
+ className = "TagItem";
+ }
+ if (className == "ContentEncoding")
+ {
+ className = "ContentEncodingItem";
+ }
+#>
+[GeneratedCode("MatroskaModels.tt", "1.0.0.0")]
+public sealed class <#=className#> : BaseModel
+{
+<#
+var subs = master.SubElements.OrderBy(e => e.Path);
+foreach (var sub in subs)
+{
+ string field = sub.Name;
+ if (sub.Name == "Tag")
+ {
+ sub.Name = "TagItem";
+ }
+ if (sub.Name == "ContentEncoding")
+ {
+ sub.Name = "ContentEncodingItem";
+ }
+
+ string a;
+ if (sub.IsList && field != "SignedElement")
+ {
+ a = string.Format(new string(' ', 4) + "[MatroskaElementDescriptor(MatroskaSpecification.{0}, typeof({1}))]", field, sub.Name);
+ }
+ else
+ {
+ a = string.Format(new string(' ', 4) + "[MatroskaElementDescriptor(MatroskaSpecification.{0})]", field);
+ }
+ WriteLine(a);
+ WriteLine(new string(' ', 4) + GetProperty(sub));
+ if (sub != subs.Last())
+ {
+ WriteLine("");
+ }
+}
+#>
+}
+
+<# } #>
+
+<#+
+public bool UseNameInsteadOfBinary(string elementName)
+{
+ return elementName.Contains("Block") && elementName != "BlockAddIDExtraData";
+}
+
+public void Loop(List<Element> all, Element current, string parentPath)
+{
+ current.SubElements = all.Where(e => e.Path.StartsWith(parentPath + "\\" + current.Name) && e.Level == current.Level + 1).ToList();
+ foreach (var sub in current.SubElements)
+ {
+ Loop(all, sub, current.Path);
+ }
+}
+
+public string GetProperty(Element property)
+{
+ string propertyType;
+ switch (property.ElementType)
+ {
+ case "utf-8":
+ propertyType = "string";
+ break;
+
+ case "string":
+ propertyType = "string";
+ break;
+
+ case "binary":
+ propertyType = property.UseNameInsteadOfBinary ? property.Name : "byte[]";
+ break;
+
+ case "date":
+ propertyType = "DateTime";
+ break;
+
+ case "float":
+ propertyType = "double";
+ break;
+
+ case "uinteger":
+ propertyType = "ulong";
+ break;
+
+ case "integer":
+ propertyType = "long";
+ break;
+
+ case "master":
+ propertyType = property.Name;
+ break;
+
+ default:
+ propertyType = "???";
+ break;
+ }
+
+ string x;
+ if (property.IsList)
+ {
+ x = string.Format("List<{0}>", propertyType) + (!property.Required ? "?" : "");
+ }
+ else
+ {
+ x = propertyType + (!property.Required || property.ElementType == "binary" ? "?" : "");
+ }
+
+ string name = property.Name;
+ if (property.IsList)
+ {
+ if (property.Name.EndsWith("y"))
+ {
+ name = property.Name.Replace("y", "ies");
+ }
+ else
+ {
+ name = name + "s";
+ }
+ }
+
+ string fixRequired = "";
+ if (property.Required && !propertyType.Contains("long") && !propertyType.Contains("double"))
+ {
+ fixRequired = " = null!;";
+ }
+
+ return "public " + x + " " + name + " { get; set; }" + fixRequired;
+}
+
+
+public class Element
+{
+ public string Name;
+ public bool UseNameInsteadOfBinary;
+ public bool IsList;
+ public string Path;
+ public string Id;
+ public string ElementType;
+ public bool Required;
+ public string Documentation;
+ public int Level;
+
+ public List<Element> SubElements = new List<Element>();
+}
+#>
\ No newline at end of file
diff --git a/Matroska/Models/SimpleBlock.cs b/Matroska/Models/SimpleBlock.cs
new file mode 100644
index 0000000..623bb40
--- /dev/null
+++ b/Matroska/Models/SimpleBlock.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Matroska.Models;
+
+/// <summary>
+/// http://matroska.sourceforge.net/technical/specs/index.html#simpleblock_structure
+/// </summary>
+public sealed class SimpleBlock : Block
+{
+ private const byte KeyFrameBit = 0b10000000;
+ private const byte DiscardableBit = 0b00000001;
+ /// <summary>
+ /// Keyframe, set when the Block contains only keyframes
+ /// </summary>
+ public bool IsKeyFrame { get; private set; }
+
+ /// <summary>
+ /// Discardable, the frames of the Block can be discarded during playing if needed
+ /// </summary>
+ public bool IsDiscardable { get; private set; }
+
+ public override void Parse(Span<byte> span)
+ {
+ base.Parse(span);
+
+ IsKeyFrame = (Flags & KeyFrameBit) == KeyFrameBit;
+ IsDiscardable = (Flags & DiscardableBit) == DiscardableBit;
+ }
+}
\ No newline at end of file
diff --git a/Matroska/Models/SimpleTag.cs b/Matroska/Models/SimpleTag.cs
new file mode 100644
index 0000000..2e6b501
--- /dev/null
+++ b/Matroska/Models/SimpleTag.cs
@@ -0,0 +1,21 @@
+using Matroska.Attributes;
+
+namespace Matroska.Models;
+
+public sealed class SimpleTag
+{
+ [MatroskaElementDescriptor(MatroskaSpecification.TagName)]
+ public string? TagName { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TagLanguage)]
+ public string? TagLanguage { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TagDefault)]
+ public ulong? TagDefault { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TagString)]
+ public string? TagString { get; set; }
+
+ [MatroskaElementDescriptor(MatroskaSpecification.TagBinary)]
+ public byte[]? TagBinary { get; set; }
+}
\ No newline at end of file
diff --git a/Matroska/NEbml/Core/DTDBase.cs b/Matroska/NEbml/Core/DTDBase.cs
new file mode 100644
index 0000000..50f0602
--- /dev/null
+++ b/Matroska/NEbml/Core/DTDBase.cs
@@ -0,0 +1,119 @@
+/* Copyright (c) 2011-2020 Oleg Zee
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+namespace NEbml.Core
+{
+ /// <summary>
+ /// Root class for Ebml type schema
+ /// </summary>
+ public class DTDBase
+ {
+ protected static ElementDescriptor Container(long id, string name = "")
+ {
+ return new ElementDescriptor(id, name, ElementType.MasterElement);
+ }
+
+ protected static ElementDescriptor Binary(long id, string name = "")
+ {
+ return new ElementDescriptor(id, name, ElementType.Binary);
+ }
+
+ protected static ElementDescriptor Uint(long id, string name = "")
+ {
+ return new ElementDescriptor(id, name, ElementType.UnsignedInteger);
+ }
+
+ protected static ElementDescriptor Int(long id, string name = "")
+ {
+ return new ElementDescriptor(id, name, ElementType.SignedInteger);
+ }
+
+ protected static ElementDescriptor Ascii(long id, string name = "")
+ {
+ return new ElementDescriptor(id, name, ElementType.AsciiString);
+ }
+
+ protected static ElementDescriptor Utf8(long id, string name = "")
+ {
+ return new ElementDescriptor(id, name, ElementType.Utf8String);
+ }
+
+ protected static ElementDescriptor Float(long id, string name = "")
+ {
+ return new ElementDescriptor(id, name, ElementType.Float);
+ }
+
+ protected static ElementDescriptor Date(long id, string name = "")
+ {
+ return new ElementDescriptor(id, name, ElementType.Date);
+ }
+
+ public class MasterElementDescriptor:ElementDescriptor
+ {
+ protected MasterElementDescriptor(long identifier, string name = "") : base(identifier, name, ElementType.MasterElement) {}
+ }
+ }
+
+ /// <summary>
+ /// Appendix C. EBML Standard definitions.
+ /// </summary>
+ public class StandardDtd:DTDBase
+ {
+ private StandardDtd() {}
+
+ public static readonly EBMLDesc
+ EBML = new EBMLDesc(0x1a45dfa3);
+ public static readonly SignatureSlotDesc
+ SignatureSlot = new SignatureSlotDesc(0x1b538667);
+ public static readonly ElementDescriptor
+ CRC32 = Container(0xc3).Named(nameof(CRC32)),
+ Void = Binary(0xec).Named(nameof(Void));
+
+ public sealed class EBMLDesc : MasterElementDescriptor
+ {
+ internal EBMLDesc(long id) : base(id, "EBML") { }
+
+ public readonly ElementDescriptor
+ EBMLVersion = Uint(0x4286, nameof(EBMLVersion)),
+ EBMLReadVersion = Uint(0x42f7, nameof(EBMLReadVersion)),
+ EBMLMaxIDLength = Uint(0x42f2, nameof(EBMLMaxIDLength)),
+ EBMLMaxSizeLength = Uint(0x42f3, nameof(EBMLMaxSizeLength)),
+ DocType = Ascii(0x4282, nameof(DocType)),
+ DocTypeVersion = Uint(0x4287, nameof(DocTypeVersion)),
+ DocTypeReadVersion = Uint(0x4285, nameof(DocTypeReadVersion));
+ }
+
+ public sealed class SignatureSlotDesc : MasterElementDescriptor
+ {
+ internal SignatureSlotDesc(long id) : base(id, "SignatureSlot") { }
+
+ public readonly ElementDescriptor
+ SignatureAlgo = Uint(0x7e8a, nameof(SignatureAlgo)),
+ SignatureHash = Uint(0x7e9a, nameof(SignatureHash)),
+ SignaturePublicKey = Binary(0x7ea5, nameof(SignaturePublicKey)),
+ Signature = Binary(0x7eb5, nameof(Signature)),
+ SignatureElements = Container(0x7e5b, nameof(SignatureElements)),
+ SignatureElementList = Container(0x7e7b, nameof(SignatureElementList)),
+ SignedElement = Binary(0x6532, nameof(SignedElement));
+ }
+ }
+}
diff --git a/Matroska/NEbml/Core/EbmlDataFormatException.cs b/Matroska/NEbml/Core/EbmlDataFormatException.cs
new file mode 100644
index 0000000..0d549c8
--- /dev/null
+++ b/Matroska/NEbml/Core/EbmlDataFormatException.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace NEbml.Core
+{
+ /// <summary>
+ /// Thrown to indicate the EBML data format violation.
+ /// </summary>
+ public class EbmlDataFormatException : System.IO.IOException
+ {
+ public EbmlDataFormatException()
+ {
+ }
+
+ public EbmlDataFormatException(string message)
+ : base(message)
+ {
+ }
+
+ public EbmlDataFormatException(string message, Exception cause)
+ : base(message, cause)
+ {
+ }
+ }
+}
diff --git a/Matroska/NEbml/Core/EbmlReader.cs b/Matroska/NEbml/Core/EbmlReader.cs
new file mode 100644
index 0000000..df71d4b
--- /dev/null
+++ b/Matroska/NEbml/Core/EbmlReader.cs
@@ -0,0 +1,577 @@
+/* Copyright (c) 2011-2020 Oleg Zee
+ *
+ * Original java code Copyright (c) 2008, Oleg S. Estekhin
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace NEbml.Core
+{
+ /// <summary>
+ /// The <code>EbmlReader</code> interface allows forward, read-only access to EBML data.
+ /// </summary>
+ public sealed class EbmlReader : IDisposable
+ {
+ private readonly Stack<Element> _containers;
+ private readonly Stream _source;
+ private Element _container;
+ private Element _element;
+ private byte[]? _sharedBuffer;
+
+ /// <summary>
+ /// Creates a new EBML reader.
+ /// </summary>
+ /// <param name="source">the source of bytes</param>
+ /// <exception cref="ArgumentNullException">if <code>source</code> is <code>null</code></exception>
+ public EbmlReader(Stream source) : this(source, long.MaxValue)
+ {
+ }
+
+ /// <summary>
+ /// Creates a new EBML reader.
+ /// </summary>
+ /// <param name="source">the source of bytes</param>
+ /// <param name="size">the maximum number of bytes to read from the source</param>
+ /// <exception cref="ArgumentNullException">if <code>source</code> is <code>null</code></exception>
+ /// <exception cref="ArgumentNullException">if <code>size</code> is negative</exception>
+ public EbmlReader(Stream source, long size)
+ {
+ if (size < 0L) throw new ArgumentException("size is negative");
+
+ _source = source ?? throw new ArgumentNullException(nameof(source));
+ _containers = new Stack<Element>();
+ _container = new Element(VInt.UnknownSize(2), size, ElementType.MasterElement);
+ _element = Element.Empty;
+ }
+
+ #region Public API
+
+ /// <summary>
+ /// Reads the next child element of the current container and positions the stream at the beginning of the element data.
+ /// </summary>
+ /// <returns><code>true</code> if the child element is available; <code>false</code> otherwise</returns>
+ /// <exception cref="EbmlDataFormatException">if the value of the element identifier or element data size read from the stream is reserved</exception>
+ public bool ReadNext()
+ {
+ Skip(_element.Remaining);
+ _container.Remaining -= _element.Size;
+ _element = _container;
+
+ if (_element.Remaining < 1)
+ {
+ _element = Element.Empty;
+ return false;
+ }
+
+ ReadElement();
+ return true;
+ }
+
+ /// <summary>
+ /// Reads the next child element of the current container at the specified position and positions the stream at the beginning of the element data.
+ /// </summary>
+ /// <param name="position">The exact position in the current container to read the next element from.</param>
+ /// <returns><code>true</code> if the child element is available; <code>false</code> otherwise</returns>
+ /// <exception cref="EbmlDataFormatException">if the value of the element identifier or element data size read from the stream is reserved</exception>
+ public bool ReadAt(long position)
+ {
+ _container.Remaining -= _element.Size;
+ _element = _container;
+
+ if (_element.Remaining < 1)
+ {
+ _element = Element.Empty;
+ return false;
+ }
+
+ // compute the desired position relative to the current position in the container
+ var relativePosition = position - (_container.Size - _container.Remaining);
+
+ if (relativePosition < 0)
+ {
+ throw new EbmlDataFormatException("invalid position, seeking backwards is not supported");
+ }
+
+ Skip(relativePosition);
+
+ ReadElement();
+ return true;
+ }
+
+ /// <summary>
+ /// Returns the identifier of the current element.
+ /// </summary>
+ /// <value>the element identifier</value>
+ /// <exception cref="InvalidOperationException">if the current element is not available</exception>
+ public VInt ElementId
+ {
+ get
+ {
+ if (_element.HasInvalidIdentifier)
+ throw new InvalidOperationException();
+ return _element.Identifier;
+ }
+ }
+
+ /// <summary>
+ /// Returns the data size of the current element.
+ /// </summary>
+ /// <value>the element data size in the encoded form</value>
+ /// <exception cref="InvalidOperationException">if the current element is not available</exception>
+ public long ElementSize
+ {
+ get
+ {
+ if (_element.HasInvalidIdentifier)
+ throw new InvalidOperationException();
+ return _element.Size;
+ }
+ }
+
+ /// <summary>
+ /// Gets starting position in file for current element
+ /// </summary>
+ public long ElementPosition { get; private set; }
+
+ /// <summary>
+ /// Instructs the reader to parse the current element data as sub-elements. The current container
+ /// will be saved on the stack and the current element will become the new container.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">if the current element is not available or if the element data was already accessed as some other type</exception>
+ public void EnterContainer()
+ {
+ if (_element.HasInvalidIdentifier || _element.Size != _element.Remaining || _element.Type != ElementType.None)
+ {
+ throw new InvalidOperationException();
+ }
+ _containers.Push(_container);
+ _container = _element;
+ _container.Type = ElementType.MasterElement;
+ _element = Element.Empty;
+ }
+
+ /// <summary>
+ /// Instructs the reader to return to the previous container.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">if the current container represents the whole input source</exception>
+ /// <exception cref="EndOfStreamException">if the input source reaches the end before reading all the bytes</exception>
+ /// <exception cref="IOException">if an I/O error has occurred</exception>
+ public void LeaveContainer()
+ {
+ if (_containers.Count == 0)
+ {
+ throw new InvalidOperationException();
+ }
+ _container.Remaining -= _element.Size;
+ _element = _container;
+ Skip(_element.Remaining);
+ _container = _containers.Pop();
+ }
+
+ /// <summary>
+ /// Reads the element data as a signed integer.
+ /// </summary>
+ /// <returns>the element data as a signed integer</returns>
+ public long ReadInt()
+ {
+ if (_element.HasInvalidIdentifier || _element.Size != _element.Remaining || _element.Type != ElementType.None)
+ {
+ throw new InvalidOperationException();
+ }
+ if (_element.Size > 8)
+ {
+ throw new EbmlDataFormatException("invalid signed integer size");
+ }
+ _element.Type = ElementType.SignedInteger;
+ var encodedValueSize = (int)_element.Size;
+
+ return encodedValueSize == 0 ? 0L : ReadSignedIntegerUnsafe(encodedValueSize);
+ }
+
+ /// <summary>
+ /// Reads the element data as an unsigned integer.
+ /// </summary>
+ /// <returns>the element data as an unsigned integer</returns>
+ /// <exception cref="InvalidOperationException">if the current element is not available or if the element data was already accessed as some other type</exception>
+ /// <exception cref="EbmlDataFormatException">if the element size is greater than <code>8</code></exception>
+ public ulong ReadUInt()
+ {
+ if (_element.HasInvalidIdentifier || _element.Size != _element.Remaining || _element.Type != ElementType.None)
+ {
+ throw new InvalidOperationException();
+ }
+ if (_element.Size > 8)
+ {
+ throw new EbmlDataFormatException("invalid unsigned integer size");
+ }
+ _element.Type = ElementType.UnsignedInteger;
+
+ var encodedValueSize = (int)_element.Size;
+ return encodedValueSize == 0 ? 0L : ReadUnsignedIntegerUnsafe(encodedValueSize);
+ }
+
+ /// <summary>
+ /// Reads the element data as a floating-point number.
+ ///
+ /// If the element data size is equal to <code>4</code>, then an instance of the <code>Float</code> is returned. If
+ /// the element data size is equal to <code>8</code>, then an instance of the <code>Double</code> is returned.
+ /// </summary>
+ /// <returns>the element data as a floating-point number</returns>
+ public double ReadFloat()
+ {
+ if (_element.HasInvalidIdentifier || _element.Size != _element.Remaining || _element.Type != ElementType.None)
+ {
+ throw new InvalidOperationException();
+ }
+ if (_element.Size != 4 && _element.Size != 8)
+ {
+ throw new EbmlDataFormatException("invalid float size");
+ }
+ _element.Type = ElementType.Float;
+ var encodedValueSize = (int)_element.Size;
+ var num = ReadUnsignedIntegerUnsafe(encodedValueSize);
+
+ switch (encodedValueSize)
+ {
+ case 4:
+ return new Union { ulval = num }.fval;
+ case 8:
+ return new Union { ulval = num }.dval;
+ default:
+ throw new EbmlDataFormatException("Incorrect float length");
+ }
+ }
+
+ /// <summary>
+ /// Reads the element data as a date.
+ /// </summary>
+ /// <returns>the element data as a date</returns>
+ public DateTime ReadDate()
+ {
+ if (_element.HasInvalidIdentifier || _element.Size != _element.Remaining || _element.Type != ElementType.None)
+ {
+ throw new InvalidOperationException();
+ }
+ if (_element.Size != 8)
+ {
+ throw new EbmlDataFormatException("invalid date size");
+ }
+ _element.Type = ElementType.Date;
+ var ns = ReadSignedIntegerUnsafe(8);
+
+ return MilleniumStart.AddTicks(ns / 100);
+ }
+
+ internal static readonly DateTime MilleniumStart = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ /// <summary>
+ /// Reads the element data as an ASCII string.
+ /// </summary>
+ /// <returns>the element data as an ASCII string</returns>
+ public string ReadAscii()
+ {
+ return ReadString(Encoding.ASCII);
+ }
+
+ /// <summary>
+ /// Reads the element data as an UTF8 string.
+ /// </summary>
+ /// <returns>the element data as an UTF8 string</returns>
+ public string ReadUtf()
+ {
+ return ReadString(Encoding.UTF8);
+ }
+
+ /// <summary>
+ /// Reads the element data as binary.
+ /// </summary>
+ /// <param name="buffer">the buffer into which the data is read</param>
+ /// <param name="offset">the start offset in <code>buffer</code> at which the data is written</param>
+ /// <param name="length">the maximum number of bytes to read</param>
+ /// <returns>the actual number of bytes read, or <code>-1</code> if the end of the element data is reached</returns>
+ public int ReadBinary(byte[] buffer, int offset, int length)
+ {
+ if (_element.HasInvalidIdentifier || _element.Type != ElementType.None && _element.Type != ElementType.Binary)
+ {
+ throw new InvalidOperationException();
+ }
+ _element.Type = ElementType.Binary;
+ if (_element.Remaining == 0L)
+ {
+ return -1;
+ }
+ var r = _source.ReadFully(buffer, offset, (int)Math.Min(_element.Remaining, length));
+ if (r == 0)
+ {
+ throw new EndOfStreamException();
+ }
+ _element.Remaining -= r;
+ return r;
+ }
+
+ #endregion
+
+ #region IDisposable Members
+
+ public void Dispose()
+ {
+ _source.Dispose();
+ _containers.Clear();
+ _container = Element.Empty;
+ _element = Element.Empty;
+ }
+
+ #endregion
+
+ #region Implementation
+
+ private byte[] GetSharedBuffer(int length)
+ {
+ if (_sharedBuffer == null || _sharedBuffer.Length < length)
+ {
+ _sharedBuffer = new byte[Math.Max(2048, length)];
+ }
+ return _sharedBuffer;
+ }
+
+ private byte[] FillBuffer(int length)
+ {
+ byte[] buffer = GetSharedBuffer(length);
+ ReadFully(buffer, 0, length);
+
+ return buffer;
+ }
+
+ /// <summary>
+ /// Reads <code>length</code> bytes of data from the current element data into an array of bytes.
+ /// </summary>
+ /// <param name="buffer">the buffer into which the data is read</param>
+ /// <param name="offset">the start offset in array <code>buffer</code> at which the data is written</param>
+ /// <param name="length">the number of bytes to read</param>
+ private void ReadFully(byte[] buffer, int offset, int length)
+ {
+ if (_element.Remaining < length)
+ {
+ throw new EndOfStreamException();
+ }
+ while (length > 0)
+ {
+ int r = _source.ReadFully(buffer, offset, length);
+ if (r == 0)
+ {
+ throw new EndOfStreamException();
+ }
+ offset += r;
+ length -= r;
+ _element.Remaining -= r;
+ }
+ }
+
+ /// <summary>
+ /// Skips <code>length</code> bytes from an input stream.
+ /// </summary>
+ /// <param name="length">the number of bytes to skip</param>
+ private void Skip(long length)
+ {
+ if (!_element.IsEmpty && _element.Remaining < length)
+ {
+ throw new EndOfStreamException();
+ }
+ long skipped = 1;
+ while (length > 0L && skipped > 0L)
+ {
+ var start = _source.Position;
+ var newPos = _source.Seek(length, SeekOrigin.Current);
+
+ skipped = newPos - start;
+ length -= skipped;
+ _element.Remaining -= skipped;
+ }
+ while (length > 0L)
+ {
+ var buffer = GetSharedBuffer(2048);
+ var r = _source.ReadFully(buffer, 0, (int)Math.Min(length, buffer.Length));
+ if (r == 0)
+ {
+ throw new EndOfStreamException();
+ }
+ length -= r;
+ _element.Remaining -= r;
+ }
+ }
+
+ private void ReadElementOriginal()
+ {
+ ElementPosition = _source.Position;
+
+ var identifier = ReadVarInt(4);
+
+ if (identifier.IsReserved)
+ {
+ throw new EbmlDataFormatException("invalid element identifier value");
+ }
+
+ var size = ReadVarInt(8).Value;
+ if (size > (ulong)_container.Remaining)
+ {
+ throw new EbmlDataFormatException("invalid element size value");
+ }
+
+ _element = new Element(identifier, (long)size, ElementType.None);
+ }
+
+ private void ReadElement()
+ {
+ ElementPosition = _source.Position;
+
+ var identifier = ReadVarInt(4);
+
+ if (identifier.IsReserved)
+ {
+ throw new EbmlDataFormatException("invalid element identifier value");
+ }
+
+ var size = ReadVarInt(8).Value;
+ if ((int)size != -1 && size > (ulong)_container.Remaining) // ⭐ I added the check for -1 here
+ {
+ throw new EbmlDataFormatException("invalid element size value");
+ }
+
+ _element = new Element(identifier, (long)size, ElementType.None);
+ }
+
+ /// <summary>
+ /// Reads the element data as a variable size integer.
+ /// </summary>
+ /// <returns>a variable size integer, or <code>null</code> if the end of the input source is reached</returns>
+ /// <exception cref="EbmlDataFormatException">if the input source contains length descriptor with zero value</exception>
+ /// <exception cref="EndOfStreamException">if the input source reaches the end before reading all the bytes</exception>
+ /// <exception cref="IOException">if an I/O error has occurred</exception>
+ private VInt ReadVarInt(int maxLength)
+ {
+ var value = VInt.Read(_source, maxLength, GetSharedBuffer(maxLength));
+ if (_element.Remaining < value.Length)
+ throw new EbmlDataFormatException();
+
+ _element.Remaining -= value.Length;
+ return value;
+ }
+
+ /// <summary>
+ /// Reads the element data as a signed integer.
+ /// </summary>
+ /// <param name="length">the number of bytes to read</param>
+ /// <returns>the element data as a signed integer</returns>
+ private long ReadSignedIntegerUnsafe(int length)
+ {
+ var buffer = FillBuffer(length);
+ long result = (sbyte)buffer[0]; // with sign extension
+ for (var i = 1; i < length; i++)
+ {
+ result = result << 8 | buffer[i] & 0xff;
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Reads the element data as an unsigned integer.
+ /// </summary>
+ /// <param name="length">the number of bytes to read</param>
+ /// <returns>the element data as an unsigned integer</returns>
+ private ulong ReadUnsignedIntegerUnsafe(int length)
+ {
+ var buffer = FillBuffer(length);
+
+ ulong result = 0;
+ for (var i = 0; i < length; i++)
+ {
+ result = (result << 8) | buffer[i];
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Reads the element data as a string in the specified charset.
+ /// </summary>
+ /// <param name="decoder">the name of the charset to be used to decode the bytes</param>
+ /// <returns>the element data as a string</returns>
+ private string ReadString(Encoding decoder)
+ {
+ if (_element.HasInvalidIdentifier || _element.Size != _element.Remaining || _element.Type != ElementType.None)
+ {
+ throw new InvalidOperationException();
+ }
+ _element.Type = ElementType.AsciiString; // or UTF_8_STRING, but for the housekeeping it does not matter
+ var encodedValueSize = (int)_element.Size;
+ if (encodedValueSize == 0)
+ {
+ return "";
+ }
+
+ var buffer = FillBuffer(encodedValueSize);
+ while (encodedValueSize > 0 && buffer[encodedValueSize - 1] == 0)
+ {
+ encodedValueSize--;
+ }
+
+ return decoder.GetString(buffer, 0, encodedValueSize);
+ }
+
+ #endregion
+
+ #region the element frame information
+
+ private class Element
+ {
+ public static readonly Element Empty = new Element();
+
+ public readonly VInt Identifier;
+ public readonly long Size;
+
+ public bool IsEmpty => !Identifier.IsValidIdentifier && Size == 0 && Type == ElementType.None;
+
+ public bool HasInvalidIdentifier => !Identifier.IsValidIdentifier;
+
+ public long Remaining;
+
+ public ElementType Type;
+
+ public Element(VInt identifier, long sizeValue, ElementType type)
+ {
+ Identifier = identifier;
+ Size = sizeValue;
+ Remaining = sizeValue;
+ Type = type;
+ }
+
+ private Element()
+ : this(VInt.UnknownSize(2), 0L, ElementType.None)
+ {
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Matroska/NEbml/Core/EbmlWriter.cs b/Matroska/NEbml/Core/EbmlWriter.cs
new file mode 100644
index 0000000..e3dafa1
--- /dev/null
+++ b/Matroska/NEbml/Core/EbmlWriter.cs
@@ -0,0 +1,270 @@
+/* Copyright (c) 2011-2020 Oleg Zee
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace NEbml.Core
+{
+ /// <summary>
+ /// Writes EBML file
+ /// </summary>
+ public class EbmlWriter
+ {
+ protected readonly Stream _stream;
+
+ /// <summary>
+ /// Initializes a new instance of the EbmlWriter class
+ /// </summary>
+ /// <param name="stream"></param>
+ public EbmlWriter(Stream stream)
+ {
+ _stream = stream ?? throw new ArgumentNullException(nameof(stream));
+ }
+
+ /// <summary>
+ /// Starts nested stream. Upon disposal of nested stream, the size is calculated and written to nested element header.
+ /// Example:
+ /// using(var data = writer.StartMasterElement(InnerDataElementId))
+ /// {
+ /// data.WriteInt(DataItem1Id, 139874)
+ /// data.WriteUtf(DataItem2Id, "Hello world");
+ /// }
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <returns></returns>
+ public MasterBlockWriter StartMasterElement(VInt elementId)
+ {
+ return new MasterBlockWriter(this, elementId);
+ }
+
+ /// <summary>
+ /// Writes element header
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <param name="size"></param>
+ /// <returns></returns>
+ public int WriteElementHeader(VInt elementId, VInt size)
+ {
+ return elementId.Write(_stream) + size.Write(_stream);
+ }
+
+ #region Writing atomic elements
+
+ /// <summary>
+ /// Writes signed integer value
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ public int Write(VInt elementId, long value)
+ {
+ var length = value == 0 ? 0u : 1u;
+ unchecked
+ {
+ var mask = (long)0xffffffffffffff80L;
+ for (var expected = value < 0L ? mask : 0L; length < 8 && (mask & value) != expected; mask <<= 8, expected <<= 8)
+ {
+ length++;
+ }
+ }
+
+ return elementId.Write(_stream) + EncodeWidth(length).Write(_stream) + WriteInt(value, length);
+ }
+
+ /// <summary>
+ /// Writes unsigned integer value
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ public int Write(VInt elementId, ulong value)
+ {
+ var length = 0u;
+ for (var mask = UInt64.MaxValue; (value & mask) != 0 && length < 8; mask <<= 8)
+ {
+ length++;
+ }
+
+ return elementId.Write(_stream) + EncodeWidth(length).Write(_stream) + WriteUInt(value, length);
+ }
+
+ /// <summary>
+ /// Writes datetime value
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <param name="dateTime"></param>
+ /// <returns></returns>
+ public int Write(VInt elementId, DateTime dateTime)
+ {
+ var d = (dateTime - EbmlReader.MilleniumStart).Ticks * 100;
+ return elementId.Write(_stream) + EncodeWidth(8).Write(_stream) + WriteInt(d, 8);
+ }
+
+ /// <summary>
+ /// Writes floating point number
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ public int Write(VInt elementId, float value)
+ {
+ var u = new Union {fval = value}.uival;
+ return elementId.Write(_stream) + EncodeWidth(4).Write(_stream) + WriteInt(u, 4);
+ }
+
+ /// <summary>
+ /// Writes floating point number with double precision
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ public int Write(VInt elementId, double value)
+ {
+ var u = new Union { dval = value }.ulval;
+ return elementId.Write(_stream) + EncodeWidth(8).Write(_stream) + WriteUInt(u, 8);
+ }
+
+ /// <summary>
+ /// Writes binary data
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <param name="data"></param>
+ /// <param name="offset"></param>
+ /// <param name="length"></param>
+ /// <returns></returns>
+ public int Write(VInt elementId, byte[] data, int offset, int length)
+ {
+ var headLen = elementId.Write(_stream) + EncodeWidth((uint) length).Write(_stream);
+ _stream.Write(data, offset, length);
+ return headLen + length;
+ }
+
+ /// <summary>
+ /// Writes binary data
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <param name="data"></param>
+ /// <returns></returns>
+ public int Write(VInt elementId, byte[] data)
+ {
+ return Write(elementId, data, 0, data.Length);
+ }
+
+ /// <summary>
+ /// Writes string in ASCII encoding
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ public int WriteAscii(VInt elementId, string value)
+ {
+ if (value == null) throw new ArgumentNullException(nameof(value));
+ var buffer = string.IsNullOrEmpty(value) ? new byte[0] : Encoding.ASCII.GetBytes(value);
+ return Write(elementId, buffer, 0, buffer.Length);
+ }
+
+ /// <summary>
+ /// Writes string in UTF-8 encoding
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ public int WriteUtf(VInt elementId, string value)
+ {
+ if (value == null) throw new ArgumentNullException(nameof(value));
+ if (!value.IsNormalized(NormalizationForm.FormC))
+ {
+ value = value.Normalize(NormalizationForm.FormC);
+ }
+ var buffer = string.IsNullOrEmpty(value) ? new byte[0] : Encoding.UTF8.GetBytes(value);
+ return Write(elementId, buffer, 0, buffer.Length);
+ }
+
+ /// <summary>
+ /// Writes raw binary data
+ /// </summary>
+ /// <param name="buffer"></param>
+ /// <param name="offset"></param>
+ /// <param name="length"></param>
+ /// <returns></returns>
+ public int Write(byte[] buffer, int offset, int length)
+ {
+ _stream.Write(buffer, offset, length);
+ return length;
+ }
+
+ #endregion
+
+ #region Implementation
+
+ private int WriteInt(Int64 value, uint length)
+ {
+ if(length > 8) throw new ArgumentOutOfRangeException(nameof(length));
+ if (value == 0 && length == 0) return 0;
+
+ var buffer = new byte[length];
+
+ var p = (int)length;
+ for (var data = value; --p >= 0; data >>= 8)
+ {
+ buffer[p] = (byte)(data & 0xff);
+ }
+
+ return Write(buffer, 0, (int)length);
+ }
+
+ private int WriteUInt(UInt64 value, uint length)
+ {
+ if (length > 8) throw new ArgumentOutOfRangeException(nameof(length));
+ if (value == 0 && length == 0) return 0;
+
+ var buffer = new byte[length];
+
+ var p = (int)length;
+ for (var data = value; --p >= 0; data >>= 8)
+ {
+ buffer[p] = (byte)(data & 0xff);
+ }
+
+ return Write(buffer, 0, (int) length);
+ }
+
+ [ThreadStatic] // avoid thread sync code
+ private static VInt[]? __smallSizes;
+
+ private const int CachedWidthCount = 20;
+
+ private static VInt EncodeWidth(uint width)
+ {
+ if (__smallSizes == null)
+ {
+ __smallSizes = Enumerable.Range(0, CachedWidthCount).Select(i => VInt.EncodeSize((ulong)i)).ToArray();
+ }
+ return width < CachedWidthCount ? __smallSizes[width] : VInt.EncodeSize(width);
+ }
+
+ #endregion
+ }
+}
diff --git a/Matroska/NEbml/Core/ElementDescriptor.cs b/Matroska/NEbml/Core/ElementDescriptor.cs
new file mode 100644
index 0000000..7d3489c
--- /dev/null
+++ b/Matroska/NEbml/Core/ElementDescriptor.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Text;
+
+namespace NEbml.Core
+{
+ /// <summary>
+ /// Defines the EBML element description.
+ /// </summary>
+ public class ElementDescriptor
+ {
+ /// <summary>
+ /// Initializes a new instance of the <code>ElementDescriptor</code> class.
+ /// </summary>
+ /// <param name="identifier"></param>
+ /// <param name="name"></param>
+ /// <param name="type"></param>
+ public ElementDescriptor(ulong identifier, string name, ElementType type)
+ : this(VInt.FromEncoded(identifier), name, type)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <code>ElementDescriptor</code> class.
+ /// </summary>
+ /// <param name="identifier"></param>
+ /// <param name="name"></param>
+ /// <param name="type"></param>
+ public ElementDescriptor(long identifier, string name, ElementType type)
+ : this(VInt.FromEncoded((ulong)identifier), name, type)
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <code>ElementDescriptor</code> class.
+ /// </summary>
+ /// <param name="identifier">the element identifier</param>
+ /// <param name="name">the element name or <code>null</code> if the name is not known</param>
+ /// <param name="type">the element type or <code>null</code> if the type is not known</param>
+ /// <exception cref="ArgumentNullException">if <code>identifier</code> is <code>null</code></exception>
+ private ElementDescriptor(VInt identifier, string name, ElementType type)
+ {
+ if (!identifier.IsValidIdentifier)
+ throw new ArgumentException("Value is not valid identifier", nameof(identifier));
+
+ Identifier = identifier;
+ Name = name;
+ Type = type;
+ }
+
+ /// <summary>
+ /// Returns the element identifier.
+ /// </summary>
+ /// <value>the element identifier in the encoded form</value>
+ public VInt Identifier { get; }
+
+ /// <summary>
+ /// Returns the element name.
+ /// </summary>
+ /// <value>the element name or &lt;code&gt;null&lt;/code&gt; if the name is not known</value>
+ public string Name { get; }
+
+ /// <summary>
+ /// Returns the element type.
+ /// </summary>
+ /// <value>the element type or &lt;code&gt;null&lt;/code&gt; if the type is not known</value>
+ public ElementType Type { get; }
+
+ public override int GetHashCode()
+ {
+ int result = 17;
+ result = 37*result + Identifier.GetHashCode();
+ result = 37*result + (Name == null ? 0 : Name.GetHashCode());
+ result = 37*result + (Type == ElementType.None ? 0 : Type.GetHashCode());
+ return result;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj is ElementDescriptor o2)
+ {
+ return Equals(Identifier, o2.Identifier)
+ && Equals(Name, o2.Name)
+ && Equals(Type, o2.Type);
+ }
+ return false;
+ }
+
+ public override string ToString()
+ {
+ var buffer = new StringBuilder();
+ buffer.Append("ElementDescriptor(");
+ buffer.Append("identifier=").Append(Identifier);
+ if (Name != null)
+ {
+ buffer.Append(',').Append("name=").Append(Name);
+ }
+ if (Type != ElementType.None)
+ {
+ buffer.Append(',').Append("type=").Append(Type);
+ }
+ buffer.Append(')');
+ return buffer.ToString();
+ }
+
+ /// <summary>
+ /// Returns a new descriptor with updated name.
+ /// </summary>
+ /// <param name="name"></param>
+ /// <returns></returns>
+ public ElementDescriptor Named(string name)
+ {
+ return new ElementDescriptor(Identifier, name, Type);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Matroska/NEbml/Core/ElementType.cs b/Matroska/NEbml/Core/ElementType.cs
new file mode 100644
index 0000000..aa14759
--- /dev/null
+++ b/Matroska/NEbml/Core/ElementType.cs
@@ -0,0 +1,50 @@
+namespace NEbml.Core
+{
+ /// <summary>
+ /// Defines the EBML element types
+ /// </summary>
+ public enum ElementType
+ {
+ /// <summary>
+ /// The signed integer
+ /// </summary>
+ SignedInteger,
+
+ /// <summary>
+ /// The unsigned integer
+ /// </summary>
+ UnsignedInteger,
+
+ /// <summary>
+ /// The floating point number
+ /// </summary>
+ Float,
+
+ /// <summary>
+ /// The character string in the ASCII encoding
+ /// </summary>
+ AsciiString,
+
+ /// <summary>
+ /// The character string in the UTF-8 encoding
+ /// </summary>
+ Utf8String,
+
+ /// <summary>
+ /// The date
+ /// </summary>
+ Date,
+
+ /// <summary>
+ /// The binary data
+ /// </summary>
+ Binary,
+
+ /// <summary>
+ /// Contains other EBML sub-elements of the next lower level
+ /// </summary>
+ MasterElement,
+
+ None = -1
+ }
+}
\ No newline at end of file
diff --git a/Matroska/NEbml/Core/MasterBlockWriter.cs b/Matroska/NEbml/Core/MasterBlockWriter.cs
new file mode 100644
index 0000000..a1a3ae2
--- /dev/null
+++ b/Matroska/NEbml/Core/MasterBlockWriter.cs
@@ -0,0 +1,63 @@
+/* Copyright (c) 2011-2020 Oleg Zee
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+using System;
+using System.IO;
+
+namespace NEbml.Core
+{
+ /// <summary>
+ /// Supplementary EbmlWriter implementation for use with StartMasterElement method
+ /// </summary>
+ public class MasterBlockWriter : EbmlWriter, IDisposable
+ {
+ private readonly Action _flushImpl;
+
+ internal MasterBlockWriter(EbmlWriter writer, VInt elementId)
+ : base(new MemoryStream())
+ {
+ var flushed = false;
+ _flushImpl = () =>
+ {
+ if (flushed) return;
+ var data = ((MemoryStream) _stream).ToArray();
+ writer.Write(elementId, data);
+
+ flushed = true;
+ _stream.Close();
+ };
+ }
+
+ /// <summary>
+ /// Flushed the writer data to master stream
+ /// </summary>
+ public void FlushData()
+ {
+ _flushImpl();
+ }
+
+ public void Dispose()
+ {
+ FlushData();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Matroska/NEbml/Core/StreamExtensions.cs b/Matroska/NEbml/Core/StreamExtensions.cs
new file mode 100644
index 0000000..70a1934
--- /dev/null
+++ b/Matroska/NEbml/Core/StreamExtensions.cs
@@ -0,0 +1,21 @@
+using System.IO;
+
+namespace NEbml.Core
+{
+ public static class StreamExtensions
+ {
+ public static int ReadFully(this Stream stream, byte[] buffer, int offset, int count)
+ {
+ int bytesRead = 0;
+ int totalBytesRead = 0;
+
+ do
+ {
+ bytesRead = stream.Read(buffer, offset + totalBytesRead, count - totalBytesRead);
+ totalBytesRead += bytesRead;
+ } while (bytesRead > 0 && totalBytesRead < count);
+
+ return totalBytesRead;
+ }
+ }
+}
diff --git a/Matroska/NEbml/Core/Union.cs b/Matroska/NEbml/Core/Union.cs
new file mode 100644
index 0000000..93e9ae6
--- /dev/null
+++ b/Matroska/NEbml/Core/Union.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace NEbml.Core
+{
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct Union
+ {
+ [FieldOffset(0)]
+ public UInt32 uival;
+ [FieldOffset(0)]
+ public float fval;
+
+ [FieldOffset(0)]
+ public UInt64 ulval;
+ [FieldOffset(0)]
+ public double dval;
+ }
+}
\ No newline at end of file
diff --git a/Matroska/NEbml/Core/VInt.cs b/Matroska/NEbml/Core/VInt.cs
new file mode 100644
index 0000000..9751f26
--- /dev/null
+++ b/Matroska/NEbml/Core/VInt.cs
@@ -0,0 +1,287 @@
+/* Copyright (c) 2011-2020 Oleg Zee
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+using System;
+using System.Diagnostics;
+using System.IO;
+
+namespace NEbml.Core
+{
+ /// <summary>
+ /// Variable size integer implementation as of http://www.matroska.org/technical/specs/rfc/index.html
+ /// </summary>
+ public readonly struct VInt
+ {
+ private readonly sbyte _length;
+
+ private VInt(ulong encodedValue, int length)
+ {
+ if(length < 1 || length > 8)
+ throw new ArgumentOutOfRangeException(nameof(length));
+
+ EncodedValue = encodedValue;
+ _length = (sbyte)length;
+ }
+
+ #region public API
+
+ /// <summary>
+ /// Gets the value
+ /// </summary>
+ public ulong Value => EncodedValue & DataBitsMask[_length];
+
+ /// <summary>
+ /// Gets true if value is reserved (i.e. all data bits are zeros or 1's)
+ /// </summary>
+ public bool IsReserved => Value == DataBitsMask[_length];
+
+ /// <summary>
+ /// Gets true if value is correct identifier
+ /// </summary>
+ public bool IsValidIdentifier
+ {
+ get
+ {
+ var isShortest = _length == 1 || Value > DataBitsMask[_length - 1];
+ return isShortest && !IsReserved;
+ }
+ }
+
+ /// <summary>
+ /// Gets the encoded value.
+ /// </summary>
+ public ulong EncodedValue { get; }
+
+ /// <summary>
+ /// Gets the encoded value's length
+ /// </summary>
+ public int Length => _length;
+
+ public static implicit operator ulong?(VInt value)
+ {
+ return !value.IsReserved ? value.Value : (ulong?) null;
+ }
+
+ #endregion
+
+ #region constructors
+
+ /// <summary>
+ /// Returns VInt value with specific encoded value with a specific length.
+ /// </summary>
+ /// <param name="value">(Size) value to be encoded.</param>
+ /// <param name="length">VInt length to store value to.</param>
+ /// <returns></returns>
+ public static VInt EncodeSize(ulong value, int length = 0)
+ {
+ if (value > MaxSizeValue)
+ throw new ArgumentException("Value exceed VInt capacity", nameof(value));
+ if (length < 0 || length > 8)
+ throw new ArgumentOutOfRangeException(nameof(length));
+ if (length > 0 && DataBitsMask[length] <= value)
+ throw new ArgumentException("Specified width is not sufficient to encode value", nameof(value));
+
+ if(length == 0)
+ {
+ while (DataBitsMask[++length] <= value) {}
+ }
+
+ var sizeMarker = 1UL << (7 * length);
+ return new VInt(value | sizeMarker, length);
+ }
+
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="elementId"></param>
+ /// <returns></returns>
+ public static VInt MakeId(uint elementId)
+ {
+ if (elementId > MaxElementIdValue)
+ throw new ArgumentException("Value exceed VInt capacity", nameof(elementId));
+
+ var id = EncodeSize(elementId);
+ Debug.Assert(id._length <= 4);
+ return id;
+ }
+
+ /// <summary>
+ /// Creates VInt for unknown size (all databits are 1's)
+ /// </summary>
+ /// <param name="length"></param>
+ /// <returns></returns>
+ public static VInt UnknownSize(int length)
+ {
+ if (length < 0 || length > 8)
+ throw new ArgumentOutOfRangeException(nameof(length));
+
+ var sizeMarker = 1UL << (7 * length);
+ var dataBits = (1UL << (7 * length)) - 1;
+ return new VInt(sizeMarker | dataBits, length);
+ }
+
+ /// <summary>
+ /// Constructs the VInt value from its encoded form
+ /// </summary>
+ /// <param name="encodedValue"></param>
+ /// <returns></returns>
+ public static VInt FromEncoded(ulong encodedValue)
+ {
+ if (encodedValue == 0)
+ throw new ArgumentException("Zero is not a correct value", nameof(encodedValue));
+
+ var mostSignificantOctetIndex = 7;
+ while ((encodedValue >> mostSignificantOctetIndex * 8) == 0x0)
+ {
+ mostSignificantOctetIndex--;
+ }
+
+ var marker = (byte)((encodedValue >> mostSignificantOctetIndex * 8) & 0xff);
+ var extraBytes = (marker >> 4 > 0) ? ExtraBytesSize[marker >> 4] : 4 + ExtraBytesSize[marker];
+
+ if (extraBytes != mostSignificantOctetIndex)
+ throw new ArgumentException("Width marker does not match its position", nameof(encodedValue));
+
+ return new VInt(encodedValue, extraBytes + 1);
+ }
+
+ #endregion
+
+ #region Persisting
+
+ /// <summary>
+ /// Reads the value from the stream
+ /// </summary>
+ /// <param name="source"></param>
+ /// <param name="maxLength">Maximal expected length (either 4 or 8)</param>
+ /// <param name="buffer">The buffer for optimization purposes. Must match the maxlength</param>
+ /// <returns></returns>
+ public static VInt Read(Stream source, int maxLength, byte[] buffer)
+ {
+ buffer = buffer ?? new byte[maxLength];
+
+ if (source.ReadFully(buffer, 0, 1) == 0)
+ {
+ throw new EndOfStreamException();
+ }
+
+ if(buffer[0] == 0)
+ throw new EbmlDataFormatException("Length bigger than 8 is not supported");
+ // TODO handle EBMLMaxSizeWidth
+
+ var extraBytes = (buffer[0] & 0xf0) != 0
+ ? ExtraBytesSize[buffer[0] >> 4]
+ : 4 + ExtraBytesSize[buffer[0]];
+
+ if (extraBytes + 1 > maxLength)
+ throw new EbmlDataFormatException($"Expected VInt with a max length of {maxLength}. Got {extraBytes + 1}");
+
+ if (source.ReadFully(buffer, 1, extraBytes) != extraBytes)
+ {
+ throw new EndOfStreamException();
+ }
+
+ ulong encodedValue = buffer[0];
+ for (var i = 0; i < extraBytes; i++)
+ {
+ encodedValue = encodedValue << 8 | buffer[i + 1];
+ }
+ return new VInt(encodedValue, extraBytes + 1);
+ }
+
+ /// <summary>
+ /// Writes value to stream
+ /// </summary>
+ /// <param name="stream"></param>
+ public int Write(Stream stream)
+ {
+ if (stream == null) throw new ArgumentNullException(nameof(stream));
+
+ var buffer = new byte[Length];
+
+ int p = Length;
+ for (var data = EncodedValue; --p >= 0; data >>= 8)
+ {
+ buffer[p] = (byte)(data & 0xff);
+ }
+
+ stream.Write(buffer, 0, buffer.Length);
+ return buffer.Length;
+ }
+
+ private static readonly sbyte[] ExtraBytesSize =
+ { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ /// <summary>
+ /// Maps length to data bits mask
+ /// </summary>
+ private static readonly ulong[] DataBitsMask =
+ {
+ (1L << 0) - 1,
+ (1L << 7) - 1,
+ (1L << 14) - 1,
+ (1L << 21) - 1,
+ (1L << 28) - 1,
+ (1L << 35) - 1,
+ (1L << 42) - 1,
+ (1L << 49) - 1,
+ (1L << 56) - 1
+ };
+
+ private const ulong MaxValue = (1L << 56) - 1;
+ /// <summary>
+ /// All 1's is reserved for unknown size
+ /// </summary>
+ private const ulong MaxSizeValue = MaxValue - 1;
+ private const ulong MaxElementIdValue = (1 << 28) - 1;
+
+ #endregion
+
+ #region Equality members
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (EncodedValue.GetHashCode()*397) ^ _length.GetHashCode();
+ }
+ }
+
+ private bool Equals(VInt other)
+ {
+ return other.EncodedValue == EncodedValue && other._length == _length;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is VInt i && Equals(i);
+ }
+
+ #endregion
+
+ public override string ToString()
+ {
+ return $"VInt, value = {Value}, length = {Length}, encoded = {EncodedValue:X}";
+ }
+ }
+}
diff --git a/Matroska/NEbml/Readme.md b/Matroska/NEbml/Readme.md
new file mode 100644
index 0000000..4ad10d3
--- /dev/null
+++ b/Matroska/NEbml/Readme.md
@@ -0,0 +1,5 @@
+# Info
+
+- source copied from https://github.com/OlegZee/NEbml
+- fixed some `?` nullability
+- Fixed `ReadElement` (https://github.com/OlegZee/NEbml/issues/13)
\ No newline at end of file
diff --git a/Matroska/ebml.xml b/Matroska/ebml.xml
new file mode 100644
index 0000000..1d5f456
--- /dev/null
+++ b/Matroska/ebml.xml
@@ -0,0 +1,42 @@
+<EBMLSchema xmlns="urn:ietf:rfc:8794" docType="ebml" version="1">
+ <element name="EBML" path="\EBML" id="0x1A45DFA3" type="master" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set the EBML characteristics of the data to follow. Each EBML document has to start with this.</documentation>
+ </element>
+ <element name="EBMLVersion" path="\EBML\EBMLVersion" id="0x4286" type="uinteger" range="not 0" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The version of EBML parser used to create the file.</documentation>
+ </element>
+ <element name="EBMLReadVersion" path="\EBML\EBMLReadVersion" id="0x42F7" type="uinteger" range="1" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The minimum EBML version a parser has to support to read this file.</documentation>
+ </element>
+ <element name="EBMLMaxIDLength" path="\EBML\EBMLMaxIDLength" id="0x42F2" type="uinteger" range="&gt;=4" default="4" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The maximum length of the IDs you'll find in this file (4 or less in Matroska).</documentation>
+ </element>
+ <element name="EBMLMaxSizeLength" path="\EBML\EBMLMaxSizeLength" id="0x42F3" type="uinteger" range="not 0" default="8" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The maximum length of the sizes you'll find in this file (8 or less in Matroska). This does not override the element size indicated at the beginning of an element. Elements that have an indicated size which is larger than what is allowed by EBMLMaxSizeLength shall be considered invalid.</documentation>
+ </element>
+ <element name="DocType" path="\EBML\DocType" id="0x4282" type="string" length="&gt;0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A string that describes the type of document that follows this EBML header, for example 'matroska' or 'webm'.</documentation>
+ </element>
+ <element name="DocTypeVersion" path="\EBML\DocTypeVersion" id="0x4287" type="uinteger" range="not 0" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The version of DocType interpreter used to create the file.</documentation>
+ </element>
+ <element name="DocTypeReadVersion" path="\EBML\DocTypeReadVersion" id="0x4285" type="uinteger" range="not 0" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The minimum DocType version an interpreter has to support to read this file.</documentation>
+ </element>
+ <element name="DocTypeExtension" path="\EBML\DocTypeExtension" id="0x4281" type="master">
+ <documentation lang="en" purpose="definition">A DocTypeExtension adds extra Elements to the main DocType+DocTypeVersion tuple it's attached to. An EBML Reader **MAY** know these extra Elements and how to use them. A DocTypeExtension **MAY** be used to iterate between experimental Elements before they are integrated into a regular DocTypeVersion. Reading one DocTypeExtension version of a DocType+DocTypeVersion tuple doesn't imply one should be able to read upper versions of this DocTypeExtension.</documentation>
+ </element>
+ <element name="DocTypeExtensionName" path="\EBML\DocTypeExtension\DocTypeExtensionName" id="0x4283" type="string" length="&gt;0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The name of the DocTypeExtension to differentiate it from other DocTypeExtensions of the same DocType+DocTypeVersion tuple. A DocTypeExtensionName value **MUST** be unique within the EBML Header.</documentation>
+ </element>
+ <element name="DocTypeExtensionVersion" path="\EBML\DocTypeExtension\DocTypeExtensionVersion" id="0x4284" type="uinteger" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The version of the DocTypeExtension. Different DocTypeExtensionVersion values of the same DocType + DocTypeVersion + DocTypeExtensionName tuple **MAY** contain completely different sets of extra Elements. An EBML Reader **MAY** support multiple versions of the same tuple, only one version of the tuple, or not support the tuple at all.</documentation>
+ </element>
+
+ <element name="Void" path="\(-\)Void" id="0xEC" type="binary">
+ <documentation lang="en" purpose="definition">Used to void damaged data, to avoid unexpected behaviors when using damaged data. The content is discarded. Also used to reserve space in a sub-element for later use.</documentation>
+ </element>
+ <element name="CRC-32" path="\(1-\)CRC-32" id="0xBF" type="binary" length="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The CRC is computed on all the data of the Master element it's in. The CRC element should be the first in it's parent master for easier reading. All level 1 elements should include a CRC-32. The CRC in use is the IEEE CRC32 Little Endian.</documentation>
+ </element>
+</EBMLSchema>
\ No newline at end of file
diff --git a/Matroska/ebml_matroska.xml b/Matroska/ebml_matroska.xml
new file mode 100644
index 0000000..97327ae
--- /dev/null
+++ b/Matroska/ebml_matroska.xml
@@ -0,0 +1,1938 @@
+<EBMLSchema xmlns="urn:ietf:rfc:8794" docType="matroska" version="4">
+ <!-- constraints on EBML Header Elements -->
+ <element name="EBMLMaxIDLength" path="\EBML\EBMLMaxIDLength" id="0x42F2" type="uinteger" range="4" default="4" minOccurs="1" maxOccurs="1"/>
+ <element name="EBMLMaxSizeLength" path="\EBML\EBMLMaxSizeLength" id="0x42F3" type="uinteger" range="1-8" default="8" minOccurs="1" maxOccurs="1"/>
+ <!-- Root Element-->
+ <element name="Segment" path="\Segment" id="0x18538067" type="master" minOccurs="1" maxOccurs="1" unknownsizeallowed="1">
+ <documentation lang="en" purpose="definition">The `Root Element` that contains all other `Top-Level Elements`; see (#data-layout).</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="SeekHead" path="\Segment\SeekHead" id="0x114D9B74" type="master" maxOccurs="2">
+ <documentation lang="en" purpose="definition">Contains seeking information of `Top-Level Elements`; see (#data-layout).</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Seek" path="\Segment\SeekHead\Seek" id="0x4DBB" type="master" minOccurs="1">
+ <documentation lang="en" purpose="definition">Contains a single seek entry to an EBML Element.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="SeekID" path="\Segment\SeekHead\Seek\SeekID" id="0x53AB" type="binary" length="4" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The binary EBML ID of a `Top-Level Element`.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="SeekPosition" path="\Segment\SeekHead\Seek\SeekPosition" id="0x53AC" type="uinteger" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The `Segment Position` ((#segment-position)) of a `Top-Level Element`.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Info" path="\Segment\Info" id="0x1549A966" type="master" minOccurs="1" maxOccurs="1" recurring="1">
+ <documentation lang="en" purpose="definition">Contains general information about the `Segment`.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="SegmentUUID" path="\Segment\Info\SegmentUUID" id="0x73A4" type="binary" length="16" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A randomly generated UID that identifies the `Segment` amongst many others (128 bits). It is equivalent to a Universally Unique Identifier (UUID) v4 [@!RFC9562] with all bits randomly (or pseudorandomly) chosen. An actual UUID v4 value, where some bits are not random, **MAY** also be used.</documentation>
+ <documentation lang="en" purpose="usage notes">
+ If the `Segment` is a part of a `Linked Segment`, then this element is **REQUIRED**.
+ The value of the UID **MUST** contain at least one bit set to 1.
+ </documentation>
+ <extension type="libmatroska" cppname="SegmentUID"/>
+ </element>
+ <element name="SegmentFilename" path="\Segment\Info\SegmentFilename" id="0x7384" type="utf-8" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A filename corresponding to this `Segment`.</documentation>
+ </element>
+ <element name="PrevUUID" path="\Segment\Info\PrevUUID" id="0x3CB923" type="binary" length="16" maxOccurs="1">
+ <documentation lang="en" purpose="definition">An ID that identifies the previous `Segment` of a `Linked Segment`.</documentation>
+ <documentation lang="en" purpose="usage notes">
+ If the `Segment` is a part of a `Linked Segment` that uses
+ Hard Linking ((#hard-linking)), then either the
+ `PrevUUID` or the `NextUUID` element is
+ **REQUIRED**. If a `Segment` contains a `PrevUUID`
+ but not a `NextUUID`, then it **MAY** be considered as the
+ last `Segment` of the `Linked Segment`. The `PrevUUID`
+ **MUST NOT** be equal to the `SegmentUUID`.
+ </documentation>
+ <extension type="libmatroska" cppname="PrevUID"/>
+ </element>
+ <element name="PrevFilename" path="\Segment\Info\PrevFilename" id="0x3C83AB" type="utf-8" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A filename corresponding to the file of the previous `Linked Segment`.</documentation>
+ <documentation lang="en" purpose="usage notes">
+ Provision of the previous filename is for display convenience,
+ but `PrevUUID` **SHOULD** be considered authoritative for identifying the previous `Segment` in a `Linked Segment`.
+ </documentation>
+ </element>
+ <element name="NextUUID" path="\Segment\Info\NextUUID" id="0x3EB923" type="binary" length="16" maxOccurs="1">
+ <documentation lang="en" purpose="definition">An ID that identifies the next `Segment` of a `Linked Segment`.</documentation>
+ <documentation lang="en" purpose="usage notes">
+ If the `Segment` is a part of a `Linked Segment` that uses Hard Linking ((#hard-linking)),
+ then either the `PrevUUID` or the `NextUUID` element is **REQUIRED**. If a `Segment` contains a `NextUUID` but not a `PrevUUID`, then it **MAY** be considered as the first `Segment` of the `Linked Segment`. The `NextUUID` **MUST NOT** be equal to the `SegmentUUID`.
+ </documentation>
+ <extension type="libmatroska" cppname="NextUID"/>
+ </element>
+ <element name="NextFilename" path="\Segment\Info\NextFilename" id="0x3E83BB" type="utf-8" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A filename corresponding to the file of the next `Linked Segment`.</documentation>
+ <documentation lang="en" purpose="usage notes">
+ Provision of the next filename is for display convenience,
+ but `NextUUID` **SHOULD** be considered authoritative for identifying the Next `Segment`.
+ </documentation>
+ </element>
+ <element name="SegmentFamily" path="\Segment\Info\SegmentFamily" id="0x4444" type="binary" length="16">
+ <documentation lang="en" purpose="definition">A UID that all `Segments` of a `Linked Segment` **MUST** share (128 bits). It is equivalent to a UUID v4 [@!RFC9562] with all bits randomly (or pseudorandomly) chosen. An actual UUID v4 value, where some bits are not random, **MAY** also be used.</documentation>
+ <documentation lang="en" purpose="usage notes">If the `Segment` `Info` contains a `ChapterTranslate` element, this element is **REQUIRED**.</documentation>
+ </element>
+ <element name="ChapterTranslate" path="\Segment\Info\ChapterTranslate" id="0x6924" type="master">
+ <documentation lang="en" purpose="definition">
+ The mapping between this `Segment` and a
+ segment value in the given Chapter Codec.
+ </documentation>
+ <documentation lang="en" purpose="rationale">
+ Chapter Codecs may need to address different segments, but they may not know of the way to identify such segments when stored in Matroska.
+ This element and its child elements add a way to map the internal segments known to the Chapter Codec to the `SegmentUUID`s in Matroska.
+ This allows remuxing a file with Chapter Codec without changing the content of the codec data, just the `Segment` mapping.
+ </documentation>
+ </element>
+ <element name="ChapterTranslateID" path="\Segment\Info\ChapterTranslate\ChapterTranslateID" id="0x69A5" type="binary" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The binary value used to represent this `Segment` in the chapter codec data.
+ The format depends on the `ChapProcessCodecID` used; see (#chapprocesscodecid-element).
+ </documentation>
+ </element>
+ <element name="ChapterTranslateCodec" path="\Segment\Info\ChapterTranslate\ChapterTranslateCodec" id="0x69BF" type="uinteger" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Applies to the chapter codec of the given chapter edition(s); see (#chapprocesscodecid-element).</documentation>
+ <extension type="enum source" source="Chapter Codec ID"/>
+ </element>
+ <element name="ChapterTranslateEditionUID" path="\Segment\Info\ChapterTranslate\ChapterTranslateEditionUID" id="0x69FC" type="uinteger">
+ <documentation lang="en" purpose="definition">Specifies a chapter edition UID to which this `ChapterTranslate` applies.</documentation>
+ <documentation lang="en" purpose="usage notes">When no `ChapterTranslateEditionUID` is specified in the `ChapterTranslate`, the `ChapterTranslate` applies to all chapter editions found in the `Segment` using the given `ChapterTranslateCodec`.</documentation>
+ </element>
+ <element name="TimestampScale" path="\Segment\Info\TimestampScale" id="0x2AD7B1" type="uinteger" range="not 0" default="1000000" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Base unit for Segment Ticks and Track Ticks, in nanoseconds. A `TimestampScale` value of 1000000 means scaled timestamps in the `Segment` are expressed in milliseconds; see (#timestamps) on how to interpret timestamps.</documentation>
+ <extension type="libmatroska" cppname="TimecodeScale"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Duration" path="\Segment\Info\Duration" id="0x4489" type="float" range="&gt; 0x0p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Duration of the `Segment`, expressed in `Segment` Ticks, which are based on `TimestampScale`; see (#timestamp-ticks).</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="DateUTC" path="\Segment\Info\DateUTC" id="0x4461" type="date" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The date and time that the `Segment` was created by the muxing application or library.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Title" path="\Segment\Info\Title" id="0x7BA9" type="utf-8" maxOccurs="1">
+ <documentation lang="en" purpose="definition">General name of the `Segment`.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="MuxingApp" path="\Segment\Info\MuxingApp" id="0x4D80" type="utf-8" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Muxing application or library (example: "libmatroska-0.4.3").</documentation>
+ <documentation lang="en" purpose="usage notes">Include the full name of the application or library followed by the version number.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="WritingApp" path="\Segment\Info\WritingApp" id="0x5741" type="utf-8" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Writing application (example: "mkvmerge-0.3.3").</documentation>
+ <documentation lang="en" purpose="usage notes">Include the full name of the application followed by the version number.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Cluster" path="\Segment\Cluster" id="0x1F43B675" type="master" unknownsizeallowed="1">
+ <documentation lang="en" purpose="definition">The `Top-Level Element` containing the (monolithic) `Block` structure.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Timestamp" path="\Segment\Cluster\Timestamp" id="0xE7" type="uinteger" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Absolute timestamp of the cluster, expressed in Segment Ticks, which are based on `TimestampScale`; see (#timestamp-ticks).</documentation>
+ <documentation lang="en" purpose="usage notes">
+ This element **SHOULD** be the first child element of the `Cluster` it belongs to
+ or the second if that `Cluster` contains a `CRC-32` element ((#crc-32)).
+ </documentation>
+ <extension type="libmatroska" cppname="ClusterTimecode"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="SilentTracks" path="\Segment\Cluster\SilentTracks" id="0x5854" type="master" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The list of tracks that are not used in that part of the stream.
+ It is useful when using overlay tracks for seeking or deciding what track to use.
+ </documentation>
+ <extension type="libmatroska" cppname="ClusterSilentTracks"/>
+ </element>
+ <element name="SilentTrackNumber" path="\Segment\Cluster\SilentTracks\SilentTrackNumber" id="0x58D7" type="uinteger" minver="0" maxver="0">
+ <documentation lang="en" purpose="definition">
+ One of the track numbers that is not used from now on in the stream.
+ It could change later if not specified as silent in a further `Cluster`.
+ </documentation>
+ <extension type="libmatroska" cppname="ClusterSilentTrackNumber"/>
+ </element>
+ <element name="Position" path="\Segment\Cluster\Position" id="0xA7" type="uinteger" maxver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The `Segment Position` of the `Cluster` in the `Segment` (0 in live streams).
+ It might help to resynchronize the offset on damaged streams.
+ </documentation>
+ <extension type="libmatroska" cppname="ClusterPosition"/>
+ </element>
+ <element name="PrevSize" path="\Segment\Cluster\PrevSize" id="0xAB" type="uinteger" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Size of the previous `Cluster`, in octets. Can be useful for backward playing.</documentation>
+ <extension type="libmatroska" cppname="ClusterPrevSize"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="SimpleBlock" path="\Segment\Cluster\SimpleBlock" id="0xA3" type="binary" minver="2">
+ <documentation lang="en" purpose="definition">
+ Similar to `Block` (see (#block-structure)) but without all the extra information.
+ Mostly used to reduce overhead when no extra feature is needed; see (#simpleblock-structure) on `SimpleBlock` Structure.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="BlockGroup" path="\Segment\Cluster\BlockGroup" id="0xA0" type="master">
+ <documentation lang="en" purpose="definition">Basic container of information containing a single `Block` and information specific to that `Block`.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Block" path="\Segment\Cluster\BlockGroup\Block" id="0xA1" type="binary" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ `Block` containing the actual data to be rendered and a timestamp relative to the `Cluster` Timestamp;
+ see (#block-structure) on `Block` Structure.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="BlockVirtual" path="\Segment\Cluster\BlockGroup\BlockVirtual" id="0xA2" type="binary" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ A `Block` with no data. It must be stored in the stream at the place the real `Block` would be in display order.
+ </documentation>
+ </element>
+ <element name="BlockAdditions" path="\Segment\Cluster\BlockGroup\BlockAdditions" id="0x75A1" type="master" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Contains additional binary data to complete the `Block` element; see [@?I-D.ietf-cellar-codec, section 4.1.5] for more information.
+ An EBML parser that has no knowledge of the `Block` structure could still see and use/skip these data.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="BlockMore" path="\Segment\Cluster\BlockGroup\BlockAdditions\BlockMore" id="0xA6" type="master" minOccurs="1">
+ <documentation lang="en" purpose="definition">Contains the `BlockAdditional` and some parameters.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="BlockAdditional" path="\Segment\Cluster\BlockGroup\BlockAdditions\BlockMore\BlockAdditional" id="0xA5" type="binary" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Interpreted by the codec as it wishes (using the `BlockAddID`).</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="BlockAddID" path="\Segment\Cluster\BlockGroup\BlockAdditions\BlockMore\BlockAddID" id="0xEE" type="uinteger" range="not 0" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ An ID that identifies how to interpret the `BlockAdditional` data; see [@?I-D.ietf-cellar-codec, section 4.1.5] for
+ more information. A value of 1 indicates that the `BlockAdditional` data is
+ defined by the codec. Any other value indicates that the `BlockAdditional` data
+ should be handled according to the `BlockAddIDType` that is located in the
+ `TrackEntry`.
+ </documentation>
+ <documentation lang="en" purpose="usage notes">Each `BlockAddID` value **MUST** be unique between all `BlockMore` elements found in a `BlockAdditions` element. To keep `MaxBlockAdditionID` as low as possible, small values **SHOULD** be used.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="BlockDuration" path="\Segment\Cluster\BlockGroup\BlockDuration" id="0x9B" type="uinteger" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The duration of the `Block`, expressed in Track Ticks; see (#timestamp-ticks).
+ The `BlockDuration` element can be useful
+ at the end of a `Track` to define the duration of the last frame (as
+ there is no subsequent `Block` available) or when there is a break in a
+ track like for subtitle tracks.
+ </documentation>
+ <implementation_note note_attribute="minOccurs">`BlockDuration` **MUST** be set (minOccurs=1) if the associated `TrackEntry` stores a `DefaultDuration` value.</implementation_note>
+ <implementation_note note_attribute="default">If a value is not present and no `DefaultDuration` is defined, the value is assumed to be the difference between the timestamp of this `Block` and the timestamp of the next `Block` in "display" order (not coding order).</implementation_note>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="ReferencePriority" path="\Segment\Cluster\BlockGroup\ReferencePriority" id="0xFA" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ This frame is referenced and has the specified cache priority.
+ In the cache, only a frame of the same or higher priority can replace this frame. A value of 0 means the frame is not referenced.
+ </documentation>
+ </element>
+ <element name="ReferenceBlock" path="\Segment\Cluster\BlockGroup\ReferenceBlock" id="0xFB" type="integer">
+ <documentation lang="en" purpose="definition">
+ A timestamp value, relative to the timestamp of the `Block` in this `BlockGroup`, expressed in Track Ticks; see (#timestamp-ticks).
+ This is used to reference other frames necessary to decode this frame.
+ The relative value **SHOULD** correspond to a valid `Block` that this `Block` depends on.
+ Historically, `Matroska Writers` didn't write the actual `Block(s)` that this `Block` depends on, but they did write *some* `Block(s)` in the past.
+
+ The value "0" **MAY** also be used to signify that this `Block` cannot be decoded on its own, but the necessary reference `Block(s)` is unknown. In this case, other `ReferenceBlock` elements **MUST NOT** be found in the same `BlockGroup`. If the `BlockGroup` doesn't have a `ReferenceBlock` element, then the `Block` it contains can be decoded without using any other `Block` data.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="ReferenceVirtual" path="\Segment\Cluster\BlockGroup\ReferenceVirtual" id="0xFD" type="integer" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The `Segment Position` of the data that would otherwise be in position of the virtual block.</documentation>
+ </element>
+ <element name="CodecState" path="\Segment\Cluster\BlockGroup\CodecState" id="0xA4" type="binary" minver="2" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The new codec state to use. Data interpretation is private to the codec.
+ This information **SHOULD** always be referenced by a seek entry.
+ </documentation>
+ </element>
+ <element name="DiscardPadding" path="\Segment\Cluster\BlockGroup\DiscardPadding" id="0x75A2" type="integer" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Duration of the silent data added to the `Block`, expressed in
+ Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks)
+ (padding at the end of the `Block` for positive values and at the
+ beginning of the `Block` for negative values). The duration of
+ `DiscardPadding` is not calculated in the duration of the
+ `TrackEntry` and **SHOULD** be discarded during
+ playback.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Slices" path="\Segment\Cluster\BlockGroup\Slices" id="0x8E" type="master" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Contains slices description.</documentation>
+ </element>
+ <element name="TimeSlice" path="\Segment\Cluster\BlockGroup\Slices\TimeSlice" id="0xE8" type="master" minver="0" maxver="0">
+ <documentation lang="en" purpose="definition">
+ Contains extra time information about the data contained in the `Block`.
+ Being able to interpret this element is not required for playback.
+ </documentation>
+ </element>
+ <element name="LaceNumber" path="\Segment\Cluster\BlockGroup\Slices\TimeSlice\LaceNumber" id="0xCC" type="uinteger" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc.).
+ Being able to interpret this element is not required for playback.
+ </documentation>
+ <extension type="libmatroska" cppname="SliceLaceNumber"/>
+ </element>
+ <element name="FrameNumber" path="\Segment\Cluster\BlockGroup\Slices\TimeSlice\FrameNumber" id="0xCD" type="uinteger" minver="0" maxver="0" default="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The number of the frame to generate from this lace with this delay
+ (allows for the generation of many frames from the same Block/Frame).
+ </documentation>
+ <extension type="libmatroska" cppname="SliceFrameNumber"/>
+ </element>
+ <element name="BlockAdditionID" path="\Segment\Cluster\BlockGroup\Slices\TimeSlice\BlockAdditionID" id="0xCB" type="uinteger" minver="0" maxver="0" default="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The ID of the `BlockAdditional` element (0 is the main `Block`).</documentation>
+ <extension type="libmatroska" cppname="SliceBlockAddID"/>
+ </element>
+ <element name="Delay" path="\Segment\Cluster\BlockGroup\Slices\TimeSlice\Delay" id="0xCE" type="uinteger" minver="0" maxver="0" default="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The delay to apply to the element, expressed in Track Ticks; see (#timestamp-ticks).</documentation>
+ <extension type="libmatroska" cppname="SliceDelay"/>
+ </element>
+ <element name="SliceDuration" path="\Segment\Cluster\BlockGroup\Slices\TimeSlice\SliceDuration" id="0xCF" type="uinteger" minver="0" maxver="0" default="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The duration to apply to the element, expressed in Track Ticks; see (#timestamp-ticks).</documentation>
+ </element>
+ <element name="ReferenceFrame" path="\Segment\Cluster\BlockGroup\ReferenceFrame" id="0xC8" type="master" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Contains information about the last reference frame. See [@?DivXTrickTrack].</documentation>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="ReferenceOffset" path="\Segment\Cluster\BlockGroup\ReferenceFrame\ReferenceOffset" id="0xC9" type="uinteger" minver="0" maxver="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The relative offset, in bytes, from the previous `BlockGroup` element for this Smooth FF/RW video track to the containing `BlockGroup`
+ element. See [@?DivXTrickTrack].
+ </documentation>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="ReferenceTimestamp" path="\Segment\Cluster\BlockGroup\ReferenceFrame\ReferenceTimestamp" id="0xCA" type="uinteger" minver="0" maxver="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The timestamp of the `BlockGroup` pointed to by ReferenceOffset, expressed in Track Ticks; see (#timestamp-ticks). See [@?DivXTrickTrack].</documentation>
+ <extension type="libmatroska" cppname="ReferenceTimeCode"/>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="EncryptedBlock" path="\Segment\Cluster\EncryptedBlock" id="0xAF" type="binary" minver="0" maxver="0">
+ <documentation lang="en" purpose="definition">
+ Similar to `SimpleBlock` (see (#simpleblock-structure)),
+ but the data inside the `Block` are Transformed (encrypted and/or signed).
+ </documentation>
+ </element>
+ <element name="Tracks" path="\Segment\Tracks" id="0x1654AE6B" type="master" maxOccurs="1" recurring="1">
+ <documentation lang="en" purpose="definition">A `Top-Level Element` of information with many tracks described.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="TrackEntry" path="\Segment\Tracks\TrackEntry" id="0xAE" type="master" minOccurs="1">
+ <documentation lang="en" purpose="definition">Describes a track with all elements.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="TrackNumber" path="\Segment\Tracks\TrackEntry\TrackNumber" id="0xD7" type="uinteger" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The track number as used in the `Block` Header.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="TrackUID" path="\Segment\Tracks\TrackEntry\TrackUID" id="0x73C5" type="uinteger" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A UID that identifies the `Track`.</documentation>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="TrackType" path="\Segment\Tracks\TrackEntry\TrackType" id="0x83" type="uinteger" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The `TrackType` defines the type of each frame found in the `Track`.
+ The value **SHOULD** be stored on 1 octet.
+ </documentation>
+ <extension type="enum source" registry="Track Type" policy="Specification Required"/>
+ <restriction>
+ <enum value="1" label="video">
+ <documentation lang="en" purpose="definition">An image.</documentation>
+ </enum>
+ <enum value="2" label="audio">
+ <documentation lang="en" purpose="definition">Audio samples.</documentation>
+ </enum>
+ <enum value="3" label="complex">
+ <documentation lang="en" purpose="definition">A mix of different other `TrackType`. The codec needs to define how the `Matroska Player` should interpret such data.</documentation>
+ </enum>
+ <enum value="16" label="logo">
+ <documentation lang="en" purpose="definition">An image to be rendered over the video track(s).</documentation>
+ </enum>
+ <enum value="17" label="subtitle">
+ <documentation lang="en" purpose="definition">Subtitle or closed caption data to be rendered over the video track(s).</documentation>
+ </enum>
+ <enum value="18" label="buttons">
+ <documentation lang="en" purpose="definition">Interactive button(s) to be rendered over the video track(s).</documentation>
+ </enum>
+ <enum value="32" label="control">
+ <documentation lang="en" purpose="definition">Metadata used to control the player of the `Matroska Player`.</documentation>
+ </enum>
+ <enum value="33" label="metadata">
+ <documentation lang="en" purpose="definition">Timed metadata that can be passed on to the `Matroska Player`.</documentation>
+ </enum>
+ </restriction>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="FlagEnabled" path="\Segment\Tracks\TrackEntry\FlagEnabled" id="0xB9" type="uinteger" minver="2" range="0-1" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set to 1 if the track is usable. It is possible to turn a track that is not usable into a usable track using chapter codecs or control tracks.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="TrackFlagEnabled"/>
+ </element>
+ <element name="FlagDefault" path="\Segment\Tracks\TrackEntry\FlagDefault" id="0x88" type="uinteger" range="0-1" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set to 1 if the track (audio, video, or subtitles) is eligible for automatic selection by the player; see (#default-track-selection) for more details.</documentation>
+ <extension type="libmatroska" cppname="TrackFlagDefault"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="FlagForced" path="\Segment\Tracks\TrackEntry\FlagForced" id="0x55AA" type="uinteger" range="0-1" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Applies only to subtitles. Set to 1 if the track is eligible for automatic selection by the player if it matches the user's language preference,
+ even if the user's preferences would not normally enable subtitles with the selected audio track;
+ this can be used for tracks containing only translations of audio in foreign languages or on-screen text.
+ See (#default-track-selection) for more details.
+ </documentation>
+ <extension type="libmatroska" cppname="TrackFlagForced"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="FlagHearingImpaired" path="\Segment\Tracks\TrackEntry\FlagHearingImpaired" id="0x55AB" type="uinteger" minver="4" range="0-1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set to 1 if and only if the track is suitable for users with hearing impairments.</documentation>
+ </element>
+ <element name="FlagVisualImpaired" path="\Segment\Tracks\TrackEntry\FlagVisualImpaired" id="0x55AC" type="uinteger" minver="4" range="0-1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set to 1 if and only if the track is suitable for users with visual impairments.</documentation>
+ </element>
+ <element name="FlagTextDescriptions" path="\Segment\Tracks\TrackEntry\FlagTextDescriptions" id="0x55AD" type="uinteger" minver="4" range="0-1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set to 1 if and only if the track contains textual descriptions of video content.</documentation>
+ </element>
+ <element name="FlagOriginal" path="\Segment\Tracks\TrackEntry\FlagOriginal" id="0x55AE" type="uinteger" minver="4" range="0-1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set to 1 if and only if the track is in the content's original language.</documentation>
+ </element>
+ <element name="FlagCommentary" path="\Segment\Tracks\TrackEntry\FlagCommentary" id="0x55AF" type="uinteger" minver="4" range="0-1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set to 1 if and only if the track contains commentary.</documentation>
+ </element>
+ <element name="FlagLacing" path="\Segment\Tracks\TrackEntry\FlagLacing" id="0x9C" type="uinteger" range="0-1" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Set to 1 if the track **MAY** contain blocks that use lacing.
+ When set to 0, all blocks **MUST** have their lacing flags set to "no lacing"; see (#block-lacing) on 'Block' Lacing.
+ </documentation>
+ <extension type="libmatroska" cppname="TrackFlagLacing"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="MinCache" path="\Segment\Tracks\TrackEntry\MinCache" id="0x6DE7" type="uinteger" minver="0" maxver="0" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The minimum number of frames a player should be able to cache during playback.
+ If set to 0, the reference pseudo-cache system is not used.
+ </documentation>
+ <extension type="libmatroska" cppname="TrackMinCache"/>
+ </element>
+ <element name="MaxCache" path="\Segment\Tracks\TrackEntry\MaxCache" id="0x6DF8" type="uinteger" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The maximum cache size necessary to store referenced frames in and the current frame.
+ 0 means no cache is needed.
+ </documentation>
+ <extension type="libmatroska" cppname="TrackMaxCache"/>
+ </element>
+ <element name="DefaultDuration" path="\Segment\Tracks\TrackEntry\DefaultDuration" id="0x23E383" type="uinteger" range="not 0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Number of nanoseconds per frame, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks)
+ ("frame" in the Matroska sense -- one element put into a (Simple)Block).
+ </documentation>
+ <extension type="libmatroska" cppname="TrackDefaultDuration"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="DefaultDecodedFieldDuration" path="\Segment\Tracks\TrackEntry\DefaultDecodedFieldDuration" id="0x234E7A" type="uinteger" minver="4" range="not 0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The period between two successive fields at the output of the decoding process, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).
+ See (#defaultdecodedfieldduration) for more information.
+ </documentation>
+ <extension type="libmatroska" cppname="TrackDefaultDecodedFieldDuration"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrackTimestampScale" path="\Segment\Tracks\TrackEntry\TrackTimestampScale" id="0x23314F" type="float" maxver="3" range="&gt; 0x0p+0" default="0x1p+0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The scale to apply on this track to work at normal speed in relation with other tracks
+ (mostly used to adjust video speed when the audio length differs).
+ </documentation>
+ <extension type="libmatroska" cppname="TrackTimecodeScale"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrackOffset" path="\Segment\Tracks\TrackEntry\TrackOffset" id="0x537F" type="integer" minver="0" maxver="0" default="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ A value to add to the `Block`'s Timestamp, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).
+ This can be used to adjust the playback offset of a track.
+ </documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="MaxBlockAdditionID" path="\Segment\Tracks\TrackEntry\MaxBlockAdditionID" id="0x55EE" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The maximum value of `BlockAddID` ((#blockaddid-element)).
+ A value of 0 means there is no `BlockAdditions` ((#blockadditions-element)) for this track.
+ </documentation>
+ </element>
+ <element name="BlockAdditionMapping" path="\Segment\Tracks\TrackEntry\BlockAdditionMapping" id="0x41E4" type="master" minver="4">
+ <documentation lang="en" purpose="definition">
+ Contains elements that extend the track format by adding content either to each frame,
+ with `BlockAddID` ((#blockaddid-element)), or to the track as a whole
+ with `BlockAddIDExtraData`.
+ </documentation>
+ </element>
+ <element name="BlockAddIDValue" path="\Segment\Tracks\TrackEntry\BlockAdditionMapping\BlockAddIDValue" id="0x41F0" type="uinteger" minver="4" range="&gt;=2" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ If the track format extension needs content beside frames,
+ the value refers to the `BlockAddID` ((#blockaddid-element)) value being described.
+ </documentation>
+ <documentation lang="en" purpose="usage notes">To keep `MaxBlockAdditionID` as low as possible, small values **SHOULD** be used.</documentation>
+ </element>
+ <element name="BlockAddIDName" path="\Segment\Tracks\TrackEntry\BlockAdditionMapping\BlockAddIDName" id="0x41A4" type="string" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ A human-friendly name describing the type of `BlockAdditional` data,
+ as defined by the associated `Block Additional Mapping`.
+ </documentation>
+ </element>
+ <element name="BlockAddIDType" path="\Segment\Tracks\TrackEntry\BlockAdditionMapping\BlockAddIDType" id="0x41E7" type="uinteger" minver="4" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Stores the registered identifier of the `Block Additional Mapping`
+ to define how the `BlockAdditional` data should be handled.
+ </documentation>
+ <documentation lang="en" purpose="usage notes">If `BlockAddIDType` is 0, the `BlockAddIDValue` and corresponding `BlockAddID` values **MUST** be 1.</documentation>
+ </element>
+ <element name="BlockAddIDExtraData" path="\Segment\Tracks\TrackEntry\BlockAdditionMapping\BlockAddIDExtraData" id="0x41ED" type="binary" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Extra binary data that the `BlockAddIDType` can use to interpret the `BlockAdditional` data.
+ The interpretation of the binary data depends on the `BlockAddIDType` value and the corresponding `Block Additional Mapping`.
+ </documentation>
+ </element>
+ <element name="Name" path="\Segment\Tracks\TrackEntry\Name" id="0x536E" type="utf-8" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A human-readable track name.</documentation>
+ <extension type="libmatroska" cppname="TrackName"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Language" path="\Segment\Tracks\TrackEntry\Language" id="0x22B59C" type="string" default="eng" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The language of the track,
+ in the Matroska languages form; see (#language-codes) on language codes.
+ This element **MUST** be ignored if the `LanguageBCP47` element is used in the same `TrackEntry`.
+ </documentation>
+ <extension type="libmatroska" cppname="TrackLanguage"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="LanguageBCP47" path="\Segment\Tracks\TrackEntry\LanguageBCP47" id="0x22B59D" type="string" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The language of the track,
+ in the form defined in [@!RFC5646]; see (#language-codes) on language codes.
+ If this element is used, then any `Language` elements used in the same `TrackEntry` **MUST** be ignored.
+ </documentation>
+ <extension type="libmatroska" cppname="LanguageIETF"/>
+ </element>
+ <element name="CodecID" path="\Segment\Tracks\TrackEntry\CodecID" id="0x86" type="string" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ An ID corresponding to the codec;
+ see [@?I-D.ietf-cellar-codec] for more info.
+ </documentation>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CodecPrivate" path="\Segment\Tracks\TrackEntry\CodecPrivate" id="0x63A2" type="binary" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Private data only known to the codec.</documentation>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CodecName" path="\Segment\Tracks\TrackEntry\CodecName" id="0x258688" type="utf-8" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A human-readable string specifying the codec.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="AttachmentLink" path="\Segment\Tracks\TrackEntry\AttachmentLink" id="0x7446" type="uinteger" maxver="3" range="not 0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The UID of an attachment that is used by this codec.</documentation>
+ <documentation lang="en" purpose="usage notes">The value **MUST** match the `FileUID` value of an attachment found in this `Segment`.</documentation>
+ <extension type="libmatroska" cppname="TrackAttachmentLink"/>
+ </element>
+ <element name="CodecSettings" path="\Segment\Tracks\TrackEntry\CodecSettings" id="0x3A9697" type="utf-8" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A string describing the encoding setting used.</documentation>
+ </element>
+ <element name="CodecInfoURL" path="\Segment\Tracks\TrackEntry\CodecInfoURL" id="0x3B4040" type="string" minver="0" maxver="0">
+ <documentation lang="en" purpose="definition">A URL to find information about the codec used.</documentation>
+ </element>
+ <element name="CodecDownloadURL" path="\Segment\Tracks\TrackEntry\CodecDownloadURL" id="0x26B240" type="string" minver="0" maxver="0">
+ <documentation lang="en" purpose="definition">A URL to download information about the codec used.</documentation>
+ </element>
+ <element name="CodecDecodeAll" path="\Segment\Tracks\TrackEntry\CodecDecodeAll" id="0xAA" type="uinteger" maxver="0" range="0-1" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set to 1 if the codec can decode potentially damaged data.</documentation>
+ </element>
+ <element name="TrackOverlay" path="\Segment\Tracks\TrackEntry\TrackOverlay" id="0x6FAB" type="uinteger" maxver="0">
+ <documentation lang="en" purpose="definition">
+ Specify that this track is an overlay track for the `Track` specified (in the u-integer).
+ This means that when this track has a gap on `SilentTracks`, the overlay track should be used instead. The order of multiple `TrackOverlay` matters; the first one is the one that should be used.
+ If the first one is not found, it should be the second, etc.
+ </documentation>
+ </element>
+ <element name="CodecDelay" path="\Segment\Tracks\TrackEntry\CodecDelay" id="0x56AA" type="uinteger" minver="4" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The built-in delay for the codec, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).
+ It represents the number of codec samples that will be discarded by the decoder during playback.
+ This timestamp value **MUST** be subtracted from each frame timestamp in order to get the timestamp that will be actually played.
+ The value **SHOULD** be small so the muxing of tracks with the same actual timestamp are in the same `Cluster`.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="SeekPreRoll" path="\Segment\Tracks\TrackEntry\SeekPreRoll" id="0x56BB" type="uinteger" minver="4" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ After a discontinuity, the duration of the data
+ that the decoder **MUST** decode before the decoded data is valid, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrackTranslate" path="\Segment\Tracks\TrackEntry\TrackTranslate" id="0x6624" type="master">
+ <documentation lang="en" purpose="definition">The mapping between this `TrackEntry` and a track value in the given Chapter Codec.</documentation>
+ <documentation lang="en" purpose="rationale">
+ Chapter Codecs may need to address content in a specific track, but they may not know of the way to identify tracks in Matroska.
+ This element and its child elements add a way to map the internal tracks known to the Chapter Codec to the track IDs in Matroska.
+ This allows remuxing a file with Chapter Codec without changing the content of the codec data, just the track mapping.
+ </documentation>
+ </element>
+ <element name="TrackTranslateTrackID" path="\Segment\Tracks\TrackEntry\TrackTranslate\TrackTranslateTrackID" id="0x66A5" type="binary" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The binary value used to represent this `TrackEntry` in the chapter codec data.
+ The format depends on the `ChapProcessCodecID` used; see (#chapprocesscodecid-element).
+ </documentation>
+ </element>
+ <element name="TrackTranslateCodec" path="\Segment\Tracks\TrackEntry\TrackTranslate\TrackTranslateCodec" id="0x66BF" type="uinteger" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Applies to the chapter codec of the given chapter edition(s); see (#chapprocesscodecid-element).</documentation>
+ <extension type="enum source" source="Chapter Codec ID"/>
+ </element>
+ <element name="TrackTranslateEditionUID" path="\Segment\Tracks\TrackEntry\TrackTranslate\TrackTranslateEditionUID" id="0x66FC" type="uinteger">
+ <documentation lang="en" purpose="definition">Specifies a chapter edition UID to which this `TrackTranslate` applies.</documentation>
+ <documentation lang="en" purpose="usage notes">When no `TrackTranslateEditionUID` is specified in the `TrackTranslate`, the `TrackTranslate` applies to all chapter editions found in the `Segment` using the given `TrackTranslateCodec`.</documentation>
+ </element>
+ <element name="Video" path="\Segment\Tracks\TrackEntry\Video" id="0xE0" type="master" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Video settings.</documentation>
+ <extension type="libmatroska" cppname="TrackVideo"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="FlagInterlaced" path="\Segment\Tracks\TrackEntry\Video\FlagInterlaced" id="0x9A" type="uinteger" minver="2" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Specifies whether the video frames in this track are interlaced.</documentation>
+ <restriction>
+ <enum value="0" label="undetermined">
+ <documentation lang="en" purpose="definition">Unknown status.</documentation>
+ <documentation lang="en" purpose="usage notes">This value **SHOULD** be avoided.</documentation>
+ </enum>
+ <enum value="1" label="interlaced">
+ <documentation lang="en" purpose="definition">Interlaced frames.</documentation>
+ </enum>
+ <enum value="2" label="progressive">
+ <documentation lang="en" purpose="definition">No interlacing.</documentation>
+ </enum>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoFlagInterlaced"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="FieldOrder" path="\Segment\Tracks\TrackEntry\Video\FieldOrder" id="0x9D" type="uinteger" minver="4" default="2" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Specifies the field ordering of video frames in this track.</documentation>
+ <documentation lang="en" purpose="usage notes">If `FlagInterlaced` is not set to 1, this element **MUST** be ignored.</documentation>
+ <restriction>
+ <enum value="0" label="progressive">
+ <documentation lang="en" purpose="definition">Interlaced frames.</documentation>
+ <documentation lang="en" purpose="usage notes">This value **SHOULD** be avoided; setting `FlagInterlaced` to 2 is sufficient.</documentation>
+ </enum>
+ <enum value="1" label="tff">
+ <documentation lang="en" purpose="definition">Top field displayed first. Top field stored first.</documentation>
+ </enum>
+ <enum value="2" label="undetermined">
+ <documentation lang="en" purpose="definition">Unknown field order.</documentation>
+ <documentation lang="en" purpose="usage notes">This value **SHOULD** be avoided.</documentation>
+ </enum>
+ <enum value="6" label="bff">
+ <documentation lang="en" purpose="definition">Bottom field displayed first. Bottom field stored first.</documentation>
+ </enum>
+ <enum value="9" label="tff (interleaved)">
+ <documentation lang="en" purpose="definition">Top field displayed first. Fields are interleaved in storage with the top line of the top field stored first.</documentation>
+ </enum>
+ <enum value="14" label="bff (interleaved)">
+ <documentation lang="en" purpose="definition">Bottom field displayed first. Fields are interleaved in storage with the top line of the top field stored first.</documentation>
+ </enum>
+ </restriction>
+ <extension type="libmatroska" cppname="VideoFieldOrder"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="StereoMode" path="\Segment\Tracks\TrackEntry\Video\StereoMode" id="0x53B8" type="uinteger" minver="3" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Stereo-3D video mode. See (#multi-planar-and-3d-videos) for more details.</documentation>
+ <extension type="enum source" registry="Stereo Mode" policy="Specification Required"/>
+ <restriction>
+ <enum value="0" label="mono"/>
+ <enum value="1" label="side by side (left eye first)"/>
+ <enum value="2" label="top - bottom (right eye is first)"/>
+ <enum value="3" label="top - bottom (left eye is first)"/>
+ <enum value="4" label="checkboard (right eye is first)"/>
+ <enum value="5" label="checkboard (left eye is first)"/>
+ <enum value="6" label="row interleaved (right eye is first)"/>
+ <enum value="7" label="row interleaved (left eye is first)"/>
+ <enum value="8" label="column interleaved (right eye is first)"/>
+ <enum value="9" label="column interleaved (left eye is first)"/>
+ <enum value="10" label="anaglyph (cyan/red)"/>
+ <enum value="11" label="side by side (right eye first)"/>
+ <enum value="12" label="anaglyph (green/magenta)"/>
+ <enum value="13" label="both eyes laced in one Block (left eye is first)"/>
+ <enum value="14" label="both eyes laced in one Block (right eye is first)"/>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoStereoMode"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="AlphaMode" path="\Segment\Tracks\TrackEntry\Video\AlphaMode" id="0x53C0" type="uinteger" minver="3" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Indicates whether the `BlockAdditional` element with `BlockAddID` of "1"
+ contains Alpha data as defined by the Codec Mapping for the `CodecID`.
+ Undefined values (i.e., values other than 0 or 1) **SHOULD NOT** be used, as the
+ behavior of known implementations is different.
+ </documentation>
+ <extension type="enum source" registry="Alpha Mode" policy="First Come First Served"/>
+ <restriction>
+ <enum value="0" label="none">
+ <documentation lang="en" purpose="definition">The `BlockAdditional` element with `BlockAddID` of "1" does not exist or **SHOULD NOT** be considered as containing such data.</documentation>
+ </enum>
+ <enum value="1" label="present">
+ <documentation lang="en" purpose="definition">The `BlockAdditional` element with `BlockAddID` of "1" contains alpha channel data.</documentation>
+ </enum>
+ </restriction>
+
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoAlphaMode"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="OldStereoMode" path="\Segment\Tracks\TrackEntry\Video\OldStereoMode" id="0x53B9" type="uinteger" maxver="2" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Bogus `StereoMode` value used in old versions of [@?libmatroska].</documentation>
+ <documentation lang="en" purpose="usage notes">This element **MUST NOT** be used. It was an incorrect value used in libmatroska up to 0.9.0.</documentation>
+ <restriction>
+ <enum value="0" label="mono"/>
+ <enum value="1" label="right eye"/>
+ <enum value="2" label="left eye"/>
+ <enum value="3" label="both eyes"/>
+ </restriction>
+ </element>
+ <element name="PixelWidth" path="\Segment\Tracks\TrackEntry\Video\PixelWidth" id="0xB0" type="uinteger" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Width of the encoded video frames in pixels.</documentation>
+ <extension type="libmatroska" cppname="VideoPixelWidth"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="PixelHeight" path="\Segment\Tracks\TrackEntry\Video\PixelHeight" id="0xBA" type="uinteger" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Height of the encoded video frames in pixels.</documentation>
+ <extension type="libmatroska" cppname="VideoPixelHeight"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="PixelCropBottom" path="\Segment\Tracks\TrackEntry\Video\PixelCropBottom" id="0x54AA" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The number of video pixels to remove at the bottom of the image.</documentation>
+ <extension type="libmatroska" cppname="VideoPixelCropBottom"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="PixelCropTop" path="\Segment\Tracks\TrackEntry\Video\PixelCropTop" id="0x54BB" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The number of video pixels to remove at the top of the image.</documentation>
+ <extension type="libmatroska" cppname="VideoPixelCropTop"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="PixelCropLeft" path="\Segment\Tracks\TrackEntry\Video\PixelCropLeft" id="0x54CC" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The number of video pixels to remove on the left of the image.</documentation>
+ <extension type="libmatroska" cppname="VideoPixelCropLeft"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="PixelCropRight" path="\Segment\Tracks\TrackEntry\Video\PixelCropRight" id="0x54DD" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The number of video pixels to remove on the right of the image.</documentation>
+ <extension type="libmatroska" cppname="VideoPixelCropRight"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="DisplayWidth" path="\Segment\Tracks\TrackEntry\Video\DisplayWidth" id="0x54B0" type="uinteger" range="not 0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Width of the video frames to display. Applies to the video frame after cropping (PixelCrop* Elements).</documentation>
+ <implementation_note note_attribute="default">If the DisplayUnit of the same `TrackEntry` is 0, then the default value for `DisplayWidth` is equal to `PixelWidth` - `PixelCropLeft` - `PixelCropRight`; else, there is no default value.</implementation_note>
+ <extension type="libmatroska" cppname="VideoDisplayWidth"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="DisplayHeight" path="\Segment\Tracks\TrackEntry\Video\DisplayHeight" id="0x54BA" type="uinteger" range="not 0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Height of the video frames to display. Applies to the video frame after cropping (PixelCrop* Elements).</documentation>
+ <implementation_note note_attribute="default">If the DisplayUnit of the same `TrackEntry` is 0, then the default value for `DisplayHeight` is equal to `PixelHeight` - `PixelCropTop` - `PixelCropBottom`; else, there is no default value.</implementation_note>
+ <extension type="libmatroska" cppname="VideoDisplayHeight"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="DisplayUnit" path="\Segment\Tracks\TrackEntry\Video\DisplayUnit" id="0x54B2" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">How `DisplayWidth` and `DisplayHeight` are interpreted.</documentation>
+ <extension type="enum source" registry="Display Unit" policy="Specification Required"/>
+ <restriction>
+ <enum value="0" label="pixels"/>
+ <enum value="1" label="centimeters"/>
+ <enum value="2" label="inches"/>
+ <enum value="3" label="display aspect ratio"/>
+ <enum value="4" label="unknown"/>
+ </restriction>
+ <extension type="libmatroska" cppname="VideoDisplayUnit"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="AspectRatioType" path="\Segment\Tracks\TrackEntry\Video\AspectRatioType" id="0x54B3" type="uinteger" minver="0" maxver="0" default="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Specifies the possible modifications to the aspect ratio.</documentation>
+ <restriction>
+ <enum value="0" label="free resizing"/>
+ <enum value="1" label="keep aspect ratio"/>
+ <enum value="2" label="fixed"/>
+ </restriction>
+ <extension type="libmatroska" cppname="VideoAspectRatio"/>
+ </element>
+ <element name="UncompressedFourCC" path="\Segment\Tracks\TrackEntry\Video\UncompressedFourCC" id="0x2EB524" type="binary" length="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Specifies the uncompressed pixel format used for the `Track`'s data as a FourCC.
+ This value is similar in scope to the biCompression value of AVI's `BITMAPINFO` [@?AVIFormat]. There is neither a definitive list of FourCC values nor an official registry. Some common values for YUV pixel formats can be found at [@?MSYUV8], [@?MSYUV16], and [@?FourCC-YUV]. Some common values for uncompressed RGB pixel formats can be found at [@?MSRGB] and [@?FourCC-RGB].
+ </documentation>
+ <implementation_note note_attribute="minOccurs">UncompressedFourCC **MUST** be set (minOccurs=1) in `TrackEntry` when the `CodecID` element of the `TrackEntry` is set to "V_UNCOMPRESSED".</implementation_note>
+ <extension type="libmatroska" cppname="VideoColourSpace"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="GammaValue" path="\Segment\Tracks\TrackEntry\Video\GammaValue" id="0x2FB523" type="float" minver="0" maxver="0" range="&gt; 0x0p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Gamma value.</documentation>
+ <extension type="libmatroska" cppname="VideoGamma"/>
+ </element>
+ <element name="FrameRate" path="\Segment\Tracks\TrackEntry\Video\FrameRate" id="0x2383E3" type="float" minver="0" maxver="0" range="&gt; 0x0p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Number of frames per second. This value is informational only. It is intended for constant frame rate streams and should not be
+ used for a variable frame rate `TrackEntry`.
+ </documentation>
+ <extension type="libmatroska" cppname="VideoFrameRate"/>
+ </element>
+ <element name="Colour" path="\Segment\Tracks\TrackEntry\Video\Colour" id="0x55B0" type="master" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Settings describing the color format.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoColour"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="MatrixCoefficients" path="\Segment\Tracks\TrackEntry\Video\Colour\MatrixCoefficients" id="0x55B1" type="uinteger" minver="4" default="2" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The Matrix Coefficients of the video used to derive luma and chroma values from red, green, and blue color primaries.
+ For clarity, the value and meanings for `MatrixCoefficients` are adopted from Table 4 of [@!ITU-H.273].
+ </documentation>
+ <restriction>
+ <enum value="0" label="Identity"/>
+ <enum value="1" label="ITU-R BT.709"/>
+ <enum value="2" label="unspecified"/>
+ <enum value="3" label="reserved"/>
+ <enum value="4" label="US FCC 73.682"/>
+ <enum value="5" label="ITU-R BT.470BG"/>
+ <enum value="6" label="SMPTE 170M"/>
+ <enum value="7" label="SMPTE 240M"/>
+ <enum value="8" label="YCoCg"/>
+ <enum value="9" label="BT2020 Non-constant Luminance"/>
+ <enum value="10" label="BT2020 Constant Luminance"/>
+ <enum value="11" label="SMPTE ST 2085"/>
+ <enum value="12" label="Chroma-derived Non-constant Luminance"/>
+ <enum value="13" label="Chroma-derived Constant Luminance"/>
+ <enum value="14" label="ITU-R BT.2100-0"/>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoColourMatrix"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="BitsPerChannel" path="\Segment\Tracks\TrackEntry\Video\Colour\BitsPerChannel" id="0x55B2" type="uinteger" minver="4" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Number of decoded bits per channel. A value of 0 indicates that the `BitsPerChannel` is unspecified.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoBitsPerChannel"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ChromaSubsamplingHorz" path="\Segment\Tracks\TrackEntry\Video\Colour\ChromaSubsamplingHorz" id="0x55B3" type="uinteger" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The number of pixels to remove in the Cr and Cb channels for every pixel not removed horizontally. Example: For video with 4:2:0 chroma subsampling, the `ChromaSubsamplingHorz`
+ **SHOULD** be set to 1.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoChromaSubsampHorz"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ChromaSubsamplingVert" path="\Segment\Tracks\TrackEntry\Video\Colour\ChromaSubsamplingVert" id="0x55B4" type="uinteger" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The number of pixels to remove in the Cr and Cb channels for every pixel not removed vertically.
+ Example: For video with 4:2:0 chroma subsampling, the `ChromaSubsamplingVert`
+ **SHOULD** be set to 1.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoChromaSubsampVert"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="CbSubsamplingHorz" path="\Segment\Tracks\TrackEntry\Video\Colour\CbSubsamplingHorz" id="0x55B5" type="uinteger" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The number of pixels to remove in the Cb channel for every pixel not removed horizontally.
+ This is additive with `ChromaSubsamplingHorz`.
+ Example: For video with 4:2:1 chroma
+ subsampling, the `ChromaSubsamplingHorz` **SHOULD** be set to 1, and `CbSubsamplingHorz` **SHOULD** be set to 1.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoCbSubsampHorz"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="CbSubsamplingVert" path="\Segment\Tracks\TrackEntry\Video\Colour\CbSubsamplingVert" id="0x55B6" type="uinteger" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The number of pixels to remove in the Cb channel for every pixel not removed vertically.
+ This is additive with `ChromaSubsamplingVert`.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoCbSubsampVert"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ChromaSitingHorz" path="\Segment\Tracks\TrackEntry\Video\Colour\ChromaSitingHorz" id="0x55B7" type="uinteger" minver="4" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">How chroma is subsampled horizontally.</documentation>
+ <extension type="enum source" registry="Horizontal Chroma Siting" policy="First Come First Served"/>
+ <restriction>
+ <enum value="0" label="unspecified"/>
+ <enum value="1" label="left collocated"/>
+ <enum value="2" label="half"/>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoChromaSitHorz"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ChromaSitingVert" path="\Segment\Tracks\TrackEntry\Video\Colour\ChromaSitingVert" id="0x55B8" type="uinteger" minver="4" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">How chroma is subsampled vertically.</documentation>
+ <extension type="enum source" registry="Vertical Chroma Siting" policy="First Come First Served"/>
+ <restriction>
+ <enum value="0" label="unspecified"/>
+ <enum value="1" label="top collocated"/>
+ <enum value="2" label="half"/>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoChromaSitVert"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="Range" path="\Segment\Tracks\TrackEntry\Video\Colour\Range" id="0x55B9" type="uinteger" minver="4" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Clipping of the color ranges.</documentation>
+ <extension type="enum source" registry="Color Range" policy="Specification Required"/>
+ <restriction>
+ <enum value="0" label="unspecified"/>
+ <enum value="1" label="broadcast range"/>
+ <enum value="2" label="full range (no clipping)"/>
+ <enum value="3" label="defined by MatrixCoefficients / TransferCharacteristics"/>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoColourRange"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TransferCharacteristics" path="\Segment\Tracks\TrackEntry\Video\Colour\TransferCharacteristics" id="0x55BA" type="uinteger" minver="4" default="2" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The transfer characteristics of the video. For clarity,
+ the value and meanings for `TransferCharacteristics` are adopted from Table 3 of [@!ITU-H.273].
+ </documentation>
+ <restriction>
+ <enum value="0" label="reserved"/>
+ <enum value="1" label="ITU-R BT.709"/>
+ <enum value="2" label="unspecified"/>
+ <enum value="3" label="reserved2"/>
+ <enum value="4" label="Gamma 2.2 curve - BT.470M"/>
+ <enum value="5" label="Gamma 2.8 curve - BT.470BG"/>
+ <enum value="6" label="SMPTE 170M"/>
+ <enum value="7" label="SMPTE 240M"/>
+ <enum value="8" label="Linear"/>
+ <enum value="9" label="Log"/>
+ <enum value="10" label="Log Sqrt"/>
+ <enum value="11" label="IEC 61966-2-4"/>
+ <enum value="12" label="ITU-R BT.1361 Extended Colour Gamut"/>
+ <enum value="13" label="IEC 61966-2-1"/>
+ <enum value="14" label="ITU-R BT.2020 10 bit"/>
+ <enum value="15" label="ITU-R BT.2020 12 bit"/>
+ <enum value="16" label="ITU-R BT.2100 Perceptual Quantization"/>
+ <enum value="17" label="SMPTE ST 428-1"/>
+ <enum value="18" label="ARIB STD-B67 (HLG)"/>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoColourTransferCharacter"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="Primaries" path="\Segment\Tracks\TrackEntry\Video\Colour\Primaries" id="0x55BB" type="uinteger" minver="4" default="2" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The color primaries of the video. For clarity,
+ the value and meanings for `Primaries` are adopted from Table 2 of [@!ITU-H.273].
+ </documentation>
+ <restriction>
+ <enum value="0" label="reserved"/>
+ <enum value="1" label="ITU-R BT.709"/>
+ <enum value="2" label="unspecified"/>
+ <enum value="3" label="reserved2"/>
+ <enum value="4" label="ITU-R BT.470M"/>
+ <enum value="5" label="ITU-R BT.470BG - BT.601 625"/>
+ <enum value="6" label="ITU-R BT.601 525 - SMPTE 170M"/>
+ <enum value="7" label="SMPTE 240M"/>
+ <enum value="8" label="FILM"/>
+ <enum value="9" label="ITU-R BT.2020"/>
+ <enum value="10" label="SMPTE ST 428-1"/>
+ <enum value="11" label="SMPTE RP 432-2"/>
+ <enum value="12" label="SMPTE EG 432-2"/>
+ <enum value="22" label="EBU Tech. 3213-E - JEDEC P22 phosphors"/>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoColourPrimaries"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="MaxCLL" path="\Segment\Tracks\TrackEntry\Video\Colour\MaxCLL" id="0x55BC" type="uinteger" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Maximum brightness of a single pixel (Maximum Content Light Level)
+ in candelas per square meter (cd/m^2^).
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoColourMaxCLL"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="MaxFALL" path="\Segment\Tracks\TrackEntry\Video\Colour\MaxFALL" id="0x55BD" type="uinteger" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Maximum brightness of a single full frame (Maximum Frame-Average Light Level)
+ in candelas per square meter (cd/m^2^).
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoColourMaxFALL"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="MasteringMetadata" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata" id="0x55D0" type="master" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">SMPTE 2086 mastering data.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoColourMasterMeta"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="PrimaryRChromaticityX" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryRChromaticityX" id="0x55D1" type="float" minver="4" range="0x0p+0-0x1p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Red X chromaticity coordinate, as defined by [@!CIE-1931].</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoRChromaX"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="PrimaryRChromaticityY" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryRChromaticityY" id="0x55D2" type="float" minver="4" range="0x0p+0-0x1p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Red Y chromaticity coordinate, as defined by [@!CIE-1931].</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoRChromaY"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="PrimaryGChromaticityX" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryGChromaticityX" id="0x55D3" type="float" minver="4" range="0x0p+0-0x1p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Green X chromaticity coordinate, as defined by [@!CIE-1931].</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoGChromaX"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="PrimaryGChromaticityY" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryGChromaticityY" id="0x55D4" type="float" minver="4" range="0x0p+0-0x1p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Green Y chromaticity coordinate, as defined by [@!CIE-1931].</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoGChromaY"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="PrimaryBChromaticityX" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryBChromaticityX" id="0x55D5" type="float" minver="4" range="0x0p+0-0x1p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Blue X chromaticity coordinate, as defined by [@!CIE-1931].</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoBChromaX"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="PrimaryBChromaticityY" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryBChromaticityY" id="0x55D6" type="float" minver="4" range="0x0p+0-0x1p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Blue Y chromaticity coordinate, as defined by [@!CIE-1931].</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoBChromaY"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="WhitePointChromaticityX" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\WhitePointChromaticityX" id="0x55D7" type="float" minver="4" range="0x0p+0-0x1p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">White X chromaticity coordinate, as defined by [@!CIE-1931].</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoWhitePointChromaX"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="WhitePointChromaticityY" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\WhitePointChromaticityY" id="0x55D8" type="float" minver="4" range="0x0p+0-0x1p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">White Y chromaticity coordinate, as defined by [@!CIE-1931].</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoWhitePointChromaY"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="LuminanceMax" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\LuminanceMax" id="0x55D9" type="float" minver="4" range="&gt;= 0x0p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Maximum luminance. Represented in candelas per square meter (cd/m^2^).</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoLuminanceMax"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="LuminanceMin" path="\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\LuminanceMin" id="0x55DA" type="float" minver="4" range="&gt;= 0x0p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Minimum luminance. Represented in candelas per square meter (cd/m^2^).</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoLuminanceMin"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="Projection" path="\Segment\Tracks\TrackEntry\Video\Projection" id="0x7670" type="master" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Describes the video projection details. Used to render spherical or VR videos or to flip videos horizontally or vertically.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoProjection"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ProjectionType" path="\Segment\Tracks\TrackEntry\Video\Projection\ProjectionType" id="0x7671" type="uinteger" minver="4" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Describes the projection used for this video track.</documentation>
+ <extension type="enum source" registry="Projection Type" policy="First Come First Served"/>
+ <restriction>
+ <enum value="0" label="rectangular"/>
+ <enum value="1" label="equirectangular"/>
+ <enum value="2" label="cubemap"/>
+ <enum value="3" label="mesh"/>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoProjectionType"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ProjectionPrivate" path="\Segment\Tracks\TrackEntry\Video\Projection\ProjectionPrivate" id="0x7672" type="binary" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Private data that only applies to a specific projection.
+ * If `ProjectionType` equals 0 (rectangular),
+ then this element **MUST NOT** be present.
+ * If `ProjectionType` equals 1 (equirectangular), then this element **MUST** be present and contain the same binary data that would be stored inside
+ an ISOBMFF Equirectangular Projection Box ("equi").
+ * If `ProjectionType` equals 2 (cubemap), then this element **MUST** be present and contain the same binary data that would be stored
+ inside an ISOBMFF Cubemap Projection Box ("cbmp").
+ * If `ProjectionType` equals 3 (mesh), then this element **MUST** be present and contain the same binary data that would be stored inside
+ an ISOBMFF Mesh Projection Box ("mshp").
+ </documentation>
+ <documentation lang="en" purpose="usage notes">
+ ISOBMFF box size and FourCC fields are not included in the binary data,
+ but the FullBox version and flag fields are. This is to avoid
+ redundant framing information while preserving versioning and semantics between the two container formats.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoProjectionPrivate"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ProjectionPoseYaw" path="\Segment\Tracks\TrackEntry\Video\Projection\ProjectionPoseYaw" id="0x7673" type="float" minver="4" range="&gt;= -0xB4p+0, &lt;= 0xB4p+0" default="0x0p+0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Specifies a yaw rotation to the projection. Value represents a clockwise rotation, in degrees, around the up vector. This rotation must be applied
+ before any `ProjectionPosePitch` or `ProjectionPoseRoll` rotations.
+ The value of this element **MUST** be in the -180 to 180 degree range, both inclusive.
+
+ Setting `ProjectionPoseYaw` to 180 or -180 degrees with `ProjectionPoseRoll` and `ProjectionPosePitch` set to 0 degrees flips the image horizontally.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoProjectionPoseYaw"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ProjectionPosePitch" path="\Segment\Tracks\TrackEntry\Video\Projection\ProjectionPosePitch" id="0x7674" type="float" minver="4" range="&gt;= -0x5Ap+0, &lt;= 0x5Ap+0" default="0x0p+0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Specifies a pitch rotation to the projection. Value represents a counter-clockwise rotation, in degrees, around the right vector. This rotation must be applied
+ after the `ProjectionPoseYaw` rotation and before the `ProjectionPoseRoll` rotation.
+ The value of this element **MUST** be in the -90 to 90 degree range, both inclusive.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoProjectionPosePitch"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ProjectionPoseRoll" path="\Segment\Tracks\TrackEntry\Video\Projection\ProjectionPoseRoll" id="0x7675" type="float" minver="4" range="&gt;= -0xB4p+0, &lt;= 0xB4p+0" default="0x0p+0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Specifies a roll rotation to the projection. Value represents a
+ counter-clockwise rotation, in degrees, around the forward vector. This
+ rotation must be applied after the `ProjectionPoseYaw` and
+ `ProjectionPosePitch` rotations. The value of this element
+ **MUST** be in the -180 to 180 degree range, both inclusive. Setting `ProjectionPoseRoll` to 180 or -180 degrees and
+ `ProjectionPoseYaw` to 180 or -180 degrees with
+ `ProjectionPosePitch` set to 0 degrees flips the image vertically.
+ Setting `ProjectionPoseRoll` to 180 or -180 degrees with
+ `ProjectionPoseYaw` and `ProjectionPosePitch` set to 0 degrees
+ flips the image horizontally and vertically.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="VideoProjectionPoseRoll"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="Audio" path="\Segment\Tracks\TrackEntry\Audio" id="0xE1" type="master" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Audio settings.</documentation>
+ <extension type="libmatroska" cppname="TrackAudio"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="SamplingFrequency" path="\Segment\Tracks\TrackEntry\Audio\SamplingFrequency" id="0xB5" type="float" range="&gt; 0x0p+0" default="0x1.f4p+12" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Sampling frequency in Hz.</documentation>
+ <extension type="libmatroska" cppname="AudioSamplingFreq"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="OutputSamplingFrequency" path="\Segment\Tracks\TrackEntry\Audio\OutputSamplingFrequency" id="0x78B5" type="float" range="&gt; 0x0p+0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Real output sampling frequency in Hz that is used for Spectral Band Replication (SBR) techniques.</documentation>
+ <implementation_note note_attribute="default">The default value for `OutputSamplingFrequency` of the same `TrackEntry` is equal to the `SamplingFrequency`.</implementation_note>
+ <extension type="libmatroska" cppname="AudioOutputSamplingFreq"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Channels" path="\Segment\Tracks\TrackEntry\Audio\Channels" id="0x9F" type="uinteger" range="not 0" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Numbers of channels in the track.</documentation>
+ <extension type="libmatroska" cppname="AudioChannels"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="ChannelPositions" path="\Segment\Tracks\TrackEntry\Audio\ChannelPositions" id="0x7D7B" type="binary" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Table of horizontal angles for each successive channel.</documentation>
+ <extension type="libmatroska" cppname="AudioPosition"/>
+ </element>
+ <element name="BitDepth" path="\Segment\Tracks\TrackEntry\Audio\BitDepth" id="0x6264" type="uinteger" range="not 0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Bits per sample, mostly used for PCM.</documentation>
+ <extension type="libmatroska" cppname="AudioBitDepth"/>
+ <extension type="stream copy" keep="1"/>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Emphasis" path="\Segment\Tracks\TrackEntry\Audio\Emphasis" id="0x52F1" type="uinteger" minver="5" default="0" minOccurs="1" maxOccurs="1" >
+ <documentation lang="en" purpose="definition">Audio emphasis applied on audio samples. The player **MUST** apply the inverse emphasis to get the proper audio samples.</documentation>
+ <restriction>
+ <enum value="0" label="No emphasis"/>
+ <enum value="1" label="CD audio">
+ <documentation lang="en" purpose="definition">First order filter with zero point at 50 microseconds and a pole at 15 microseconds. Also found on DVD Audio and MPEG audio.</documentation>
+ </enum>
+ <enum value="2" label="reserved"/>
+ <enum value="3" label="CCIT J.17">
+ <documentation lang="en" purpose="definition">Defined in [@!ITU-J.17].</documentation>
+ </enum>
+ <enum value="4" label="FM 50">
+ <documentation lang="en" purpose="definition">FM Radio in Europe. RC Filter with a time constant of 50 microseconds.</documentation>
+ </enum>
+ <enum value="5" label="FM 75">
+ <documentation lang="en" purpose="definition">FM Radio in the USA. RC Filter with a time constant of 75 microseconds.</documentation>
+ </enum>
+ <enum value="10" label="Phono RIAA">
+ <documentation lang="en" purpose="definition">Phono filter with time constants of t1=3180, t2=318 and t3=75 microseconds. [@!NAB1964]</documentation>
+ </enum>
+ <enum value="11" label="Phono IEC N78">
+ <documentation lang="en" purpose="definition">Phono filter with time constants of t1=3180, t2=450 and t3=50 microseconds.</documentation>
+ </enum>
+ <enum value="12" label="Phono TELDEC">
+ <documentation lang="en" purpose="definition">Phono filter with time constants of t1=3180, t2=318 and t3=50 microseconds.</documentation>
+ </enum>
+ <enum value="13" label="Phono EMI">
+ <documentation lang="en" purpose="definition">Phono filter with time constants of t1=2500, t2=500 and t3=70 microseconds.</documentation>
+ </enum>
+ <enum value="14" label="Phono Columbia LP">
+ <documentation lang="en" purpose="definition">Phono filter with time constants of t1=1590, t2=318 and t3=100 microseconds.</documentation>
+ </enum>
+ <enum value="15" label="Phono LONDON">
+ <documentation lang="en" purpose="definition">Phono filter with time constants of t1=1590, t2=318 and t3=50 microseconds.</documentation>
+ </enum>
+ <enum value="16" label="Phono NARTB">
+ <documentation lang="en" purpose="definition">Phono filter with time constants of t1=3180, t2=318 and t3=100 microseconds.</documentation>
+ </enum>
+ </restriction>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrackOperation" path="\Segment\Tracks\TrackEntry\TrackOperation" id="0xE2" type="master" minver="3" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Operation that needs to be applied on tracks to create this virtual track. For more details, see (#track-operation).</documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrackCombinePlanes" path="\Segment\Tracks\TrackEntry\TrackOperation\TrackCombinePlanes" id="0xE3" type="master" minver="3" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Contains the list of all video plane tracks that need to be combined to create this 3D track.</documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrackPlane" path="\Segment\Tracks\TrackEntry\TrackOperation\TrackCombinePlanes\TrackPlane" id="0xE4" type="master" minver="3" minOccurs="1">
+ <documentation lang="en" purpose="definition">Contains a video plane track that needs to be combined to create this 3D track.</documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrackPlaneUID" path="\Segment\Tracks\TrackEntry\TrackOperation\TrackCombinePlanes\TrackPlane\TrackPlaneUID" id="0xE5" type="uinteger" minver="3" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The `TrackUID` number of the track representing the plane.</documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrackPlaneType" path="\Segment\Tracks\TrackEntry\TrackOperation\TrackCombinePlanes\TrackPlane\TrackPlaneType" id="0xE6" type="uinteger" minver="3" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The kind of plane this track corresponds to.</documentation>
+ <extension type="enum source" registry="Track Plane Type" policy="First Come First Served"/>
+ <restriction>
+ <enum value="0" label="left eye"/>
+ <enum value="1" label="right eye"/>
+ <enum value="2" label="background"/>
+ </restriction>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrackJoinBlocks" path="\Segment\Tracks\TrackEntry\TrackOperation\TrackJoinBlocks" id="0xE9" type="master" minver="3" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Contains the list of all tracks whose `Blocks` need to be combined to create this virtual track.</documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrackJoinUID" path="\Segment\Tracks\TrackEntry\TrackOperation\TrackJoinBlocks\TrackJoinUID" id="0xED" type="uinteger" minver="3" range="not 0" minOccurs="1">
+ <documentation lang="en" purpose="definition">The `TrackUID` number of a track whose blocks are used to create this virtual track.</documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="TrickTrackUID" path="\Segment\Tracks\TrackEntry\TrickTrackUID" id="0xC0" type="uinteger" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The `TrackUID` of the Smooth FF/RW video in the paired EBML structure corresponding to this video track. See [@?DivXTrickTrack].</documentation>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="TrickTrackSegmentUID" path="\Segment\Tracks\TrackEntry\TrickTrackSegmentUID" id="0xC1" type="binary" minver="0" maxver="0" length="16" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The `SegmentUUID` of the `Segment` containing the track identified by TrickTrackUID. See [@?DivXTrickTrack].</documentation>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="TrickTrackFlag" path="\Segment\Tracks\TrackEntry\TrickTrackFlag" id="0xC6" type="uinteger" minver="0" maxver="0" default="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Set to 1 if this video track is a Smooth FF/RW track. If set to 1, `MasterTrackUID` and `MasterTrackSegUID` should be present, and
+ `BlockGroups` for this track must contain ReferenceFrame structures.
+ Otherwise, TrickTrackUID and TrickTrackSegUID must be present if this track has a corresponding Smooth FF/RW track. See [@?DivXTrickTrack].
+ </documentation>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="TrickMasterTrackUID" path="\Segment\Tracks\TrackEntry\TrickMasterTrackUID" id="0xC7" type="uinteger" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The `TrackUID` of the video track in the paired EBML structure that corresponds to this Smooth FF/RW track. See [@?DivXTrickTrack].</documentation>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="TrickMasterTrackSegmentUID" path="\Segment\Tracks\TrackEntry\TrickMasterTrackSegmentUID" id="0xC4" type="binary" minver="0" maxver="0" length="16" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The `SegmentUUID` of the `Segment` containing the track identified by MasterTrackUID. See [@?DivXTrickTrack].</documentation>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="ContentEncodings" path="\Segment\Tracks\TrackEntry\ContentEncodings" id="0x6D80" type="master" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Settings for several content encoding mechanisms like compression or encryption.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentEncoding" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding" id="0x6240" type="master" minOccurs="1">
+ <documentation lang="en" purpose="definition">Settings for one content encoding like compression or encryption.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentEncodingOrder" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncodingOrder" id="0x5031" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Defines the order to apply each `ContentEncoding` of the `ContentEncodings`.
+ The decoder/demuxer **MUST** start with the `ContentEncoding` with the highest `ContentEncodingOrder` and work its way down to the `ContentEncoding` with the lowest `ContentEncodingOrder`.
+ This value **MUST** be unique for each `ContentEncoding` found in the `ContentEncodings` of this `TrackEntry`.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentEncodingScope" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncodingScope" id="0x5032" type="uinteger" range="not 0" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A bit field that describes which elements have been modified in this way. Values (big-endian) can be OR'ed.</documentation>
+ <extension type="enum source" registry="Content Encoding Scope" policy="Specification Required" bitfield="1"/>
+ <restriction>
+ <enum value="0x1" label="Block">
+ <documentation lang="en" purpose="definition">All frame contents, excluding lacing data.</documentation>
+ </enum>
+ <enum value="0x2" label="Private">
+ <documentation lang="en" purpose="definition">The track's `CodecPrivate` data.</documentation>
+ </enum>
+ <enum value="0x4" label="Next">
+ <documentation lang="en" purpose="definition">The next ContentEncoding (next `ContentEncodingOrder`; the data inside `ContentCompression` and/or `ContentEncryption`).</documentation>
+ <documentation lang="en" purpose="usage notes">This value **SHOULD NOT** be used, as it's not supported by players.</documentation>
+ </enum>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentEncodingType" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncodingType" id="0x5033" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A value describing the kind of transformation that is applied.</documentation>
+ <extension type="enum source" registry="Content Encoding Type" policy="Specification Required"/>
+ <restriction>
+ <enum value="0" label="Compression"/>
+ <enum value="1" label="Encryption"/>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentCompression" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentCompression" id="0x5034" type="master" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Settings describing the compression used.
+ This element **MUST** be present if the value of `ContentEncodingType` is 0 and absent otherwise.
+ Each block **MUST** be decompressable, even if no previous block is available in order to not prevent seeking.
+ </documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentCompAlgo" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentCompression\ContentCompAlgo" id="0x4254" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The compression algorithm used.</documentation>
+ <documentation lang="en" purpose="usage notes">Compression method "1" (bzlib) and "2" (lzo1x) lack proper documentation on the format, which limits implementation possibilities. Due to licensing conflicts on commonly available libraries' compression methods, "2" (lzo1x) does not offer widespread interoperability. A `Matroska Writer` **SHOULD NOT** use these compression methods by default. A `Matroska Reader` **MAY** support methods "1" and "2" and **SHOULD** support other methods.</documentation>
+ <extension type="enum source" registry="Compression Algorithm" policy="Specification Required"/>
+ <restriction>
+ <enum value="0" label="zlib">
+ <documentation lang="en" purpose="definition">zlib compression [@!RFC1950].</documentation>
+ </enum>
+ <enum value="1" label="bzlib">
+ <documentation lang="en" purpose="definition">bzip2 compression [@?BZIP2] **SHOULD NOT** be used; see usage notes.</documentation>
+ </enum>
+ <enum value="2" label="lzo1x">
+ <documentation lang="en" purpose="definition">Lempel-Ziv-Oberhumer compression [@?LZO] **SHOULD NOT** be used; see usage notes.</documentation>
+ </enum>
+ <enum value="3" label="Header Stripping">
+ <documentation lang="en" purpose="definition">Octets in `ContentCompSettings` ((#contentcompsettings-element)) have been stripped from each frame.</documentation>
+ </enum>
+ </restriction>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentCompSettings" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentCompression\ContentCompSettings" id="0x4255" type="binary" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Settings that might be needed by the decompressor. For Header Stripping (`ContentCompAlgo`=3),
+ the bytes that were removed from the beginning of each frame of the track.
+ </documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentEncryption" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption" id="0x5035" type="master" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Settings describing the encryption used.
+ This element **MUST** be present if the value of `ContentEncodingType` is 1 (encryption) and **MUST** be ignored otherwise.
+ A `Matroska Player` **MAY** support encryption.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentEncAlgo" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentEncAlgo" id="0x47E1" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The encryption algorithm used.</documentation>
+ <extension type="enum source" registry="Encryption Algorithm" policy="First Come First Served"/>
+ <restriction>
+ <enum value="0" label="Not encrypted">
+ <documentation lang="en" purpose="definition">The data are not encrypted.</documentation>
+ </enum>
+ <enum value="1" label="DES">
+ <documentation lang="en" purpose="definition">Data Encryption Standard (DES) [@?FIPS46-3].</documentation>
+ <documentation lang="en" purpose="usage notes">This value **SHOULD** be avoided.</documentation>
+ </enum>
+ <enum value="2" label="3DES">
+ <documentation lang="en" purpose="definition">Triple Data Encryption Algorithm [@?SP800-67].</documentation>
+ <documentation lang="en" purpose="usage notes">This value **SHOULD** be avoided.</documentation>
+ </enum>
+ <enum value="3" label="Twofish">
+ <documentation lang="en" purpose="definition">Twofish Encryption Algorithm [@?Twofish].</documentation>
+ </enum>
+ <enum value="4" label="Blowfish">
+ <documentation lang="en" purpose="definition">Blowfish Encryption Algorithm [@?Blowfish].</documentation>
+ <documentation lang="en" purpose="usage notes">This value **SHOULD** be avoided.</documentation>
+ </enum>
+ <enum value="5" label="AES">
+ <documentation lang="en" purpose="definition">Advanced Encryption Standard (AES) [@?FIPS197].</documentation>
+ </enum>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentEncKeyID" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentEncKeyID" id="0x47E2" type="binary" maxOccurs="1">
+ <documentation lang="en" purpose="definition">For public key algorithms, the ID of the public key that the data was encrypted with.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentEncAESSettings" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentEncAESSettings" id="0x47E7" type="master" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Settings describing the encryption algorithm used.</documentation>
+ <implementation_note note_attribute="maxOccurs">ContentEncAESSettings **MUST NOT** be set (maxOccurs=0) if ContentEncAlgo is not AES (5).</implementation_note>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="AESSettingsCipherMode" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentEncAESSettings\AESSettingsCipherMode" id="0x47E8" type="uinteger" minver="4" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The AES cipher mode used in the encryption.</documentation>
+ <implementation_note note_attribute="maxOccurs">AESSettingsCipherMode **MUST NOT** be set (maxOccurs=0) if ContentEncAlgo is not AES (5).</implementation_note>
+ <extension type="enum source" registry="AES Cipher Mode" policy="First Come First Served"/>
+ <restriction>
+ <enum value="1" label="AES-CTR">
+ <documentation lang="en" purpose="definition">Counter [@?SP800-38A]</documentation>
+ </enum>
+ <enum value="2" label="AES-CBC">
+ <documentation lang="en" purpose="definition">Cipher Block Chaining [@?SP800-38A]</documentation>
+ </enum>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ContentSignature" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentSignature" id="0x47E3" type="binary" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A cryptographic signature of the contents.</documentation>
+ </element>
+ <element name="ContentSigKeyID" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentSigKeyID" id="0x47E4" type="binary" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">This is the ID of the private key that the data was signed with.</documentation>
+ </element>
+ <element name="ContentSigAlgo" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentSigAlgo" id="0x47E5" type="uinteger" maxver="0" default="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The algorithm used for the signature.</documentation>
+ <restriction>
+ <enum value="0" label="Not signed"/>
+ <enum value="1" label="RSA"/>
+ </restriction>
+ </element>
+ <element name="ContentSigHashAlgo" path="\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentSigHashAlgo" id="0x47E6" type="uinteger" maxver="0" default="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The hash algorithm used for the signature.</documentation>
+ <restriction>
+ <enum value="0" label="Not signed"/>
+ <enum value="1" label="SHA1-160"/>
+ <enum value="2" label="MD5"/>
+ </restriction>
+ </element>
+ <element name="Cues" path="\Segment\Cues" id="0x1C53BB6B" type="master" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ A `Top-Level Element` to speed seeking access. All entries are
+ local to the `Segment`.
+ </documentation>
+ <implementation_note note_attribute="minOccurs">This element **SHOULD** be set when the `Segment` is not transmitted as a live stream; see (#livestreaming).</implementation_note>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CuePoint" path="\Segment\Cues\CuePoint" id="0xBB" type="master" minOccurs="1">
+ <documentation lang="en" purpose="definition">Contains all information relative to a seek point in the `Segment`.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CueTime" path="\Segment\Cues\CuePoint\CueTime" id="0xB3" type="uinteger" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Absolute timestamp of the seek point, expressed in Segment Ticks, which are based on `TimestampScale`; see (#timestamp-ticks).</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CueTrackPositions" path="\Segment\Cues\CuePoint\CueTrackPositions" id="0xB7" type="master" minOccurs="1">
+ <documentation lang="en" purpose="definition">Contains positions for different tracks corresponding to the timestamp.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CueTrack" path="\Segment\Cues\CuePoint\CueTrackPositions\CueTrack" id="0xF7" type="uinteger" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The track for which a position is given.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CueClusterPosition" path="\Segment\Cues\CuePoint\CueTrackPositions\CueClusterPosition" id="0xF1" type="uinteger" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The `Segment Position` ((#segment-position)) of the `Cluster` containing the associated `Block`.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CueRelativePosition" path="\Segment\Cues\CuePoint\CueTrackPositions\CueRelativePosition" id="0xF0" type="uinteger" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The relative position inside the `Cluster` of the referenced `SimpleBlock` or `BlockGroup`
+ with 0 being the first possible position for an element inside that `Cluster`.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CueDuration" path="\Segment\Cues\CuePoint\CueTrackPositions\CueDuration" id="0xB2" type="uinteger" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The duration of the block, expressed in Segment Ticks, which are based on `TimestampScale`; see (#timestamp-ticks).
+ If missing, the track's `DefaultDuration` does not apply and no duration information is available in terms of the cues.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CueBlockNumber" path="\Segment\Cues\CuePoint\CueTrackPositions\CueBlockNumber" id="0x5378" type="uinteger" range="not 0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Number of the `Block` in the specified `Cluster`.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="CueCodecState" path="\Segment\Cues\CuePoint\CueTrackPositions\CueCodecState" id="0xEA" type="uinteger" minver="2" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The `Segment Position` ((#segment-position)) of the
+ Codec State corresponding to this `Cues` element. 0 means that the
+ data is taken from the initial `TrackEntry`.
+ </documentation>
+ </element>
+ <element name="CueReference" path="\Segment\Cues\CuePoint\CueTrackPositions\CueReference" id="0xDB" type="master" minver="2">
+ <documentation lang="en" purpose="definition">The `Clusters` containing the referenced `Blocks`.</documentation>
+ </element>
+ <element name="CueRefTime" path="\Segment\Cues\CuePoint\CueTrackPositions\CueReference\CueRefTime" id="0x96" type="uinteger" minver="2" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Timestamp of the referenced `Block`, expressed in Segment Ticks which is based on `TimestampScale`; see (#timestamp-ticks).</documentation>
+ </element>
+ <element name="CueRefCluster" path="\Segment\Cues\CuePoint\CueTrackPositions\CueReference\CueRefCluster" id="0x97" type="uinteger" minver="0" maxver="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The `Segment Position` of the `Cluster` containing the referenced `Block`.</documentation>
+ </element>
+ <element name="CueRefNumber" path="\Segment\Cues\CuePoint\CueTrackPositions\CueReference\CueRefNumber" id="0x535F" type="uinteger" minver="0" maxver="0" range="not 0" default="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Number of the referenced `Block` of Track X in the specified `Cluster`.</documentation>
+ </element>
+ <element name="CueRefCodecState" path="\Segment\Cues\CuePoint\CueTrackPositions\CueReference\CueRefCodecState" id="0xEB" type="uinteger" minver="0" maxver="0" default="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The `Segment Position` of the Codec State corresponding to this referenced element.
+ 0 means that the data is taken from the initial `TrackEntry`.
+ </documentation>
+ </element>
+ <element name="Attachments" path="\Segment\Attachments" id="0x1941A469" type="master" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Contains attached files.</documentation>
+ </element>
+ <element name="AttachedFile" path="\Segment\Attachments\AttachedFile" id="0x61A7" type="master" minOccurs="1">
+ <documentation lang="en" purpose="definition">An attached file.</documentation>
+ <extension type="libmatroska" cppname="Attached"/>
+ </element>
+ <element name="FileDescription" path="\Segment\Attachments\AttachedFile\FileDescription" id="0x467E" type="utf-8" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A human-friendly name for the attached file.</documentation>
+ </element>
+ <element name="FileName" path="\Segment\Attachments\AttachedFile\FileName" id="0x466E" type="utf-8" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Filename of the attached file.</documentation>
+ </element>
+ <element name="FileMediaType" path="\Segment\Attachments\AttachedFile\FileMediaType" id="0x4660" type="string" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Media type of the file following the format described in [@!RFC6838].</documentation>
+ <extension type="libmatroska" cppname="MimeType"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="FileData" path="\Segment\Attachments\AttachedFile\FileData" id="0x465C" type="binary" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The data of the file.</documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="FileUID" path="\Segment\Attachments\AttachedFile\FileUID" id="0x46AE" type="uinteger" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">UID representing the file, as random as possible.</documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="FileReferral" path="\Segment\Attachments\AttachedFile\FileReferral" id="0x4675" type="binary" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A binary value that a track/codec can refer to when the attachment is needed.</documentation>
+ </element>
+ <element name="FileUsedStartTime" path="\Segment\Attachments\AttachedFile\FileUsedStartTime" id="0x4661" type="uinteger" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The timestamp at which this optimized font attachment comes into context, expressed in Segment Ticks, which are based on
+ `TimestampScale`. See [@?DivXWorldFonts].
+ </documentation>
+ <documentation lang="en" purpose="usage notes">This element is reserved for future use and if written **MUST** be the segment start timestamp.</documentation>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="FileUsedEndTime" path="\Segment\Attachments\AttachedFile\FileUsedEndTime" id="0x4662" type="uinteger" minver="0" maxver="0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The timestamp at which this optimized font attachment goes out of context, expressed in Segment Ticks, which are based on
+ `TimestampScale`. See [@?DivXWorldFonts].
+ </documentation>
+ <documentation lang="en" purpose="usage notes">This element is reserved for future use and if written **MUST** be the segment end timestamp.</documentation>
+ <extension type="divx.com" divx="1"/>
+ </element>
+ <element name="Chapters" path="\Segment\Chapters" id="0x1043A770" type="master" maxOccurs="1" recurring="1">
+ <documentation lang="en" purpose="definition">
+ A system to define basic menus and partition data.
+ For more detailed information, see (#chapters).
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="EditionEntry" path="\Segment\Chapters\EditionEntry" id="0x45B9" type="master" minOccurs="1">
+ <documentation lang="en" purpose="definition">Contains all information about a `Segment` edition.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="EditionUID" path="\Segment\Chapters\EditionEntry\EditionUID" id="0x45BC" type="uinteger" range="not 0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A UID that identifies the edition. It's useful for tagging an edition.</documentation>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="EditionFlagHidden" path="\Segment\Chapters\EditionEntry\EditionFlagHidden" id="0x45BD" type="uinteger" range="0-1" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Set to 1 if an edition is hidden. Hidden editions **SHOULD NOT** be available to the user interface
+ (but still be available to Control Tracks; see (#chapter-flags) on `Chapter` flags).
+ </documentation>
+ <extension type="other document" spec="control-track"/>
+ </element>
+ <element name="EditionFlagDefault" path="\Segment\Chapters\EditionEntry\EditionFlagDefault" id="0x45DB" type="uinteger" range="0-1" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set to 1 if the edition **SHOULD** be used as the default one.</documentation>
+ </element>
+ <element name="EditionFlagOrdered" path="\Segment\Chapters\EditionEntry\EditionFlagOrdered" id="0x45DD" type="uinteger" range="0-1" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Set to 1 if the chapters can be defined multiple times and the order to play them is enforced; see (#editionflagordered).</documentation>
+ </element>
+ <element name="EditionDisplay" path="\Segment\Chapters\EditionEntry\EditionDisplay" id="0x4520" type="master" minver="5">
+ <documentation lang="en" purpose="definition">Contains a possible string to use for the edition display for the given languages.</documentation>
+ </element>
+ <element name="EditionString" path="\Segment\Chapters\EditionEntry\EditionDisplay\EditionString" id="0x4521" type="utf-8" minver="5" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Contains the string to use as the edition name.</documentation>
+ </element>
+ <element name="EditionLanguageIETF" path="\Segment\Chapters\EditionEntry\EditionDisplay\EditionLanguageIETF" id="0x45E4" type="string" minver="5">
+ <documentation lang="en" purpose="definition">
+ One language corresponding to the EditionString,
+ in the form defined in [@!RFC5646]; see (#language-codes) on language codes.
+ </documentation>
+ </element>
+ <element name="ChapterAtom" path="\Segment\Chapters\EditionEntry\+ChapterAtom" id="0xB6" type="master" minOccurs="1" recursive="1">
+ <documentation lang="en" purpose="definition">Contains the atom information to use as the chapter atom (applies to all tracks).</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="ChapterUID" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterUID" id="0x73C4" type="uinteger" range="not 0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A UID that identifies the `Chapter`.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="stream copy" keep="1"/>
+ </element>
+ <element name="ChapterStringUID" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterStringUID" id="0x5654" type="utf-8" minver="3" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ A unique string ID that identifies the `Chapter`.
+ For example, it is used as the storage for cue identifier values [@?WebVTT].
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="ChapterTimeStart" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterTimeStart" id="0x91" type="uinteger" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Timestamp of the start of `Chapter`, expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="ChapterTimeEnd" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterTimeEnd" id="0x92" type="uinteger" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Timestamp of the end of `Chapter` (timestamp excluded), expressed in Matroska Ticks -- i.e., in nanoseconds; see (#timestamp-ticks).
+ The value **MUST** be greater than or equal to the `ChapterTimeStart` of the same `ChapterAtom`.
+ </documentation>
+ <documentation lang="en" purpose="usage notes">
+ With the `ChapterTimeEnd` timestamp value being excluded, it **MUST** take into account the duration of
+ the last frame it includes, especially for the `ChapterAtom` using the last frames of the `Segment`.
+ </documentation>
+ <implementation_note note_attribute="minOccurs">ChapterTimeEnd **MUST** be set (minOccurs=1) if the `Edition` is an ordered edition; see (#editionflagordered). If it's a `Parent Chapter`, see (#nested-chapters).</implementation_note>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="ChapterFlagHidden" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterFlagHidden" id="0x98" type="uinteger" range="0-1" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Set to 1 if a chapter is hidden.
+ Hidden chapters **SHOULD NOT** be available to the user interface
+ (but still be available to Control Tracks; see (#chapterflaghidden) on `Chapter` flags).
+ </documentation>
+ </element>
+ <element name="ChapterFlagEnabled" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterFlagEnabled" id="0x4598" type="uinteger" range="0-1" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Set to 1 if the chapter is enabled. It can be enabled/disabled by a Control Track.
+ When disabled, the movie **SHOULD** skip all the content between the TimeStart and TimeEnd of this chapter; see (#chapter-flags) on `Chapter` flags.
+ </documentation>
+ <extension type="other document" spec="control-track"/>
+ </element>
+ <element name="ChapterSegmentUUID" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterSegmentUUID" id="0x6E67" type="binary" length="16" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The `SegmentUUID` of another `Segment` to play during this chapter.</documentation>
+ <documentation lang="en" purpose="usage notes">The value **MUST NOT** be the `SegmentUUID` value of the `Segment` it belongs to.</documentation>
+ <implementation_note note_attribute="minOccurs">`ChapterSegmentUUID` **MUST** be set (minOccurs=1) if `ChapterSegmentEditionUID` is used; see (#medium-linking) on Medium-Linking `Segments`.</implementation_note>
+ <extension type="libmatroska" cppname="ChapterSegmentUID"/>
+ </element>
+ <element name="ChapterSkipType" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterSkipType" id="0x4588" type="uinteger" minver="5" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Indicates what type of content the `ChapterAtom` contains and might be skipped.
+ It can be used to automatically skip content based on the type.
+ If a `ChapterAtom` is inside a `ChapterAtom` that has a `ChapterSkipType` set, it
+ **MUST NOT** have a `ChapterSkipType` or have a `ChapterSkipType` with the same value as it's parent `ChapterAtom`.
+ If the `ChapterAtom` doesn't contain a `ChapterTimeEnd`, the value of the `ChapterSkipType` is only valid until the next `ChapterAtom` with a `ChapterSkipType` value or the end of the file.
+ </documentation>
+ <extension type="webmproject.org" webm="0"/>
+ <restriction>
+ <enum value="0" label="No Skipping">
+ <documentation lang="en" purpose="definition">Content which should not be skipped.</documentation>
+ </enum>
+ <enum value="1" label="Opening Credits">
+ <documentation lang="en" purpose="definition">Credits usually found at the beginning of the content.</documentation>
+ </enum>
+ <enum value="2" label="End Credits">
+ <documentation lang="en" purpose="definition">Credits usually found at the end of the content.</documentation>
+ </enum>
+ <enum value="3" label="Recap">
+ <documentation lang="en" purpose="definition">Recap of previous episodes of the content, usually found around the beginning.</documentation>
+ </enum>
+ <enum value="4" label="Next Preview">
+ <documentation lang="en" purpose="definition">Preview of the next episode of the content, usually found around the end. It may contain spoilers the user wants to avoid.</documentation>
+ </enum>
+ <enum value="5" label="Preview">
+ <documentation lang="en" purpose="definition">Preview of the current episode of the content, usually found around the beginning. It may contain spoilers the user want to avoid.</documentation>
+ </enum>
+ <enum value="6" label="Advertisement">
+ <documentation lang="en" purpose="definition">Advertisement within the content.</documentation>
+ </enum>
+ <enum value="7" label="Intermission">
+ <documentation lang="en" purpose="definition">A pause of content between main parts of the content.</documentation>
+ </enum>
+ </restriction>
+ </element>
+ <element name="ChapterSegmentEditionUID" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterSegmentEditionUID" id="0x6EBC" type="uinteger" range="not 0" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The `EditionUID` to play from the `Segment` linked in `ChapterSegmentUUID`.
+ If `ChapterSegmentEditionUID` is undeclared, then no `Edition` of the `Linked Segment` is used; see (#medium-linking) on Medium-Linking `Segments`.
+ </documentation>
+ </element>
+ <element name="ChapterPhysicalEquiv" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterPhysicalEquiv" id="0x63C3" type="uinteger" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Specifies the physical equivalent of this `ChapterAtom`, e.g., "DVD" (60) or "SIDE" (50);
+ see (#physical-types) for a complete list of values.
+ </documentation>
+ </element>
+ <element name="ChapterTrack" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterTrack" id="0x8F" type="master" maxOccurs="1">
+ <documentation lang="en" purpose="definition">List of tracks on which the chapter applies. If this element is not present, all tracks apply.</documentation>
+ <extension type="other document" spec="control-track"/>
+ </element>
+ <element name="ChapterTrackUID" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterTrack\ChapterTrackUID" id="0x89" type="uinteger" range="not 0" minOccurs="1">
+ <documentation lang="en" purpose="definition">
+ UID of the `Track` to apply this chapter to.
+ In the absence of a control track, choosing this chapter will select the listed `Tracks` and deselect unlisted tracks.
+ Absence of this element indicates that the `Chapter` **SHOULD** be applied to any currently used `Tracks`.
+ </documentation>
+ <extension type="libmatroska" cppname="ChapterTrackNumber"/>
+ <extension type="other document" spec="control-track"/>
+ </element>
+ <element name="ChapterDisplay" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterDisplay" id="0x80" type="master">
+ <documentation lang="en" purpose="definition">Contains all possible strings to use for the chapter display.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="ChapString" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterDisplay\ChapString" id="0x85" type="utf-8" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Contains the string to use as the chapter atom.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="ChapterString"/>
+ </element>
+ <element name="ChapLanguage" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterDisplay\ChapLanguage" id="0x437C" type="string" default="eng" minOccurs="1">
+ <documentation lang="en" purpose="definition">
+ A language corresponding to the string,
+ in the Matroska languages form; see (#language-codes) on language codes.
+ This element **MUST** be ignored if a `ChapLanguageBCP47` element is used within the same `ChapterDisplay` element.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="ChapterLanguage"/>
+ </element>
+ <element name="ChapLanguageBCP47" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterDisplay\ChapLanguageBCP47" id="0x437D" type="string" minver="4">
+ <documentation lang="en" purpose="definition">
+ A language corresponding to the `ChapString`,
+ in the form defined in [@!RFC5646]; see (#language-codes) on language codes.
+ If a `ChapLanguageBCP47` element is used, then any `ChapLanguage` and `ChapCountry` elements used in the same `ChapterDisplay` **MUST** be ignored.
+ </documentation>
+ <extension type="libmatroska" cppname="ChapLanguageIETF"/>
+ </element>
+ <element name="ChapCountry" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterDisplay\ChapCountry" id="0x437E" type="string">
+ <documentation lang="en" purpose="definition">
+ A country corresponding to the string,
+ in the Matroska countries form; see (#country-codes) on country codes.
+ This element **MUST** be ignored if a `ChapLanguageBCP47` element is used within the same `ChapterDisplay` element.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="ChapterCountry"/>
+ </element>
+ <element name="ChapProcess" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess" id="0x6944" type="master">
+ <documentation lang="en" purpose="definition">Contains all the commands associated with the Atom.</documentation>
+ <extension type="libmatroska" cppname="ChapterProcess"/>
+ </element>
+ <element name="ChapProcessCodecID" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess\ChapProcessCodecID" id="0x6955" type="uinteger" default="0" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Contains the type of the codec used for processing.</documentation>
+ <extension type="enum source" registry="Chapter Codec ID" policy="Specification Required"/>
+ <restriction>
+ <enum value="0" label="Matroska Script">
+ <documentation lang="en" purpose="definition">Chapter commands using the Matroska Script codec.</documentation>
+ </enum>
+ <enum value="1" label="DVD-menu">
+ <documentation lang="en" purpose="definition">Chapter commands using the DVD-like codec.</documentation>
+ </enum>
+ </restriction>
+ <extension type="libmatroska" cppname="ChapterProcessCodecID"/>
+ </element>
+ <element name="ChapProcessPrivate" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess\ChapProcessPrivate" id="0x450D" type="binary" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Optional data attached to the `ChapProcessCodecID` information.
+ For `ChapProcessCodecID` = 1, it is the "DVD level" equivalent; see (#menu-features) on DVD menus.
+ </documentation>
+ <extension type="libmatroska" cppname="ChapterProcessPrivate"/>
+ </element>
+ <element name="ChapProcessCommand" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess\ChapProcessCommand" id="0x6911" type="master">
+ <documentation lang="en" purpose="definition">Contains all the commands associated with the Atom.</documentation>
+ <extension type="libmatroska" cppname="ChapterProcessCommand"/>
+ </element>
+ <element name="ChapProcessTime" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess\ChapProcessCommand\ChapProcessTime" id="0x6922" type="uinteger" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">Defines when the process command **SHOULD** be handled.</documentation>
+ <restriction>
+ <enum value="0" label="during the whole chapter"/>
+ <enum value="1" label="before starting playback"/>
+ <enum value="2" label="after playback of the chapter"/>
+ </restriction>
+ <extension type="libmatroska" cppname="ChapterProcessTime"/>
+ </element>
+ <element name="ChapProcessData" path="\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess\ChapProcessCommand\ChapProcessData" id="0x6933" type="binary" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Contains the command information.
+ The data **SHOULD** be interpreted depending on the `ChapProcessCodecID` value. For `ChapProcessCodecID` = 1,
+ the data correspond to the binary DVD cell pre/post commands; see (#menu-features) on DVD menus.
+ </documentation>
+ <extension type="libmatroska" cppname="ChapterProcessData"/>
+ </element>
+ <element name="Tags" path="\Segment\Tags" id="0x1254C367" type="master">
+ <documentation lang="en" purpose="definition">
+ Element containing metadata describing `Tracks`, `Editions`, `Chapters`, `Attachments`, or the `Segment` as a whole.
+ A list of valid tags can be found in [@?I-D.ietf-cellar-tags].
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Tag" path="\Segment\Tags\Tag" id="0x7373" type="master" minOccurs="1">
+ <documentation lang="en" purpose="definition">A single metadata descriptor.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="Targets" path="\Segment\Tags\Tag\Targets" id="0x63C0" type="master" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Specifies which other elements the metadata represented by the tag value applies to.
+ If empty or omitted, then the tag value describes everything in the `Segment`.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="TagTargets"/>
+ </element>
+ <element name="TargetTypeValue" path="\Segment\Tags\Tag\Targets\TargetTypeValue" id="0x68CA" type="uinteger" range="not 0" default="50" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A number to indicate the logical level of the target.</documentation>
+ <documentation lang="en" purpose="usage notes">
+ The `TargetTypeValue` values are meant to be compared.
+ Higher values **MUST** correspond to a logical level that contains the lower logical level `TargetTypeValue` values.
+ </documentation>
+ <extension type="enum source" registry="Tags Target Type" policy="Specification Required"/>
+ <restriction>
+ <enum value="10" label="SHOT">
+ <documentation lang="en" purpose="definition">The lowest hierarchy found in music or movies.</documentation>
+ </enum>
+ <enum value="20" label="SUBTRACK / MOVEMENT / SCENE">
+ <documentation lang="en" purpose="definition">Corresponds to parts of a track for audio, such as a movement or scene in a movie.</documentation>
+ </enum>
+ <enum value="30" label="TRACK / SONG / CHAPTER">
+ <documentation lang="en" purpose="definition">The common parts of an album or movie.</documentation>
+ </enum>
+ <enum value="40" label="PART / SESSION">
+ <documentation lang="en" purpose="definition">When an album or episode has different logical parts.</documentation>
+ </enum>
+ <enum value="50" label="ALBUM / OPERA / CONCERT / MOVIE / EPISODE">
+ <documentation lang="en" purpose="definition">The most common grouping level of music and video (e.g., an episode for TV series).</documentation>
+ </enum>
+ <enum value="60" label="EDITION / ISSUE / VOLUME / OPUS / SEASON / SEQUEL">
+ <documentation lang="en" purpose="definition">A list of lower levels grouped together.</documentation>
+ </enum>
+ <enum value="70" label="COLLECTION">
+ <documentation lang="en" purpose="definition">The highest hierarchical level that tags can describe.</documentation>
+ </enum>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="TagTargetTypeValue"/>
+ </element>
+ <element name="TargetType" path="\Segment\Tags\Tag\Targets\TargetType" id="0x63CA" type="string" maxOccurs="1">
+ <documentation lang="en" purpose="definition">An informational string that can be used to display the logical level of the target, such as "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc.</documentation>
+ <restriction>
+ <enum value="COLLECTION" label="TargetTypeValue 70"/>
+ <enum value="EDITION" label="TargetTypeValue 60"/>
+ <enum value="ISSUE" label="TargetTypeValue 60"/>
+ <enum value="VOLUME" label="TargetTypeValue 60"/>
+ <enum value="OPUS" label="TargetTypeValue 60"/>
+ <enum value="SEASON" label="TargetTypeValue 60"/>
+ <enum value="SEQUEL" label="TargetTypeValue 60"/>
+ <enum value="ALBUM" label="TargetTypeValue 50"/>
+ <enum value="OPERA" label="TargetTypeValue 50"/>
+ <enum value="CONCERT" label="TargetTypeValue 50"/>
+ <enum value="MOVIE" label="TargetTypeValue 50"/>
+ <enum value="EPISODE" label="TargetTypeValue 50"/>
+ <enum value="PART" label="TargetTypeValue 40"/>
+ <enum value="SESSION" label="TargetTypeValue 40"/>
+ <enum value="TRACK" label="TargetTypeValue 30"/>
+ <enum value="SONG" label="TargetTypeValue 30"/>
+ <enum value="CHAPTER" label="TargetTypeValue 30"/>
+ <enum value="SUBTRACK" label="TargetTypeValue 20"/>
+ <enum value="MOVEMENT" label="TargetTypeValue 20"/>
+ <enum value="SCENE" label="TargetTypeValue 20"/>
+ <enum value="SHOT" label="TargetTypeValue 10"/>
+ </restriction>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="TagTargetType"/>
+ </element>
+ <element name="TagTrackUID" path="\Segment\Tags\Tag\Targets\TagTrackUID" id="0x63C5" type="uinteger" default="0">
+ <documentation lang="en" purpose="definition">A UID that identifies the `Track(s)` that the tags belong to.</documentation>
+ <documentation lang="en" purpose="usage notes">
+ If the value is 0 at this level, the tags apply to all tracks in the `Segment`.
+ If set to any other value, it **MUST** match the `TrackUID` value of a track found in this `Segment`.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="TagEditionUID" path="\Segment\Tags\Tag\Targets\TagEditionUID" id="0x63C9" type="uinteger" default="0">
+ <documentation lang="en" purpose="definition">A UID that identifies the `EditionEntry(s)` that the tags belong to.</documentation>
+ <documentation lang="en" purpose="usage notes">
+ If the value is 0 at this level, the tags apply to all editions in the `Segment`.
+ If set to any other value, it **MUST** match the `EditionUID` value of an edition found in this `Segment`.
+ </documentation>
+ </element>
+ <element name="TagChapterUID" path="\Segment\Tags\Tag\Targets\TagChapterUID" id="0x63C4" type="uinteger" default="0">
+ <documentation lang="en" purpose="definition">A UID that identifies the `Chapter(s)` that the tags belong to.</documentation>
+ <documentation lang="en" purpose="usage notes">
+ If the value is 0 at this level, the tags apply to all chapters in the `Segment`.
+ If set to any other value, it **MUST** match the `ChapterUID` value of a chapter found in this `Segment`.
+ </documentation>
+ </element>
+ <element name="TagAttachmentUID" path="\Segment\Tags\Tag\Targets\TagAttachmentUID" id="0x63C6" type="uinteger" default="0">
+ <documentation lang="en" purpose="definition">A UID that identifies the Attachment(s) that the tags belong to.</documentation>
+ <documentation lang="en" purpose="usage notes">
+ If the value is 0 at this level, the tags apply to all the attachments in
+ the `Segment`. If set to any other value, it **MUST** match
+ the `FileUID` value of an attachment found in this `Segment`.
+ </documentation>
+ </element>
+ <element name="SimpleTag" path="\Segment\Tags\Tag\+SimpleTag" id="0x67C8" type="master" minOccurs="1" recursive="1">
+ <documentation lang="en" purpose="definition">Contains general information about the target.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="TagSimple"/>
+ </element>
+ <element name="TagName" path="\Segment\Tags\Tag\+SimpleTag\TagName" id="0x45A3" type="utf-8" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The name of the tag value that is going to be stored.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="TagLanguage" path="\Segment\Tags\Tag\+SimpleTag\TagLanguage" id="0x447A" type="string" default="und" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ Specifies the language of the specified tag in the Matroska languages form; see (#language-codes) on language codes.
+ This element **MUST** be ignored if the `TagLanguageBCP47` element is used within the same `SimpleTag` element.
+ </documentation>
+ <extension type="webmproject.org" webm="1"/>
+ <extension type="libmatroska" cppname="TagLangue"/>
+ </element>
+ <element name="TagLanguageBCP47" path="\Segment\Tags\Tag\+SimpleTag\TagLanguageBCP47" id="0x447B" type="string" minver="4" maxOccurs="1">
+ <documentation lang="en" purpose="definition">
+ The language used in the `TagString`,
+ in the form defined in [@!RFC5646]; see (#language-codes) on language codes.
+ If this element is used, then any `TagLanguage` elements used in the same `SimpleTag` **MUST** be ignored.
+ </documentation>
+ <extension type="libmatroska" cppname="TagLanguageIETF"/>
+ </element>
+ <element name="TagDefault" path="\Segment\Tags\Tag\+SimpleTag\TagDefault" id="0x4484" type="uinteger" range="0-1" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A boolean value to indicate if this is the default/original language to use for the given tag.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="TagDefaultBogus" path="\Segment\Tags\Tag\+SimpleTag\TagDefaultBogus" id="0x44B4" type="uinteger" minver="0" maxver="0" range="0-1" default="1" minOccurs="1" maxOccurs="1">
+ <documentation lang="en" purpose="definition">A variant of the `TagDefault` element with a bogus element ID; see (#tagdefault-element).</documentation>
+ </element>
+ <element name="TagString" path="\Segment\Tags\Tag\+SimpleTag\TagString" id="0x4487" type="utf-8" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The tag value.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+ <element name="TagBinary" path="\Segment\Tags\Tag\+SimpleTag\TagBinary" id="0x4485" type="binary" maxOccurs="1">
+ <documentation lang="en" purpose="definition">The tag value if it is binary. Note that this cannot be used in the same `SimpleTag` as `TagString`.</documentation>
+ <extension type="webmproject.org" webm="1"/>
+ </element>
+</EBMLSchema>
\ No newline at end of file
diff --git a/MatroskaDemuxer/MatroskaDemuxer.csproj b/MatroskaDemuxer/MatroskaDemuxer.csproj
new file mode 100644
index 0000000..fdd34af
--- /dev/null
+++ b/MatroskaDemuxer/MatroskaDemuxer.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net10.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Matroska\Matroska.csproj" />
+ <ProjectReference Include="..\MediaContainers.Matroska\MediaContainers.Matroska.csproj" />
+ </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/MatroskaDemuxer/Program.cs b/MatroskaDemuxer/Program.cs
new file mode 100644
index 0000000..f2fbb1a
--- /dev/null
+++ b/MatroskaDemuxer/Program.cs
@@ -0,0 +1,25 @@
+using System.IO;
+using System.Threading.Tasks;
+using MediaContainers.Matroska;
+
+namespace MatroskaDemuxer;
+
+class Program
+{
+ private static async Task Main(string[] args)
+ {
+ var doc = await MatroskaReader.Read(new BufferedStream(File.OpenRead(@"d:\temp\a.mp4")));
+
+ await doc.ReadTrackInfo();
+
+ while (true)
+ {
+ var frame = await doc.ReadFrame();
+ if (frame.Buffer == null)
+ {
+ break;
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/MediaContainers.Matroska/EBML/EBMLDocType.cs b/MediaContainers.Matroska/EBML/EBMLDocType.cs
new file mode 100644
index 0000000..9011f26
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLDocType.cs
@@ -0,0 +1,29 @@
+namespace MediaContainers;
+
+public struct EBMLDocType
+{
+ public readonly string DocType;
+ public readonly int WriteVersion;
+ public readonly int ReadVersion;
+
+ public EBMLDocType(string type, int writeVersion, int readVersion)
+ {
+ DocType = type;
+ WriteVersion = writeVersion;
+ ReadVersion = readVersion;
+ }
+
+ public bool CanReadDocument(EBMLHeader header)
+ {
+ if (header.DocType != DocType) { return false; }
+ if (header.DocTypeReadVersion > ReadVersion) { return false; }
+ return true;
+ }
+
+ public bool CanWriteDocument(EBMLHeader header)
+ {
+ if (header.DocType != DocType) { return false; }
+ if (header.DocTypeVersion > WriteVersion) { return false; }
+ return true;
+ }
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLDocTypeLookup.cs b/MediaContainers.Matroska/EBML/EBMLDocTypeLookup.cs
new file mode 100644
index 0000000..bfc91d0
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLDocTypeLookup.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+
+namespace MediaContainers;
+
+public static class EBMLDocTypeLookup
+{
+ private static readonly List<Func<EBMLHeader, EBMLReader, bool>> handlers = new List<Func<EBMLHeader, EBMLReader, bool>>();
+
+ public static void AddEBMLDocType(Func<EBMLHeader, EBMLReader, bool> handler)
+ {
+ lock (handlers) { handlers.Add(handler); }
+ }
+
+ public static bool HandleEBMLDocType(EBMLHeader header, EBMLReader reader)
+ {
+ for (int i = handlers.Count - 1; i >= 0; i--)
+ {
+ if (handlers[i](header, reader)) { return true; }
+ }
+ return false;
+ }
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLDocumentReader.cs b/MediaContainers.Matroska/EBML/EBMLDocumentReader.cs
new file mode 100644
index 0000000..f97b1a9
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLDocumentReader.cs
@@ -0,0 +1,60 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers;
+
+public sealed class EBMLDocumentReader : IDisposable
+{
+ private readonly EBMLReader reader;
+
+ public EBMLHeader Header { get; private set; }
+ public EBMLMasterElement Body { get; private set; }
+ public EBMLReader Reader => reader;
+
+ private EBMLDocumentReader(EBMLReader reader, EBMLHeader header, EBMLMasterElement body)
+ {
+ this.reader = reader;
+ Header = header;
+ Body = body;
+ }
+
+ public bool CanBeReadBy(EBMLDocType type)
+ {
+ return type.CanReadDocument(Header);
+ }
+
+ private static async ValueTask<EBMLDocumentReader> Read(EBMLHeader header, EBMLReader reader, CancellationToken cancellationToken = default)
+ {
+ if (!EBMLDocTypeLookup.HandleEBMLDocType(header, reader))
+ {
+ throw new Exception("Unrecognised EBML doctype: " + header.DocType + " version " + header.DocTypeVersion);
+ }
+ var body = await reader.ReadNextElement(true, cancellationToken) as EBMLMasterElement;
+ if (body == null) { throw new Exception("Expected a master element as document body"); }
+ return new EBMLDocumentReader(reader, header, body);
+ }
+
+ public static async ValueTask<EBMLDocumentReader> Read(IDataQueueReader reader, bool keepReaderOpen = false,
+ DataBufferCache cache = null, CancellationToken cancellationToken = default)
+ {
+ var header = await EBMLHeader.Read(reader, cache, cancellationToken);
+ var ebml = new EBMLReader(reader, keepReaderOpen, cache);
+ return await Read(header, ebml, cancellationToken);
+ }
+
+ public static async ValueTask<EBMLDocumentReader> Read(Stream stream, bool keepStreamOpen = false,
+ DataBufferCache cache = null, CancellationToken cancellationToken = default)
+ {
+ var header = await EBMLHeader.Read(stream, cache, cancellationToken);
+ var ebml = new EBMLReader(stream, keepStreamOpen, cache);
+ return await Read(header, ebml, cancellationToken);
+ }
+
+ public void Dispose()
+ {
+ reader.Dispose();
+ }
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLDocumentWriter.cs b/MediaContainers.Matroska/EBML/EBMLDocumentWriter.cs
new file mode 100644
index 0000000..f61d3f5
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLDocumentWriter.cs
@@ -0,0 +1,52 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers;
+
+public sealed class EBMLDocumentWriter : IDisposable, IAsyncDisposable
+{
+ private readonly EBMLWriter writer;
+
+ public EBMLHeader Header { get; private set; }
+ public EBMLWriter Writer => writer;
+
+ private EBMLDocumentWriter(EBMLWriter writer, EBMLHeader header)
+ {
+ this.writer = writer;
+ Header = header;
+ }
+
+ public bool CanBeWrittenBy(EBMLDocType type)
+ {
+ return type.CanWriteDocument(Header);
+ }
+
+ public static async ValueTask<EBMLDocumentWriter> Write(EBMLHeader header, IDataQueueWriter writer, bool keepWriterOpen = false,
+ DataBufferCache cache = null, CancellationToken cancellationToken = default)
+ {
+ var ebml = new EBMLWriter(writer, keepWriterOpen, cache);
+ await header.Write(ebml, cancellationToken);
+ return new EBMLDocumentWriter(ebml, header);
+ }
+
+ public static async ValueTask<EBMLDocumentWriter> Write(EBMLHeader header, Stream stream, bool keepStreamOpen = false,
+ DataBufferCache cache = null, CancellationToken cancellationToken = default)
+ {
+ var ebml = new EBMLWriter(stream, keepStreamOpen, cache);
+ await header.Write(ebml, cancellationToken);
+ return new EBMLDocumentWriter(ebml, header);
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ return writer.DisposeAsync();
+ }
+
+ public void Dispose()
+ {
+ writer.Dispose();
+ }
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLElement.cs b/MediaContainers.Matroska/EBML/EBMLElement.cs
new file mode 100644
index 0000000..39d1b82
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLElement.cs
@@ -0,0 +1,295 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers;
+
+public abstract class EBMLElement
+{
+ public readonly EBMLElementDefiniton Definition;
+ public readonly EBMLVInt DataSize;
+ public readonly long DataOffset;
+ protected EBMLMasterElement parent;
+
+ public virtual EBMLElementType ElementType => Definition.Type;
+
+ public virtual long IntValue => 0;
+ public virtual ulong UIntValue => 0;
+ public virtual float FloatValue => 0;
+ public virtual double DoubleValue => 0;
+ public virtual string StringValue => "<UNKNOWN>";
+ public virtual DateTime? DateValue => null;
+
+ public EBMLElement Parent => parent;
+ public virtual EBMLElement[] Children => Array.Empty<EBMLElement>();
+ public virtual bool IsFullyRead => true;
+
+ protected EBMLElement(EBMLElementDefiniton def, EBMLVInt dataSize, long offset)
+ {
+ Definition = def;
+ DataSize = dataSize;
+ DataOffset = offset;
+ }
+
+ internal void SetParent(EBMLMasterElement parent) { this.parent = parent; }
+
+ public override string ToString()
+ {
+ return Definition.FullPath + ":" + StringValue +
+ (DataOffset < 0 ? " (?" : (" (0x" + Convert.ToString(DataOffset, 16))) + ":" +
+ (DataSize.IsUnknownValue ? "?" : ("0x" + Convert.ToString((long)DataSize.Value, 16))) + ")";
+ }
+}
+
+public sealed class EBMLSignedIntegerElement : EBMLElement
+{
+ public readonly long Value;
+
+ public override long IntValue => Value;
+ public override ulong UIntValue => (ulong)Value;
+ public override float FloatValue => Value;
+ public override double DoubleValue => Value;
+ public override string StringValue => Value.ToString();
+
+ public EBMLSignedIntegerElement(EBMLElementDefiniton def, EBMLVInt dataSize, long offset, long value)
+ : base(def, dataSize, offset)
+ {
+ if (def.Type != EBMLElementType.SignedInteger) { throw new ArgumentException("Type mismatch", nameof(def)); }
+ if (dataSize.Value > 8) { throw new ArgumentOutOfRangeException(nameof(dataSize)); }
+ Value = value;
+ }
+
+ public EBMLSignedIntegerElement(EBMLElementDefiniton def, long value)
+ : this(def, new EBMLVInt((ulong)EBMLWriter.CalculateWidth(value)), -1, value) { }
+}
+
+public sealed class EBMLUnsignedIntegerElement : EBMLElement
+{
+ public readonly ulong Value;
+
+ public override long IntValue => (long)Value;
+ public override ulong UIntValue => Value;
+ public override float FloatValue => Value;
+ public override double DoubleValue => Value;
+ public override string StringValue => Value.ToString();
+
+ public EBMLUnsignedIntegerElement(EBMLElementDefiniton def, EBMLVInt dataSize, long offset, ulong value)
+ : base(def, dataSize, offset)
+ {
+ if (def.Type != EBMLElementType.UnsignedInteger) { throw new ArgumentException("Type mismatch", nameof(def)); }
+ if (dataSize.Value > 8) { throw new ArgumentOutOfRangeException(nameof(dataSize)); }
+ Value = value;
+ }
+
+ public EBMLUnsignedIntegerElement(EBMLElementDefiniton def, ulong value)
+ : this(def, new EBMLVInt((ulong)EBMLWriter.CalculateWidth(value)), -1, value) { }
+}
+
+public sealed class EBMLFloatElement : EBMLElement
+{
+ public readonly double Value64;
+ public readonly float Value32;
+
+ public override long IntValue => (long)Value64;
+ public override ulong UIntValue => (ulong)Value64;
+ public override float FloatValue => Value32;
+ public override double DoubleValue => Value64;
+ public override string StringValue => Value64.ToString();
+
+ public EBMLFloatElement(EBMLElementDefiniton def, EBMLVInt dataSize, long offset, double value)
+ : base(def, dataSize, offset)
+ {
+ if (def.Type != EBMLElementType.Float) { throw new ArgumentException("Type mismatch", nameof(def)); }
+ if (dataSize.Value != 0 && dataSize.Value != 8) { throw new ArgumentOutOfRangeException(nameof(dataSize)); }
+ Value64 = value; Value32 = (float)value;
+ }
+
+ public EBMLFloatElement(EBMLElementDefiniton def, EBMLVInt dataSize, long offset, float value)
+ : base(def, dataSize, offset)
+ {
+ if (dataSize.Value != 0 && dataSize.Value != 4) { throw new ArgumentOutOfRangeException(nameof(dataSize)); }
+ Value64 = value; Value32 = value;
+ }
+
+ public EBMLFloatElement(EBMLElementDefiniton def, double value)
+ : this(def, new EBMLVInt(8), -1, value) { }
+
+ public EBMLFloatElement(EBMLElementDefiniton def, float value)
+ : this(def, new EBMLVInt(4), -1, value) { }
+}
+
+public sealed class EBMLStringElement : EBMLElement
+{
+ public readonly string Value;
+
+ public override EBMLElementType ElementType => EBMLElementType.String;
+ public override long IntValue { get { _ = long.TryParse(Value, out var i); return i; } }
+ public override ulong UIntValue { get { _ = ulong.TryParse(Value, out var i); return i; } }
+ public override float FloatValue { get { _ = float.TryParse(Value, out var i); return i; } }
+ public override double DoubleValue { get { _ = double.TryParse(Value, out var i); return i; } }
+ public override string StringValue => Value;
+ public override DateTime? DateValue { get { if (DateTime.TryParse(Value, out var i)) { return i; } return null; } }
+
+ public EBMLStringElement(EBMLElementDefiniton def, EBMLVInt dataSize, long offset, string value)
+ : base(def, dataSize, offset)
+ {
+ if (def.Type != EBMLElementType.String && def.Type != EBMLElementType.UTF8) { throw new ArgumentException("Type mismatch", nameof(def)); }
+ Value = value;
+ }
+
+ public EBMLStringElement(EBMLElementDefiniton def, string str)
+ : this(def, new EBMLVInt((ulong)str.Length), -1, str) { }
+}
+
+public sealed class EBMLDateElement : EBMLElement
+{
+ public static readonly DateTime Epoch = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ public readonly DateTime Value;
+
+ public override string StringValue => Value.ToString();
+ public override DateTime? DateValue => Value;
+
+ public EBMLDateElement(EBMLElementDefiniton def, EBMLVInt dataSize, long offset, DateTime value)
+ : base(def, dataSize, offset)
+ {
+ if (def.Type != EBMLElementType.Date) { throw new ArgumentException("Type mismatch", nameof(def)); }
+ if (dataSize.Value != 8 && (dataSize.Value != 0 || value != Epoch)) { throw new ArgumentOutOfRangeException(nameof(dataSize)); }
+ Value = value;
+ }
+
+ public EBMLDateElement(EBMLElementDefiniton def, DateTime value)
+ : this(def, new EBMLVInt(8), -1, value) { }
+}
+
+public sealed class EBMLBinaryElement : EBMLElement
+{
+ public readonly ReadOnlyMemory<byte> Value;
+
+ private readonly IDataQueueReader reader;
+
+ public IDataQueueReader Reader => reader;
+ public override string StringValue => "<BINARY>";
+ public override bool IsFullyRead => reader?.IsReadClosed ?? true;
+
+ public EBMLBinaryElement(EBMLElementDefiniton def, EBMLVInt dataSize, long offset, ReadOnlyMemory<byte> value)
+ : base(def, dataSize, offset)
+ {
+ if (def.Type != EBMLElementType.Binary && def.Type != EBMLElementType.Unknown) { throw new ArgumentException("Type mismatch", nameof(def)); }
+ if ((int)dataSize.Value > value.Length) { throw new ArgumentOutOfRangeException(nameof(dataSize)); }
+ if ((int)dataSize.Value < value.Length) { value = value.Slice(0, (int)dataSize.Value); }
+ Value = value;
+ }
+
+ public EBMLBinaryElement(EBMLElementDefiniton def, EBMLVInt dataSize, long offset, IDataQueueReader reader, bool encapsulated = false)
+ : base(def, dataSize, offset)
+ {
+ if (def.Type != EBMLElementType.Binary) { throw new ArgumentException("Type mismatch", nameof(def)); }
+ if (encapsulated) { this.reader = reader; }
+ else { this.reader = new DataQueueLimitedReader(reader, (long)dataSize.Value, true); }
+ }
+
+ public EBMLBinaryElement(EBMLElementDefiniton def, ReadOnlyMemory<byte> value)
+ : this(def, new EBMLVInt((ulong)value.Length), -1, value) { }
+}
+
+public sealed class EBMLVoidElement : EBMLElement
+{
+ private readonly IDataQueueReader reader;
+
+ public IDataQueueReader Reader => reader;
+ public override string StringValue => "<VOID>";
+ public override bool IsFullyRead => reader?.IsReadClosed ?? true;
+
+ public EBMLVoidElement(EBMLVInt dataSize, long offset)
+ : base(EBMLElementDefiniton.Void, dataSize, offset) { }
+
+ public EBMLVoidElement(EBMLVInt dataSize, long offset, IDataQueueReader reader, bool encapsulated = false)
+ : base(EBMLElementDefiniton.Void, dataSize, offset)
+ {
+ if (encapsulated) { this.reader = reader; }
+ else { this.reader = new DataQueueLimitedReader(reader, (long)dataSize.Value, true); }
+ }
+
+ public EBMLVoidElement(int size) : this(new EBMLVInt((ulong)size), -1) { }
+}
+
+public sealed class EBMLMasterElement : EBMLElement
+{
+ private bool isFullyRead;
+
+ public override string StringValue => "<MASTER>";
+ public override bool IsFullyRead => isFullyRead;
+
+ private readonly List<EBMLElement> children = new();
+ private EBMLElement[] childArray;
+
+ public override EBMLElement[] Children => childArray ??= children.ToArray();
+
+ public EBMLMasterElement(EBMLElementDefiniton def)
+ : base(def, EBMLVInt.CreateUnknown(1), -1) { }
+
+ public EBMLMasterElement(EBMLElementDefiniton def, EBMLVInt dataSize, long offset)
+ : base(def, dataSize, offset)
+ {
+ if (def.Type != EBMLElementType.Master) { throw new ArgumentException("Type mismatch", nameof(def)); }
+ }
+
+ public int EstimateSize()
+ {
+ int size = 0;
+ foreach (var child in Children)
+ {
+ if (child is EBMLMasterElement m) { size += m.EstimateSize(); }
+ else if (!child.DataSize.IsUnknownValue) { size += (int)child.DataSize.Value; }
+ size += child.Definition.Id.WidthBytes + child.DataSize.WidthBytes;
+ }
+ return size;
+ }
+
+ public void AddChild(EBMLElement element)
+ {
+ childArray = null;
+ element.SetParent(this);
+ children.Add(element);
+ }
+
+ public bool IsParentOf(EBMLElement element)
+ {
+ do
+ {
+ if (element == null) { return false; }
+ element = element.Parent;
+ if (element == this) { return true; }
+ }
+ while (true);
+ }
+
+ internal void MarkAsFullyRead()
+ {
+ isFullyRead = true;
+ }
+
+ public string ToFullString(string prefix = null, string indent = " ")
+ {
+ if (prefix == null) { prefix = string.Empty; }
+ var text = new StringBuilder();
+ text.Append(prefix);
+ text.AppendLine(ToString());
+ prefix += indent;
+ foreach (var child in Children)
+ {
+ if (child is EBMLMasterElement m)
+ {
+ text.Append(m.ToFullString(prefix, indent));
+ }
+ else
+ {
+ text.Append(prefix);
+ text.AppendLine(child.ToString());
+ }
+ }
+ return text.ToString();
+ }
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLElementDefiniton.cs b/MediaContainers.Matroska/EBML/EBMLElementDefiniton.cs
new file mode 100644
index 0000000..17bebd4
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLElementDefiniton.cs
@@ -0,0 +1,76 @@
+using System.Linq;
+
+namespace MediaContainers;
+
+public class EBMLElementDefiniton
+{
+ public static readonly EBMLElementDefiniton EBML = new EBMLElementDefiniton(0x1A45DFA3, EBMLElementType.Master, @"\EBML");
+ public static readonly EBMLElementDefiniton EBMLVersion = new EBMLElementDefiniton(0x4286, EBMLElementType.UnsignedInteger, @"\EBML\EBMLVersion", defaultVal: "1");
+ public static readonly EBMLElementDefiniton EBMLReadVersion = new EBMLElementDefiniton(0x42F7, EBMLElementType.UnsignedInteger, @"\EBML\EBMLReadVersion");
+ public static readonly EBMLElementDefiniton EBMLMaxIDLength = new EBMLElementDefiniton(0x42F2, EBMLElementType.UnsignedInteger, @"\EBML\EBMLMaxIDLength", defaultVal: "4");
+ public static readonly EBMLElementDefiniton EBMLMaxSizeLength = new EBMLElementDefiniton(0x42F3, EBMLElementType.UnsignedInteger, @"\EBML\EBMLMaxSizeLength", defaultVal: "8");
+ public static readonly EBMLElementDefiniton DocType = new EBMLElementDefiniton(0x4282, EBMLElementType.String, @"\EBML\DocType");
+ public static readonly EBMLElementDefiniton DocTypeVersion = new EBMLElementDefiniton(0x4287, EBMLElementType.UnsignedInteger, @"\EBML\DocTypeVersion");
+ public static readonly EBMLElementDefiniton DocTypeReadVersion = new EBMLElementDefiniton(0x4285, EBMLElementType.UnsignedInteger, @"\EBML\DocTypeReadVersion");
+ public static readonly EBMLElementDefiniton DocTypeExtension = new EBMLElementDefiniton(0x4281, EBMLElementType.Master, @"\EBML\DocTypeExtension");
+ public static readonly EBMLElementDefiniton DocTypeExtensionName = new EBMLElementDefiniton(0x4283, EBMLElementType.String, @"\EBML\DocTypeExtension\DocTypeExtensionName");
+ public static readonly EBMLElementDefiniton DocTypeExtensionVersion = new EBMLElementDefiniton(0x4284, EBMLElementType.UnsignedInteger, @"\EBML\DocTypeExtension\DocTypeExtensionVersion");
+ public static readonly EBMLElementDefiniton Crc32 = new EBMLElementDefiniton(0xBF, EBMLElementType.Binary, "CRC-32");
+ public static readonly EBMLElementDefiniton Void = new EBMLElementDefiniton(0xEC, EBMLElementType.Binary, "Void");
+ public static readonly EBMLElementDefiniton Unknown = new EBMLElementDefiniton(0, EBMLElementType.Binary, "Unknown");
+
+ private static readonly EBMLElementDefiniton[] headerElements = new EBMLElementDefiniton[]
+ {
+ EBML, EBMLVersion, EBMLReadVersion, EBMLMaxIDLength, EBMLMaxSizeLength,
+ DocType, DocTypeVersion, DocTypeReadVersion, DocTypeExtension, DocTypeExtensionName, DocTypeExtensionVersion,
+ };
+
+ private static readonly EBMLElementDefiniton[] globalElements = new EBMLElementDefiniton[]
+ {
+ Crc32, Void
+ };
+
+ public readonly EBMLVInt Id;
+ public readonly EBMLElementType Type;
+ public readonly string Name;
+ public readonly string Path;
+ public readonly string FullPath;
+ public readonly string DefaultValue;
+ public readonly bool AllowUnknownSize;
+ public readonly bool IsGlobal;
+
+ public EBMLElementDefiniton(ulong id, EBMLElementType type, string fullPath, bool allowUnknownSize = false, string defaultVal = null)
+ {
+ Id = EBMLVInt.CreateWithMarker(id);
+ Type = type;
+ FullPath = fullPath;
+ var components = fullPath.Split('\\', System.StringSplitOptions.RemoveEmptyEntries);
+ Name = components[^1];
+ if (Name.StartsWith('+')) { Name = Name.Substring(1); }
+ if (!fullPath.StartsWith('\\') && components.Length == 1) { IsGlobal = true; Path = "\\"; }
+ else { Path = "\\" + string.Join("\\", components.Take(components.Length - 1)); }
+ AllowUnknownSize = allowUnknownSize;
+ DefaultValue = defaultVal;
+ }
+
+ public static void AddHeaderElements(EBMLReader reader)
+ {
+ for (int i = 0; i < headerElements.Length; i++)
+ {
+ reader.AddElementDefinition(headerElements[i]);
+ }
+ }
+
+ public static void AddGlobalElements(EBMLReader reader)
+ {
+ for (int i = 0; i < globalElements.Length; i++)
+ {
+ reader.AddElementDefinition(globalElements[i]);
+ }
+ }
+
+ public bool IsDirectChildOf(EBMLElementDefiniton parent)
+ {
+ return IsGlobal || Path == parent.FullPath;
+ }
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLElementStruct.cs b/MediaContainers.Matroska/EBML/EBMLElementStruct.cs
new file mode 100644
index 0000000..087175e
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLElementStruct.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Runtime.InteropServices;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers;
+
+public struct EBMLElementStruct
+{
+ public readonly EBMLElementDefiniton Definition;
+ public readonly EBMLVInt DataSize;
+ public readonly long DataOffset;
+ public EBMLElementStructValue Value;
+ public string String;
+ public byte[] Binary;
+ public IDataQueueReader Reader;
+ public bool UseFloat32;
+
+ public EBMLElementStruct(EBMLElementDefiniton definition, EBMLVInt dataSize, long dataOffset)
+ {
+ Definition = definition;
+ DataSize = dataSize;
+ DataOffset = dataOffset;
+ Value = default;
+ String = null;
+ Binary = null;
+ Reader = null;
+ UseFloat32 = false;
+ }
+
+ public EBMLElement ToElement()
+ {
+ switch (Definition.Type)
+ {
+ case EBMLElementType.SignedInteger:
+ return new EBMLSignedIntegerElement(Definition, DataSize, DataOffset, Value.SignedInteger);
+ case EBMLElementType.UnsignedInteger:
+ return new EBMLUnsignedIntegerElement(Definition, DataSize, DataOffset, Value.UnsignedInteger);
+ case EBMLElementType.Float:
+ if (UseFloat32) { return new EBMLFloatElement(Definition, DataSize, DataOffset, Value.Float32); }
+ return new EBMLFloatElement(Definition, DataSize, DataOffset, Value.Float64);
+ case EBMLElementType.Date:
+ return new EBMLDateElement(Definition, DataSize, DataOffset, Value.Date);
+ case EBMLElementType.String:
+ case EBMLElementType.UTF8:
+ return new EBMLStringElement(Definition, DataSize, DataOffset, String);
+ case EBMLElementType.Master:
+ return new EBMLMasterElement(Definition, DataSize, DataOffset);
+ case EBMLElementType.Binary:
+ default:
+ if (Definition == EBMLElementDefiniton.Void)
+ {
+ if (Reader != null) { return new EBMLVoidElement(DataSize, DataOffset, Reader, true); }
+ return new EBMLVoidElement(DataSize, DataOffset);
+ }
+ if (Reader != null) { return new EBMLBinaryElement(Definition, DataSize, DataOffset, Reader, true); }
+ return new EBMLBinaryElement(Definition, DataSize, DataOffset, Binary);
+ }
+ }
+}
+
+[StructLayout(LayoutKind.Explicit)]
+public struct EBMLElementStructValue
+{
+ [FieldOffset(0)] public long SignedInteger;
+ [FieldOffset(0)] public ulong UnsignedInteger;
+ [FieldOffset(0)] public double Float64;
+ [FieldOffset(0)] public float Float32;
+ [FieldOffset(0)] public DateTime Date;
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLElementType.cs b/MediaContainers.Matroska/EBML/EBMLElementType.cs
new file mode 100644
index 0000000..df84d36
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLElementType.cs
@@ -0,0 +1,14 @@
+namespace MediaContainers;
+
+public enum EBMLElementType
+{
+ Unknown = 0,
+ SignedInteger,
+ UnsignedInteger,
+ Float,
+ String,
+ UTF8,
+ Date,
+ Master,
+ Binary,
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLHeader.cs b/MediaContainers.Matroska/EBML/EBMLHeader.cs
new file mode 100644
index 0000000..4f21c52
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLHeader.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers;
+
+public class EBMLHeader
+{
+ public EBMLMasterElement OriginalElement { get; private set; }
+ public int EBMLVersion { get; set; } = 1;
+ public int EBMLReadVersion { get; set; } = 1;
+ public int EBMLMaxIDLength { get; set; } = 4;
+ public int EBMLMaxSizeLength { get; set; } = 8;
+ public string DocType { get; set; }
+ public int DocTypeVersion { get; set; } = 1;
+ public int DocTypeReadVersion { get; set; } = 1;
+ public List<KeyValuePair<string, ulong>> DocTypeExtensions { get; set; }
+
+ public EBMLHeader() { }
+
+ public EBMLHeader(EBMLMasterElement element)
+ {
+ if (element == null) { throw new ArgumentNullException(nameof(element)); }
+ if (element.Definition != EBMLElementDefiniton.EBML) { throw new ArgumentException("Element must be an EBML header section", nameof(element)); }
+ if (!element.IsFullyRead) { throw new ArgumentException("Element must be fully read", nameof(element)); }
+ OriginalElement = element;
+ foreach (var item in element.Children)
+ {
+ if (item.Definition == EBMLElementDefiniton.EBMLVersion) { EBMLVersion = (int)item.IntValue; }
+ else if (item.Definition == EBMLElementDefiniton.EBMLReadVersion) { EBMLReadVersion = (int)item.IntValue; }
+ else if (item.Definition == EBMLElementDefiniton.EBMLMaxIDLength) { EBMLMaxIDLength = (int)item.IntValue; }
+ else if (item.Definition == EBMLElementDefiniton.EBMLMaxSizeLength) { EBMLMaxSizeLength = (int)item.IntValue; }
+ else if (item.Definition == EBMLElementDefiniton.DocType) { DocType = item.StringValue; }
+ else if (item.Definition == EBMLElementDefiniton.DocTypeVersion) { DocTypeVersion = (int)item.IntValue; }
+ else if (item.Definition == EBMLElementDefiniton.DocTypeReadVersion) { DocTypeReadVersion = (int)item.IntValue; }
+ else if (item.Definition == EBMLElementDefiniton.DocTypeExtension)
+ {
+ string name = string.Empty;
+ ulong version = 0;
+ foreach (var ext in ((EBMLMasterElement)item).Children)
+ {
+ if (item.Definition == EBMLElementDefiniton.DocTypeExtensionName) { name = item.StringValue ?? name; }
+ else if (item.Definition == EBMLElementDefiniton.DocTypeExtensionVersion) { version = item.UIntValue; }
+ }
+ if (name.Length > 0 && version > 0)
+ {
+ if (DocTypeExtensions == null) { DocTypeExtensions = new List<KeyValuePair<string, ulong>>(); }
+ DocTypeExtensions.Add(new KeyValuePair<string, ulong>(name, version));
+ }
+ }
+ }
+ }
+
+ public static async ValueTask<EBMLHeader> Read(EBMLReader reader, CancellationToken cancellationToken = default)
+ {
+ var header = await reader.ReadNextElement(true, cancellationToken);
+ if (header == null) { throw new Exception("Unexpected end of stream"); }
+ if (header.Definition != EBMLElementDefiniton.EBML) { throw new Exception("Expected EBML header; read different element"); }
+ while (!header.IsFullyRead) { if ((await reader.ReadNextElement(true, cancellationToken)) == null) { break; } }
+ return new EBMLHeader(header as EBMLMasterElement);
+ }
+
+ public static async ValueTask<EBMLHeader> Read(IDataQueueReader reader, DataBufferCache cache = null, CancellationToken cancellationToken = default)
+ {
+ using (var ebml = new EBMLReader(reader, true, cache))
+ {
+ EBMLElementDefiniton.AddHeaderElements(ebml);
+ return await Read(ebml, cancellationToken);
+ }
+ }
+
+ public static async ValueTask<EBMLHeader> Read(Stream stream, DataBufferCache cache = null, CancellationToken cancellationToken = default)
+ {
+ using (var ebml = new EBMLReader(stream, true, cache))
+ {
+ EBMLElementDefiniton.AddHeaderElements(ebml);
+ return await Read(ebml, cancellationToken);
+ }
+ }
+
+ public async ValueTask Write(EBMLWriter writer, CancellationToken cancellationToken = default)
+ {
+ await writer.BeginMasterElement(EBMLElementDefiniton.EBML, cancellationToken);
+ await writer.WriteUnsignedInteger(EBMLElementDefiniton.EBMLVersion, (ulong)EBMLVersion, cancellationToken);
+ await writer.WriteUnsignedInteger(EBMLElementDefiniton.EBMLReadVersion, (ulong)EBMLReadVersion, cancellationToken);
+ await writer.WriteUnsignedInteger(EBMLElementDefiniton.EBMLMaxIDLength, (ulong)EBMLMaxIDLength, cancellationToken);
+ await writer.WriteUnsignedInteger(EBMLElementDefiniton.EBMLMaxSizeLength, (ulong)EBMLMaxSizeLength, cancellationToken);
+ await writer.WriteString(EBMLElementDefiniton.DocType, DocType, cancellationToken);
+ await writer.WriteUnsignedInteger(EBMLElementDefiniton.DocTypeVersion, (ulong)DocTypeVersion, cancellationToken);
+ await writer.WriteUnsignedInteger(EBMLElementDefiniton.DocTypeReadVersion, (ulong)DocTypeReadVersion, cancellationToken);
+ if (DocTypeExtensions != null)
+ {
+ foreach (var ext in DocTypeExtensions)
+ {
+ await writer.BeginMasterElement(EBMLElementDefiniton.DocTypeExtension, cancellationToken);
+ await writer.WriteString(EBMLElementDefiniton.DocTypeExtensionName, ext.Key, cancellationToken);
+ await writer.WriteUnsignedInteger(EBMLElementDefiniton.DocTypeExtensionVersion, ext.Value, cancellationToken);
+ await writer.EndMasterElement(cancellationToken);
+ }
+ }
+ await writer.EndMasterElement(cancellationToken);
+ }
+
+ public EBMLMasterElement ToElement()
+ {
+ var header = new EBMLMasterElement(EBMLElementDefiniton.EBML);
+ header.AddChild(new EBMLUnsignedIntegerElement(EBMLElementDefiniton.EBMLVersion, (ulong)EBMLVersion));
+ header.AddChild(new EBMLUnsignedIntegerElement(EBMLElementDefiniton.EBMLReadVersion, (ulong)EBMLReadVersion));
+ header.AddChild(new EBMLUnsignedIntegerElement(EBMLElementDefiniton.EBMLMaxIDLength, (ulong)EBMLMaxIDLength));
+ header.AddChild(new EBMLUnsignedIntegerElement(EBMLElementDefiniton.EBMLMaxSizeLength, (ulong)EBMLMaxSizeLength));
+ header.AddChild(new EBMLStringElement(EBMLElementDefiniton.DocType, DocType));
+ header.AddChild(new EBMLUnsignedIntegerElement(EBMLElementDefiniton.DocTypeVersion, (ulong)DocTypeVersion));
+ header.AddChild(new EBMLUnsignedIntegerElement(EBMLElementDefiniton.DocTypeReadVersion, (ulong)DocTypeReadVersion));
+ if (DocTypeExtensions != null)
+ {
+ var extMaster = new EBMLMasterElement(EBMLElementDefiniton.DocTypeExtension);
+ foreach (var ext in DocTypeExtensions)
+ {
+ extMaster.AddChild(new EBMLStringElement(EBMLElementDefiniton.DocTypeExtensionName, ext.Key));
+ extMaster.AddChild(new EBMLUnsignedIntegerElement(EBMLElementDefiniton.DocTypeExtensionVersion, ext.Value));
+ }
+ header.AddChild(extMaster);
+ }
+ return header;
+ }
+
+ public override string ToString()
+ {
+ return (DocType ?? string.Empty) + " " + DocTypeVersion;
+ }
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLReader.cs b/MediaContainers.Matroska/EBML/EBMLReader.cs
new file mode 100644
index 0000000..fbf725e
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLReader.cs
@@ -0,0 +1,335 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers;
+
+public sealed class EBMLReader : IDisposable
+{
+ private readonly Dictionary<ulong, EBMLElementDefiniton> elementTypes = new Dictionary<ulong, EBMLElementDefiniton>();
+ private readonly DataBufferCache cache;
+ private readonly IDataQueueReader reader;
+ private readonly Stream stream;
+ private readonly long startOffset;
+ private readonly bool keepReaderOpen;
+ private readonly byte[] readTmp = new byte[8];
+ private readonly List<ElementLevel> masterBlocks = new();
+ private readonly DataQueueLimitedReaderMutable blockLimitedReader = new();
+ private volatile bool disposed;
+ private int maxInlineBinarySize = 4096;
+ private ElementLevel currentLevel;
+ private EBMLElement lastCachedElement;
+
+ public EBMLMasterElement CurrentCachedContainer => currentLevel?.CachedElement;
+ public EBMLElement LastCachedElement => lastCachedElement;
+ public long CurrentReaderOffset => reader.TotalBytesRead + startOffset;
+ public bool CanSeek => stream?.CanSeek ?? false;
+
+ public int MaxInlineBinarySize
+ {
+ get { return maxInlineBinarySize; }
+ set { maxInlineBinarySize = Math.Max(value, 0); }
+ }
+
+ private IDataQueueReader currentLevelReader => currentLevel?.BlockLimitedReader ?? reader;
+
+ private class ElementLevel
+ {
+ public readonly int Level;
+ public readonly DataQueueLimitedReaderMutable BlockLimitedReader = new();
+ public EBMLElementDefiniton Definition;
+ public EBMLMasterElement CachedElement;
+ public EBMLVInt DataSize;
+ public long DataOffset;
+
+ public ElementLevel(int level) { Level = level; }
+
+ public void Clear()
+ {
+ CachedElement?.MarkAsFullyRead();
+ Definition = null;
+ }
+ }
+
+ public EBMLReader(IDataQueueReader reader, bool keepReaderOpen = false, DataBufferCache cache = null)
+ {
+ this.reader = reader;
+ this.keepReaderOpen = keepReaderOpen;
+ this.cache = cache ?? DataBufferCache.DefaultCache;
+ EBMLElementDefiniton.AddGlobalElements(this);
+ }
+
+ public EBMLReader(Stream stream, bool keepStreamOpen = false, DataBufferCache cache = null)
+ : this(new DataQueueStreamReader(stream, keepStreamOpen), false, cache)
+ {
+ this.stream = stream;
+ if (stream.CanSeek) { startOffset = stream.Position; }
+ }
+
+ public void AddElementDefinition(EBMLElementDefiniton def)
+ {
+ elementTypes.Add(def.Id.ValueWithMarker, def);
+ }
+
+ public EBMLElementDefiniton GetElementDefinition(ulong id)
+ {
+ if (elementTypes.TryGetValue(id, out var def)) { return def; }
+ return null;
+ }
+
+ private async ValueTask<EBMLElementStruct> ReadSignedInteger(EBMLElementDefiniton def, CancellationToken cancellationToken = default)
+ {
+ var reader = currentLevelReader;
+ var size = await EBMLVInt.Read(reader, cancellationToken);
+ if (size.IsEmpty) { return default; }
+ if (size.IsUnknownValue || size.Value > 8) { return default; }
+ var offset = CurrentReaderOffset;
+ long value = 0;
+ for (int i = (int)size.Value; i > 0; i--)
+ {
+ var read = await reader.ReadByteAsync(cancellationToken);
+ if (read < 0) { return default; }
+ value = (value << 8) | (byte)read;
+ }
+ var shift = 64 - ((int)size.Value << 3);
+ value = (value << shift) >> shift;
+ return new EBMLElementStruct(def, size, offset)
+ {
+ Value = new EBMLElementStructValue() { SignedInteger = value }
+ };
+ }
+
+ private async ValueTask<EBMLElementStruct> ReadUnsignedInteger(EBMLElementDefiniton def, CancellationToken cancellationToken = default)
+ {
+ var reader = currentLevelReader;
+ var size = await EBMLVInt.Read(reader, cancellationToken);
+ if (size.IsEmpty) { return default; }
+ if (size.IsUnknownValue || size.Value > 8) { return default; }
+ var offset = CurrentReaderOffset;
+ ulong value = 0;
+ for (int i = (int)size.Value; i > 0; i--)
+ {
+ var read = await reader.ReadByteAsync(cancellationToken);
+ if (read < 0) { return default; }
+ value = (value << 8) | (byte)read;
+ }
+ return new EBMLElementStruct(def, size, offset)
+ {
+ Value = new EBMLElementStructValue() { UnsignedInteger = value }
+ };
+ }
+
+ private async ValueTask<EBMLElementStruct> ReadFloat(EBMLElementDefiniton def, CancellationToken cancellationToken = default)
+ {
+ var reader = currentLevelReader;
+ var size = await EBMLVInt.Read(reader, cancellationToken);
+ if (size.IsEmpty) { return default; }
+ if (size.IsUnknownValue) { return default; }
+ var offset = CurrentReaderOffset;
+ if (size.Value == 8)
+ {
+ await reader.ReadAsync(new Memory<byte>(readTmp, 0, 8), true, cancellationToken);
+ var value = System.Buffers.Binary.BinaryPrimitives.ReadDoubleBigEndian(readTmp);
+ return new EBMLElementStruct(def, size, offset)
+ {
+ Value = new EBMLElementStructValue() { Float64 = value }
+ };
+ }
+ if (size.Value == 4)
+ {
+ await reader.ReadAsync(new Memory<byte>(readTmp, 0, 4), true, cancellationToken);
+ var value = System.Buffers.Binary.BinaryPrimitives.ReadSingleBigEndian(readTmp);
+ return new EBMLElementStruct(def, size, offset)
+ {
+ Value = new EBMLElementStructValue() { Float32 = value },
+ UseFloat32 = true
+ };
+ }
+ return default;
+ }
+
+ private async ValueTask<EBMLElementStruct> ReadString(EBMLElementDefiniton def, CancellationToken cancellationToken = default)
+ {
+ var reader = currentLevelReader;
+ var size = await EBMLVInt.Read(reader, cancellationToken);
+ if (size.IsEmpty) { return default; }
+ if (size.IsUnknownValue) { return default; }
+ var offset = CurrentReaderOffset;
+ int readBytes = (int)size.Value;
+ if (readBytes == 0) { return new EBMLElementStruct(def, size, offset) { String = string.Empty }; }
+ using (var buffer = cache.Pop(readBytes))
+ {
+ readBytes = await reader.ReadAsync(new Memory<byte>(buffer.Buffer, 0, readBytes), true, cancellationToken);
+ var str = (def.Type == EBMLElementType.UTF8 ? Encoding.UTF8 : Encoding.ASCII).GetString(buffer.Buffer, 0, readBytes);
+ return new EBMLElementStruct(def, size, offset) { String = str };
+ }
+ }
+
+ private async ValueTask<EBMLElementStruct> ReadDate(EBMLElementDefiniton def, CancellationToken cancellationToken = default)
+ {
+ var reader = currentLevelReader;
+ var size = await EBMLVInt.Read(reader, cancellationToken);
+ if (size.IsEmpty) { return default; }
+ if (size.IsUnknownValue) { return default; }
+ var offset = CurrentReaderOffset;
+ if (size.Value == 0)
+ {
+ return new EBMLElementStruct(def, size, offset)
+ {
+ Value = new EBMLElementStructValue() { Date = EBMLDateElement.Epoch }
+ };
+ }
+ if (size.Value == 8)
+ {
+ await reader.ReadAsync(new Memory<byte>(readTmp, 0, 8), true, cancellationToken);
+ var value = System.Buffers.Binary.BinaryPrimitives.ReadInt64BigEndian(readTmp);
+ return new EBMLElementStruct(def, size, offset)
+ {
+ Value = new EBMLElementStructValue() { Date = new DateTime(EBMLDateElement.Epoch.Ticks + (value / 100), DateTimeKind.Utc) }
+ };
+ }
+ return default;
+ }
+
+ private async ValueTask<EBMLElementStruct> ReadBinary(EBMLElementDefiniton def, CancellationToken cancellationToken = default)
+ {
+ var reader = currentLevelReader;
+ var size = await EBMLVInt.Read(reader, cancellationToken);
+ if (size.IsEmpty) { return default; }
+ if (size.IsUnknownValue) { return default; }
+ var offset = CurrentReaderOffset;
+ if (size.Value == 0) { return new EBMLElementStruct(def, size, offset); }
+ if (size.Value <= (ulong)maxInlineBinarySize)
+ {
+ var bytes = new byte[(int)size.Value];
+ await reader.ReadAsync(bytes, true, cancellationToken);
+ return new EBMLElementStruct(def, size, offset) { Binary = bytes };
+ }
+ blockLimitedReader.SetReadSource(reader, (long)size.Value);
+ return new EBMLElementStruct(def, size, offset) { Reader = blockLimitedReader };
+ }
+
+ private async ValueTask<EBMLElementStruct> ReadVoid(CancellationToken cancellationToken = default)
+ {
+ var reader = currentLevelReader;
+ var size = await EBMLVInt.Read(reader, cancellationToken);
+ if (size.IsEmpty) { return default; }
+ if (size.IsUnknownValue) { return default; }
+ var offset = CurrentReaderOffset;
+ if (size.Value == 0) { return new EBMLElementStruct(EBMLElementDefiniton.Void, size, offset); }
+ if (size.Value <= (ulong)maxInlineBinarySize)
+ {
+ await reader.ReadAsync((int)size.Value, cancellationToken);
+ return new EBMLElementStruct(EBMLElementDefiniton.Void, size, offset);
+ }
+ blockLimitedReader.SetReadSource(reader, (long)size.Value);
+ return new EBMLElementStruct(EBMLElementDefiniton.Void, size, offset) { Reader = blockLimitedReader };
+ }
+
+ private async ValueTask<EBMLElementStruct> ReadMaster(EBMLElementDefiniton def, CancellationToken cancellationToken = default)
+ {
+ var reader = currentLevelReader;
+ var size = await EBMLVInt.Read(reader, cancellationToken);
+ if (size.IsEmpty) { return default; }
+ return new EBMLElementStruct(def, size, CurrentReaderOffset) { Reader = reader };
+ }
+
+ public async ValueTask<EBMLElementStruct> ReadNextElementRaw(bool cacheElement = true, CancellationToken cancellationToken = default)
+ {
+ if (blockLimitedReader.CanReadLength > 0)
+ {
+ await blockLimitedReader.ReadAsync((int)blockLimitedReader.CanReadLength, cancellationToken);
+ }
+ while (true)
+ {
+ var id = await EBMLVInt.Read(currentLevelReader, cancellationToken);
+ if (id.IsEmpty) { if (currentLevel == null) { return default; } PopLevel(); continue; }
+ elementTypes.TryGetValue(id.ValueWithMarker, out var def);
+ EBMLElementStruct element;
+ if (def != null)
+ {
+ while (currentLevel != null && currentLevel.BlockLimitedReader.IsUnknownSize && !def.IsDirectChildOf(currentLevel.Definition)) { PopLevel(); }
+ if (def == EBMLElementDefiniton.Void) { element = await ReadVoid(cancellationToken); }
+ else switch (def.Type)
+ {
+ case EBMLElementType.SignedInteger: element = await ReadSignedInteger(def, cancellationToken); break;
+ case EBMLElementType.UnsignedInteger: element = await ReadUnsignedInteger(def, cancellationToken); break;
+ case EBMLElementType.Float: element = await ReadFloat(def, cancellationToken); break;
+ case EBMLElementType.String: case EBMLElementType.UTF8: element = await ReadString(def, cancellationToken); break;
+ case EBMLElementType.Date: element = await ReadDate(def, cancellationToken); break;
+ case EBMLElementType.Master: element = await ReadMaster(def, cancellationToken); break;
+ case EBMLElementType.Binary: default: element = await ReadBinary(def, cancellationToken); break;
+ }
+ }
+ else
+ {
+ element = await ReadBinary(EBMLElementDefiniton.Unknown, cancellationToken);
+ }
+ if (element.Definition == null) { return element; }
+ lastCachedElement = null;
+ if (cacheElement)
+ {
+ lastCachedElement = element.ToElement();
+ if (currentLevel != null)
+ {
+ if (currentLevel.CachedElement == null) { CacheCurrentContainerTree(); }
+ currentLevel.CachedElement.AddChild(lastCachedElement);
+ }
+ }
+ if (element.Definition.Type == EBMLElementType.Master)
+ {
+ var newLevel = (currentLevel?.Level ?? -1) + 1;
+ if (masterBlocks.Count <= newLevel) { masterBlocks.Add(new ElementLevel(masterBlocks.Count)); }
+ currentLevel = masterBlocks[newLevel];
+ currentLevel.BlockLimitedReader.SetReadSource(element.Reader, element.DataSize.IsUnknownValue ? -1 : (long)element.DataSize.Value);
+ currentLevel.Definition = element.Definition;
+ currentLevel.DataSize = element.DataSize;
+ currentLevel.DataOffset = element.DataOffset;
+ currentLevel.CachedElement = cacheElement ? (EBMLMasterElement)lastCachedElement : null;
+ }
+ while (currentLevel != null && currentLevel.BlockLimitedReader.IsReadClosed) { PopLevel(); }
+ return element;
+ }
+ }
+
+ public async ValueTask<EBMLElement> ReadNextElement(bool cacheElement = true, CancellationToken cancellationToken = default)
+ {
+ var element = await ReadNextElementRaw(cacheElement, cancellationToken);
+ if (element.Definition == null) { return null; }
+ return lastCachedElement;
+ }
+
+ public void CacheCurrentContainerTree()
+ {
+ int level = (currentLevel?.Level ?? -1) + 1;
+ for (int i = 0; i < level; i++)
+ {
+ var obj = masterBlocks[i];
+ if (obj.CachedElement == null)
+ {
+ obj.CachedElement = new EBMLMasterElement(obj.Definition, obj.DataSize, obj.DataOffset);
+ if (i > 0) { masterBlocks[i - 1].CachedElement.AddChild(obj.CachedElement); }
+ }
+ }
+ }
+
+ private void PopLevel()
+ {
+ currentLevel.Clear();
+ if (currentLevel.Level == 0) { currentLevel = null; }
+ else { currentLevel = masterBlocks[currentLevel.Level - 1]; }
+ }
+
+ public void Dispose()
+ {
+ if (disposed) { return; }
+ disposed = true;
+ while (currentLevel != null) { PopLevel(); }
+ masterBlocks.Clear();
+ if (!keepReaderOpen) { reader.Dispose(); }
+ }
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLVInt.cs b/MediaContainers.Matroska/EBML/EBMLVInt.cs
new file mode 100644
index 0000000..4bedab7
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLVInt.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers;
+
+public struct EBMLVInt
+{
+ public static readonly EBMLVInt Empty = new();
+
+ public readonly byte WidthBytes;
+ public readonly ulong Value;
+
+ public ulong ValueMask => (1UL << ((WidthBytes << 3) - WidthBytes)) - 1;
+ public ulong ValueWithMarker => Value | ((0x100UL >> WidthBytes) << ((WidthBytes - 1) << 3));
+ public long SignedValue { get { var shift = 64 - ((WidthBytes << 3) - WidthBytes); return (long)(Value << shift) >> shift; } }
+ public bool IsUnknownValue => Value == ValueMask;
+ public bool IsMinWidth => WidthBytes == CalculateWidth(Value);
+ public bool IsValidValue => WidthBytes != 0;
+ public bool IsEmpty => WidthBytes == 0;
+
+ public EBMLVInt(byte width, ulong value)
+ {
+ WidthBytes = width;
+ Value = value;
+ if (width <= 0 || width > 8) { throw new ArgumentOutOfRangeException(nameof(width)); }
+ if ((value & ~ValueMask) != 0) { throw new ArgumentOutOfRangeException(nameof(value)); }
+ }
+
+ public EBMLVInt(ulong value) : this(CalculateWidth(value), value) { }
+
+ public static byte CalculateWidth(ulong value)
+ {
+ byte width = 1;
+ do
+ {
+ var mask = (1UL << ((width << 3) - width)) - 1;
+ if ((value & ~mask) == 0 && value != mask) { break; }
+ }
+ while (++width < 8);
+ return width;
+ }
+
+ public static EBMLVInt CreateUnknown(int width = 1)
+ {
+ if (width <= 0) { width = 1; }
+ if (width > 8) { width = 8; }
+ var mask = (1UL << ((width << 3) - width)) - 1;
+ return new EBMLVInt((byte)width, mask);
+ }
+
+ public static EBMLVInt CreateWithMarker(ulong value)
+ {
+ if (value == 0) { return Empty; }
+ byte width = 1;
+ do
+ {
+ var mask = (1UL << ((width << 3) + 1 - width)) - 1;
+ if ((value & ~mask) == 0) { break; }
+ }
+ while (++width < 8);
+ var widthMask = (ulong)(-1L << ((width << 3) + 1 - width));
+ if ((value & widthMask) != 0) { throw new ArgumentException(nameof(value)); }
+ var markerBit = ((0x100UL >> width) << ((width - 1) << 3));
+ if ((value & markerBit) != markerBit) { throw new ArgumentException(nameof(value)); }
+ return new EBMLVInt(width, value & ~markerBit);
+ }
+
+ public async ValueTask Write(IDataQueueWriter buffer, CancellationToken cancellationToken = default)
+ {
+ await buffer.WriteByteAsync((byte)((0x100 >> WidthBytes) | (byte)(Value >> ((WidthBytes - 1) << 3))), cancellationToken);
+ for (int i = 2; i <= WidthBytes; i++)
+ {
+ await buffer.WriteByteAsync((byte)((Value >> ((WidthBytes - i) << 3)) & 0xff), cancellationToken);
+ }
+ }
+
+ public void Write(DataBuffer buffer)
+ {
+ buffer.Buffer[buffer.WriteOffset++] = (byte)((0x100 >> WidthBytes) | (byte)(Value >> ((WidthBytes - 1) << 3)));
+ for (int i = 2; i <= WidthBytes; i++) { buffer.Buffer[buffer.WriteOffset++] = (byte)((Value >> ((WidthBytes - i) << 3)) & 0xff); }
+ }
+
+ public static async ValueTask<EBMLVInt> Read(IDataQueueReader buffer, CancellationToken cancellationToken = default)
+ {
+ var prefix = await buffer.ReadByteAsync(cancellationToken);
+ if (prefix <= 0) { return Empty; }
+ byte width = 1;
+ if ((prefix & 0x80) == 0)
+ {
+ width++;
+ if ((prefix & 0x40) == 0)
+ {
+ width++;
+ while (width < 8 && (prefix & (0x100 >> width)) == 0) { width++; }
+ }
+ }
+ if (width == 1) { return new EBMLVInt(1, (ulong)(prefix & 0x7F)); }
+ if (width == 2)
+ {
+ var next = await buffer.ReadByteAsync(cancellationToken);
+ if (next < 0) { return Empty; }
+ return new EBMLVInt(2, ((ulong)(prefix & 0x3F) << 8) | (byte)next);
+ }
+ ulong value = (ulong)(prefix & ((0x100 >> width) - 1));
+ for (int i = 1; i < width; i++)
+ {
+ var next = await buffer.ReadByteAsync(cancellationToken);
+ if (next < 0) { return Empty; }
+ value = (value << 8) | (byte)next;
+ }
+ return new EBMLVInt(width, value);
+ }
+}
diff --git a/MediaContainers.Matroska/EBML/EBMLWriter.cs b/MediaContainers.Matroska/EBML/EBMLWriter.cs
new file mode 100644
index 0000000..546d59a
--- /dev/null
+++ b/MediaContainers.Matroska/EBML/EBMLWriter.cs
@@ -0,0 +1,607 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers;
+
+public sealed class EBMLWriter : IDisposable, IAsyncDisposable
+{
+ private readonly DataBufferCache cache;
+ private readonly IDataQueueWriter writer;
+ private readonly Stream stream;
+ private readonly bool keepWriterOpen;
+ private readonly DataBuffer writeBuffer = new(null, new byte[4096]);
+ private int writeBusy;
+ private long startOffset;
+ private bool rewindOnDispose;
+ private volatile bool disposed;
+ private Level level;
+
+ const int maxMasterElementBufferSize = (1 << (24 - 3)) - 1;
+
+ private class Level : IDisposable
+ {
+ public readonly Level Parent;
+ public readonly EBMLElementDefiniton Definition;
+ public readonly DataQueue Buffer;
+ public readonly IDataQueueWriter ParentWriter;
+ public readonly long RelativeDataOffset;
+ public readonly bool CanSeek;
+ public bool NeedSizeWritten;
+ public int? DataSizeWidth;
+ public Task ForwardTask;
+
+ public Level(Level parent, EBMLElementDefiniton def, DataQueue buffer, IDataQueueWriter parentWriter, bool canSeek)
+ {
+ Parent = parent;
+ Definition = def;
+ Buffer = buffer;
+ ParentWriter = parentWriter;
+ CanSeek = canSeek;
+ RelativeDataOffset = parentWriter.TotalBytesWritten;
+ buffer.OnBlockWrite += OnBlockWriteEvent;
+ }
+
+ public Task ForwardBuffer(CancellationToken cancellationToken = default)
+ {
+ lock (this)
+ {
+ if (ForwardTask == null) { ForwardTask = ForwardAsync(); }
+ if (cancellationToken.CanBeCanceled && !ForwardTask.IsCompleted) { return ForwardTask.WaitAsync(cancellationToken); }
+ return ForwardTask;
+ }
+ }
+
+ private void OnBlockWriteEvent()
+ {
+ ForwardBuffer();
+ }
+
+ private async Task ForwardAsync()
+ {
+ EBMLVInt sizeVInt;
+ if (Buffer.IsWriteClosed)
+ {
+ sizeVInt = new EBMLVInt((ulong)Buffer.UnreadLength);
+ }
+ else
+ {
+ NeedSizeWritten = true;
+ if (!CanSeek && Definition.AllowUnknownSize) { NeedSizeWritten = false; }
+ sizeVInt = EBMLVInt.CreateUnknown(NeedSizeWritten ? 8 : 1);
+ }
+ DataSizeWidth = sizeVInt.WidthBytes;
+ await sizeVInt.Write(ParentWriter);
+ await Buffer.ForwardTo(ParentWriter);
+ }
+
+ public void Dispose()
+ {
+ Buffer.Dispose();
+ }
+ }
+
+ public bool StreamingMode { get; set; }
+ public bool WindowsCompatibilityMode { get; set; } = true;
+ public EBMLElementDefiniton CurrentContainer => level?.Definition;
+ public long CurrentContainerSize => currentLevelWriter.TotalBytesWritten + writeBuffer.UnreadSize;
+ public bool CanSeek => !StreamingMode && (stream?.CanSeek ?? false);
+
+ private IDataQueueWriter currentLevelWriter => level?.Buffer ?? writer;
+
+ public EBMLWriter(IDataQueueWriter writer, bool keepWriterOpen = false, DataBufferCache cache = null)
+ {
+ this.writer = writer;
+ this.keepWriterOpen = keepWriterOpen;
+ this.cache = cache ?? DataBufferCache.DefaultCache;
+ }
+
+ public EBMLWriter(Stream stream, bool keepStreamOpen = false, DataBufferCache cache = null, bool rewindOnDispose = false)
+ : this(new DataQueueStreamWriter(stream, keepStreamOpen), false, cache)
+ {
+ this.stream = stream;
+ if (stream.CanSeek && rewindOnDispose)
+ {
+ startOffset = stream.Position;
+ this.rewindOnDispose = true;
+ }
+ }
+
+ public static int CalculateWidth(long value)
+ {
+ if (value == 0) { return 0; }
+ for (int size = 1; size < 8; size++)
+ {
+ var shift = 64 - (size << 3);
+ if (((value << shift) >> shift) == value) { return size; }
+ }
+ return 8;
+ }
+
+ public static int CalculateWidth(ulong value)
+ {
+ if (value == 0) { return 0; }
+ for (int size = 1; size < 8; size++)
+ {
+ var mask = (ulong)(-1L << (size << 3));
+ if ((mask & value) == 0) { return size; }
+ }
+ return 8;
+ }
+
+ public ValueTask FlushInternalBuffer(CancellationToken cancellationToken = default)
+ {
+ if (writeBuffer.UnreadSize == 0) { return ValueTask.CompletedTask; }
+ var memory = new ReadOnlyMemory<byte>(writeBuffer.Buffer, writeBuffer.ReadOffset, writeBuffer.WriteOffset - writeBuffer.ReadOffset);
+ writeBuffer.ReadOffset = 0; writeBuffer.WriteOffset = 0;
+ return currentLevelWriter.WriteAsync(memory, cancellationToken);
+ }
+
+ public async ValueTask WriteSignedInteger(EBMLElementDefiniton def, long value, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (def == null) { throw new ArgumentNullException(nameof(def)); }
+ if (def.Type != EBMLElementType.SignedInteger) { throw new ArgumentException("Element type mismatch", nameof(def)); }
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ var size = (value == 0 && WindowsCompatibilityMode) ? 1 : CalculateWidth(value);
+ var sizeVInt = new EBMLVInt((ulong)size);
+ if (writeBuffer.SpaceLeft < (def.Id.WidthBytes + sizeVInt.WidthBytes + size)) { await FlushInternalBuffer(cancellationToken); }
+ def.Id.Write(writeBuffer);
+ sizeVInt.Write(writeBuffer);
+ if (size == 0) { return; }
+ for (int i = (size - 1) << 3; i >= 0; i -= 8)
+ {
+ writeBuffer.Buffer[writeBuffer.WriteOffset++] = (byte)((value >> i) & 0xff);
+ }
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask WriteUnsignedInteger(EBMLElementDefiniton def, ulong value, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (def == null) { throw new ArgumentNullException(nameof(def)); }
+ if (def.Type != EBMLElementType.UnsignedInteger) { throw new ArgumentException("Element type mismatch", nameof(def)); }
+
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ var size = (value == 0 && WindowsCompatibilityMode) ? 1 : CalculateWidth(value);
+ var sizeVInt = new EBMLVInt((ulong)size);
+ if (writeBuffer.SpaceLeft < (def.Id.WidthBytes + sizeVInt.WidthBytes + size)) { await FlushInternalBuffer(cancellationToken); }
+ def.Id.Write(writeBuffer);
+ sizeVInt.Write(writeBuffer);
+ if (size == 0) { return; }
+ for (int i = (size - 1) << 3; i >= 0; i -= 8)
+ {
+ writeBuffer.Buffer[writeBuffer.WriteOffset++] = (byte)((value >> i) & 0xff);
+ }
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask WriteFloat(EBMLElementDefiniton def, float value, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (def == null) { throw new ArgumentNullException(nameof(def)); }
+ if (def.Type != EBMLElementType.Float) { throw new ArgumentException("Element type mismatch", nameof(def)); }
+
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ var sizeVInt = new EBMLVInt(4);
+ if (writeBuffer.SpaceLeft < (def.Id.WidthBytes + sizeVInt.WidthBytes + 4)) { await FlushInternalBuffer(cancellationToken); }
+ def.Id.Write(writeBuffer);
+ sizeVInt.Write(writeBuffer);
+ System.Buffers.Binary.BinaryPrimitives.WriteSingleBigEndian(writeBuffer.WriteSpan, value);
+ writeBuffer.WriteOffset += 4;
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask WriteFloat(EBMLElementDefiniton def, double value, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (def == null) { throw new ArgumentNullException(nameof(def)); }
+ if (def.Type != EBMLElementType.Float) { throw new ArgumentException("Element type mismatch", nameof(def)); }
+
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ var sizeVInt = new EBMLVInt(8);
+ if (writeBuffer.SpaceLeft < (def.Id.WidthBytes + sizeVInt.WidthBytes + 8)) { await FlushInternalBuffer(cancellationToken); }
+ def.Id.Write(writeBuffer);
+ sizeVInt.Write(writeBuffer);
+ System.Buffers.Binary.BinaryPrimitives.WriteDoubleBigEndian(writeBuffer.WriteSpan, value);
+ writeBuffer.WriteOffset += 8;
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask WriteDate(EBMLElementDefiniton def, DateTime date, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (def == null) { throw new ArgumentNullException(nameof(def)); }
+ if (def.Type != EBMLElementType.Date) { throw new ArgumentException("Element type mismatch", nameof(def)); }
+
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ var sizeVInt = new EBMLVInt(8);
+ if (writeBuffer.SpaceLeft < (def.Id.WidthBytes + sizeVInt.WidthBytes + 8)) { await FlushInternalBuffer(cancellationToken); }
+ def.Id.Write(writeBuffer);
+ sizeVInt.Write(writeBuffer);
+ var value = (date.Ticks - EBMLDateElement.Epoch.Ticks) * 100;
+ System.Buffers.Binary.BinaryPrimitives.WriteInt64BigEndian(writeBuffer.WriteSpan, value);
+ writeBuffer.WriteOffset += 8;
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask WriteString(EBMLElementDefiniton def, string str, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (def == null) { throw new ArgumentNullException(nameof(def)); }
+ if (def.Type != EBMLElementType.String && def.Type != EBMLElementType.UTF8) { throw new ArgumentException("Element type mismatch", nameof(def)); }
+
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ if (str == null) { str = string.Empty; }
+ var estimatedMaxSize = def.Id.WidthBytes + 8 + (def.Type != EBMLElementType.String ? str.Length : (str.Length * 4));
+ if (writeBuffer.SpaceLeft < estimatedMaxSize) { await FlushInternalBuffer(cancellationToken); }
+ if (writeBuffer.SpaceLeft >= estimatedMaxSize)
+ {
+ var originalOffset = writeBuffer.WriteOffset;
+ writeBuffer.WriteOffset += def.Id.WidthBytes + 1;
+ var size = (def.Type == EBMLElementType.UTF8 ? System.Text.Encoding.UTF8 : System.Text.Encoding.ASCII).GetBytes(str, writeBuffer.WriteSpan);
+ var sizeVInt = new EBMLVInt((ulong)size);
+ if (sizeVInt.WidthBytes > 1) { writeBuffer.WriteSpan.Slice(0, size).CopyTo(writeBuffer.WriteSpan.Slice(sizeVInt.WidthBytes - 1)); }
+ writeBuffer.WriteOffset = originalOffset;
+ def.Id.Write(writeBuffer);
+ sizeVInt.Write(writeBuffer);
+ writeBuffer.WriteOffset += size;
+ }
+ else
+ {
+ if (writeBuffer.UnreadSize > 0) { await FlushInternalBuffer(cancellationToken); }
+ var writer = currentLevelWriter;
+ using (var tmp = cache.Pop(str.Length * 4))
+ {
+ var size = (def.Type == EBMLElementType.UTF8 ? System.Text.Encoding.UTF8 : System.Text.Encoding.ASCII).GetBytes(str, tmp.Buffer);
+ var sizeVInt = new EBMLVInt((ulong)size);
+ await def.Id.Write(writer, cancellationToken);
+ await sizeVInt.Write(writer, cancellationToken);
+ await writer.WriteAsync(new ReadOnlyMemory<byte>(tmp.Buffer, 0, size), cancellationToken);
+ }
+ }
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask WriteVoid(int bytes, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ if (bytes < 0) { bytes = 0; }
+ var sizeVInt = new EBMLVInt((ulong)bytes);
+ if (writeBuffer.SpaceLeft < (EBMLElementDefiniton.Void.Id.WidthBytes + sizeVInt.WidthBytes + 16)) { await FlushInternalBuffer(cancellationToken); }
+ EBMLElementDefiniton.Void.Id.Write(writeBuffer);
+ sizeVInt.Write(writeBuffer);
+ while (bytes > 0)
+ {
+ if (writeBuffer.SpaceLeft < 16) { await FlushInternalBuffer(cancellationToken); }
+ var canWrite = writeBuffer.SpaceLeft;
+ if (canWrite > bytes) { canWrite = bytes; }
+ writeBuffer.WriteSpan.Slice(0, canWrite).Clear();
+ writeBuffer.WriteOffset += canWrite;
+ bytes -= canWrite;
+ }
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask WriteBinary(EBMLElementDefiniton def, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (def == null) { throw new ArgumentNullException(nameof(def)); }
+ if (def.Type != EBMLElementType.Binary && def.Type != EBMLElementType.Unknown) { throw new ArgumentException("Element type mismatch", nameof(def)); }
+
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ var sizeVInt = new EBMLVInt((ulong)bytes.Length);
+ var estimatedMaxSize = def.Id.WidthBytes + sizeVInt.WidthBytes + bytes.Length;
+ if (writeBuffer.SpaceLeft < estimatedMaxSize) { await FlushInternalBuffer(cancellationToken); }
+ def.Id.Write(writeBuffer);
+ sizeVInt.Write(writeBuffer);
+ if (bytes.Length == 0) { return; }
+
+ var canWrite = writeBuffer.SpaceLeft;
+ if (canWrite > bytes.Length) { canWrite = bytes.Length; }
+ bytes.Slice(0, canWrite).Span.CopyTo(writeBuffer.WriteSpan);
+ writeBuffer.WriteOffset += canWrite;
+ if (bytes.Length > canWrite)
+ {
+ await FlushInternalBuffer(cancellationToken);
+ await currentLevelWriter.WriteAsync(bytes.Slice(canWrite), cancellationToken);
+ }
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask WriteBinary(EBMLElementDefiniton def, ReadOnlyMemory<byte> header, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (def == null) { throw new ArgumentNullException(nameof(def)); }
+ if (def.Type != EBMLElementType.Binary && def.Type != EBMLElementType.Unknown) { throw new ArgumentException("Element type mismatch", nameof(def)); }
+
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ var sizeVInt = new EBMLVInt((ulong)(header.Length + bytes.Length));
+ var estimatedMaxSize = def.Id.WidthBytes + sizeVInt.WidthBytes + header.Length + bytes.Length;
+ if (writeBuffer.SpaceLeft < estimatedMaxSize) { await FlushInternalBuffer(cancellationToken); }
+ def.Id.Write(writeBuffer);
+ sizeVInt.Write(writeBuffer);
+ if (header.Length == 0 && bytes.Length == 0) { return; }
+
+ var canWrite = writeBuffer.SpaceLeft;
+ if (canWrite > header.Length) { canWrite = header.Length; }
+ header.Slice(0, canWrite).Span.CopyTo(writeBuffer.WriteSpan);
+ writeBuffer.WriteOffset += canWrite;
+ if (header.Length > canWrite)
+ {
+ await FlushInternalBuffer(cancellationToken);
+ await currentLevelWriter.WriteAsync(header.Slice(canWrite), cancellationToken);
+ }
+
+ if (writeBuffer.UnreadSize == 0) { canWrite = 0; }
+ else
+ {
+ canWrite = writeBuffer.SpaceLeft;
+ if (canWrite > bytes.Length) { canWrite = bytes.Length; }
+ bytes.Slice(0, canWrite).Span.CopyTo(writeBuffer.WriteSpan);
+ writeBuffer.WriteOffset += canWrite;
+ }
+ if (bytes.Length > canWrite)
+ {
+ await FlushInternalBuffer(cancellationToken);
+ await currentLevelWriter.WriteAsync(bytes.Slice(canWrite), cancellationToken);
+ }
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask WriteBinary(EBMLElementDefiniton def, Stream bytes, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (def == null) { throw new ArgumentNullException(nameof(def)); }
+ if (bytes == null) { throw new ArgumentNullException(nameof(bytes)); }
+ if (def.Type != EBMLElementType.Binary && def.Type != EBMLElementType.Unknown) { throw new ArgumentException("Element type mismatch", nameof(def)); }
+
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ if (writeBuffer.UnreadSize > 0) { await FlushInternalBuffer(cancellationToken); }
+ var writer = currentLevelWriter;
+ await def.Id.Write(writer, cancellationToken);
+ if (bytes.CanSeek)
+ {
+ var size = new EBMLVInt((ulong)(bytes.Length - bytes.Position));
+ await size.Write(writer, cancellationToken);
+ if (writer is IDataQueue dq) { await bytes.CopyToAsync(dq.WriteStream, cancellationToken); }
+ else
+ {
+ using (var target = new DataQueueWriteStream(writer, true))
+ {
+ await bytes.CopyToAsync(target, cancellationToken);
+ }
+ }
+ }
+ else
+ {
+ using (var mem = new MemoryStream())
+ {
+ await bytes.CopyToAsync(mem, cancellationToken);
+ await new EBMLVInt((ulong)mem.Length).Write(writer, cancellationToken);
+ mem.Position = 0;
+ using (var target = new DataQueueWriteStream(writer, true)) { await mem.CopyToAsync(target, cancellationToken); }
+ }
+ }
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask WriteElement(EBMLElement element, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (element == null) { throw new ArgumentNullException(nameof(element)); }
+ switch (element.ElementType)
+ {
+ case EBMLElementType.SignedInteger:
+ await WriteSignedInteger(element.Definition, element.IntValue, cancellationToken);
+ break;
+ case EBMLElementType.UnsignedInteger:
+ await WriteUnsignedInteger(element.Definition, element.UIntValue, cancellationToken);
+ break;
+ case EBMLElementType.Float:
+ if (element.DataSize.Value == 8) { await WriteFloat(element.Definition, element.DoubleValue, cancellationToken); }
+ else { await WriteFloat(element.Definition, element.FloatValue, cancellationToken); }
+ break;
+ case EBMLElementType.Date:
+ await WriteDate(element.Definition, element.DateValue ?? EBMLDateElement.Epoch, cancellationToken);
+ break;
+ case EBMLElementType.String:
+ case EBMLElementType.UTF8:
+ await WriteString(element.Definition, element.StringValue, cancellationToken);
+ break;
+ case EBMLElementType.Binary:
+ case EBMLElementType.Unknown:
+ if (element is EBMLVoidElement)
+ {
+ await WriteVoid((int)element.DataSize.Value, cancellationToken);
+ }
+ else
+ {
+ var bin = (EBMLBinaryElement)element;
+ if (bin.Reader == null) { await WriteBinary(element.Definition, bin.Value, cancellationToken); }
+ else { await WriteBinary(element.Definition, new DataQueueReadStream(bin.Reader), cancellationToken); }
+ }
+ break;
+ case EBMLElementType.Master:
+ var master = (EBMLMasterElement)element;
+ await BeginMasterElement(master.Definition, cancellationToken);
+ foreach (var child in master.Children) { await WriteElement(child, cancellationToken); }
+ await EndMasterElement(cancellationToken);
+ break;
+ }
+ }
+
+ public async ValueTask BeginMasterElement(EBMLElementDefiniton def, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(EBMLWriter)); }
+ if (def == null) { throw new ArgumentNullException(nameof(def)); }
+ if (def.Type != EBMLElementType.Master) { throw new ArgumentException("Element type mismatch", nameof(def)); }
+
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ if (writeBuffer.SpaceLeft < def.Id.WidthBytes) { await FlushInternalBuffer(cancellationToken); }
+ def.Id.Write(writeBuffer);
+ await FlushInternalBuffer(cancellationToken);
+ level = new Level(level, def, new DataQueue(maxMasterElementBufferSize, cache), currentLevelWriter, CanSeek);
+ }
+ finally { Interlocked.Decrement(ref writeBusy); }
+ }
+
+ public async ValueTask EndMasterElement(CancellationToken cancellationToken = default)
+ {
+ if (level == null) { throw new InvalidOperationException(); }
+ Level endedLevel = null;
+ try
+ {
+ if (Interlocked.Increment(ref writeBusy) != 1) { throw new InvalidOperationException("Concurrent writes not allowed."); }
+ await FlushInternalBuffer(cancellationToken);
+ endedLevel = level;
+ level = level.Parent;
+ endedLevel.Buffer.CloseWrite();
+ await endedLevel.ForwardBuffer(cancellationToken);
+ if (endedLevel.NeedSizeWritten)
+ {
+ if (CanSeek)
+ {
+ await FlushAsync(cancellationToken);
+ var blockSize = endedLevel.Buffer.TotalBytesWritten;
+ stream.Seek(-blockSize - 8, SeekOrigin.Current);
+ if ((blockSize & ~((1L << 56) - 1)) != 0) { throw new InvalidOperationException("Block size too large"); }
+ System.Buffers.Binary.BinaryPrimitives.WriteInt64BigEndian(writeBuffer.WriteSpan, blockSize | (1L << 56));
+ await stream.WriteAsync(writeBuffer.Buffer, writeBuffer.WriteOffset, 8, cancellationToken);
+ stream.Seek(blockSize, SeekOrigin.Current);
+ }
+ else if (!endedLevel.Definition.AllowUnknownSize)
+ {
+ throw new InvalidOperationException("Block size unknown without seekable stream");
+ }
+ }
+ }
+ finally
+ {
+ Interlocked.Decrement(ref writeBusy);
+ endedLevel?.Dispose();
+ }
+ }
+
+ public struct RelativeOffset
+ {
+ private readonly EBMLElementDefiniton relativeTo;
+ private readonly Level parent;
+ private readonly long relOffset;
+ private readonly bool inited;
+
+ public bool Marked => inited;
+
+ public RelativeOffset(EBMLWriter writer, EBMLElementDefiniton relativeTo)
+ {
+ this.relativeTo = relativeTo;
+ parent = writer.level;
+ relOffset = writer.currentLevelWriter.TotalBytesWritten + writer.writeBuffer.UnreadSize;
+ inited = true;
+ }
+
+ public long? Resolve()
+ {
+ if (!inited) { return null; }
+ var offset = relOffset;
+ var current = parent;
+ while (current != null && current.Definition != relativeTo)
+ {
+ if (current.DataSizeWidth == null)
+ {
+ current.ForwardBuffer();
+ if (current.DataSizeWidth == null) { return null; }
+ }
+ offset += current.DataSizeWidth.Value + current.RelativeDataOffset;
+ current = current.Parent;
+ }
+ if (current?.Definition != relativeTo)
+ {
+ throw new ArgumentException("Element not found in current hierarchy.", "relativeTo");
+ }
+ return offset;
+ }
+ }
+
+ public RelativeOffset MarkRelativeOffset(EBMLElementDefiniton relativeTo = null)
+ {
+ return new RelativeOffset(this, relativeTo);
+ }
+
+ public async ValueTask FlushAsync(CancellationToken cancellationToken = default)
+ {
+ if (level != null)
+ {
+ var current = level; do { _ = current.ForwardBuffer(); } while ((current = current.Parent) != null);
+ current = level; do { await current.Buffer.WaitForEmpty(cancellationToken); } while ((current = current.Parent) != null);
+ }
+ if (stream != null) { await stream.FlushAsync(cancellationToken); }
+ }
+
+ public async ValueTask<EBMLWriter> SeekWriter(long offset, SeekOrigin origin, CancellationToken cancellationToken = default)
+ {
+ if (!CanSeek) { throw new InvalidOperationException(); }
+ await FlushAsync(cancellationToken);
+ var writer = new EBMLWriter(stream, true, cache, true);
+ stream.Seek(offset, origin);
+ return writer;
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (disposed) { return; }
+ disposed = true;
+ while (level != null) { await EndMasterElement(); }
+ await FlushInternalBuffer();
+ if (rewindOnDispose) { stream.Seek(startOffset, SeekOrigin.Begin); }
+ if (!keepWriterOpen)
+ {
+ writer.CloseWrite();
+ if (writer is IAsyncDisposable a) { await a.DisposeAsync(); }
+ else if (writer is IDisposable b) { b.Dispose(); }
+ }
+ }
+
+ public void Dispose()
+ {
+ var task = DisposeAsync();
+ if (!task.IsCompletedSuccessfully) { task.AsTask().Wait(); }
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaCuePoint.cs b/MediaContainers.Matroska/Matroska/MatroskaCuePoint.cs
new file mode 100644
index 0000000..1d6c7b5
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaCuePoint.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaCuePoint : List<MatroskaCueTrackPosition>
+{
+ public ulong SegmentOffset { get; set; }
+ public ulong Timestamp { get; set; }
+
+ public void ReadFrom(EBMLMasterElement element)
+ {
+ if (element == null) { return; }
+ if (element.Definition != MatroskaSpecification.CuePoint) { return; }
+ foreach (var child in element.Children)
+ {
+ if (child.Definition == MatroskaSpecification.CueTime) { Timestamp = child.UIntValue; }
+ else if (child.Definition == MatroskaSpecification.CueTrackPositions)
+ {
+ var track = new MatroskaCueTrackPosition();
+ foreach (var sub in ((EBMLMasterElement)child).Children)
+ {
+ if (sub.Definition == MatroskaSpecification.CueTrack) { track.CueTrack = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.CueClusterPosition) { track.CueClusterPosition = sub.UIntValue + SegmentOffset; }
+ else if (sub.Definition == MatroskaSpecification.CueRelativePosition) { track.CueRelativePosition = sub.UIntValue; }
+ else if (sub.Definition == MatroskaSpecification.CueDuration) { track.CueDuration = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.CueBlockNumber) { track.CueBlockNumber = (int)sub.IntValue; }
+ }
+ Add(track);
+ }
+ }
+ }
+
+ public async ValueTask Write(EBMLWriter writer, CancellationToken cancellationToken = default)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.CuePoint, cancellationToken);
+ await writer.WriteUnsignedInteger(MatroskaSpecification.CueTime, Timestamp, cancellationToken);
+ for (int i = 0; i < Count; i++)
+ {
+ var track = this[i];
+ await writer.BeginMasterElement(MatroskaSpecification.CueTrackPositions, cancellationToken);
+ await writer.WriteUnsignedInteger(MatroskaSpecification.CueTrack, (ulong)track.CueTrack, cancellationToken);
+ await writer.WriteUnsignedInteger(MatroskaSpecification.CueClusterPosition, track.CueClusterPosition - SegmentOffset, cancellationToken);
+ if (track.CueRelativePosition > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.CueRelativePosition, track.CueRelativePosition, cancellationToken); }
+ if (track.CueDuration > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.CueDuration, (ulong)track.CueDuration, cancellationToken); }
+ if (track.CueBlockNumber > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.CueBlockNumber, (ulong)track.CueBlockNumber, cancellationToken); }
+ await writer.EndMasterElement(cancellationToken);
+ }
+ await writer.EndMasterElement(cancellationToken);
+ }
+
+ public EBMLMasterElement ToElement()
+ {
+ var cue = new EBMLMasterElement(MatroskaSpecification.CuePoint);
+ cue.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.CueTime, Timestamp));
+ foreach (var track in this)
+ {
+ var pos = new EBMLMasterElement(MatroskaSpecification.CueTrackPositions);
+ pos.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.CueTrack, (ulong)track.CueTrack));
+ pos.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.CueClusterPosition, track.CueClusterPosition - SegmentOffset));
+ if (track.CueRelativePosition > 0) { pos.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.CueRelativePosition, track.CueRelativePosition)); }
+ if (track.CueDuration > 0) { pos.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.CueDuration, (ulong)track.CueDuration)); }
+ if (track.CueBlockNumber > 0) { pos.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.CueBlockNumber, (ulong)track.CueBlockNumber)); }
+ cue.AddChild(pos);
+ }
+ return cue;
+ }
+}
+
+public class MatroskaCueTrackPosition
+{
+ public int CueTrack { get; set; }
+ public ulong CueClusterPosition { get; set; }
+ public ulong CueRelativePosition { get; set; }
+ public int CueDuration { get; set; }
+ public int CueBlockNumber { get; set; }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaCues.cs b/MediaContainers.Matroska/Matroska/MatroskaCues.cs
new file mode 100644
index 0000000..a5a855f
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaCues.cs
@@ -0,0 +1,40 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaCues : List<MatroskaCuePoint>
+{
+ public void AddCueEntry(EBMLMasterElement element, long segmentOffset)
+ {
+ if (element == null) { return; }
+ if (element.Definition == MatroskaSpecification.Cues)
+ {
+ foreach (var child in element.Children)
+ {
+ AddCueEntry(child as EBMLMasterElement, segmentOffset);
+ }
+ }
+ else if (element.Definition == MatroskaSpecification.CuePoint)
+ {
+ var entry = new MatroskaCuePoint() { SegmentOffset = (ulong)segmentOffset };
+ entry.ReadFrom(element);
+ Add(entry);
+ }
+ }
+
+ public async ValueTask Write(EBMLWriter writer, CancellationToken cancellationToken = default)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.Cues, cancellationToken);
+ for (int i = 0; i < Count; i++) { await this[i].Write(writer, cancellationToken); }
+ await writer.EndMasterElement(cancellationToken);
+ }
+
+ public EBMLMasterElement ToElement()
+ {
+ var tracks = new EBMLMasterElement(MatroskaSpecification.Cues);
+ foreach (var track in this) { tracks.AddChild(track.ToElement()); }
+ return tracks;
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaFrame.cs b/MediaContainers.Matroska/Matroska/MatroskaFrame.cs
new file mode 100644
index 0000000..eb5ef70
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaFrame.cs
@@ -0,0 +1,27 @@
+using System;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers.Matroska;
+
+public struct MatroskaFrame : IDisposable
+{
+ public readonly MatroskaTrackEntry Track;
+ public readonly long Timestamp;
+ public readonly DataBuffer Buffer;
+ public readonly int TrackIndex;
+ public readonly bool IsKeyFrame;
+
+ public MatroskaFrame(MatroskaTrackEntry track, int trackIndex, long timestamp, DataBuffer buffer, bool keyFrame)
+ {
+ Track = track;
+ TrackIndex = trackIndex;
+ Timestamp = timestamp;
+ Buffer = buffer;
+ IsKeyFrame = keyFrame;
+ }
+
+ public void Dispose()
+ {
+ Buffer?.Dispose();
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaInfo.cs b/MediaContainers.Matroska/Matroska/MatroskaInfo.cs
new file mode 100644
index 0000000..f65441f
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaInfo.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaInfo
+{
+ public byte[] SegmentUID { get; set; }
+ public string SegmentFilename { get; set; }
+ public byte[] PrevUID { get; set; }
+ public string PrevFilename { get; set; }
+ public byte[] NextUID { get; set; }
+ public string NextFilename { get; set; }
+ public byte[] SegmentFamily { get; set; }
+ public List<ChapterTranslate> ChapterTranslateEntries { get; set; }
+ public ulong TimestampScale { get; set; } = 1000000;
+ public double Duration { get; set; }
+ public DateTime? DateUTC { get; set; }
+ public string Title { get; set; }
+ public string MuxingApp { get; set; } = "mkvdotnet_1.0";
+ public string WritingApp { get; set; } = "mkvdotnet_1.0";
+
+ public class ChapterTranslate
+ {
+ public byte[] ChapterTranslateEditionUID { get; set; }
+ public int ChapterTranslateCodec { get; set; }
+ public byte[] ChapterTranslateID { get; set; }
+ }
+
+ public void CopyTo(MatroskaInfo info, bool shallow = false, bool copyAppName = false)
+ {
+ info.SegmentUID = SegmentUID;
+ info.SegmentFilename = SegmentFilename;
+ info.PrevUID = PrevUID;
+ info.PrevFilename = PrevFilename;
+ info.NextUID = NextUID;
+ info.NextFilename = NextFilename;
+ info.SegmentFamily = SegmentFamily;
+ if (shallow) { info.ChapterTranslateEntries = ChapterTranslateEntries; }
+ else
+ {
+ info.ChapterTranslateEntries = null;
+ if (ChapterTranslateEntries != null)
+ {
+ info.ChapterTranslateEntries = ChapterTranslateEntries.Select(x => new ChapterTranslate()
+ {
+ ChapterTranslateEditionUID = x.ChapterTranslateEditionUID,
+ ChapterTranslateCodec = x.ChapterTranslateCodec,
+ ChapterTranslateID = x.ChapterTranslateID
+ })
+ .ToList();
+ }
+ }
+ info.TimestampScale = TimestampScale;
+ info.Duration = Duration;
+ info.DateUTC = DateUTC;
+ info.Title = Title;
+ if (copyAppName)
+ {
+ info.MuxingApp = MuxingApp;
+ info.WritingApp = WritingApp;
+ }
+ }
+
+ public void ReadFrom(EBMLMasterElement element)
+ {
+ if (element == null) { return; }
+ if (element.Definition != MatroskaSpecification.Info) { return; }
+ foreach (var child in element.Children)
+ {
+ if (child.Definition == MatroskaSpecification.SegmentUID) { SegmentUID = (child as EBMLBinaryElement).Value.ToArray(); }
+ else if (child.Definition == MatroskaSpecification.SegmentFilename) { SegmentFilename = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.PrevUID) { PrevUID = (child as EBMLBinaryElement).Value.ToArray(); }
+ else if (child.Definition == MatroskaSpecification.PrevFilename) { PrevFilename = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.NextUID) { NextUID = (child as EBMLBinaryElement).Value.ToArray(); }
+ else if (child.Definition == MatroskaSpecification.NextFilename) { NextFilename = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.SegmentFamily) { SegmentFamily = (child as EBMLBinaryElement).Value.ToArray(); }
+ else if (child.Definition == MatroskaSpecification.TimestampScale) { TimestampScale = child.UIntValue; }
+ else if (child.Definition == MatroskaSpecification.Duration) { Duration = child.DoubleValue; }
+ else if (child.Definition == MatroskaSpecification.DateUTC) { DateUTC = child.DateValue; }
+ else if (child.Definition == MatroskaSpecification.Title) { Title = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.MuxingApp) { MuxingApp = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.WritingApp) { WritingApp = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.ChapterTranslate)
+ {
+ var entry = new ChapterTranslate();
+ foreach (var sub in ((EBMLMasterElement)child).Children)
+ {
+ if (sub.Definition == MatroskaSpecification.ChapterTranslateID) { entry.ChapterTranslateID = (sub as EBMLBinaryElement).Value.ToArray(); }
+ else if (sub.Definition == MatroskaSpecification.ChapterTranslateCodec) { entry.ChapterTranslateCodec = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.ChapterTranslateEditionUID) { entry.ChapterTranslateEditionUID = (sub as EBMLBinaryElement).Value.ToArray(); }
+ }
+ if (ChapterTranslateEntries == null) { ChapterTranslateEntries = new List<ChapterTranslate>(); }
+ ChapterTranslateEntries.Add(entry);
+ }
+ }
+ }
+
+ public async ValueTask<EBMLWriter.RelativeOffset> Write(EBMLWriter writer, CancellationToken cancellationToken = default)
+ {
+ EBMLWriter.RelativeOffset durationMark = default;
+ await writer.BeginMasterElement(MatroskaSpecification.Info, cancellationToken);
+ if (SegmentUID != null) { await writer.WriteBinary(MatroskaSpecification.SegmentUID, SegmentUID, cancellationToken); }
+ if (SegmentFilename != null) { await writer.WriteString(MatroskaSpecification.SegmentFilename, SegmentFilename, cancellationToken); }
+ if (PrevUID != null) { await writer.WriteBinary(MatroskaSpecification.PrevUID, PrevUID, cancellationToken); }
+ if (PrevFilename != null) { await writer.WriteString(MatroskaSpecification.PrevFilename, SegmentFilename, cancellationToken); }
+ if (NextUID != null) { await writer.WriteBinary(MatroskaSpecification.NextUID, NextUID, cancellationToken); }
+ if (NextFilename != null) { await writer.WriteString(MatroskaSpecification.NextFilename, NextFilename, cancellationToken); }
+ if (SegmentFamily != null) { await writer.WriteBinary(MatroskaSpecification.SegmentFamily, SegmentFamily, cancellationToken); }
+ if (ChapterTranslateEntries != null && ChapterTranslateEntries.Count > 0)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.ChapterTranslate, cancellationToken);
+ for (int i = 0; i < ChapterTranslateEntries.Count; i++)
+ {
+ var item = ChapterTranslateEntries[i];
+ if (item.ChapterTranslateID != null) { await writer.WriteBinary(MatroskaSpecification.ChapterTranslateID, item.ChapterTranslateID, cancellationToken); }
+ await writer.WriteUnsignedInteger(MatroskaSpecification.ChapterTranslateCodec, (ulong)item.ChapterTranslateCodec, cancellationToken);
+ await writer.WriteBinary(MatroskaSpecification.ChapterTranslateEditionUID, item.ChapterTranslateEditionUID, cancellationToken);
+ }
+ await writer.EndMasterElement(cancellationToken);
+ }
+ await writer.WriteUnsignedInteger(MatroskaSpecification.TimestampScale, TimestampScale, cancellationToken);
+ if (Duration > 0)
+ {
+ durationMark = writer.MarkRelativeOffset(null);
+ await writer.WriteFloat(MatroskaSpecification.Duration, Duration, cancellationToken);
+ }
+ if (DateUTC != null) { await writer.WriteDate(MatroskaSpecification.DateUTC, DateUTC.Value, cancellationToken); }
+ if (Title != null) { await writer.WriteString(MatroskaSpecification.Title, Title, cancellationToken); }
+ await writer.WriteString(MatroskaSpecification.MuxingApp, MuxingApp, cancellationToken);
+ await writer.WriteString(MatroskaSpecification.WritingApp, WritingApp, cancellationToken);
+ await writer.EndMasterElement(cancellationToken);
+ return durationMark;
+ }
+
+ public EBMLMasterElement ToElement()
+ {
+ var info = new EBMLMasterElement(MatroskaSpecification.Info);
+ if (SegmentUID != null) { info.AddChild(new EBMLBinaryElement(MatroskaSpecification.SegmentUID, SegmentUID)); }
+ if (SegmentFilename != null) { info.AddChild(new EBMLStringElement(MatroskaSpecification.SegmentFilename, SegmentFilename)); }
+ if (PrevUID != null) { info.AddChild(new EBMLBinaryElement(MatroskaSpecification.PrevUID, PrevUID)); }
+ if (PrevFilename != null) { info.AddChild(new EBMLStringElement(MatroskaSpecification.PrevFilename, PrevFilename)); }
+ if (NextUID != null) { info.AddChild(new EBMLBinaryElement(MatroskaSpecification.NextUID, NextUID)); }
+ if (NextFilename != null) { info.AddChild(new EBMLStringElement(MatroskaSpecification.NextFilename, NextFilename)); }
+ if (SegmentFamily != null) { info.AddChild(new EBMLBinaryElement(MatroskaSpecification.SegmentFamily, SegmentFamily)); }
+ if (ChapterTranslateEntries != null && ChapterTranslateEntries.Count > 0)
+ {
+ var chap = new EBMLMasterElement(MatroskaSpecification.ChapterTranslate);
+ foreach (var item in ChapterTranslateEntries)
+ {
+ if (item.ChapterTranslateID != null) { info.AddChild(new EBMLBinaryElement(MatroskaSpecification.ChapterTranslateID, item.ChapterTranslateID)); }
+ info.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.ChapterTranslateCodec, (ulong)item.ChapterTranslateCodec));
+ info.AddChild(new EBMLBinaryElement(MatroskaSpecification.ChapterTranslateEditionUID, item.ChapterTranslateEditionUID));
+ }
+ info.AddChild(chap);
+ }
+ info.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TimestampScale, TimestampScale));
+ if (Duration > 0) { info.AddChild(new EBMLFloatElement(MatroskaSpecification.Duration, Duration)); }
+ if (DateUTC != null) { info.AddChild(new EBMLDateElement(MatroskaSpecification.DateUTC, DateUTC.Value)); }
+ if (Title != null) { info.AddChild(new EBMLStringElement(MatroskaSpecification.Title, Title)); }
+ info.AddChild(new EBMLStringElement(MatroskaSpecification.MuxingApp, MuxingApp));
+ info.AddChild(new EBMLStringElement(MatroskaSpecification.WritingApp, WritingApp));
+ return info;
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaReader.cs b/MediaContainers.Matroska/Matroska/MatroskaReader.cs
new file mode 100644
index 0000000..1fece67
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaReader.cs
@@ -0,0 +1,268 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaReader : IDisposable
+{
+ private static readonly EBMLDocType[] docTypes = new EBMLDocType[]
+ {
+ new EBMLDocType("matroska", 4, 4),
+ new EBMLDocType("webm", 4, 4),
+ };
+
+ private readonly DataBufferCache cache;
+ private readonly EBMLDocumentReader document;
+ private readonly MatroskaSeekHead seekHead = new();
+ private readonly MatroskaInfo info = new();
+ private readonly MatroskaTracks tracks = new();
+ private readonly MatroskaTags tags = new();
+ private readonly MatroskaCues cues = new();
+ private readonly DataQueueMemoryReaderMutable memoryReader = new();
+ private readonly byte[] readBytes = new byte[8];
+ private MatroskaTrackEntry[] trackByNumber;
+ private Queue<MatroskaFrame> frameQueue;
+ private long clusterTimestamp;
+ private int scanIndex;
+ private int minTrackNumber;
+ private int[] lacingSizes;
+ private bool readTrackInfo;
+ private volatile bool disposed;
+
+ public virtual EBMLDocType[] SupportedDocTypes => docTypes;
+ public EBMLDocumentReader Document => document;
+ public MatroskaSeekHead SeekHead => seekHead;
+ public MatroskaInfo Info => info;
+ public MatroskaTracks Tracks => tracks;
+ public MatroskaTags Tags => tags;
+ public MatroskaCues Cues => cues;
+
+ static MatroskaReader()
+ {
+ MatroskaSpecification.RegisterFormat();
+ }
+
+ public MatroskaReader(EBMLDocumentReader doc, DataBufferCache cache = null)
+ {
+ if (doc == null) { throw new ArgumentNullException(nameof(doc)); }
+ var docTypes = SupportedDocTypes;
+ var canRead = false;
+ for (int i = docTypes.Length - 1; i >= 0; i--)
+ {
+ if (doc.CanBeReadBy(docTypes[i])) { canRead = true; break; }
+ }
+ if (!canRead)
+ {
+ throw new ArgumentException("Unsupported DocType: " + doc.Header.DocType + " " + doc.Header.DocTypeReadVersion, nameof(doc));
+ }
+ document = doc;
+ if (doc.Body.Definition != MatroskaSpecification.Segment)
+ {
+ throw new ArgumentException("Expected Segment as document body", nameof(doc));
+ }
+ this.cache = cache ?? DataBufferCache.DefaultCache;
+ }
+
+ public static async ValueTask<MatroskaReader> Read(IDataQueueReader reader, bool keepReaderOpen = false,
+ DataBufferCache cache = null, CancellationToken cancellationToken = default)
+ {
+ var doc = await EBMLDocumentReader.Read(reader, keepReaderOpen, cache, cancellationToken);
+ var matroska = new MatroskaReader(doc, cache);
+ await matroska.ReadTrackInfo(cancellationToken);
+ return matroska;
+ }
+
+ public static async ValueTask<MatroskaReader> Read(Stream stream, bool keepStreamOpen = false,
+ DataBufferCache cache = null, CancellationToken cancellationToken = default)
+ {
+ var doc = await EBMLDocumentReader.Read(stream, keepStreamOpen, cache, cancellationToken);
+ var matroska = new MatroskaReader(doc, cache);
+ await matroska.ReadTrackInfo(cancellationToken);
+ return matroska;
+ }
+
+ public static bool CanReadDocument(EBMLDocumentReader doc)
+ {
+ for (int i = docTypes.Length - 1; i >= 0; i--)
+ {
+ if (doc.CanBeReadBy(docTypes[i])) { return true; }
+ }
+ return false;
+ }
+
+ public void ScanTrackInfo()
+ {
+ var body = document.Body;
+ for (; scanIndex < body.Children.Length; scanIndex++)
+ {
+ var element = body.Children[scanIndex];
+ if (element.Definition == MatroskaSpecification.SeekHead)
+ {
+ seekHead.AddSeekIndex(document.Reader, element as EBMLMasterElement, document.Body.DataOffset);
+ }
+ else if (element.Definition == MatroskaSpecification.Info)
+ {
+ info.ReadFrom(element as EBMLMasterElement);
+ }
+ else if (element.Definition == MatroskaSpecification.Tracks)
+ {
+ tracks.AddTrackEntry(element as EBMLMasterElement);
+ }
+ else if (element.Definition == MatroskaSpecification.Tags)
+ {
+ tags.AddTagEntry(element as EBMLMasterElement);
+ }
+ else if (element.Definition == MatroskaSpecification.Cues)
+ {
+ cues.AddCueEntry(element as EBMLMasterElement, document.Body.DataOffset);
+ }
+ }
+ if (tracks.Count > 0)
+ {
+ int maxTrackNumber = tracks[0].TrackNumber;
+ minTrackNumber = maxTrackNumber;
+ for (int i = tracks.Count - 1; i >= 0; i--)
+ {
+ if (maxTrackNumber < tracks[i].TrackNumber) { maxTrackNumber = tracks[i].TrackNumber; }
+ if (minTrackNumber > tracks[i].TrackNumber) { minTrackNumber = tracks[i].TrackNumber; }
+ }
+ var range = maxTrackNumber + 1 - minTrackNumber;
+ if (range > 256) { trackByNumber = null; }
+ else
+ {
+ trackByNumber = new MatroskaTrackEntry[range];
+ for (int i = tracks.Count - 1; i >= 0; i--)
+ {
+ trackByNumber[tracks[i].TrackNumber - minTrackNumber] = tracks[i];
+ }
+ }
+ }
+ }
+
+ public async ValueTask ReadTrackInfo(CancellationToken cancellationToken = default)
+ {
+ if (readTrackInfo) { return; }
+ readTrackInfo = true;
+ document.Reader.MaxInlineBinarySize = 4096;
+ while (document.Reader.LastCachedElement?.Definition != MatroskaSpecification.Cluster)
+ {
+ if (await document.Reader.ReadNextElement(true, cancellationToken) == null) { break; }
+ }
+ ScanTrackInfo();
+ }
+
+ public async ValueTask<MatroskaFrame> ReadFrame(CancellationToken cancellationToken = default)
+ {
+ if (frameQueue != null && frameQueue.Count > 0) { return frameQueue.Dequeue(); }
+ if (!readTrackInfo) { await ReadTrackInfo(cancellationToken); }
+ while (true)
+ {
+ document.Reader.MaxInlineBinarySize = 0;
+ var element = await document.Reader.ReadNextElementRaw(false, cancellationToken);
+ if (element.Definition == null) { return default; }
+ if (element.Definition == MatroskaSpecification.Timestamp)
+ {
+ clusterTimestamp = (long)element.Value.UnsignedInteger;
+ }
+ else if (element.Definition == MatroskaSpecification.SimpleBlock ||
+ element.Definition == MatroskaSpecification.Block)
+ {
+ var reader = element.Reader;
+ if (reader == null) { memoryReader.SetBuffer(element.Binary); reader = memoryReader; }
+ MatroskaTrackEntry trackEntry = null;
+ var trackIndexVint = await EBMLVInt.Read(reader, cancellationToken);
+ var trackIndex = (int)trackIndexVint.Value;
+ if (trackByNumber != null)
+ {
+ var idx = trackIndex - minTrackNumber;
+ if (idx < trackByNumber.Length) { trackEntry = trackByNumber[idx]; }
+ }
+ else
+ {
+ for (int idx = tracks.Count - 1; idx >= 0; idx--)
+ {
+ if (tracks[idx].TrackNumber == trackIndex) { trackEntry = tracks[idx]; break; }
+ }
+ }
+ if (await reader.ReadAsync(new Memory<byte>(readBytes, 0, 3), true, cancellationToken) < 3) { return default; }
+ var timestampOffset = (short)((readBytes[0] << 8) | readBytes[1]);
+ var timestamp = clusterTimestamp + timestampOffset;
+ var flags = readBytes[2];
+ MatroskaFrame firstFrame = default;
+ int frameCount = 0;
+ if ((flags & 0x06) != 0) // Has lacing
+ {
+ if (lacingSizes == null) { lacingSizes = new int[256]; }
+ frameCount = await reader.ReadByteAsync(cancellationToken);
+ if ((flags & 0x06) == 0x02) // Xiph
+ {
+ for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
+ {
+ lacingSizes[frameIndex] = await XiphLacingSize.Read(reader, cancellationToken);
+ }
+ }
+ else if ((flags & 0x06) == 0x04) // EBML
+ {
+ for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
+ {
+ var val = await EBMLVInt.Read(reader, cancellationToken);
+ if (frameIndex == 0) { lacingSizes[frameIndex] = (int)val.Value; }
+ else { lacingSizes[frameIndex] = (int)val.SignedValue + lacingSizes[frameIndex - 1]; }
+ }
+ }
+ else // Fixed
+ {
+ var fixedSize = (int)reader.UnreadLength / (frameCount + 1);
+ for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
+ {
+ lacingSizes[frameIndex] = fixedSize;
+ }
+ }
+ }
+ for (int frameIndex = 0; frameIndex <= frameCount; frameIndex++)
+ {
+ DataBuffer buffer = null;
+ try
+ {
+ var size = frameIndex == frameCount ? (int)reader.UnreadLength : lacingSizes[frameIndex];
+ buffer = cache.Pop(size);
+ buffer.WriteOffset = await reader.ReadAsync(new Memory<byte>(buffer.Buffer, 0, size), true, cancellationToken);
+ var frame = new MatroskaFrame(trackEntry, trackIndex, timestamp, buffer, (flags & 0x80) != 0);
+ if (frameIndex == 0) { firstFrame = frame; }
+ else
+ {
+ if (frameQueue == null) { frameQueue = new Queue<MatroskaFrame>(); }
+ frameQueue.Enqueue(frame);
+ }
+ buffer = null;
+ }
+ finally { buffer?.Dispose(); }
+ }
+ return firstFrame;
+ }
+ else if (element.Definition != MatroskaSpecification.Cluster &&
+ element.Definition.Type == EBMLElementType.Master &&
+ !element.Definition.Path.StartsWith(@"\Segment\Cluster"))
+ {
+ readTrackInfo = false;
+ await ReadTrackInfo(cancellationToken);
+ }
+ }
+ }
+
+ public virtual void Dispose()
+ {
+ if (disposed) { return; }
+ disposed = true;
+ document.Dispose();
+ if (frameQueue != null)
+ {
+ while (frameQueue.Count > 0) { frameQueue.Dequeue().Buffer.Dispose(); }
+ frameQueue = null;
+ }
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaSeekHead.cs b/MediaContainers.Matroska/Matroska/MatroskaSeekHead.cs
new file mode 100644
index 0000000..b40330c
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaSeekHead.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaSeekHead
+{
+ private Dictionary<EBMLElementDefiniton, long> seekIndices = new();
+
+ public KeyValuePair<EBMLElementDefiniton, long>[] Indices => seekIndices.ToArray();
+
+ public void AddSeekIndex(EBMLElementDefiniton def, long offset)
+ {
+ seekIndices[def] = offset;
+ }
+
+ public void AddSeekIndex(EBMLReader ebml, EBMLMasterElement element, long segmentOffset)
+ {
+ if (element == null) { return; }
+ if (element.Definition == MatroskaSpecification.SeekHead)
+ {
+ foreach (var child in element.Children)
+ {
+ AddSeekIndex(ebml, child as EBMLMasterElement, segmentOffset);
+ }
+ }
+ else if (element.Definition == MatroskaSpecification.Seek)
+ {
+ EBMLElementDefiniton def = null; long position = -1;
+ foreach (var child in element.Children)
+ {
+ if (child.Definition == MatroskaSpecification.SeekID)
+ {
+ var bin = (EBMLBinaryElement)child;
+ var span = bin.Value.Span;
+ ulong id = 0;
+ for (int i = 0; i < span.Length; i++) { id = (id << 8) | span[i]; }
+ if (id != 0) { def = ebml.GetElementDefinition(id); }
+ }
+ else if (child.Definition == MatroskaSpecification.SeekPosition)
+ {
+ position = child.IntValue;
+ }
+ }
+ if (def != null && position >= 0)
+ {
+ AddSeekIndex(def, position + segmentOffset);
+ }
+ }
+ }
+
+ public long GetSeekOffset(EBMLElementDefiniton def)
+ {
+ if (seekIndices.TryGetValue(def, out var idx)) { return idx; }
+ return -1;
+ }
+
+ public async ValueTask Write(EBMLWriter writer, CancellationToken cancellationToken = default)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.SeekHead, cancellationToken);
+ foreach (var index in seekIndices)
+ {
+ var buffer = new byte[index.Key.Id.WidthBytes];
+ ulong id = index.Key.Id.ValueWithMarker;
+ for (int i = buffer.Length - 1; i >= 0; i--) { buffer[i] = (byte)(id & 0xff); id >>= 8; }
+ await writer.BeginMasterElement(MatroskaSpecification.Seek, cancellationToken);
+ await writer.WriteBinary(MatroskaSpecification.SeekID, buffer, cancellationToken);
+ await writer.WriteUnsignedInteger(MatroskaSpecification.SeekPosition, (ulong)index.Value, cancellationToken);
+ await writer.EndMasterElement(cancellationToken);
+ }
+ await writer.EndMasterElement(cancellationToken);
+ }
+
+ public EBMLMasterElement ToElement()
+ {
+ var seekHead = new EBMLMasterElement(MatroskaSpecification.SeekHead);
+ foreach (var index in seekIndices)
+ {
+ var buffer = new byte[index.Key.Id.WidthBytes];
+ ulong id = index.Key.Id.ValueWithMarker;
+ for (int i = buffer.Length - 1; i >= 0; i--) { buffer[i] = (byte)(id & 0xff); id >>= 8; }
+ var seek = new EBMLMasterElement(MatroskaSpecification.Seek);
+ seek.AddChild(new EBMLBinaryElement(MatroskaSpecification.SeekID, buffer));
+ seek.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.SeekPosition, (ulong)index.Value));
+ seekHead.AddChild(seek);
+ }
+ return seekHead;
+ }
+
+ public override string ToString()
+ {
+ var str = new StringBuilder();
+ foreach (var idx in seekIndices)
+ {
+ str.AppendLine(idx.Key.FullPath + ":0x" + Convert.ToString(idx.Value, 16));
+ }
+ return str.ToString();
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaSimpleTag.cs b/MediaContainers.Matroska/Matroska/MatroskaSimpleTag.cs
new file mode 100644
index 0000000..56bfbf5
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaSimpleTag.cs
@@ -0,0 +1,87 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaSimpleTag : List<MatroskaSimpleTag>
+{
+ public string TagName { get; set; }
+ public string TagLanguage { get; set; }
+ public string TagLanguageIETF { get; set; }
+ public bool TagDefault { get; set; } = true;
+ public string TagString { get; set; }
+ public byte[] TagBinary { get; set; }
+
+ public void ReadFrom(EBMLMasterElement element)
+ {
+ if (element == null) { return; }
+ if (element.Definition != MatroskaSpecification.SimpleTag) { return; }
+ foreach (var child in element.Children)
+ {
+ if (child.Definition == MatroskaSpecification.TagName) { TagName = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.TagLanguage) { TagLanguage = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.TagLanguageIETF) { TagLanguageIETF = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.TagDefault) { TagDefault = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.TagString) { TagString = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.TagBinary) { TagBinary = ((EBMLBinaryElement)child).Value.ToArray(); }
+ else if (child.Definition == MatroskaSpecification.SimpleTag)
+ {
+ var tag = new MatroskaSimpleTag();
+ tag.ReadFrom((EBMLMasterElement)child);
+ Add(tag);
+ }
+ }
+ }
+
+ public void CopyTo(MatroskaSimpleTag simple, bool shallow = false)
+ {
+ simple.TagName = TagName;
+ simple.TagLanguage = TagLanguage;
+ simple.TagLanguageIETF = TagLanguageIETF;
+ simple.TagDefault = TagDefault;
+ simple.TagString = TagString;
+ simple.TagBinary = TagBinary;
+ if (shallow)
+ {
+ simple.Clear();
+ for (int i = 0, j = Count; i < j; i++) { simple.Add(this[i]); }
+ }
+ else
+ {
+ simple.Clear();
+ for (int i = 0, j = Count; i < j; i++)
+ {
+ var sub = new MatroskaSimpleTag();
+ this[i].CopyTo(sub);
+ simple.Add(sub);
+ }
+ }
+ }
+
+ public async ValueTask Write(EBMLWriter writer, CancellationToken cancellationToken = default)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.SimpleTag, cancellationToken);
+ await writer.WriteString(MatroskaSpecification.TagName, TagName ?? string.Empty, cancellationToken);
+ if (TagLanguage != null) { await writer.WriteString(MatroskaSpecification.TagLanguage, TagLanguage, cancellationToken); }
+ if (TagLanguageIETF != null) { await writer.WriteString(MatroskaSpecification.TagLanguageIETF, TagLanguageIETF, cancellationToken); }
+ if (!TagDefault) { await writer.WriteUnsignedInteger(MatroskaSpecification.TagDefault, 0, cancellationToken); }
+ if (TagString != null) { await writer.WriteString(MatroskaSpecification.TagString, TagString, cancellationToken); }
+ if (TagBinary != null) { await writer.WriteBinary(MatroskaSpecification.TagBinary, TagBinary, cancellationToken); }
+ for (int i = 0; i < Count; i++) { await this[i].Write(writer, cancellationToken); }
+ await writer.EndMasterElement(cancellationToken);
+ }
+
+ public EBMLMasterElement ToElement()
+ {
+ var tracks = new EBMLMasterElement(MatroskaSpecification.SimpleTag);
+ tracks.AddChild(new EBMLStringElement(MatroskaSpecification.TagName, TagName ?? string.Empty));
+ if (TagLanguage != null) { tracks.AddChild(new EBMLStringElement(MatroskaSpecification.TagLanguage, TagLanguage)); }
+ if (TagLanguageIETF != null) { tracks.AddChild(new EBMLStringElement(MatroskaSpecification.TagLanguageIETF, TagLanguageIETF)); }
+ if (!TagDefault) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TagDefault, 0)); }
+ if (TagString != null) { tracks.AddChild(new EBMLStringElement(MatroskaSpecification.TagString, TagString)); }
+ if (TagBinary != null) { tracks.AddChild(new EBMLBinaryElement(MatroskaSpecification.TagBinary, TagBinary)); }
+ foreach (var track in this) { tracks.AddChild(track.ToElement()); }
+ return tracks;
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaSpecification.cs b/MediaContainers.Matroska/Matroska/MatroskaSpecification.cs
new file mode 100644
index 0000000..50ad7d6
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaSpecification.cs
@@ -0,0 +1,314 @@
+namespace MediaContainers.Matroska;
+
+public static class MatroskaSpecification
+{
+ public static readonly EBMLElementDefiniton Segment = new EBMLElementDefiniton(0x18538067, EBMLElementType.Master, @"\Segment", allowUnknownSize: true);
+ public static readonly EBMLElementDefiniton SeekHead = new EBMLElementDefiniton(0x114D9B74, EBMLElementType.Master, @"\Segment\SeekHead");
+ public static readonly EBMLElementDefiniton Seek = new EBMLElementDefiniton(0x4DBB, EBMLElementType.Master, @"\Segment\SeekHead\Seek");
+ public static readonly EBMLElementDefiniton SeekID = new EBMLElementDefiniton(0x53AB, EBMLElementType.Binary, @"\Segment\SeekHead\Seek\SeekID");
+ public static readonly EBMLElementDefiniton SeekPosition = new EBMLElementDefiniton(0x53AC, EBMLElementType.UnsignedInteger, @"\Segment\SeekHead\Seek\SeekPosition");
+ public static readonly EBMLElementDefiniton Info = new EBMLElementDefiniton(0x1549A966, EBMLElementType.Master, @"\Segment\Info");
+ public static readonly EBMLElementDefiniton SegmentUID = new EBMLElementDefiniton(0x73A4, EBMLElementType.Binary, @"\Segment\Info\SegmentUID");
+ public static readonly EBMLElementDefiniton SegmentFilename = new EBMLElementDefiniton(0x7384, EBMLElementType.UTF8, @"\Segment\Info\SegmentFilename");
+ public static readonly EBMLElementDefiniton PrevUID = new EBMLElementDefiniton(0x3CB923, EBMLElementType.Binary, @"\Segment\Info\PrevUID");
+ public static readonly EBMLElementDefiniton PrevFilename = new EBMLElementDefiniton(0x3C83AB, EBMLElementType.UTF8, @"\Segment\Info\PrevFilename");
+ public static readonly EBMLElementDefiniton NextUID = new EBMLElementDefiniton(0x3EB923, EBMLElementType.Binary, @"\Segment\Info\NextUID");
+ public static readonly EBMLElementDefiniton NextFilename = new EBMLElementDefiniton(0x3E83BB, EBMLElementType.UTF8, @"\Segment\Info\NextFilename");
+ public static readonly EBMLElementDefiniton SegmentFamily = new EBMLElementDefiniton(0x4444, EBMLElementType.Binary, @"\Segment\Info\SegmentFamily");
+ public static readonly EBMLElementDefiniton ChapterTranslate = new EBMLElementDefiniton(0x6924, EBMLElementType.Master, @"\Segment\Info\ChapterTranslate");
+ public static readonly EBMLElementDefiniton ChapterTranslateID = new EBMLElementDefiniton(0x69A5, EBMLElementType.Binary, @"\Segment\Info\ChapterTranslate\ChapterTranslateID");
+ public static readonly EBMLElementDefiniton ChapterTranslateCodec = new EBMLElementDefiniton(0x69BF, EBMLElementType.UnsignedInteger, @"\Segment\Info\ChapterTranslate\ChapterTranslateCodec");
+ public static readonly EBMLElementDefiniton ChapterTranslateEditionUID = new EBMLElementDefiniton(0x69FC, EBMLElementType.UnsignedInteger, @"\Segment\Info\ChapterTranslate\ChapterTranslateEditionUID");
+ public static readonly EBMLElementDefiniton TimestampScale = new EBMLElementDefiniton(0x2AD7B1, EBMLElementType.UnsignedInteger, @"\Segment\Info\TimestampScale", defaultVal: "1000000");
+ public static readonly EBMLElementDefiniton Duration = new EBMLElementDefiniton(0x4489, EBMLElementType.Float, @"\Segment\Info\Duration");
+ public static readonly EBMLElementDefiniton DateUTC = new EBMLElementDefiniton(0x4461, EBMLElementType.Date, @"\Segment\Info\DateUTC");
+ public static readonly EBMLElementDefiniton Title = new EBMLElementDefiniton(0x7BA9, EBMLElementType.UTF8, @"\Segment\Info\Title");
+ public static readonly EBMLElementDefiniton MuxingApp = new EBMLElementDefiniton(0x4D80, EBMLElementType.UTF8, @"\Segment\Info\MuxingApp");
+ public static readonly EBMLElementDefiniton WritingApp = new EBMLElementDefiniton(0x5741, EBMLElementType.UTF8, @"\Segment\Info\WritingApp");
+ public static readonly EBMLElementDefiniton Cluster = new EBMLElementDefiniton(0x1F43B675, EBMLElementType.Master, @"\Segment\Cluster", allowUnknownSize: true);
+ public static readonly EBMLElementDefiniton Timestamp = new EBMLElementDefiniton(0xE7, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\Timestamp");
+ public static readonly EBMLElementDefiniton SilentTracks = new EBMLElementDefiniton(0x5854, EBMLElementType.Master, @"\Segment\Cluster\SilentTracks");
+ public static readonly EBMLElementDefiniton SilentTrackNumber = new EBMLElementDefiniton(0x58D7, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\SilentTracks\SilentTrackNumber");
+ public static readonly EBMLElementDefiniton Position = new EBMLElementDefiniton(0xA7, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\Position");
+ public static readonly EBMLElementDefiniton PrevSize = new EBMLElementDefiniton(0xAB, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\PrevSize");
+ public static readonly EBMLElementDefiniton SimpleBlock = new EBMLElementDefiniton(0xA3, EBMLElementType.Binary, @"\Segment\Cluster\SimpleBlock");
+ public static readonly EBMLElementDefiniton BlockGroup = new EBMLElementDefiniton(0xA0, EBMLElementType.Master, @"\Segment\Cluster\BlockGroup");
+ public static readonly EBMLElementDefiniton Block = new EBMLElementDefiniton(0xA1, EBMLElementType.Binary, @"\Segment\Cluster\BlockGroup\Block");
+ public static readonly EBMLElementDefiniton BlockVirtual = new EBMLElementDefiniton(0xA2, EBMLElementType.Binary, @"\Segment\Cluster\BlockGroup\BlockVirtual");
+ public static readonly EBMLElementDefiniton BlockAdditions = new EBMLElementDefiniton(0x75A1, EBMLElementType.Master, @"\Segment\Cluster\BlockGroup\BlockAdditions");
+ public static readonly EBMLElementDefiniton BlockMore = new EBMLElementDefiniton(0xA6, EBMLElementType.Master, @"\Segment\Cluster\BlockGroup\BlockAdditions\BlockMore");
+ public static readonly EBMLElementDefiniton BlockAddID = new EBMLElementDefiniton(0xEE, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\BlockGroup\BlockAdditions\BlockMore\BlockAddID", defaultVal: "1");
+ public static readonly EBMLElementDefiniton BlockAdditional = new EBMLElementDefiniton(0xA5, EBMLElementType.Binary, @"\Segment\Cluster\BlockGroup\BlockAdditions\BlockMore\BlockAdditional");
+ public static readonly EBMLElementDefiniton BlockDuration = new EBMLElementDefiniton(0x9B, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\BlockGroup\BlockDuration");
+ public static readonly EBMLElementDefiniton ReferencePriority = new EBMLElementDefiniton(0xFA, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\BlockGroup\ReferencePriority", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ReferenceBlock = new EBMLElementDefiniton(0xFB, EBMLElementType.SignedInteger, @"\Segment\Cluster\BlockGroup\ReferenceBlock");
+ public static readonly EBMLElementDefiniton ReferenceVirtual = new EBMLElementDefiniton(0xFD, EBMLElementType.SignedInteger, @"\Segment\Cluster\BlockGroup\ReferenceVirtual");
+ public static readonly EBMLElementDefiniton CodecState = new EBMLElementDefiniton(0xA4, EBMLElementType.Binary, @"\Segment\Cluster\BlockGroup\CodecState");
+ public static readonly EBMLElementDefiniton DiscardPadding = new EBMLElementDefiniton(0x75A2, EBMLElementType.SignedInteger, @"\Segment\Cluster\BlockGroup\DiscardPadding");
+ public static readonly EBMLElementDefiniton Slices = new EBMLElementDefiniton(0x8E, EBMLElementType.Master, @"\Segment\Cluster\BlockGroup\Slices");
+ public static readonly EBMLElementDefiniton TimeSlice = new EBMLElementDefiniton(0xE8, EBMLElementType.Master, @"\Segment\Cluster\BlockGroup\Slices\TimeSlice");
+ public static readonly EBMLElementDefiniton LaceNumber = new EBMLElementDefiniton(0xCC, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\BlockGroup\Slices\TimeSlice\LaceNumber");
+ public static readonly EBMLElementDefiniton FrameNumber = new EBMLElementDefiniton(0xCD, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\BlockGroup\Slices\TimeSlice\FrameNumber", defaultVal: "0");
+ public static readonly EBMLElementDefiniton BlockAdditionID = new EBMLElementDefiniton(0xCB, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\BlockGroup\Slices\TimeSlice\BlockAdditionID", defaultVal: "0");
+ public static readonly EBMLElementDefiniton Delay = new EBMLElementDefiniton(0xCE, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\BlockGroup\Slices\TimeSlice\Delay", defaultVal: "0");
+ public static readonly EBMLElementDefiniton SliceDuration = new EBMLElementDefiniton(0xCF, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\BlockGroup\Slices\TimeSlice\SliceDuration", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ReferenceFrame = new EBMLElementDefiniton(0xC8, EBMLElementType.Master, @"\Segment\Cluster\BlockGroup\ReferenceFrame");
+ public static readonly EBMLElementDefiniton ReferenceOffset = new EBMLElementDefiniton(0xC9, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\BlockGroup\ReferenceFrame\ReferenceOffset");
+ public static readonly EBMLElementDefiniton ReferenceTimestamp = new EBMLElementDefiniton(0xCA, EBMLElementType.UnsignedInteger, @"\Segment\Cluster\BlockGroup\ReferenceFrame\ReferenceTimestamp");
+ public static readonly EBMLElementDefiniton EncryptedBlock = new EBMLElementDefiniton(0xAF, EBMLElementType.Binary, @"\Segment\Cluster\EncryptedBlock");
+ public static readonly EBMLElementDefiniton Tracks = new EBMLElementDefiniton(0x1654AE6B, EBMLElementType.Master, @"\Segment\Tracks");
+ public static readonly EBMLElementDefiniton TrackEntry = new EBMLElementDefiniton(0xAE, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry");
+ public static readonly EBMLElementDefiniton TrackNumber = new EBMLElementDefiniton(0xD7, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrackNumber");
+ public static readonly EBMLElementDefiniton TrackUID = new EBMLElementDefiniton(0x73C5, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrackUID");
+ public static readonly EBMLElementDefiniton TrackType = new EBMLElementDefiniton(0x83, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrackType");
+ public static readonly EBMLElementDefiniton FlagEnabled = new EBMLElementDefiniton(0xB9, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\FlagEnabled", defaultVal: "1");
+ public static readonly EBMLElementDefiniton FlagDefault = new EBMLElementDefiniton(0x88, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\FlagDefault", defaultVal: "1");
+ public static readonly EBMLElementDefiniton FlagForced = new EBMLElementDefiniton(0x55AA, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\FlagForced", defaultVal: "0");
+ public static readonly EBMLElementDefiniton FlagHearingImpaired = new EBMLElementDefiniton(0x55AB, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\FlagHearingImpaired");
+ public static readonly EBMLElementDefiniton FlagVisualImpaired = new EBMLElementDefiniton(0x55AC, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\FlagVisualImpaired");
+ public static readonly EBMLElementDefiniton FlagTextDescriptions = new EBMLElementDefiniton(0x55AD, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\FlagTextDescriptions");
+ public static readonly EBMLElementDefiniton FlagOriginal = new EBMLElementDefiniton(0x55AE, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\FlagOriginal");
+ public static readonly EBMLElementDefiniton FlagCommentary = new EBMLElementDefiniton(0x55AF, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\FlagCommentary");
+ public static readonly EBMLElementDefiniton FlagLacing = new EBMLElementDefiniton(0x9C, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\FlagLacing", defaultVal: "1");
+ public static readonly EBMLElementDefiniton MinCache = new EBMLElementDefiniton(0x6DE7, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\MinCache", defaultVal: "0");
+ public static readonly EBMLElementDefiniton MaxCache = new EBMLElementDefiniton(0x6DF8, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\MaxCache");
+ public static readonly EBMLElementDefiniton DefaultDuration = new EBMLElementDefiniton(0x23E383, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\DefaultDuration");
+ public static readonly EBMLElementDefiniton DefaultDecodedFieldDuration = new EBMLElementDefiniton(0x234E7A, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\DefaultDecodedFieldDuration");
+ public static readonly EBMLElementDefiniton TrackTimestampScale = new EBMLElementDefiniton(0x23314F, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\TrackTimestampScale", defaultVal: "0x1p+0");
+ public static readonly EBMLElementDefiniton TrackOffset = new EBMLElementDefiniton(0x537F, EBMLElementType.SignedInteger, @"\Segment\Tracks\TrackEntry\TrackOffset", defaultVal: "0");
+ public static readonly EBMLElementDefiniton MaxBlockAdditionID = new EBMLElementDefiniton(0x55EE, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\MaxBlockAdditionID", defaultVal: "0");
+ public static readonly EBMLElementDefiniton BlockAdditionMapping = new EBMLElementDefiniton(0x41E4, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\BlockAdditionMapping");
+ public static readonly EBMLElementDefiniton BlockAddIDValue = new EBMLElementDefiniton(0x41F0, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\BlockAdditionMapping\BlockAddIDValue");
+ public static readonly EBMLElementDefiniton BlockAddIDName = new EBMLElementDefiniton(0x41A4, EBMLElementType.String, @"\Segment\Tracks\TrackEntry\BlockAdditionMapping\BlockAddIDName");
+ public static readonly EBMLElementDefiniton BlockAddIDType = new EBMLElementDefiniton(0x41E7, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\BlockAdditionMapping\BlockAddIDType", defaultVal: "0");
+ public static readonly EBMLElementDefiniton BlockAddIDExtraData = new EBMLElementDefiniton(0x41ED, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\BlockAdditionMapping\BlockAddIDExtraData");
+ public static readonly EBMLElementDefiniton Name = new EBMLElementDefiniton(0x536E, EBMLElementType.UTF8, @"\Segment\Tracks\TrackEntry\Name");
+ public static readonly EBMLElementDefiniton Language = new EBMLElementDefiniton(0x22B59C, EBMLElementType.String, @"\Segment\Tracks\TrackEntry\Language", defaultVal: "eng");
+ public static readonly EBMLElementDefiniton LanguageIETF = new EBMLElementDefiniton(0x22B59D, EBMLElementType.String, @"\Segment\Tracks\TrackEntry\LanguageIETF");
+ public static readonly EBMLElementDefiniton CodecID = new EBMLElementDefiniton(0x86, EBMLElementType.String, @"\Segment\Tracks\TrackEntry\CodecID");
+ public static readonly EBMLElementDefiniton CodecPrivate = new EBMLElementDefiniton(0x63A2, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\CodecPrivate");
+ public static readonly EBMLElementDefiniton CodecName = new EBMLElementDefiniton(0x258688, EBMLElementType.UTF8, @"\Segment\Tracks\TrackEntry\CodecName");
+ public static readonly EBMLElementDefiniton AttachmentLink = new EBMLElementDefiniton(0x7446, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\AttachmentLink");
+ public static readonly EBMLElementDefiniton CodecSettings = new EBMLElementDefiniton(0x3A9697, EBMLElementType.UTF8, @"\Segment\Tracks\TrackEntry\CodecSettings");
+ public static readonly EBMLElementDefiniton CodecInfoURL = new EBMLElementDefiniton(0x3B4040, EBMLElementType.String, @"\Segment\Tracks\TrackEntry\CodecInfoURL");
+ public static readonly EBMLElementDefiniton CodecDownloadURL = new EBMLElementDefiniton(0x26B240, EBMLElementType.String, @"\Segment\Tracks\TrackEntry\CodecDownloadURL");
+ public static readonly EBMLElementDefiniton CodecDecodeAll = new EBMLElementDefiniton(0xAA, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\CodecDecodeAll", defaultVal: "1");
+ public static readonly EBMLElementDefiniton TrackOverlay = new EBMLElementDefiniton(0x6FAB, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrackOverlay");
+ public static readonly EBMLElementDefiniton CodecDelay = new EBMLElementDefiniton(0x56AA, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\CodecDelay", defaultVal: "0");
+ public static readonly EBMLElementDefiniton SeekPreRoll = new EBMLElementDefiniton(0x56BB, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\SeekPreRoll", defaultVal: "0");
+ public static readonly EBMLElementDefiniton TrackTranslate = new EBMLElementDefiniton(0x6624, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\TrackTranslate");
+ public static readonly EBMLElementDefiniton TrackTranslateTrackID = new EBMLElementDefiniton(0x66A5, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\TrackTranslate\TrackTranslateTrackID");
+ public static readonly EBMLElementDefiniton TrackTranslateCodec = new EBMLElementDefiniton(0x66BF, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrackTranslate\TrackTranslateCodec");
+ public static readonly EBMLElementDefiniton TrackTranslateEditionUID = new EBMLElementDefiniton(0x66FC, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrackTranslate\TrackTranslateEditionUID");
+ public static readonly EBMLElementDefiniton Video = new EBMLElementDefiniton(0xE0, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\Video");
+ public static readonly EBMLElementDefiniton FlagInterlaced = new EBMLElementDefiniton(0x9A, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\FlagInterlaced", defaultVal: "0");
+ public static readonly EBMLElementDefiniton FieldOrder = new EBMLElementDefiniton(0x9D, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\FieldOrder", defaultVal: "2");
+ public static readonly EBMLElementDefiniton StereoMode = new EBMLElementDefiniton(0x53B8, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\StereoMode", defaultVal: "0");
+ public static readonly EBMLElementDefiniton AlphaMode = new EBMLElementDefiniton(0x53C0, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\AlphaMode", defaultVal: "0");
+ public static readonly EBMLElementDefiniton OldStereoMode = new EBMLElementDefiniton(0x53B9, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\OldStereoMode");
+ public static readonly EBMLElementDefiniton PixelWidth = new EBMLElementDefiniton(0xB0, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\PixelWidth");
+ public static readonly EBMLElementDefiniton PixelHeight = new EBMLElementDefiniton(0xBA, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\PixelHeight");
+ public static readonly EBMLElementDefiniton PixelCropBottom = new EBMLElementDefiniton(0x54AA, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\PixelCropBottom", defaultVal: "0");
+ public static readonly EBMLElementDefiniton PixelCropTop = new EBMLElementDefiniton(0x54BB, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\PixelCropTop", defaultVal: "0");
+ public static readonly EBMLElementDefiniton PixelCropLeft = new EBMLElementDefiniton(0x54CC, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\PixelCropLeft", defaultVal: "0");
+ public static readonly EBMLElementDefiniton PixelCropRight = new EBMLElementDefiniton(0x54DD, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\PixelCropRight", defaultVal: "0");
+ public static readonly EBMLElementDefiniton DisplayWidth = new EBMLElementDefiniton(0x54B0, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\DisplayWidth");
+ public static readonly EBMLElementDefiniton DisplayHeight = new EBMLElementDefiniton(0x54BA, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\DisplayHeight");
+ public static readonly EBMLElementDefiniton DisplayUnit = new EBMLElementDefiniton(0x54B2, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\DisplayUnit", defaultVal: "0");
+ public static readonly EBMLElementDefiniton AspectRatioType = new EBMLElementDefiniton(0x54B3, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\AspectRatioType", defaultVal: "0");
+ public static readonly EBMLElementDefiniton UncompressedFourCC = new EBMLElementDefiniton(0x2EB524, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\Video\UncompressedFourCC");
+ public static readonly EBMLElementDefiniton GammaValue = new EBMLElementDefiniton(0x2FB523, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\GammaValue");
+ public static readonly EBMLElementDefiniton FrameRate = new EBMLElementDefiniton(0x2383E3, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\FrameRate");
+ public static readonly EBMLElementDefiniton Colour = new EBMLElementDefiniton(0x55B0, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\Video\Colour");
+ public static readonly EBMLElementDefiniton MatrixCoefficients = new EBMLElementDefiniton(0x55B1, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\MatrixCoefficients", defaultVal: "2");
+ public static readonly EBMLElementDefiniton BitsPerChannel = new EBMLElementDefiniton(0x55B2, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\BitsPerChannel", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ChromaSubsamplingHorz = new EBMLElementDefiniton(0x55B3, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\ChromaSubsamplingHorz");
+ public static readonly EBMLElementDefiniton ChromaSubsamplingVert = new EBMLElementDefiniton(0x55B4, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\ChromaSubsamplingVert");
+ public static readonly EBMLElementDefiniton CbSubsamplingHorz = new EBMLElementDefiniton(0x55B5, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\CbSubsamplingHorz");
+ public static readonly EBMLElementDefiniton CbSubsamplingVert = new EBMLElementDefiniton(0x55B6, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\CbSubsamplingVert");
+ public static readonly EBMLElementDefiniton ChromaSitingHorz = new EBMLElementDefiniton(0x55B7, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\ChromaSitingHorz", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ChromaSitingVert = new EBMLElementDefiniton(0x55B8, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\ChromaSitingVert", defaultVal: "0");
+ public static readonly EBMLElementDefiniton Range = new EBMLElementDefiniton(0x55B9, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\Range", defaultVal: "0");
+ public static readonly EBMLElementDefiniton TransferCharacteristics = new EBMLElementDefiniton(0x55BA, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\TransferCharacteristics", defaultVal: "2");
+ public static readonly EBMLElementDefiniton Primaries = new EBMLElementDefiniton(0x55BB, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\Primaries", defaultVal: "2");
+ public static readonly EBMLElementDefiniton MaxCLL = new EBMLElementDefiniton(0x55BC, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\MaxCLL");
+ public static readonly EBMLElementDefiniton MaxFALL = new EBMLElementDefiniton(0x55BD, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Colour\MaxFALL");
+ public static readonly EBMLElementDefiniton MasteringMetadata = new EBMLElementDefiniton(0x55D0, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata");
+ public static readonly EBMLElementDefiniton PrimaryRChromaticityX = new EBMLElementDefiniton(0x55D1, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryRChromaticityX");
+ public static readonly EBMLElementDefiniton PrimaryRChromaticityY = new EBMLElementDefiniton(0x55D2, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryRChromaticityY");
+ public static readonly EBMLElementDefiniton PrimaryGChromaticityX = new EBMLElementDefiniton(0x55D3, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryGChromaticityX");
+ public static readonly EBMLElementDefiniton PrimaryGChromaticityY = new EBMLElementDefiniton(0x55D4, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryGChromaticityY");
+ public static readonly EBMLElementDefiniton PrimaryBChromaticityX = new EBMLElementDefiniton(0x55D5, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryBChromaticityX");
+ public static readonly EBMLElementDefiniton PrimaryBChromaticityY = new EBMLElementDefiniton(0x55D6, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\PrimaryBChromaticityY");
+ public static readonly EBMLElementDefiniton WhitePointChromaticityX = new EBMLElementDefiniton(0x55D7, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\WhitePointChromaticityX");
+ public static readonly EBMLElementDefiniton WhitePointChromaticityY = new EBMLElementDefiniton(0x55D8, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\WhitePointChromaticityY");
+ public static readonly EBMLElementDefiniton LuminanceMax = new EBMLElementDefiniton(0x55D9, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\LuminanceMax");
+ public static readonly EBMLElementDefiniton LuminanceMin = new EBMLElementDefiniton(0x55DA, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Colour\MasteringMetadata\LuminanceMin");
+ public static readonly EBMLElementDefiniton Projection = new EBMLElementDefiniton(0x7670, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\Video\Projection");
+ public static readonly EBMLElementDefiniton ProjectionType = new EBMLElementDefiniton(0x7671, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Video\Projection\ProjectionType", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ProjectionPrivate = new EBMLElementDefiniton(0x7672, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\Video\Projection\ProjectionPrivate");
+ public static readonly EBMLElementDefiniton ProjectionPoseYaw = new EBMLElementDefiniton(0x7673, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Projection\ProjectionPoseYaw", defaultVal: "0x0p+0");
+ public static readonly EBMLElementDefiniton ProjectionPosePitch = new EBMLElementDefiniton(0x7674, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Projection\ProjectionPosePitch", defaultVal: "0x0p+0");
+ public static readonly EBMLElementDefiniton ProjectionPoseRoll = new EBMLElementDefiniton(0x7675, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Video\Projection\ProjectionPoseRoll", defaultVal: "0x0p+0");
+ public static readonly EBMLElementDefiniton Audio = new EBMLElementDefiniton(0xE1, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\Audio");
+ public static readonly EBMLElementDefiniton SamplingFrequency = new EBMLElementDefiniton(0xB5, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Audio\SamplingFrequency", defaultVal: "0x1.f4p+12");
+ public static readonly EBMLElementDefiniton OutputSamplingFrequency = new EBMLElementDefiniton(0x78B5, EBMLElementType.Float, @"\Segment\Tracks\TrackEntry\Audio\OutputSamplingFrequency");
+ public static readonly EBMLElementDefiniton Channels = new EBMLElementDefiniton(0x9F, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Audio\Channels", defaultVal: "1");
+ public static readonly EBMLElementDefiniton ChannelPositions = new EBMLElementDefiniton(0x7D7B, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\Audio\ChannelPositions");
+ public static readonly EBMLElementDefiniton BitDepth = new EBMLElementDefiniton(0x6264, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\Audio\BitDepth");
+ public static readonly EBMLElementDefiniton TrackOperation = new EBMLElementDefiniton(0xE2, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\TrackOperation");
+ public static readonly EBMLElementDefiniton TrackCombinePlanes = new EBMLElementDefiniton(0xE3, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\TrackOperation\TrackCombinePlanes");
+ public static readonly EBMLElementDefiniton TrackPlane = new EBMLElementDefiniton(0xE4, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\TrackOperation\TrackCombinePlanes\TrackPlane");
+ public static readonly EBMLElementDefiniton TrackPlaneUID = new EBMLElementDefiniton(0xE5, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrackOperation\TrackCombinePlanes\TrackPlane\TrackPlaneUID");
+ public static readonly EBMLElementDefiniton TrackPlaneType = new EBMLElementDefiniton(0xE6, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrackOperation\TrackCombinePlanes\TrackPlane\TrackPlaneType");
+ public static readonly EBMLElementDefiniton TrackJoinBlocks = new EBMLElementDefiniton(0xE9, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\TrackOperation\TrackJoinBlocks");
+ public static readonly EBMLElementDefiniton TrackJoinUID = new EBMLElementDefiniton(0xED, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrackOperation\TrackJoinBlocks\TrackJoinUID");
+ public static readonly EBMLElementDefiniton TrickTrackUID = new EBMLElementDefiniton(0xC0, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrickTrackUID");
+ public static readonly EBMLElementDefiniton TrickTrackSegmentUID = new EBMLElementDefiniton(0xC1, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\TrickTrackSegmentUID");
+ public static readonly EBMLElementDefiniton TrickTrackFlag = new EBMLElementDefiniton(0xC6, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrickTrackFlag", defaultVal: "0");
+ public static readonly EBMLElementDefiniton TrickMasterTrackUID = new EBMLElementDefiniton(0xC7, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\TrickMasterTrackUID");
+ public static readonly EBMLElementDefiniton TrickMasterTrackSegmentUID = new EBMLElementDefiniton(0xC4, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\TrickMasterTrackSegmentUID");
+ public static readonly EBMLElementDefiniton ContentEncodings = new EBMLElementDefiniton(0x6D80, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\ContentEncodings");
+ public static readonly EBMLElementDefiniton ContentEncoding = new EBMLElementDefiniton(0x6240, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding");
+ public static readonly EBMLElementDefiniton ContentEncodingOrder = new EBMLElementDefiniton(0x5031, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncodingOrder", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ContentEncodingScope = new EBMLElementDefiniton(0x5032, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncodingScope", defaultVal: "1");
+ public static readonly EBMLElementDefiniton ContentEncodingType = new EBMLElementDefiniton(0x5033, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncodingType", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ContentCompression = new EBMLElementDefiniton(0x5034, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentCompression");
+ public static readonly EBMLElementDefiniton ContentCompAlgo = new EBMLElementDefiniton(0x4254, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentCompression\ContentCompAlgo", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ContentCompSettings = new EBMLElementDefiniton(0x4255, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentCompression\ContentCompSettings");
+ public static readonly EBMLElementDefiniton ContentEncryption = new EBMLElementDefiniton(0x5035, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption");
+ public static readonly EBMLElementDefiniton ContentEncAlgo = new EBMLElementDefiniton(0x47E1, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentEncAlgo", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ContentEncKeyID = new EBMLElementDefiniton(0x47E2, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentEncKeyID");
+ public static readonly EBMLElementDefiniton ContentEncAESSettings = new EBMLElementDefiniton(0x47E7, EBMLElementType.Master, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentEncAESSettings");
+ public static readonly EBMLElementDefiniton AESSettingsCipherMode = new EBMLElementDefiniton(0x47E8, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentEncAESSettings\AESSettingsCipherMode");
+ public static readonly EBMLElementDefiniton ContentSignature = new EBMLElementDefiniton(0x47E3, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentSignature");
+ public static readonly EBMLElementDefiniton ContentSigKeyID = new EBMLElementDefiniton(0x47E4, EBMLElementType.Binary, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentSigKeyID");
+ public static readonly EBMLElementDefiniton ContentSigAlgo = new EBMLElementDefiniton(0x47E5, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentSigAlgo", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ContentSigHashAlgo = new EBMLElementDefiniton(0x47E6, EBMLElementType.UnsignedInteger, @"\Segment\Tracks\TrackEntry\ContentEncodings\ContentEncoding\ContentEncryption\ContentSigHashAlgo", defaultVal: "0");
+ public static readonly EBMLElementDefiniton Cues = new EBMLElementDefiniton(0x1C53BB6B, EBMLElementType.Master, @"\Segment\Cues");
+ public static readonly EBMLElementDefiniton CuePoint = new EBMLElementDefiniton(0xBB, EBMLElementType.Master, @"\Segment\Cues\CuePoint");
+ public static readonly EBMLElementDefiniton CueTime = new EBMLElementDefiniton(0xB3, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTime");
+ public static readonly EBMLElementDefiniton CueTrackPositions = new EBMLElementDefiniton(0xB7, EBMLElementType.Master, @"\Segment\Cues\CuePoint\CueTrackPositions");
+ public static readonly EBMLElementDefiniton CueTrack = new EBMLElementDefiniton(0xF7, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTrackPositions\CueTrack");
+ public static readonly EBMLElementDefiniton CueClusterPosition = new EBMLElementDefiniton(0xF1, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTrackPositions\CueClusterPosition");
+ public static readonly EBMLElementDefiniton CueRelativePosition = new EBMLElementDefiniton(0xF0, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTrackPositions\CueRelativePosition");
+ public static readonly EBMLElementDefiniton CueDuration = new EBMLElementDefiniton(0xB2, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTrackPositions\CueDuration");
+ public static readonly EBMLElementDefiniton CueBlockNumber = new EBMLElementDefiniton(0x5378, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTrackPositions\CueBlockNumber");
+ public static readonly EBMLElementDefiniton CueCodecState = new EBMLElementDefiniton(0xEA, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTrackPositions\CueCodecState", defaultVal: "0");
+ public static readonly EBMLElementDefiniton CueReference = new EBMLElementDefiniton(0xDB, EBMLElementType.Master, @"\Segment\Cues\CuePoint\CueTrackPositions\CueReference");
+ public static readonly EBMLElementDefiniton CueRefTime = new EBMLElementDefiniton(0x96, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTrackPositions\CueReference\CueRefTime");
+ public static readonly EBMLElementDefiniton CueRefCluster = new EBMLElementDefiniton(0x97, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTrackPositions\CueReference\CueRefCluster");
+ public static readonly EBMLElementDefiniton CueRefNumber = new EBMLElementDefiniton(0x535F, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTrackPositions\CueReference\CueRefNumber", defaultVal: "1");
+ public static readonly EBMLElementDefiniton CueRefCodecState = new EBMLElementDefiniton(0xEB, EBMLElementType.UnsignedInteger, @"\Segment\Cues\CuePoint\CueTrackPositions\CueReference\CueRefCodecState", defaultVal: "0");
+ public static readonly EBMLElementDefiniton Attachments = new EBMLElementDefiniton(0x1941A469, EBMLElementType.Master, @"\Segment\Attachments");
+ public static readonly EBMLElementDefiniton AttachedFile = new EBMLElementDefiniton(0x61A7, EBMLElementType.Master, @"\Segment\Attachments\AttachedFile");
+ public static readonly EBMLElementDefiniton FileDescription = new EBMLElementDefiniton(0x467E, EBMLElementType.UTF8, @"\Segment\Attachments\AttachedFile\FileDescription");
+ public static readonly EBMLElementDefiniton FileName = new EBMLElementDefiniton(0x466E, EBMLElementType.UTF8, @"\Segment\Attachments\AttachedFile\FileName");
+ public static readonly EBMLElementDefiniton FileMimeType = new EBMLElementDefiniton(0x4660, EBMLElementType.String, @"\Segment\Attachments\AttachedFile\FileMimeType");
+ public static readonly EBMLElementDefiniton FileData = new EBMLElementDefiniton(0x465C, EBMLElementType.Binary, @"\Segment\Attachments\AttachedFile\FileData");
+ public static readonly EBMLElementDefiniton FileUID = new EBMLElementDefiniton(0x46AE, EBMLElementType.UnsignedInteger, @"\Segment\Attachments\AttachedFile\FileUID");
+ public static readonly EBMLElementDefiniton FileReferral = new EBMLElementDefiniton(0x4675, EBMLElementType.Binary, @"\Segment\Attachments\AttachedFile\FileReferral");
+ public static readonly EBMLElementDefiniton FileUsedStartTime = new EBMLElementDefiniton(0x4661, EBMLElementType.UnsignedInteger, @"\Segment\Attachments\AttachedFile\FileUsedStartTime");
+ public static readonly EBMLElementDefiniton FileUsedEndTime = new EBMLElementDefiniton(0x4662, EBMLElementType.UnsignedInteger, @"\Segment\Attachments\AttachedFile\FileUsedEndTime");
+ public static readonly EBMLElementDefiniton Chapters = new EBMLElementDefiniton(0x1043A770, EBMLElementType.Master, @"\Segment\Chapters");
+ public static readonly EBMLElementDefiniton EditionEntry = new EBMLElementDefiniton(0x45B9, EBMLElementType.Master, @"\Segment\Chapters\EditionEntry");
+ public static readonly EBMLElementDefiniton EditionUID = new EBMLElementDefiniton(0x45BC, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\EditionUID");
+ public static readonly EBMLElementDefiniton EditionFlagHidden = new EBMLElementDefiniton(0x45BD, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\EditionFlagHidden", defaultVal: "0");
+ public static readonly EBMLElementDefiniton EditionFlagDefault = new EBMLElementDefiniton(0x45DB, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\EditionFlagDefault", defaultVal: "0");
+ public static readonly EBMLElementDefiniton EditionFlagOrdered = new EBMLElementDefiniton(0x45DD, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\EditionFlagOrdered", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ChapterAtom = new EBMLElementDefiniton(0xB6, EBMLElementType.Master, @"\Segment\Chapters\EditionEntry\+ChapterAtom");
+ public static readonly EBMLElementDefiniton ChapterUID = new EBMLElementDefiniton(0x73C4, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterUID");
+ public static readonly EBMLElementDefiniton ChapterStringUID = new EBMLElementDefiniton(0x5654, EBMLElementType.UTF8, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterStringUID");
+ public static readonly EBMLElementDefiniton ChapterTimeStart = new EBMLElementDefiniton(0x91, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterTimeStart");
+ public static readonly EBMLElementDefiniton ChapterTimeEnd = new EBMLElementDefiniton(0x92, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterTimeEnd");
+ public static readonly EBMLElementDefiniton ChapterFlagHidden = new EBMLElementDefiniton(0x98, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterFlagHidden", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ChapterFlagEnabled = new EBMLElementDefiniton(0x4598, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterFlagEnabled", defaultVal: "1");
+ public static readonly EBMLElementDefiniton ChapterSegmentUID = new EBMLElementDefiniton(0x6E67, EBMLElementType.Binary, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterSegmentUID");
+ public static readonly EBMLElementDefiniton ChapterSegmentEditionUID = new EBMLElementDefiniton(0x6EBC, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterSegmentEditionUID");
+ public static readonly EBMLElementDefiniton ChapterPhysicalEquiv = new EBMLElementDefiniton(0x63C3, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterPhysicalEquiv");
+ public static readonly EBMLElementDefiniton ChapterTrack = new EBMLElementDefiniton(0x8F, EBMLElementType.Master, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterTrack");
+ public static readonly EBMLElementDefiniton ChapterTrackUID = new EBMLElementDefiniton(0x89, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterTrack\ChapterTrackUID");
+ public static readonly EBMLElementDefiniton ChapterDisplay = new EBMLElementDefiniton(0x80, EBMLElementType.Master, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterDisplay");
+ public static readonly EBMLElementDefiniton ChapString = new EBMLElementDefiniton(0x85, EBMLElementType.UTF8, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterDisplay\ChapString");
+ public static readonly EBMLElementDefiniton ChapLanguage = new EBMLElementDefiniton(0x437C, EBMLElementType.String, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterDisplay\ChapLanguage", defaultVal: "eng");
+ public static readonly EBMLElementDefiniton ChapLanguageIETF = new EBMLElementDefiniton(0x437D, EBMLElementType.String, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterDisplay\ChapLanguageIETF");
+ public static readonly EBMLElementDefiniton ChapCountry = new EBMLElementDefiniton(0x437E, EBMLElementType.String, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapterDisplay\ChapCountry");
+ public static readonly EBMLElementDefiniton ChapProcess = new EBMLElementDefiniton(0x6944, EBMLElementType.Master, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess");
+ public static readonly EBMLElementDefiniton ChapProcessCodecID = new EBMLElementDefiniton(0x6955, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess\ChapProcessCodecID", defaultVal: "0");
+ public static readonly EBMLElementDefiniton ChapProcessPrivate = new EBMLElementDefiniton(0x450D, EBMLElementType.Binary, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess\ChapProcessPrivate");
+ public static readonly EBMLElementDefiniton ChapProcessCommand = new EBMLElementDefiniton(0x6911, EBMLElementType.Master, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess\ChapProcessCommand");
+ public static readonly EBMLElementDefiniton ChapProcessTime = new EBMLElementDefiniton(0x6922, EBMLElementType.UnsignedInteger, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess\ChapProcessCommand\ChapProcessTime");
+ public static readonly EBMLElementDefiniton ChapProcessData = new EBMLElementDefiniton(0x6933, EBMLElementType.Binary, @"\Segment\Chapters\EditionEntry\+ChapterAtom\ChapProcess\ChapProcessCommand\ChapProcessData");
+ public static readonly EBMLElementDefiniton Tags = new EBMLElementDefiniton(0x1254C367, EBMLElementType.Master, @"\Segment\Tags");
+ public static readonly EBMLElementDefiniton Tag = new EBMLElementDefiniton(0x7373, EBMLElementType.Master, @"\Segment\Tags\Tag");
+ public static readonly EBMLElementDefiniton Targets = new EBMLElementDefiniton(0x63C0, EBMLElementType.Master, @"\Segment\Tags\Tag\Targets");
+ public static readonly EBMLElementDefiniton TargetTypeValue = new EBMLElementDefiniton(0x68CA, EBMLElementType.UnsignedInteger, @"\Segment\Tags\Tag\Targets\TargetTypeValue", defaultVal: "50");
+ public static readonly EBMLElementDefiniton TargetType = new EBMLElementDefiniton(0x63CA, EBMLElementType.String, @"\Segment\Tags\Tag\Targets\TargetType");
+ public static readonly EBMLElementDefiniton TagTrackUID = new EBMLElementDefiniton(0x63C5, EBMLElementType.UnsignedInteger, @"\Segment\Tags\Tag\Targets\TagTrackUID", defaultVal: "0");
+ public static readonly EBMLElementDefiniton TagEditionUID = new EBMLElementDefiniton(0x63C9, EBMLElementType.UnsignedInteger, @"\Segment\Tags\Tag\Targets\TagEditionUID", defaultVal: "0");
+ public static readonly EBMLElementDefiniton TagChapterUID = new EBMLElementDefiniton(0x63C4, EBMLElementType.UnsignedInteger, @"\Segment\Tags\Tag\Targets\TagChapterUID", defaultVal: "0");
+ public static readonly EBMLElementDefiniton TagAttachmentUID = new EBMLElementDefiniton(0x63C6, EBMLElementType.UnsignedInteger, @"\Segment\Tags\Tag\Targets\TagAttachmentUID", defaultVal: "0");
+ public static readonly EBMLElementDefiniton SimpleTag = new EBMLElementDefiniton(0x67C8, EBMLElementType.Master, @"\Segment\Tags\Tag\+SimpleTag");
+ public static readonly EBMLElementDefiniton TagName = new EBMLElementDefiniton(0x45A3, EBMLElementType.UTF8, @"\Segment\Tags\Tag\+SimpleTag\TagName");
+ public static readonly EBMLElementDefiniton TagLanguage = new EBMLElementDefiniton(0x447A, EBMLElementType.String, @"\Segment\Tags\Tag\+SimpleTag\TagLanguage", defaultVal: "und");
+ public static readonly EBMLElementDefiniton TagLanguageIETF = new EBMLElementDefiniton(0x447B, EBMLElementType.String, @"\Segment\Tags\Tag\+SimpleTag\TagLanguageIETF");
+ public static readonly EBMLElementDefiniton TagDefault = new EBMLElementDefiniton(0x4484, EBMLElementType.UnsignedInteger, @"\Segment\Tags\Tag\+SimpleTag\TagDefault", defaultVal: "1");
+ public static readonly EBMLElementDefiniton TagDefaultBogus = new EBMLElementDefiniton(0x44B4, EBMLElementType.UnsignedInteger, @"\Segment\Tags\Tag\+SimpleTag\TagDefaultBogus", defaultVal: "1");
+ public static readonly EBMLElementDefiniton TagString = new EBMLElementDefiniton(0x4487, EBMLElementType.UTF8, @"\Segment\Tags\Tag\+SimpleTag\TagString");
+ public static readonly EBMLElementDefiniton TagBinary = new EBMLElementDefiniton(0x4485, EBMLElementType.Binary, @"\Segment\Tags\Tag\+SimpleTag\TagBinary");
+
+ private static readonly EBMLElementDefiniton[] elements = new EBMLElementDefiniton[]
+ {
+ Segment, SeekHead, Seek, SeekID, SeekPosition, Info, SegmentUID, SegmentFilename, PrevUID, PrevFilename,
+ NextUID, NextFilename, SegmentFamily, ChapterTranslate, ChapterTranslateID, ChapterTranslateCodec, ChapterTranslateEditionUID, TimestampScale,
+ Duration, DateUTC, Title, MuxingApp, WritingApp, Cluster, Timestamp, SilentTracks, SilentTrackNumber, Position,
+ PrevSize, SimpleBlock, BlockGroup, Block, BlockVirtual, BlockAdditions, BlockMore, BlockAddID, BlockAdditional, BlockDuration,
+ ReferencePriority, ReferenceBlock, ReferenceVirtual, CodecState, DiscardPadding, Slices, TimeSlice, LaceNumber, FrameNumber, BlockAdditionID,
+ Delay, SliceDuration, ReferenceFrame, ReferenceOffset, ReferenceTimestamp, EncryptedBlock, Tracks, TrackEntry, TrackNumber, TrackUID,
+ TrackType, FlagEnabled, FlagDefault, FlagForced, FlagHearingImpaired, FlagVisualImpaired, FlagTextDescriptions, FlagOriginal, FlagCommentary, FlagLacing,
+ MinCache, MaxCache, DefaultDuration, DefaultDecodedFieldDuration, TrackTimestampScale, TrackOffset, MaxBlockAdditionID, BlockAdditionMapping, BlockAddIDValue, BlockAddIDName,
+ BlockAddIDType, BlockAddIDExtraData, Name, Language, LanguageIETF, CodecID, CodecPrivate, CodecName, AttachmentLink, CodecSettings,
+ CodecInfoURL, CodecDownloadURL, CodecDecodeAll, TrackOverlay, CodecDelay, SeekPreRoll, TrackTranslate, TrackTranslateTrackID, TrackTranslateCodec, TrackTranslateEditionUID,
+ Video, FlagInterlaced, FieldOrder, StereoMode, AlphaMode, OldStereoMode, PixelWidth, PixelHeight, PixelCropBottom, PixelCropTop,
+ PixelCropLeft, PixelCropRight, DisplayWidth, DisplayHeight, DisplayUnit, AspectRatioType, UncompressedFourCC, GammaValue, FrameRate, Colour,
+ MatrixCoefficients, BitsPerChannel, ChromaSubsamplingHorz, ChromaSubsamplingVert, CbSubsamplingHorz, CbSubsamplingVert, ChromaSitingHorz, ChromaSitingVert, Range, TransferCharacteristics,
+ Primaries, MaxCLL, MaxFALL, MasteringMetadata, PrimaryRChromaticityX, PrimaryRChromaticityY, PrimaryGChromaticityX, PrimaryGChromaticityY, PrimaryBChromaticityX, PrimaryBChromaticityY,
+ WhitePointChromaticityX, WhitePointChromaticityY, LuminanceMax, LuminanceMin, Projection, ProjectionType, ProjectionPrivate, ProjectionPoseYaw, ProjectionPosePitch, ProjectionPoseRoll,
+ Audio, SamplingFrequency, OutputSamplingFrequency, Channels, ChannelPositions, BitDepth, TrackOperation, TrackCombinePlanes, TrackPlane, TrackPlaneUID,
+ TrackPlaneType, TrackJoinBlocks, TrackJoinUID, TrickTrackUID, TrickTrackSegmentUID, TrickTrackFlag, TrickMasterTrackUID, TrickMasterTrackSegmentUID, ContentEncodings, ContentEncoding,
+ ContentEncodingOrder, ContentEncodingScope, ContentEncodingType, ContentCompression, ContentCompAlgo, ContentCompSettings, ContentEncryption, ContentEncAlgo, ContentEncKeyID, ContentEncAESSettings,
+ AESSettingsCipherMode, ContentSignature, ContentSigKeyID, ContentSigAlgo, ContentSigHashAlgo, Cues, CuePoint, CueTime, CueTrackPositions, CueTrack,
+ CueClusterPosition, CueRelativePosition, CueDuration, CueBlockNumber, CueCodecState, CueReference, CueRefTime, CueRefCluster, CueRefNumber, CueRefCodecState,
+ Attachments, AttachedFile, FileDescription, FileName, FileMimeType, FileData, FileUID, FileReferral, FileUsedStartTime, FileUsedEndTime,
+ Chapters, EditionEntry, EditionUID, EditionFlagHidden, EditionFlagDefault, EditionFlagOrdered, ChapterAtom, ChapterUID, ChapterStringUID, ChapterTimeStart,
+ ChapterTimeEnd, ChapterFlagHidden, ChapterFlagEnabled, ChapterSegmentUID, ChapterSegmentEditionUID, ChapterPhysicalEquiv, ChapterTrack, ChapterTrackUID, ChapterDisplay, ChapString,
+ ChapLanguage, ChapLanguageIETF, ChapCountry, ChapProcess, ChapProcessCodecID, ChapProcessPrivate, ChapProcessCommand, ChapProcessTime, ChapProcessData, Tags,
+ Tag, Targets, TargetTypeValue, TargetType, TagTrackUID, TagEditionUID, TagChapterUID, TagAttachmentUID, SimpleTag, TagName,
+ TagLanguage, TagLanguageIETF, TagDefault, TagDefaultBogus, TagString, TagBinary,
+ };
+
+ public static void AddElements(EBMLReader reader)
+ {
+ for (int i = 0; i < elements.Length; i++)
+ {
+ reader.AddElementDefinition(elements[i]);
+ }
+ }
+
+ private static bool registered;
+
+ public static void RegisterFormat()
+ {
+ if (registered) { return; }
+ registered = true;
+ EBMLDocTypeLookup.AddEBMLDocType((header, reader) =>
+ {
+ if (header.DocType == "matroska" || header.DocType == "webm")
+ {
+ AddElements(reader);
+ return true;
+ }
+ return false;
+ });
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaTag.cs b/MediaContainers.Matroska/Matroska/MatroskaTag.cs
new file mode 100644
index 0000000..e89e479
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaTag.cs
@@ -0,0 +1,166 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaTag : List<MatroskaSimpleTag>
+{
+ public int TargetTypeValue { get; set; } = 50;
+ public string TargetType { get; set; }
+ public List<ulong> TagTrackUID { get; set; }
+ public List<ulong> TagEditionUID { get; set; }
+ public List<ulong> TagChapterUID { get; set; }
+ public List<ulong> TagAttachmentUID { get; set; }
+
+ public void ReadFrom(EBMLMasterElement element)
+ {
+ if (element == null) { return; }
+ if (element.Definition != MatroskaSpecification.Tag) { return; }
+ foreach (var child in element.Children)
+ {
+ if (child.Definition == MatroskaSpecification.Targets)
+ {
+ foreach (var sub in child.Children)
+ {
+ if (sub.Definition == MatroskaSpecification.TargetTypeValue) { TargetTypeValue = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.TargetType) { TargetType = sub.StringValue; }
+ else if (sub.Definition == MatroskaSpecification.TagTrackUID)
+ {
+ if (TagTrackUID == null) { TagTrackUID = new List<ulong>(); }
+ TagTrackUID.Add(sub.UIntValue);
+ }
+ else if (sub.Definition == MatroskaSpecification.TagEditionUID)
+ {
+ if (TagEditionUID == null) { TagEditionUID = new List<ulong>(); }
+ TagEditionUID.Add(sub.UIntValue);
+ }
+ else if (sub.Definition == MatroskaSpecification.TagChapterUID)
+ {
+ if (TagChapterUID == null) { TagChapterUID = new List<ulong>(); }
+ TagChapterUID.Add(sub.UIntValue);
+ }
+ else if (sub.Definition == MatroskaSpecification.TagAttachmentUID)
+ {
+ if (TagAttachmentUID == null) { TagAttachmentUID = new List<ulong>(); }
+ TagAttachmentUID.Add(sub.UIntValue);
+ }
+ }
+ }
+ else if (child.Definition == MatroskaSpecification.SimpleTag)
+ {
+ var tag = new MatroskaSimpleTag();
+ tag.ReadFrom((EBMLMasterElement)child);
+ Add(tag);
+ }
+ }
+ }
+
+ public void CopyTo(MatroskaTag tag, bool shallow = false)
+ {
+ tag.TargetTypeValue = TargetTypeValue;
+ tag.TargetType = TargetType;
+ if (shallow)
+ {
+ tag.Clear();
+ tag.TagTrackUID = TagTrackUID;
+ tag.TagEditionUID = TagEditionUID;
+ tag.TagChapterUID = TagChapterUID;
+ tag.TagAttachmentUID = TagAttachmentUID;
+ for (int i = 0, j = Count; i < j; i++) { tag.Add(this[i]); }
+ }
+ else
+ {
+ tag.Clear();
+ tag.TagTrackUID = TagTrackUID?.ToList();
+ tag.TagEditionUID = TagEditionUID?.ToList();
+ tag.TagChapterUID = TagChapterUID?.ToList();
+ tag.TagAttachmentUID = TagAttachmentUID?.ToList();
+ for (int i = 0, j = Count; i < j; i++)
+ {
+ var simple = new MatroskaSimpleTag();
+ this[i].CopyTo(simple);
+ tag.Add(simple);
+ }
+ }
+ }
+
+ public async ValueTask Write(EBMLWriter writer, CancellationToken cancellationToken = default)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.Tag, cancellationToken);
+ await writer.BeginMasterElement(MatroskaSpecification.Targets, cancellationToken);
+ if (TargetTypeValue != 50) { await writer.WriteUnsignedInteger(MatroskaSpecification.TargetTypeValue, (ulong)TargetTypeValue, cancellationToken); }
+ if (TargetType != null) { await writer.WriteString(MatroskaSpecification.TargetType, TargetType, cancellationToken); }
+ if (TagTrackUID != null)
+ {
+ for (int i = 0; i < TagTrackUID.Count; i++)
+ {
+ await writer.WriteUnsignedInteger(MatroskaSpecification.TagTrackUID, TagTrackUID[i], cancellationToken);
+ }
+ }
+ if (TagEditionUID != null)
+ {
+ for (int i = 0; i < TagEditionUID.Count; i++)
+ {
+ await writer.WriteUnsignedInteger(MatroskaSpecification.TagEditionUID, TagEditionUID[i], cancellationToken);
+ }
+ }
+ if (TagChapterUID != null)
+ {
+ for (int i = 0; i < TagChapterUID.Count; i++)
+ {
+ await writer.WriteUnsignedInteger(MatroskaSpecification.TagChapterUID, TagChapterUID[i], cancellationToken);
+ }
+ }
+ if (TagAttachmentUID != null)
+ {
+ for (int i = 0; i < TagAttachmentUID.Count; i++)
+ {
+ await writer.WriteUnsignedInteger(MatroskaSpecification.TagAttachmentUID, TagAttachmentUID[i], cancellationToken);
+ }
+ }
+ await writer.EndMasterElement(cancellationToken);
+ for (int i = 0; i < Count; i++) { await this[i].Write(writer, cancellationToken); }
+ await writer.EndMasterElement(cancellationToken);
+ }
+
+ public EBMLMasterElement ToElement()
+ {
+ var tracks = new EBMLMasterElement(MatroskaSpecification.Tag);
+ var target = new EBMLMasterElement(MatroskaSpecification.Targets);
+ if (TargetTypeValue != 50) { target.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TargetTypeValue, (ulong)TargetTypeValue)); }
+ if (TargetType != null) { target.AddChild(new EBMLStringElement(MatroskaSpecification.TargetType, TargetType)); }
+ if (TagTrackUID != null)
+ {
+ for (int i = 0; i < TagTrackUID.Count; i++)
+ {
+ target.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TagTrackUID, TagTrackUID[i]));
+ }
+ }
+ if (TagEditionUID != null)
+ {
+ for (int i = 0; i < TagEditionUID.Count; i++)
+ {
+ target.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TagEditionUID, TagEditionUID[i]));
+ }
+ }
+ if (TagChapterUID != null)
+ {
+ for (int i = 0; i < TagChapterUID.Count; i++)
+ {
+ target.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TagChapterUID, TagChapterUID[i]));
+ }
+ }
+ if (TagAttachmentUID != null)
+ {
+ for (int i = 0; i < TagAttachmentUID.Count; i++)
+ {
+ target.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TagAttachmentUID, TagAttachmentUID[i]));
+ }
+ }
+ tracks.AddChild(target);
+ foreach (var track in this) { tracks.AddChild(track.ToElement()); }
+ return tracks;
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaTags.cs b/MediaContainers.Matroska/Matroska/MatroskaTags.cs
new file mode 100644
index 0000000..648512f
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaTags.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaTags : List<MatroskaTag>
+{
+ public void AddTagEntry(EBMLMasterElement element)
+ {
+ if (element == null) { return; }
+ if (element.Definition == MatroskaSpecification.Tags)
+ {
+ foreach (var child in element.Children)
+ {
+ AddTagEntry(child as EBMLMasterElement);
+ }
+ }
+ else if (element.Definition == MatroskaSpecification.Tag)
+ {
+ var entry = new MatroskaTag();
+ entry.ReadFrom(element);
+ Add(entry);
+ }
+ }
+
+ public void CopyTo(MatroskaTags tags, bool shallow = false)
+ {
+ if (shallow)
+ {
+ tags.Clear();
+ for (int i = 0, j = Count; i < j; i++) { tags.Add(this[i]); }
+ }
+ else
+ {
+ tags.Clear();
+ for (int i = 0, j = Count; i < j; i++)
+ {
+ var tag = new MatroskaTag();
+ this[i].CopyTo(tag);
+ tags.Add(tag);
+ }
+ }
+ }
+
+ public async ValueTask Write(EBMLWriter writer, CancellationToken cancellationToken = default)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.Tags, cancellationToken);
+ for (int i = 0; i < Count; i++) { await this[i].Write(writer, cancellationToken); }
+ await writer.EndMasterElement(cancellationToken);
+ }
+
+ public EBMLMasterElement ToElement()
+ {
+ var tracks = new EBMLMasterElement(MatroskaSpecification.Tags);
+ foreach (var track in this) { tracks.AddChild(track.ToElement()); }
+ return tracks;
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaTrackEntry.cs b/MediaContainers.Matroska/Matroska/MatroskaTrackEntry.cs
new file mode 100644
index 0000000..af4e7e0
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaTrackEntry.cs
@@ -0,0 +1,506 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaTrackEntry
+{
+ public int TrackNumber { get; set; }
+ public ulong TrackUID { get; set; }
+ public MatroskaTrackType TrackType { get; set; }
+ public bool FlagEnabled { get; set; } = true;
+ public bool FlagDefault { get; set; } = true;
+ public bool FlagForced { get; set; }
+ public bool FlagHearingImpaired { get; set; }
+ public bool FlagVisualImpaired { get; set; }
+ public bool FlagTextDescriptions { get; set; }
+ public bool FlagOriginal { get; set; }
+ public bool FlagCommentary { get; set; }
+ public bool FlagLacing { get; set; } = true;
+ public int MinCache { get; set; }
+ public int? MaxCache { get; set; }
+ public ulong DefaultDuration { get; set; }
+ public ulong DefaultDecodedFieldDuration { get; set; }
+ public ulong MaxBlockAdditionID { get; set; }
+ public List<BlockAdditionMapping> BlockAdditionMappingEntries { get; set; }
+ public string Name { get; set; }
+ public string Language { get; set; }
+ public string LanguageIETF { get; set; }
+ public string CodecID { get; set; }
+ public byte[] CodecPrivate { get; set; }
+ public string CodecName { get; set; }
+ public bool CodecDecodeAll { get; set; } = true;
+ public List<int> TrackOverlay { get; set; }
+ public int CodecDelay { get; set; }
+ public int SeekPreRoll { get; set; }
+ public List<TrackTranslate> TrackTranslateEntries { get; set; }
+
+ public bool HasVideo { get; set; }
+ public VideoInterlacing FlagInterlaced { get; set; } = VideoInterlacing.Undetermined;
+ public VideoInterlacingFieldOrder FieldOrder { get; set; } = VideoInterlacingFieldOrder.Undetermined;
+ public int StereoMode { get; set; }
+ public int AlphaMode { get; set; }
+ public int PixelWidth { get; set; }
+ public int PixelHeight { get; set; }
+ public int PixelCropBottom { get; set; }
+ public int PixelCropTop { get; set; }
+ public int PixelCropLeft { get; set; }
+ public int PixelCropRight { get; set; }
+ public int DisplayWidth { get; set; }
+ public int DisplayHeight { get; set; }
+ public VideoDisplayUnit DisplayUnit { get; set; } = VideoDisplayUnit.Pixels;
+ public VideoAspectRatioType AspectRatioType { get; set; } = VideoAspectRatioType.FreeResizing;
+ public byte[] UncompressedFourCC { get; set; }
+ public double GammaValue { get; set; }
+ public double FrameRate { get; set; }
+
+ public bool HasAudio { get; set; }
+ public double SamplingFrequency { get; set; }
+ public double OutputSamplingFrequency { get; set; }
+ public int Channels { get; set; }
+ public byte[] ChannelPositions { get; set; }
+ public int BitDepth { get; set; }
+
+ public class BlockAdditionMapping
+ {
+ public ulong BlockAddIDValue { get; set; }
+ public string BlockAddIDName { get; set; }
+ public ulong BlockAddIDType { get; set; }
+ public byte[] BlockAddIDExtraData { get; set; }
+ }
+
+ public class TrackTranslate
+ {
+ public byte[] TrackTranslateTrackID { get; set; }
+ public int TrackTranslateCodec { get; set; }
+ public byte[] TrackTranslateEditionUID { get; set; }
+ }
+
+ public enum VideoInterlacing
+ {
+ Undetermined = 0,
+ Interlaced = 1,
+ Progressive = 2,
+ }
+
+ public enum VideoInterlacingFieldOrder
+ {
+ Progressive = 0,
+ Tff = 1,
+ Undetermined = 2,
+ Bff = 6,
+ BffSwapped = 9,
+ TffSwapped = 14,
+ }
+
+ public enum VideoDisplayUnit
+ {
+ Pixels = 0,
+ Centimeters = 1,
+ Inches = 2,
+ DisplayAspectRatio = 3,
+ Unknown = 4
+ }
+
+ public enum VideoAspectRatioType
+ {
+ FreeResizing = 0,
+ KeepAspectRatio = 1,
+ Fixed = 2
+ }
+
+ public void CopyTo(MatroskaTrackEntry track, bool shallow = false)
+ {
+ track.TrackNumber = TrackNumber;
+ track.TrackUID = TrackUID;
+ track.TrackType = TrackType;
+ track.FlagEnabled = FlagEnabled;
+ track.FlagDefault = FlagDefault;
+ track.FlagForced = FlagForced;
+ track.FlagHearingImpaired = FlagHearingImpaired;
+ track.FlagVisualImpaired = FlagVisualImpaired;
+ track.FlagTextDescriptions = FlagTextDescriptions;
+ track.FlagOriginal = FlagOriginal;
+ track.FlagCommentary = FlagCommentary;
+ track.FlagLacing = FlagLacing;
+ track.MinCache = MinCache;
+ track.MaxCache = MaxCache;
+ track.DefaultDuration = DefaultDuration;
+ track.DefaultDecodedFieldDuration = DefaultDecodedFieldDuration;
+ track.MaxBlockAdditionID = MaxBlockAdditionID;
+ if (shallow) { track.BlockAdditionMappingEntries = BlockAdditionMappingEntries; }
+ else
+ {
+ track.BlockAdditionMappingEntries = null;
+ if (BlockAdditionMappingEntries != null)
+ {
+ track.BlockAdditionMappingEntries = BlockAdditionMappingEntries.Select(x => new BlockAdditionMapping()
+ {
+ BlockAddIDExtraData = x.BlockAddIDExtraData,
+ BlockAddIDName = x.BlockAddIDName,
+ BlockAddIDType = x.BlockAddIDType,
+ BlockAddIDValue = x.BlockAddIDValue
+ })
+ .ToList();
+ }
+ }
+ track.Name = Name;
+ track.Language = Language;
+ track.LanguageIETF = LanguageIETF;
+ track.CodecID = CodecID;
+ track.CodecPrivate = CodecPrivate;
+ track.CodecName = CodecName;
+ track.CodecDecodeAll = CodecDecodeAll;
+ if (shallow) { track.TrackOverlay = TrackOverlay; }
+ else { track.TrackOverlay = TrackOverlay?.ToList(); }
+ track.CodecDelay = CodecDelay;
+ track.SeekPreRoll = SeekPreRoll;
+ if (shallow) { track.TrackTranslateEntries = TrackTranslateEntries; }
+ else
+ {
+ track.TrackTranslateEntries = null;
+ if (TrackTranslateEntries != null)
+ {
+ track.TrackTranslateEntries = TrackTranslateEntries.Select(x => new TrackTranslate()
+ {
+ TrackTranslateCodec = x.TrackTranslateCodec,
+ TrackTranslateEditionUID = x.TrackTranslateEditionUID,
+ TrackTranslateTrackID = x.TrackTranslateTrackID
+ })
+ .ToList();
+ }
+ }
+ track.HasVideo = HasVideo;
+ track.FlagInterlaced = FlagInterlaced;
+ track.FieldOrder = FieldOrder;
+ track.StereoMode = StereoMode;
+ track.AlphaMode = AlphaMode;
+ track.PixelWidth = PixelWidth;
+ track.PixelHeight = PixelHeight;
+ track.PixelCropBottom = PixelCropBottom;
+ track.PixelCropTop = PixelCropTop;
+ track.PixelCropLeft = PixelCropLeft;
+ track.PixelCropRight = PixelCropRight;
+ track.DisplayWidth = DisplayWidth;
+ track.DisplayHeight = DisplayHeight;
+ track.DisplayUnit = DisplayUnit;
+ track.AspectRatioType = AspectRatioType;
+ track.UncompressedFourCC = UncompressedFourCC;
+ track.GammaValue = GammaValue;
+ track.FrameRate = FrameRate;
+ track.HasAudio = HasAudio;
+ track.SamplingFrequency = SamplingFrequency;
+ track.Channels = Channels;
+ track.ChannelPositions = ChannelPositions;
+ track.BitDepth = BitDepth;
+ }
+
+ public void ReadFrom(EBMLMasterElement element)
+ {
+ if (element == null) { return; }
+ if (element.Definition != MatroskaSpecification.TrackEntry) { return; }
+ foreach (var child in element.Children)
+ {
+ if (child.Definition == MatroskaSpecification.TrackNumber) { TrackNumber = (int)child.IntValue; }
+ else if (child.Definition == MatroskaSpecification.TrackUID) { TrackUID = child.UIntValue; }
+ else if (child.Definition == MatroskaSpecification.TrackType) { TrackType = (MatroskaTrackType)child.IntValue; }
+ else if (child.Definition == MatroskaSpecification.FlagEnabled) { FlagEnabled = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.FlagDefault) { FlagDefault = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.FlagForced) { FlagForced = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.FlagHearingImpaired) { FlagHearingImpaired = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.FlagVisualImpaired) { FlagVisualImpaired = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.FlagTextDescriptions) { FlagTextDescriptions = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.FlagOriginal) { FlagOriginal = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.FlagCommentary) { FlagCommentary = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.FlagLacing) { FlagLacing = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.MinCache) { MinCache = (int)child.IntValue; }
+ else if (child.Definition == MatroskaSpecification.MaxCache) { MaxCache = (int)child.IntValue; }
+ else if (child.Definition == MatroskaSpecification.DefaultDuration) { DefaultDuration = child.UIntValue; }
+ else if (child.Definition == MatroskaSpecification.DefaultDecodedFieldDuration) { DefaultDecodedFieldDuration = child.UIntValue; }
+ else if (child.Definition == MatroskaSpecification.MaxBlockAdditionID) { MaxBlockAdditionID = child.UIntValue; }
+ else if (child.Definition == MatroskaSpecification.BlockAdditionMapping)
+ {
+ var entry = new BlockAdditionMapping();
+ foreach (var sub in ((EBMLMasterElement)child).Children)
+ {
+ if (sub.Definition == MatroskaSpecification.BlockAddIDValue) { entry.BlockAddIDValue = child.UIntValue; }
+ else if (sub.Definition == MatroskaSpecification.BlockAddIDName) { entry.BlockAddIDName = child.StringValue; }
+ else if (sub.Definition == MatroskaSpecification.BlockAddIDType) { entry.BlockAddIDType = child.UIntValue; }
+ else if (sub.Definition == MatroskaSpecification.BlockAddIDExtraData) { entry.BlockAddIDExtraData = (child as EBMLBinaryElement).Value.ToArray(); }
+ }
+ if (BlockAdditionMappingEntries == null) { BlockAdditionMappingEntries = new List<BlockAdditionMapping>(); }
+ BlockAdditionMappingEntries.Add(entry);
+ }
+ else if (child.Definition == MatroskaSpecification.Name) { Name = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.Language) { Language = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.LanguageIETF) { LanguageIETF = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.CodecID) { CodecID = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.CodecPrivate) { CodecPrivate = (child as EBMLBinaryElement).Value.ToArray(); }
+ else if (child.Definition == MatroskaSpecification.CodecName) { CodecName = child.StringValue; }
+ else if (child.Definition == MatroskaSpecification.CodecDecodeAll) { CodecDecodeAll = child.IntValue != 0; }
+ else if (child.Definition == MatroskaSpecification.TrackOverlay)
+ {
+ if (TrackOverlay == null) { TrackOverlay = new List<int>(); }
+ TrackOverlay.Add((int)child.IntValue);
+ }
+ else if (child.Definition == MatroskaSpecification.CodecDelay) { CodecDelay = (int)child.IntValue; }
+ else if (child.Definition == MatroskaSpecification.SeekPreRoll) { SeekPreRoll = (int)child.IntValue; }
+ else if (child.Definition == MatroskaSpecification.TrackTranslate)
+ {
+ var entry = new TrackTranslate();
+ foreach (var sub in ((EBMLMasterElement)child).Children)
+ {
+ if (sub.Definition == MatroskaSpecification.TrackTranslateTrackID) { entry.TrackTranslateTrackID = (sub as EBMLBinaryElement).Value.ToArray(); }
+ else if (sub.Definition == MatroskaSpecification.TrackTranslateCodec) { entry.TrackTranslateCodec = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.TrackTranslateEditionUID) { entry.TrackTranslateEditionUID = (sub as EBMLBinaryElement).Value.ToArray(); }
+ }
+ if (TrackTranslateEntries == null) { TrackTranslateEntries = new List<TrackTranslate>(); }
+ TrackTranslateEntries.Add(entry);
+ }
+ else if (child.Definition == MatroskaSpecification.Video)
+ {
+ HasVideo = true;
+ foreach (var sub in ((EBMLMasterElement)child).Children)
+ {
+ if (sub.Definition == MatroskaSpecification.FlagInterlaced) { FlagInterlaced = (VideoInterlacing)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.FieldOrder) { FieldOrder = (VideoInterlacingFieldOrder)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.StereoMode) { StereoMode = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.AlphaMode) { AlphaMode = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.PixelWidth) { PixelWidth = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.PixelHeight) { PixelHeight = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.PixelCropBottom) { PixelCropBottom = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.PixelCropTop) { PixelCropTop = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.PixelCropLeft) { PixelCropLeft = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.PixelCropRight) { PixelCropRight = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.DisplayWidth) { DisplayWidth = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.DisplayHeight) { DisplayHeight = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.DisplayUnit) { DisplayUnit = (VideoDisplayUnit)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.AspectRatioType) { AspectRatioType = (VideoAspectRatioType)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.UncompressedFourCC) { UncompressedFourCC = (sub as EBMLBinaryElement).Value.ToArray(); }
+ else if (sub.Definition == MatroskaSpecification.GammaValue) { GammaValue = sub.DoubleValue; }
+ else if (sub.Definition == MatroskaSpecification.FrameRate) { FrameRate = sub.DoubleValue; }
+ else if (sub.Definition == MatroskaSpecification.Colour)
+ {
+ /* ... */
+ }
+ else if (sub.Definition == MatroskaSpecification.Projection)
+ {
+ /* ... */
+ }
+ }
+ }
+ else if (child.Definition == MatroskaSpecification.Audio)
+ {
+ HasAudio = true;
+ foreach (var sub in ((EBMLMasterElement)child).Children)
+ {
+ if (sub.Definition == MatroskaSpecification.SamplingFrequency) { SamplingFrequency = sub.DoubleValue; }
+ else if (sub.Definition == MatroskaSpecification.OutputSamplingFrequency) { OutputSamplingFrequency = sub.DoubleValue; }
+ else if (sub.Definition == MatroskaSpecification.Channels) { Channels = (int)sub.IntValue; }
+ else if (sub.Definition == MatroskaSpecification.ChannelPositions) { ChannelPositions = (sub as EBMLBinaryElement).Value.ToArray(); }
+ else if (sub.Definition == MatroskaSpecification.BitDepth) { BitDepth = (int)sub.IntValue; }
+ }
+ }
+ else if (child.Definition == MatroskaSpecification.TrackOperation)
+ {
+ /* ... */
+ }
+ else if (child.Definition == MatroskaSpecification.ContentEncodings)
+ {
+ /* ... */
+ }
+ }
+ }
+
+ public async ValueTask Write(EBMLWriter writer, CancellationToken cancellationToken = default)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.TrackEntry, cancellationToken);
+ await writer.WriteUnsignedInteger(MatroskaSpecification.TrackNumber, (ulong)TrackNumber, cancellationToken);
+ await writer.WriteUnsignedInteger(MatroskaSpecification.TrackUID, TrackUID, cancellationToken);
+ await writer.WriteUnsignedInteger(MatroskaSpecification.TrackType, (ulong)TrackType, cancellationToken);
+ if (!FlagEnabled) { await writer.WriteUnsignedInteger(MatroskaSpecification.FlagEnabled, 0, cancellationToken); }
+ if (!FlagDefault) { await writer.WriteUnsignedInteger(MatroskaSpecification.FlagDefault, 0, cancellationToken); }
+ if (FlagForced) { await writer.WriteUnsignedInteger(MatroskaSpecification.FlagForced, 1, cancellationToken); }
+ if (FlagHearingImpaired) { await writer.WriteUnsignedInteger(MatroskaSpecification.FlagHearingImpaired, 1, cancellationToken); }
+ if (FlagVisualImpaired) { await writer.WriteUnsignedInteger(MatroskaSpecification.FlagVisualImpaired, 1, cancellationToken); }
+ if (FlagTextDescriptions) { await writer.WriteUnsignedInteger(MatroskaSpecification.FlagTextDescriptions, 1, cancellationToken); }
+ if (FlagOriginal) { await writer.WriteUnsignedInteger(MatroskaSpecification.FlagOriginal, 1, cancellationToken); }
+ if (FlagCommentary) { await writer.WriteUnsignedInteger(MatroskaSpecification.FlagCommentary, 1, cancellationToken); }
+ await writer.WriteUnsignedInteger(MatroskaSpecification.FlagLacing, FlagLacing ? 1UL : 0UL, cancellationToken);
+ if (MinCache > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.MinCache, (ulong)MinCache, cancellationToken); }
+ if (MaxCache > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.MaxCache, (ulong)MaxCache, cancellationToken); }
+ if (DefaultDuration > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.DefaultDuration, DefaultDuration, cancellationToken); }
+ if (DefaultDecodedFieldDuration > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.DefaultDecodedFieldDuration, DefaultDecodedFieldDuration, cancellationToken); }
+ if (MaxBlockAdditionID > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.MaxBlockAdditionID, MaxBlockAdditionID, cancellationToken); }
+ if (BlockAdditionMappingEntries != null && BlockAdditionMappingEntries.Count > 0)
+ {
+ for (int i = 0; i < BlockAdditionMappingEntries.Count; i++)
+ {
+ var entry = BlockAdditionMappingEntries[i];
+ await writer.BeginMasterElement(MatroskaSpecification.BlockAdditionMapping, cancellationToken);
+ if (entry.BlockAddIDValue > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.BlockAddIDValue, entry.BlockAddIDValue, cancellationToken); }
+ if (entry.BlockAddIDName != null) { await writer.WriteString(MatroskaSpecification.BlockAddIDName, entry.BlockAddIDName, cancellationToken); }
+ await writer.WriteUnsignedInteger(MatroskaSpecification.BlockAddIDType, entry.BlockAddIDType, cancellationToken);
+ if (entry.BlockAddIDExtraData != null) { await writer.WriteBinary(MatroskaSpecification.BlockAddIDExtraData, entry.BlockAddIDExtraData, cancellationToken); }
+ await writer.EndMasterElement(cancellationToken);
+ }
+ }
+ if (Name != null) { await writer.WriteString(MatroskaSpecification.Name, Name, cancellationToken); }
+ if (Language != null) { await writer.WriteString(MatroskaSpecification.Language, Language, cancellationToken); }
+ if (LanguageIETF != null) { await writer.WriteString(MatroskaSpecification.LanguageIETF, LanguageIETF, cancellationToken); }
+ if (CodecID != null) { await writer.WriteString(MatroskaSpecification.CodecID, CodecID, cancellationToken); }
+ if (CodecPrivate != null) { await writer.WriteBinary(MatroskaSpecification.CodecPrivate, CodecPrivate, cancellationToken); }
+ if (CodecName != null) { await writer.WriteString(MatroskaSpecification.CodecName, CodecName, cancellationToken); }
+ if (!CodecDecodeAll) { await writer.WriteUnsignedInteger(MatroskaSpecification.CodecDecodeAll, 0, cancellationToken); }
+ if (TrackOverlay != null && TrackOverlay.Count > 0)
+ {
+ for (int i = 0; i < TrackOverlay.Count; i++)
+ {
+ await writer.WriteUnsignedInteger(MatroskaSpecification.TrackOverlay, (ulong)TrackOverlay[i], cancellationToken);
+ }
+ }
+ if (CodecDelay > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.CodecDelay, (ulong)CodecDelay, cancellationToken); }
+ if (SeekPreRoll > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.SeekPreRoll, (ulong)SeekPreRoll, cancellationToken); }
+ if (TrackTranslateEntries != null && TrackTranslateEntries.Count > 0)
+ {
+ for (int i = 0; i < TrackTranslateEntries.Count; i++)
+ {
+ var entry = TrackTranslateEntries[i];
+ await writer.BeginMasterElement(MatroskaSpecification.TrackTranslate, cancellationToken);
+ if (entry.TrackTranslateEditionUID != null) { await writer.WriteBinary(MatroskaSpecification.TrackTranslateEditionUID, entry.TrackTranslateEditionUID, cancellationToken); }
+ await writer.WriteUnsignedInteger(MatroskaSpecification.TrackTranslateCodec, (ulong)entry.TrackTranslateCodec, cancellationToken);
+ await writer.WriteBinary(MatroskaSpecification.TrackTranslateTrackID, entry.TrackTranslateTrackID, cancellationToken);
+ await writer.EndMasterElement(cancellationToken);
+ }
+ }
+ if (HasVideo)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.Video, cancellationToken);
+ if (FlagInterlaced != VideoInterlacing.Undetermined) { await writer.WriteUnsignedInteger(MatroskaSpecification.FlagInterlaced, (ulong)FlagInterlaced, cancellationToken); }
+ if (FieldOrder != VideoInterlacingFieldOrder.Undetermined) { await writer.WriteUnsignedInteger(MatroskaSpecification.FieldOrder, (ulong)FieldOrder, cancellationToken); }
+ if (StereoMode > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.StereoMode, (ulong)StereoMode, cancellationToken); }
+ if (AlphaMode > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.AlphaMode, (ulong)AlphaMode, cancellationToken); }
+ await writer.WriteUnsignedInteger(MatroskaSpecification.PixelWidth, (ulong)PixelWidth, cancellationToken);
+ await writer.WriteUnsignedInteger(MatroskaSpecification.PixelHeight, (ulong)PixelHeight, cancellationToken);
+ if (PixelCropBottom > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.PixelCropBottom, (ulong)PixelCropBottom, cancellationToken); }
+ if (PixelCropTop > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.PixelCropTop, (ulong)PixelCropTop, cancellationToken); }
+ if (PixelCropLeft > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.PixelCropLeft, (ulong)PixelCropLeft, cancellationToken); }
+ if (PixelCropRight > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.PixelCropRight, (ulong)PixelCropRight, cancellationToken); }
+ if (DisplayWidth > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.DisplayWidth, (ulong)DisplayWidth, cancellationToken); }
+ if (DisplayHeight > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.DisplayHeight, (ulong)DisplayHeight, cancellationToken); }
+ if (DisplayUnit != VideoDisplayUnit.Pixels) { await writer.WriteUnsignedInteger(MatroskaSpecification.DisplayUnit, (ulong)DisplayUnit, cancellationToken); }
+ if (AspectRatioType != VideoAspectRatioType.FreeResizing) { await writer.WriteUnsignedInteger(MatroskaSpecification.AspectRatioType, (ulong)AspectRatioType, cancellationToken); }
+ if (UncompressedFourCC != null) { await writer.WriteBinary(MatroskaSpecification.UncompressedFourCC, UncompressedFourCC, cancellationToken); }
+ if (GammaValue > 0) { await writer.WriteFloat(MatroskaSpecification.GammaValue, GammaValue, cancellationToken); }
+ if (FrameRate > 0) { await writer.WriteFloat(MatroskaSpecification.FrameRate, FrameRate, cancellationToken); }
+ await writer.EndMasterElement(cancellationToken);
+ }
+ if (HasAudio)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.Audio, cancellationToken);
+ await writer.WriteFloat(MatroskaSpecification.SamplingFrequency, SamplingFrequency, cancellationToken);
+ if (OutputSamplingFrequency > 0) { await writer.WriteFloat(MatroskaSpecification.OutputSamplingFrequency, OutputSamplingFrequency, cancellationToken); }
+ await writer.WriteUnsignedInteger(MatroskaSpecification.Channels, (ulong)Channels, cancellationToken);
+ if (ChannelPositions != null) { await writer.WriteBinary(MatroskaSpecification.ChannelPositions, ChannelPositions, cancellationToken); }
+ if (BitDepth > 0) { await writer.WriteUnsignedInteger(MatroskaSpecification.BitDepth, (ulong)BitDepth, cancellationToken); }
+ await writer.EndMasterElement(cancellationToken);
+ }
+ await writer.EndMasterElement(cancellationToken);
+ }
+
+ public EBMLMasterElement ToElement()
+ {
+ var tracks = new EBMLMasterElement(MatroskaSpecification.TrackEntry);
+ tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TrackNumber, (ulong)TrackNumber));
+ tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TrackUID, TrackUID));
+ tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TrackType, (ulong)TrackType));
+ if (!FlagEnabled) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FlagEnabled, 0)); }
+ if (!FlagDefault) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FlagDefault, 0)); }
+ if (FlagForced) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FlagForced, 1)); }
+ if (FlagHearingImpaired) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FlagHearingImpaired, 1)); }
+ if (FlagVisualImpaired) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FlagVisualImpaired, 1)); }
+ if (FlagTextDescriptions) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FlagTextDescriptions, 1)); }
+ if (FlagOriginal) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FlagOriginal, 1)); }
+ if (FlagCommentary) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FlagCommentary, 1)); }
+ tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FlagLacing, FlagLacing ? 1UL : 0UL));
+ if (MinCache > 0) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.MinCache, (ulong)MinCache)); }
+ if (MaxCache > 0) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.MaxCache, (ulong)MaxCache)); }
+ if (DefaultDuration > 0) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.DefaultDuration, DefaultDuration)); }
+ if (DefaultDecodedFieldDuration > 0) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.DefaultDecodedFieldDuration, DefaultDecodedFieldDuration)); }
+ if (MaxBlockAdditionID > 0) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.MaxBlockAdditionID, MaxBlockAdditionID)); }
+ if (BlockAdditionMappingEntries != null && BlockAdditionMappingEntries.Count > 0)
+ {
+ foreach (var entry in BlockAdditionMappingEntries)
+ {
+ var mapping = new EBMLMasterElement(MatroskaSpecification.BlockAdditionMapping);
+ if (entry.BlockAddIDValue > 0) { mapping.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.BlockAddIDValue, entry.BlockAddIDValue)); }
+ if (entry.BlockAddIDName != null) { mapping.AddChild(new EBMLStringElement(MatroskaSpecification.BlockAddIDName, entry.BlockAddIDName)); }
+ mapping.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.BlockAddIDType, entry.BlockAddIDType));
+ if (entry.BlockAddIDExtraData != null) { mapping.AddChild(new EBMLBinaryElement(MatroskaSpecification.BlockAddIDExtraData, entry.BlockAddIDExtraData)); }
+ tracks.AddChild(mapping);
+ }
+ }
+ if (Name != null) { tracks.AddChild(new EBMLStringElement(MatroskaSpecification.Name, Name)); }
+ if (Language != null) { tracks.AddChild(new EBMLStringElement(MatroskaSpecification.Language, Language)); }
+ if (LanguageIETF != null) { tracks.AddChild(new EBMLStringElement(MatroskaSpecification.LanguageIETF, LanguageIETF)); }
+ if (CodecID != null) { tracks.AddChild(new EBMLStringElement(MatroskaSpecification.CodecID, CodecID)); }
+ if (CodecPrivate != null) { tracks.AddChild(new EBMLBinaryElement(MatroskaSpecification.CodecPrivate, CodecPrivate)); }
+ if (CodecName != null) { tracks.AddChild(new EBMLStringElement(MatroskaSpecification.CodecName, CodecName)); }
+ if (!CodecDecodeAll) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.CodecDecodeAll, 0)); }
+ if (TrackOverlay != null && TrackOverlay.Count > 0)
+ {
+ foreach (var entry in TrackOverlay)
+ {
+ tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TrackOverlay, (ulong)entry));
+ }
+ }
+ if (CodecDelay > 0) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.CodecDelay, (ulong)CodecDelay)); }
+ if (SeekPreRoll > 0) { tracks.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.SeekPreRoll, (ulong)SeekPreRoll)); }
+ if (TrackTranslateEntries != null && TrackTranslateEntries.Count > 0)
+ {
+ foreach (var entry in TrackTranslateEntries)
+ {
+ var translate = new EBMLMasterElement(MatroskaSpecification.TrackTranslate);
+ if (entry.TrackTranslateEditionUID != null) { translate.AddChild(new EBMLBinaryElement(MatroskaSpecification.TrackTranslateEditionUID, entry.TrackTranslateEditionUID)); }
+ translate.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.TrackTranslateCodec, (ulong)entry.TrackTranslateCodec));
+ translate.AddChild(new EBMLBinaryElement(MatroskaSpecification.TrackTranslateTrackID, entry.TrackTranslateTrackID));
+ tracks.AddChild(translate);
+ }
+ }
+ if (HasVideo)
+ {
+ var video = new EBMLMasterElement(MatroskaSpecification.Video);
+ if (FlagInterlaced != VideoInterlacing.Undetermined) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FlagInterlaced, (ulong)FlagInterlaced)); }
+ if (FieldOrder != VideoInterlacingFieldOrder.Undetermined) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.FieldOrder, (ulong)FieldOrder)); }
+ if (StereoMode > 0) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.StereoMode, (ulong)StereoMode)); }
+ if (AlphaMode > 0) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.AlphaMode, (ulong)AlphaMode)); }
+ video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.PixelWidth, (ulong)PixelWidth));
+ video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.PixelHeight, (ulong)PixelHeight));
+ if (PixelCropBottom > 0) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.PixelCropBottom, (ulong)PixelCropBottom)); }
+ if (PixelCropTop > 0) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.PixelCropTop, (ulong)PixelCropTop)); }
+ if (PixelCropLeft > 0) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.PixelCropLeft, (ulong)PixelCropLeft)); }
+ if (PixelCropRight > 0) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.PixelCropRight, (ulong)PixelCropRight)); }
+ if (DisplayWidth > 0) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.DisplayWidth, (ulong)DisplayWidth)); }
+ if (DisplayHeight > 0) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.DisplayHeight, (ulong)DisplayHeight)); }
+ if (DisplayUnit != VideoDisplayUnit.Pixels) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.DisplayUnit, (ulong)DisplayUnit)); }
+ if (AspectRatioType != VideoAspectRatioType.FreeResizing) { video.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.AspectRatioType, (ulong)AspectRatioType)); }
+ if (UncompressedFourCC != null) { video.AddChild(new EBMLBinaryElement(MatroskaSpecification.UncompressedFourCC, UncompressedFourCC)); }
+ if (GammaValue > 0) { video.AddChild(new EBMLFloatElement(MatroskaSpecification.GammaValue, GammaValue)); }
+ if (FrameRate > 0) { video.AddChild(new EBMLFloatElement(MatroskaSpecification.FrameRate, FrameRate)); }
+ tracks.AddChild(video);
+ }
+ if (HasAudio)
+ {
+ var audio = new EBMLMasterElement(MatroskaSpecification.Audio);
+ audio.AddChild(new EBMLFloatElement(MatroskaSpecification.SamplingFrequency, SamplingFrequency));
+ if (OutputSamplingFrequency > 0) { audio.AddChild(new EBMLFloatElement(MatroskaSpecification.OutputSamplingFrequency, OutputSamplingFrequency)); }
+ audio.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.Channels, (ulong)Channels));
+ if (ChannelPositions != null) { audio.AddChild(new EBMLBinaryElement(MatroskaSpecification.ChannelPositions, ChannelPositions)); }
+ if (BitDepth > 0) { audio.AddChild(new EBMLUnsignedIntegerElement(MatroskaSpecification.BitDepth, (ulong)BitDepth)); }
+ tracks.AddChild(audio);
+ }
+ return tracks;
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaTrackType.cs b/MediaContainers.Matroska/Matroska/MatroskaTrackType.cs
new file mode 100644
index 0000000..335ac65
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaTrackType.cs
@@ -0,0 +1,14 @@
+namespace MediaContainers.Matroska;
+
+public enum MatroskaTrackType
+{
+ Unknown = 0,
+ Video = 1,
+ Audio = 2,
+ Complex = 3,
+ Logo = 16,
+ Subtitle = 17,
+ Buttons = 18,
+ Control = 32,
+ Metadata = 33,
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaTracks.cs b/MediaContainers.Matroska/Matroska/MatroskaTracks.cs
new file mode 100644
index 0000000..789fda4
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaTracks.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaTracks : List<MatroskaTrackEntry>
+{
+ public void AddTrackEntry(EBMLMasterElement element)
+ {
+ if (element == null) { return; }
+ if (element.Definition == MatroskaSpecification.Tracks)
+ {
+ foreach (var child in element.Children)
+ {
+ AddTrackEntry(child as EBMLMasterElement);
+ }
+ }
+ else if (element.Definition == MatroskaSpecification.TrackEntry)
+ {
+ var entry = new MatroskaTrackEntry();
+ entry.ReadFrom(element);
+ Add(entry);
+ }
+ }
+
+ public void CopyTo(MatroskaTracks tracks, bool shallow = false)
+ {
+ if (shallow)
+ {
+ tracks.Clear();
+ for (int i = 0, j = Count; i < j; i++) { tracks.Add(this[i]); }
+ }
+ else
+ {
+ tracks.Clear();
+ for (int i = 0, j = Count; i < j; i++)
+ {
+ var track = new MatroskaTrackEntry();
+ this[i].CopyTo(track);
+ tracks.Add(track);
+ }
+ }
+ }
+
+ public async ValueTask Write(EBMLWriter writer, CancellationToken cancellationToken = default)
+ {
+ await writer.BeginMasterElement(MatroskaSpecification.Tracks, cancellationToken);
+ for (int i = 0; i < Count; i++) { await this[i].Write(writer, cancellationToken); }
+ await writer.EndMasterElement(cancellationToken);
+ }
+
+ public EBMLMasterElement ToElement()
+ {
+ var tracks = new EBMLMasterElement(MatroskaSpecification.Tracks);
+ foreach (var track in this) { tracks.AddChild(track.ToElement()); }
+ return tracks;
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/MatroskaWriter.cs b/MediaContainers.Matroska/Matroska/MatroskaWriter.cs
new file mode 100644
index 0000000..be3f3ae
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/MatroskaWriter.cs
@@ -0,0 +1,349 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers.Matroska;
+
+public class MatroskaWriter : IDisposable, IAsyncDisposable
+{
+ public static readonly EBMLDocType DocTypeMatroska = new EBMLDocType("matroska", 4, 2);
+ public static readonly EBMLDocType DocTypeWebM = new EBMLDocType("webm", 4, 2);
+
+ private static readonly EBMLDocType[] docTypes = new EBMLDocType[]
+ {
+ DocTypeMatroska,
+ DocTypeWebM,
+ };
+
+ private readonly DataBufferCache cache;
+ private readonly EBMLDocumentWriter document;
+ private readonly MatroskaInfo info = new();
+ private readonly MatroskaTracks tracks = new();
+ private readonly MatroskaTags tags = new();
+ private readonly DataQueueMemoryReaderMutable memoryReader = new();
+ private readonly DataBuffer writeBuffer = new(null, new byte[32]);
+ private MatroskaTrackEntry[] trackByNumber;
+ private MatroskaCues cues;
+ private bool writtenHeaders;
+ private bool writtenSeekInfo;
+ private EBMLWriter.RelativeOffset seekHeadOffset;
+ private EBMLWriter.RelativeOffset infoOffset;
+ private EBMLWriter.RelativeOffset tracksOffset;
+ private EBMLWriter.RelativeOffset tagsOffset;
+ private EBMLWriter.RelativeOffset clusterOffset;
+ private EBMLWriter.RelativeOffset durationOffset;
+ private Queue<CueMark> clusterOffsets;
+ private bool streamingMode;
+ private int minTrackNumber;
+ private int cueTrackNumber;
+ private bool hasClusterCue;
+ private bool firstCluster;
+ private long firstClusterTimestampOffset;
+ private bool firstBlock;
+ private long lastClusterTimestamp;
+ private long maxFrameTimestamp;
+ private long lastVideoTimestamp;
+ private long defaultDurationTimestamp;
+ private int maxClusterSize = (1 << 21) - 1;
+ private double timestampScaleSec;
+ private volatile bool disposed;
+
+ public virtual EBMLDocType[] SupportedDocTypes => docTypes;
+ public EBMLDocumentWriter Document => document;
+ public MatroskaInfo Info => info;
+ public MatroskaTracks Tracks => tracks;
+ public MatroskaTags Tags => tags;
+
+ public long CalculatedDurationTimestamp => maxFrameTimestamp + defaultDurationTimestamp;
+
+ public bool StreamingMode { get { return streamingMode; } set { document.Writer.StreamingMode = streamingMode = value; } }
+ public bool AutoCalculateDuration { get; set; }
+ public bool DropAudioToSyncVideo { get; set; }
+ public double DropAudioMaxDelaySeconds { get; set; } = 1;
+
+ private bool useCalculatedDuration => AutoCalculateDuration || DropAudioToSyncVideo;
+
+ private struct CueMark
+ {
+ public readonly long Timestamp;
+ public readonly EBMLWriter.RelativeOffset ClusterOffset;
+ public readonly EBMLWriter.RelativeOffset BlockOffset;
+
+ public CueMark(long timestamp, EBMLWriter.RelativeOffset clusterOffset, EBMLWriter.RelativeOffset blockOffset)
+ {
+ Timestamp = timestamp;
+ ClusterOffset = clusterOffset;
+ BlockOffset = blockOffset;
+ }
+ }
+
+ public MatroskaWriter(EBMLDocumentWriter doc, DataBufferCache cache = null)
+ {
+ if (doc == null) { throw new ArgumentNullException(nameof(doc)); }
+ var docTypes = SupportedDocTypes;
+ var canWrite = false;
+ for (int i = docTypes.Length - 1; i >= 0; i--)
+ {
+ if (doc.CanBeWrittenBy(docTypes[i])) { canWrite = true; break; }
+ }
+ if (!canWrite)
+ {
+ throw new ArgumentException("Unsupported DocType: " + doc.Header.DocType + " " + doc.Header.DocTypeVersion, nameof(doc));
+ }
+ document = doc;
+ this.cache = cache ?? DataBufferCache.DefaultCache;
+ }
+
+ public static async ValueTask<MatroskaWriter> Write(IDataQueueWriter writer, bool keepWriterOpen = false,
+ DataBufferCache cache = null, EBMLDocType format = default, CancellationToken cancellationToken = default)
+ {
+ if (format.DocType == null) { format = DocTypeMatroska; }
+ var header = new EBMLHeader()
+ {
+ DocType = format.DocType,
+ DocTypeVersion = format.WriteVersion,
+ DocTypeReadVersion = format.ReadVersion
+ };
+ var doc = await EBMLDocumentWriter.Write(header, writer, keepWriterOpen, cache, cancellationToken);
+ return new MatroskaWriter(doc, cache);
+ }
+
+ public static async ValueTask<MatroskaWriter> Write(Stream stream, bool keepStreamOpen = false,
+ DataBufferCache cache = null, EBMLDocType format = default, CancellationToken cancellationToken = default)
+ {
+ if (format.DocType == null) { format = DocTypeMatroska; }
+ var header = new EBMLHeader()
+ {
+ DocType = format.DocType,
+ DocTypeVersion = format.WriteVersion,
+ DocTypeReadVersion = format.ReadVersion
+ };
+ var doc = await EBMLDocumentWriter.Write(header, stream, keepStreamOpen, cache, cancellationToken);
+ return new MatroskaWriter(doc, cache);
+ }
+
+ public static bool CanWriteDocument(EBMLDocumentWriter doc)
+ {
+ for (int i = docTypes.Length - 1; i >= 0; i--)
+ {
+ if (doc.CanBeWrittenBy(docTypes[i])) { return true; }
+ }
+ return false;
+ }
+
+ public async ValueTask WriteTrackInfo(CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(MatroskaWriter)); }
+ if (writtenHeaders) { return; }
+ if (tracks.Count == 0) { throw new InvalidOperationException("Need to add at least one track."); }
+ writtenHeaders = true;
+ var canSeek = document.Writer.CanSeek && !streamingMode;
+ await document.Writer.BeginMasterElement(MatroskaSpecification.Segment, cancellationToken);
+ if (canSeek)
+ {
+ seekHeadOffset = document.Writer.MarkRelativeOffset();
+ await document.Writer.WriteVoid(126, cancellationToken);
+ infoOffset = document.Writer.MarkRelativeOffset(MatroskaSpecification.Segment);
+ if (AutoCalculateDuration && info.Duration <= 0) { info.Duration = 1; }
+ }
+ if (streamingMode) { info.Duration = 0; }
+ durationOffset = await info.Write(document.Writer, cancellationToken);
+ if (canSeek) { tracksOffset = document.Writer.MarkRelativeOffset(MatroskaSpecification.Segment); }
+ await tracks.Write(document.Writer, cancellationToken);
+ if (canSeek) { tagsOffset = document.Writer.MarkRelativeOffset(MatroskaSpecification.Segment); }
+ await tags.Write(document.Writer, cancellationToken);
+ if (!streamingMode) { await document.Writer.WriteVoid(126, cancellationToken); }
+ timestampScaleSec = info.TimestampScale / 1000000000.0;
+
+ int maxTrackNumber = tracks[0].TrackNumber;
+ defaultDurationTimestamp = 0;
+ minTrackNumber = maxTrackNumber;
+ cueTrackNumber = -1;
+ for (int i = tracks.Count - 1; i >= 0; i--)
+ {
+ if (tracks[i].HasAudio || tracks[i].TrackType == MatroskaTrackType.Video) { cueTrackNumber = tracks[i].TrackNumber; }
+ if (maxTrackNumber < tracks[i].TrackNumber) { maxTrackNumber = tracks[i].TrackNumber; }
+ if (minTrackNumber > tracks[i].TrackNumber) { minTrackNumber = tracks[i].TrackNumber; }
+ if (defaultDurationTimestamp < (long)tracks[i].DefaultDuration) { defaultDurationTimestamp = (long)tracks[i].DefaultDuration; }
+ }
+ defaultDurationTimestamp = defaultDurationTimestamp / (long)info.TimestampScale;
+ if (defaultDurationTimestamp <= 0) { defaultDurationTimestamp = 33333333 / (long)info.TimestampScale; }
+ if (cueTrackNumber < 0) { cueTrackNumber = tracks[0].TrackNumber; }
+ var range = maxTrackNumber + 1 - minTrackNumber;
+ if (range > 256) { trackByNumber = null; }
+ else
+ {
+ trackByNumber = new MatroskaTrackEntry[range];
+ for (int i = tracks.Count - 1; i >= 0; i--)
+ {
+ trackByNumber[tracks[i].TrackNumber - minTrackNumber] = tracks[i];
+ }
+ }
+ }
+
+ public async ValueTask WriteSeekInfo(CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(MatroskaWriter)); }
+ if (writtenSeekInfo) { return; }
+ writtenSeekInfo = true;
+ while (document.Writer.CurrentContainer != MatroskaSpecification.Segment)
+ {
+ await document.Writer.EndMasterElement(cancellationToken);
+ }
+ if (document.Writer.CanSeek && !streamingMode)
+ {
+ EBMLWriter.RelativeOffset cuesOffset = default;
+ WriteClusterOffset();
+ if (cues != null)
+ {
+ cuesOffset = document.Writer.MarkRelativeOffset(MatroskaSpecification.Segment);
+ await cues.Write(document.Writer, cancellationToken);
+ }
+ var seekHeadResolved = seekHeadOffset.Resolve();
+ if (seekHeadResolved != null)
+ {
+ var seekHead = new MatroskaSeekHead();
+ var offset = infoOffset.Resolve(); if (offset != null) { seekHead.AddSeekIndex(MatroskaSpecification.Info, offset.Value); }
+ offset = tracksOffset.Resolve(); if (offset != null) { seekHead.AddSeekIndex(MatroskaSpecification.Tracks, offset.Value); }
+ offset = tagsOffset.Resolve(); if (offset != null) { seekHead.AddSeekIndex(MatroskaSpecification.Tags, offset.Value); }
+ offset = cuesOffset.Resolve(); if (offset != null) { seekHead.AddSeekIndex(MatroskaSpecification.Cues, offset.Value); }
+ using (var seeked = await document.Writer.SeekWriter(seekHeadResolved.Value, SeekOrigin.Begin, cancellationToken))
+ {
+ var written = seeked.CurrentContainerSize;
+ await seekHead.Write(seeked, cancellationToken);
+ written = seeked.CurrentContainerSize - written;
+ await seeked.WriteVoid(126 - (int)written, cancellationToken);
+ await seeked.FlushInternalBuffer(cancellationToken);
+ }
+ }
+ if (useCalculatedDuration)
+ {
+ var durationOffsetResolved = durationOffset.Resolve();
+ if (durationOffsetResolved != null)
+ {
+ using (var seeked = await document.Writer.SeekWriter(durationOffsetResolved.Value, SeekOrigin.Begin, cancellationToken))
+ {
+ await seeked.WriteFloat(MatroskaSpecification.Duration, (double)CalculatedDurationTimestamp, cancellationToken);
+ await seeked.FlushInternalBuffer(cancellationToken);
+ }
+ }
+ }
+ }
+ }
+
+ private void WriteClusterOffset()
+ {
+ if (clusterOffsets == null) { return; }
+ while (clusterOffsets.Count > 0)
+ {
+ var mark = clusterOffsets.Peek();
+ var offset = mark.ClusterOffset.Resolve();
+ if (offset == null) { return; }
+ clusterOffsets.Dequeue();
+ if (cues == null) { cues = new MatroskaCues(); }
+ var point = new MatroskaCuePoint() { Timestamp = (ulong)mark.Timestamp };
+ point.Add(new MatroskaCueTrackPosition()
+ {
+ CueTrack = cueTrackNumber,
+ CueClusterPosition = (ulong)offset.Value,
+ CueRelativePosition = (ulong)(mark.BlockOffset.Resolve() ?? 0)
+ });
+ cues.Add(point);
+ }
+ }
+
+ public async ValueTask WriteFrame(MatroskaFrame frame, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(MatroskaWriter)); }
+ if (!writtenHeaders) { await WriteTrackInfo(cancellationToken); }
+ MatroskaTrackEntry trackEntry = frame.Track;
+ if (trackEntry == null)
+ {
+ if (trackByNumber != null)
+ {
+ var idx = frame.TrackIndex - minTrackNumber;
+ if (idx < trackByNumber.Length) { trackEntry = trackByNumber[idx]; }
+ }
+ else
+ {
+ for (int idx = tracks.Count - 1; idx >= 0; idx--)
+ {
+ if (tracks[idx].TrackNumber == frame.TrackIndex) { trackEntry = tracks[idx]; break; }
+ }
+ }
+ if (trackEntry == null) { throw new ArgumentNullException("Track"); }
+ }
+ var trackIndex = trackEntry.TrackNumber;
+ var delta = frame.Timestamp - lastClusterTimestamp;
+
+ if (document.Writer.CurrentContainer == MatroskaSpecification.Cluster)
+ {
+ if (delta < -32768 || delta >= 32768 ||
+ (document.Writer.CurrentContainerSize + frame.Buffer.UnreadSize + 16) > maxClusterSize ||
+ (delta * timestampScaleSec) >= 5)
+ {
+ await document.Writer.EndMasterElement(cancellationToken);
+ WriteClusterOffset();
+ }
+ }
+
+ if (DropAudioToSyncVideo)
+ {
+ if (trackEntry.HasVideo || trackEntry.TrackType == MatroskaTrackType.Video) { lastVideoTimestamp = frame.Timestamp; }
+ else if ((frame.Timestamp - lastVideoTimestamp) * timestampScaleSec >= DropAudioMaxDelaySeconds) { return; }
+ }
+
+ if (document.Writer.CurrentContainer != MatroskaSpecification.Cluster)
+ {
+ if (!firstCluster) { firstCluster = true; firstClusterTimestampOffset = frame.Timestamp; }
+ lastClusterTimestamp = frame.Timestamp;
+ delta = frame.Timestamp - lastClusterTimestamp;
+ if (!streamingMode)
+ {
+ clusterOffset = document.Writer.MarkRelativeOffset(MatroskaSpecification.Segment);
+ hasClusterCue = false;
+ }
+ firstBlock = true;
+ await document.Writer.BeginMasterElement(MatroskaSpecification.Cluster, cancellationToken);
+ await document.Writer.WriteUnsignedInteger(MatroskaSpecification.Timestamp,
+ (ulong)(lastClusterTimestamp - firstClusterTimestampOffset), cancellationToken);
+ }
+
+ if (!streamingMode && !hasClusterCue && frame.IsKeyFrame && trackIndex == cueTrackNumber)
+ {
+ hasClusterCue = true;
+ if (clusterOffsets == null) { clusterOffsets = new Queue<CueMark>(); }
+ EBMLWriter.RelativeOffset blockOffset = default;
+ if (!firstBlock) { blockOffset = document.Writer.MarkRelativeOffset(MatroskaSpecification.Cluster); }
+ clusterOffsets.Enqueue(new CueMark(frame.Timestamp - firstClusterTimestampOffset, clusterOffset, blockOffset));
+ }
+
+ if (maxFrameTimestamp < frame.Timestamp) { maxFrameTimestamp = frame.Timestamp; }
+ writeBuffer.ReadOffset = 0; writeBuffer.WriteOffset = 0;
+ if (trackIndex <= 126) { writeBuffer.Buffer[writeBuffer.WriteOffset++] = (byte)(trackIndex | 0x80); }
+ else { new EBMLVInt((ulong)trackIndex).Write(writeBuffer); }
+ writeBuffer.Buffer[writeBuffer.WriteOffset++] = (byte)(delta >> 8);
+ writeBuffer.Buffer[writeBuffer.WriteOffset++] = (byte)(delta & 0xff);
+ writeBuffer.Buffer[writeBuffer.WriteOffset++] = (byte)(frame.IsKeyFrame ? 0x80 : 0x00);
+ await document.Writer.WriteBinary(MatroskaSpecification.SimpleBlock, writeBuffer.ReadMemory, frame.Buffer.ReadMemory, cancellationToken);
+ if (firstBlock && streamingMode) { await document.Writer.FlushAsync(cancellationToken); }
+ firstBlock = false;
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ if (disposed) { return ValueTask.CompletedTask; }
+ disposed = true;
+ return document.DisposeAsync();
+ }
+
+ public virtual void Dispose()
+ {
+ if (disposed) { return; }
+ disposed = true;
+ document.Dispose();
+ }
+}
diff --git a/MediaContainers.Matroska/Matroska/XiphLacingSize.cs b/MediaContainers.Matroska/Matroska/XiphLacingSize.cs
new file mode 100644
index 0000000..3bdbcb7
--- /dev/null
+++ b/MediaContainers.Matroska/Matroska/XiphLacingSize.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Xtremegaida.DataStructures;
+
+namespace MediaContainers.Matroska;
+
+public static class XiphLacingSize
+{
+ public static async ValueTask<int> Read(IDataQueueReader reader, CancellationToken cancellationToken = default)
+ {
+ int size = 0;
+ do
+ {
+ var val = await reader.ReadByteAsync(cancellationToken);
+ if (val < 0) { return size; }
+ if (val < 255) { return size + val; }
+ size += 255;
+ }
+ while (true);
+ }
+
+ public static int Read(DataBuffer buffer)
+ {
+ int size = 0;
+ do
+ {
+ if (buffer.ReadOffset >= buffer.WriteOffset) { return -1; }
+ var val = buffer.Buffer[buffer.ReadOffset++];
+ if (val < 0) { return size; }
+ if (val < 255) { return size + val; }
+ size += 255;
+ }
+ while (true);
+ }
+
+ public static async ValueTask Write(IDataQueueWriter writer, int value, CancellationToken cancellationToken = default)
+ {
+ if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value)); }
+ while (value >= 255) { value -= 255; await writer.WriteByteAsync(255, cancellationToken); }
+ await writer.WriteByteAsync((byte)value, cancellationToken);
+ }
+
+ public static void Write(DataBuffer buffer, int value)
+ {
+ if (value < 0) { throw new ArgumentOutOfRangeException(nameof(value)); }
+ while (value >= 255) { value -= 255; buffer.Buffer[buffer.WriteOffset++] = 255; }
+ buffer.Buffer[buffer.WriteOffset++] = (byte)value;
+ }
+}
diff --git a/MediaContainers.Matroska/MediaContainers.Matroska.csproj b/MediaContainers.Matroska/MediaContainers.Matroska.csproj
new file mode 100644
index 0000000..534adde
--- /dev/null
+++ b/MediaContainers.Matroska/MediaContainers.Matroska.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\Xtremegaida.DataStructures\Xtremegaida.DataStructures.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Xtremegaida.DataStructures/DataBufferCache.cs b/Xtremegaida.DataStructures/DataBufferCache.cs
new file mode 100644
index 0000000..c09431e
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataBufferCache.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Xtremegaida.DataStructures;
+
+public class DataBufferCache
+{
+ public static readonly DataBufferCache DefaultCache = new DataBufferCache();
+
+ private readonly ConcurrentQueue<DataBuffer> bucket0 = new();
+ private readonly ConcurrentQueue<DataBuffer> bucket1 = new();
+ private readonly ConcurrentQueue<DataBuffer> bucket2 = new();
+ private readonly ConcurrentQueue<DataBuffer>[] buckets;
+ private long memReserve;
+ private long memReserveMax = 32 << 20; // 32MiB
+ private long memUsed;
+ private int memMinSize = 256;
+ private int memMaxSize = 262144;
+ private int bucketMaxSize0 = 1024;
+ private int bucketMaxSize1 = 32768;
+
+ public int BufferMinSize
+ {
+ get { return memMinSize; }
+ set
+ {
+ var size = value;
+ if (size <= 16) { size = 16; }
+ if (size > 65536) { size = 65536; }
+ memMinSize = size;
+ for (int i = buckets.Length - 1; i >= 0; i--)
+ {
+ var bucket = buckets[i];
+ var itemCount = bucket.Count;
+ while (--itemCount >= 0 && bucket.TryDequeue(out var buffer))
+ {
+ if (buffer.Buffer.Length >= size) { bucket.Enqueue(buffer); }
+ else { Interlocked.Add(ref memReserve, -buffer.Buffer.Length); }
+ }
+ }
+ }
+ }
+
+ public int BufferMaxSize
+ {
+ get { return memMaxSize; }
+ set
+ {
+ var size = value;
+ if (size <= 256) { size = 256; }
+ memMaxSize = size;
+ for (int i = buckets.Length - 1; i >= 0; i--)
+ {
+ var bucket = buckets[i];
+ var itemCount = bucket.Count;
+ while (--itemCount >= 0 && bucket.TryDequeue(out var buffer))
+ {
+ if (buffer.Buffer.Length <= size) { bucket.Enqueue(buffer); }
+ else { Interlocked.Add(ref memReserve, -buffer.Buffer.Length); }
+ }
+ }
+ }
+ }
+
+ public long BufferMemoryReserveBytes => Interlocked.Read(ref memReserve);
+ public long BufferMemoryUsedBytes => Interlocked.Read(ref memUsed);
+ public long BufferMemoryMaxBytes
+ {
+ get { return Interlocked.Read(ref memReserveMax); }
+ set
+ {
+ Interlocked.Exchange(ref memReserveMax, value);
+ if (BufferMemoryUsedBytes > BufferMemoryMaxBytes)
+ {
+ for (int i = buckets.Length - 1; i >= 0 && BufferMemoryUsedBytes > BufferMemoryMaxBytes; i--)
+ {
+ while (BufferMemoryUsedBytes > BufferMemoryMaxBytes && buckets[i].TryDequeue(out var buffer))
+ {
+ Interlocked.Add(ref memReserve, -buffer.Buffer.Length);
+ }
+ }
+ }
+ }
+ }
+
+ public DataBufferCache()
+ {
+ buckets = new ConcurrentQueue<DataBuffer>[] { bucket0, bucket1, bucket2 };
+ }
+
+ internal void _PushInternal(DataBuffer buffer)
+ {
+ if (buffer.References != 0) { throw new InvalidOperationException(); }
+ var length = buffer.Buffer.Length;
+ if (length < memMinSize || length > memMaxSize) { return; }
+ if ((BufferMemoryUsedBytes + length) > BufferMemoryMaxBytes) { return; }
+ buffer.ReadOffset = 0;
+ buffer.WriteOffset = 0;
+ if (length > bucketMaxSize1) { bucket2.Enqueue(buffer); }
+ else if (length > bucketMaxSize0) { bucket1.Enqueue(buffer); }
+ else { bucket0.Enqueue(buffer); }
+ Interlocked.Add(ref memReserve, length);
+ Interlocked.Add(ref memUsed, -length);
+ }
+
+ public DataBuffer Pop(int minLength)
+ {
+ var bucket = minLength >= bucketMaxSize1 ? bucket2 : minLength >= bucketMaxSize0 ? bucket1 : bucket0;
+ var itemCount = bucket.Count;
+ while (--itemCount >= 0 && bucket.TryDequeue(out var buffer))
+ {
+ var length = buffer.Buffer.Length;
+ if (length < minLength)
+ {
+#if DEBUG
+ System.Diagnostics.Debug.Assert(buffer.WriteOffset == 0);
+ System.Diagnostics.Debug.Assert(buffer.ReadOffset == 0);
+ System.Diagnostics.Debug.Assert(buffer.References == 0);
+#endif
+ bucket.Enqueue(buffer);
+ }
+ else
+ {
+ Interlocked.Add(ref memReserve, -length);
+ Interlocked.Add(ref memUsed, length);
+#if DEBUG
+ System.Diagnostics.Debug.Assert(buffer.WriteOffset == 0);
+ System.Diagnostics.Debug.Assert(buffer.ReadOffset == 0);
+ System.Diagnostics.Debug.Assert(buffer.References == 0);
+#endif
+ buffer._ResetInternal();
+ return buffer;
+ }
+ }
+ if (minLength < memMinSize) { minLength = memMinSize; }
+ Interlocked.Add(ref memUsed, minLength);
+ return new DataBuffer(this, new byte[minLength]);
+ }
+
+ public void Clear()
+ {
+ for (int i = buckets.Length - 1; i >= 0; i--)
+ {
+ var bucket = buckets[i];
+ while (bucket.TryDequeue(out var buffer))
+ {
+ Interlocked.Add(ref memReserve, -buffer.Buffer.Length);
+ }
+ }
+ }
+}
+
+public sealed class DataBuffer : IDisposable
+{
+ public readonly DataBufferCache Owner;
+ public readonly byte[] Buffer;
+ public int ReadOffset;
+ public int WriteOffset;
+
+ private int refCount;
+
+ public int References => refCount;
+ public int UnreadSize => WriteOffset - ReadOffset;
+ public int SpaceLeft => Buffer.Length - WriteOffset;
+ public Span<byte> ReadSpan => new Span<byte>(Buffer, ReadOffset, WriteOffset - ReadOffset);
+ public Span<byte> WriteSpan => new Span<byte>(Buffer, WriteOffset, Buffer.Length - WriteOffset);
+ public ReadOnlyMemory<byte> ReadMemory => new ReadOnlyMemory<byte>(Buffer, ReadOffset, WriteOffset - ReadOffset);
+ public Memory<byte> WriteMemory => new Memory<byte>(Buffer, WriteOffset, Buffer.Length - WriteOffset);
+
+ public DataBuffer(DataBufferCache owner, byte[] buffer)
+ {
+ Owner = owner;
+ Buffer = buffer;
+ refCount = 1;
+ }
+
+ public void AddReference()
+ {
+ Interlocked.Increment(ref refCount);
+ }
+
+ internal void _ResetInternal()
+ {
+ ReadOffset = 0;
+ WriteOffset = 0;
+ refCount = 1;
+ }
+
+ public void Dispose()
+ {
+ if (Interlocked.Decrement(ref refCount) <= 0)
+ {
+ ReadOffset = 0;
+ WriteOffset = 0;
+ Owner?._PushInternal(this);
+ }
+ }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/DataQueue.cs b/Xtremegaida.DataStructures/DataQueue/DataQueue.cs
new file mode 100644
index 0000000..bef8854
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/DataQueue.cs
@@ -0,0 +1,547 @@
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public sealed class DataQueue : IDataQueue, IDataQueueReader, IDataQueueWriter, IDisposable
+{
+ private readonly DataBufferCache cache;
+ private readonly ConcurrentQueue<DataBuffer> bufferQueue = new();
+ private EventWaitLight readWait;
+ private EventWaitLight writeWait;
+ private EventWaitLight closedWait;
+ private EventWaitLight emptyWait;
+#if DEBUG
+ private SpinLock readLock = new(true);
+ private SpinLock writeLock = new(true);
+#else
+ private SpinLock readLock = new(false);
+ private SpinLock writeLock = new(false);
+#endif
+ private long currentCapacity;
+ private long maxCapacity;
+ private DataBuffer headRead;
+ private DataBuffer tailWrite;
+ private DataQueueReadStream readStream;
+ private DataQueueWriteStream writeStream;
+ private IDataQueueWriter forward;
+ private volatile bool closedRead;
+ private volatile bool closedWrite;
+ private volatile bool disposed;
+ private long bytesWritten;
+ private long bytesRead;
+
+ const int maxPacketSize = 65536;
+
+ public long MaxCapacityBytes
+ {
+ get { return maxCapacity; }
+ set { maxCapacity = value; writeWait.Trigger(); }
+ }
+
+ public long UnreadLength => currentCapacity;
+ public bool IsReadClosed => closedRead;
+ public bool IsWriteClosed => closedWrite;
+ public long TotalBytesWritten => bytesWritten;
+ public long TotalBytesRead => bytesRead;
+
+ public IDataQueueReader Reader => this;
+ public IDataQueueWriter Writer => this;
+ public Stream ReadStream => readStream ??= new DataQueueReadStream(this);
+ public Stream WriteStream => writeStream ??= new DataQueueWriteStream(this);
+
+ public event Action OnBlockWrite;
+ public event Action OnBlockRead;
+
+ public DataQueue() : this(maxPacketSize * 4, null) { }
+
+ public DataQueue(int maxCapacity, DataBufferCache cache = null)
+ {
+ this.maxCapacity = maxCapacity;
+ this.cache = cache ?? DataBufferCache.DefaultCache;
+ }
+
+ #region Read
+
+ public async ValueTask<int> ReadByteAsync(CancellationToken cancellationToken = default)
+ {
+ bool locked = false;
+ try
+ {
+ readLock.Enter(ref locked);
+ while (true)
+ {
+ if (closedRead) { return -1; }
+ if (headRead == null || headRead.ReadOffset >= headRead.Buffer.Length)
+ {
+ if (headRead != null) { RecycleHead(); }
+ bufferQueue.TryDequeue(out headRead);
+ }
+ if (headRead == null || headRead.ReadOffset >= headRead.WriteOffset)
+ {
+ if (closedWrite) { return -1; }
+ var task = readWait.WaitAsync(cancellationToken);
+ if (locked) { locked = false; readLock.Exit(); }
+ writeWait.Trigger();
+ emptyWait.Trigger();
+ OnBlockRead?.Invoke();
+ await task;
+ readLock.Enter(ref locked);
+ }
+ else
+ {
+ var result = headRead.Buffer[headRead.ReadOffset++];
+ if (headRead.ReadOffset >= headRead.Buffer.Length) { RecycleHead(); }
+ Interlocked.Decrement(ref currentCapacity);
+ bytesRead++;
+ if (locked) { locked = false; readLock.Exit(); }
+ writeWait.Trigger();
+ return result;
+ }
+ }
+ }
+ finally
+ {
+ if (locked) { readLock.Exit(); }
+ }
+ }
+
+ public async ValueTask<int> ReadAsync(Memory<byte> buffer, bool waitUntilFull = false, CancellationToken cancellationToken = default)
+ {
+ int readBytes = 0;
+ bool locked = false;
+ try
+ {
+ readLock.Enter(ref locked);
+ while (!buffer.IsEmpty && !closedRead)
+ {
+ if (headRead == null || headRead.ReadOffset >= headRead.Buffer.Length)
+ {
+ if (headRead != null) { RecycleHead(); }
+ bufferQueue.TryDequeue(out headRead);
+ }
+ if (headRead == null || headRead.ReadOffset >= headRead.WriteOffset)
+ {
+ if ((!waitUntilFull && readBytes > 0) || closedWrite) { break; }
+ var task = readWait.WaitAsync(cancellationToken);
+ if (locked) { locked = false; readLock.Exit(); }
+ writeWait.Trigger();
+ emptyWait.Trigger();
+ OnBlockRead?.Invoke();
+ await task;
+ readLock.Enter(ref locked);
+ }
+ else
+ {
+ var canRead = headRead.WriteOffset - headRead.ReadOffset;
+ if (canRead > buffer.Length) { canRead = buffer.Length; }
+ if (canRead > 0)
+ {
+ new Memory<byte>(headRead.Buffer, headRead.ReadOffset, canRead).CopyTo(buffer);
+ buffer = buffer.Slice(canRead);
+ headRead.ReadOffset += canRead;
+ readBytes += canRead;
+ Interlocked.Add(ref currentCapacity, -canRead);
+ bytesRead += canRead;
+ if (headRead.ReadOffset >= headRead.Buffer.Length) { RecycleHead(); }
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (locked) { readLock.Exit(); }
+ }
+ if (readBytes > 0) { writeWait.Trigger(); }
+ return readBytes;
+ }
+
+ public async ValueTask<int> ReadAsync(int skipBytes, CancellationToken cancellationToken = default)
+ {
+ int readBytes = 0;
+ bool locked = false;
+ try
+ {
+ readLock.Enter(ref locked);
+ while (skipBytes > 0 && !closedRead)
+ {
+ if (headRead == null || headRead.ReadOffset >= headRead.Buffer.Length)
+ {
+ if (headRead != null) { RecycleHead(); }
+ bufferQueue.TryDequeue(out headRead);
+ }
+ if (headRead == null || headRead.ReadOffset >= headRead.WriteOffset)
+ {
+ if (closedWrite) { break; }
+ var task = readWait.WaitAsync(cancellationToken);
+ if (locked) { locked = false; readLock.Exit(); }
+ writeWait.Trigger();
+ emptyWait.Trigger();
+ OnBlockRead?.Invoke();
+ await task;
+ readLock.Enter(ref locked);
+ }
+ else
+ {
+ var canRead = headRead.WriteOffset - headRead.ReadOffset;
+ if (canRead > skipBytes) { canRead = skipBytes; }
+ if (canRead > 0)
+ {
+ skipBytes -= canRead;
+ readBytes += canRead;
+ headRead.ReadOffset += canRead;
+ Interlocked.Add(ref currentCapacity, -canRead);
+ bytesRead += canRead;
+ if (headRead.ReadOffset >= headRead.Buffer.Length) { RecycleHead(); }
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (locked) { readLock.Exit(); }
+ }
+ if (readBytes > 0) { writeWait.Trigger(); }
+ return readBytes;
+ }
+
+ private void RecycleHead()
+ {
+ // Already in readLock
+ if (tailWrite == headRead)
+ {
+ bool writeLocked = false;
+ try
+ {
+ writeLock.Enter(ref writeLocked);
+ if (tailWrite == headRead) { tailWrite = null; }
+ }
+ finally
+ {
+ if (writeLocked) { writeLock.Exit(); }
+ }
+ }
+ headRead.Dispose();
+ headRead = null;
+ }
+
+ #endregion
+
+ #region Write
+
+ public async ValueTask WriteByteAsync(byte data, CancellationToken cancellationToken = default)
+ {
+ if (forward != null)
+ {
+ await forward.WriteByteAsync(data, cancellationToken);
+ bytesRead++; bytesWritten++;
+ return;
+ }
+ bool locked = false;
+ try
+ {
+ writeLock.Enter(ref locked);
+ while (true)
+ {
+ if (closedWrite) { throw new InvalidOperationException(); }
+ if (currentCapacity >= maxCapacity)
+ {
+ var task = writeWait.WaitAsync(cancellationToken);
+ if (locked) { locked = false; writeLock.Exit(); }
+ readWait.Trigger();
+ OnBlockWrite?.Invoke();
+ await task;
+ writeLock.Enter(ref locked);
+ if (forward != null)
+ {
+ if (locked) { locked = false; writeLock.Exit(); }
+ await forward.WriteByteAsync(data, cancellationToken);
+ bytesRead++; bytesWritten++;
+ return;
+ }
+ }
+ else
+ {
+ if (tailWrite == null || tailWrite.WriteOffset >= tailWrite.Buffer.Length)
+ {
+ tailWrite = cache.Pop(256);
+ bufferQueue.Enqueue(tailWrite);
+ }
+ tailWrite.Buffer[tailWrite.WriteOffset++] = data;
+ Interlocked.Increment(ref currentCapacity);
+ bytesWritten++;
+ if (locked) { locked = false; writeLock.Exit(); }
+ readWait.Trigger();
+ return;
+ }
+ }
+ }
+ finally
+ {
+ if (locked) { writeLock.Exit(); }
+ }
+ }
+
+ public async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ if (forward != null)
+ {
+ await forward.WriteAsync(buffer, cancellationToken);
+ bytesRead += buffer.Length;
+ bytesWritten += buffer.Length;
+ return;
+ }
+ bool written = false;
+ bool locked = false;
+ try
+ {
+ writeLock.Enter(ref locked);
+ while (!buffer.IsEmpty)
+ {
+ if (closedWrite) { throw new InvalidOperationException(); }
+ if (currentCapacity >= maxCapacity)
+ {
+ var task = writeWait.WaitAsync(cancellationToken);
+ if (locked) { locked = false; writeLock.Exit(); }
+ readWait.Trigger();
+ OnBlockWrite?.Invoke();
+ await task;
+ writeLock.Enter(ref locked);
+ if (forward != null)
+ {
+ if (locked) { locked = false; writeLock.Exit(); }
+ await forward.WriteAsync(buffer, cancellationToken);
+ bytesRead += buffer.Length;
+ bytesWritten += buffer.Length;
+ return;
+ }
+ }
+ else
+ {
+ if (tailWrite == null || tailWrite.WriteOffset >= tailWrite.Buffer.Length)
+ {
+ var capacityLeft = maxCapacity - currentCapacity;
+ if (capacityLeft > buffer.Length) { capacityLeft = buffer.Length; }
+ tailWrite = cache.Pop((int)Math.Min(capacityLeft, maxPacketSize));
+ bufferQueue.Enqueue(tailWrite);
+ }
+ var canWrite = tailWrite.Buffer.Length - tailWrite.WriteOffset;
+ if (canWrite > buffer.Length) { canWrite = buffer.Length; }
+ if (canWrite > 0)
+ {
+ buffer.Slice(0, canWrite).CopyTo(new Memory<byte>(tailWrite.Buffer, tailWrite.WriteOffset, canWrite));
+ buffer = buffer.Slice(canWrite);
+ tailWrite.WriteOffset += canWrite;
+ Interlocked.Add(ref currentCapacity, canWrite);
+ bytesWritten += canWrite;
+ written = true;
+ }
+ }
+ }
+ }
+ finally
+ {
+ if (locked) { writeLock.Exit(); }
+ }
+ if (written) { readWait.Trigger(); }
+ }
+
+ #endregion
+
+ public void Clear(bool resetStats = true)
+ {
+ bool lockedRead = false, lockedWrite = false;
+ try
+ {
+ readLock.Enter(ref lockedRead);
+ writeLock.Enter(ref lockedWrite);
+ if (resetStats)
+ {
+ bytesRead = 0;
+ bytesWritten = 0;
+ }
+ tailWrite = null;
+ if (headRead != null)
+ {
+ Interlocked.Add(ref currentCapacity, headRead.ReadOffset - headRead.WriteOffset);
+ headRead.Dispose();
+ headRead = null;
+ }
+ while (bufferQueue.TryDequeue(out var buffer))
+ {
+ Interlocked.Add(ref currentCapacity, buffer.ReadOffset - buffer.WriteOffset);
+ buffer.Dispose();
+ }
+ }
+ finally
+ {
+ if (lockedRead) { readLock.Exit(); }
+ if (lockedWrite) { writeLock.Exit(); }
+ }
+ writeWait.Trigger();
+ emptyWait.Trigger();
+ }
+
+ public byte[] ToArray(bool clear = false)
+ {
+ byte[] snapshot;
+ bool lockedRead = false, lockedWrite = false;
+ try
+ {
+ readLock.Enter(ref lockedRead);
+ writeLock.Enter(ref lockedWrite);
+ snapshot = new byte[currentCapacity];
+ var snapshotSpan = new Span<byte>(snapshot);
+ if (headRead != null)
+ {
+ var read = headRead.ReadSpan;
+ read.CopyTo(snapshotSpan);
+ snapshotSpan = snapshotSpan.Slice(read.Length);
+ if (clear)
+ {
+ Interlocked.Add(ref currentCapacity, headRead.ReadOffset - headRead.WriteOffset);
+ headRead.Dispose();
+ headRead = null;
+ }
+ }
+ if (clear)
+ {
+ bytesRead = 0;
+ bytesWritten = 0;
+ tailWrite = null;
+ while (bufferQueue.TryDequeue(out var buffer))
+ {
+ var read = buffer.ReadSpan;
+ read.CopyTo(snapshotSpan);
+ snapshotSpan = snapshotSpan.Slice(read.Length);
+ Interlocked.Add(ref currentCapacity, buffer.ReadOffset - buffer.WriteOffset);
+ buffer.Dispose();
+ }
+ }
+ else
+ {
+ foreach (var buffer in bufferQueue)
+ {
+ var read = buffer.ReadSpan;
+ read.CopyTo(snapshotSpan);
+ snapshotSpan = snapshotSpan.Slice(read.Length);
+ }
+ }
+ }
+ finally
+ {
+ if (lockedRead) { readLock.Exit(); }
+ if (lockedWrite) { writeLock.Exit(); }
+ }
+ if (clear) { emptyWait.Trigger(); }
+ return snapshot;
+ }
+
+ public async ValueTask ForwardTo(IDataQueueWriter writer, CancellationToken cancellationToken = default)
+ {
+ if (closedRead) { return; }
+ bool locked = false;
+ readLock.Enter(ref locked);
+ closedRead = true;
+ if (locked) { locked = false; readLock.Exit(); }
+ while (!disposed)
+ {
+ if (headRead == null || headRead.ReadOffset >= headRead.Buffer.Length)
+ {
+ if (headRead != null) { RecycleHead(); }
+ bufferQueue.TryDequeue(out headRead);
+ }
+ if (headRead == null || headRead.ReadOffset >= headRead.WriteOffset)
+ {
+ if (closedWrite) { break; }
+ try
+ {
+ writeLock.Enter(ref locked);
+ if ((headRead == null || headRead.ReadOffset >= headRead.WriteOffset) && bufferQueue.Count == 0)
+ {
+ forward = writer;
+ if (headRead != null)
+ {
+ headRead.Dispose();
+ headRead = null;
+ }
+ tailWrite = null;
+ break;
+ }
+ }
+ finally
+ {
+ if (locked) { locked = false; writeLock.Exit(); }
+ }
+ writeWait.Trigger();
+ emptyWait.Trigger();
+ OnBlockRead?.Invoke();
+ await readWait.WaitAsync(cancellationToken);
+ }
+ else
+ {
+ var canRead = headRead.WriteOffset - headRead.ReadOffset;
+ await writer.WriteAsync(new ReadOnlyMemory<byte>
+ (headRead.Buffer, headRead.ReadOffset, canRead), cancellationToken);
+ headRead.ReadOffset += canRead;
+ Interlocked.Add(ref currentCapacity, -canRead);
+ bytesRead += canRead;
+ if (headRead.ReadOffset >= headRead.Buffer.Length) { RecycleHead(); }
+ }
+ }
+ writeWait.Trigger();
+ emptyWait.Trigger();
+ await WaitForClose(cancellationToken);
+ }
+
+ public async ValueTask WaitForEmpty(CancellationToken cancellationToken = default)
+ {
+ bool locked = false;
+ try
+ {
+ readLock.Enter(ref locked);
+ while (!disposed)
+ {
+ if (forward != null) { return; }
+ if ((headRead == null || headRead.ReadOffset >= headRead.WriteOffset) && bufferQueue.Count == 0) { return; }
+ if (locked) { locked = false; readLock.Exit(); }
+ await emptyWait.WaitAsync(cancellationToken);
+ readLock.Enter(ref locked);
+ }
+ }
+ finally
+ {
+ if (locked) { readLock.Exit(); }
+ }
+ }
+
+ public Task WaitForClose(CancellationToken cancellationToken = default)
+ {
+ return closedWait.WaitAsync(cancellationToken);
+ }
+
+ public void CloseWrite()
+ {
+ if (closedWrite) { return; }
+ closedWrite = true;
+ writeWait.Trigger();
+ bool locked = false;
+ writeLock.Enter(ref locked);
+ tailWrite = null;
+ if (locked) { writeLock.Exit(); }
+ readWait.Trigger();
+ closedWait.Trigger(null, true);
+ }
+
+ public void Dispose()
+ {
+ if (disposed) { return; }
+ disposed = true;
+ closedRead = true;
+ CloseWrite();
+ Clear(false);
+ }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/IDataQueue.cs b/Xtremegaida.DataStructures/DataQueue/IDataQueue.cs
new file mode 100644
index 0000000..a7edfe6
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/IDataQueue.cs
@@ -0,0 +1,17 @@
+using System;
+using System.IO;
+
+namespace Xtremegaida.DataStructures;
+
+public interface IDataQueue : IDisposable
+{
+ long UnreadLength { get; }
+ bool IsReadClosed { get; }
+ bool IsWriteClosed { get; }
+ long TotalBytesWritten { get; }
+ long TotalBytesRead { get; }
+ IDataQueueReader Reader { get; }
+ IDataQueueWriter Writer { get; }
+ Stream ReadStream { get; }
+ Stream WriteStream { get; }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/Read/DataQueueLimitedReader.cs b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueLimitedReader.cs
new file mode 100644
index 0000000..4eed837
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueLimitedReader.cs
@@ -0,0 +1,80 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public class DataQueueLimitedReader : IDataQueueReader
+{
+ private readonly IDataQueueReader reader;
+ private readonly bool keepStreamOpen;
+ private readonly long maxReadBytes;
+ private long totalBytesRead;
+ private volatile bool disposed;
+
+ public long UnreadLength
+ {
+ get
+ {
+ var canRead = reader.UnreadLength;
+ var maxRead = maxReadBytes - totalBytesRead;
+ if (maxRead < canRead) { canRead = maxRead; }
+ return canRead;
+ }
+ }
+
+ public long CanReadLength => maxReadBytes - totalBytesRead;
+ public bool IsReadClosed => disposed || (totalBytesRead >= maxReadBytes);
+ public long TotalBytesRead => totalBytesRead;
+
+ public DataQueueLimitedReader(IDataQueueReader reader, long maxReadBytes, bool keepStreamOpen = false)
+ {
+ if (maxReadBytes < 0) { throw new ArgumentOutOfRangeException(nameof(maxReadBytes)); }
+ this.reader = reader;
+ this.keepStreamOpen = keepStreamOpen;
+ this.maxReadBytes = maxReadBytes;
+ }
+
+ public async ValueTask<int> ReadAsync(Memory<byte> buffer, bool waitUntilFull = false, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { return 0; }
+ if (maxReadBytes >= 0)
+ {
+ var canRead = maxReadBytes - totalBytesRead;
+ if (canRead < buffer.Length) { buffer = buffer.Slice(0, (int)canRead); }
+ }
+ int readBytes = await reader.ReadAsync(buffer, waitUntilFull, cancellationToken);
+ Interlocked.Add(ref totalBytesRead, readBytes);
+ return readBytes;
+ }
+
+ public async ValueTask<int> ReadAsync(int skipBytes, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { return 0; }
+ if (maxReadBytes >= 0)
+ {
+ var canRead = maxReadBytes - totalBytesRead;
+ if (canRead < skipBytes) { skipBytes = (int)canRead; }
+ }
+ if (skipBytes <= 0) { return 0; }
+ var readBytes = await reader.ReadAsync(skipBytes, cancellationToken);
+ Interlocked.Add(ref totalBytesRead, readBytes);
+ return readBytes;
+ }
+
+ public async ValueTask<int> ReadByteAsync(CancellationToken cancellationToken = default)
+ {
+ if (disposed || (maxReadBytes >= 0 && totalBytesRead >= maxReadBytes)) { return -1; }
+ var read = await reader.ReadByteAsync(cancellationToken);
+ Interlocked.Increment(ref totalBytesRead);
+ return read;
+ }
+
+ public void Dispose()
+ {
+ if (disposed) { return; }
+ disposed = true;
+ if (!keepStreamOpen) { reader.Dispose(); }
+ }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/Read/DataQueueLimitedReaderMutable.cs b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueLimitedReaderMutable.cs
new file mode 100644
index 0000000..e48e94e
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueLimitedReaderMutable.cs
@@ -0,0 +1,86 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public class DataQueueLimitedReaderMutable : IDataQueueReader
+{
+ private IDataQueueReader reader;
+ private long maxReadBytes;
+ private long totalBytesRead;
+ private bool isUnknownSize;
+ private volatile bool disposed;
+
+ public long UnreadLength
+ {
+ get
+ {
+ var canRead = reader?.UnreadLength ?? 0;
+ if (!isUnknownSize)
+ {
+ var maxRead = maxReadBytes - totalBytesRead;
+ if (maxRead < canRead) { canRead = maxRead; }
+ }
+ return canRead;
+ }
+ }
+
+ public long CanReadLength => isUnknownSize ? 0 : (maxReadBytes - totalBytesRead);
+ public bool IsReadClosed => disposed || (!isUnknownSize && totalBytesRead >= maxReadBytes);
+ public long TotalBytesRead => totalBytesRead;
+ public bool IsUnknownSize => isUnknownSize;
+
+ public DataQueueLimitedReaderMutable() { }
+
+ public void SetReadSource(IDataQueueReader reader, long maxReadBytes)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(DataQueueLimitedReaderMutable)); }
+ isUnknownSize = false;
+ if (maxReadBytes < 0) { isUnknownSize = true; maxReadBytes = 0; }
+ this.reader = reader;
+ this.maxReadBytes = maxReadBytes;
+ totalBytesRead = 0;
+ }
+
+ public async ValueTask<int> ReadAsync(Memory<byte> buffer, bool waitUntilFull = false, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { return 0; }
+ if (!isUnknownSize)
+ {
+ var canRead = maxReadBytes - totalBytesRead;
+ if (canRead < buffer.Length) { buffer = buffer.Slice(0, (int)canRead); }
+ }
+ int readBytes = await reader.ReadAsync(buffer, waitUntilFull, cancellationToken);
+ Interlocked.Add(ref totalBytesRead, readBytes);
+ return readBytes;
+ }
+
+ public async ValueTask<int> ReadAsync(int skipBytes, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { return 0; }
+ if (!isUnknownSize)
+ {
+ var canRead = maxReadBytes - totalBytesRead;
+ if (canRead < skipBytes) { skipBytes = (int)canRead; }
+ }
+ if (skipBytes <= 0) { return 0; }
+ var readBytes = await reader.ReadAsync(skipBytes, cancellationToken);
+ Interlocked.Add(ref totalBytesRead, readBytes);
+ return readBytes;
+ }
+
+ public async ValueTask<int> ReadByteAsync(CancellationToken cancellationToken = default)
+ {
+ if (disposed || (!isUnknownSize && totalBytesRead >= maxReadBytes)) { return -1; }
+ var read = await reader.ReadByteAsync(cancellationToken);
+ Interlocked.Increment(ref totalBytesRead);
+ return read;
+ }
+
+ public void Dispose()
+ {
+ disposed = true;
+ }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/Read/DataQueueMemoryReader.cs b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueMemoryReader.cs
new file mode 100644
index 0000000..7a8e8de
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueMemoryReader.cs
@@ -0,0 +1,54 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public class DataQueueMemoryReader : IDataQueueReader
+{
+ private readonly ReadOnlyMemory<byte> memory;
+ private int totalBytesRead;
+ private volatile bool disposed;
+
+ public long UnreadLength => memory.Length - totalBytesRead;
+ public bool IsReadClosed => disposed || totalBytesRead >= memory.Length;
+ public long TotalBytesRead => totalBytesRead;
+
+ public DataQueueMemoryReader(ReadOnlyMemory<byte> memory)
+ {
+ this.memory = memory;
+ }
+
+ public ValueTask<int> ReadAsync(Memory<byte> buffer, bool waitUntilFull = false, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { return ValueTask.FromResult(0); }
+ var canRead = memory.Length - totalBytesRead;
+ if (canRead > buffer.Length) { canRead = buffer.Length; }
+ memory.Slice(totalBytesRead, canRead).CopyTo(buffer);
+ Interlocked.Add(ref totalBytesRead, canRead);
+ return ValueTask.FromResult(canRead);
+ }
+
+ public ValueTask<int> ReadAsync(int skipBytes, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { return ValueTask.FromResult(0); }
+ var canRead = memory.Length - totalBytesRead;
+ if (canRead > skipBytes) { canRead = skipBytes; }
+ Interlocked.Add(ref totalBytesRead, canRead);
+ return ValueTask.FromResult(canRead);
+ }
+
+ public ValueTask<int> ReadByteAsync(CancellationToken cancellationToken = default)
+ {
+ if (disposed || totalBytesRead >= memory.Length) { return ValueTask.FromResult(-1); }
+ var result = memory.Span[totalBytesRead];
+ Interlocked.Increment(ref totalBytesRead);
+ return ValueTask.FromResult((int)result);
+ }
+
+ public void Dispose()
+ {
+ disposed = true;
+ }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/Read/DataQueueMemoryReaderMutable.cs b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueMemoryReaderMutable.cs
new file mode 100644
index 0000000..6ad5633
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueMemoryReaderMutable.cs
@@ -0,0 +1,65 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public class DataQueueMemoryReaderMutable : IDataQueueReader
+{
+ private ReadOnlyMemory<byte> memory;
+ private int totalBytesRead;
+ private volatile bool disposed;
+
+ public long UnreadLength => memory.Length - totalBytesRead;
+ public bool IsReadClosed => disposed || totalBytesRead >= memory.Length;
+ public long TotalBytesRead => totalBytesRead;
+ public ReadOnlyMemory<byte> Memory => memory;
+
+ public DataQueueMemoryReaderMutable() { }
+
+ public void SetBuffer(ReadOnlyMemory<byte> memory)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(DataQueueMemoryReaderMutable)); }
+ this.memory = memory;
+ totalBytesRead = 0;
+ }
+
+ public void SetReadOffset(int offset)
+ {
+ if (disposed) { throw new ObjectDisposedException(nameof(DataQueueMemoryReaderMutable)); }
+ totalBytesRead = offset;
+ }
+
+ public ValueTask<int> ReadAsync(Memory<byte> buffer, bool waitUntilFull = false, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { return ValueTask.FromResult(0); }
+ var canRead = memory.Length - totalBytesRead;
+ if (canRead > buffer.Length) { canRead = buffer.Length; }
+ memory.Slice(totalBytesRead, canRead).CopyTo(buffer);
+ Interlocked.Add(ref totalBytesRead, canRead);
+ return ValueTask.FromResult(canRead);
+ }
+
+ public ValueTask<int> ReadAsync(int skipBytes, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { return ValueTask.FromResult(0); }
+ var canRead = memory.Length - totalBytesRead;
+ if (canRead > skipBytes) { canRead = skipBytes; }
+ Interlocked.Add(ref totalBytesRead, canRead);
+ return ValueTask.FromResult(canRead);
+ }
+
+ public ValueTask<int> ReadByteAsync(CancellationToken cancellationToken = default)
+ {
+ if (disposed || totalBytesRead >= memory.Length) { return ValueTask.FromResult(-1); }
+ var result = memory.Span[totalBytesRead];
+ Interlocked.Increment(ref totalBytesRead);
+ return ValueTask.FromResult((int)result);
+ }
+
+ public void Dispose()
+ {
+ disposed = true;
+ }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/Read/DataQueueReadStream.cs b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueReadStream.cs
new file mode 100644
index 0000000..1f97bb0
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueReadStream.cs
@@ -0,0 +1,60 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public class DataQueueReadStream : Stream
+{
+ private readonly IDataQueueReader owner;
+ private readonly bool keepReaderOpen;
+
+ public override bool CanRead => true;
+ public override bool CanSeek => false;
+ public override bool CanWrite => false;
+ public override long Length => owner.TotalBytesRead + owner.UnreadLength;
+ public override long Position { get => owner.TotalBytesRead; set => throw new NotSupportedException(); }
+
+ public DataQueueReadStream(IDataQueueReader owner, bool keepReaderOpen = false)
+ {
+ this.owner = owner;
+ this.keepReaderOpen = keepReaderOpen;
+ }
+
+ public override void Flush() { }
+ public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
+ public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
+ public override void SetLength(long value) { throw new NotSupportedException(); }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ var task = owner.ReadAsync(new Memory<byte>(buffer, offset, count));
+ if (task.IsCompletedSuccessfully) { return task.Result; }
+ return task.AsTask().Result;
+ }
+
+ public override int ReadByte()
+ {
+ var task = owner.ReadByteAsync();
+ if (task.IsCompletedSuccessfully) { return task.Result; }
+ return task.AsTask().Result;
+ }
+
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ var task = owner.ReadAsync(new Memory<byte>(buffer, offset, count), false, cancellationToken);
+ if (task.IsCompletedSuccessfully) { return Task.FromResult(task.Result); }
+ return task.AsTask();
+ }
+
+ public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ return owner.ReadAsync(buffer, false, cancellationToken);
+ }
+
+ public override void Close()
+ {
+ if (!keepReaderOpen) { owner.Dispose(); }
+ }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/Read/DataQueueStreamReader.cs b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueStreamReader.cs
new file mode 100644
index 0000000..87a5b62
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/Read/DataQueueStreamReader.cs
@@ -0,0 +1,144 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public class DataQueueStreamReader : IDataQueueReader, IAsyncDisposable
+{
+ private readonly Stream baseStream;
+ private readonly bool keepStreamOpen;
+ private readonly long maxReadBytes = -1;
+ private volatile bool disposed;
+ private long totalBytesRead;
+ private byte[] byteRead;
+
+ public long UnreadLength
+ {
+ get
+ {
+ var canRead = !baseStream.CanSeek ? (disposed ? 0 : long.MaxValue) : (baseStream.Length - baseStream.Position);
+ if (maxReadBytes >= 0)
+ {
+ var maxRead = maxReadBytes - totalBytesRead;
+ if (maxRead < canRead) { canRead = maxRead; }
+ }
+ return canRead;
+ }
+ }
+
+ public bool IsReadClosed => disposed;
+
+ public long TotalBytesRead => totalBytesRead;
+
+ public DataQueueStreamReader(Stream baseStream, bool keepStreamOpen = false)
+ {
+ this.baseStream = baseStream;
+ this.keepStreamOpen = keepStreamOpen;
+ }
+
+ public DataQueueStreamReader(Stream baseStream, long maxReadBytes, bool keepStreamOpen = false) : this(baseStream, keepStreamOpen)
+ {
+ if (maxReadBytes < 0) { throw new ArgumentOutOfRangeException(nameof(maxReadBytes)); }
+ this.maxReadBytes = maxReadBytes;
+ }
+
+ public async ValueTask<int> ReadAsync(Memory<byte> buffer, bool waitUntilFull = false, CancellationToken cancellationToken = default)
+ {
+ int readBytes = 0;
+ if (disposed) { return 0; }
+ if (maxReadBytes >= 0)
+ {
+ var canRead = maxReadBytes - totalBytesRead;
+ if (canRead < buffer.Length) { buffer = buffer.Slice(0, (int)canRead); }
+ }
+ if (!waitUntilFull)
+ {
+ readBytes = await baseStream.ReadAsync(buffer, cancellationToken);
+ }
+ else
+ {
+ while (!buffer.IsEmpty)
+ {
+ var read = await baseStream.ReadAsync(buffer, cancellationToken);
+ if (read <= 0) { break; }
+ buffer = buffer.Slice(read);
+ readBytes += read;
+ }
+ ;
+ }
+ Interlocked.Add(ref totalBytesRead, readBytes);
+ return readBytes;
+ }
+
+ public async ValueTask<int> ReadAsync(int skipBytes, CancellationToken cancellationToken = default)
+ {
+ if (disposed) { return 0; }
+ if (maxReadBytes >= 0)
+ {
+ var canRead = maxReadBytes - totalBytesRead;
+ if (canRead < skipBytes) { skipBytes = (int)canRead; }
+ }
+ if (skipBytes <= 0) { return 0; }
+ if (baseStream.CanSeek)
+ {
+ var length = baseStream.Length;
+ var pos = baseStream.Position;
+ var canRead = length - pos;
+ if (canRead > skipBytes) { canRead = skipBytes; }
+ if (canRead <= 0) { canRead = 0; }
+ else { baseStream.Seek(canRead, SeekOrigin.Current); }
+ Interlocked.Add(ref totalBytesRead, canRead);
+ return (int)canRead;
+ }
+ int readBytes = 0;
+ if (skipBytes <= 4)
+ {
+ while (readBytes < skipBytes)
+ {
+ if (await ReadByteAsync(cancellationToken) < 0) { break; }
+ readBytes++;
+ }
+ }
+ else
+ {
+ var buffer = new byte[Math.Min(skipBytes, 4096)];
+ while (readBytes < skipBytes)
+ {
+ var canRead = skipBytes - readBytes;
+ if (canRead > buffer.Length) { canRead = buffer.Length; }
+ var read = await baseStream.ReadAsync(buffer, 0, canRead, cancellationToken);
+ if (read <= 0) { break; }
+ readBytes += read;
+ }
+ ;
+ Interlocked.Add(ref totalBytesRead, readBytes);
+ }
+ return readBytes;
+ }
+
+ public async ValueTask<int> ReadByteAsync(CancellationToken cancellationToken = default)
+ {
+ if (disposed || (maxReadBytes >= 0 && totalBytesRead >= maxReadBytes)) { return -1; }
+ if (byteRead == null) { byteRead = new byte[1]; }
+ var read = await baseStream.ReadAsync(byteRead, cancellationToken);
+ if (read == 0) { return -1; }
+ Interlocked.Increment(ref totalBytesRead);
+ return byteRead[0];
+ }
+
+ public void Dispose()
+ {
+ if (disposed) { return; }
+ disposed = true;
+ if (!keepStreamOpen) { baseStream.Dispose(); }
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (disposed) { return; }
+ disposed = true;
+ if (!keepStreamOpen) { await baseStream.DisposeAsync(); }
+ }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/Read/IDataQueueReader.cs b/Xtremegaida.DataStructures/DataQueue/Read/IDataQueueReader.cs
new file mode 100644
index 0000000..d217e74
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/Read/IDataQueueReader.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public interface IDataQueueReader : IDisposable
+{
+ long UnreadLength { get; }
+ bool IsReadClosed { get; }
+ long TotalBytesRead { get; }
+ ValueTask<int> ReadByteAsync(CancellationToken cancellationToken = default);
+ ValueTask<int> ReadAsync(Memory<byte> buffer, bool waitUntilFull = false, CancellationToken cancellationToken = default);
+ ValueTask<int> ReadAsync(int skipBytes, CancellationToken cancellationToken = default);
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/Write/DataQueueStreamWriter.cs b/Xtremegaida.DataStructures/DataQueue/Write/DataQueueStreamWriter.cs
new file mode 100644
index 0000000..441ea78
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/Write/DataQueueStreamWriter.cs
@@ -0,0 +1,57 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public class DataQueueStreamWriter : IDataQueueWriter, IDisposable, IAsyncDisposable
+{
+ private readonly Stream baseStream;
+ private readonly bool keepStreamOpen;
+ private volatile bool disposed;
+ private long totalBytesWritten;
+ private byte[] byteWrite;
+
+ public bool IsWriteClosed => disposed;
+ public long TotalBytesWritten => totalBytesWritten;
+
+ public DataQueueStreamWriter(Stream baseStream, bool keepStreamOpen = false)
+ {
+ this.baseStream = baseStream;
+ this.keepStreamOpen = keepStreamOpen;
+ }
+
+ public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ Interlocked.Add(ref totalBytesWritten, buffer.Length);
+ return baseStream.WriteAsync(buffer, cancellationToken);
+ }
+
+ public ValueTask WriteByteAsync(byte data, CancellationToken cancellationToken = default)
+ {
+ if (byteWrite == null) { byteWrite = new byte[1]; }
+ byteWrite[0] = data;
+ Interlocked.Increment(ref totalBytesWritten);
+ return baseStream.WriteAsync(byteWrite, cancellationToken);
+ }
+
+ public void CloseWrite()
+ {
+ Dispose();
+ }
+
+ public void Dispose()
+ {
+ if (disposed) { return; }
+ disposed = true;
+ if (!keepStreamOpen) { baseStream.Dispose(); }
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (disposed) { return; }
+ disposed = true;
+ if (!keepStreamOpen) { await baseStream.DisposeAsync(); }
+ }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/Write/DataQueueWriteStream.cs b/Xtremegaida.DataStructures/DataQueue/Write/DataQueueWriteStream.cs
new file mode 100644
index 0000000..28ed668
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/Write/DataQueueWriteStream.cs
@@ -0,0 +1,60 @@
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public class DataQueueWriteStream : Stream
+{
+ private readonly IDataQueueWriter owner;
+ private readonly bool keepWriterOpen;
+
+ public override bool CanRead => false;
+ public override bool CanSeek => false;
+ public override bool CanWrite => true;
+ public override long Length => owner.TotalBytesWritten;
+ public override long Position { get => owner.TotalBytesWritten; set => throw new NotSupportedException(); }
+
+ public DataQueueWriteStream(IDataQueueWriter owner, bool keepWriterOpen = false)
+ {
+ this.owner = owner;
+ this.keepWriterOpen = keepWriterOpen;
+ }
+
+ public override void Flush() { }
+ public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
+ public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
+ public override void SetLength(long value) { throw new NotSupportedException(); }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ var task = owner.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count));
+ if (task.IsCompletedSuccessfully) { return; }
+ task.AsTask().Wait();
+ }
+
+ public override void WriteByte(byte value)
+ {
+ var task = owner.WriteByteAsync(value);
+ if (task.IsCompletedSuccessfully) { return; }
+ task.AsTask().Wait();
+ }
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ var task = owner.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
+ if (task.IsCompletedSuccessfully) { return Task.CompletedTask; }
+ return task.AsTask();
+ }
+
+ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ return owner.WriteAsync(buffer, cancellationToken);
+ }
+
+ public override void Close()
+ {
+ if (!keepWriterOpen) { owner.CloseWrite(); }
+ }
+}
diff --git a/Xtremegaida.DataStructures/DataQueue/Write/IDataQueueWriter.cs b/Xtremegaida.DataStructures/DataQueue/Write/IDataQueueWriter.cs
new file mode 100644
index 0000000..7b76d91
--- /dev/null
+++ b/Xtremegaida.DataStructures/DataQueue/Write/IDataQueueWriter.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+public interface IDataQueueWriter
+{
+ bool IsWriteClosed { get; }
+ long TotalBytesWritten { get; }
+ ValueTask WriteByteAsync(byte data, CancellationToken cancellationToken = default);
+ ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default);
+ void CloseWrite();
+}
diff --git a/Xtremegaida.DataStructures/EventWaitLight.cs b/Xtremegaida.DataStructures/EventWaitLight.cs
new file mode 100644
index 0000000..5e4c86e
--- /dev/null
+++ b/Xtremegaida.DataStructures/EventWaitLight.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xtremegaida.DataStructures;
+
+[System.Diagnostics.DebuggerDisplay("TriggerState = {triggerState}")]
+public struct EventWaitLight
+{
+ private SpinLock taskLock;
+ private TaskCompletionSource taskSource;
+ private Exception lastError;
+ private volatile int triggerState;
+
+ public bool Triggered => triggerState != 0;
+
+ public Task WaitAsync(CancellationToken cancellationToken = default)
+ {
+ if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); }
+ Task task;
+ bool locked = false;
+ try
+ {
+ taskLock.TryEnter(ref locked);
+ if (triggerState != 0)
+ {
+ var error = lastError;
+ if (triggerState > 0) { triggerState = 0; lastError = null; }
+ if (error != null) { return Task.FromException(error); }
+ return Task.CompletedTask;
+ }
+ if (taskSource == null) { taskSource = new TaskCompletionSource(); }
+ task = taskSource.Task;
+ }
+ finally
+ {
+ if (locked) { taskLock.Exit(); }
+ }
+ if (cancellationToken.CanBeCanceled) { return task.WaitAsync(cancellationToken); }
+ return task;
+ }
+
+ public void Trigger(Exception error = null, bool manualReset = false, bool withReEntrantSafety = false)
+ {
+ // WARNING: TrySetResult runs continuations synchronously on the same thread.
+ // This can cause issues with non-reentrant locking.
+ TaskCompletionSource source = null;
+ bool locked = false;
+ try
+ {
+ taskLock.Enter(ref locked);
+ lastError = error; triggerState = 1;
+ if (taskSource != null)
+ {
+ source = taskSource; taskSource = null;
+ lastError = null; triggerState = 0;
+ }
+ if (manualReset) { triggerState = -1; }
+ }
+ finally
+ {
+ if (locked) { taskLock.Exit(); }
+ }
+ if (source != null)
+ {
+ if (withReEntrantSafety)
+ {
+ RunAsTask(source, error);
+ }
+ else
+ {
+ if (error != null) { source.TrySetException(error); }
+ else { source.TrySetResult(); }
+ }
+ }
+ }
+
+ private void RunAsTask(TaskCompletionSource source, Exception error)
+ {
+ _ = Task.Run(() =>
+ {
+ if (error != null) { source.TrySetException(error); }
+ else { source.TrySetResult(); }
+ });
+ }
+
+ public void Reset()
+ {
+ bool locked = false;
+ try
+ {
+ taskLock.Enter(ref locked);
+ if (triggerState != 0)
+ {
+ taskSource = null;
+ lastError = null;
+ triggerState = 0;
+ }
+ }
+ finally
+ {
+ if (locked) { taskLock.Exit(); }
+ }
+ }
+}
diff --git a/Xtremegaida.DataStructures/PolyfillTaskExtensions.cs b/Xtremegaida.DataStructures/PolyfillTaskExtensions.cs
new file mode 100644
index 0000000..35055bf
--- /dev/null
+++ b/Xtremegaida.DataStructures/PolyfillTaskExtensions.cs
@@ -0,0 +1,54 @@
+namespace System.Threading.Tasks;
+
+public static class PolyfillTaskExtensions // Polyfill for < .NET 6
+{
+ public static Task WaitAsync(this Task task, int millisecondsTimeout) =>
+ WaitAsync(task, TimeSpan.FromMilliseconds(millisecondsTimeout), default);
+
+ public static Task WaitAsync(this Task task, TimeSpan timeout) =>
+ WaitAsync(task, timeout, default);
+
+ public static async Task WaitAsync(this Task task, CancellationToken cancellationToken)
+ {
+ var tcs = new TaskCompletionSource();
+ using (cancellationToken.Register(s => ((TaskCompletionSource)s).TrySetCanceled(), tcs))
+ {
+ await (await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)).ConfigureAwait(false);
+ }
+ }
+
+ public static async Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken cancellationToken)
+ {
+ var tcs = new TaskCompletionSource<bool>();
+ using (new Timer(s => ((TaskCompletionSource<bool>)s).TrySetException(new TimeoutException()), tcs, timeout, Timeout.InfiniteTimeSpan))
+ using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(), tcs))
+ {
+ await (await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)).ConfigureAwait(false);
+ }
+ }
+
+ public static Task<TResult> WaitAsync<TResult>(this Task<TResult> task, int millisecondsTimeout) =>
+ WaitAsync(task, TimeSpan.FromMilliseconds(millisecondsTimeout), default);
+
+ public static Task<TResult> WaitAsync<TResult>(this Task<TResult> task, TimeSpan timeout) =>
+ WaitAsync(task, timeout, default);
+
+ public static async Task<TResult> WaitAsync<TResult>(this Task<TResult> task, CancellationToken cancellationToken)
+ {
+ var tcs = new TaskCompletionSource<TResult>();
+ using (cancellationToken.Register(s => ((TaskCompletionSource<TResult>)s).TrySetCanceled(), tcs))
+ {
+ return await (await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)).ConfigureAwait(false);
+ }
+ }
+
+ public static async Task<TResult> WaitAsync<TResult>(this Task<TResult> task, TimeSpan timeout, CancellationToken cancellationToken)
+ {
+ var tcs = new TaskCompletionSource<TResult>();
+ using (new Timer(s => ((TaskCompletionSource<TResult>)s).TrySetException(new TimeoutException()), tcs, timeout, Timeout.InfiniteTimeSpan))
+ using (cancellationToken.Register(s => ((TaskCompletionSource<TResult>)s).TrySetCanceled(), tcs))
+ {
+ return await (await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/Xtremegaida.DataStructures/Xtremegaida.DataStructures.csproj b/Xtremegaida.DataStructures/Xtremegaida.DataStructures.csproj
new file mode 100644
index 0000000..2a9edf6
--- /dev/null
+++ b/Xtremegaida.DataStructures/Xtremegaida.DataStructures.csproj
@@ -0,0 +1,7 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ </PropertyGroup>
+
+</Project>
\ No newline at end of file