├── icon-128.png ├── BlazorEBMLViewer ├── wwwroot │ ├── css │ │ ├── radzen-tweak.css │ │ └── app.css │ ├── favicon.png │ ├── icon-192.png │ ├── icon-512.png │ └── index.html ├── Layout │ ├── RadzenTheme.razor │ ├── MainLayoutService.cs │ ├── MainLayout.razor │ ├── MainLayout.razor.cs │ └── RadzenTheme.razor.cs ├── Services │ ├── EBMLSchemaService.cs │ ├── AppService.cs │ ├── FullscreenTrayIconService.cs │ ├── BatteryTrayIconService.cs │ ├── UndoService.cs │ └── ThemeTrayIconService.cs ├── App.razor ├── Components │ ├── RowContextMenuArgs.cs │ ├── AppTray │ │ ├── AppTrayArea.razor │ │ ├── AppTrayIcon.cs │ │ ├── AppTrayArea.razor.cs │ │ ├── AppTrayArea.razor.css │ │ └── AppTrayService.cs │ ├── BinaryElementView.razor │ ├── EBMLTreeView.razor │ ├── BinaryElementCellEditor.razor │ ├── EBMLDataGrid.razor │ └── EBMLDataGrid.razor.cs ├── _Imports.razor ├── BlazorEBMLViewer.csproj ├── Properties │ └── launchSettings.json ├── Program.cs └── Pages │ ├── Home.razor │ └── Home.razor.cs ├── SpawnDev.EBML ├── Elements │ ├── ElementFind.cs │ ├── UintElement.cs │ ├── FloatElement.cs │ ├── IntElement.cs │ ├── DateElement.cs │ ├── IteratedElementInfo.cs │ ├── StringElement.cs │ ├── BinaryElement.cs │ ├── ElementStreamInfo.cs │ ├── ElementHeader.cs │ └── ElementBase.cs ├── ElementTypes │ ├── UnknownElement.cs │ ├── EBMLHeader.cs │ ├── VoidElement.cs │ ├── ElementNameAttribute.cs │ └── CRC32Element.cs ├── Matroska │ ├── Block.cs │ ├── Cluster.cs │ ├── Segment.cs │ └── SimpleBlock.cs ├── Engines │ ├── DocumentIssue.cs │ ├── EngineInfo.cs │ ├── EBMLEngine.cs │ ├── DocumentEngine.cs │ └── MatroskaEngine.cs ├── MasterCacheItem.cs ├── SpawnDev.EBML.csproj ├── Crc32 │ ├── SafeProxy.cs │ └── Crc32.cs ├── Extensions │ ├── BigEndian.cs │ └── EBMLConverter.cs └── Schemas │ ├── Schema.cs │ ├── ebml.xml │ └── SchemaElement.cs ├── ConsoleEBMLViewer ├── TestData │ └── Big_Buck_Bunny_180 10s.webm ├── ConsoleEBMLViewer.csproj ├── Program.cs └── EBMLConsole.cs ├── LICENSE.txt ├── .github └── workflows │ └── main.yml ├── README.md ├── .gitattributes ├── SpawnDev.EBML.sln └── .gitignore /icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LostBeard/SpawnDev.EBML/main/icon-128.png -------------------------------------------------------------------------------- /BlazorEBMLViewer/wwwroot/css/radzen-tweak.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --rz-grid-cell-padding: 0.300rem 0.300rem; 3 | } 4 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LostBeard/SpawnDev.EBML/main/BlazorEBMLViewer/wwwroot/favicon.png -------------------------------------------------------------------------------- /BlazorEBMLViewer/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LostBeard/SpawnDev.EBML/main/BlazorEBMLViewer/wwwroot/icon-192.png -------------------------------------------------------------------------------- /BlazorEBMLViewer/wwwroot/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LostBeard/SpawnDev.EBML/main/BlazorEBMLViewer/wwwroot/icon-512.png -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/ElementFind.cs: -------------------------------------------------------------------------------- 1 | namespace SpawnDev.EBML.Elements 2 | { 3 | public partial class MasterElement 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ConsoleEBMLViewer/TestData/Big_Buck_Bunny_180 10s.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LostBeard/SpawnDev.EBML/main/ConsoleEBMLViewer/TestData/Big_Buck_Bunny_180 10s.webm -------------------------------------------------------------------------------- /BlazorEBMLViewer/Layout/RadzenTheme.razor: -------------------------------------------------------------------------------- 1 | @inject ThemeService ThemeService 2 | @inject PersistentComponentState PersistentComponentState 3 | 4 | @if (wcag) 5 | { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /SpawnDev.EBML/ElementTypes/UnknownElement.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | 3 | namespace SpawnDev.EBML.ElementTypes 4 | { 5 | public class UnknownElement : ElementBase 6 | { 7 | /// 8 | /// New instance 9 | /// 10 | /// 11 | public UnknownElement(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SpawnDev.EBML/ElementTypes/EBMLHeader.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | 3 | namespace SpawnDev.EBML.ElementTypes 4 | { 5 | [ElementName("ebml", "EBML")] 6 | public class EBMLHeader : MasterElement 7 | { 8 | /// 9 | /// New instance 10 | /// 11 | /// 12 | public EBMLHeader(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /SpawnDev.EBML/ElementTypes/VoidElement.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | 3 | namespace SpawnDev.EBML.ElementTypes 4 | { 5 | [ElementName("ebml", "Void")] 6 | public class VoidElement : BinaryElement 7 | { 8 | /// 9 | /// New instance 10 | /// 11 | /// 12 | public VoidElement(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Services/EBMLSchemaService.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Engines; 2 | using SpawnDev.EBML.Schemas; 3 | 4 | namespace BlazorEBMLViewer.Services 5 | { 6 | public class EBMLSchemaService 7 | { 8 | public EBMLParser Parser { get; } 9 | public EBMLSchemaService() 10 | { 11 | Parser = new EBMLParser(); 12 | Parser.LoadDefaultSchemas(); 13 | Parser.RegisterDocumentEngine(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Matroska/Block.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | 3 | namespace SpawnDev.EBML.Matroska 4 | { 5 | [ElementName("webm", "Block")] 6 | [ElementName("matroska", "Block")] 7 | public class Block : ElementBase 8 | { 9 | /// 10 | /// New instance 11 | /// 12 | /// 13 | public Block(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Engines/DocumentIssue.cs: -------------------------------------------------------------------------------- 1 | namespace SpawnDev.EBML.Engines 2 | { 3 | /// 4 | /// A document issue 5 | /// 6 | public class DocumentIssue 7 | { 8 | /// 9 | /// Issue description 10 | /// 11 | public string Description { get; set; } = ""; 12 | /// 13 | /// Issue location 14 | /// 15 | public string Container { get; set; } = ""; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Matroska/Cluster.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | 3 | namespace SpawnDev.EBML.Matroska 4 | { 5 | [ElementName("webm", "Cluster")] 6 | [ElementName("matroska", "Cluster")] 7 | public class Cluster : MasterElement 8 | { 9 | /// 10 | /// New instance 11 | /// 12 | /// 13 | public Cluster(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Matroska/Segment.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | 3 | namespace SpawnDev.EBML.Matroska 4 | { 5 | [ElementName("webm", "Segment")] 6 | [ElementName("matroska", "Segment")] 7 | public class Segment : MasterElement 8 | { 9 | /// 10 | /// New instance 11 | /// 12 | /// 13 | public Segment(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Matroska/SimpleBlock.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | 3 | namespace SpawnDev.EBML.Matroska 4 | { 5 | [ElementName("webm", "SimpleBlock")] 6 | [ElementName("matroska", "SimpleBlock")] 7 | public class SimpleBlock : BinaryElement 8 | { 9 | /// 10 | /// New instance 11 | /// 12 | /// 13 | public SimpleBlock(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/RowContextMenuArgs.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | using SpawnDev.EBML.Elements; 3 | 4 | namespace BlazorEBMLViewer.Components 5 | { 6 | public class RowContextMenuArgs 7 | { 8 | public MouseEventArgs MouseEventArgs { get; set; } 9 | public ElementBase Element { get; set; } 10 | public RowContextMenuArgs(MouseEventArgs mouseEventArgs, ElementBase element) 11 | { 12 | MouseEventArgs = mouseEventArgs; 13 | Element = element; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SpawnDev.EBML/ElementTypes/ElementNameAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SpawnDev.EBML 8 | { 9 | [AttributeUsage(AttributeTargets.Class, AllowMultiple =true)] 10 | internal class ElementNameAttribute : Attribute 11 | { 12 | public string DocType { get; set; } 13 | public string Name { get; set; } 14 | public ElementNameAttribute(string docType, string name) 15 | { 16 | DocType = docType; 17 | Name = name; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/AppTray/AppTrayArea.razor: -------------------------------------------------------------------------------- 1 |
2 | @foreach (var trayIcon in TrayIconService.TrayIcons) 3 | { 4 | if (!trayIcon.Visible) continue; 5 | var style = $"{trayIcon.Style}"; 6 |
7 | 8 |
@trayIcon.TLText
9 |
10 | } 11 |
12 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using SpawnDev.BlazorJS 10 | @using SpawnDev.BlazorJS.JSObjects 11 | @using BlazorEBMLViewer 12 | @using BlazorEBMLViewer.Layout 13 | @using BlazorEBMLViewer.Services 14 | @using BlazorEBMLViewer.Components 15 | @using BlazorEBMLViewer.Components.AppTray 16 | @using Radzen 17 | @using Radzen.Blazor 18 | -------------------------------------------------------------------------------- /ConsoleEBMLViewer/ConsoleEBMLViewer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/AppTray/AppTrayIcon.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | using Radzen; 3 | 4 | namespace BlazorEBMLViewer.Components.AppTray 5 | { 6 | public class AppTrayIcon 7 | { 8 | public string TLText { get; set; } = ""; 9 | public string Title { get; set; } = ""; 10 | public string Style { get; set; } = ""; 11 | public string Icon { get; set; } = ""; 12 | public Action ClickCallback { get; set; } = new Action((args) => { }); 13 | public Action ContextCallback { get; set; } = new Action((args) => { }); 14 | public IconStyle? IconStyle { get; set; } 15 | public bool Visible { get; set; } = true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/UintElement.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Extensions; 2 | 3 | namespace SpawnDev.EBML.Elements 4 | { 5 | public class UintElement : ElementBase 6 | { 7 | /// 8 | /// The element type name 9 | /// 10 | public const string TypeName = "uinteger"; 11 | public ulong Data 12 | { 13 | get 14 | { 15 | Stream.Position = DataOffset; 16 | return Stream.ReadEBMLUInt((int)DataSize); 17 | } 18 | set 19 | { 20 | ReplaceData(EBMLConverter.ToUIntBytes(value)); 21 | } 22 | } 23 | public UintElement(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/FloatElement.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Extensions; 2 | 3 | namespace SpawnDev.EBML.Elements 4 | { 5 | public class FloatElement : ElementBase 6 | { 7 | /// 8 | /// The element type name 9 | /// 10 | public const string TypeName = "float"; 11 | public double Data 12 | { 13 | get 14 | { 15 | Stream.Position = DataOffset; 16 | return Stream.ReadEBMLFloat((int)DataSize); 17 | } 18 | set 19 | { 20 | ReplaceData(EBMLConverter.ToFloatBytes(value)); 21 | } 22 | } 23 | public FloatElement(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/IntElement.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Extensions; 2 | using SpawnDev.PatchStreams; 3 | 4 | namespace SpawnDev.EBML.Elements 5 | { 6 | public class IntElement : ElementBase 7 | { 8 | /// 9 | /// The element type name 10 | /// 11 | public const string TypeName = "integer"; 12 | public long Data 13 | { 14 | get 15 | { 16 | Stream.Position = DataOffset; 17 | return Stream.ReadEBMLInt((int)DataSize); 18 | } 19 | set 20 | { 21 | ReplaceData(EBMLConverter.ToIntBytes(value)); 22 | } 23 | } 24 | public IntElement(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/DateElement.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Extensions; 2 | 3 | namespace SpawnDev.EBML.Elements 4 | { 5 | public class DateElement : ElementBase 6 | { 7 | /// 8 | /// The element type name 9 | /// 10 | public const string TypeName = "date"; 11 | public DateTime Data 12 | { 13 | get 14 | { 15 | if (!Exists) return default; 16 | Stream.Position = DataOffset; 17 | return Stream.ReadEBMLDate((int)DataSize); 18 | } 19 | set 20 | { 21 | ReplaceData(EBMLConverter.ToDateBytes(value)); 22 | } 23 | } 24 | public DateElement(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/BlazorEBMLViewer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/AppTray/AppTrayArea.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace BlazorEBMLViewer.Components.AppTray 4 | { 5 | public partial class AppTrayArea : IDisposable 6 | { 7 | [Inject] 8 | AppTrayService TrayIconService { get; set; } 9 | 10 | protected override void OnInitialized() 11 | { 12 | TrayIconService.OnStateHasChanged += TrayIconService_OnStateHasChanged; 13 | 14 | } 15 | protected override void OnAfterRender(bool firstRender) 16 | { 17 | 18 | } 19 | public void Dispose() 20 | { 21 | TrayIconService.OnStateHasChanged -= TrayIconService_OnStateHasChanged; 22 | } 23 | private void TrayIconService_OnStateHasChanged() 24 | { 25 | StateHasChanged(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/AppTray/AppTrayArea.razor.css: -------------------------------------------------------------------------------- 1 | 2 | .app-tray-area { 3 | border-radius: 0.5rem; 4 | border: 1px solid grey; 5 | display: flex; 6 | flex-direction: row; 7 | } 8 | 9 | .app-tray-icon { 10 | position: relative; 11 | height: 36px; 12 | min-width: 36px; 13 | display: flex; 14 | justify-content: center; 15 | align-items: center; 16 | cursor: pointer; 17 | margin: 0 0; 18 | } 19 | 20 | .app-tray-icon:hover { 21 | background-color: #ffffff12; 22 | } 23 | 24 | .app-tray-icon-tltext { 25 | top: 0; 26 | right: 0; 27 | min-width: 1.2rem; 28 | position: absolute; 29 | padding: 0 0.2px; 30 | background-color: #770000cc; 31 | border: 1px solid #808080; 32 | border-radius: 10px; 33 | text-align: center; 34 | } 35 | 36 | .app-tray-icon-tltext:empty { 37 | display: none; 38 | } 39 | -------------------------------------------------------------------------------- /SpawnDev.EBML/MasterCacheItem.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | 3 | namespace SpawnDev.EBML 4 | { 5 | /// 6 | /// Cache item 7 | /// 8 | internal class MasterCacheItem 9 | { 10 | /// 11 | /// Element instance path 12 | /// 13 | public string InstancePath { get; set; } 14 | /// 15 | /// When marked true the entire master element has been iterated and Children contains all of it's children
16 | /// This allows the already iterated result to be returned or searched instead of reiterating over the file 17 | ///
18 | public bool Complete { get; set; } 19 | /// 20 | /// Children 21 | /// 22 | public Dictionary Children = new Dictionary(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Layout/MainLayoutService.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorEBMLViewer.Layout 2 | { 3 | public class MainLayoutService 4 | { 5 | public string DefaultTitle { get; set; } = "EBML Viewer"; 6 | public string Title 7 | { 8 | get => string.IsNullOrEmpty(_Title) ? DefaultTitle : _Title; 9 | set 10 | { 11 | if (_Title == value) return; 12 | _Title = value; 13 | OnTitleChanged?.Invoke(); 14 | } 15 | } 16 | string _Title { get; set; } = ""; 17 | public delegate void AfterRender(MainLayout mainLayout, bool firstRender); 18 | public event AfterRender OnAfterRender; 19 | public event Action OnTitleChanged; 20 | public void TriggerOnAfterRender(MainLayout mainLayout, bool firstRender) 21 | { 22 | OnAfterRender?.Invoke(mainLayout, firstRender); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/IteratedElementInfo.cs: -------------------------------------------------------------------------------- 1 | namespace SpawnDev.EBML.Elements 2 | { 3 | /// 4 | /// Used internally when parsing a stream 5 | /// 6 | internal class IteratedElementInfo 7 | { 8 | public Dictionary Counts = new Dictionary(); 9 | public int Seen(ulong id) 10 | { 11 | if (!Counts.TryGetValue(id, out var count)) 12 | { 13 | Counts.Add(id, 0); 14 | } 15 | return Counts[id]++; 16 | } 17 | public int ChildCount => Counts.Values.Sum(o => o); 18 | public string Path { get; set; } 19 | public string InstancePath { get; set; } 20 | public long MaxDataSize { get; set; } 21 | public long DataOffset { get; set; } 22 | public bool UnknownSize { get; set; } 23 | public int Depth { get; set; } 24 | public MasterCacheItem MasterCacheItem { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 9 | "applicationUrl": "http://localhost:5008", 10 | "environmentVariables": { 11 | "ASPNETCORE_ENVIRONMENT": "Development" 12 | } 13 | }, 14 | "https": { 15 | "commandName": "Project", 16 | "dotnetRunMessages": true, 17 | "launchBrowser": true, 18 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 19 | "applicationUrl": "https://localhost:7125;http://localhost:5008", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/AppTray/AppTrayService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | 3 | namespace BlazorEBMLViewer.Components.AppTray 4 | { 5 | public class AppTrayService 6 | { 7 | List _TrayIcons { get; } = new List(); 8 | public IEnumerable TrayIcons => ReverseOrder ? _TrayIcons.AsReadOnly().Reverse() : _TrayIcons.AsReadOnly(); 9 | public event Action OnStateHasChanged; 10 | public bool ReverseOrder { get; set; } = true; 11 | public AppTrayService() 12 | { 13 | 14 | } 15 | public void Add(AppTrayIcon trayIcon) 16 | { 17 | _TrayIcons.Add(trayIcon); 18 | StateHasChanged(); 19 | } 20 | public void Remove(AppTrayIcon trayIcon) 21 | { 22 | _TrayIcons.Remove(trayIcon); 23 | StateHasChanged(); 24 | } 25 | public void StateHasChanged() 26 | { 27 | OnStateHasChanged?.Invoke(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BlazorEBMLViewer 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 |
21 |
22 | 23 |
24 | An unhandled error has occurred. 25 | Reload 26 | 🗙 27 |
28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/StringElement.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Extensions; 2 | using System.Text; 3 | 4 | namespace SpawnDev.EBML.Elements 5 | { 6 | public class StringElement : ElementBase 7 | { 8 | /// 9 | /// The element type name 10 | /// 11 | public const string TypeNameString = "string"; 12 | /// 13 | /// The element type name 14 | /// 15 | public const string TypeNameUTF8 = "utf-8"; 16 | public string Data 17 | { 18 | get 19 | { 20 | Stream.Position = DataOffset; 21 | return IsUTF8 ? Stream.ReadEBMLStringUTF8((int)Size!.Value) : Stream.ReadEBMLStringASCII((int)Size!.Value); 22 | } 23 | set 24 | { 25 | ReplaceData((IsUTF8 ? Encoding.UTF8 : Encoding.ASCII).GetBytes(value ?? "")); 26 | } 27 | } 28 | public bool IsUTF8 => SchemaElement?.Type == "utf-8"; 29 | public StringElement(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/BinaryElement.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.PatchStreams; 2 | 3 | namespace SpawnDev.EBML.Elements 4 | { 5 | /// 6 | /// Basic read all, write all binary element
7 | ///
8 | public class BinaryElement : ElementBase 9 | { 10 | /// 11 | /// The element type name 12 | /// 13 | public const string TypeName = "binary"; 14 | /// 15 | /// Returns the element's data as PatchStream stream.
16 | /// Editing the returned stream will not modify the element 17 | ///
18 | public PatchStream Data 19 | { 20 | get => ElementStreamDataSlice(); 21 | set 22 | { 23 | // Out of sync element values cannot be set 24 | ReplaceData(value); 25 | } 26 | } 27 | /// 28 | /// New instance 29 | /// 30 | /// 31 | public BinaryElement(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Engines/EngineInfo.cs: -------------------------------------------------------------------------------- 1 | namespace SpawnDev.EBML.Engines 2 | { 3 | /// 4 | /// Information about a document engine 5 | /// 6 | public class EngineInfo 7 | { 8 | /// 9 | /// Engine Type 10 | /// 11 | public Type EngineType { get; private set; } 12 | /// 13 | /// Engine factory that will be called when the engine is being attached to a new Document 14 | /// 15 | private Func? Factory { get; set; } 16 | /// 17 | /// Creates a new engine info 18 | /// 19 | /// 20 | /// 21 | public EngineInfo(Type type, Func? factory = null) 22 | { 23 | EngineType = type; 24 | Factory = factory; 25 | } 26 | /// 27 | /// Creates a new instance of the document engine 28 | /// 29 | /// 30 | /// 31 | public DocumentEngine Create(EBMLDocument doc) 32 | { 33 | return Factory != null ? Factory(doc) : (DocumentEngine)Activator.CreateInstance(EngineType, new object?[] { doc })!; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Services/AppService.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.BlazorJS; 2 | using SpawnDev.EBML.Elements; 3 | using SpawnDev.EBML.Schemas; 4 | 5 | namespace BlazorEBMLViewer.Services 6 | { 7 | public class AppService : IAsyncBackgroundService 8 | { 9 | 10 | public Task Ready => _Ready ??= InitAsync(); 11 | private Task? _Ready = null; 12 | BlazorJSRuntime JS; 13 | public AppService(BlazorJSRuntime js) 14 | { 15 | JS = js; 16 | } 17 | async Task InitAsync() 18 | { 19 | 20 | } 21 | public string GetElementTypeIcon(SchemaElement? elementType) 22 | { 23 | return GetElementTypeIcon(elementType?.Type); 24 | } 25 | public string GetElementTypeIcon(string? elementType) 26 | { 27 | return elementType switch 28 | { 29 | MasterElement.TypeName => "folder", 30 | UintElement.TypeName => "tag", 31 | IntElement.TypeName => "tag", 32 | FloatElement.TypeName => "tag", 33 | StringElement.TypeNameString => "text_snippet", 34 | StringElement.TypeNameUTF8 => "text_snippet", 35 | BinaryElement.TypeName => "grid_view", 36 | DateElement.TypeName => "calendar_month", 37 | _ => "help_outline", 38 | }; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/ElementStreamInfo.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Schemas; 2 | using SpawnDev.PatchStreams; 3 | 4 | namespace SpawnDev.EBML.Elements 5 | { 6 | public class ElementStreamInfo 7 | { 8 | public string ParentInstancePath { get; protected internal set; } 9 | public ulong Id { get; protected internal set; } 10 | public string Path { get; protected internal set; } 11 | public string InstancePath { get; protected internal set; } 12 | public SchemaElement? SchemaElement { get; protected internal set; } 13 | public string Name { get; protected internal set; } 14 | public int Index { get; protected internal set; } 15 | public int TypeIndex { get; protected internal set; } 16 | public long DocumentOffset { get; protected internal set; } 17 | public long Offset { get; protected internal set; } 18 | public ulong? Size { get; protected internal set; } 19 | public long DataSize { get; protected internal set; } 20 | public long TotalSize { get; protected internal set; } 21 | public long DataOffset { get; protected internal set; } 22 | public long HeaderSize { get; protected internal set; } 23 | public string PatchId { get; protected internal set; } 24 | public bool Exists{ get; protected internal set; } 25 | public int Depth { get; protected internal set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Program.cs: -------------------------------------------------------------------------------- 1 | using BlazorEBMLViewer; 2 | using BlazorEBMLViewer.Components.AppTray; 3 | using BlazorEBMLViewer.Layout; 4 | using BlazorEBMLViewer.Services; 5 | using Microsoft.AspNetCore.Components.Web; 6 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 7 | using Radzen; 8 | using SpawnDev.BlazorJS; 9 | 10 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 11 | // SpawnDev.BlazorJS Javascript interop 12 | builder.Services.AddBlazorJSRuntime(out var JS); 13 | // Radzen components 14 | builder.Services.AddRadzenComponents(); 15 | // MainLayoutService 16 | builder.Services.AddScoped(); 17 | // AppTray service 18 | builder.Services.AddScoped(); 19 | // AppTray icon services 20 | // Theme switcher tray icon 21 | // - Icon click switches theme dark/light mode 22 | // - Icon shift+click switches to next theme 23 | // - Icon ctrl+click switches previous theme 24 | // - Icon right click shows theme select context menu 25 | builder.Services.AddScoped(); 26 | // Battery tray icon service (simple battery state indicator) 27 | // - Does not show if no state is found 28 | //builder.Services.AddScoped(); 29 | // EBML Schema service 30 | builder.Services.AddScoped(); 31 | // Fullscreen switcher tray icon 32 | // - Icon click to toggle fullscreen mode 33 | builder.Services.AddScoped(); 34 | // App primary service 35 | builder.Services.AddScoped(); 36 | // If in a window scope, add Blazor document elements 37 | if (JS.IsWindow) 38 | { 39 | builder.RootComponents.Add("#app"); 40 | builder.RootComponents.Add("head::after"); 41 | } 42 | // Start Blazor (BlazorJSRunAsync is scope aware and supports auto-starting services) 43 | await builder.Build().BlazorJSRunAsync(); -------------------------------------------------------------------------------- /SpawnDev.EBML/Engines/EBMLEngine.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | using SpawnDev.EBML.ElementTypes; 3 | 4 | namespace SpawnDev.EBML.Engines 5 | { 6 | /// 7 | /// Handles EBML specific document verification and CRC-32 updating 8 | /// 9 | class EBMLEngine : DocumentEngine 10 | { 11 | public EBMLEngine(EBMLDocument document) : base(document) { } 12 | 13 | public override void DocumentCheck(List changedElements) 14 | { 15 | if (changedElements.Count > 0) 16 | { 17 | var updatedCount = 0; 18 | var count = 0; 19 | foreach (var element in changedElements) 20 | { 21 | if (element.SchemaElement?.Type == "master") 22 | { 23 | var elMaster = element.As(); 24 | var crcEl = elMaster.First(); 25 | if (crcEl != null) 26 | { 27 | count++; 28 | updatedCount += crcEl.UpdateCRC() ? 1 : 0; 29 | } 30 | } 31 | var ancestors = element.GetAncestors(true); 32 | foreach (var ancestor in ancestors) 33 | { 34 | var crcEl = ancestor.First(); 35 | if (crcEl != null) 36 | { 37 | count++; 38 | updatedCount += crcEl.UpdateCRC() ? 1 : 0; 39 | } 40 | } 41 | } 42 | if (count > 0) 43 | { 44 | Log($"Updated {updatedCount} and Verified {count - updatedCount} CRC-32 values"); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | # Run workflow on every push to the master branch 4 | on: workflow_dispatch 5 | 6 | jobs: 7 | deploy-to-github-pages: 8 | permissions: 9 | contents: write 10 | # use ubuntu-latest image to run steps on 11 | runs-on: ubuntu-latest 12 | steps: 13 | # uses GitHub's checkout action to checkout code form the master branch 14 | - uses: actions/checkout@v2 15 | 16 | # sets up .NET Core SDK 17 | - name: Setup .NET Core SDK 18 | uses: actions/setup-dotnet@v3.0.3 19 | with: 20 | dotnet-version: 9.0.100-preview.7.24407.12 21 | 22 | # Install dotnet wasm buildtools workload 23 | - name: Install .NET WASM Build Tools 24 | run: dotnet workload install wasm-tools 25 | 26 | # publishes Blazor project to the publish-folder 27 | - name: Publish .NET Core Project 28 | run: dotnet publish ./BlazorEBMLViewer/ --nologo -c:Release --output publish 29 | 30 | # changes the base-tag in index.html from '/' to '/SpawnDev.BlazorJS.WebTorrents/' to match GitHub Pages repository subdirectory 31 | - name: Change base-tag in index.html from / to /SpawnDev.EBML/ 32 | run: sed -i 's/ 2 | 3 | 4 | net9.0;net8.0;net7.0;net6.0 5 | enable 6 | enable 7 | 3.0.0-preview.3 8 | True 9 | true 10 | true 11 | Embedded 12 | SpawnDev.EBML 13 | LostBeard 14 | An extendable .Net library for reading and writing Extensible Binary Meta Language (aka EBML) document streams. Includes schema for Matroska and WebM. 15 | https://github.com/LostBeard/SpawnDev.EBML 16 | README.md 17 | LICENSE.txt 18 | icon-128.png 19 | https://github.com/LostBeard/SpawnDev.EBML.git 20 | git 21 | EBML;WebM;Matroska 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Engines/DocumentEngine.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | 3 | namespace SpawnDev.EBML.Engines 4 | { 5 | /// 6 | /// Base class for EBML document engines
7 | /// After changes are made to an EBML Document, and the Document is marked as being in a stable state, the DocumentCheck methods of registered DocumentEngines
8 | /// are invoked to give them a chance to 9 | ///
10 | public abstract class DocumentEngine 11 | { 12 | /// 13 | /// The EBML document this engine is attached to 14 | /// 15 | public EBMLDocument Document { get; private set; } 16 | /// 17 | /// Required EBMLDocumentEngine constructor 18 | /// 19 | /// 20 | public DocumentEngine(EBMLDocument document) 21 | { 22 | Document = document; 23 | } 24 | /// 25 | /// Called by the Document when the document has changed
26 | /// This gives the engine a chance to update the document if needed 27 | ///
28 | public abstract void DocumentCheck(List changedElements); 29 | /// 30 | /// Fired when a log entry has been added 31 | /// 32 | public event Action OnLog = default!; 33 | /// 34 | /// Get or set whether this engine is enabled 35 | /// 36 | public virtual bool Enabled { get; set; } = true; 37 | /// 38 | /// Add a log entry 39 | /// 40 | /// 41 | protected void Log(string msg) 42 | { 43 | OnLog?.Invoke($"{GetType().Name} {msg}"); 44 | } 45 | /// 46 | /// A list of issues this engine sees with the current document 47 | /// 48 | public IEnumerable Issues { get; protected set; } = new List(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpawnDev.EBML 2 | 3 | | Name | Package | Description | 4 | |---------|-------------|-------------| 5 | |**SpawnDev.EBML**|[![NuGet version](https://badge.fury.io/nu/SpawnDev.EBML.svg)](https://www.nuget.org/packages/SpawnDev.EBML)| An extendable .Net library for reading and writing Extensible Binary Meta Language (aka EBML) documents. Includes schema for Matroska and WebM. | 6 | 7 | ## Demo 8 | [Blazor EBML Editor](https://lostbeard.github.io/SpawnDev.EBML/) 9 | 10 | 11 | ## Version 2 changes 12 | - The library now uses string paths instead of the Enums found in version 1. 13 | - The library now uses (and includes) EBML XML schema files: 14 | - [EBML](https://github.com/ietf-wg-cellar/ebml-specification/blob/master/ebml.xml) 15 | - [Matroska](https://github.com/ietf-wg-cellar/matroska-specification/blob/master/ebml_matroska.xml) 16 | 17 | Note: The Matroska schema xml is currently also used for WebM ebml documents. 18 | 19 | ## Usage 20 | 21 | ```cs 22 | using SpawnDev.EBML; 23 | using SpawnDev.EBML.Elements; 24 | 25 | // Create the EBML parser with the default configuration 26 | // default configuration supports matroska and webm reading and modification 27 | var ebml = new EBMLParser(); 28 | // get a stream containing an EBML document (or multiple documents) 29 | using var fileStream = File.Open(@"TestData/Big_Buck_Bunny_180 10s.webm", FileMode.Open); 30 | // parse the EBML document stream (ParseDocuments can be used to parse all documents in the stream) 31 | var document = ebml.ParseDocument(fileStream); 32 | if (document != null) 33 | { 34 | Console.WriteLine($"DocType: {document.DocType}"); 35 | // or using path 36 | Console.WriteLine($"DocType: {document.ReadString(@"/EBML/DocType")}"); 37 | 38 | // Get an element using the path 39 | var durationElement = document.GetElement(@"/Segment/Info/Duration"); 40 | if (durationElement != null) 41 | { 42 | var duration = durationElement.Data; 43 | var durationTime = TimeSpan.FromMilliseconds(duration); 44 | Console.WriteLine($"Duration: {durationTime}"); 45 | } 46 | } 47 | 48 | // Create a new matroska EBML file 49 | var matroskaDoc = ebml.CreateDocument("matroska"); 50 | Console.WriteLine($"DocType: {matroskaDoc.DocType}"); 51 | ``` -------------------------------------------------------------------------------- /BlazorEBMLViewer/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | @Title 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | @* 27 | *@ 28 | 29 |
30 | 31 |
32 |
33 | 34 |
35 | @Body 36 |
37 |
38 | 39 |
40 | 41 |
42 |
43 |
-------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/BinaryElementView.razor: -------------------------------------------------------------------------------- 1 | @using SpawnDev.BlazorJS.Toolbox 2 | @using SpawnDev.EBML.Elements 3 | 4 |
5 | @if (!string.IsNullOrEmpty(ImageDataUrl)) 6 | { 7 | 8 | } 9 |
10 | 11 | @code { 12 | [Parameter] 13 | public BinaryElement Element { get; set; } 14 | 15 | [Parameter] 16 | public EventCallback OnChanged { get; set; } 17 | 18 | [Inject] 19 | DialogService DialogService { get; set; } 20 | 21 | string? ImageDataUrl = null; 22 | 23 | Dictionary BinPatters = new Dictionary 24 | { 25 | { "image/jpeg", new int[] { 0xff, 0xd8, 0xff, 0xe0, -1, -1, 0x4a, 0x46, 0x49, 0x46, 0 }}, 26 | { "image/png", new int[] { 0x89 , 0x50 , 0x4E , 0x47 , 0x0D , 0x0A , 0x1A , 0x0A }}, 27 | }; 28 | string DetectContentType(byte[] data) 29 | { 30 | var temp = BinPatters.ToDictionary(); 31 | var maxPatternSize = BinPatters.Max(o => o.Value.Length); 32 | for (var i = 0; i < maxPatternSize && i < data.Length && temp.Count > 0; i++) 33 | { 34 | var b = data[i]; 35 | var failed = new List(); 36 | foreach (var t in temp) 37 | { 38 | var pByte = t.Value[i]; 39 | if (pByte >= 0 && b != pByte) 40 | { 41 | failed.Add(t.Key); 42 | } 43 | else if (i == t.Value.Length - 1) 44 | { 45 | // this pattern made it all the way through 46 | return t.Key; 47 | } 48 | } 49 | foreach (var t in failed) 50 | { 51 | temp.Remove(t); 52 | } 53 | } 54 | return ""; 55 | } 56 | protected override async Task OnParametersSetAsync() 57 | { 58 | var data = Element.Data.ToArray(true); 59 | var fileType = DetectContentType(data); 60 | if (fileType != null && fileType.StartsWith("image/")) 61 | { 62 | using var blob = string.IsNullOrEmpty(fileType) ? new Blob(new byte[][] { data }) : new Blob(new byte[][] { data }, new BlobOptions { Type = fileType }); 63 | ImageDataUrl = await blob.ToDataURLAsync(); 64 | } 65 | else 66 | { 67 | ImageDataUrl = null; 68 | } 69 | StateHasChanged(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Crc32/SafeProxy.cs: -------------------------------------------------------------------------------- 1 | namespace SpawnDev.EBML.Crc32 2 | { 3 | internal class SafeProxy 4 | { 5 | private const uint Poly = 0xedb88320u; 6 | 7 | private readonly uint[] _table = new uint[16 * 256]; 8 | 9 | internal SafeProxy() 10 | { 11 | Init(Poly); 12 | } 13 | 14 | protected void Init(uint poly) 15 | { 16 | var table = _table; 17 | for (uint i = 0; i < 256; i++) 18 | { 19 | uint res = i; 20 | for (int t = 0; t < 16; t++) 21 | { 22 | for (int k = 0; k < 8; k++) res = (res & 1) == 1 ? poly ^ res >> 1 : res >> 1; 23 | table[t * 256 + i] = res; 24 | } 25 | } 26 | } 27 | 28 | public uint Append(uint crc, byte[] input, int offset, int length) 29 | { 30 | uint crcLocal = uint.MaxValue ^ crc; 31 | 32 | uint[] table = _table; 33 | while (length >= 16) 34 | { 35 | var a = table[3 * 256 + input[offset + 12]] 36 | ^ table[2 * 256 + input[offset + 13]] 37 | ^ table[1 * 256 + input[offset + 14]] 38 | ^ table[0 * 256 + input[offset + 15]]; 39 | 40 | var b = table[7 * 256 + input[offset + 8]] 41 | ^ table[6 * 256 + input[offset + 9]] 42 | ^ table[5 * 256 + input[offset + 10]] 43 | ^ table[4 * 256 + input[offset + 11]]; 44 | 45 | var c = table[11 * 256 + input[offset + 4]] 46 | ^ table[10 * 256 + input[offset + 5]] 47 | ^ table[9 * 256 + input[offset + 6]] 48 | ^ table[8 * 256 + input[offset + 7]]; 49 | 50 | var d = table[15 * 256 + ((byte)crcLocal ^ input[offset])] 51 | ^ table[14 * 256 + ((byte)(crcLocal >> 8) ^ input[offset + 1])] 52 | ^ table[13 * 256 + ((byte)(crcLocal >> 16) ^ input[offset + 2])] 53 | ^ table[12 * 256 + (crcLocal >> 24 ^ input[offset + 3])]; 54 | 55 | crcLocal = d ^ c ^ b ^ a; 56 | offset += 16; 57 | length -= 16; 58 | } 59 | 60 | while (--length >= 0) 61 | crcLocal = table[(byte)(crcLocal ^ input[offset++])] ^ crcLocal >> 8; 62 | 63 | return crcLocal ^ uint.MaxValue; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/EBMLTreeView.razor: -------------------------------------------------------------------------------- 1 | @using SpawnDev.EBML; 2 | @using SpawnDev.EBML.Elements; 3 | 4 |
5 | 6 | 7 | 8 |
9 | 10 | @code { 11 | 12 | [Parameter] 13 | public SpawnDev.EBML.EBMLDocument? Document { get; set; } 14 | 15 | SpawnDev.EBML.EBMLDocument? _Document = null; 16 | 17 | IEnumerable entries; 18 | protected override void OnInitialized() 19 | { 20 | entries = Document == null ? new List() : Document.Children; 21 | } 22 | protected override void OnParametersSet() 23 | { 24 | base.OnParametersSet(); 25 | if (_Document != Document) 26 | { 27 | _Document = Document; 28 | entries = Document == null ? new List() : Document.Children; 29 | } 30 | } 31 | protected override void OnAfterRender(bool firstRender) 32 | { 33 | base.OnAfterRender(firstRender); 34 | } 35 | void LoadFiles(TreeExpandEventArgs args) 36 | { 37 | var entry = args.Value as ElementBase; 38 | if (entry == null) 39 | { 40 | return; 41 | } 42 | if (Document == null) 43 | { 44 | return; 45 | } 46 | var data = entry is MasterElement masterElement ? masterElement.Children : null; 47 | 48 | args.Children.Data = data; // Directory.EnumerateFileSystemEntries(directory); 49 | args.Children.Text = GetTextForNode; 50 | args.Children.HasChildren = (entry) => entry is MasterElement; 51 | args.Children.Template = FileOrFolderTemplate; 52 | args.Children.Checkable = o => false; 53 | } 54 | string GetTextForNode(object data) 55 | { 56 | var el = data as ElementBase; 57 | return el?.Name ?? "UNKNOWN!"; 58 | } 59 | RenderFragment FileOrFolderTemplate = (context) => builder => 60 | { 61 | var el = context.Value as ElementBase; 62 | var isDirectory = false; 63 | if (el is MasterElement masterElement) 64 | { 65 | isDirectory = true; 66 | } 67 | else 68 | { 69 | 70 | } 71 | builder.OpenComponent(0); 72 | builder.AddAttribute(1, nameof(RadzenIcon.Icon), isDirectory ? "folder" : "insert_drive_file"); 73 | builder.CloseComponent(); 74 | builder.AddContent(3, context.Text); 75 | }; 76 | } -------------------------------------------------------------------------------- /SpawnDev.EBML/ElementTypes/CRC32Element.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | using SpawnDev.PatchStreams; 3 | 4 | namespace SpawnDev.EBML.ElementTypes 5 | { 6 | /// 7 | /// CRC-32 element
8 | ///
9 | [ElementName("ebml", "CRC-32")] 10 | public class CRC32Element : ElementBase 11 | { 12 | /// 13 | /// Returns the element's data as PatchStream stream.
14 | /// Editing the returned stream will not modify the element 15 | ///
16 | public byte[] Data 17 | { 18 | get 19 | { 20 | var bytes = new byte[DataSize]; 21 | Stream.LatestStable.Position = DataOffset; 22 | _ = Stream.LatestStable.Read(bytes); 23 | return bytes; 24 | } 25 | set 26 | { 27 | ReplaceData(value); 28 | } 29 | } 30 | public override string ToString() 31 | { 32 | return "0x" + Convert.ToHexString(Data); 33 | } 34 | /// 35 | /// Calculates a CRC for this element's data 36 | /// 37 | /// 38 | public byte[] CalculateCRC() 39 | { 40 | var parent = Parent; 41 | if (parent == null) throw new Exception("No parent"); 42 | var allButCrc = parent.Children.Where(o => o.Name != "CRC-32").ToList(); 43 | var dataToCRC = allButCrc.Select(o => o.ElementStreamSlice()).ToList(); 44 | using var stream = new PatchStream(dataToCRC); 45 | var hash = CRC.ComputeHash(stream); 46 | return hash; 47 | } 48 | /// 49 | /// Returns true if the CRC-32 value was updated 50 | /// 51 | /// 52 | public bool UpdateCRC() 53 | { 54 | var crc = CalculateCRC(); 55 | var currentCRC = Data; 56 | if (!currentCRC.SequenceEqual(crc)) 57 | { 58 | Data = crc; 59 | return true; 60 | } 61 | return false; 62 | } 63 | public bool VerifyCRC() 64 | { 65 | var crc = CalculateCRC(); 66 | var currentCRC = Data; 67 | return currentCRC.SequenceEqual(crc); 68 | } 69 | /// 70 | /// New instance 71 | /// 72 | /// 73 | public CRC32Element(EBMLDocument document, ElementStreamInfo element) : base(document, element) { } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ConsoleEBMLViewer/Program.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML; 2 | using SpawnDev.EBML.Elements; 3 | using SpawnDev.EBML.ElementTypes; 4 | using SpawnDev.EBML.Matroska; 5 | 6 | var bigFile = @"k:\Video\Alita Battle Angel.mkv"; 7 | var file1 = @"TestData/Big_Buck_Bunny_180 10s.webm"; 8 | var file2 = @"k:\Video\matroska_audio_test.mka"; 9 | 10 | // input stream 11 | using var fileStreamIn = File.Open(bigFile, new FileStreamOptions { Options = FileOptions.Asynchronous, Mode = FileMode.Open, Access = FileAccess.Read, Share = FileShare.Read }); 12 | var existingDoc = new EBMLDocument(fileStreamIn); 13 | 14 | //var info = existingDoc.Find("/Segment/").ToList(); 15 | //var info1 = existingDoc.Find("/Segment/").ToList(); 16 | 17 | //var els1 = await existingDoc.FindAsync("/Segment/", CancellationToken.None).ToListAsync(); 18 | //var els2 = await existingDoc.FindAsync("/Segment/", CancellationToken.None).ToListAsync(); 19 | 20 | var newDoc = new EBMLDocument("webm"); 21 | Console.WriteLine($"DocType: 'webm' == {newDoc.DocType}"); 22 | 23 | Console.WriteLine($"DocType: 'webm' == {newDoc.DocType}"); 24 | var segment = newDoc.Add(); 25 | var cluster = segment.Add(); 26 | // Adds an auto updating CRC-32 element to /Segement,0 27 | segment.AddCRC32(); 28 | newDoc.Add("/Segment/Cluster"); 29 | newDoc.Add("/Segment/Cluster"); 30 | var crc32 = newDoc.First("/Segment,0/CRC-32"); 31 | Console.WriteLine($"CRC: {crc32!.ToString()}"); 32 | newDoc.Add("/Segment/Cluster"); 33 | newDoc.Add("/Segment/Cluster"); 34 | 35 | newDoc.WriteString("/EBML/DocTypeExtensionName", "some ext"); 36 | 37 | newDoc.Add("/Segment/Cluster"); 38 | 39 | // get a specific element type by index 40 | var docTypeEl = newDoc.First("/EBML/DocType"); 41 | var docType = docTypeEl.Data; 42 | 43 | Console.WriteLine($"DocType: 'webm' == {newDoc.DocType}"); 44 | docTypeEl.Data = "matroska"; 45 | Console.WriteLine($"DocType: 'matroska' == {newDoc.DocType}"); 46 | newDoc.Undo(); 47 | Console.WriteLine($"DocType : 'webm' == {newDoc.DocType}"); 48 | newDoc.Redo(); 49 | Console.WriteLine($"DocType: 'matroska' == {newDoc.DocType}"); 50 | newDoc.DocType = "wrongone"; 51 | newDoc.Undo(); // undoes newDoc.DocType = "wrongone"; 52 | newDoc.Undo(); // newDoc.Redo(); 53 | Console.WriteLine($"DocType: 'webm' == {newDoc.DocType}"); 54 | docTypeEl.Remove(); 55 | Console.WriteLine($"DocType: '' == {newDoc.DocType}"); 56 | // newDoc.DocType == null because it does not exist in the document 57 | // docTypeEl.Exists == false, signifying it is no longer a part of a Document, but it still has a snapshot of it's data 58 | newDoc.Undo(); 59 | // restored to 'webm' 60 | // docTypeEl.Exists == true 61 | Console.WriteLine($"DocType: 'webm' == {newDoc.DocType}"); 62 | var nmt = true; -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /SpawnDev.EBML.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.9.34407.89 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpawnDev.EBML", "SpawnDev.EBML\SpawnDev.EBML.csproj", "{DEC7BF8B-AEB0-4A24-8D20-E4514549A2A7}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorEBMLViewer", "BlazorEBMLViewer\BlazorEBMLViewer.csproj", "{70B3DC83-19B7-4B28-9C26-2CD92C66ADA6}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleEBMLViewer", "ConsoleEBMLViewer\ConsoleEBMLViewer.csproj", "{80FBC20A-A3DE-495A-B46A-BCF156BFECFF}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpawnDev.PatchStreams", "..\..\SpawnDev.PatchStreams\SpawnDev.PatchStreams\SpawnDev.PatchStreams\SpawnDev.PatchStreams.csproj", "{DE19AFA3-3D43-41DC-93F9-D7BC27AC083B}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {DEC7BF8B-AEB0-4A24-8D20-E4514549A2A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {DEC7BF8B-AEB0-4A24-8D20-E4514549A2A7}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {DEC7BF8B-AEB0-4A24-8D20-E4514549A2A7}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {DEC7BF8B-AEB0-4A24-8D20-E4514549A2A7}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {70B3DC83-19B7-4B28-9C26-2CD92C66ADA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {70B3DC83-19B7-4B28-9C26-2CD92C66ADA6}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {70B3DC83-19B7-4B28-9C26-2CD92C66ADA6}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {70B3DC83-19B7-4B28-9C26-2CD92C66ADA6}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {80FBC20A-A3DE-495A-B46A-BCF156BFECFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {80FBC20A-A3DE-495A-B46A-BCF156BFECFF}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {80FBC20A-A3DE-495A-B46A-BCF156BFECFF}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {80FBC20A-A3DE-495A-B46A-BCF156BFECFF}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {DE19AFA3-3D43-41DC-93F9-D7BC27AC083B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {DE19AFA3-3D43-41DC-93F9-D7BC27AC083B}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {DE19AFA3-3D43-41DC-93F9-D7BC27AC083B}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {DE19AFA3-3D43-41DC-93F9-D7BC27AC083B}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {73B33837-9A17-4E9D-BC20-A3338431CA0F} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Layout/MainLayout.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Radzen; 3 | using SpawnDev.BlazorJS; 4 | using BlazorEBMLViewer.Components.AppTray; 5 | using BlazorEBMLViewer.Services; 6 | 7 | namespace BlazorEBMLViewer.Layout 8 | { 9 | public partial class MainLayout 10 | { 11 | [Inject] 12 | BlazorJSRuntime JS { get; set; } 13 | [Inject] 14 | NotificationService NotificationService { get; set; } 15 | [Inject] 16 | DialogService DialogService { get; set; } 17 | [Inject] 18 | AppTrayService TrayIconService { get; set; } 19 | [Inject] 20 | protected NavigationManager NavigationManager { get; set; } 21 | [Inject] 22 | MainLayoutService MainLayoutService { get; set; } 23 | [Inject] 24 | ThemeService ThemeService { get; set; } 25 | [Inject] 26 | EBMLSchemaService EBMLSchemaService { get; set; } 27 | 28 | string Title => MainLayoutService.Title; 29 | bool leftSidebarExpanded = false; 30 | bool rightSidebarExpanded = false; 31 | public Type? PageType { get; private set; } 32 | public string PageTypeName => PageType?.Name ?? ""; 33 | public string Location { get; private set; } = ""; 34 | public string? HistoryEntryState { get; private set; } 35 | public DateTime LocationUpdated { get; private set; } = DateTime.MinValue; 36 | protected override void OnInitialized() 37 | { 38 | NavigationManager.LocationChanged += NavigationManager_LocationChanged; 39 | MainLayoutService.OnTitleChanged += MainLayoutService_OnTitleChanged; 40 | } 41 | private void MainLayoutService_OnTitleChanged() 42 | { 43 | StateHasChanged(); 44 | } 45 | private void NavigationManager_LocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) 46 | { 47 | AfterLocationChanged(e.HistoryEntryState); 48 | } 49 | protected override void OnAfterRender(bool firstRender) 50 | { 51 | MainLayoutService.TriggerOnAfterRender(this, firstRender); 52 | if (firstRender) 53 | { 54 | AfterLocationChanged(); 55 | } 56 | } 57 | void AfterLocationChanged(string? historyEntryState = null) 58 | { 59 | var pageType = Body != null && Body.Target != null && Body.Target is RouteView routeView ? routeView.RouteData.PageType : null; 60 | var location = NavigationManager.Uri; 61 | if (PageType == pageType && Location == location) 62 | { 63 | Console.WriteLine($"SendLocationChanged: false"); 64 | return; 65 | } 66 | LocationUpdated = DateTime.Now; 67 | PageType = pageType; 68 | Location = location; 69 | HistoryEntryState = historyEntryState; 70 | Console.WriteLine($"LocationChanged: {PageTypeName} [{HistoryEntryState ?? ""}] {Location}"); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/BinaryElementCellEditor.razor: -------------------------------------------------------------------------------- 1 | @using SpawnDev.BlazorJS.Toolbox 2 | @using SpawnDev.EBML.Elements 3 | 4 | 5 | 6 | 7 | 8 | @code { 9 | [Parameter] 10 | public BinaryElement Element { get; set; } 11 | 12 | [Parameter] 13 | public EventCallback OnChanged { get; set; } 14 | 15 | [Inject] 16 | DialogService DialogService { get; set; } 17 | 18 | async Task Download() 19 | { 20 | try 21 | { 22 | await Task.Delay(50); 23 | var mem = new MemoryStream(); 24 | mem.Position = 0; 25 | var length = mem.Length; 26 | // If this is: 27 | // /Segment/Attachments/AttachedFile/FileData 28 | // then the filename is located here 29 | // /Segment/Attachments/AttachedFile/FileName 30 | var filename = Element.Parent!.ReadString("FileName"); 31 | if (string.IsNullOrWhiteSpace(filename)) 32 | { 33 | filename = $"{Element.Name}.data"; 34 | } 35 | using var tmp = new Blob(new byte[][] { Element.Data.ToArray(true) }); 36 | await tmp.StartDownload(filename); 37 | } 38 | catch 39 | { 40 | 41 | } 42 | } 43 | 44 | async Task OnClick(MouseEventArgs args) 45 | { 46 | var result = await FilePicker.ShowOpenFilePicker(multiple: false); 47 | var file = result?.FirstOrDefault(); 48 | if (file == null) return; 49 | await Task.Delay(50); 50 | using var arrayBuffer = await file.ArrayBuffer(); 51 | var bytes = arrayBuffer.ReadBytes(); 52 | Element.Data = new SpawnDev.PatchStreams.PatchStream(bytes); 53 | var filename = file.Name; 54 | // offer to set the filename field if it exists 55 | var fileNameElement = Element.Parent!.First("FileName"); 56 | var isMkvAttachedFile = Element.Path == @"/Segment/Attachments/AttachedFile/FileData"; 57 | if (fileNameElement != null || isMkvAttachedFile) 58 | { 59 | var confirm = await DialogService.Confirm("Set FileName field?"); 60 | if (confirm == true) 61 | { 62 | if (fileNameElement != null) 63 | { 64 | fileNameElement.Data = filename; 65 | } 66 | else 67 | { 68 | Element.Parent!.AddString("FileName", filename); 69 | } 70 | } 71 | } 72 | await OnChanged.InvokeAsync(); 73 | } 74 | async Task Clear(MouseEventArgs args) 75 | { 76 | var confirm = await DialogService.Confirm("Clear data?"); 77 | if (confirm != true) return; 78 | Element.Data = new SpawnDev.PatchStreams.PatchStream(); 79 | await OnChanged.InvokeAsync(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | .valid.modified:not([type=checkbox]) { 2 | outline: 1px solid #26b050; 3 | } 4 | 5 | .invalid { 6 | outline: 1px solid red; 7 | } 8 | 9 | .validation-message { 10 | color: red; 11 | } 12 | 13 | #blazor-error-ui { 14 | background: lightyellow; 15 | bottom: 0; 16 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 17 | box-sizing: border-box; 18 | display: none; 19 | left: 0; 20 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 21 | position: fixed; 22 | width: 100%; 23 | z-index: 1000; 24 | } 25 | 26 | #blazor-error-ui .dismiss { 27 | cursor: pointer; 28 | position: absolute; 29 | right: 0.75rem; 30 | top: 0.5rem; 31 | } 32 | 33 | .blazor-error-boundary { 34 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 35 | padding: 1rem 1rem 1rem 3.7rem; 36 | color: white; 37 | } 38 | 39 | .blazor-error-boundary::after { 40 | content: "An error has occurred." 41 | } 42 | 43 | .loading-progress { 44 | position: relative; 45 | display: block; 46 | width: 8rem; 47 | height: 8rem; 48 | margin: 20vh auto 1rem auto; 49 | } 50 | 51 | .loading-progress circle { 52 | fill: none; 53 | stroke: #e0e0e0; 54 | stroke-width: 0.6rem; 55 | transform-origin: 50% 50%; 56 | transform: rotate(-90deg); 57 | } 58 | 59 | .loading-progress circle:last-child { 60 | stroke: #1b6ec2; 61 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 62 | transition: stroke-dasharray 0.05s ease-in-out; 63 | } 64 | 65 | .loading-progress-text { 66 | position: absolute; 67 | text-align: center; 68 | font-weight: bold; 69 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 70 | } 71 | 72 | .loading-progress-text:after { 73 | content: var(--blazor-load-percentage-text, "Loading"); 74 | } 75 | 76 | code { 77 | color: #c02d76; 78 | } 79 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Services/FullscreenTrayIconService.cs: -------------------------------------------------------------------------------- 1 | using BlazorEBMLViewer.Components.AppTray; 2 | using Microsoft.AspNetCore.Components.Web; 3 | using Radzen; 4 | using SpawnDev.BlazorJS; 5 | using SpawnDev.BlazorJS.JSObjects; 6 | 7 | namespace BlazorEBMLViewer.Services 8 | { 9 | public class FullscreenTrayIconService : IBackgroundService 10 | { 11 | BlazorJSRuntime JS; 12 | Document? Document; 13 | Window? Window; 14 | AppTrayService TrayIconService; 15 | AppTrayIcon FullscreenTrayIcon; 16 | public FullscreenTrayIconService(BlazorJSRuntime js, AppTrayService trayIconService, DialogService dialogService) 17 | { 18 | JS = js; 19 | TrayIconService = trayIconService; 20 | if (JS.IsWindow) 21 | { 22 | Window = JS.Get("window"); 23 | Document = JS.Get("document"); 24 | Document!.OnFullscreenChange += Document_OnFullscreenChange; 25 | Window!.OnResize += Window_OnResized; 26 | // fullscreen indicator and toggle icon 27 | FullscreenTrayIcon = new AppTrayIcon 28 | { 29 | ClickCallback = FullscreenTrayIcon_ClickCallback, 30 | }; 31 | TrayIconService.Add(FullscreenTrayIcon); 32 | UpdateTrayIcon(); 33 | } 34 | } 35 | void UpdateTrayIcon() 36 | { 37 | var isFullscreen = IsFullscreen; 38 | if (isFullscreen) 39 | { 40 | FullscreenTrayIcon.Title = "Exit Fullscreen"; 41 | FullscreenTrayIcon.Icon = "fullscreen_exit"; 42 | } 43 | else 44 | { 45 | FullscreenTrayIcon.Title = "Enter Fullscreen"; 46 | FullscreenTrayIcon.Icon = "fullscreen"; 47 | } 48 | TrayIconService.StateHasChanged(); 49 | } 50 | void FullscreenTrayIcon_ClickCallback(MouseEventArgs mouseEventArgs) 51 | { 52 | _ = ToggleFullscreen(); 53 | } 54 | public event Action OnFullscreenStateChanged; 55 | void Window_OnResized() 56 | { 57 | // Need this? 58 | } 59 | void Document_OnFullscreenChange() 60 | { 61 | UpdateTrayIcon(); 62 | } 63 | public async Task EnterFullscreen() 64 | { 65 | if (Document == null) return; 66 | try 67 | { 68 | await JS.CallVoidAsync("document.body.requestFullscreen"); 69 | } 70 | catch { } 71 | } 72 | public async Task ExitFullscreen() 73 | { 74 | if (Document == null) return; 75 | try 76 | { 77 | await Document.ExitFullscreen(); 78 | } 79 | catch { } 80 | } 81 | public Element? GetFullscreenElement() => Document?.FullscreenElement; 82 | public async Task ToggleFullscreen() 83 | { 84 | if (IsFullscreen) 85 | { 86 | await ExitFullscreen(); 87 | } 88 | else 89 | { 90 | await EnterFullscreen(); 91 | } 92 | } 93 | public bool IsFullscreen 94 | { 95 | get 96 | { 97 | using var element = GetFullscreenElement(); 98 | return element != null; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Extensions/BigEndian.cs: -------------------------------------------------------------------------------- 1 | namespace SpawnDev.EBML.Extensions 2 | { 3 | public static class BigEndian 4 | { 5 | public static float ToSingle(byte[] bytes, int startIndex = 0) 6 | { 7 | if (!BitConverter.IsLittleEndian) return BitConverter.ToSingle(bytes, startIndex); 8 | return BitConverter.ToSingle(bytes.ReverseSegment(startIndex, sizeof(float))); 9 | } 10 | public static double ToDouble(byte[] bytes, int startIndex = 0) 11 | { 12 | if (!BitConverter.IsLittleEndian) return BitConverter.ToDouble(bytes, startIndex); 13 | return BitConverter.ToDouble(bytes.ReverseSegment(startIndex, sizeof(double))); 14 | } 15 | public static ushort ToUInt16(byte[] bytes, int startIndex = 0) 16 | { 17 | if (!BitConverter.IsLittleEndian) return BitConverter.ToUInt16(bytes, startIndex); 18 | return BitConverter.ToUInt16(bytes.ReverseSegment(startIndex, sizeof(ushort))); 19 | } 20 | public static short ToInt16(byte[] bytes, int startIndex = 0) 21 | { 22 | if (!BitConverter.IsLittleEndian) return BitConverter.ToInt16(bytes, startIndex); 23 | return BitConverter.ToInt16(bytes.ReverseSegment(startIndex, sizeof(short))); 24 | } 25 | public static uint ToUInt32(byte[] bytes, int startIndex = 0) 26 | { 27 | if (!BitConverter.IsLittleEndian) return BitConverter.ToUInt32(bytes, startIndex); 28 | return BitConverter.ToUInt32(bytes.ReverseSegment(startIndex, sizeof(uint))); 29 | } 30 | public static int ToInt32(byte[] bytes, int startIndex = 0) 31 | { 32 | if (!BitConverter.IsLittleEndian) return BitConverter.ToInt32(bytes, startIndex); 33 | return BitConverter.ToInt32(bytes.ReverseSegment(startIndex, sizeof(int))); 34 | } 35 | public static ulong ToUInt64(byte[] bytes, int startIndex = 0) 36 | { 37 | if (!BitConverter.IsLittleEndian) return BitConverter.ToUInt64(bytes, startIndex); 38 | return BitConverter.ToUInt64(bytes.ReverseSegment(startIndex, sizeof(ulong))); 39 | } 40 | public static long ToInt64(byte[] bytes, int startIndex = 0) 41 | { 42 | if (!BitConverter.IsLittleEndian) return BitConverter.ToInt64(bytes, startIndex); 43 | return BitConverter.ToInt64(bytes.ReverseSegment(startIndex, sizeof(long))); 44 | } 45 | public static byte[] ReverseSegment(this byte[] bytes, int startIndex, int size) 46 | { 47 | var chunk = new byte[size]; 48 | for (var i = 0; i < size; i++) chunk[size - (i + 1)] = bytes[startIndex + i]; 49 | return chunk; 50 | } 51 | public static byte[] GetBytes(float value) => BitConverter.GetBytes(value).ReverseIfLittleEndian(); 52 | public static byte[] GetBytes(double value) => BitConverter.GetBytes(value).ReverseIfLittleEndian(); 53 | public static byte[] GetBytes(ushort value) => BitConverter.GetBytes(value).ReverseIfLittleEndian(); 54 | public static byte[] GetBytes(short value) => BitConverter.GetBytes(value).ReverseIfLittleEndian(); 55 | public static byte[] GetBytes(uint value) => BitConverter.GetBytes(value).ReverseIfLittleEndian(); 56 | public static byte[] GetBytes(int value) => BitConverter.GetBytes(value).ReverseIfLittleEndian(); 57 | public static byte[] GetBytes(long value) => BitConverter.GetBytes(value).ReverseIfLittleEndian(); 58 | public static byte[] GetBytes(ulong value) => BitConverter.GetBytes(value).ReverseIfLittleEndian(); 59 | public static byte[] ReverseIfLittleEndian(this byte[] _this) => !BitConverter.IsLittleEndian ? _this : _this.Reverse().ToArray(); 60 | public static byte[] ReverseArray(this byte[] _this) => _this.Reverse().ToArray(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ConsoleEBMLViewer/EBMLConsole.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using System.Collections.Generic; 3 | //using System.Linq; 4 | //using System.Text; 5 | //using System.Threading.Tasks; 6 | //using SpawnDev.EBML; 7 | //using SpawnDev.EBML.Elements; 8 | 9 | //namespace ConsoleEBMLViewer 10 | //{ 11 | // public class MenuOption 12 | // { 13 | // public string Name { get; set; } 14 | // public Action Action { get; set; } 15 | // public Func Available { get; set; } 16 | // public bool IsGlobal { get; set; } 17 | // } 18 | // public class EBMLConsole 19 | // { 20 | // public EBMLParser? ebml = null; 21 | // public Document? Document = null; 22 | // public FileStream? fileStream = null; 23 | // public string CurrentMenu = ""; 24 | // public Dictionary> Menus = new Dictionary>(); 25 | // public async Task RunAsync(string[]? args = null) 26 | // { 27 | // // Create the EBML parser with default configuration 28 | // // default configuration supports matroska and webm reading and modification 29 | // ebml = new EBMLParser(); 30 | // Menus.Add("main", new List 31 | // { 32 | // new MenuOption{ 33 | // Name = "Main menu", 34 | // Available = () => fileStream == null 35 | // }, 36 | // new MenuOption{ 37 | // Name = "Open file", 38 | // Available = () => fileStream == null 39 | // }, 40 | // new MenuOption{ 41 | // Name = "Close file", 42 | // Available = () => fileStream == null 43 | // }, 44 | // }); 45 | // SetMenu(); 46 | // } 47 | // void DrawMenu() 48 | // { 49 | // if (!Menus.TryGetValue(CurrentMenu, out var menu)) 50 | // { 51 | // if (Menus.Count > 0) 52 | // { 53 | // SetMenu(Menus.First().Key); 54 | // } 55 | // return; 56 | // } 57 | 58 | // } 59 | // void SetMenu(string menuName) 60 | // { 61 | // if (Menus.TryGetValue(menuName, out var menu)) 62 | // { 63 | // CurrentMenu = menuName; 64 | // DrawMenu(); 65 | // } 66 | // } 67 | // void OpenFile() 68 | // { 69 | // // get a stream containing an EBML document (or multiple documents) 70 | // var fileStream = File.Open(@"TestData/Big_Buck_Bunny_180 10s.webm", FileMode.Open); 71 | // // parse the EBML document stream (ParseDocuments can be used to parse all documents in the stream) 72 | // var document = ebml.ParseDocument(fileStream); 73 | // if (document != null) 74 | // { 75 | // Console.WriteLine($"DocType: {document.DocType}"); 76 | // // or using path 77 | // Console.WriteLine($"DocType: {document.ReadString(@"/EBML/DocType")}"); 78 | 79 | // // Get an element using the path 80 | // var durationElement = document.GetElement(@"/Segment/Info/Duration"); 81 | // if (durationElement != null) 82 | // { 83 | // var duration = durationElement.Data; 84 | // var durationTime = TimeSpan.FromMilliseconds(duration); 85 | // Console.WriteLine($"Duration: {durationTime}"); 86 | // } 87 | // } 88 | 89 | // // Create a new matroska EBML file 90 | // var matroskaDoc = ebml.CreateDocument("matroska"); 91 | // Console.WriteLine($"DocType: {matroskaDoc.DocType}"); 92 | 93 | // // ... 94 | 95 | // } 96 | // } 97 | //} 98 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Layout/RadzenTheme.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Radzen; 3 | 4 | namespace BlazorEBMLViewer.Layout 5 | { 6 | /// 7 | /// Registers and manages the current theme. Requires to be registered in the DI container. 8 | /// 9 | public partial class RadzenTheme : IDisposable 10 | { 11 | /// 12 | /// Gets or sets the theme. 13 | /// 14 | [Parameter] 15 | public string Theme { get; set; } 16 | 17 | /// 18 | /// Enables WCAG contrast requirements. If set to true additional CSS file will be loaded. 19 | /// 20 | [Parameter] 21 | public bool Wcag { get; set; } 22 | 23 | private string theme; 24 | 25 | private bool wcag; 26 | 27 | private static readonly string Version = typeof(RadzenTheme).Assembly.GetName().Version.ToString(); 28 | 29 | private string Href => $"{Path}/{theme}-base.css?v={Version}"; 30 | 31 | private string WcagHref => $"{Path}/{theme}-wcag.css?v={Version}"; 32 | 33 | private string Path => Embedded ? $"_content/Radzen.Blazor/css" : "css"; 34 | 35 | private bool Embedded => theme switch 36 | { 37 | "material" => true, 38 | "material-dark" => true, 39 | "standard" => true, 40 | "standard-dark" => true, 41 | "humanistic" => true, 42 | "humanistic-dark" => true, 43 | "software" => true, 44 | "software-dark" => true, 45 | "default" => true, 46 | "dark" => true, 47 | _ => false 48 | }; 49 | 50 | private PersistingComponentStateSubscription persistingSubscription; 51 | 52 | /// 53 | protected override void OnInitialized() 54 | { 55 | theme = ThemeService.Theme ?? GetCurrentTheme(); 56 | wcag = ThemeService.Wcag ?? Wcag; 57 | 58 | ThemeService.SetTheme(theme, true); 59 | 60 | theme = theme.ToLowerInvariant(); 61 | 62 | ThemeService.ThemeChanged += OnThemeChanged; 63 | 64 | persistingSubscription = PersistentComponentState.RegisterOnPersisting(PersistTheme); 65 | 66 | base.OnInitialized(); 67 | } 68 | 69 | private string GetCurrentTheme() 70 | { 71 | if (PersistentComponentState.TryTakeFromJson(nameof(Theme), out string theme)) 72 | { 73 | return theme; 74 | } 75 | 76 | return Theme; 77 | } 78 | 79 | private Task PersistTheme() 80 | { 81 | PersistentComponentState.PersistAsJson(nameof(Theme), theme); 82 | 83 | return Task.CompletedTask; 84 | } 85 | 86 | private void OnThemeChanged() 87 | { 88 | var requiresChange = false; 89 | 90 | var newTheme = ThemeService.Theme.ToLowerInvariant(); 91 | 92 | if (theme != newTheme) 93 | { 94 | theme = newTheme; 95 | requiresChange = true; 96 | } 97 | 98 | var newWcag = ThemeService.Wcag ?? Wcag; 99 | 100 | if (wcag != newWcag) 101 | { 102 | wcag = newWcag; 103 | requiresChange = true; 104 | } 105 | 106 | if (requiresChange) 107 | { 108 | StateHasChanged(); 109 | } 110 | } 111 | 112 | /// 113 | /// Releases all resources used by the component. 114 | /// 115 | public void Dispose() 116 | { 117 | ThemeService.ThemeChanged -= OnThemeChanged; 118 | persistingSubscription.Dispose(); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /BlazorEBMLViewer/Services/BatteryTrayIconService.cs: -------------------------------------------------------------------------------- 1 | using BlazorEBMLViewer.Components.AppTray; 2 | using Microsoft.AspNetCore.Components.Web; 3 | using Radzen; 4 | using SpawnDev.BlazorJS; 5 | using SpawnDev.BlazorJS.JSObjects; 6 | using SpawnDev.BlazorJS.Toolbox; 7 | 8 | namespace BlazorEBMLViewer.Services 9 | { 10 | public class BatteryTrayIconService : IAsyncBackgroundService 11 | { 12 | public Task Ready => _Ready ??= InitAsync(); 13 | private Task? _Ready = null; 14 | BlazorJSRuntime JS; 15 | AppTrayService TrayIconService; 16 | AppTrayIcon BatteryTrayIcon; 17 | BatteryManager? BatteryManager = null; 18 | public BatteryTrayIconService(BlazorJSRuntime js, AppTrayService trayIconService, DialogService dialogService) 19 | { 20 | JS = js; 21 | TrayIconService = trayIconService; 22 | if (JS.IsWindow) 23 | { 24 | // battery indicator 25 | BatteryTrayIcon = new AppTrayIcon 26 | { 27 | Icon = "battery", 28 | Visible = false, 29 | }; 30 | TrayIconService.Add(BatteryTrayIcon); 31 | } 32 | } 33 | async Task InitAsync() 34 | { 35 | using var navigator = JS.Get("navigator"); 36 | BatteryManager = await navigator.GetBattery(); 37 | if (BatteryManager != null) 38 | { 39 | BatteryTrayIcon.Visible = true; 40 | BatteryManager.OnChargingChange += BatteryManager_OnChargingChange; 41 | BatteryManager.OnLevelChange += BatteryManager_OnLevelChange; 42 | BatteryManager.OnChargingTimeChange += BatteryManager_OnChargingTimeChange; 43 | BatteryManager.OnDischargingTimeChange += BatteryManager_OnDischargingTimeChange; 44 | UpdateBatteryIcon(); 45 | } 46 | } 47 | void UpdateBatteryIcon() 48 | { 49 | if (BatteryManager == null) return; 50 | try 51 | { 52 | var level = BatteryManager.Level; 53 | var charging = BatteryManager.Charging; 54 | var chargingTime = BatteryManager.ChargingTime; 55 | var dischargingTime = BatteryManager.DischargingTime; 56 | BatteryTrayIcon.Visible = !(charging && level >= 1f); 57 | BatteryTrayIcon.IconStyle = charging ? IconStyle.Info : IconStyle.Warning; 58 | if (level >= 0.9f) 59 | { 60 | BatteryTrayIcon.Icon = "battery_full"; 61 | } 62 | else if (level >= 0.8f) 63 | { 64 | BatteryTrayIcon.Icon = "battery_5_bar"; 65 | } 66 | else if (level >= 0.6f) 67 | { 68 | BatteryTrayIcon.Icon = "battery_4_bar"; 69 | } 70 | else if (level >= 0.4f) 71 | { 72 | BatteryTrayIcon.Icon = "battery_3_bar"; 73 | } 74 | else if (level >= 0.2f) 75 | { 76 | BatteryTrayIcon.Icon = "battery_2_bar"; 77 | } 78 | else 79 | { 80 | BatteryTrayIcon.Icon = "battery_1_bar"; 81 | } 82 | TrayIconService.StateHasChanged(); 83 | Console.WriteLine($"Battery: {level} {charging} {chargingTime} {dischargingTime}"); 84 | } 85 | catch (Exception ex) 86 | { 87 | Console.WriteLine($"Battery error: {ex.Message}"); 88 | } 89 | } 90 | void BatteryManager_OnDischargingTimeChange() 91 | { 92 | UpdateBatteryIcon(); 93 | } 94 | void BatteryManager_OnChargingTimeChange() 95 | { 96 | UpdateBatteryIcon(); 97 | } 98 | void BatteryManager_OnLevelChange() 99 | { 100 | UpdateBatteryIcon(); 101 | } 102 | void BatteryManager_OnChargingChange() 103 | { 104 | UpdateBatteryIcon(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Schemas/Schema.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace SpawnDev.EBML.Schemas 4 | { 5 | /// 6 | /// An EBML Schema is a well-formed XML Document [@!XML] that defines the properties, arrangement, and usage of EBML Elements that compose a specific EBML Document Type. The relationship of an EBML Schema to an EBML Document is analogous to the relationship of an XML Schema [@!XML-SCHEMA] to an XML Document [@!XML]. An EBML Schema MUST be clearly associated with one or more EBML Document Types. An EBML Document Type is identified by a string stored within the EBML Header in the DocType Element -- for example, Matroska or WebM (see (#doctype-element)). The DocType value for an EBML Document Type MUST be unique, persistent, and described in the IANA registry (see (#ebml-doctypes-registry)). 7 | /// 8 | public class Schema 9 | { 10 | /// 11 | /// Within an EBML Schema, the XPath of the @docType attribute is /EBMLSchema/@docType.
12 | /// The docType lists the official name of the EBML Document Type that is defined by the EBML Schema; for example, <EBMLSchema docType="matroska">.
13 | /// The docType attribute is REQUIRED within the <EBMLSchema> Element. 14 | ///
15 | public string DocType { get; private set; } 16 | /// 17 | /// Within an EBML Schema, the XPath of the @version attribute is /EBMLSchema/@version. 18 | /// 19 | public string? Version { get; private set; } 20 | /// 21 | /// Contains elements defined for this DocType in the parsed XML schema 22 | /// 23 | public Dictionary Elements { get; } = new Dictionary(); 24 | /// 25 | /// Creates a new empty schema with the specified DocType 26 | /// 27 | /// 28 | /// 29 | public Schema(string docType, string? version = null) 30 | { 31 | DocType = docType; 32 | Version = version; 33 | } 34 | /// 35 | /// Returns a list of Schemas found in the specified XML 36 | /// 37 | /// 38 | /// 39 | public static List FromXML(string xml) 40 | { 41 | var ret = new List(); 42 | var xdoc = XDocument.Parse(xml); 43 | var nodes = xdoc.Elements().ToList(); 44 | foreach (var node in nodes) 45 | { 46 | if (node.Name.LocalName == "EBMLSchema") 47 | { 48 | var schema = FromXML(node); 49 | if (schema != null) ret.Add(schema); 50 | } 51 | } 52 | return ret; 53 | } 54 | /// 55 | /// Returns a Schema from the specified XElement 56 | /// 57 | /// 58 | /// 59 | public static Schema FromXML(XElement schemaRoot) 60 | { 61 | var nodes = schemaRoot.Elements(); 62 | var docType = schemaRoot.Attribute("docType")!.Value; 63 | var version = schemaRoot.Attribute("version")?.Value; 64 | var ret = new Schema(docType, version); 65 | var tmp = new Dictionary(); 66 | foreach (var node in nodes) 67 | { 68 | if (node.Name.LocalName == "element") 69 | { 70 | var idHex = node.Attribute("id")?.Value; 71 | if (idHex == null) continue; 72 | if (idHex.StartsWith("0x")) idHex = idHex.Substring(2); 73 | var idBytes = Convert.FromHexString(idHex).ToList(); 74 | idBytes.Reverse(); 75 | while (idBytes.Count < 8) idBytes.Add(0); 76 | var id = BitConverter.ToUInt64(idBytes.ToArray()); 77 | var el = new SchemaElement(docType, id, node); 78 | tmp.Add(el.Id, el); 79 | } 80 | } 81 | var list = tmp.ToList(); 82 | list.Sort((pair1, pair2) => pair1.Value.Name.CompareTo(pair2.Value.Name)); 83 | foreach (var item in list) 84 | { 85 | ret.Elements.Add(item.Key, item.Value); 86 | } 87 | return ret; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Pages/Home.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @using SpawnDev.EBML 3 | @using SpawnDev.EBML.Elements 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | @foreach (var schema in EBMLSchemaService.Parser.Schemas.Values) 16 | { 17 | 18 | } 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 |
37 | 38 | 39 |
40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 |
48 | 49 | Definition: @Grid?.Selected?.SchemaElement?.Definition 50 | 51 |
52 |
53 | 54 |
55 | @if (Grid.Selected is BinaryElement binaryElement) 56 | { 57 | 58 | } 59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | 68 |
69 |
70 |
-------------------------------------------------------------------------------- /SpawnDev.EBML/Schemas/ebml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Set the EBML characteristics of the data to follow. Each EBML document has to start with this. 4 | 5 | 6 | The version of EBML parser used to create the file. 7 | 8 | 9 | The minimum EBML version a parser has to support to read this file. 10 | 11 | 12 | The maximum length of the IDs you'll find in this file (4 or less in Matroska). 13 | 14 | 15 | 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. 16 | 17 | 18 | A string that describes the type of document that follows this EBML header, for example 'matroska' or 'webm'. 19 | 20 | 21 | The version of DocType interpreter used to create the file. 22 | 23 | 24 | The minimum DocType version an interpreter has to support to read this file. 25 | 26 | 27 | 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. 28 | 29 | 30 | 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. 31 | 32 | 33 | 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. 34 | 35 | 36 | 37 | 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. 38 | 39 | 40 | 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. 41 | 42 | 43 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Services/UndoService.cs: -------------------------------------------------------------------------------- 1 | //namespace BlazorEBMLViewer.Services 2 | //{ 3 | // public class Undoable 4 | // { 5 | // public Action DoItCb { get; set; } 6 | // public Action? UndoItCb { get; set; } 7 | // public Func? CanUndoCb { get; set; } 8 | // public Func? CanDoCb { get; set; } 9 | // public string Name { get; set; } 10 | // public bool CanUndo => !Undone && UndoItCb != null && (CanUndoCb == null || CanUndoCb()); 11 | // public bool CanDo => Undone && DoItCb != null && (CanDoCb == null || CanDoCb()); 12 | // public bool Undone { get; private set; } = true; 13 | // public bool DoIt() 14 | // { 15 | // if (!CanDo) return false; 16 | // DoItCb(); 17 | // Undone = false; 18 | // return true; 19 | // } 20 | // public bool UndoIt() 21 | // { 22 | // if (!CanUndo) return false; 23 | // UndoItCb!.Invoke(); 24 | // Undone = true; 25 | // return true; 26 | // } 27 | // public Undoable() { } 28 | // public Undoable(Action doItCb, Action? undoItCb = null) => (DoItCb, UndoItCb) = (doItCb, undoItCb); 29 | // } 30 | // //public class AsyncUndoable 31 | // //{ 32 | // // public Func DoItCb { get; set; } 33 | // // public Func UndoItCb { get; set; } 34 | // // public Func> CanUndoCb { get; set; } 35 | // // public bool CanUndo => UndoItCb != null &&() 36 | // // public bool Undone { get; private set; } 37 | // // public string Name { get; set; } 38 | // // public void DoIt() 39 | // // { 40 | // // undoable.DoIt(); 41 | // // Undone = false; 42 | // // } 43 | // //} 44 | // public class UndoService 45 | // { 46 | // public event Action OnStateHasChanged; 47 | // public bool CanUndo => Undoable?.CanUndo ?? false; 48 | // public bool CanRedo => Redoable?.CanDo ?? false; 49 | // public string UndoName => Undoable?.Name ?? ""; 50 | // public string RedoName => Redoable?.Name ?? ""; 51 | // public Undoable? Undoable => Index >= 0 && Undoables.Count > Index ? Undoables[Index] : null; 52 | // public Undoable? Redoable => RedoableIndex >= 0 && Undoables.Count > RedoableIndex ? Undoables[RedoableIndex] : null; 53 | // List Undoables { get; set; } = new List(); 54 | // public int Index 55 | // { 56 | // get 57 | // { 58 | // for(var i = Undoables.Count - 1; i >= 0; i--) 59 | // { 60 | // var undoable = Undoables[i]; 61 | // if (!undoable.Undone) return i; 62 | // } 63 | // return -1; 64 | // } 65 | // } 66 | // public int RedoableIndex 67 | // { 68 | // get 69 | // { 70 | // for (var i = Undoables.Count - 1; i >= 0; i--) 71 | // { 72 | // var undoable = Undoables[i]; 73 | // if (undoable.Undone) return i; 74 | // } 75 | // return -1; 76 | // } 77 | // } 78 | // public void StateHasChanged() => OnStateHasChanged?.Invoke(); 79 | // /// 80 | // /// Adding a new undoable removes all undone undoables from the stack 81 | // /// 82 | // /// 83 | // public void Do(Undoable undoable) 84 | // { 85 | // if (!undoable.DoIt()) return; 86 | // if (!undoable.CanUndo) return; 87 | // while (Undoables.Count > 0 && Undoables.Last().Undone) 88 | // { 89 | // Undoables.RemoveAt(Undoables.Count - 1); 90 | // } 91 | // Undoables.Add(undoable); 92 | // StateHasChanged(); 93 | // } 94 | // public void Do(Action doItCb, Action undoItCb) 95 | // { 96 | // Do(new Undoable(doItCb, undoItCb)); 97 | // } 98 | // public void Do(string name, Action doItCb, Action undoItCb) 99 | // { 100 | // Do(new Undoable(doItCb, undoItCb) { Name = name }); 101 | // } 102 | // public bool Redo() 103 | // { 104 | // if (!CanRedo) return false; 105 | // var active = Redoable; 106 | // var ret = active != null && active.DoIt(); 107 | // StateHasChanged(); 108 | // return ret; 109 | // } 110 | // public bool Undo() 111 | // { 112 | // if (!CanUndo) return false; 113 | // var active = Undoable; 114 | // var ret = active != null && active.UndoIt(); 115 | // StateHasChanged(); 116 | // return ret; 117 | // } 118 | // public void Clear() 119 | // { 120 | // Undoables.Clear(); 121 | // StateHasChanged(); 122 | // } 123 | // } 124 | //} 125 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Engines/MatroskaEngine.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Elements; 2 | using SpawnDev.EBML.Extensions; 3 | 4 | namespace SpawnDev.EBML.Engines 5 | { 6 | /// 7 | /// Matroska EBML document engine 8 | /// 9 | public class MatroskaEngine : DocumentEngine 10 | { 11 | /// 12 | /// This constructor can take an existing EBMLDocument and parse it
13 | /// This is used by the generic Parser 14 | ///
15 | public MatroskaEngine(EBMLDocument document) : base(document) 16 | { 17 | 18 | } 19 | public List DefaultSeekHeadTargets = new List { "Info", "Tracks", "Chapters", "Cues", "Attachments" }; 20 | public bool AutoPopulateSeekDefaultTargets { get; set; } = true; 21 | string[] DocTypes = new[] { "matroska", "webm" }; 22 | public override void DocumentCheck(List changedElements) 23 | { 24 | if (!DocTypes.Contains(Document.DocType)) return; 25 | //var issues = new List(); 26 | //var foundSeekTargetElementNames = new List(); 27 | //// verify seek data 28 | //var segmentElement = Document.GetContainer("Segment"); 29 | //if (segmentElement == null) return; 30 | //var segmentStart = segmentElement.DataOffset; 31 | //var seekHeadElements = segmentElement.GetContainers("SeekHead"); 32 | //foreach (var seekHeadElement in seekHeadElements) 33 | //{ 34 | // var seekElements = seekHeadElement.GetContainers("Seek"); 35 | // foreach (var seekElement in seekElements) 36 | // { 37 | // var seekIdBytes = seekElement.ReadBinary("SeekID"); 38 | // if (seekIdBytes != null) 39 | // { 40 | // var seekId = EBMLConverter.ToUInt(seekIdBytes); 41 | // var targetElement = segmentElement.Data.FirstOrDefault(e => e.Id == seekId); 42 | // if (targetElement != null) 43 | // { 44 | // foundSeekTargetElementNames.Add(targetElement.Name); 45 | // var seekPosition = seekElement.ReadUint("SeekPosition"); 46 | // var targetPosition = targetElement.Offset; 47 | // if (seekPosition == null || seekPosition.Value + segmentStart != targetPosition) 48 | // { 49 | // var correctSeekPosition = targetPosition - segmentStart; 50 | // var diff = correctSeekPosition - targetPosition; 51 | // Log($"Seek position is off by {diff}. Fixing seek position."); 52 | // seekElement.UpdateUint("SeekPosition", correctSeekPosition); 53 | // } 54 | // else 55 | // { 56 | // // correct. nothing to do 57 | // Log($"Seek position verified for {targetElement.Name}"); 58 | // } 59 | // } 60 | // else 61 | // { 62 | // // Target is missing! 63 | // // remove seek? 64 | // Log("Seek target is missing. Removing seek element."); 65 | // seekElement.Remove(); 66 | // } 67 | // } 68 | // } 69 | //} 70 | //// if auto-populate verify Seek elements exist for "Info", "Tracks", "Chapters", "Cues", "Attachments" 71 | //if (AutoPopulateSeekDefaultTargets) 72 | //{ 73 | // if (seekHeadElements.Count() > 0) 74 | // { 75 | // var targetNamesToAdd = DefaultSeekHeadTargets.Except(foundSeekTargetElementNames).ToList(); 76 | // var seekHeadElement = seekHeadElements.First(); 77 | // foreach (var targetName in targetNamesToAdd) 78 | // { 79 | // var targetElement = segmentElement.GetContainer(targetName); 80 | // if (targetElement != null) 81 | // { 82 | // var seekPosition = targetElement.Offset - segmentStart; 83 | // var seekElement = seekHeadElement.CreateContainer("Seek"); 84 | // var seekIdBytes = EBMLConverter.ToUIntBytes(targetElement.Id); 85 | // seekElement.AddBinary("SeekID", seekIdBytes); 86 | // seekElement.AddUint("SeekPosition", seekPosition); 87 | // seekHeadElement.AddElement(seekElement); 88 | // Log($"Seek added for {targetElement.Name}"); 89 | // } 90 | // } 91 | // } 92 | //} 93 | } 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/EBMLDataGrid.razor: -------------------------------------------------------------------------------- 1 | @using SpawnDev.EBML 2 | @using SpawnDev.EBML.Elements 3 | @using System.Linq.Dynamic.Core 4 | 5 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 32 | 33 | @switch (detail.Type) 34 | { 35 | case "master": 36 | { 37 | 38 | break; 39 | } 40 | case "date": 41 | { 42 | var el = detail as DateElement; 43 | 44 | break; 45 | } 46 | case "binary": 47 | { 48 | var el = detail as BinaryElement; 49 | 50 | break; 51 | } 52 | case "float": 53 | { 54 | var el = detail as FloatElement; 55 | 56 | break; 57 | } 58 | case "uinteger": 59 | { 60 | var el = detail as UintElement; 61 | 62 | break; 63 | } 64 | case "integer": 65 | { 66 | var el = detail as IntElement; 67 | 68 | break; 69 | } 70 | case "utf-8": 71 | case "string": 72 | { 73 | var el = detail as StringElement; 74 | 75 | break; 76 | } 77 | } 78 | 79 | 80 | 81 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Services/ThemeTrayIconService.cs: -------------------------------------------------------------------------------- 1 | using BlazorEBMLViewer.Components.AppTray; 2 | using Microsoft.AspNetCore.Components.Web; 3 | using Radzen; 4 | using SpawnDev.BlazorJS; 5 | using SpawnDev.BlazorJS.JSObjects; 6 | 7 | namespace BlazorEBMLViewer.Services 8 | { 9 | public class ThemeTrayIconService : IBackgroundService 10 | { 11 | BlazorJSRuntime JS; 12 | Storage? LocalStorage; 13 | AppTrayService TrayIconService; 14 | AppTrayIcon ThemeTrayIcon; 15 | ThemeService ThemeService; 16 | ContextMenuService ContextMenuService; 17 | public string Theme => ThemeService.Theme; 18 | public bool IsDarkTheme => GetIsThemeDark(ThemeService.Theme); 19 | public string LightTheme => GetLightTheme(ThemeService.Theme); 20 | public string DarkTheme => GetDarkTheme(ThemeService.Theme); 21 | public string ThemeName => GetThemeName(ThemeService.Theme); 22 | // (LightTheme, DarkTheme, ThemeName) 23 | public List<(string, string, string)> Themes { get; } = new List<(string, string, string)> 24 | { 25 | ("default" , "dark", "Default"), 26 | ("material" , "material-dark", "Material"), 27 | ("software" , "software-dark", "Software"), 28 | ("humanistic" , "humanistic-dark", "Humanistic"), 29 | ("standard" , "standard-dark", "Standard"), 30 | // Not working with free Radzen 31 | //("fluent" , "fluent-dark", "Fluent"), 32 | //("material3" , "material3-dark", "Material 3"), 33 | }; 34 | bool DefaultThemeIsDark = false; 35 | public ThemeTrayIconService(BlazorJSRuntime js, AppTrayService trayIconService, ContextMenuService contextMenuService, ThemeService themeService) 36 | { 37 | ContextMenuService = contextMenuService; 38 | ThemeService = themeService; 39 | JS = js; 40 | TrayIconService = trayIconService; 41 | DefaultThemeIsDark = GetIsThemeDark(ThemeService.Theme); 42 | if (JS.IsWindow) 43 | { 44 | using var window = JS.Get("window"); 45 | LocalStorage = window.LocalStorage; 46 | // Theme icon 47 | ThemeTrayIcon = new AppTrayIcon 48 | { 49 | ClickCallback = ThemeTrayIcon_ClickCallback, 50 | ContextCallback = ThemeTrayIcon_ContextCallback, 51 | Icon = GetThemeIcon(), 52 | Visible = true, 53 | }; 54 | TrayIconService.Add(ThemeTrayIcon); 55 | ThemeService.ThemeChanged += ThemeService_ThemeChanged; 56 | LoadUserTheme(); 57 | } 58 | JS.Log("Current theme", ThemeService.Theme); 59 | } 60 | void SaveUserTheme() 61 | { 62 | if (LocalStorage == null) return; 63 | LocalStorage.SetItem("theme", ThemeService.Theme); 64 | } 65 | int GetThemeIndex(string theme) 66 | { 67 | for (var i = 0; i < Themes.Count; i++) 68 | { 69 | var themePair = Themes[i]; 70 | if (themePair.Item1.Equals(theme, StringComparison.OrdinalIgnoreCase) || themePair.Item2.Equals(theme, StringComparison.OrdinalIgnoreCase)) return i; 71 | } 72 | return -1; 73 | } 74 | void LoadUserTheme() 75 | { 76 | if (LocalStorage == null) return; 77 | var theme = LocalStorage.GetItem("theme"); 78 | if (!string.IsNullOrEmpty(theme)) 79 | { 80 | ThemeService.SetTheme(theme); 81 | } 82 | } 83 | string GetThemeIcon() => GetIsThemeDark(ThemeService.Theme) ? "dark_mode" : "light_mode"; 84 | bool GetIsThemeDark(string themeName) => themeName != null && (themeName.ToLowerInvariant().StartsWith("dark") || themeName.ToLowerInvariant().Contains("-dark")); 85 | private string GetLightTheme(string theme) 86 | { 87 | var entry = GetThemeEntry(theme) ?? Themes[0]; 88 | return entry.Item1; 89 | } 90 | private string GetDarkTheme(string theme) 91 | { 92 | var entry = GetThemeEntry(theme) ?? Themes[0]; 93 | return entry.Item2; 94 | } 95 | private string GetThemeName(string theme) 96 | { 97 | var entry = GetThemeEntry(theme) ?? Themes[0]; 98 | return entry.Item3; 99 | } 100 | private (string, string, string)? GetThemeEntry(string theme) 101 | { 102 | var i = GetThemeIndex(theme); 103 | if (i == -1) return null; 104 | return Themes[i]; 105 | } 106 | private void ThemeService_ThemeChanged() 107 | { 108 | SaveUserTheme(); 109 | ThemeTrayIcon.Icon = GetThemeIcon(); 110 | ThemeTrayIcon.Title = IsDarkTheme ? $"{ThemeName} Dark" : ThemeName; 111 | TrayIconService.StateHasChanged(); 112 | } 113 | void ThemeMenu_Click(MenuItemEventArgs args) 114 | { 115 | var entry = ((string, string, string))args.Value; 116 | ContextMenuService.Close(); 117 | var theme = GetIsThemeDark(ThemeService.Theme) ? entry.Item2 : entry.Item1; 118 | ThemeService.SetTheme(theme); 119 | } 120 | IEnumerable GetThemeMenuOptions() 121 | { 122 | var menuItems = new List(); 123 | foreach (var entry in Themes) 124 | { 125 | var isCurrentTheme = entry.Item1.Equals(ThemeService.Theme, StringComparison.OrdinalIgnoreCase) || entry.Item2.Equals(ThemeService.Theme, StringComparison.OrdinalIgnoreCase); 126 | menuItems.Add(new ContextMenuItem 127 | { 128 | Text = entry.Item3, 129 | Value = entry, 130 | Icon = isCurrentTheme ? "radio_button_checked" : "radio_button_unchecked", 131 | }); 132 | } 133 | return menuItems; 134 | } 135 | void ThemeTrayIcon_ContextCallback(MouseEventArgs mouseEventArgs) 136 | { 137 | ContextMenuService.Open(mouseEventArgs, GetThemeMenuOptions(), ThemeMenu_Click); 138 | } 139 | void ThemeTrayIcon_ClickCallback(MouseEventArgs mouseEventArgs) 140 | { 141 | if (mouseEventArgs.ShiftKey) 142 | { 143 | NextTheme(false); 144 | } 145 | else if (mouseEventArgs.CtrlKey) 146 | { 147 | NextTheme(true); 148 | } 149 | else 150 | { 151 | ToggleDark(); 152 | } 153 | } 154 | public void NextTheme(bool reverse = false) 155 | { 156 | var theme = ThemeService.Theme?.ToLowerInvariant() ?? ""; 157 | var isDark = GetIsThemeDark(theme); 158 | var themes = Themes.SelectMany(o => DefaultThemeIsDark ? new string[] { o.Item2, o.Item1 } : new string[] { o.Item1, o.Item2 }).ToList(); 159 | var i = themes.IndexOf(theme); 160 | if (i == -1) i = 0; 161 | i += reverse ? -1 : 1; 162 | if (i < 0) i = themes.Count - 1; 163 | if (i >= themes.Count) i = 0; 164 | var newTheme = themes[i]; 165 | ThemeService.SetTheme(newTheme); 166 | } 167 | public void SetDark() => ThemeService.SetTheme(DarkTheme); 168 | public void SetTheme(string theme) => ThemeService.SetTheme(theme); 169 | public void SetLight() => ThemeService.SetTheme(LightTheme); 170 | public void ToggleDark() 171 | { 172 | ThemeService.SetTheme(IsDarkTheme ? LightTheme : DarkTheme); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/ElementHeader.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | 3 | namespace SpawnDev.EBML.Extensions 4 | { 5 | /// 6 | /// EBML element header 7 | /// 8 | public class ElementHeader 9 | { 10 | /// 11 | /// Element Id 12 | /// 13 | public ulong Id { get; set; } 14 | /// 15 | /// Element size 16 | /// 17 | public ulong? Size { get; set; } 18 | /// 19 | /// The number of bytes Size occupied when read
20 | ///
21 | public int SizeLength { get; set; } 22 | /// 23 | /// The minimum number of bytes for Size to occupy when written
24 | /// This will default to SizeLength 25 | ///
26 | public int SizeMinLength { get; set; } 27 | /// 28 | /// Creates a new instance 29 | /// 30 | /// 31 | /// 32 | /// 33 | public ElementHeader(ulong id, ulong? size, int sizeLength = 0) 34 | { 35 | Id = id; 36 | Size = size; 37 | SizeLength = sizeLength; 38 | SizeMinLength = sizeLength; 39 | } 40 | /// 41 | /// Copy to a stream 42 | /// 43 | /// 44 | /// 45 | public int CopyTo(Stream data) => Write(data, Id, Size, SizeMinLength); 46 | /// 47 | /// Copy to a stream 48 | /// 49 | /// 50 | /// 51 | /// 52 | public Task CopyToAsync(Stream data, CancellationToken cancellationToken) => WriteAsync(data, Id, Size, cancellationToken, SizeMinLength); 53 | /// 54 | /// To a byte array 55 | /// 56 | /// 57 | public byte[] ToArray() => ToArray(Id, Size, SizeMinLength); 58 | /// 59 | /// Write to a stream 60 | /// 61 | /// 62 | /// 63 | /// 64 | /// 65 | /// 66 | public static int Write(Stream data, ulong id, ulong? size, int sizeMinLength = 1) 67 | { 68 | var bytes = ToArray(id, size, sizeMinLength); 69 | ulong sizeL = size == null ? EBMLConverter.GetUnknownSizeValue(Math.Min(sizeMinLength, 0)) : size.Value; 70 | var sizeBytes = EBMLConverter.ToVINTBytes(sizeL, sizeMinLength); 71 | bytes = bytes.Concat(sizeBytes).ToArray(); 72 | data.Write(bytes); 73 | return bytes.Length; 74 | } 75 | /// 76 | /// Write to a stream 77 | /// 78 | /// 79 | /// 80 | /// 81 | /// 82 | /// 83 | /// 84 | public static async Task WriteAsync(Stream data, ulong id, ulong? size, CancellationToken cancellationToken, int sizeMinLength = 1) 85 | { 86 | var bytes = ToArray(id, size, sizeMinLength); 87 | ulong sizeL = size == null ? EBMLConverter.GetUnknownSizeValue(Math.Min(sizeMinLength, 0)) : size.Value; 88 | var sizeBytes = EBMLConverter.ToVINTBytes(sizeL, sizeMinLength); 89 | bytes = bytes.Concat(sizeBytes).ToArray(); 90 | await data.WriteAsync(bytes, cancellationToken); 91 | return bytes.Length; 92 | } 93 | /// 94 | /// To a byte array 95 | /// 96 | /// 97 | /// 98 | /// 99 | /// 100 | public static byte[] ToArray(ulong id, ulong? size, int sizeMinLength = 1) 101 | { 102 | var bytes = EBMLConverter.ToUIntBytes(id); 103 | ulong sizeL = size == null ? EBMLConverter.GetUnknownSizeValue(Math.Min(sizeMinLength, 0)) : size.Value; 104 | var sizeBytes = EBMLConverter.ToVINTBytes(sizeL, sizeMinLength); 105 | bytes = bytes.Concat(sizeBytes).ToArray(); 106 | return bytes; 107 | } 108 | /// 109 | /// Read from a stream 110 | /// 111 | /// 112 | /// 113 | public static ElementHeader Read(Stream data) 114 | { 115 | #if false 116 | // reads bytes as needed. possibly 4 reads. first is 1 byte.... 117 | // chunk read alternative is faster 118 | var id = data.ReadEBMLElementIdRaw(); 119 | var pos = data.Position; 120 | var size = data.ReadEBMLElementSizeN(); 121 | var sizeLength = data.Position - pos; 122 | #else 123 | var start = data.Position; 124 | var chunkSize = Math.Min(data.Length - start, 12); 125 | if (chunkSize < 2) throw new Exception("End of stream"); 126 | var chunk = new byte[chunkSize]; 127 | var bytesRead = data.Read(chunk, 0, (int)chunkSize); 128 | if (bytesRead != chunkSize) throw new Exception("End of stream"); 129 | var headerSize = EBMLConverter.ReadElementHeader(chunk, out var id, out var size, out var sizeLength); 130 | if (bytesRead == 0) throw new Exception("Invalid data"); 131 | data.Position = start + headerSize; 132 | #endif 133 | return new ElementHeader(id, size, (int)sizeLength); 134 | } 135 | /// 136 | /// Read from a stream 137 | /// 138 | /// 139 | /// 140 | /// 141 | public static async Task ReadAsync(Stream data, CancellationToken cancellationToken) 142 | { 143 | #if false 144 | // reads bytes as needed. possibly 4 reads. first is 1 byte.... 145 | // chunk read alternative is faster 146 | var id = await data.ReadEBMLElementIdRawAsync(cancellationToken); 147 | var pos = data.Position; 148 | var size = await data.ReadEBMLElementSizeNAsync(cancellationToken); 149 | var sizeLength = data.Position - pos; 150 | #else 151 | var start = data.Position; 152 | var chunkSize = Math.Min(data.Length - start, 12); 153 | if (chunkSize < 2) throw new Exception("End of stream"); 154 | var chunk = new byte[chunkSize]; 155 | var bytesRead = await data.ReadAsync(chunk, 0, (int)chunkSize, cancellationToken); 156 | if (bytesRead != chunkSize) throw new Exception("End of stream"); 157 | var headerSize = EBMLConverter.ReadElementHeader(chunk, out var id, out var size, out var sizeLength); 158 | if (bytesRead == 0) throw new Exception("Invalid data"); 159 | data.Position = start + headerSize; 160 | #endif 161 | return new ElementHeader(id, size, (int)sizeLength); 162 | } 163 | /// 164 | /// Read from a stream 165 | /// 166 | /// 167 | /// 168 | /// 169 | /// 170 | /// 171 | public static int Read(Stream data, out ulong id, out ulong? size, out int sizeLength) 172 | { 173 | var start = data.Position; 174 | id = data.ReadEBMLElementIdRaw(); 175 | var pos = data.Position; 176 | size = data.ReadEBMLElementSizeN(); 177 | sizeLength = (int)(data.Position - pos); 178 | return (int)(data.Position - start); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Components/EBMLDataGrid.razor.cs: -------------------------------------------------------------------------------- 1 | using BlazorEBMLViewer.Services; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Web; 4 | using Radzen; 5 | using Radzen.Blazor; 6 | using SpawnDev.BlazorJS; 7 | using SpawnDev.EBML; 8 | using SpawnDev.EBML.Elements; 9 | using System.Linq.Dynamic.Core; 10 | 11 | namespace BlazorEBMLViewer.Components 12 | { 13 | public partial class EBMLDataGrid 14 | { 15 | [Inject] 16 | BlazorJSRuntime JS { get; set; } 17 | 18 | [Inject] 19 | AppService AppService { get; set; } 20 | 21 | [CascadingParameter] 22 | public bool DocumentBusy { get; set; } 23 | 24 | [CascadingParameter] 25 | public EBMLDocument? Document { get; set; } 26 | 27 | [CascadingParameter] 28 | public MasterElement? ActiveContainer { get; set; } 29 | 30 | RadzenDataGrid grid; 31 | int count; 32 | IEnumerable orderDetails = new List(); 33 | 34 | bool IsLoading { get; set; } 35 | 36 | public ElementBase? Selected { get; set; } = null; 37 | 38 | [Parameter] 39 | public EventCallback DoubleClick { get; set; } 40 | [Parameter] 41 | public EventCallback Select { get; set; } 42 | [Parameter] 43 | public EventCallback Deselect { get; set; } 44 | [Parameter] 45 | public EventCallback RowContextMenu { get; set; } 46 | 47 | async Task RowDoubleClick(DataGridRowMouseEventArgs args) 48 | { 49 | await DoubleClick.InvokeAsync(args.Data); 50 | } 51 | MasterElement? _ActiveContainer = null; 52 | protected override async Task OnParametersSetAsync() 53 | { 54 | if (_ActiveContainer != ActiveContainer) 55 | { 56 | if (_ActiveContainer != null) 57 | { 58 | _ActiveContainer.Document.OnChanged -= Document_OnChanged; 59 | } 60 | await grid.SelectRow(null, true); 61 | _ActiveContainer = ActiveContainer; 62 | if (ActiveContainer != null) 63 | { 64 | ActiveContainer.Document.OnChanged += Document_OnChanged; 65 | } 66 | if (grid != null) 67 | { 68 | await LoadData(null); 69 | await grid.RefreshDataAsync(); 70 | StateHasChanged(); 71 | } 72 | } 73 | } 74 | bool IsEditing(string columnName, ElementBase order) 75 | { 76 | // Comparing strings is quicker than checking the contents of a List, so let the property check fail first. 77 | if (columnName != nameof(ElementBase.DataString)) 78 | { 79 | return false; 80 | } 81 | return columnEditing == columnName && Editing == order; 82 | } 83 | string? columnEditing = null; 84 | void OnCellClick(DataGridCellMouseEventArgs args) 85 | { 86 | var detail = args.Data; 87 | // This sets which column is currently being edited. 88 | if (columnEditing == args.Column.Property && detail == Editing) 89 | { 90 | return; 91 | } 92 | columnEditing = args.Column.Property; 93 | // This triggers a save on the previous edit. This can be removed if you are going to batch edits through another method. 94 | if (Editing != null) 95 | { 96 | OnUpdateRow(Editing); 97 | } 98 | // only the data column is editable 99 | if (columnEditing != nameof(ElementBase.DataString)) 100 | { 101 | return; 102 | } 103 | var canEdit = true; 104 | switch (detail.Type) 105 | { 106 | case "date": 107 | { 108 | 109 | break; 110 | } 111 | case "binary": 112 | { 113 | 114 | break; 115 | } 116 | case "master": 117 | { 118 | canEdit = false; 119 | break; 120 | } 121 | case "float": 122 | case "uinteger": 123 | case "integer": 124 | case "utf-8": 125 | case "string": 126 | { 127 | 128 | break; 129 | } 130 | } 131 | if (!canEdit) return; 132 | // This sets the Item to be edited. 133 | EditRow(detail); 134 | } 135 | ElementBase? Editing = null; 136 | void ResetEdit() 137 | { 138 | if (Editing == null) return; 139 | Editing = null; 140 | _ = ReloadIt(); 141 | } 142 | async Task ReloadIt() 143 | { 144 | await ReLoadData(); 145 | await grid.RefreshDataAsync(); 146 | StateHasChanged(); 147 | } 148 | void OnUpdateRow(ElementBase order) 149 | { 150 | ResetEdit(); 151 | 152 | // dbContext.Update(order); 153 | 154 | // dbContext.SaveChanges(); 155 | 156 | // If you were doing row-level edits and handling RowDeselect, you could use the line below to 157 | // clear edits for the current record. 158 | 159 | //editedFields = editedFields.Where(c => c.Key != order.OrderID).ToList(); 160 | } 161 | void EditRow(ElementBase order) 162 | { 163 | ResetEdit(); 164 | Editing = order; 165 | } 166 | async Task RowSelect(ElementBase element) 167 | { 168 | if (element == null) return; 169 | Selected = element; 170 | await Select.InvokeAsync(element); 171 | } 172 | async Task RowDeselect(ElementBase element) 173 | { 174 | if (Selected == null) return; 175 | await Deselect.InvokeAsync(element); 176 | Selected = null; 177 | } 178 | async Task ContextMenu(MouseEventArgs args, ElementBase element) 179 | { 180 | await RowContextMenu.InvokeAsync(new RowContextMenuArgs(args, element)); 181 | } 182 | private async void Document_OnChanged() 183 | { 184 | //var element = elements.First(); 185 | //JS.Log("GRID: Document_OnChanged", elements.Count(), element.Depth, element.Name, element.Path); 186 | if (grid != null) 187 | { 188 | await ReLoadData(); 189 | await grid.RefreshDataAsync(); 190 | StateHasChanged(); 191 | } 192 | } 193 | LoadDataArgs? lastArgs = new LoadDataArgs(); 194 | string? lastfilter = null; 195 | Task ReLoadData() => LoadData(null); 196 | async Task LoadData(LoadDataArgs args) 197 | { 198 | args ??= lastArgs ?? new LoadDataArgs(); 199 | lastArgs = args; 200 | IsLoading = true; 201 | await Task.Delay(50); 202 | if (!string.IsNullOrEmpty(args.Filter) && lastfilter != args.Filter) 203 | { 204 | args.Skip = 0; 205 | } 206 | var query = ActiveContainer?.Children.AsQueryable() ?? new List().AsQueryable(); 207 | if (!string.IsNullOrEmpty(args.Filter)) 208 | { 209 | lastfilter = args.Filter; 210 | query = query.Where(args.Filter); 211 | count = query.Count(); 212 | } 213 | else 214 | { 215 | count = query.Count(); 216 | } 217 | if (!string.IsNullOrEmpty(args.OrderBy)) 218 | { 219 | query = query.OrderBy(args.OrderBy); 220 | } 221 | if (args.Skip != null) query = query.Skip(args.Skip.Value); 222 | if (args.Top != null) query = query.Take(args.Top.Value); 223 | orderDetails = query.ToList(); 224 | IsLoading = false; 225 | } 226 | public static string HumandReadableBytes(double len) 227 | { 228 | string[] sizes = { "B", "KB", "MB", "GB", "TB" }; 229 | int order = 0; 230 | while (len >= 1024 && order < sizes.Length - 1) 231 | { 232 | order++; 233 | len = len / 1024; 234 | } 235 | return string.Format("{0:0.##} {1}", len, sizes[order]); 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Crc32/Crc32.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | 3 | namespace SpawnDev.EBML.Crc32 4 | { 5 | /// 6 | /// Implementation of CRC-32. 7 | /// This class supports several convenient static methods returning the CRC as UInt32. 8 | /// 9 | public class Crc32Algorithm : HashAlgorithm 10 | { 11 | private uint _currentCrc; 12 | 13 | private readonly bool _isBigEndian = true; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public Crc32Algorithm() 19 | { 20 | #if !NETCORE13 21 | HashSizeValue = 32; 22 | #endif 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | /// Should return bytes result as big endian or little endian 29 | // Crc32 by dariogriffo uses big endian, so, we need to be compatible and return big endian as default 30 | public Crc32Algorithm(bool isBigEndian = true) 31 | : this() 32 | { 33 | _isBigEndian = isBigEndian; 34 | } 35 | 36 | /// 37 | /// Computes CRC-32 from multiple buffers. 38 | /// Call this method multiple times to chain multiple buffers. 39 | /// 40 | /// 41 | /// Initial CRC value for the algorithm. It is zero for the first buffer. 42 | /// Subsequent buffers should have their initial value set to CRC value returned by previous call to this method. 43 | /// 44 | /// Input buffer with data to be checksummed. 45 | /// Offset of the input data within the buffer. 46 | /// Length of the input data in the buffer. 47 | /// Accumulated CRC-32 of all buffers processed so far. 48 | public static uint Append(uint initial, byte[] input, int offset, int length) 49 | { 50 | if (input == null) 51 | throw new ArgumentNullException("input"); 52 | if (offset < 0 || length < 0 || offset + length > input.Length) 53 | throw new ArgumentOutOfRangeException("length"); 54 | return AppendInternal(initial, input, offset, length); 55 | } 56 | 57 | /// 58 | /// Computes CRC-32 from multiple buffers. 59 | /// Call this method multiple times to chain multiple buffers. 60 | /// 61 | /// 62 | /// Initial CRC value for the algorithm. It is zero for the first buffer. 63 | /// Subsequent buffers should have their initial value set to CRC value returned by previous call to this method. 64 | /// 65 | /// Input buffer containing data to be checksummed. 66 | /// Accumulated CRC-32 of all buffers processed so far. 67 | public static uint Append(uint initial, byte[] input) 68 | { 69 | if (input == null) 70 | throw new ArgumentNullException(); 71 | return AppendInternal(initial, input, 0, input.Length); 72 | } 73 | 74 | /// 75 | /// Computes CRC-32 from input buffer. 76 | /// 77 | /// Input buffer with data to be checksummed. 78 | /// Offset of the input data within the buffer. 79 | /// Length of the input data in the buffer. 80 | /// CRC-32 of the data in the buffer. 81 | public static uint Compute(byte[] input, int offset, int length) 82 | { 83 | return Append(0, input, offset, length); 84 | } 85 | 86 | /// 87 | /// Computes CRC-32 from input buffer. 88 | /// 89 | /// Input buffer containing data to be checksummed. 90 | /// CRC-32 of the buffer. 91 | public static uint Compute(byte[] input) 92 | { 93 | return Append(0, input); 94 | } 95 | 96 | /// 97 | /// Computes CRC-32 from input buffer and writes it after end of data (buffer should have 4 bytes reserved space for it). Can be used in conjunction with 98 | /// 99 | /// Input buffer with data to be checksummed. 100 | /// Offset of the input data within the buffer. 101 | /// Length of the input data in the buffer. 102 | /// CRC-32 of the data in the buffer. 103 | public static uint ComputeAndWriteToEnd(byte[] input, int offset, int length) 104 | { 105 | if (length + 4 > input.Length) 106 | throw new ArgumentOutOfRangeException("length", "Length of data should be less than array length - 4 bytes of CRC data"); 107 | var crc = Append(0, input, offset, length); 108 | var r = offset + length; 109 | input[r] = (byte)crc; 110 | input[r + 1] = (byte)(crc >> 8); 111 | input[r + 2] = (byte)(crc >> 16); 112 | input[r + 3] = (byte)(crc >> 24); 113 | return crc; 114 | } 115 | 116 | /// 117 | /// Computes CRC-32 from input buffer - 4 bytes and writes it as last 4 bytes of buffer. Can be used in conjunction with 118 | /// 119 | /// Input buffer with data to be checksummed. 120 | /// CRC-32 of the data in the buffer. 121 | public static uint ComputeAndWriteToEnd(byte[] input) 122 | { 123 | if (input.Length < 4) 124 | throw new ArgumentOutOfRangeException("input", "Input array should be 4 bytes at least"); 125 | return ComputeAndWriteToEnd(input, 0, input.Length - 4); 126 | } 127 | 128 | /// 129 | /// Validates correctness of CRC-32 data in source buffer with assumption that CRC-32 data located at end of buffer in reverse bytes order. Can be used in conjunction with 130 | /// 131 | /// Input buffer with data to be checksummed. 132 | /// Offset of the input data within the buffer. 133 | /// Length of the input data in the buffer with CRC-32 bytes. 134 | /// Is checksum valid. 135 | public static bool IsValidWithCrcAtEnd(byte[] input, int offset, int lengthWithCrc) 136 | { 137 | return Append(0, input, offset, lengthWithCrc) == 0x2144DF1C; 138 | } 139 | 140 | /// 141 | /// Validates correctness of CRC-32 data in source buffer with assumption that CRC-32 data located at end of buffer in reverse bytes order. Can be used in conjunction with 142 | /// 143 | /// Input buffer with data to be checksummed. 144 | /// Is checksum valid. 145 | public static bool IsValidWithCrcAtEnd(byte[] input) 146 | { 147 | if (input.Length < 4) 148 | throw new ArgumentOutOfRangeException("input", "Input array should be 4 bytes at least"); 149 | return Append(0, input, 0, input.Length) == 0x2144DF1C; 150 | } 151 | 152 | /// 153 | /// Resets internal state of the algorithm. Used internally. 154 | /// 155 | public override void Initialize() 156 | { 157 | _currentCrc = 0; 158 | } 159 | 160 | /// 161 | /// Appends CRC-32 from given buffer 162 | /// 163 | protected override void HashCore(byte[] input, int offset, int length) 164 | { 165 | _currentCrc = AppendInternal(_currentCrc, input, offset, length); 166 | } 167 | 168 | /// 169 | /// Computes CRC-32 from 170 | /// 171 | protected override byte[] HashFinal() 172 | { 173 | if (_isBigEndian) 174 | return new[] { (byte)(_currentCrc >> 24), (byte)(_currentCrc >> 16), (byte)(_currentCrc >> 8), (byte)_currentCrc }; 175 | else 176 | return new[] { (byte)_currentCrc, (byte)(_currentCrc >> 8), (byte)(_currentCrc >> 16), (byte)(_currentCrc >> 24) }; 177 | } 178 | 179 | private static readonly SafeProxy _proxy = new SafeProxy(); 180 | 181 | private static uint AppendInternal(uint initial, byte[] input, int offset, int length) 182 | { 183 | if (length > 0) 184 | { 185 | return _proxy.Append(initial, input, offset, length); 186 | } 187 | else 188 | return initial; 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Schemas/SchemaElement.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using System.Xml.Linq; 3 | 4 | namespace SpawnDev.EBML.Schemas 5 | { 6 | /// 7 | /// Defines an EBML schema element
8 | /// https://github.com/ietf-wg-cellar/ebml-specification/blob/master/specification.markdown#element-element 9 | ///
10 | public class SchemaElement 11 | { 12 | /// 13 | /// Within an EBML Schema, the XPath of the @name attribute is /EBMLSchema/element/@name.
14 | /// The name provides the human-readable name of the EBML Element.The value of the name MUST be in the form of characters "A" to "Z", "a" to "z", "0" to "9", "-", and ".". The first character of the name MUST be in the form of an "A" to "Z", "a" to "z", or "0" to "9" character.
15 | /// The name attribute is REQUIRED. 16 | ///
17 | public string Name { get; } 18 | /// 19 | /// The path defines the allowed storage locations of the EBML Element within an EBML Document. This path MUST be defined with the full hierarchy of EBML Elements separated with a \. The top EBML Element in the path hierarchy is the first in the value. The syntax of the path attribute is defined using this Augmented Backus-Naur Form (ABNF) [@!RFC5234] with the case-sensitive update [@!RFC7405] notation: 20 | /// 21 | public string Path { get; } 22 | /// 23 | /// The DocType this element is defined for 24 | /// 25 | public string DocType { get; } 26 | /// 27 | /// The Element ID is encoded as a Variable-Size Integer. It is read and stored in big-endian order. In the EBML Schema, it is expressed in hexadecimal notation prefixed by a 0x. To reduce the risk of false positives while parsing EBML Streams, the Element IDs of the Root Element and Top-Level Elements SHOULD be at least 4 octets in length. Element IDs defined for use at Root Level or directly under the Root Level MAY use shorter octet lengths to facilitate padding and optimize edits to EBML Documents; for instance, the Void Element uses an Element ID with a length of one octet to allow its usage in more writing and editing scenarios. 28 | /// 29 | public ulong Id { get; } 30 | /// 31 | /// The type MUST be set to one of the following values: integer (signed integer), uinteger (unsigned integer), float, string, date, utf-8, master, or binary. The content of each type is defined in (#ebml-element-types). 32 | /// 33 | public string Type { get; } 34 | /// 35 | /// If an Element is mandatory (has a minOccurs value greater than zero) but not written within its Parent Element or stored as an Empty Element, then the EBML Reader of the EBML Document MUST semantically interpret the EBML Element as present with this specified default value for the EBML Element. An unwritten mandatory Element with a declared default value is semantically equivalent to that Element if written with the default value stored as the Element Data. EBML Elements that are Master Elements MUST NOT declare a default value. EBML Elements with a minOccurs value greater than 1 MUST NOT declare a default value. 36 | /// 37 | public string? Default { get; } 38 | /// 39 | /// A numerical range for EBML Elements that are of numerical types (Unsigned Integer, Signed Integer, Float, and Date). If specified, the value of the EBML Element MUST be within the defined range. See (#expression-of-range) for rules applied to expression of range values. 40 | /// 41 | public string? Range { get; } 42 | /// 43 | /// The minver (minimum version) attribute stores a nonnegative integer that represents the first version of the docType to support the EBML Element. 44 | /// 45 | public string? MinVer { get; } 46 | /// 47 | /// The maxver (maximum version) attribute stores a nonnegative integer that represents the last or most recent version of the docType to support the element. maxver MUST be greater than or equal to minver. 48 | /// 49 | public string? MaxVer { get; } 50 | /// 51 | /// This attribute is a boolean to express whether or not an EBML Element is defined as an Identically Recurring Element; see (#identically-recurring-elements). 52 | /// 53 | public bool Recurring { get; } 54 | /// 55 | /// This attribute is a boolean to express whether an EBML Element is permitted to be stored recursively. If it is allowed, the EBML Element MAY be stored within another EBML Element that has the same Element ID, which itself can be stored in an EBML Element that has the same Element ID, and so on. EBML Elements that are not Master Elements MUST NOT set recursive to true. 56 | /// 57 | public bool Recursive { get; } 58 | /// 59 | /// An Element Data Size with all VINT_DATA bits set to one is reserved as an indicator that the size of the EBML Element is unknown. The only reserved value for the VINT_DATA of Element Data Size is all bits set to one. An EBML Element with an unknown Element Data Size is referred to as an Unknown-Sized Element. Only a Master Element is allowed to be of unknown size, and it can only be so if the unknownsizeallowed attribute of its EBML Schema is set to true (see (#unknownsizeallowed)). 60 | /// 61 | public bool? UnknownSizeAllowed { get; } 62 | /// 63 | /// The length attribute is a value to express the valid length of the Element Data as written, measured in octets. The length provides a constraint in addition to the Length value of the definition of the corresponding EBML Element Type. This length MUST be expressed as either a nonnegative integer or a range (see (#expression-of-range)) that consists of only nonnegative integers and valid operators. 64 | /// 65 | public string? Length { get; } 66 | /// 67 | /// The position the element must be set to
68 | /// == -1 - last element
69 | /// >= 0 - first element
70 | /// If multiple elements exist in the same container with the same position value the greater PositionWeight will gt the Position and the others will be next to
71 | /// Non-standard metadata added by Todd Tanner 72 | ///
73 | public int? Position { get; } 74 | /// 75 | /// If two elements are defined to have the same position value, the element with the greater weight will get the position 76 | /// 77 | public int PositionWeight { get; } 78 | /// 79 | /// Within an EBML Schema, the XPath of the @minOccurs attribute is /EBMLSchema/element/@minOccurs.
80 | /// minOccurs is a nonnegative integer expressing the minimum permitted number of occurrences of this EBML Element within its Parent Element. 81 | ///
82 | public int MinOccurs { get; } 83 | /// 84 | /// Within an EBML Schema, the XPath of the @maxOccurs attribute is /EBMLSchema/element/@maxOccurs.
85 | /// maxOccurs is a nonnegative integer expressing the maximum permitted number of occurrences of this EBML Element within its Parent Element. 86 | ///
87 | public int MaxOccurs { get; } 88 | /// 89 | /// Returns the first definition 90 | /// 91 | public string Definition => Definitions.Values.FirstOrDefault() ?? ""; 92 | /// 93 | /// The minimum depth this element must have 94 | /// 95 | public int MinDepth { get; } = 0; 96 | /// 97 | /// If true, this element is a global element (does not have a specific parent type) 98 | /// 99 | public bool IsGlobal { get; } = false; 100 | /// 101 | /// Element definitions (informative) 102 | /// 103 | public Dictionary Definitions { get; } = new Dictionary(); 104 | /// 105 | /// Creates a new SchemaElement 106 | /// 107 | /// 108 | /// 109 | /// 110 | public SchemaElement(string docType, ulong id, XElement node) 111 | { 112 | DocType = docType; 113 | Id = id; 114 | Name = node.Attribute("name")!.Value; 115 | // The delimiter in Path is changed from the default used in ebml schema '\\' to '/' which this library uses 116 | var path = node.Attribute("path")!.Value; 117 | var pathParts = path.Split(EBMLParser.PathDelimiters); 118 | Path = string.Join(EBMLParser.PathDelimiter, pathParts); 119 | Type = node.Attribute("type")!.Value; 120 | Default = node.Attribute("default")?.Value; 121 | MinVer = node.Attribute("minVer")?.Value; 122 | MaxVer = node.Attribute("maxVer")?.Value; 123 | Recurring = node.Attribute("recurring")?.Value == "1"; 124 | Recursive = node.Attribute("recursive")?.Value == "1"; 125 | UnknownSizeAllowed = node.Attribute("unknownsizeallowed")?.Value == "1" && Type == "master"; 126 | Length = node.Attribute("length")?.Value; 127 | MaxOccurs = node.Attribute("maxOccurs")?.Value == null ? 0 : int.Parse(node.Attribute("maxOccurs")!.Value); 128 | MinOccurs = node.Attribute("minOccurs")?.Value == null ? 0 : int.Parse(node.Attribute("minOccurs")!.Value); 129 | Range = node.Attribute("range")?.Value; 130 | Position = node.Attribute("position")?.Value == null ? null : int.Parse(node.Attribute("position")!.Value); 131 | PositionWeight = node.Attribute("positionWeight")?.Value == null ? 0 : int.Parse(node.Attribute("positionWeight")!.Value); 132 | var minDepthMatch = Regex.Match(path, $@"^\\\(([0-9]+)-\\\){Name}$"); 133 | if (minDepthMatch.Success) 134 | { 135 | var minDepthStr = minDepthMatch.Groups[1].Value; 136 | MinDepth = int.Parse(minDepthStr); 137 | IsGlobal = true; 138 | } 139 | else 140 | { 141 | IsGlobal = Regex.IsMatch(path, $@"^\\\(-\\\){Name}$"); 142 | } 143 | var childElements = node.Elements(); 144 | foreach (var childEl in childElements) 145 | { 146 | if (childEl.Name.LocalName == "documentation" && childEl.Attribute("purpose")?.Value == "definition") 147 | { 148 | var lang = childEl.Attribute("lang")?.Value ?? ""; 149 | Definitions[lang] = childEl.Value; 150 | } 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /BlazorEBMLViewer/Pages/Home.razor.cs: -------------------------------------------------------------------------------- 1 | using BlazorEBMLViewer.Components; 2 | using BlazorEBMLViewer.Layout; 3 | using BlazorEBMLViewer.Services; 4 | using Microsoft.AspNetCore.Components; 5 | using Radzen; 6 | using SpawnDev.BlazorJS; 7 | using SpawnDev.BlazorJS.JSObjects; 8 | using SpawnDev.BlazorJS.Toolbox; 9 | using SpawnDev.EBML.Elements; 10 | using SpawnDev.EBML.Schemas; 11 | using File = SpawnDev.BlazorJS.JSObjects.File; 12 | 13 | namespace BlazorEBMLViewer.Pages 14 | { 15 | public partial class Home : IDisposable 16 | { 17 | public bool DocumentBusy { get; set; } 18 | public SpawnDev.EBML.EBMLDocument? Document { get; set; } 19 | public string? ActiveContainerTypeName => ActiveContainer?.GetType().Name; 20 | public MasterElement? ActiveContainer { get; set; } 21 | public string Path => ActiveContainer?.InstancePath ?? ""; 22 | bool CanGoUp => !(ActiveContainer?.DocumentRoot ?? true); 23 | [Inject] 24 | BlazorJSRuntime JS { get; set; } 25 | [Inject] 26 | MainLayoutService MainLayoutService { get; set; } 27 | [Inject] 28 | EBMLSchemaService EBMLSchemaService { get; set; } 29 | [Inject] 30 | ContextMenuService ContextMenuService { get; set; } 31 | [Inject] 32 | DialogService DialogService { get; set; } 33 | 34 | EBMLDataGrid Grid { get; set; } 35 | 36 | [Inject] 37 | AppService AppService { get; set; } 38 | 39 | async Task RowSelect(ElementBase element) 40 | { 41 | StateHasChanged(); 42 | } 43 | async Task RowDeselect(ElementBase element) 44 | { 45 | StateHasChanged(); 46 | } 47 | async Task NewDocument(Schema schema) 48 | { 49 | var filename = "NewDocument.ebml"; 50 | CloseDocument(); 51 | DocumentBusy = true; 52 | StateHasChanged(); 53 | await Task.Delay(50); 54 | Document = EBMLSchemaService.Parser.CreateDocument(schema.DocType, filename); 55 | //Document.OnElementAdded += Document_OnElementAdded; 56 | //Document.OnElementRemoved += Document_OnElementRemoved; 57 | Document.OnChanged += Document_OnChanged; 58 | MainLayoutService.Title = Document.Filename; 59 | ActiveContainer = Document; 60 | DocumentBusy = false; 61 | StateHasChanged(); 62 | await SetPath(Document); 63 | } 64 | private void Document_OnChanged() 65 | { 66 | //var element = elements.First(); 67 | //Console.WriteLine($"VIEW: Document_OnChanged: {elements.Count()} {element.Depth} {element.Name} {element.Path}"); 68 | } 69 | //private void Document_OnElementRemoved(MasterElement masterElement, EBMLElement element) 70 | //{ 71 | // //Console.WriteLine($"VIEW: Document_OnElementRemoved: {element.Depth} {element.Name} {element.Path}"); 72 | //} 73 | //private void Document_OnElementAdded(MasterElement masterElement, EBMLElement element) 74 | //{ 75 | // //Console.WriteLine($"VIEW: Document_OnElementAdded: {element.Depth} {element.Name} {element.Path}"); 76 | //} 77 | protected override void OnInitialized() 78 | { 79 | // 80 | } 81 | public void Dispose() 82 | { 83 | // 84 | } 85 | List History = new List(); 86 | async Task SetPath(string path) 87 | { 88 | DocumentBusy = true; 89 | StateHasChanged(); 90 | await Task.Delay(50); 91 | if (string.IsNullOrEmpty(path) || path.Trim(EBMLParser.PathDelimiters) == "") 92 | { 93 | await SetPath(Document); 94 | } 95 | else 96 | { 97 | var element = Document?.FindMaster(path); 98 | if (element != null) 99 | { 100 | await SetPath(element); 101 | } 102 | } 103 | DocumentBusy = false; 104 | StateHasChanged(); 105 | } 106 | bool CanUndo => Document?.CanUndo ?? false; 107 | bool CanRedo => Document?.CanRedo ?? false; 108 | async Task Undo() 109 | { 110 | Document?.Undo(); 111 | } 112 | async Task Redo() 113 | { 114 | Document?.Redo(); 115 | } 116 | async Task SetPath(ElementBase element) 117 | { 118 | if (element == null) return; 119 | DocumentBusy = true; 120 | StateHasChanged(); 121 | await Task.Delay(50); 122 | if (element is MasterElement source) 123 | { 124 | ActiveContainer = source; 125 | } 126 | DocumentBusy = false; 127 | StateHasChanged(); 128 | } 129 | async Task GoUp() 130 | { 131 | if (ActiveContainer?.Parent != null) 132 | { 133 | await SetPath(ActiveContainer.Parent); 134 | } 135 | } 136 | IEnumerable GetAddElementOptions() 137 | { 138 | var ret = new List(); 139 | if (ActiveContainer == null) return ret; 140 | var addables = ActiveContainer.GetAddableElementSchemas(true); 141 | var missing = new List(); 142 | foreach (var addable in addables) 143 | { 144 | var requiresAdd = false; 145 | var atMaxCount = false; 146 | if (addable.MaxOccurs > 0 || addable.MinOccurs > 0) 147 | { 148 | var count = ActiveContainer.Children.Count(o => o.Id == addable.Id); 149 | requiresAdd = addable.MinOccurs > 0 && count < addable.MinOccurs; 150 | atMaxCount = addable.MaxOccurs > 0 && count >= addable.MaxOccurs; 151 | if (requiresAdd) missing.Add(addable); 152 | } 153 | var option = new ContextMenuItem 154 | { 155 | Text = addable.Name + (requiresAdd ? " (required)" : ""), 156 | Value = addable, 157 | Disabled = atMaxCount, 158 | Icon = AppService.GetElementTypeIcon(addable), 159 | }; 160 | if (requiresAdd) option.IconColor = Colors.Danger; 161 | ret.Add(option); 162 | } 163 | if (missing.Count > 0) 164 | { 165 | ret.Insert(0, new ContextMenuItem 166 | { 167 | Text = $"{missing.Count} required", 168 | Value = missing, 169 | Icon = "add", 170 | IconColor = Colors.Danger, 171 | }); 172 | } 173 | return ret; 174 | } 175 | void AddElementClicked(MenuItemEventArgs args) 176 | { 177 | if (ActiveContainer == null || Document == null) return; 178 | ContextMenuService.Close(); 179 | if (args.Value is List missing) 180 | { 181 | Document.DisableDocumentEngines(); 182 | foreach (var addable in missing) 183 | { 184 | ActiveContainer.AddElement(addable); 185 | } 186 | Document.EnableDocumentEngines(); 187 | } 188 | else if (args.Value is SchemaElement addable) 189 | { 190 | ActiveContainer.AddElement(addable); 191 | } 192 | } 193 | void AddElement(MenuItemEventArgs args) 194 | { 195 | ContextMenuService.Open(args, GetAddElementOptions(), AddElementClicked); 196 | } 197 | void CloseDocument() 198 | { 199 | if (Document == null) return; 200 | //Document.OnElementAdded -= Document_OnElementAdded; 201 | //Document.OnElementRemoved -= Document_OnElementRemoved; 202 | Document.OnChanged -= Document_OnChanged; 203 | Document = null; 204 | ActiveContainer = null; 205 | History.Clear(); 206 | MainLayoutService.Title = ""; 207 | if (DocumentSourceHandle != null) 208 | { 209 | DocumentSourceHandle.Dispose(); 210 | DocumentSourceHandle = null; 211 | } 212 | StateHasChanged(); 213 | } 214 | async Task ShowOpenFileDialogFallback() 215 | { 216 | File[] result; 217 | try 218 | { 219 | result = await FilePicker.ShowOpenFilePicker(".ebml,.webm,.mkv,.mka,.mks"); 220 | } 221 | catch 222 | { 223 | return; 224 | } 225 | var file = result?.FirstOrDefault(); 226 | if (file == null) return; 227 | CloseDocument(); 228 | DocumentBusy = true; 229 | StateHasChanged(); 230 | await Task.Delay(50); 231 | var arrayBuffer = await file.ArrayBuffer(); 232 | var fileStream = new ArrayBufferStream(arrayBuffer); 233 | Document = EBMLSchemaService.Parser.ParseDocument(fileStream, file.Name); 234 | MainLayoutService.Title = file.Name; 235 | ActiveContainer = Document; 236 | DocumentBusy = false; 237 | StateHasChanged(); 238 | await SetPath(Document); 239 | } 240 | async Task GridRowContextMenu(RowContextMenuArgs args) 241 | { 242 | var element = args.Element; 243 | var options = new List(); 244 | options.Add(new ContextMenuItem 245 | { 246 | Text = "Delete", 247 | Icon = "delete", 248 | Value = async () => 249 | { 250 | var confirm = await DialogService.Confirm($"Delete {element.Name}?"); 251 | if (confirm == true) 252 | { 253 | element.Remove(); 254 | } 255 | } 256 | }); 257 | ContextMenuService.Open(args.MouseEventArgs, options, ContextMenuActionInvoker); 258 | } 259 | void ContextMenuActionInvoker(MenuItemEventArgs args) 260 | { 261 | if (args.Value is Action action) action(); 262 | else if (args.Value is Func asyncAction) _ = asyncAction(); 263 | } 264 | async Task ShowOpenFileDialog() 265 | { 266 | if (true || JS.IsUndefined("window.showOpenFilePicker")) 267 | { 268 | await ShowOpenFileDialogFallback(); 269 | return; 270 | } 271 | DocumentBusy = true; 272 | StateHasChanged(); 273 | FileSystemFileHandle? file = null; 274 | Array result; 275 | try 276 | { 277 | result = await JS.WindowThis!.ShowOpenFilePicker(new ShowOpenFilePickerOptions 278 | { 279 | Multiple = false, 280 | Types = new List 281 | { 282 | new ShowOpenFilePickerType{ Accept = new Dictionary> 283 | { 284 | { "application/octet-stream", new List{ ".ebml", ".mks" } }, 285 | { "video/x-matroska", new List{ ".mkv", ".webm" } }, 286 | { "audio/x-matroska", new List{ ".mka" } }, 287 | }, Description = "EBML Files" } 288 | } 289 | }); 290 | } 291 | catch 292 | { 293 | return; 294 | } 295 | try 296 | { 297 | file = result.FirstOrDefault(); 298 | result.Dispose(); 299 | if (file == null) return; 300 | CloseDocument(); 301 | DocumentSourceHandle = file; 302 | using var f = await file.GetFile(); 303 | using var arrayBuffer = await f.ArrayBuffer(); 304 | var fileStream = new ArrayBufferStream(arrayBuffer); 305 | Document = EBMLSchemaService.Parser.ParseDocument(fileStream, file.Name); 306 | MainLayoutService.Title = file.Name; 307 | ActiveContainer = Document; 308 | DocumentBusy = false; 309 | StateHasChanged(); 310 | await SetPath(Document); 311 | } 312 | finally 313 | { 314 | DocumentBusy = false; 315 | StateHasChanged(); 316 | } 317 | } 318 | async Task DownloadDocument() 319 | { 320 | if (Document == null) return; 321 | DocumentBusy = true; 322 | StateHasChanged(); 323 | try 324 | { 325 | await Task.Delay(50); 326 | var mem = new MemoryStream(); 327 | await Document.CopyToAsync(mem, CancellationToken.None); 328 | mem.Position = 0; 329 | var length = mem.Length; 330 | using var tmp = new Blob(new byte[][] { mem.ToArray() }); 331 | await tmp.StartDownload(Document.Filename); 332 | } 333 | finally 334 | { 335 | DocumentBusy = false; 336 | StateHasChanged(); 337 | } 338 | } 339 | FileSystemFileHandle? DocumentSourceHandle = null; 340 | async Task ShowSaveFileDialog() 341 | { 342 | if (Document == null) return; 343 | if (JS.IsUndefined("window.showSaveFilePicker")) 344 | { 345 | await DownloadDocument(); 346 | return; 347 | } 348 | DocumentBusy = true; 349 | StateHasChanged(); 350 | try 351 | { 352 | try 353 | { 354 | var result = await JS.WindowThis!.ShowSaveFilePicker(new ShowSaveFilePickerOptions { SuggestedName = Document.Filename }); 355 | // TODO - create stream wrapper for FileSystemFileHandle 356 | // then we could copy directly to it instead of usign a memory stream 357 | //var writable = await result.CreateWritable(); 358 | //var writer = writable.GetWriter(); 359 | //await writer.Write(chunk); 360 | if (result != null) 361 | { 362 | var mem = new MemoryStream(); 363 | Document.CopyTo(mem); 364 | mem.Position = 0; 365 | await result.Write(mem.ToArray()); 366 | } 367 | } 368 | catch (Exception ex) 369 | { 370 | var nmttt = true; 371 | } 372 | } 373 | finally 374 | { 375 | DocumentBusy = false; 376 | } 377 | } 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Elements/ElementBase.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Crc32; 2 | using SpawnDev.EBML.Extensions; 3 | using SpawnDev.EBML.Schemas; 4 | using SpawnDev.PatchStreams; 5 | using System.Text; 6 | 7 | namespace SpawnDev.EBML.Elements 8 | { 9 | /// 10 | /// An EBML element 11 | /// 12 | public class ElementBase 13 | { 14 | /// 15 | /// Instance stream info 16 | /// 17 | public virtual ElementStreamInfo Info 18 | { 19 | get 20 | { 21 | if (!DocumentRoot) 22 | { 23 | var info = Document.FindInfo(_Info.InstancePath).FirstOrDefault(); 24 | if (info != null) 25 | { 26 | _Info.Exists = true; 27 | if (_Info != info) _Info = info; 28 | } 29 | else 30 | { 31 | _Info.Exists = false; 32 | } 33 | } 34 | return _Info; 35 | } 36 | protected set => _Info = value; 37 | } 38 | /// 39 | /// Recast this element to another Element type
40 | /// IF this element is already assignable to TElement, this element is returned
41 | /// TElement must have a constructor: (StreamElementInfo info) : base(info) 42 | ///
43 | /// Element type to return 44 | /// 45 | public TElement As() where TElement : ElementBase 46 | { 47 | if (this is TElement element) return (TElement)element; 48 | var ret = (TElement)Activator.CreateInstance(typeof(TElement), Document, Info)!; 49 | return ret; 50 | } 51 | /// 52 | /// Throws an exception if can edit returns false 53 | /// 54 | /// 55 | protected void ThrowIfCannotEdit() 56 | { 57 | if (!CanEdit) throw new Exception("Cannot edit element in current state"); 58 | } 59 | /// 60 | /// Returns true if the current element stream info is update to date with the document primary stream
61 | /// If true, this element's data is available for editing 62 | ///
63 | public bool CanEdit 64 | { 65 | get 66 | { 67 | return Stream != null && PatchId == Document.Stream.PatchId && Document.Stream.RestorePoint; 68 | } 69 | } 70 | /// 71 | /// Returns true if an update is needed and not already updating 72 | /// 73 | public bool CanUpdate 74 | { 75 | get 76 | { 77 | return !UsingLatestStable && !Updating; 78 | } 79 | } 80 | /// 81 | /// Returns true if this element is using data from the current primary stream patch
82 | /// An element will only ever use the latest patch if it is marked as stable (a restore point) 83 | ///
84 | public bool UsingLatest 85 | { 86 | get 87 | { 88 | return PatchId == Document.Stream.PatchId; 89 | } 90 | } 91 | /// 92 | /// Returns true if the latest stream patch is marked as a restore point, also referred to in this library as a stable patch 93 | /// 94 | public bool LatestIsStable => Document.Stream.RestorePoint; 95 | /// 96 | /// Returns true if this element is using data from the latest (relative to the current) stable stream patch
97 | /// The latest patch may not be marked as stable as modifications may be in process that would cause the stream to appear corrupted.
98 | /// So elements generally only look at the latest stable stream patch 99 | ///
100 | public bool UsingLatestStable 101 | { 102 | get 103 | { 104 | return PatchId == Document.Stream.LatestStable.PatchId; 105 | } 106 | } 107 | /// 108 | /// Returns true if updating elements stream info 109 | /// 110 | public bool Updating { get; protected set; } = false; 111 | /// 112 | /// The Document this element belong(s|ed) to 113 | /// 114 | public virtual EBMLDocument Document { get; protected set; } 115 | /// 116 | /// EBML schema parser 117 | /// 118 | public virtual EBMLParser Parser => Document.Parser; 119 | /// 120 | /// The source stream. The data in this stream may change. If it does Stream.PatchId will no longer match PatchId. UpdateNeeded will == true.
121 | /// StreamSnapShot will still contain the data before Stream was changed, if it is needed. Calling Update() will update this element's metadata and SnapShot 122 | ///
123 | public virtual PatchStream Stream => Document.Stream[PatchId]; 124 | /// This element's index in its container 125 | /// 126 | public int Index => Info.Index; 127 | /// 128 | /// This element's index by type in its container 129 | /// 130 | public int TypeIndex => Info.TypeIndex; 131 | /// 132 | /// The element's Id 133 | /// 134 | public ulong Id => Info.Id; 135 | /// 136 | /// The element's hex id 137 | /// 138 | public string HexId => EBMLConverter.ElementIdToHexId(Id); 139 | /// 140 | /// The element's name 141 | /// 142 | public string Name => Info.Name ?? ""; 143 | /// 144 | /// The element's path 145 | /// 146 | public string Path => Info.Path; 147 | /// 148 | /// The element's instance path 149 | /// 150 | public string InstancePath => Info.InstancePath; 151 | /// 152 | /// The element's EBML schema 153 | /// 154 | public SchemaElement? SchemaElement => Info.SchemaElement; 155 | /// 156 | /// Returns true is a snap shot will be used for reads. Update() to resync. 157 | /// 158 | /// 159 | /// The position in the stream where the EBML document containing this element starts 160 | /// 161 | public virtual long DocumentOffset => Info.DocumentOffset; 162 | /// 163 | /// The position in the stream of this element 164 | /// 165 | public long Offset => Info.Offset; 166 | /// 167 | /// The position of the byte following this element 168 | /// 169 | public long EndPos => Offset + TotalSize; 170 | /// 171 | /// The size of this element, if specified by header 172 | /// 173 | public ulong? Size => DocumentRoot ? (ulong)Stream.LatestStable.Length - (ulong)DocumentOffset : Info.Size; 174 | /// 175 | /// The size of the element's header 176 | /// 177 | public long HeaderSize => Info.HeaderSize; 178 | /// 179 | /// The size of this element, if specified by header, else the size of data left in the stream 180 | /// 181 | public long DataSize => DocumentRoot ? Stream.LatestStable.Length - DocumentOffset : Info.DataSize; 182 | /// 183 | /// The total size of this element. Header size + data size. 184 | /// 185 | public long TotalSize => DocumentRoot ? Stream.LatestStable.Length - DocumentOffset : Info.TotalSize; 186 | /// 187 | /// The position in the stream where this element's data starts 188 | /// 189 | public long DataOffset => Info.DataOffset; 190 | /// 191 | /// The patch id of the PatchStream when this element's metadata was last updated 192 | /// 193 | public string PatchId => Info.PatchId; 194 | /// 195 | /// Returns true if this element's InstancePath is still found in the containing EBML document or if this element is an EBML document master element
196 | ///
197 | public bool Exists => Info.Exists; 198 | /// 199 | /// Returns true if this element has an empty name and Offset == DocumentOffset 200 | /// 201 | public bool DocumentRoot => this is EBMLDocument; 202 | /// 203 | /// The number of elements if this elements path - 1 204 | /// 205 | public int Depth => Info.Depth; 206 | /// 207 | /// A Root Element is a mandatory, nonrepeating EBML Element that occurs at the top level of the path hierarchy within an EBML Body and contains all other EBML Elements of the EBML Body, excepting optional Void Elements. 208 | /// 209 | public bool Root => Depth == 0; 210 | /// 211 | /// A Top-Level Element is an EBML Element defined to only occur as a Child Element of the Root Element. 212 | /// 213 | public bool TopLevel => Depth == 1; 214 | private ElementStreamInfo _Info = new ElementStreamInfo(); 215 | /// 216 | /// Creates a new instance 217 | /// 218 | /// 219 | public ElementBase(EBMLDocument document, ElementStreamInfo element) 220 | { 221 | //Console.WriteLine($"** {GetType().Name}"); 222 | if (element == null) throw new ArgumentNullException(nameof(element)); 223 | Document = document; 224 | Info = element; 225 | } 226 | public static bool Verbose { get; set; } = false; 227 | /// 228 | /// Constructor for derived classes 229 | /// 230 | protected ElementBase() 231 | { 232 | //Console.WriteLine($"** {GetType().Name}"); 233 | } 234 | 235 | /// 236 | /// Removes the element from the Document
237 | /// The Element.Exists 238 | ///
239 | /// 240 | public bool Remove() 241 | { 242 | ThrowIfCannotEdit(); 243 | if (!Exists || Size == null) return false; 244 | DataChanged(this, -1); 245 | return true; 246 | } 247 | public List GetAncestors(bool removeRoot = false) 248 | { 249 | var paths = GetAncestorInstancePaths(removeRoot); 250 | if (removeRoot && paths.FirstOrDefault() == "/") paths.RemoveAt(0); 251 | var ret = paths.Select(o => Document.FindMaster(o)).ToList(); 252 | return ret; 253 | } 254 | public List GetAncestorInstancePaths(bool removeRoot = false) 255 | { 256 | var ret = new List(); 257 | var path = EBMLConverter.PathParent(InstancePath); 258 | while (!string.IsNullOrEmpty(path)) 259 | { 260 | if (removeRoot && path == "/") break; 261 | ret.Add(path); 262 | path = EBMLConverter.PathParent(path); 263 | if (ret.Count > 5) 264 | { 265 | var ggg = true; 266 | } 267 | } 268 | return ret; 269 | } 270 | protected internal virtual void AfterAdded() { } 271 | /// 272 | /// Returns the element this element is contained in 273 | /// 274 | public MasterElement? Parent => DocumentRoot ? null : Root ? Document : Document.FindMaster(ParentInstancePath!); 275 | /// 276 | /// Returns this elements parent instance path 277 | /// 278 | public string? ParentInstancePath => DocumentRoot || Root ? null : Info.ParentInstancePath; 279 | /// 280 | /// This method should be called when this element's data has changed so the header data size information for all parent nodes can be updated 281 | /// 282 | /// The element that is calling the event 283 | /// The number of bytes added or removed from this element's data. May be 0, but this needs to be called if the element's data has changed 284 | protected internal virtual void DataChanged(ElementBase changedElement, long newDataSize) 285 | { 286 | Document.DataChanged(changedElement, newDataSize); 287 | } 288 | /// 289 | /// Returns the DocType 290 | /// 291 | public virtual string? DocType { get => SchemaElement?.DocType ?? Document.DocType; set { } } 292 | /// 293 | /// Returns the entire element, header and data, as a PatchStream 294 | /// 295 | /// 296 | public PatchStream ElementStreamSlice() 297 | { 298 | return Stream.Slice(Offset, TotalSize); 299 | } 300 | /// 301 | /// Returns the element data as a PatchStream 302 | /// 303 | /// 304 | public PatchStream ElementStreamDataSlice() 305 | { 306 | return Stream.Slice(DataOffset, DataSize); 307 | } 308 | public virtual void CopyTo(Stream stream) 309 | { 310 | Stream.Position = Offset; 311 | Stream.CopyTo(stream, (int)TotalSize); 312 | } 313 | public virtual async Task CopyToAsync(Stream stream, CancellationToken cancellationToken) 314 | { 315 | Stream.Position = Offset; 316 | await Stream.CopyToAsync(stream, (int)TotalSize, cancellationToken); 317 | } 318 | /// 319 | /// Replace this element's data with the specified stream 320 | /// 321 | /// 322 | public void ReplaceData(byte[] replacementData) 323 | { 324 | ReplaceData(new MemoryStream(replacementData)); 325 | } 326 | /// 327 | /// The element type 328 | /// 329 | public string Type => DocumentRoot ? "document" : SchemaElement?.Type ?? ""; 330 | /// 331 | /// A string that represents this element 332 | /// 333 | public virtual string DataString 334 | { 335 | get 336 | { 337 | return $"{InstancePath}"; 338 | } 339 | set 340 | { 341 | 342 | } 343 | } 344 | /// 345 | /// Replace this element's data with the specified stream 346 | /// 347 | /// 348 | public void ReplaceData(Stream replacementData) 349 | { 350 | ThrowIfCannotEdit(); 351 | Stream.Position = DataOffset; 352 | Stream.Insert(replacementData, DataSize); 353 | DataChanged(this, replacementData.Length); 354 | } 355 | /// 356 | /// Deletes the elements data
357 | /// The element still exist with a data size of 0 358 | ///
359 | public void DeleteData() 360 | { 361 | ThrowIfCannotEdit(); 362 | Stream.Position = DataOffset; 363 | Stream.Delete(DataSize); 364 | DataChanged(this, 0); 365 | } 366 | #region Equals 367 | public static bool operator ==(ElementBase? b1, ElementBase? b2) 368 | { 369 | if ((object?)b1 == null) return (object?)b2 == null; 370 | return b1.Equals(b2); 371 | } 372 | 373 | public static bool operator !=(ElementBase? b1, ElementBase? b2) 374 | { 375 | return !(b1 == b2); 376 | } 377 | 378 | public override bool Equals(object? obj) 379 | { 380 | if (obj == null || !(obj is ElementBase element)) return false; 381 | // if the instance path is different or the Document is different ? false 382 | if (InstancePath != element.InstancePath || !object.ReferenceEquals(Document, element.Document)) return false; 383 | return true; 384 | } 385 | 386 | protected static Crc32Algorithm CRC = new Crc32Algorithm(false); 387 | public override int GetHashCode() 388 | { 389 | var bytes = Encoding.UTF8.GetBytes(InstancePath); 390 | var crc = CRC.ComputeHash(bytes); 391 | var hashCode = BitConverter.ToInt32(crc); 392 | return hashCode; 393 | } 394 | #endregion 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /SpawnDev.EBML/Extensions/EBMLConverter.cs: -------------------------------------------------------------------------------- 1 | using SpawnDev.EBML.Schemas; 2 | using System; 3 | using System.IO; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace SpawnDev.EBML.Extensions 8 | { 9 | /// 10 | /// Converter tools for EBML
11 | ///
12 | public static class EBMLConverter 13 | { 14 | static Regex PathPartRegex = new Regex(@"^(.*?)(?:,([0-9-]*))?$", RegexOptions.Compiled); 15 | static Regex PathFromInstancePathRegex = new Regex(@",[0-9-]*", RegexOptions.Compiled); 16 | /// 17 | /// Returns a name index pair fro ma name. Used for name matching with Find 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// If true, and no index is found in [name] then -1 is returned which maxes all indexes 23 | public static void NameToNameIndex(string nameIn, out string name, out int index, bool defaultIndexAll = true) 24 | { 25 | var m = PathPartRegex.Match(nameIn); 26 | if (m.Success) 27 | { 28 | index = m.Groups.Count == 3 && m.Groups[2].Value.Length > 0 ? int.Parse(m.Groups[2].Value) : (defaultIndexAll ? -1 : 0); 29 | name = m.Groups[1].Value; 30 | } 31 | else 32 | { 33 | name = nameIn; 34 | index = defaultIndexAll ? - 1 : 0; 35 | } 36 | } 37 | public static void PathToParentInstancePathNameIndex(string path, out string parentInstancePath, out string name, out int index, bool defaultIndexAll = true) 38 | { 39 | //if (!path.StartsWith("/")) path = $"/{path}"; 40 | var parentPath = PathParent(path); 41 | parentInstancePath = PathToInstancePath(parentPath); 42 | var nameMaybeIndex= PathName(path); 43 | NameToNameIndex(nameMaybeIndex, out name, out index, defaultIndexAll); 44 | } 45 | /// 46 | /// Removes instance indexes fro mn instance path leaving only the element's non-indexed path 47 | /// 48 | /// 49 | /// 50 | public static string PathFromInstancePath(string instancePath) 51 | { 52 | return PathFromInstancePathRegex.Replace(instancePath, ""); 53 | } 54 | /// 55 | /// Returns the element name from the specified path 56 | /// 57 | /// 58 | /// 59 | public static string PathName(string path) 60 | { 61 | return path.Substring(path.LastIndexOf("/") + 1); 62 | } 63 | /// 64 | /// Returns the path parent 65 | /// 66 | /// 67 | /// 68 | public static string PathParent(string path, bool prependDelimiter = true) 69 | { 70 | if (string.IsNullOrEmpty(path) || path == "/") return ""; 71 | var pos = path.LastIndexOf("/"); 72 | if (pos < 1) return "/"; 73 | return path.Substring(0, pos); 74 | } 75 | /// 76 | /// Converts an EBML path 77 | /// Ex: (/[ELEMENT_NAME]/[ELEMENT_NAME]/[ELEMENT_NAME]) 78 | /// to an EBML instance path 79 | /// Ex: (/[ELEMENT_NAME],[INDEX]/[ELEMENT_NAME],[INDEX]/[ELEMENT_NAME],[INDEX]) 80 | /// 81 | /// 82 | /// 83 | /// 84 | public static string PathToInstancePath(string path, bool lastIndexDefaultsAll = false) 85 | { 86 | if (string.IsNullOrEmpty(path) || path == "/") return path; 87 | var parts = path.Split(EBMLParser.PathDelimiters); 88 | for (var i = 0; i < parts.Length; i++) 89 | { 90 | var part = parts[i]; 91 | if (part == "" && i == 0) continue; 92 | var m = PathPartRegex.Match(part); 93 | if (m.Success) 94 | { 95 | var index = lastIndexDefaultsAll ? -1 : 0; 96 | if (m.Groups.Count == 3 && !string.IsNullOrWhiteSpace(m.Groups[2].Value)) 97 | { 98 | index = int.Parse(m.Groups[2].Value); 99 | var nmttt = true; 100 | } 101 | var name = m.Groups[1].Value; 102 | parts[i] = $"{name}{EBMLParser.IndexDelimiter}{index}"; 103 | } 104 | else 105 | { 106 | throw new Exception("Invalid path"); 107 | } 108 | } 109 | var ret1 = string.Join(EBMLParser.PathDelimiter, parts); 110 | return ret1; 111 | } 112 | /// 113 | /// Returns the specified element id as a hex id string 114 | /// 115 | /// 116 | /// 117 | /// 118 | public static string ElementIdToHexId(ulong id, bool prepend0x = true) 119 | { 120 | return prepend0x ? $"0x{Convert.ToHexString(EBMLConverter.ToUIntBytes(id))}" : Convert.ToHexString(EBMLConverter.ToUIntBytes(id)); 121 | } 122 | /// 123 | /// Returns the element id from the specified hex id string 124 | /// 125 | /// 126 | /// 127 | public static ulong ElementIdFromHexId(string hexId) 128 | { 129 | if (hexId == null) return 0; 130 | if (hexId.StartsWith("0x")) hexId = hexId.Substring(2); 131 | var idBytes = Convert.FromHexString(hexId).ToList(); 132 | idBytes.Reverse(); 133 | while (idBytes.Count < 8) idBytes.Add(0); 134 | var id = BitConverter.ToUInt64(idBytes.ToArray()); 135 | return id; 136 | } 137 | public static byte[] ToVINTBytes(ulong x, int minOctets = 0) 138 | { 139 | int bytes; 140 | ulong flag; 141 | for (bytes = 1, flag = 0x80; x >= flag && bytes < 8; bytes++, flag *= 0x80) { } 142 | if (IsUnknownSizeVINT(x, bytes)) 143 | { 144 | bytes += 1; 145 | flag *= 0x80; 146 | } 147 | while (bytes < minOctets) 148 | { 149 | bytes += 1; 150 | flag *= 0x80; 151 | } 152 | var ret = new byte[bytes]; 153 | var value = flag + x; 154 | for (var i = bytes - 1; i >= 0; i--) 155 | { 156 | var c = value % 256; 157 | ret[i] = (byte)c; 158 | value = (value - c) / 256; 159 | } 160 | return ret; 161 | } 162 | public static int ToVINTByteSize(ulong x, int minOctets = 0) 163 | { 164 | int bytes; 165 | ulong flag; 166 | for (bytes = 1, flag = 0x80; x >= flag && bytes < 8; bytes++, flag *= 0x80) { } 167 | if (IsUnknownSizeVINT(x, bytes)) 168 | { 169 | bytes += 1; 170 | } 171 | bytes = Math.Max(minOctets, bytes); 172 | return bytes; 173 | } 174 | public static byte[] ToUIntBytes(ulong value, int minOctets = 0) 175 | { 176 | var bytes = BigEndian.GetBytes(value).ToList(); 177 | while (bytes.Count > 1 && bytes[0] == 0 && (minOctets <= 0 || bytes.Count > minOctets)) bytes.RemoveAt(0); 178 | return bytes.ToArray(); 179 | } 180 | public static byte[] ToIntBytes(long value, int minOctets = 0) 181 | { 182 | var bytes = BigEndian.GetBytes(value).ToList(); 183 | while (bytes.Count > 1 && bytes[0] == 0 && (minOctets <= 0 || bytes.Count > minOctets)) bytes.RemoveAt(0); 184 | return bytes.ToArray(); 185 | } 186 | public static byte[] ToFloatBytes(double value) 187 | { 188 | return BigEndian.GetBytes(value); 189 | } 190 | public static byte[] ToFloatBytes(float value) 191 | { 192 | return BigEndian.GetBytes(value); 193 | } 194 | private static readonly double TimeScale = 1000000; 195 | public static readonly DateTime DateTimeReferencePoint = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc); 196 | public static byte[] ToDateBytes(DateTime value, int minOctets = 0) 197 | { 198 | var timeOffset = (long)((value - DateTimeReferencePoint).TotalMilliseconds * TimeScale); 199 | return ToIntBytes(timeOffset, minOctets); 200 | } 201 | public static int GetFirstSetBitIndex(byte value, out byte leftover) 202 | { 203 | for (int i = 0; i < 8; i++) 204 | { 205 | var v = 1 << 7 - i; 206 | if ((value & v) != 0) 207 | { 208 | leftover = (byte)(value - v); 209 | return i; 210 | } 211 | } 212 | leftover = 0; 213 | return -1; 214 | } 215 | private static int GetFirstSetBitIndex(byte value) 216 | { 217 | for (var i = 0; i < 8; i++) 218 | { 219 | var v = 1 << 7 - i; 220 | if ((value & v) != 0) return i; 221 | } 222 | return -1; 223 | } 224 | public static ulong ToElementSize(byte[] data, out int vintSize, out bool isUnknownSize, int index = 0) 225 | { 226 | var size = ToVINT(data, out vintSize, index); 227 | isUnknownSize = IsUnknownSizeVINT(size, vintSize); 228 | return size; 229 | } 230 | public static ulong? ToElementSizeN(byte[] data, out int vintSize, out bool isUnknownSize, int index = 0) 231 | { 232 | var size = ToVINT(data, out vintSize, index); 233 | isUnknownSize = IsUnknownSizeVINT(size, vintSize); 234 | return isUnknownSize ? null : size; 235 | } 236 | public static int ReadElementHeader(byte[] data, out ulong id, out ulong? size, out int sizeLength) 237 | { 238 | var position = 0; 239 | var succ = ReadElementHeader(data, out id, out size, out sizeLength, ref position); 240 | return succ ? position : 0; 241 | } 242 | public static bool ReadElementHeader(byte[] data, out ulong id, out ulong? size, out int sizeLength, ref int position) 243 | { 244 | try 245 | { 246 | id = ReadElementIdRaw(data, ref position); 247 | var pos = position; 248 | size = ReadElementSizeN(data, ref position); 249 | sizeLength = position - pos; 250 | return true; 251 | } 252 | catch(Exception ex) 253 | { 254 | id = 0; 255 | size = null; 256 | sizeLength = 0; 257 | return false; 258 | } 259 | } 260 | public static bool IsParent(string instancePath, string test) 261 | { 262 | var parent = PathParent(instancePath); 263 | var ret = parent == test; 264 | return ret; 265 | } 266 | public static bool IsAncestor(string instancePath, string test) 267 | { 268 | var ret = $"{instancePath}/".StartsWith($"{test}/"); 269 | return ret; 270 | } 271 | public static List GetAncestorInstancePaths(string instancePath, bool removeRoot = false) 272 | { 273 | var ret = new List(); 274 | var path = EBMLConverter.PathParent(instancePath); 275 | while (!string.IsNullOrEmpty(path)) 276 | { 277 | if (removeRoot && path == "/") break; 278 | ret.Add(path); 279 | path = EBMLConverter.PathParent(path); 280 | if (ret.Count > 5) 281 | { 282 | var ggg = true; 283 | } 284 | } 285 | return ret; 286 | } 287 | /// 288 | /// V3 289 | /// 290 | /// 291 | /// 292 | /// 293 | public static ulong? ReadElementSizeN(byte[] data, ref int position) 294 | { 295 | var size = ToVINT(data, out var vintSize, position); 296 | var isUnknownSize = IsUnknownSizeVINT(size, vintSize); 297 | position += vintSize; 298 | return isUnknownSize ? null : size; 299 | } 300 | /// 301 | /// V3 302 | /// 303 | /// 304 | /// 305 | /// 306 | public static ulong ReadElementIdRaw(byte[] data, ref int position) 307 | { 308 | var firstByte = data[position]; 309 | var bitIndex = GetFirstSetBitIndex(firstByte); 310 | if (bitIndex < 0) throw new Exception("Invalid data"); 311 | var ulongBytes = new byte[8]; 312 | var destIndex = 8 - bitIndex; 313 | ulongBytes[destIndex - 1] = firstByte; 314 | if (bitIndex > 0) Buffer.BlockCopy(data, position + 1, ulongBytes, destIndex, bitIndex); 315 | position = bitIndex + 1; 316 | return BigEndian.ToUInt64(ulongBytes); 317 | } 318 | public static ulong ToElementId(byte[] data, out int vintSize, out bool isInvalid, int index = 0) 319 | { 320 | var size = ToVINT(data, out vintSize, index); 321 | isInvalid = IsUnknownSizeVINT(size, vintSize); 322 | return size; 323 | } 324 | public static ulong ToElementId(byte[] data, out int vintSize, int index = 0) => ToVINT(data, out vintSize, index); 325 | /// 326 | /// V3 327 | /// 328 | /// 329 | /// 330 | /// 331 | /// 332 | /// 333 | public static ulong ToVINT(byte[] data, out int size, int index = 0) 334 | { 335 | var firstByte = data[index]; 336 | var bitIndex = GetFirstSetBitIndex(firstByte, out var leftover); 337 | if (bitIndex < 0) throw new Exception("Invalid data"); 338 | var ulongBytes = new byte[8]; 339 | var destIndex = 8 - bitIndex; 340 | ulongBytes[destIndex - 1] = leftover; 341 | if (bitIndex > 0) Buffer.BlockCopy(data, index + 1, ulongBytes, destIndex, bitIndex); 342 | size = bitIndex + 1; 343 | return BigEndian.ToUInt64(ulongBytes); 344 | } 345 | public static ulong ToUInt(byte[] data) 346 | { 347 | var index = 0; 348 | var size = data.Length; 349 | var bytes = new byte[8]; 350 | var destIndex = 8 - size; 351 | if (size > 0) 352 | { 353 | Buffer.BlockCopy(data, index, bytes, destIndex, size); 354 | } 355 | return BigEndian.ToUInt64(bytes); 356 | } 357 | public static ulong ToUInt(byte[] data, int size, int index = 0) 358 | { 359 | var bytes = new byte[8]; 360 | var destIndex = 8 - size; 361 | if (size > 0) 362 | { 363 | Buffer.BlockCopy(data, index, bytes, destIndex, size); 364 | } 365 | return BigEndian.ToUInt64(bytes); 366 | } 367 | public static long ToInt(byte[] data, int size, int index = 0) 368 | { 369 | var bytes = new byte[8]; 370 | var destIndex = 8 - size; 371 | if (size > 0) 372 | { 373 | Buffer.BlockCopy(data, index, bytes, destIndex, size); 374 | } 375 | return BigEndian.ToInt64(bytes); 376 | } 377 | public static long ToInt(byte[] data) 378 | { 379 | var size = data.Length; 380 | var bytes = new byte[8]; 381 | var destIndex = 8 - size; 382 | if (size > 0) 383 | { 384 | Buffer.BlockCopy(data, 0, bytes, destIndex, size); 385 | } 386 | return BigEndian.ToInt64(bytes); 387 | } 388 | public static double ToFloat(byte[] data) 389 | { 390 | var size = data.Length; 391 | if (size == 4) 392 | { 393 | return BigEndian.ToSingle(data, 0); 394 | } 395 | else if (size == 8) 396 | { 397 | return BigEndian.ToDouble(data, 0); 398 | } 399 | return 0; 400 | } 401 | public static double ToFloat(byte[] data, int size, int index = 0) 402 | { 403 | if (size == 4) 404 | { 405 | return BigEndian.ToSingle(data, index); 406 | } 407 | else if (size == 8) 408 | { 409 | return BigEndian.ToDouble(data, index); 410 | } 411 | return 0; 412 | } 413 | public static string ToString(byte[] data, int count, int index = 0, Encoding? encoding = null) 414 | { 415 | if (encoding == null) encoding = Encoding.UTF8; 416 | return encoding.GetString(data, index, count).TrimEnd('\0'); 417 | } 418 | public static DateTime ToDate(byte[] data, int size, int index = 0) 419 | { 420 | if (size == 0) return DateTimeReferencePoint; 421 | var timeOffset = ToInt(data, size, index); 422 | return DateTimeReferencePoint + TimeSpan.FromMilliseconds(timeOffset / TimeScale); 423 | } 424 | public const ulong UnknownSizeVINT8 = 72057594037927935ul; 425 | public static ulong GetUnknownSizeValue(int size) => (ulong)Math.Pow(2, size * 8 - size) - 1ul; 426 | public static bool IsUnknownSizeVINT(ulong value, int size) => value == GetUnknownSizeValue(size); 427 | } 428 | } 429 | --------------------------------------------------------------------------------