FLV: Refactor text log accompanying recording files

This commit is contained in:
genteure 2022-06-17 23:16:40 +08:00
parent 0fde321637
commit fe527e7bc9
18 changed files with 96 additions and 90 deletions

View File

@ -296,11 +296,11 @@ namespace BililiveRecorder.Core.Recording
return (stream, state);
}
public Stream CreateAlternativeHeaderStream()
public Stream CreateAccompanyingTextLogStream()
{
var path = string.IsNullOrWhiteSpace(this.last_path)
? Path.ChangeExtension(this.task.CreateFileName().fullPath, "headers.txt")
: Path.ChangeExtension(this.last_path, "headers.txt");
? Path.ChangeExtension(this.task.CreateFileName().fullPath, "txt")
: Path.ChangeExtension(this.last_path, "txt");
try
{ Directory.CreateDirectory(Path.GetDirectoryName(path)); }

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BililiveRecorder.Flv.Amf;
@ -15,6 +14,6 @@ namespace BililiveRecorder.Flv
Task WriteTag(Tag tag);
Task OverwriteMetadata(ScriptTagBody metadata);
Task WriteAlternativeHeaders(IEnumerable<Tag> tags);
Task WriteAccompanyingTextLog(double lastTagDuration, string message);
}
}

View File

@ -6,6 +6,6 @@ namespace BililiveRecorder.Flv
{
(Stream stream, object? state) CreateOutputStream();
Stream CreateAlternativeHeaderStream();
Stream CreateAccompanyingTextLogStream();
}
}

View File

@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace BililiveRecorder.Flv.Pipeline.Actions
{
public class PipelineLogAlternativeHeaderAction : PipelineAction
{
public IReadOnlyList<Tag> Tags { get; set; }
public PipelineLogAlternativeHeaderAction(IReadOnlyList<Tag> tags)
{
this.Tags = tags ?? throw new ArgumentNullException(nameof(tags));
}
public override PipelineAction Clone() => new PipelineLogAlternativeHeaderAction(this.Tags.ToArray());
}
}

View File

@ -1,20 +1,16 @@
using System;
using Serilog.Events;
namespace BililiveRecorder.Flv.Pipeline.Actions
{
public class PipelineLogMessageWithLocationAction : PipelineAction
{
public PipelineLogMessageWithLocationAction(LogEventLevel level, string message)
public PipelineLogMessageWithLocationAction(string message)
{
this.Level = level;
this.Message = message ?? throw new ArgumentNullException(nameof(message));
}
public LogEventLevel Level { get; }
public string Message { get; }
public override PipelineAction Clone() => new PipelineLogMessageWithLocationAction(this.Level, this.Message);
public override PipelineAction Clone() => new PipelineLogMessageWithLocationAction(this.Message);
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BililiveRecorder.Flv.Pipeline.Actions;
using StructLinq;
using StructLinq.Where;
@ -81,7 +82,20 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
// 输出所有 Header 到一个独立的文件,以防出现无法播放的问题
// 如果不能正常播放,后期可以使用这里保存的 Header 配合 FlvInteractiveRebase 工具手动修复
if (multiple_header_present)
yield return new PipelineLogAlternativeHeaderAction(header.AllTags);
{
var b = new StringBuilder("连续遇到了多个不同的音视频Header如果录制的文件不能正常播放可以尝试用这里的数据进行手动修复\n");
foreach (var tag in header.AllTags)
{
b.Append("\n");
b.Append(tag.ToString());
b.Append("\n");
b.Append(tag.BinaryDataForSerializationUseOnly);
b.Append("\n");
}
yield return new PipelineLogMessageWithLocationAction(b.ToString());
}
}
else

View File

@ -93,19 +93,19 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
}
else
{
// 记录信息,不输出到文件,不对文件进行分段。
var message = $"重复收到 onMetaData, onMetaData 内容: {data?.ToJson() ?? "(null)"}";
// 记录信息,不对文件进行分段。
var message = $"收到直播服务器发送的 onMetaData 数据,请检查此位置是否有重复的直播片段或缺少数据。\n造成这个问题的原因可能是录播姬所连接的直播服务器与它的上级服务器的连接断开重连了。\n数据内容: {data?.ToJson() ?? "(null)"}";
context.AddComment(new ProcessingComment(CommentType.OnMetaData, false, message));
yield return new PipelineLogMessageWithLocationAction(Serilog.Events.LogEventLevel.Warning, "重复收到 onMetaData");
yield return new PipelineLogMessageWithLocationAction(message);
yield return (new PipelineScriptAction(new Tag
{
Type = TagType.Script,
ScriptData = new ScriptTagBody(new List<IScriptDataValue>
{
(ScriptDataString)onMetaData,
value
})
{
(ScriptDataString)onMetaData,
value
})
}));
yield break;
}

