Initial commit

This commit is contained in:
2022-03-12 17:12:52 +01:00
commit 09d1a52aed
14 changed files with 1189 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
namespace MkvPropEditWrapper.MkvInfo;
public class MkvInfoReader
{
public const char LineDelimiter = '\n';
private readonly ReadOnlyMemory<char> _memory;
private ReadOnlyMemory<char> _remainingMemory;
private readonly MkvNodeRoot _root;
private MkvNode _currentNode;
private int _currentPosition;
public MkvInfoReader(string mkvInfoOutput)
{
_memory = mkvInfoOutput.AsMemory();
_remainingMemory = _memory;
_root = new MkvNodeRoot();
_currentNode = _root;
_currentPosition = 0;
}
public MkvNodeRoot Read()
{
if (_root.Children.Count > 0)
{
return _root;
}
while (_currentPosition < _memory.Length)
{
var line = ReadLine();
// Skip empty lines
if (line.Length < 1)
{
continue;
}
ProcessLine(line);
}
return _root;
}
private void ProcessLine(ReadOnlyMemory<char> line)
{
var lineSpan = line.Span;
// Find depth
int depth = lineSpan.IndexOf('+');
// Climb up the node tree
while (depth <= _currentNode.Depth)
{
_currentNode = _currentNode.Parent!;
}
// Node content
var content = line[(depth + 2)..];
var contentSpan = content.Span;
// Check for property
int propertyIndex = contentSpan.IndexOf(':');
MkvNode node;
// Property node
if (propertyIndex != -1)
{
var name = content[..propertyIndex];
var value = content[(propertyIndex + 1)..];
node = new MkvProperty(_currentNode, name, value);
}
// Generic node
else
{
node = new MkvNode(_currentNode, content);
}
_currentNode.Children.Add(node);
_currentNode = node;
}
/// <summary>
/// Read in the span till next \n or EOF while incrementing _currentPosition
/// </summary>
/// <returns></returns>
private ReadOnlyMemory<char> ReadLine()
{
var remainingSpan = _remainingMemory.Span;
ReadOnlyMemory<char> line;
int index = remainingSpan.IndexOf(LineDelimiter);
// Reach the end
if (index == -1)
{
line = _remainingMemory;
_currentPosition = _memory.Length;
}
else
{
line = _remainingMemory[..(index - 1)]; // -1 to skip \r
_currentPosition += index + 1; // Skip \n
}
// Move forward in the span
_remainingMemory = _memory[_currentPosition..];
return line;
}
}

View File

