Initial commit
This commit is contained in:
117
MkvToolnixWrapper/MkvInfo/MkvInfoReader.cs
Normal file
117
MkvToolnixWrapper/MkvInfo/MkvInfoReader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
80
MkvToolnixWrapper/MkvInfo/MkvNode.cs
Normal file
80
MkvToolnixWrapper/MkvInfo/MkvNode.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
19
MkvToolnixWrapper/MkvInfo/MkvNodeRoot.cs
Normal file
19
MkvToolnixWrapper/MkvInfo/MkvNodeRoot.cs
Normal 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
52
MkvToolnixWrapper/MkvInfo/MkvProperty.cs
Normal file
52
MkvToolnixWrapper/MkvInfo/MkvProperty.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
14
MkvToolnixWrapper/MkvInfo/NodeType.cs
Normal file
14
MkvToolnixWrapper/MkvInfo/NodeType.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace MkvPropEditWrapper.MkvInfo;
|
||||
|
||||
public enum NodeType
|
||||
{
|
||||
Unknown,
|
||||
Property,
|
||||
Root,
|
||||
Head,
|
||||
Segment,
|
||||
Track,
|
||||
Tag,
|
||||
Attachment,
|
||||
Chapter
|
||||
}
|
||||
21
MkvToolnixWrapper/MkvInfo/NodeUtils.cs
Normal file
21
MkvToolnixWrapper/MkvInfo/NodeUtils.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
MkvToolnixWrapper/MkvInfo/PropertyType.cs
Normal file
9
MkvToolnixWrapper/MkvInfo/PropertyType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace MkvPropEditWrapper.MkvInfo;
|
||||
|
||||
public enum PropertyType
|
||||
{
|
||||
Unknown,
|
||||
FlagDefault,
|
||||
FlagForced,
|
||||
Language
|
||||
}
|
||||
8
MkvToolnixWrapper/MkvPropEdit/Flags.cs
Normal file
8
MkvToolnixWrapper/MkvPropEdit/Flags.cs
Normal 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";
|
||||
}
|
||||
10
MkvToolnixWrapper/MkvToolnixWrapper.csproj
Normal file
10
MkvToolnixWrapper/MkvToolnixWrapper.csproj
Normal 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>
|
||||
103
MkvToolnixWrapper/Utils/ProcessRunner.cs
Normal file
103
MkvToolnixWrapper/Utils/ProcessRunner.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user