View File

@ -96,20 +96,16 @@ namespace BililiveRecorder.Flv.Writer
PipelineHeaderAction headerAction => this.WriteHeaderTags(headerAction),
PipelineDataAction dataAction => this.WriteDataTags(dataAction),
PipelineEndAction endAction => this.WriteEndTag(endAction),
PipelineLogAlternativeHeaderAction logAlternativeHeaderAction => this.WriteAlternativeHeader(logAlternativeHeaderAction),
PipelineLogMessageWithLocationAction pipelineLogMessageWithLocationAction => this.LogMessageWithLocation(pipelineLogMessageWithLocationAction),
_ => Task.CompletedTask,
};
private Task LogMessageWithLocation(PipelineLogMessageWithLocationAction logMessageWithLocationAction)
private async Task LogMessageWithLocation(PipelineLogMessageWithLocationAction logMessageWithLocationAction)
{
this.logger?.Write(logMessageWithLocationAction.Level, logMessageWithLocationAction.Message + $"\n 位置:视频时间 {this.lastDuration} 秒, 文件位置 {this.tagWriter.FileSize} 字节。");
return Task.CompletedTask;
this.logger?.Debug("写入录制记录,位置:视频时间 {FileDuration} 秒, 文件位置 {FileSize} 字节。\n{Message}", this.lastDuration, this.tagWriter.FileSize, logMessageWithLocationAction.Message);
await this.tagWriter.WriteAccompanyingTextLog(this.lastDuration, logMessageWithLocationAction.Message).ConfigureAwait(false);
}
private Task WriteAlternativeHeader(PipelineLogAlternativeHeaderAction logAlternativeHeaderAction) =>
this.tagWriter.WriteAlternativeHeaders(logAlternativeHeaderAction.Tags);
private Task OpenNewFile()
{
this.CloseCurrentFileImpl();

View File

@ -1,7 +1,6 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
@ -19,6 +18,7 @@ namespace BililiveRecorder.Flv.Writer
private readonly ILogger? logger;
private Stream? stream;
private StreamWriter? textFile;
private uint lastMetadataLength;
private bool disposedValue;
@ -43,10 +43,20 @@ namespace BililiveRecorder.Flv.Writer
try
{ this.stream.Dispose(); }
catch (Exception ex)
{ this.logger?.Warning(ex, "关闭文件时发生错误"); }
{ this.logger?.Warning(ex, "关闭录像文件时发生错误"); }
this.stream = null;
if (this.textFile is not null)
{
try
{ this.textFile.Dispose(); }
catch (Exception ex)
{ this.logger?.Warning(ex, "关闭录像记录文本文件时发生错误"); }
this.textFile = null;
}
return true;
}
@ -93,24 +103,37 @@ namespace BililiveRecorder.Flv.Writer
}
}
public async Task WriteAlternativeHeaders(IEnumerable<Tag> tags)
public async Task WriteAccompanyingTextLog(double lastTagDuration, string message)
{
if (this.disposedValue)
throw new ObjectDisposedException(nameof(FlvTagFileWriter));
using var writer = new StreamWriter(this.targetProvider.CreateAlternativeHeaderStream(), Encoding.UTF8);
await writer.WriteLineAsync("----- Group Start -----").ConfigureAwait(false);
await writer.WriteLineAsync("连续遇到了多个不同的音视频Header如果录制的文件不能正常播放可以尝试用这里的数据进行修复").ConfigureAwait(false);
await writer.WriteLineAsync(DateTimeOffset.Now.ToString("O")).ConfigureAwait(false);
foreach (var tag in tags)
if (this.textFile is null || !this.textFile.BaseStream.CanWrite)
{
await writer.WriteLineAsync().ConfigureAwait(false);
await writer.WriteLineAsync(tag.ToString()).ConfigureAwait(false);
await writer.WriteLineAsync(tag.BinaryDataForSerializationUseOnly).ConfigureAwait(false);
this.textFile = new StreamWriter(this.targetProvider.CreateAccompanyingTextLogStream(), Encoding.UTF8);
await this.textFile.WriteLineAsync("此文件内记录了对应的视频文件中可能存在的问题").ConfigureAwait(false);
await this.textFile.WriteAsync("B站录播姬 ").ConfigureAwait(false);
await this.textFile.WriteLineAsync(GitVersionInformation.FullSemVer).ConfigureAwait(false);
await this.textFile.WriteLineAsync().ConfigureAwait(false);
}
await writer.WriteLineAsync("----- Group End -----").ConfigureAwait(false);
var fileTime = TimeSpan.FromSeconds(lastTagDuration);
await this.textFile.WriteLineAsync("-----记录开始-----").ConfigureAwait(false);
await this.textFile.WriteAsync("记录时间: ").ConfigureAwait(false);
await this.textFile.WriteLineAsync(DateTimeOffset.Now.ToString("O")).ConfigureAwait(false);
await this.textFile.WriteAsync("视频时间: ").ConfigureAwait(false);
await this.textFile.WriteLineAsync($"{(int)Math.Floor(fileTime.TotalHours):D2}:{fileTime.Minutes:D2}:{fileTime.Seconds:D2}.{fileTime.Milliseconds:D3}").ConfigureAwait(false);
await this.textFile.WriteAsync("文件位置: ").ConfigureAwait(false);
await this.textFile.WriteAsync(this.FileSize.ToString()).ConfigureAwait(false);
await this.textFile.WriteLineAsync(" 字节").ConfigureAwait(false);
await this.textFile.WriteLineAsync(message).ConfigureAwait(false);
await this.textFile.WriteLineAsync("-----记录结束-----").ConfigureAwait(false);
await this.textFile.FlushAsync().ConfigureAwait(false);
}
public Task WriteTag(Tag tag)

