mirror of
https://github.com/BililiveRecorder/BililiveRecorder.git
synced 2024-11-16 11:42:22 +08:00
Add ToolBox
This commit is contained in:
parent
14ffd7b700
commit
c6eae11f95
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\BililiveRecorder.Core\BililiveRecorder.Core.csproj" />
|
<ProjectReference Include="..\BililiveRecorder.Core\BililiveRecorder.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\BililiveRecorder.ToolBox\BililiveRecorder.ToolBox.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -9,6 +9,7 @@ using BililiveRecorder.Core;
|
||||||
using BililiveRecorder.Core.Config;
|
using BililiveRecorder.Core.Config;
|
||||||
using BililiveRecorder.Core.Config.V2;
|
using BililiveRecorder.Core.Config.V2;
|
||||||
using BililiveRecorder.DependencyInjection;
|
using BililiveRecorder.DependencyInjection;
|
||||||
|
using BililiveRecorder.ToolBox;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
@ -39,7 +40,8 @@ namespace BililiveRecorder.Cli
|
||||||
var root = new RootCommand("A Stream Recorder For Bilibili Live")
|
var root = new RootCommand("A Stream Recorder For Bilibili Live")
|
||||||
{
|
{
|
||||||
cmd_run,
|
cmd_run,
|
||||||
cmd_portable
|
cmd_portable,
|
||||||
|
new ToolCommand()
|
||||||
};
|
};
|
||||||
|
|
||||||
return root.Invoke(args);
|
return root.Invoke(args);
|
||||||
|
@ -85,7 +87,8 @@ namespace BililiveRecorder.Cli
|
||||||
var logger = BuildLogger();
|
var logger = BuildLogger();
|
||||||
Log.Logger = logger;
|
Log.Logger = logger;
|
||||||
|
|
||||||
var config = new ConfigV2(){
|
var config = new ConfigV2()
|
||||||
|
{
|
||||||
DisableConfigSave = true,
|
DisableConfigSave = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace BililiveRecorder.Flv.Writer
|
||||||
|
|
||||||
public bool CloseCurrentFile()
|
public bool CloseCurrentFile()
|
||||||
{
|
{
|
||||||
if (this.disposedValue)
|
if (this.disposedValue)
|
||||||
throw new ObjectDisposedException(nameof(FlvTagFileWriter));
|
throw new ObjectDisposedException(nameof(FlvTagFileWriter));
|
||||||
|
|
||||||
if (this.stream is null)
|
if (this.stream is null)
|
||||||
|
@ -52,6 +52,7 @@ namespace BililiveRecorder.Flv.Writer
|
||||||
if (this.disposedValue)
|
if (this.disposedValue)
|
||||||
throw new ObjectDisposedException(nameof(FlvTagFileWriter));
|
throw new ObjectDisposedException(nameof(FlvTagFileWriter));
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.Assert(this.stream is null, "stream is not null");
|
||||||
this.stream?.Dispose();
|
this.stream?.Dispose();
|
||||||
|
|
||||||
(this.stream, this.State) = this.targetProvider.CreateOutputStream();
|
(this.stream, this.State) = this.targetProvider.CreateOutputStream();
|
||||||
|
|
16
BililiveRecorder.ToolBox/BililiveRecorder.ToolBox.csproj
Normal file
16
BililiveRecorder.ToolBox/BililiveRecorder.ToolBox.csproj
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||||
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20574.7" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\BililiveRecorder.Flv\BililiveRecorder.Flv.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
158
BililiveRecorder.ToolBox/Commands/Analyze.cs
Normal file
158
BililiveRecorder.ToolBox/Commands/Analyze.cs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BililiveRecorder.Flv;
|
||||||
|
using BililiveRecorder.Flv.Amf;
|
||||||
|
using BililiveRecorder.Flv.Grouping;
|
||||||
|
using BililiveRecorder.Flv.Parser;
|
||||||
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
|
using BililiveRecorder.Flv.Writer;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.ToolBox.Commands
|
||||||
|
{
|
||||||
|
public class AnalyzeRequest : ICommandRequest<AnalyzeResponse>
|
||||||
|
{
|
||||||
|
public string Input { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AnalyzeResponse
|
||||||
|
{
|
||||||
|
public string InputPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool NeedFix { get; set; }
|
||||||
|
public bool Unrepairable { get; set; }
|
||||||
|
|
||||||
|
public int OutputFileCount { get; set; }
|
||||||
|
|
||||||
|
public int IssueTypeOther { get; set; }
|
||||||
|
public int IssueTypeUnrepairable { get; set; }
|
||||||
|
public int IssueTypeTimestampJump { get; set; }
|
||||||
|
public int IssueTypeDecodingHeader { get; set; }
|
||||||
|
public int IssueTypeRepeatingData { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AnalyzeHandler : ICommandHandler<AnalyzeRequest, AnalyzeResponse>
|
||||||
|
{
|
||||||
|
private static readonly ILogger logger = Log.ForContext<AnalyzeHandler>();
|
||||||
|
|
||||||
|
public Task<AnalyzeResponse> Handle(AnalyzeRequest request) => this.Handle(request, null);
|
||||||
|
|
||||||
|
public async Task<AnalyzeResponse> Handle(AnalyzeRequest request, Func<double, Task>? progress)
|
||||||
|
{
|
||||||
|
var inputPath = Path.GetFullPath(request.Input);
|
||||||
|
|
||||||
|
var memoryStreamProvider = new DefaultMemoryStreamProvider();
|
||||||
|
var tagWriter = new AnalyzeMockFlvTagWriter();
|
||||||
|
var comments = new List<ProcessingComment>();
|
||||||
|
var context = new FlvProcessingContext();
|
||||||
|
var session = new Dictionary<object, object?>();
|
||||||
|
|
||||||
|
{
|
||||||
|
using var inputStream = File.OpenRead(inputPath);
|
||||||
|
|
||||||
|
using var grouping = new TagGroupReader(new FlvTagPipeReader(PipeReader.Create(inputStream), memoryStreamProvider, skipData: false, logger: logger));
|
||||||
|
using var writer = new FlvProcessingContextWriter(tagWriter);
|
||||||
|
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).AddDefault().AddRemoveFillerData().Build();
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var group = await grouping.ReadGroupAsync(default).ConfigureAwait(false);
|
||||||
|
if (group is null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
context.Reset(group, session);
|
||||||
|
pipeline(context);
|
||||||
|
|
||||||
|
if (context.Comments.Count > 0)
|
||||||
|
{
|
||||||
|
comments.AddRange(context.Comments);
|
||||||
|
logger.Debug("分析逻辑输出 {@Comments}", context.Comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
await writer.WriteAsync(context).ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var action in context.Actions)
|
||||||
|
if (action is PipelineDataAction dataAction)
|
||||||
|
foreach (var tag in dataAction.Tags)
|
||||||
|
tag.BinaryData?.Dispose();
|
||||||
|
|
||||||
|
if (count++ % 10 == 0)
|
||||||
|
{
|
||||||
|
progress?.Invoke((double)inputStream.Position / inputStream.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var countableComments = comments.Where(x => x.T != CommentType.Logging);
|
||||||
|
|
||||||
|
var response = new AnalyzeResponse
|
||||||
|
{
|
||||||
|
InputPath = inputPath,
|
||||||
|
|
||||||
|
NeedFix = tagWriter.OutputFileCount != 1 || countableComments.Any(),
|
||||||
|
Unrepairable = countableComments.Any(x => x.T == CommentType.Unrepairable),
|
||||||
|
|
||||||
|
OutputFileCount = tagWriter.OutputFileCount,
|
||||||
|
|
||||||
|
IssueTypeOther = countableComments.Count(x => x.T == CommentType.Other),
|
||||||
|
IssueTypeUnrepairable = countableComments.Count(x => x.T == CommentType.Unrepairable),
|
||||||
|
IssueTypeTimestampJump = countableComments.Count(x => x.T == CommentType.TimestampJump),
|
||||||
|
IssueTypeDecodingHeader = countableComments.Count(x => x.T == CommentType.DecodingHeader),
|
||||||
|
IssueTypeRepeatingData = countableComments.Count(x => x.T == CommentType.RepeatingData)
|
||||||
|
};
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintResponse(AnalyzeResponse response)
|
||||||
|
{
|
||||||
|
Console.Write("Input: ");
|
||||||
|
Console.WriteLine(response.InputPath);
|
||||||
|
|
||||||
|
Console.WriteLine(response.NeedFix ? "File needs repair" : "File doesn't need repair");
|
||||||
|
|
||||||
|
if (response.Unrepairable)
|
||||||
|
Console.WriteLine("File contains error(s) that are unrepairable (yet), please send sample to the author of this program.");
|
||||||
|
|
||||||
|
Console.WriteLine("Will output {0} file(s) if repaired", response.OutputFileCount);
|
||||||
|
|
||||||
|
Console.WriteLine("Types of error:");
|
||||||
|
Console.Write("Other: ");
|
||||||
|
Console.WriteLine(response.IssueTypeOther);
|
||||||
|
Console.Write("Unrepairable: ");
|
||||||
|
Console.WriteLine(response.IssueTypeUnrepairable);
|
||||||
|
Console.Write("TimestampJump: ");
|
||||||
|
Console.WriteLine(response.IssueTypeTimestampJump);
|
||||||
|
Console.Write("DecodingHeader: ");
|
||||||
|
Console.WriteLine(response.IssueTypeDecodingHeader);
|
||||||
|
Console.Write("RepeatingData: ");
|
||||||
|
Console.WriteLine(response.IssueTypeRepeatingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AnalyzeMockFlvTagWriter : IFlvTagWriter
|
||||||
|
{
|
||||||
|
public long FileSize => 0;
|
||||||
|
public object? State => null;
|
||||||
|
|
||||||
|
public int OutputFileCount { get; private set; }
|
||||||
|
|
||||||
|
public bool CloseCurrentFile() => true;
|
||||||
|
public Task CreateNewFile()
|
||||||
|
{
|
||||||
|
this.OutputFileCount++;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() { }
|
||||||
|
public Task OverwriteMetadata(ScriptTagBody metadata) => Task.CompletedTask;
|
||||||
|
public Task WriteAlternativeHeaders(IEnumerable<Tag> tags) => Task.CompletedTask;
|
||||||
|
public Task WriteTag(Tag tag) => Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
BililiveRecorder.ToolBox/Commands/Export.cs
Normal file
66
BililiveRecorder.ToolBox/Commands/Export.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BililiveRecorder.Flv;
|
||||||
|
using BililiveRecorder.Flv.Parser;
|
||||||
|
using BililiveRecorder.Flv.Xml;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.ToolBox.Commands
|
||||||
|
{
|
||||||
|
public class ExportRequest : ICommandRequest<ExportResponse>
|
||||||
|
{
|
||||||
|
public string Input { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Output { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExportResponse
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExportHandler : ICommandHandler<ExportRequest, ExportResponse>
|
||||||
|
{
|
||||||
|
private static readonly ILogger logger = Log.ForContext<ExportHandler>();
|
||||||
|
|
||||||
|
public Task<ExportResponse> Handle(ExportRequest request) => this.Handle(request, null);
|
||||||
|
|
||||||
|
public async Task<ExportResponse> Handle(ExportRequest request, Func<double, Task>? progress)
|
||||||
|
{
|
||||||
|
using var inputStream = File.OpenRead(request.Input);
|
||||||
|
using var outputStream = File.OpenWrite(request.Output);
|
||||||
|
|
||||||
|
var tags = new List<Tag>();
|
||||||
|
|
||||||
|
{
|
||||||
|
using var reader = new FlvTagPipeReader(PipeReader.Create(inputStream), new DefaultMemoryStreamProvider(), skipData: true, logger: logger);
|
||||||
|
var count = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var tag = await reader.ReadTagAsync(default).ConfigureAwait(false);
|
||||||
|
if (tag is null) break;
|
||||||
|
tags.Add(tag);
|
||||||
|
|
||||||
|
if (count++ % 300 == 0)
|
||||||
|
progress?.Invoke((double)inputStream.Position / inputStream.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
using var writer = new StreamWriter(new GZipStream(outputStream, CompressionLevel.Optimal));
|
||||||
|
XmlFlvFile.Serializer.Serialize(writer, new XmlFlvFile
|
||||||
|
{
|
||||||
|
Tags = tags
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ExportResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintResponse(ExportResponse response)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
180
BililiveRecorder.ToolBox/Commands/Fix.cs
Normal file
180
BililiveRecorder.ToolBox/Commands/Fix.cs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BililiveRecorder.Flv;
|
||||||
|
using BililiveRecorder.Flv.Grouping;
|
||||||
|
using BililiveRecorder.Flv.Parser;
|
||||||
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
|
using BililiveRecorder.Flv.Writer;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.ToolBox.Commands
|
||||||
|
{
|
||||||
|
public class FixRequest : ICommandRequest<FixResponse>
|
||||||
|
{
|
||||||
|
public string Input { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string OutputBase { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FixResponse
|
||||||
|
{
|
||||||
|
public string InputPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string[] OutputPaths { get; set; } = Array.Empty<string>();
|
||||||
|
|
||||||
|
public bool NeedFix { get; set; }
|
||||||
|
public bool Unrepairable { get; set; }
|
||||||
|
|
||||||
|
public int OutputFileCount { get; set; }
|
||||||
|
|
||||||
|
public int IssueTypeOther { get; set; }
|
||||||
|
public int IssueTypeUnrepairable { get; set; }
|
||||||
|
public int IssueTypeTimestampJump { get; set; }
|
||||||
|
public int IssueTypeDecodingHeader { get; set; }
|
||||||
|
public int IssueTypeRepeatingData { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FixHandler : ICommandHandler<FixRequest, FixResponse>
|
||||||
|
{
|
||||||
|
private static readonly ILogger logger = Log.ForContext<FixHandler>();
|
||||||
|
|
||||||
|
public Task<FixResponse> Handle(FixRequest request) => this.Handle(request, null);
|
||||||
|
|
||||||
|
public async Task<FixResponse> Handle(FixRequest request, Func<double, Task>? progress)
|
||||||
|
{
|
||||||
|
var inputPath = Path.GetFullPath(request.Input);
|
||||||
|
|
||||||
|
var outputPaths = new List<string>();
|
||||||
|
var targetProvider = new AutoFixFlvWriterTargetProvider(request.OutputBase);
|
||||||
|
targetProvider.BeforeFileOpen += (sender, path) => outputPaths.Add(path);
|
||||||
|
|
||||||
|
var memoryStreamProvider = new DefaultMemoryStreamProvider();
|
||||||
|
var tagWriter = new FlvTagFileWriter(targetProvider, memoryStreamProvider, logger);
|
||||||
|
|
||||||
|
var comments = new List<ProcessingComment>();
|
||||||
|
var context = new FlvProcessingContext();
|
||||||
|
var session = new Dictionary<object, object?>();
|
||||||
|
|
||||||
|
{
|
||||||
|
using var inputStream = File.OpenRead(inputPath);
|
||||||
|
|
||||||
|
using var grouping = new TagGroupReader(new FlvTagPipeReader(PipeReader.Create(inputStream), memoryStreamProvider, skipData: false, logger: logger));
|
||||||
|
using var writer = new FlvProcessingContextWriter(tagWriter);
|
||||||
|
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).AddDefault().AddRemoveFillerData().Build();
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var group = await grouping.ReadGroupAsync(default).ConfigureAwait(false);
|
||||||
|
if (group is null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
context.Reset(group, session);
|
||||||
|
pipeline(context);
|
||||||
|
|
||||||
|
if (context.Comments.Count > 0)
|
||||||
|
{
|
||||||
|
comments.AddRange(context.Comments);
|
||||||
|
logger.Debug("修复逻辑输出 {@Comments}", context.Comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
await writer.WriteAsync(context).ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (var action in context.Actions)
|
||||||
|
if (action is PipelineDataAction dataAction)
|
||||||
|
foreach (var tag in dataAction.Tags)
|
||||||
|
tag.BinaryData?.Dispose();
|
||||||
|
|
||||||
|
if (count++ % 10 == 0)
|
||||||
|
{
|
||||||
|
progress?.Invoke((double)inputStream.Position / inputStream.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var countableComments = comments.Where(x => x.T != CommentType.Logging);
|
||||||
|
|
||||||
|
var response = new FixResponse
|
||||||
|
{
|
||||||
|
InputPath = inputPath,
|
||||||
|
OutputPaths = outputPaths.ToArray(),
|
||||||
|
OutputFileCount = outputPaths.Count,
|
||||||
|
|
||||||
|
NeedFix = outputPaths.Count != 1 || countableComments.Any(),
|
||||||
|
Unrepairable = countableComments.Any(x => x.T == CommentType.Unrepairable),
|
||||||
|
|
||||||
|
IssueTypeOther = countableComments.Count(x => x.T == CommentType.Other),
|
||||||
|
IssueTypeUnrepairable = countableComments.Count(x => x.T == CommentType.Unrepairable),
|
||||||
|
IssueTypeTimestampJump = countableComments.Count(x => x.T == CommentType.TimestampJump),
|
||||||
|
IssueTypeDecodingHeader = countableComments.Count(x => x.T == CommentType.DecodingHeader),
|
||||||
|
IssueTypeRepeatingData = countableComments.Count(x => x.T == CommentType.RepeatingData)
|
||||||
|
};
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintResponse(FixResponse response)
|
||||||
|
{
|
||||||
|
Console.Write("Input: ");
|
||||||
|
Console.WriteLine(response.InputPath);
|
||||||
|
|
||||||
|
Console.WriteLine(response.NeedFix ? "File needs repair" : "File doesn't need repair");
|
||||||
|
|
||||||
|
if (response.Unrepairable)
|
||||||
|
Console.WriteLine("File contains error(s) that are unrepairable (yet), please send sample to the author of this program.");
|
||||||
|
|
||||||
|
Console.WriteLine("{0} file(s) written", response.OutputFileCount);
|
||||||
|
|
||||||
|
foreach (var path in response.OutputPaths)
|
||||||
|
{
|
||||||
|
Console.Write(" ");
|
||||||
|
Console.WriteLine(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Types of error:");
|
||||||
|
Console.Write("Other: ");
|
||||||
|
Console.WriteLine(response.IssueTypeOther);
|
||||||
|
Console.Write("Unrepairable: ");
|
||||||
|
Console.WriteLine(response.IssueTypeUnrepairable);
|
||||||
|
Console.Write("TimestampJump: ");
|
||||||
|
Console.WriteLine(response.IssueTypeTimestampJump);
|
||||||
|
Console.Write("DecodingHeader: ");
|
||||||
|
Console.WriteLine(response.IssueTypeDecodingHeader);
|
||||||
|
Console.Write("RepeatingData: ");
|
||||||
|
Console.WriteLine(response.IssueTypeRepeatingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AutoFixFlvWriterTargetProvider : IFlvWriterTargetProvider
|
||||||
|
{
|
||||||
|
private readonly string pathTemplate;
|
||||||
|
private int fileIndex = 1;
|
||||||
|
|
||||||
|
public event EventHandler<string>? BeforeFileOpen;
|
||||||
|
|
||||||
|
public AutoFixFlvWriterTargetProvider(string pathTemplate)
|
||||||
|
{
|
||||||
|
this.pathTemplate = pathTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream CreateAlternativeHeaderStream()
|
||||||
|
{
|
||||||
|
var path = Path.ChangeExtension(this.pathTemplate, "header.txt");
|
||||||
|
return File.Open(path, FileMode.Append, FileAccess.Write, FileShare.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (Stream stream, object state) CreateOutputStream()
|
||||||
|
{
|
||||||
|
var i = this.fileIndex++;
|
||||||
|
var path = Path.ChangeExtension(this.pathTemplate, $"fix_p{i}.flv");
|
||||||
|
var fileStream = File.Create(path);
|
||||||
|
BeforeFileOpen?.Invoke(this, path);
|
||||||
|
return (fileStream, null!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
BililiveRecorder.ToolBox/ICommandHandler.cs
Normal file
10
BililiveRecorder.ToolBox/ICommandHandler.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.ToolBox
|
||||||
|
{
|
||||||
|
public interface ICommandHandler<TRequest, TResponse> where TRequest : ICommandRequest<TResponse>
|
||||||
|
{
|
||||||
|
Task<TResponse> Handle(TRequest request);
|
||||||
|
void PrintResponse(TResponse response);
|
||||||
|
}
|
||||||
|
}
|
4
BililiveRecorder.ToolBox/ICommandRequest.cs
Normal file
4
BililiveRecorder.ToolBox/ICommandRequest.cs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
namespace BililiveRecorder.ToolBox
|
||||||
|
{
|
||||||
|
public interface ICommandRequest<TResponse> { }
|
||||||
|
}
|
67
BililiveRecorder.ToolBox/ToolCommand.cs
Normal file
67
BililiveRecorder.ToolBox/ToolCommand.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using System;
|
||||||
|
using System.CommandLine;
|
||||||
|
using System.CommandLine.Invocation;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BililiveRecorder.ToolBox.Commands;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace BililiveRecorder.ToolBox
|
||||||
|
{
|
||||||
|
public class ToolCommand : Command
|
||||||
|
{
|
||||||
|
public ToolCommand() : base("tool", "Run Tools")
|
||||||
|
{
|
||||||
|
this.RegisterCommand<AnalyzeHandler, AnalyzeRequest, AnalyzeResponse>("analyze", null, c =>
|
||||||
|
{
|
||||||
|
c.Add(new Argument<string>("input", "example: input.flv"));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.RegisterCommand<FixHandler, FixRequest, FixResponse>("fix", null, c =>
|
||||||
|
{
|
||||||
|
c.Add(new Argument<string>("input", "example: input.flv"));
|
||||||
|
c.Add(new Argument<string>("output-base", "example: output.flv"));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.RegisterCommand<ExportHandler, ExportRequest, ExportResponse>("export", null, c =>
|
||||||
|
{
|
||||||
|
c.Add(new Argument<string>("input", "example: input.flv"));
|
||||||
|
c.Add(new Argument<string>("output", "example: output.brec.xml.gz"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterCommand<IHandler, IRequest, IResponse>(string name, string? description, Action<Command> configure)
|
||||||
|
where IHandler : ICommandHandler<IRequest, IResponse>
|
||||||
|
where IRequest : ICommandRequest<IResponse>
|
||||||
|
{
|
||||||
|
var cmd = new Command(name, description)
|
||||||
|
{
|
||||||
|
new Option<bool>("--json", "print result as json string"),
|
||||||
|
new Option<bool>("--json-indented", "print result as indented json string")
|
||||||
|
};
|
||||||
|
cmd.Handler = CommandHandler.Create((IRequest r, bool json, bool jsonIndented) => RunSubCommand<IHandler, IRequest, IResponse>(r, json, jsonIndented));
|
||||||
|
configure(cmd);
|
||||||
|
this.Add(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<int> RunSubCommand<IHandler, IRequest, IResponse>(IRequest request, bool json, bool jsonIndented)
|
||||||
|
where IHandler : ICommandHandler<IRequest, IResponse>
|
||||||
|
where IRequest : ICommandRequest<IResponse>
|
||||||
|
{
|
||||||
|
var handler = Activator.CreateInstance<IHandler>();
|
||||||
|
|
||||||
|
var response = await handler.Handle(request).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (json || jsonIndented)
|
||||||
|
{
|
||||||
|
var json_str = JsonConvert.SerializeObject(response, jsonIndented ? Formatting.Indented : Formatting.None);
|
||||||
|
Console.WriteLine(json_str);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handler.PrintResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -120,7 +120,6 @@
|
||||||
<Compile Include="Converters\RatioToArrowIconConverter.cs" />
|
<Compile Include="Converters\RatioToArrowIconConverter.cs" />
|
||||||
<Compile Include="Converters\ShortRoomIdToVisibilityConverter.cs" />
|
<Compile Include="Converters\ShortRoomIdToVisibilityConverter.cs" />
|
||||||
<Compile Include="Converters\ValueConverterGroup.cs" />
|
<Compile Include="Converters\ValueConverterGroup.cs" />
|
||||||
<Compile Include="Models\AnalyzeResultModel.cs" />
|
|
||||||
<Compile Include="Models\Commands.cs" />
|
<Compile Include="Models\Commands.cs" />
|
||||||
<Compile Include="Models\LogModel.cs" />
|
<Compile Include="Models\LogModel.cs" />
|
||||||
<Compile Include="Models\PollyPolicyModel.cs" />
|
<Compile Include="Models\PollyPolicyModel.cs" />
|
||||||
|
@ -293,6 +292,10 @@
|
||||||
<Project>{7610e19c-d3ab-4cbc-983e-6fda36f4d4b3}</Project>
|
<Project>{7610e19c-d3ab-4cbc-983e-6fda36f4d4b3}</Project>
|
||||||
<Name>BililiveRecorder.Flv</Name>
|
<Name>BililiveRecorder.Flv</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
|
<ProjectReference Include="..\BililiveRecorder.ToolBox\BililiveRecorder.ToolBox.csproj">
|
||||||
|
<Project>{4faae8e7-ac4e-4e99-a7d1-53d20ad8a200}</Project>
|
||||||
|
<Name>BililiveRecorder.ToolBox</Name>
|
||||||
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="GitVersion.MsBuild" Version="5.6.6">
|
<PackageReference Include="GitVersion.MsBuild" Version="5.6.6">
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
namespace BililiveRecorder.WPF.Models
|
|
||||||
{
|
|
||||||
internal class AnalyzeResultModel : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
private string file = string.Empty;
|
|
||||||
private bool needFix;
|
|
||||||
private bool unrepairable;
|
|
||||||
private int issueTypeOther;
|
|
||||||
private int issueTypeUnrepairable;
|
|
||||||
private int issueTypeTimestampJump;
|
|
||||||
private int issueTypeDecodingHeader;
|
|
||||||
private int issueTypeRepeatingData;
|
|
||||||
|
|
||||||
public string File { get => this.file; set => this.SetField(ref this.file, value); }
|
|
||||||
public bool NeedFix { get => this.needFix; set => this.SetField(ref this.needFix, value); }
|
|
||||||
public bool Unrepairable { get => this.unrepairable; set => this.SetField(ref this.unrepairable, value); }
|
|
||||||
|
|
||||||
public int IssueTypeOther { get => this.issueTypeOther; set => this.SetField(ref this.issueTypeOther, value); }
|
|
||||||
public int IssueTypeUnrepairable { get => this.issueTypeUnrepairable; set => this.SetField(ref this.issueTypeUnrepairable, value); }
|
|
||||||
public int IssueTypeTimestampJump { get => this.issueTypeTimestampJump; set => this.SetField(ref this.issueTypeTimestampJump, value); }
|
|
||||||
public int IssueTypeDecodingHeader { get => this.issueTypeDecodingHeader; set => this.SetField(ref this.issueTypeDecodingHeader, value); }
|
|
||||||
public int IssueTypeRepeatingData { get => this.issueTypeRepeatingData; set => this.SetField(ref this.issueTypeRepeatingData, value); }
|
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
|
||||||
protected virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
|
|
||||||
{
|
|
||||||
if (EqualityComparer<T>.Default.Equals(field, value)) { return false; }
|
|
||||||
field = value; this.OnPropertyChanged(propertyName); return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,7 +11,8 @@
|
||||||
l:ResxLocalizationProvider.DefaultDictionary="Strings"
|
l:ResxLocalizationProvider.DefaultDictionary="Strings"
|
||||||
xmlns:local="clr-namespace:BililiveRecorder.WPF.Pages"
|
xmlns:local="clr-namespace:BililiveRecorder.WPF.Pages"
|
||||||
xmlns:model="clr-namespace:BililiveRecorder.WPF.Models"
|
xmlns:model="clr-namespace:BililiveRecorder.WPF.Models"
|
||||||
xmlns:c="clr-namespace:BililiveRecorder.WPF.Converters"
|
xmlns:c="clr-namespace:BililiveRecorder.WPF.Converters"
|
||||||
|
xmlns:tool="clr-namespace:BililiveRecorder.ToolBox.Commands;assembly=BililiveRecorder.ToolBox"
|
||||||
mc:Ignorable="d" DataContext="{x:Null}"
|
mc:Ignorable="d" DataContext="{x:Null}"
|
||||||
d:DesignHeight="600" d:DesignWidth="900"
|
d:DesignHeight="600" d:DesignWidth="900"
|
||||||
Title="ToolboxAutoFixPage">
|
Title="ToolboxAutoFixPage">
|
||||||
|
@ -67,7 +68,7 @@
|
||||||
<TextBlock Text="注:不分析也可以进行修复操作" VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
<TextBlock Text="注:不分析也可以进行修复操作" VerticalAlignment="Center" HorizontalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
<DataTemplate x:Key="NormalAnalyzeResult" DataType="{x:Type model:AnalyzeResultModel}">
|
<DataTemplate x:Key="NormalAnalyzeResult" DataType="{x:Type tool:AnalyzeResponse}">
|
||||||
<Grid Margin="5">
|
<Grid Margin="5">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
|
@ -75,7 +76,7 @@
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBox Grid.Row="0" IsReadOnly="True" Text="{Binding File}" ui:ControlHelper.Header="文件:"/>
|
<TextBox Grid.Row="0" IsReadOnly="True" Text="{Binding InputPath}" ui:ControlHelper.Header="文件:"/>
|
||||||
<TextBlock Grid.Row="1" HorizontalAlignment="Center" FontSize="24" Text="无需修复" Foreground="Green"
|
<TextBlock Grid.Row="1" HorizontalAlignment="Center" FontSize="24" Text="无需修复" Foreground="Green"
|
||||||
Visibility="{Binding NeedFix,Converter={StaticResource InvertBooleanToVisibilityCollapsedConverter}}"/>
|
Visibility="{Binding NeedFix,Converter={StaticResource InvertBooleanToVisibilityCollapsedConverter}}"/>
|
||||||
<TextBlock Grid.Row="1" HorizontalAlignment="Center" FontSize="24" Text="需要修复" Foreground="Red"
|
<TextBlock Grid.Row="1" HorizontalAlignment="Center" FontSize="24" Text="需要修复" Foreground="Red"
|
||||||
|
@ -86,6 +87,7 @@
|
||||||
<TextBlock HorizontalAlignment="Center" Text="请点击“修复失败?”按钮并反馈本问题"/>
|
<TextBlock HorizontalAlignment="Center" Text="请点击“修复失败?”按钮并反馈本问题"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Row="3" HorizontalAlignment="Center" Margin="10">
|
<StackPanel Grid.Row="3" HorizontalAlignment="Center" Margin="10">
|
||||||
|
<TextBlock Text="{Binding OutputFileCount,StringFormat=修复将会输出 {0} 个文件}" Margin="0,0,0,5"/>
|
||||||
<TextBlock Text="{Binding IssueTypeTimestampJump,StringFormat=时间戳问题 {0} 处}"/>
|
<TextBlock Text="{Binding IssueTypeTimestampJump,StringFormat=时间戳问题 {0} 处}"/>
|
||||||
<TextBlock Text="{Binding IssueTypeDecodingHeader,StringFormat=分辨率、解码问题 {0} 处}"/>
|
<TextBlock Text="{Binding IssueTypeDecodingHeader,StringFormat=分辨率、解码问题 {0} 处}"/>
|
||||||
<TextBlock Text="{Binding IssueTypeRepeatingData,StringFormat=重复片段 {0} 处}"/>
|
<TextBlock Text="{Binding IssueTypeRepeatingData,StringFormat=重复片段 {0} 处}"/>
|
||||||
|
|
|
@ -12,6 +12,7 @@ using BililiveRecorder.Flv.Parser;
|
||||||
using BililiveRecorder.Flv.Pipeline;
|
using BililiveRecorder.Flv.Pipeline;
|
||||||
using BililiveRecorder.Flv.Writer;
|
using BililiveRecorder.Flv.Writer;
|
||||||
using BililiveRecorder.Flv.Xml;
|
using BililiveRecorder.Flv.Xml;
|
||||||
|
using BililiveRecorder.ToolBox.Commands;
|
||||||
using BililiveRecorder.WPF.Controls;
|
using BililiveRecorder.WPF.Controls;
|
||||||
using BililiveRecorder.WPF.Models;
|
using BililiveRecorder.WPF.Models;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
@ -72,7 +73,7 @@ namespace BililiveRecorder.WPF.Pages
|
||||||
progressDialog = new AutoFixProgressDialog();
|
progressDialog = new AutoFixProgressDialog();
|
||||||
var showTask = progressDialog.ShowAsync();
|
var showTask = progressDialog.ShowAsync();
|
||||||
|
|
||||||
IFlvWriterTargetProvider? targetProvider = null;
|
string? output_path;
|
||||||
{
|
{
|
||||||
var title = "选择保存位置";
|
var title = "选择保存位置";
|
||||||
var fileDialog = new CommonSaveFileDialog()
|
var fileDialog = new CommonSaveFileDialog()
|
||||||
|
@ -87,49 +88,25 @@ namespace BililiveRecorder.WPF.Pages
|
||||||
DefaultFileName = Path.GetFileName(inputPath)
|
DefaultFileName = Path.GetFileName(inputPath)
|
||||||
};
|
};
|
||||||
if (fileDialog.ShowDialog() == CommonFileDialogResult.Ok)
|
if (fileDialog.ShowDialog() == CommonFileDialogResult.Ok)
|
||||||
targetProvider = new AutoFixFlvWriterTargetProvider(fileDialog.FileName);
|
output_path = fileDialog.FileName;
|
||||||
else
|
else
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var inputStream = File.OpenRead(inputPath);
|
var req = new FixRequest
|
||||||
var memoryStreamProvider = new DefaultMemoryStreamProvider();
|
|
||||||
using var grouping = new TagGroupReader(new FlvTagPipeReader(PipeReader.Create(inputStream), memoryStreamProvider, skipData: false, logger: logger));
|
|
||||||
using var writer = new FlvProcessingContextWriter(new FlvTagFileWriter(targetProvider, memoryStreamProvider, logger));
|
|
||||||
var context = new FlvProcessingContext();
|
|
||||||
var session = new Dictionary<object, object?>();
|
|
||||||
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).AddDefault().AddRemoveFillerData().Build();
|
|
||||||
|
|
||||||
await Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
var count = 0;
|
Input = inputPath,
|
||||||
while (true)
|
OutputBase = output_path,
|
||||||
|
};
|
||||||
|
|
||||||
|
var handler = new FixHandler();
|
||||||
|
|
||||||
|
var resp = await handler.Handle(req, async p =>
|
||||||
|
{
|
||||||
|
await this.Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
var group = await grouping.ReadGroupAsync(default).ConfigureAwait(false);
|
progressDialog.Progress = (int)(p * 98d);
|
||||||
if (group is null)
|
});
|
||||||
break;
|
|
||||||
|
|
||||||
context.Reset(group, session);
|
|
||||||
pipeline(context);
|
|
||||||
|
|
||||||
if (context.Comments.Count > 0)
|
|
||||||
logger.Debug("修复逻辑输出 {@Comments}", context.Comments);
|
|
||||||
|
|
||||||
await writer.WriteAsync(context).ConfigureAwait(false);
|
|
||||||
|
|
||||||
foreach (var action in context.Actions)
|
|
||||||
if (action is PipelineDataAction dataAction)
|
|
||||||
foreach (var tag in dataAction.Tags)
|
|
||||||
tag.BinaryData?.Dispose();
|
|
||||||
|
|
||||||
if (count++ % 5 == 0)
|
|
||||||
{
|
|
||||||
await this.Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
progressDialog.Progress = (int)((double)inputStream.Position / inputStream.Length * 98d);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).ConfigureAwait(true);
|
}).ConfigureAwait(true);
|
||||||
|
|
||||||
progressDialog.Hide();
|
progressDialog.Hide();
|
||||||
|
@ -138,6 +115,7 @@ namespace BililiveRecorder.WPF.Pages
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.Error(ex, "修复时发生错误");
|
logger.Error(ex, "修复时发生错误");
|
||||||
|
MessageBox.Show("修复时发生错误\n" + ex.Message);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -163,60 +141,22 @@ namespace BililiveRecorder.WPF.Pages
|
||||||
progressDialog = new AutoFixProgressDialog();
|
progressDialog = new AutoFixProgressDialog();
|
||||||
var showTask = progressDialog.ShowAsync();
|
var showTask = progressDialog.ShowAsync();
|
||||||
|
|
||||||
using var inputStream = File.OpenRead(inputPath);
|
var req = new AnalyzeRequest
|
||||||
var memoryStreamProvider = new DefaultMemoryStreamProvider();
|
|
||||||
using var grouping = new TagGroupReader(new FlvTagPipeReader(PipeReader.Create(inputStream), memoryStreamProvider, skipData: false, logger: logger));
|
|
||||||
var comments = new List<ProcessingComment>();
|
|
||||||
var context = new FlvProcessingContext();
|
|
||||||
var session = new Dictionary<object, object?>();
|
|
||||||
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).AddDefault().AddRemoveFillerData().Build();
|
|
||||||
await Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
var count = 0;
|
Input = inputPath
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var group = await grouping.ReadGroupAsync(default).ConfigureAwait(false);
|
|
||||||
if (group is null)
|
|
||||||
break;
|
|
||||||
|
|
||||||
context.Reset(group, session);
|
|
||||||
pipeline(context);
|
|
||||||
|
|
||||||
if (context.Comments.Count > 0)
|
|
||||||
{
|
|
||||||
logger.Debug("分析逻辑输出 {@Comments}", context.Comments);
|
|
||||||
comments.AddRange(context.Comments);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var action in context.Actions)
|
|
||||||
if (action is PipelineDataAction dataAction)
|
|
||||||
foreach (var tag in dataAction.Tags)
|
|
||||||
tag.BinaryData?.Dispose();
|
|
||||||
|
|
||||||
if (count++ % 5 == 0)
|
|
||||||
{
|
|
||||||
await this.Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
progressDialog.Progress = (int)((double)inputStream.Position / inputStream.Length * 98d);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).ConfigureAwait(true);
|
|
||||||
|
|
||||||
var countableComments = comments.Where(x => x.T != CommentType.Logging);
|
|
||||||
var model = new AnalyzeResultModel
|
|
||||||
{
|
|
||||||
File = inputPath,
|
|
||||||
NeedFix = countableComments.Any(),
|
|
||||||
Unrepairable = countableComments.Any(x => x.T == CommentType.Unrepairable),
|
|
||||||
IssueTypeOther = countableComments.Count(x => x.T == CommentType.Other),
|
|
||||||
IssueTypeUnrepairable = countableComments.Count(x => x.T == CommentType.Unrepairable),
|
|
||||||
IssueTypeTimestampJump = countableComments.Count(x => x.T == CommentType.TimestampJump),
|
|
||||||
IssueTypeDecodingHeader = countableComments.Count(x => x.T == CommentType.DecodingHeader),
|
|
||||||
IssueTypeRepeatingData = countableComments.Count(x => x.T == CommentType.RepeatingData)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.analyzeResultDisplayArea.DataContext = model;
|
var handler = new AnalyzeHandler();
|
||||||
|
|
||||||
|
var resp = await handler.Handle(req, async p =>
|
||||||
|
{
|
||||||
|
await this.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
progressDialog.Progress = (int)(p * 98d);
|
||||||
|
});
|
||||||
|
}).ConfigureAwait(true);
|
||||||
|
|
||||||
|
this.analyzeResultDisplayArea.DataContext = resp;
|
||||||
|
|
||||||
progressDialog.Hide();
|
progressDialog.Hide();
|
||||||
await showTask.ConfigureAwait(true);
|
await showTask.ConfigureAwait(true);
|
||||||
|
@ -224,6 +164,7 @@ namespace BililiveRecorder.WPF.Pages
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.Error(ex, "分析时发生错误");
|
logger.Error(ex, "分析时发生错误");
|
||||||
|
MessageBox.Show("分析时发生错误\n" + ex.Message);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -273,35 +214,19 @@ namespace BililiveRecorder.WPF.Pages
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var inputStream = File.OpenRead(inputPath);
|
var req = new ExportRequest
|
||||||
var outputStream = File.OpenWrite(outputPath);
|
|
||||||
|
|
||||||
var tags = new List<Tag>();
|
|
||||||
using var reader = new FlvTagPipeReader(PipeReader.Create(inputStream), new DefaultMemoryStreamProvider(), skipData: true, logger: logger);
|
|
||||||
await Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
var count = 0;
|
Input = inputPath,
|
||||||
while (true)
|
Output = outputPath
|
||||||
{
|
};
|
||||||
var tag = await reader.ReadTagAsync(default).ConfigureAwait(false);
|
|
||||||
if (tag is null) break;
|
|
||||||
tags.Add(tag);
|
|
||||||
if (count++ % 300 == 0)
|
|
||||||
{
|
|
||||||
await this.Dispatcher.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
progressDialog.Progress = (int)((double)inputStream.Position / inputStream.Length * 95d);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).ConfigureAwait(true);
|
|
||||||
|
|
||||||
await Task.Run(() =>
|
var handler = new ExportHandler();
|
||||||
|
|
||||||
|
var resp = await handler.Handle(req, async p =>
|
||||||
{
|
{
|
||||||
using var writer = new StreamWriter(new GZipStream(outputStream, CompressionLevel.Optimal));
|
await this.Dispatcher.InvokeAsync(() =>
|
||||||
XmlFlvFile.Serializer.Serialize(writer, new XmlFlvFile
|
|
||||||
{
|
{
|
||||||
Tags = tags
|
progressDialog.Progress = (int)(p * 95d);
|
||||||
});
|
});
|
||||||
}).ConfigureAwait(true);
|
}).ConfigureAwait(true);
|
||||||
|
|
||||||
|
@ -311,6 +236,7 @@ namespace BililiveRecorder.WPF.Pages
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.Error(ex, "导出时发生错误");
|
logger.Error(ex, "导出时发生错误");
|
||||||
|
MessageBox.Show("导出时发生错误\n" + ex.Message);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -321,30 +247,5 @@ namespace BililiveRecorder.WPF.Pages
|
||||||
catch (Exception) { }
|
catch (Exception) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AutoFixFlvWriterTargetProvider : IFlvWriterTargetProvider
|
|
||||||
{
|
|
||||||
private readonly string pathTemplate;
|
|
||||||
private int fileIndex = 1;
|
|
||||||
|
|
||||||
public AutoFixFlvWriterTargetProvider(string pathTemplate)
|
|
||||||
{
|
|
||||||
this.pathTemplate = pathTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stream CreateAlternativeHeaderStream()
|
|
||||||
{
|
|
||||||
var path = Path.ChangeExtension(this.pathTemplate, "header.txt");
|
|
||||||
return File.Open(path, FileMode.Append, FileAccess.Write, FileShare.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
public (Stream stream, object state) CreateOutputStream()
|
|
||||||
{
|
|
||||||
var i = this.fileIndex++;
|
|
||||||
var path = Path.ChangeExtension(this.pathTemplate, $"fix_p{i}.flv");
|
|
||||||
var fileStream = File.Create(path);
|
|
||||||
return (fileStream, null!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,12 @@ using System.CommandLine.Invocation;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.ExceptionServices;
|
using System.Runtime.ExceptionServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
using BililiveRecorder.ToolBox;
|
||||||
using Sentry;
|
using Sentry;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Core;
|
using Serilog.Core;
|
||||||
|
@ -29,6 +31,7 @@ namespace BililiveRecorder.WPF
|
||||||
|
|
||||||
static Program()
|
static Program()
|
||||||
{
|
{
|
||||||
|
AttachConsole(-1);
|
||||||
levelSwitchGlobal = new LoggingLevelSwitch(Serilog.Events.LogEventLevel.Debug);
|
levelSwitchGlobal = new LoggingLevelSwitch(Serilog.Events.LogEventLevel.Debug);
|
||||||
if (Debugger.IsAttached)
|
if (Debugger.IsAttached)
|
||||||
levelSwitchGlobal.MinimumLevel = Serilog.Events.LogEventLevel.Verbose;
|
levelSwitchGlobal.MinimumLevel = Serilog.Events.LogEventLevel.Verbose;
|
||||||
|
@ -82,7 +85,8 @@ namespace BililiveRecorder.WPF
|
||||||
new Option<bool>("--squirrel-firstrun")
|
new Option<bool>("--squirrel-firstrun")
|
||||||
{
|
{
|
||||||
IsHidden = true
|
IsHidden = true
|
||||||
}
|
},
|
||||||
|
new ToolCommand(),
|
||||||
};
|
};
|
||||||
root.Handler = CommandHandler.Create((bool squirrelFirstrun) => Commands.RunWpfHandler(null, squirrelFirstrun));
|
root.Handler = CommandHandler.Create((bool squirrelFirstrun) => Commands.RunWpfHandler(null, squirrelFirstrun));
|
||||||
return root;
|
return root;
|
||||||
|
@ -126,20 +130,6 @@ namespace BililiveRecorder.WPF
|
||||||
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits
|
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static int Tool_Fix(string path)
|
|
||||||
{
|
|
||||||
levelSwitchConsole.MinimumLevel = Serilog.Events.LogEventLevel.Information;
|
|
||||||
// run code
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static int Tool_Parse(string path)
|
|
||||||
{
|
|
||||||
levelSwitchConsole.MinimumLevel = Serilog.Events.LogEventLevel.Information;
|
|
||||||
// run code
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Logger BuildLogger() => new LoggerConfiguration()
|
private static Logger BuildLogger() => new LoggerConfiguration()
|
||||||
|
@ -156,7 +146,7 @@ namespace BililiveRecorder.WPF
|
||||||
#else
|
#else
|
||||||
.WriteTo.Sink<WpfLogEventSink>(Serilog.Events.LogEventLevel.Information)
|
.WriteTo.Sink<WpfLogEventSink>(Serilog.Events.LogEventLevel.Information)
|
||||||
#endif
|
#endif
|
||||||
.WriteTo.File(new CompactJsonFormatter(), "./logs/bilirec.txt", shared: true, rollingInterval: RollingInterval.Day)
|
.WriteTo.File(new CompactJsonFormatter(), "./logs/bilirec.txt", shared: true, rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true)
|
||||||
.WriteTo.Sentry(o =>
|
.WriteTo.Sentry(o =>
|
||||||
{
|
{
|
||||||
o.Dsn = "https://7c6c5da3140543809661813aaa836207@o210546.ingest.sentry.io/5556540";
|
o.Dsn = "https://7c6c5da3140543809661813aaa836207@o210546.ingest.sentry.io/5556540";
|
||||||
|
@ -175,6 +165,9 @@ namespace BililiveRecorder.WPF
|
||||||
})
|
})
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
|
[DllImport("kernel32")]
|
||||||
|
private static extern bool AttachConsole(int pid);
|
||||||
|
|
||||||
[HandleProcessCorruptedStateExceptions, SecurityCritical]
|
[HandleProcessCorruptedStateExceptions, SecurityCritical]
|
||||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,7 +27,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BililiveRecorder.Core.UnitT
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BililiveRecorder.Flv.UnitTests", "test\BililiveRecorder.Flv.UnitTests\BililiveRecorder.Flv.UnitTests.csproj", "{560E8483-9293-410E-81E9-AB36B49F8A7C}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BililiveRecorder.Flv.UnitTests", "test\BililiveRecorder.Flv.UnitTests\BililiveRecorder.Flv.UnitTests.csproj", "{560E8483-9293-410E-81E9-AB36B49F8A7C}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BililiveRecorder.Flv.RuleTests", "test\BililiveRecorder.Flv.RuleTests\BililiveRecorder.Flv.RuleTests.csproj", "{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BililiveRecorder.Flv.RuleTests", "test\BililiveRecorder.Flv.RuleTests\BililiveRecorder.Flv.RuleTests.csproj", "{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BililiveRecorder.ToolBox", "BililiveRecorder.ToolBox\BililiveRecorder.ToolBox.csproj", "{4FAAE8E7-AC4E-4E99-A7D1-53D20AD8A200}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -63,6 +65,10 @@ Global
|
||||||
{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}.Release|Any CPU.Build.0 = Release|Any CPU
|
{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4FAAE8E7-AC4E-4E99-A7D1-53D20AD8A200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4FAAE8E7-AC4E-4E99-A7D1-53D20AD8A200}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4FAAE8E7-AC4E-4E99-A7D1-53D20AD8A200}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4FAAE8E7-AC4E-4E99-A7D1-53D20AD8A200}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -75,12 +81,13 @@ Global
|
||||||
{521EC763-5694-45A8-B87F-6E6B7F2A3BD4} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1}
|
{521EC763-5694-45A8-B87F-6E6B7F2A3BD4} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1}
|
||||||
{560E8483-9293-410E-81E9-AB36B49F8A7C} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1}
|
{560E8483-9293-410E-81E9-AB36B49F8A7C} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1}
|
||||||
{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1}
|
{75DA0162-DE06-4FA0-B6F8-C82C11AF65BC} = {623A2ACC-DAC6-4E6F-9242-B4B54381AAE1}
|
||||||
|
{4FAAE8E7-AC4E-4E99-A7D1-53D20AD8A200} = {2D44A59D-E437-4FEE-8A2E-3FF00D53A64D}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
RESX_ShowErrorsInErrorList = False
|
|
||||||
RESX_SaveFilesImmediatelyUponChange = True
|
|
||||||
RESX_NeutralResourcesLanguage = zh-Hans
|
|
||||||
SolutionGuid = {F3CB8B14-077A-458F-BD8E-1747ED0F5170}
|
|
||||||
RESX_SortFileContentOnSave = True
|
RESX_SortFileContentOnSave = True
|
||||||
|
SolutionGuid = {F3CB8B14-077A-458F-BD8E-1747ED0F5170}
|
||||||
|
RESX_NeutralResourcesLanguage = zh-Hans
|
||||||
|
RESX_SaveFilesImmediatelyUponChange = True
|
||||||
|
RESX_ShowErrorsInErrorList = False
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
Loading…
Reference in New Issue
Block a user