@@ -0,0 +1,80 @@
using System.Collections;
using System.Text;
namespace MkvPropEditWrapper.MkvInfo;
public class MkvNode : IEnumerable<MkvNode>
{
private static class KnowNodeTypes
{
public const string Head = "EBML head";
public const string Segment = "Segment";
public const string Chapter = "Chapters";
public const string Tag = "Tags";
public const string Track = "Tracks";
public const string Attachment = "Attachments";
}
public MkvNode? Parent { get; }
public IList<MkvNode> Children { get; }
public virtual NodeType NodeType { get; }
public virtual int Depth { get; }
public ReadOnlyMemory<char> Name { get; }
public MkvNode(MkvNode? parent, ReadOnlyMemory<char> name)
{
Parent = parent;
Name = name;
Depth = parent?.Depth + 1 ?? 0;
NodeType = parent is null ? NodeType.Root : FindNodeType();
Children = new List<MkvNode>();
}
private NodeType FindNodeType()
{
var span = Name.Span;
return Depth switch
{
0 when span.SequenceEqual(KnowNodeTypes.Head) => NodeType.Head,
0 when span.StartsWith(KnowNodeTypes.Segment) => NodeType.Segment,
1 when span.SequenceEqual(KnowNodeTypes.Chapter) => NodeType.Chapter,
1 when span.SequenceEqual(KnowNodeTypes.Tag) => NodeType.Tag,
1 when span.SequenceEqual(KnowNodeTypes.Track) => NodeType.Track,
1 when span.SequenceEqual(KnowNodeTypes.Attachment) => NodeType.Attachment,
1 => NodeType.Unknown,
_ => Parent!.NodeType
};
}
protected virtual void AppendSelf(StringBuilder builder) => builder.AppendLine($"{new string('-', Depth)}> [{NodeType}] {Name}");
public IEnumerator<MkvNode> GetEnumerator()
{
return Children.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public override string ToString()
{
var builder = new StringBuilder();
AppendSelf(builder);
foreach (var mkvNode in Children)
{
builder.Append(mkvNode);
}
return builder.ToString();
}
}

View File

@@ -0,0 +1,19 @@
using System.Text;
namespace MkvPropEditWrapper.MkvInfo;
public class MkvNodeRoot : MkvNode
{
public override NodeType NodeType => NodeType.Root;
public override int Depth => -1;
public MkvNodeRoot()
: base(null, "Root".AsMemory())
{
}
protected override void AppendSelf(StringBuilder builder)
{
}
}

View File

@@ -0,0 +1,52 @@
using System.Text;
namespace MkvPropEditWrapper.MkvInfo;
public class MkvProperty : MkvNode
{
private static class KnownProperties
{
public const string FlagDefault = "\"Default track\" flag";
public const string FlagForced = "\"Forced display\" flag";
public const string Language = "Language";
}
public override NodeType NodeType => NodeType.Property;
public PropertyType PropertyType { get; }
public ReadOnlyMemory<char> Value { get; }
public MkvProperty(MkvNode? parent, ReadOnlyMemory<char> name, ReadOnlyMemory<char> value)
: base(parent, name)
{
Value = value;
PropertyType = FindPropertyType(name);
}
private static PropertyType FindPropertyType(ReadOnlyMemory<char> name)
{
var span = name.Span;
if (span.SequenceEqual(KnownProperties.FlagDefault))
{
return PropertyType.FlagDefault;
}
if (span.SequenceEqual(KnownProperties.FlagForced))
{
return PropertyType.FlagForced;
}
if (span.SequenceEqual(KnownProperties.Language))
{
return PropertyType.Language;
}
return PropertyType.Unknown;
}
protected override void AppendSelf(StringBuilder builder)
{
builder.AppendLine($"{new string('-', Depth)}> [{NodeType}] {Name}: {Value}");
}
}

View File

@@ -0,0 +1,14 @@
namespace MkvPropEditWrapper.MkvInfo;
public enum NodeType
{
Unknown,
Property,
Root,
Head,
Segment,
Track,
Tag,
Attachment,
Chapter
}

View File

@@ -0,0 +1,21 @@
namespace MkvPropEditWrapper.MkvInfo;
public static class NodeUtils
{
public static IEnumerable<MkvNode> Descendants(this MkvNode root)
{
var nodes = new Stack<MkvNode>(new[] {root});
while (nodes.Any())
{
var node = nodes.Pop();
yield return node;
foreach (var newNode in node.Children)
{
nodes.Push(newNode);
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace MkvPropEditWrapper.MkvInfo;
public enum PropertyType
{
Unknown,
FlagDefault,
FlagForced,
Language
}

View File

@@ -0,0 +1,8 @@
namespace MkvPropEditWrapper;
public static class Flags
{
public const string Default = "flag-default";
public const string Enabled = "flag-enabled";
public const string Forced = "flag-forced";
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>MkvPropEditWrapper</RootNamespace>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,103 @@
using System.Diagnostics;
using System.Text;
namespace MkvPropEditWrapper.Utils;
public sealed class ProcessRunner
{
private readonly ProcessStartInfo _startInfo;
public ProcessRunner(ProcessStartInfo startInfo)
{
_startInfo = startInfo;
_startInfo.RedirectStandardOutput = true;
_startInfo.RedirectStandardError = true;
_startInfo.StandardOutputEncoding = Encoding.UTF8;
_startInfo.StandardErrorEncoding = Encoding.UTF8;
}
public bool TryRun(out string output)
{
using var process = Process.Start(_startInfo);
var outputBuilder = new StringBuilder();
var errorBuilder = new StringBuilder();
if (process is null)
{
output = "";
return false;
}
process.OutputDataReceived += (_, args) =>
{
outputBuilder.AppendLine(args.Data);
};
process.ErrorDataReceived += (_, args) =>
{
errorBuilder.AppendLine(args.Data);
};
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
if (process.ExitCode != 0)
{
output = BuildError(process, outputBuilder, errorBuilder);
return false;
}
output = outputBuilder.ToString();
return true;
}
public async Task<(bool sucess, string output)> TryRunAsync()
{
using var process = Process.Start(_startInfo);
var outputBuilder = new StringBuilder();
var errorBuilder = new StringBuilder();
if (process is null)
{
return (false, "");
}
process.OutputDataReceived += (_, args) =>
{
outputBuilder.AppendLine(args.Data);
};
process.ErrorDataReceived += (_, args) =>
{
errorBuilder.AppendLine(args.Data);
};
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync();
return process.ExitCode != 0
? (false, BuildError(process, outputBuilder, errorBuilder))
: (true, outputBuilder.ToString());
}
private static string BuildError(Process process, StringBuilder output, StringBuilder error)
{
StringBuilder builder = new();
builder.AppendLine($"Error while running process '{process.StartInfo.FileName} {process.StartInfo.Arguments}'\n");
builder.AppendLine($"Standard output:\n{output.ToString()}\n");
builder.AppendLine($"Standard error:\n{error.ToString()}\n");
return builder.ToString();
}
}