View File

@ -12,11 +12,11 @@ namespace BililiveRecorder.Flv.Writer
public FlvTagListWriter()
{
this.Files = new List<List<Tag>>();
this.AlternativeHeaders = new List<Tag>();
this.AccompanyingTextLogs = new List<(double, string)>();
}
public List<List<Tag>> Files { get; }
public List<Tag> AlternativeHeaders { get; }
public List<(double lastTagDuration, string message)> AccompanyingTextLogs { get; }
public long FileSize => -1;
@ -42,9 +42,9 @@ namespace BililiveRecorder.Flv.Writer
public Task OverwriteMetadata(ScriptTagBody metadata) => Task.CompletedTask;
public Task WriteAlternativeHeaders(IEnumerable<Tag> tags)
public Task WriteAccompanyingTextLog(double lastTagDuration, string message)
{
this.AlternativeHeaders.AddRange(tags);
this.AccompanyingTextLogs.Add((lastTagDuration, message));
return Task.CompletedTask;
}

View File

@ -218,7 +218,7 @@ namespace BililiveRecorder.ToolBox.Tool.Analyze
public void Dispose() { }
public Task OverwriteMetadata(ScriptTagBody metadata) => Task.CompletedTask;
public Task WriteAlternativeHeaders(IEnumerable<Tag> tags) => Task.CompletedTask;
public Task WriteAccompanyingTextLog(double lastTagDuration, string message) => Task.CompletedTask;
public Task WriteTag(Tag tag) => Task.CompletedTask;
}
}

View File

@ -170,15 +170,15 @@ namespace BililiveRecorder.ToolBox.Tool.Fix
XmlFlvFile.Serializer.Serialize(file, new XmlFlvFile { Tags = w.Files[i] });
}
if (w.AlternativeHeaders.Count > 0)
if (w.AccompanyingTextLogs.Count > 0)
{
var path = Path.ChangeExtension(request.OutputBase, $"headers.txt");
var path = Path.ChangeExtension(request.OutputBase, "txt");
using var writer = new StreamWriter(File.Open(path, FileMode.Append, FileAccess.Write, FileShare.None));
foreach (var tag in w.AlternativeHeaders)
foreach (var (lastTagDuration, message) in w.AccompanyingTextLogs)
{
writer.WriteLine();
writer.WriteLine(tag.ToString());
writer.WriteLine(tag.BinaryDataForSerializationUseOnly);
writer.WriteLine(lastTagDuration);
writer.WriteLine(message);
}
}
});
@ -264,9 +264,9 @@ namespace BililiveRecorder.ToolBox.Tool.Fix
this.pathTemplate = pathTemplate;
}
public Stream CreateAlternativeHeaderStream()
public Stream CreateAccompanyingTextLogStream()
{
var path = Path.ChangeExtension(this.pathTemplate, "header.txt");
var path = Path.ChangeExtension(this.pathTemplate, "txt");
return new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read);
}

View File

@ -342,12 +342,12 @@ namespace BililiveRecorder.Flv
bool CloseCurrentFile();
System.Threading.Tasks.Task CreateNewFile();
System.Threading.Tasks.Task OverwriteMetadata(BililiveRecorder.Flv.Amf.ScriptTagBody metadata);
System.Threading.Tasks.Task WriteAlternativeHeaders(System.Collections.Generic.IEnumerable<BililiveRecorder.Flv.Tag> tags);
System.Threading.Tasks.Task WriteAccompanyingTextLog(double lastTagDuration, string message);
System.Threading.Tasks.Task WriteTag(BililiveRecorder.Flv.Tag tag);
}
public interface IFlvWriterTargetProvider
{
System.IO.Stream CreateAlternativeHeaderStream();
System.IO.Stream CreateAccompanyingTextLogStream();
[return: System.Runtime.CompilerServices.TupleElementNames(new string[] {
"stream",
"state"})]
@ -671,16 +671,9 @@ namespace BililiveRecorder.Flv.Pipeline.Actions
public BililiveRecorder.Flv.Tag? VideoHeader { get; set; }
public override BililiveRecorder.Flv.Pipeline.Actions.PipelineAction Clone() { }
}
public class PipelineLogAlternativeHeaderAction : BililiveRecorder.Flv.Pipeline.Actions.PipelineAction
{
public PipelineLogAlternativeHeaderAction(System.Collections.Generic.IReadOnlyList<BililiveRecorder.Flv.Tag> tags) { }
public System.Collections.Generic.IReadOnlyList<BililiveRecorder.Flv.Tag> Tags { get; set; }
public override BililiveRecorder.Flv.Pipeline.Actions.PipelineAction Clone() { }
}
public class PipelineLogMessageWithLocationAction : BililiveRecorder.Flv.Pipeline.Actions.PipelineAction
{
public PipelineLogMessageWithLocationAction(Serilog.Events.LogEventLevel level, string message) { }
public Serilog.Events.LogEventLevel Level { get; }
public PipelineLogMessageWithLocationAction(string message) { }
public string Message { get; }
public override BililiveRecorder.Flv.Pipeline.Actions.PipelineAction Clone() { }
}
@ -832,13 +825,16 @@ namespace BililiveRecorder.Flv.Writer
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
public System.Threading.Tasks.Task OverwriteMetadata(BililiveRecorder.Flv.Amf.ScriptTagBody metadata) { }
public System.Threading.Tasks.Task WriteAlternativeHeaders(System.Collections.Generic.IEnumerable<BililiveRecorder.Flv.Tag> tags) { }
public System.Threading.Tasks.Task WriteAccompanyingTextLog(double lastTagDuration, string message) { }
public System.Threading.Tasks.Task WriteTag(BililiveRecorder.Flv.Tag tag) { }
}
public class FlvTagListWriter : BililiveRecorder.Flv.IFlvTagWriter, System.IDisposable
{
public FlvTagListWriter() { }
public System.Collections.Generic.List<BililiveRecorder.Flv.Tag> AlternativeHeaders { get; }
[System.Runtime.CompilerServices.TupleElementNames(new string[] {
"lastTagDuration",
"message"})]
public System.Collections.Generic.List<System.ValueTuple<double, string>> AccompanyingTextLogs { get; }
public long FileSize { get; }
public System.Collections.Generic.List<System.Collections.Generic.List<BililiveRecorder.Flv.Tag>> Files { get; }
public object? State { get; }
@ -846,7 +842,7 @@ namespace BililiveRecorder.Flv.Writer
public System.Threading.Tasks.Task CreateNewFile() { }
public void Dispose() { }
public System.Threading.Tasks.Task OverwriteMetadata(BililiveRecorder.Flv.Amf.ScriptTagBody metadata) { }
public System.Threading.Tasks.Task WriteAlternativeHeaders(System.Collections.Generic.IEnumerable<BililiveRecorder.Flv.Tag> tags) { }
public System.Threading.Tasks.Task WriteAccompanyingTextLog(double lastTagDuration, string message) { }
public System.Threading.Tasks.Task WriteTag(BililiveRecorder.Flv.Tag tag) { }
}
}

View File

@ -43,7 +43,7 @@ namespace BililiveRecorder.Flv.Tests.FlvTests
private bool flag = false;
public Stream CreateAlternativeHeaderStream() => throw new System.NotImplementedException();
public Stream CreateAccompanyingTextLogStream() => throw new System.NotImplementedException();
public (Stream stream, object? state) CreateOutputStream()
{

View File

@ -39,7 +39,7 @@ namespace BililiveRecorder.Flv.Tests.RuleTests
var outputResult = new OutputResult
{
AlternativeHeaders = flvTagListWriter.AlternativeHeaders.Select(x => x.BinaryDataForSerializationUseOnly).ToArray(),
AccompanyingTextLogs = flvTagListWriter.AccompanyingTextLogs.Select(x => x.lastTagDuration + "\n" + x.message).ToArray(),
Comments = comments.GroupBy(x => x.Type).Select(x => new CommentCount(x.Key, x.Count())).ToArray(),
TagCounts = flvTagListWriter.Files.Select(x => x.Count).ToArray()
};
@ -69,7 +69,7 @@ namespace BililiveRecorder.Flv.Tests.RuleTests
public CommentCount[] Comments { get; set; } = Array.Empty<CommentCount>();
public string?[] AlternativeHeaders { get; set; } = Array.Empty<string>();
public string?[] AccompanyingTextLogs { get; set; } = Array.Empty<string>();
}
public struct CommentCount

View File

@ -33,7 +33,7 @@ namespace BililiveRecorder.Flv.Tests.RuleTests
Assert.Empty(comments);
Assert.Empty(flvTagListWriter.AlternativeHeaders);
Assert.Empty(flvTagListWriter.AccompanyingTextLogs);
var outputTags = Assert.Single(flvTagListWriter.Files);
Assert.Equal(originalTags.Count, outputTags.Count);
@ -77,7 +77,7 @@ namespace BililiveRecorder.Flv.Tests.RuleTests
comments.RemoveAll(x => !x.ActionRequired);
Assert.Equal(CommentType.TimestampJump, Assert.Single(comments).Type);
Assert.Empty(output.AlternativeHeaders);
Assert.Empty(output.AccompanyingTextLogs);
var outputTags = Assert.Single(output.Files);
Assert.Equal(originalTags.Count, outputTags.Count);

View File

@ -47,7 +47,7 @@ namespace BililiveRecorder.Flv.Tests.RuleTests
// 不应该有任何问题 Shouldn't have any problems
Assert.Empty(comments);
// 不应该有多个 Header Shouldn't have multiple headers
Assert.Empty(output.AlternativeHeaders);
Assert.Empty(output.AccompanyingTextLogs);
// 只应该有一个文件输出 Should output only a single file
var outputTags = Assert.Single(output.Files);

@ -1 +1 @@
Subproject commit 6ae84c024d9d556774f9da4677503bbc9aa8315c
Subproject commit d5c346c26e2d8c5bc7d3aa68a6d281f0b603e31a