Add new option FlvProcessorSplitOnScriptTag

This commit is contained in:
genteure 2022-06-25 17:31:21 +08:00
parent 8aa53b5b30
commit 232eb1af5b
25 changed files with 292 additions and 50 deletions

View File

@ -24,6 +24,7 @@ namespace BililiveRecorder.Cli.Configure
RecordDanmakuGuard,
RecordingQuality,
FileNameRecordTemplate,
FlvProcessorSplitOnScriptTag,
WebHookUrls,
WebHookUrlsV2,
WpfShowTitleAndArea,
@ -54,7 +55,8 @@ namespace BililiveRecorder.Cli.Configure
RecordDanmakuSuperChat,
RecordDanmakuGift,
RecordDanmakuGuard,
RecordingQuality
RecordingQuality,
FlvProcessorSplitOnScriptTag
}
public static class ConfigInstructions
{
@ -73,6 +75,7 @@ namespace BililiveRecorder.Cli.Configure
GlobalConfig.Add(GlobalConfigProperties.RecordDanmakuGuard, new ConfigInstruction<GlobalConfig, bool>(config => config.HasRecordDanmakuGuard = false, (config, value) => config.RecordDanmakuGuard = value) { Name = "RecordDanmakuGuard", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.RecordingQuality, new ConfigInstruction<GlobalConfig, string>(config => config.HasRecordingQuality = false, (config, value) => config.RecordingQuality = value) { Name = "RecordingQuality", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.FileNameRecordTemplate, new ConfigInstruction<GlobalConfig, string>(config => config.HasFileNameRecordTemplate = false, (config, value) => config.FileNameRecordTemplate = value) { Name = "FileNameRecordTemplate", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.FlvProcessorSplitOnScriptTag, new ConfigInstruction<GlobalConfig, bool>(config => config.HasFlvProcessorSplitOnScriptTag = false, (config, value) => config.FlvProcessorSplitOnScriptTag = value) { Name = "FlvProcessorSplitOnScriptTag", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.WebHookUrls, new ConfigInstruction<GlobalConfig, string>(config => config.HasWebHookUrls = false, (config, value) => config.WebHookUrls = value) { Name = "WebHookUrls", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.WebHookUrlsV2, new ConfigInstruction<GlobalConfig, string>(config => config.HasWebHookUrlsV2 = false, (config, value) => config.WebHookUrlsV2 = value) { Name = "WebHookUrlsV2", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.WpfShowTitleAndArea, new ConfigInstruction<GlobalConfig, bool>(config => config.HasWpfShowTitleAndArea = false, (config, value) => config.WpfShowTitleAndArea = value) { Name = "WpfShowTitleAndArea", CanBeOptional = true });
@ -100,6 +103,7 @@ namespace BililiveRecorder.Cli.Configure
RoomConfig.Add(RoomConfigProperties.RecordDanmakuGift, new ConfigInstruction<RoomConfig, bool>(config => config.HasRecordDanmakuGift = false, (config, value) => config.RecordDanmakuGift = value) { Name = "RecordDanmakuGift", CanBeOptional = true });
RoomConfig.Add(RoomConfigProperties.RecordDanmakuGuard, new ConfigInstruction<RoomConfig, bool>(config => config.HasRecordDanmakuGuard = false, (config, value) => config.RecordDanmakuGuard = value) { Name = "RecordDanmakuGuard", CanBeOptional = true });
RoomConfig.Add(RoomConfigProperties.RecordingQuality, new ConfigInstruction<RoomConfig, string>(config => config.HasRecordingQuality = false, (config, value) => config.RecordingQuality = value) { Name = "RecordingQuality", CanBeOptional = true });
RoomConfig.Add(RoomConfigProperties.FlvProcessorSplitOnScriptTag, new ConfigInstruction<RoomConfig, bool>(config => config.HasFlvProcessorSplitOnScriptTag = false, (config, value) => config.FlvProcessorSplitOnScriptTag = value) { Name = "FlvProcessorSplitOnScriptTag", CanBeOptional = true });
}
}

View File

@ -101,6 +101,14 @@ namespace BililiveRecorder.Core.Config.V3
[JsonProperty(nameof(RecordingQuality)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<string?> OptionalRecordingQuality { get => this.GetPropertyValueOptional<string>(nameof(this.RecordingQuality)); set => this.SetPropertyValueOptional(value, nameof(this.RecordingQuality)); }
/// <summary>
/// FLV修复-检测到可能缺少数据时分段
/// </summary>
public bool FlvProcessorSplitOnScriptTag { get => this.GetPropertyValue<bool>(); set => this.SetPropertyValue(value); }
public bool HasFlvProcessorSplitOnScriptTag { get => this.GetPropertyHasValue(nameof(this.FlvProcessorSplitOnScriptTag)); set => this.SetPropertyHasValue<bool>(value, nameof(this.FlvProcessorSplitOnScriptTag)); }
[JsonProperty(nameof(FlvProcessorSplitOnScriptTag)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<bool> OptionalFlvProcessorSplitOnScriptTag { get => this.GetPropertyValueOptional<bool>(nameof(this.FlvProcessorSplitOnScriptTag)); set => this.SetPropertyValueOptional(value, nameof(this.FlvProcessorSplitOnScriptTag)); }
/// <summary>
/// 录制文件名模板
/// </summary>
@ -266,6 +274,14 @@ namespace BililiveRecorder.Core.Config.V3
[JsonProperty(nameof(FileNameRecordTemplate)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<string?> OptionalFileNameRecordTemplate { get => this.GetPropertyValueOptional<string>(nameof(this.FileNameRecordTemplate)); set => this.SetPropertyValueOptional(value, nameof(this.FileNameRecordTemplate)); }
/// <summary>
/// FLV修复-检测到可能缺少数据时分段
/// </summary>
public bool FlvProcessorSplitOnScriptTag { get => this.GetPropertyValue<bool>(); set => this.SetPropertyValue(value); }
public bool HasFlvProcessorSplitOnScriptTag { get => this.GetPropertyHasValue(nameof(this.FlvProcessorSplitOnScriptTag)); set => this.SetPropertyHasValue<bool>(value, nameof(this.FlvProcessorSplitOnScriptTag)); }
[JsonProperty(nameof(FlvProcessorSplitOnScriptTag)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<bool> OptionalFlvProcessorSplitOnScriptTag { get => this.GetPropertyValueOptional<bool>(nameof(this.FlvProcessorSplitOnScriptTag)); set => this.SetPropertyValueOptional(value, nameof(this.FlvProcessorSplitOnScriptTag)); }
/// <summary>
/// WebhookV1
/// </summary>
@ -413,6 +429,8 @@ namespace BililiveRecorder.Core.Config.V3
public string FileNameRecordTemplate => @"{{ roomId }}-{{ name }}/录制-{{ roomId }}-{{ ""now"" | time_zone: ""Asia/Shanghai"" | format_date: ""yyyyMMdd-HHmmss-fff"" }}-{{ title }}.flv";
public bool FlvProcessorSplitOnScriptTag => false;
public string WebHookUrls => @"";
public string WebHookUrlsV2 => @"";

View File

@ -15,6 +15,7 @@ using BililiveRecorder.Flv;
using BililiveRecorder.Flv.Amf;
using BililiveRecorder.Flv.Pipeline;
using BililiveRecorder.Flv.Pipeline.Actions;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
namespace BililiveRecorder.Core.Recording
@ -63,10 +64,14 @@ namespace BililiveRecorder.Core.Recording
this.statsRule.StatsUpdated += this.StatsRule_StatsUpdated;
this.pipeline = builder
.Add(this.statsRule)
.Add(this.splitFileRule)
.AddDefault()
.AddRemoveFillerData()
.ConfigureServices(services => services.AddSingleton(new ProcessingPipelineSettings
{
SplitOnScriptTag = room.RoomConfig.FlvProcessorSplitOnScriptTag
}))
.AddRule(this.statsRule)
.AddRule(this.splitFileRule)
.AddDefaultRules()
.AddRemoveFillerDataRule()
.Build();
this.targetProvider = new WriterTargetProvider(this, paths =>

View File

@ -11,6 +11,7 @@
<PackageReference Include="FastHashes" Version="2.5.0" />
<PackageReference Include="JsonSubTypes" Version="1.9.0" />
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Nullable" Version="1.3.0">

View File

@ -1,12 +1,13 @@
using System;
using Microsoft.Extensions.DependencyInjection;
namespace BililiveRecorder.Flv.Pipeline
{
public interface IProcessingPipelineBuilder
{
IServiceProvider ServiceProvider { get; }
IServiceCollection ServiceCollection { get; }
IProcessingPipelineBuilder Add(Func<ProcessingDelegate, ProcessingDelegate> rule);
IProcessingPipelineBuilder AddRule(Func<ProcessingDelegate, IServiceProvider, ProcessingDelegate> rule);
ProcessingDelegate Build();
}

View File

@ -6,34 +6,40 @@ namespace BililiveRecorder.Flv.Pipeline
{
public static class IProcessingPipelineBuilderExtensions
{
public static IProcessingPipelineBuilder Add<T>(this IProcessingPipelineBuilder builder) where T : IProcessingRule =>
builder.Add(next => (ActivatorUtilities.GetServiceOrCreateInstance<T>(builder.ServiceProvider)) switch
public static IProcessingPipelineBuilder ConfigureServices(this IProcessingPipelineBuilder builder, Action<IServiceCollection> configure)
{
configure?.Invoke(builder.ServiceCollection);
return builder;
}
public static IProcessingPipelineBuilder AddRule<T>(this IProcessingPipelineBuilder builder) where T : IProcessingRule =>
builder.AddRule((next, services) => ActivatorUtilities.GetServiceOrCreateInstance<T>(services) switch
{
ISimpleProcessingRule simple => context => simple.Run(context, () => next(context)),
IFullProcessingRule full => context => full.Run(context, next),
_ => throw new ArgumentException($"Type ({typeof(T).FullName}) does not ISimpleProcessingRule or IFullProcessingRule")
});
public static IProcessingPipelineBuilder Add<T>(this IProcessingPipelineBuilder builder, T instance) where T : IProcessingRule =>
public static IProcessingPipelineBuilder AddRule<T>(this IProcessingPipelineBuilder builder, T instance) where T : IProcessingRule =>
instance switch
{
ISimpleProcessingRule simple => builder.Add(next => context => simple.Run(context, () => next(context))),
IFullProcessingRule full => builder.Add(next => context => full.Run(context, next)),
ISimpleProcessingRule simple => builder.AddRule((next, services) => context => simple.Run(context, () => next(context))),
IFullProcessingRule full => builder.AddRule((next, services) => context => full.Run(context, next)),
_ => throw new ArgumentException($"Type ({typeof(T).FullName}) does not ISimpleProcessingRule or IFullProcessingRule")
};
public static IProcessingPipelineBuilder AddDefault(this IProcessingPipelineBuilder builder) =>
public static IProcessingPipelineBuilder AddDefaultRules(this IProcessingPipelineBuilder builder) =>
builder
.Add<HandleEndTagRule>()
.Add<HandleDelayedAudioHeaderRule>()
.Add<UpdateTimestampOffsetRule>()
.Add<UpdateTimestampJumpRule>()
.Add<HandleNewScriptRule>()
.Add<HandleNewHeaderRule>()
.Add<RemoveDuplicatedChunkRule>()
.AddRule<HandleEndTagRule>()
.AddRule<HandleDelayedAudioHeaderRule>()
.AddRule<UpdateTimestampOffsetRule>()
.AddRule<UpdateTimestampJumpRule>()
.AddRule<HandleNewScriptRule>()
.AddRule<HandleNewHeaderRule>()
.AddRule<RemoveDuplicatedChunkRule>()
;
public static IProcessingPipelineBuilder AddRemoveFillerData(this IProcessingPipelineBuilder builder) =>
builder.Add<RemoveFillerDataRule>();
public static IProcessingPipelineBuilder AddRemoveFillerDataRule(this IProcessingPipelineBuilder builder) =>
builder.AddRule<RemoveFillerDataRule>();
}
}

View File

@ -1,27 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace BililiveRecorder.Flv.Pipeline
{
public class ProcessingPipelineBuilder : IProcessingPipelineBuilder
{
public IServiceProvider ServiceProvider { get; }
public IServiceCollection ServiceCollection { get; }
private readonly List<Func<ProcessingDelegate, ProcessingDelegate>> rules = new List<Func<ProcessingDelegate, ProcessingDelegate>>();
private readonly List<Func<ProcessingDelegate, IServiceProvider, ProcessingDelegate>> rules = new();
public ProcessingPipelineBuilder(IServiceProvider serviceProvider)
public ProcessingPipelineBuilder() : this(new ServiceCollection())
{ }
public ProcessingPipelineBuilder(IServiceCollection servicesCollection)
{
this.ServiceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
this.ServiceCollection = servicesCollection;
}
public IProcessingPipelineBuilder Add(Func<ProcessingDelegate, ProcessingDelegate> rule)
public IProcessingPipelineBuilder AddRule(Func<ProcessingDelegate, IServiceProvider, ProcessingDelegate> rule)
{
this.rules.Add(rule);
return this;
}
public ProcessingDelegate Build()
=> this.rules.AsEnumerable().Reverse().Aggregate((ProcessingDelegate)(_ => { }), (i, o) => o(i));
{
this.ServiceCollection.TryAddSingleton(_ => new ProcessingPipelineSettings());
var provider = this.ServiceCollection.BuildServiceProvider();
return this.rules.AsEnumerable().Reverse().Aggregate((ProcessingDelegate)(_ => { }), (i, o) => o(i, provider));
}
}
}

View File

@ -0,0 +1,13 @@
namespace BililiveRecorder.Flv.Pipeline
{
public class ProcessingPipelineSettings
{
public ProcessingPipelineSettings()
{ }
/// <summary>
/// 控制收到 onMetaData 时是否分段
/// </summary>
public bool SplitOnScriptTag { get; set; } = false;
}
}

View File

@ -14,6 +14,13 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
private const string onMetaData = "onMetaData";
private static readonly ProcessingComment comment_onmetadata = new ProcessingComment(CommentType.OnMetaData, false, "收到了 onMetaData");
private readonly bool splitOnScriptTag;
public HandleNewScriptRule(ProcessingPipelineSettings? processingPipelineSettings)
{
this.splitOnScriptTag = processingPipelineSettings?.SplitOnScriptTag ?? false;
}
public void Run(FlvProcessingContext context, Action next)
{
context.PerActionRun(this.RunPerAction);
@ -93,11 +100,20 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
}
else
{
// 记录信息,不对文件进行分段。
var message = $"收到直播服务器发送的 onMetaData 数据,请检查此位置是否有重复的直播片段或缺少数据。\n造成这个问题的原因可能是录播姬所连接的直播服务器与它的上级服务器的连接断开重连了。\n数据内容: {data?.ToJson() ?? "(null)"}";
context.AddComment(new ProcessingComment(CommentType.OnMetaData, false, message));
yield return new PipelineLogMessageWithLocationAction(message);
if (this.splitOnScriptTag)
{
// 对文件进行分段
yield return PipelineNewFileAction.Instance;
}
else
{
// 记录信息,不对文件进行分段。
yield return new PipelineLogMessageWithLocationAction(message);
}
yield return (new PipelineScriptAction(new Tag
{
Type = TagType.Script,
@ -107,6 +123,7 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
value
})
}));
yield break;
}
notOnMetaData:

View File

@ -95,7 +95,13 @@ namespace BililiveRecorder.ToolBox.Tool.Analyze
using var writer = new FlvProcessingContextWriter(tagWriter: tagWriter, allowMissingHeader: true, disableKeyframes: true, logger: logger);
var statsRule = new StatsRule();
var ffmpegDetectionRule = new FfmpegDetectionRule();
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).Add(statsRule).Add(ffmpegDetectionRule).AddDefault().AddRemoveFillerData().Build();
var pipeline = new ProcessingPipelineBuilder()
.ConfigureServices(services => services.AddSingleton(request.PipelineSettings ?? new ProcessingPipelineSettings()))
.AddRule(statsRule)
.AddRule(ffmpegDetectionRule)
.AddDefaultRules()
.AddRemoveFillerDataRule()
.Build();
// Run
await Task.Run(async () =>

View File

@ -1,7 +1,11 @@
namespace BililiveRecorder.ToolBox.Tool.Analyze
using BililiveRecorder.Flv.Pipeline;
namespace BililiveRecorder.ToolBox.Tool.Analyze
{
public class AnalyzeRequest : ICommandRequest<AnalyzeResponse>
{
public string Input { get; set; } = string.Empty;
public ProcessingPipelineSettings? PipelineSettings { get; set; }
}
}

View File

@ -114,7 +114,13 @@ namespace BililiveRecorder.ToolBox.Tool.Fix
using var writer = new FlvProcessingContextWriter(tagWriter: tagWriter, allowMissingHeader: true, disableKeyframes: false, logger: logger);
var statsRule = new StatsRule();
var ffmpegDetectionRule = new FfmpegDetectionRule();
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).Add(statsRule).Add(ffmpegDetectionRule).AddDefault().AddRemoveFillerData().Build();
var pipeline = new ProcessingPipelineBuilder()
.ConfigureServices(services => services.AddSingleton(request.PipelineSettings ?? new ProcessingPipelineSettings()))
.AddRule(statsRule)
.AddRule(ffmpegDetectionRule)
.AddDefaultRules()
.AddRemoveFillerDataRule()
.Build();
// Run
await Task.Run(async () =>

View File

@ -1,9 +1,13 @@
namespace BililiveRecorder.ToolBox.Tool.Fix
using BililiveRecorder.Flv.Pipeline;
namespace BililiveRecorder.ToolBox.Tool.Fix
{
public class FixRequest : ICommandRequest<FixResponse>
{
public string Input { get; set; } = string.Empty;
public string OutputBase { get; set; } = string.Empty;
public ProcessingPipelineSettings? PipelineSettings { get; set; }
}
}

View File

@ -1,7 +1,10 @@
using System;
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
using System.CommandLine.Parsing;
using System.Linq;
using System.Threading.Tasks;
using BililiveRecorder.Flv.Pipeline;
using BililiveRecorder.ToolBox.Tool.Analyze;
using BililiveRecorder.ToolBox.Tool.DanmakuMerger;
using BililiveRecorder.ToolBox.Tool.DanmakuStartTime;
@ -19,12 +22,14 @@ namespace BililiveRecorder.ToolBox
this.RegisterCommand<AnalyzeHandler, AnalyzeRequest, AnalyzeResponse>("analyze", null, c =>
{
c.Add(new Argument<string>("input", "example: input.flv"));
c.Add(new Option<ProcessingPipelineSettings?>(name: "pipeline-settings", parseArgument: this.ParseProcessingPipelineSettings));
});
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"));
c.Add(new Option<ProcessingPipelineSettings?>(name: "pipeline-settings", parseArgument: this.ParseProcessingPipelineSettings));
});
this.RegisterCommand<ExportHandler, ExportRequest, ExportResponse>("export", null, c =>
@ -46,6 +51,21 @@ namespace BililiveRecorder.ToolBox
});
}
private ProcessingPipelineSettings? ParseProcessingPipelineSettings(ArgumentResult result)
{
if (result.Tokens.Count == 0) return null;
try
{
return JsonConvert.DeserializeObject<ProcessingPipelineSettings>(result.Tokens.Single().Value);
}
catch (Exception)
{
result.ErrorMessage = "Pipeline settings must be a valid json string";
return null;
}
}
private void RegisterCommand<THandler, TRequest, TResponse>(string name, string? description, Action<Command> configure)
where THandler : ICommandHandler<TRequest, TResponse>
where TRequest : ICommandRequest<TResponse>

View File

@ -61,6 +61,15 @@
</local:SettingWithDefault>
</StackPanel>
</GroupBox>
<GroupBox Header="标准模式录制修复设置">
<StackPanel>
<local:SettingWithDefault IsSettingNotUsingDefault="{Binding HasFlvProcessorSplitOnScriptTag}">
<StackPanel>
<ui:ToggleSwitch IsOn="{Binding FlvProcessorSplitOnScriptTag}" OnContent="检测到可能缺少数据时分段" OffContent="检测到可能缺少数据时分段"/>
</StackPanel>
</local:SettingWithDefault>
</StackPanel>
</GroupBox>
<GroupBox Header="{l:Loc Settings_Splitting_Title}">
<StackPanel>
<local:SettingWithDefault IsSettingNotUsingDefault="{Binding HasCuttingMode}">

View File

@ -52,6 +52,11 @@
ConverterParameter={x:Static config:RecordMode.RawData}}" />
</StackPanel>
</GroupBox>
<GroupBox Header="标准模式录制修复设置">
<StackPanel>
<ui:ToggleSwitch IsOn="{Binding FlvProcessorSplitOnScriptTag}" OnContent="检测到可能缺少数据时分段" OffContent="检测到可能缺少数据时分段"/>
</StackPanel>
</GroupBox>
<GroupBox Header="{l:Loc Settings_Splitting_Title}">
<StackPanel>
<RadioButton GroupName="Splitting" Name="CutDisabledRadioButton" Content="{l:Loc Settings_Splitting_RadioButton_Disabled}"

View File

@ -52,6 +52,21 @@
<TextBlock Text="{l:Loc Toolbox_AutoFix_ButtonAnalyze}"/>
</StackPanel>
</Button>
<Button VerticalAlignment="Bottom" DockPanel.Dock="Right" Margin="0,0,5,0">
<StackPanel Orientation="Horizontal">
<ui:PathIcon Height="14" Margin="0,0,5,0" Style="{StaticResource PathIconDataCogOutline}"/>
<TextBlock Text="设置"/>
</StackPanel>
<ui:FlyoutService.Flyout>
<ui:Flyout Placement="Bottom">
<Grid DataContext="{x:Null}" x:Name="SettingsArea">
<StackPanel Margin="10" Orientation="Vertical">
<CheckBox Content="检测到可能缺少数据时分段" IsChecked="{Binding SplitOnScriptTag}"/>
</StackPanel>
</Grid>
</ui:Flyout>
</ui:FlyoutService.Flyout>
</Button>
<Button VerticalAlignment="Bottom" DockPanel.Dock="Right" Margin="0,0,5,0" Content="{l:Loc Toolbox_AutoFix_ButtonSelectInput}" Click="SelectFile_Button_Click"/>
<TextBox ui:ControlHelper.PlaceholderText="{l:Loc Toolbox_AutoFix_InputPathPlaceholder}" ui:TextBoxHelper.IsDeleteButtonVisible="False" x:Name="FileNameTextBox" />
</DockPanel>

View File

@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@ -23,9 +26,13 @@ namespace BililiveRecorder.WPF.Pages
private static readonly ILogger logger = Log.ForContext<ToolboxAutoFixPage>();
private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
private readonly AutoFixSettings settings = new AutoFixSettings();
public ToolboxAutoFixPage()
{
this.InitializeComponent();
this.SettingsArea.DataContext = this.settings;
}
private void SelectFile_Button_Click(object sender, RoutedEventArgs e)
@ -104,6 +111,10 @@ namespace BililiveRecorder.WPF.Pages
{
Input = inputPath,
OutputBase = output_path,
PipelineSettings = new Flv.Pipeline.ProcessingPipelineSettings
{
SplitOnScriptTag = this.settings.SplitOnScriptTag
}
};
var handler = new FixHandler();
@ -179,7 +190,11 @@ namespace BililiveRecorder.WPF.Pages
var req = new AnalyzeRequest
{
Input = inputPath
Input = inputPath,
PipelineSettings = new Flv.Pipeline.ProcessingPipelineSettings
{
SplitOnScriptTag = this.settings.SplitOnScriptTag
}
};
var handler = new AnalyzeHandler();
@ -331,5 +346,25 @@ namespace BililiveRecorder.WPF.Pages
catch (Exception)
{ }
}
public sealed class AutoFixSettings : INotifyPropertyChanged
{
private bool splitOnScriptTag;
public event PropertyChangedEventHandler? PropertyChanged;
private bool SetField<T>(ref T location, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(location, value))
return false;
location = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
/// <summary>
/// FLV修复-检测到可能缺少数据时分段
/// </summary>
public bool SplitOnScriptTag { get => this.splitOnScriptTag; set => this.SetField(ref this.splitOnScriptTag, value); }
}
}
}

View File

@ -22,6 +22,7 @@ namespace BililiveRecorder.Web.Models
public Optional<bool>? OptionalRecordDanmakuGift { get; set; }
public Optional<bool>? OptionalRecordDanmakuGuard { get; set; }
public Optional<string?>? OptionalRecordingQuality { get; set; }
public Optional<bool>? OptionalFlvProcessorSplitOnScriptTag { get; set; }
public void ApplyTo(RoomConfig config)
{
@ -35,6 +36,7 @@ namespace BililiveRecorder.Web.Models
if (this.OptionalRecordDanmakuGift.HasValue) config.OptionalRecordDanmakuGift = this.OptionalRecordDanmakuGift.Value;
if (this.OptionalRecordDanmakuGuard.HasValue) config.OptionalRecordDanmakuGuard = this.OptionalRecordDanmakuGuard.Value;
if (this.OptionalRecordingQuality.HasValue) config.OptionalRecordingQuality = this.OptionalRecordingQuality.Value;
if (this.OptionalFlvProcessorSplitOnScriptTag.HasValue) config.OptionalFlvProcessorSplitOnScriptTag = this.OptionalFlvProcessorSplitOnScriptTag.Value;
}
}
@ -50,6 +52,7 @@ namespace BililiveRecorder.Web.Models
public Optional<bool>? OptionalRecordDanmakuGuard { get; set; }
public Optional<string?>? OptionalRecordingQuality { get; set; }
public Optional<string?>? OptionalFileNameRecordTemplate { get; set; }
public Optional<bool>? OptionalFlvProcessorSplitOnScriptTag { get; set; }
public Optional<string?>? OptionalWebHookUrls { get; set; }
public Optional<string?>? OptionalWebHookUrlsV2 { get; set; }
public Optional<bool>? OptionalWpfShowTitleAndArea { get; set; }
@ -78,6 +81,7 @@ namespace BililiveRecorder.Web.Models
if (this.OptionalRecordDanmakuGuard.HasValue) config.OptionalRecordDanmakuGuard = this.OptionalRecordDanmakuGuard.Value;
if (this.OptionalRecordingQuality.HasValue) config.OptionalRecordingQuality = this.OptionalRecordingQuality.Value;
if (this.OptionalFileNameRecordTemplate.HasValue) config.OptionalFileNameRecordTemplate = this.OptionalFileNameRecordTemplate.Value;
if (this.OptionalFlvProcessorSplitOnScriptTag.HasValue) config.OptionalFlvProcessorSplitOnScriptTag = this.OptionalFlvProcessorSplitOnScriptTag.Value;
if (this.OptionalWebHookUrls.HasValue) config.OptionalWebHookUrls = this.OptionalWebHookUrls.Value;
if (this.OptionalWebHookUrlsV2.HasValue) config.OptionalWebHookUrlsV2 = this.OptionalWebHookUrlsV2.Value;
if (this.OptionalWpfShowTitleAndArea.HasValue) config.OptionalWpfShowTitleAndArea = this.OptionalWpfShowTitleAndArea.Value;
@ -112,6 +116,7 @@ namespace BililiveRecorder.Web.Models.Rest
public Optional<bool> OptionalRecordDanmakuGift { get; set; }
public Optional<bool> OptionalRecordDanmakuGuard { get; set; }
public Optional<string?> OptionalRecordingQuality { get; set; }
public Optional<bool> OptionalFlvProcessorSplitOnScriptTag { get; set; }
}
public class GlobalConfigDto
@ -126,6 +131,7 @@ namespace BililiveRecorder.Web.Models.Rest
public Optional<bool> OptionalRecordDanmakuGuard { get; set; }
public Optional<string?> OptionalRecordingQuality { get; set; }
public Optional<string?> OptionalFileNameRecordTemplate { get; set; }
public Optional<bool> OptionalFlvProcessorSplitOnScriptTag { get; set; }
public Optional<string?> OptionalWebHookUrls { get; set; }
public Optional<string?> OptionalWebHookUrlsV2 { get; set; }
public Optional<bool> OptionalWpfShowTitleAndArea { get; set; }
@ -162,6 +168,7 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.OptionalRecordDanmakuGift, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalRecordDanmakuGuard, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalRecordingQuality, type: typeof(HierarchicalOptionalType<string>));
this.Field(x => x.OptionalFlvProcessorSplitOnScriptTag, type: typeof(HierarchicalOptionalType<bool>));
}
}
@ -179,6 +186,7 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.OptionalRecordDanmakuGuard, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalRecordingQuality, type: typeof(HierarchicalOptionalType<string>));
this.Field(x => x.OptionalFileNameRecordTemplate, type: typeof(HierarchicalOptionalType<string>));
this.Field(x => x.OptionalFlvProcessorSplitOnScriptTag, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalWebHookUrls, type: typeof(HierarchicalOptionalType<string>));
this.Field(x => x.OptionalWebHookUrlsV2, type: typeof(HierarchicalOptionalType<string>));
this.Field(x => x.OptionalWpfShowTitleAndArea, type: typeof(HierarchicalOptionalType<bool>));
@ -211,6 +219,7 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.RecordDanmakuGuard);
this.Field(x => x.RecordingQuality);
this.Field(x => x.FileNameRecordTemplate);
this.Field(x => x.FlvProcessorSplitOnScriptTag);
this.Field(x => x.WebHookUrls);
this.Field(x => x.WebHookUrlsV2);
this.Field(x => x.WpfShowTitleAndArea);
@ -243,6 +252,7 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.OptionalRecordDanmakuGift, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
this.Field(x => x.OptionalRecordDanmakuGuard, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
this.Field(x => x.OptionalRecordingQuality, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
this.Field(x => x.OptionalFlvProcessorSplitOnScriptTag, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
}
}
@ -260,6 +270,7 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.OptionalRecordDanmakuGuard, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
this.Field(x => x.OptionalRecordingQuality, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
this.Field(x => x.OptionalFileNameRecordTemplate, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
this.Field(x => x.OptionalFlvProcessorSplitOnScriptTag, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
this.Field(x => x.OptionalWebHookUrls, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
this.Field(x => x.OptionalWebHookUrlsV2, nullable: true, type: typeof(HierarchicalOptionalInputType<string>));
this.Field(x => x.OptionalWpfShowTitleAndArea, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));

View File

@ -440,6 +440,22 @@
"default": "10000"
}
}
},
"FlvProcessorSplitOnScriptTag": {
"description": "FLV修复-检测到可能缺少数据时分段\n默认: false",
"markdownDescription": "FLV修复-检测到可能缺少数据时分段 \n默认: `false `\n\n",
"type": "object",
"additionalProperties": false,
"properties": {
"HasValue": {
"type": "boolean",
"default": true
},
"Value": {
"type": "boolean",
"default": false
}
}
}
}
},
@ -637,6 +653,22 @@
"default": "10000"
}
}
},
"FlvProcessorSplitOnScriptTag": {
"description": "FLV修复-检测到可能缺少数据时分段\n默认: false",
"markdownDescription": "FLV修复-检测到可能缺少数据时分段 \n默认: `false `\n\n",
"type": "object",
"additionalProperties": false,
"properties": {
"HasValue": {
"type": "boolean",
"default": true
},
"Value": {
"type": "boolean",
"default": false
}
}
}
}
}

View File

@ -86,6 +86,13 @@ export const data: Array<ConfigEntry> = [
configType: "globalOnly",
default: '{{ roomId }}-{{ name }}/录制-{{ roomId }}-{{ "now" | time_zone: "Asia/Shanghai" | format_date: "yyyyMMdd-HHmmss-fff" }}-{{ title }}.flv',
},
{
id: "FlvProcessorSplitOnScriptTag",
name: "FLV修复-检测到可能缺少数据时分段",
type: "bool",
configType: "room",
default: false
},
{
id: "WebHookUrls",
name: "WebhookV1",

View File

@ -68,6 +68,7 @@ namespace BililiveRecorder.Core.Config.V3
public BililiveRecorder.Core.Config.CuttingMode CuttingMode { get; }
public uint CuttingNumber { get; }
public string FileNameRecordTemplate { get; }
public bool FlvProcessorSplitOnScriptTag { get; }
public string LiveApiHost { get; }
public BililiveRecorder.Core.Config.AllowedAddressFamily NetworkTransportAllowedAddressFamily { get; }
public bool NetworkTransportUseSystemProxy { get; }
@ -98,10 +99,12 @@ namespace BililiveRecorder.Core.Config.V3
public BililiveRecorder.Core.Config.CuttingMode CuttingMode { get; set; }
public uint CuttingNumber { get; set; }
public string? FileNameRecordTemplate { get; set; }
public bool FlvProcessorSplitOnScriptTag { get; set; }
public bool HasCookie { get; set; }
public bool HasCuttingMode { get; set; }
public bool HasCuttingNumber { get; set; }
public bool HasFileNameRecordTemplate { get; set; }
public bool HasFlvProcessorSplitOnScriptTag { get; set; }
public bool HasLiveApiHost { get; set; }
public bool HasNetworkTransportAllowedAddressFamily { get; set; }
public bool HasNetworkTransportUseSystemProxy { get; set; }
@ -134,6 +137,8 @@ namespace BililiveRecorder.Core.Config.V3
public HierarchicalPropertyDefault.Optional<uint> OptionalCuttingNumber { get; set; }
[Newtonsoft.Json.JsonProperty("FileNameRecordTemplate")]
public HierarchicalPropertyDefault.Optional<string?> OptionalFileNameRecordTemplate { get; set; }
[Newtonsoft.Json.JsonProperty("FlvProcessorSplitOnScriptTag")]
public HierarchicalPropertyDefault.Optional<bool> OptionalFlvProcessorSplitOnScriptTag { get; set; }
[Newtonsoft.Json.JsonProperty("LiveApiHost")]
public HierarchicalPropertyDefault.Optional<string?> OptionalLiveApiHost { get; set; }
[Newtonsoft.Json.JsonProperty("NetworkTransportAllowedAddressFamily")]
@ -205,9 +210,11 @@ namespace BililiveRecorder.Core.Config.V3
public BililiveRecorder.Core.Config.CuttingMode CuttingMode { get; set; }
public uint CuttingNumber { get; set; }
public string? FileNameRecordTemplate { get; }
public bool FlvProcessorSplitOnScriptTag { get; set; }
public bool HasAutoRecord { get; set; }
public bool HasCuttingMode { get; set; }
public bool HasCuttingNumber { get; set; }
public bool HasFlvProcessorSplitOnScriptTag { get; set; }
public bool HasRecordDanmaku { get; set; }
public bool HasRecordDanmakuGift { get; set; }
public bool HasRecordDanmakuGuard { get; set; }
@ -225,6 +232,8 @@ namespace BililiveRecorder.Core.Config.V3
public HierarchicalPropertyDefault.Optional<BililiveRecorder.Core.Config.CuttingMode> OptionalCuttingMode { get; set; }
[Newtonsoft.Json.JsonProperty("CuttingNumber")]
public HierarchicalPropertyDefault.Optional<uint> OptionalCuttingNumber { get; set; }
[Newtonsoft.Json.JsonProperty("FlvProcessorSplitOnScriptTag")]
public HierarchicalPropertyDefault.Optional<bool> OptionalFlvProcessorSplitOnScriptTag { get; set; }
[Newtonsoft.Json.JsonProperty("RecordDanmaku")]
public HierarchicalPropertyDefault.Optional<bool> OptionalRecordDanmaku { get; set; }
[Newtonsoft.Json.JsonProperty("RecordDanmakuGift")]

View File

@ -725,18 +725,19 @@ namespace BililiveRecorder.Flv.Pipeline
}
public interface IProcessingPipelineBuilder
{
System.IServiceProvider ServiceProvider { get; }
BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder Add(System.Func<BililiveRecorder.Flv.Pipeline.ProcessingDelegate, BililiveRecorder.Flv.Pipeline.ProcessingDelegate> rule);
Microsoft.Extensions.DependencyInjection.IServiceCollection ServiceCollection { get; }
BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder AddRule(System.Func<BililiveRecorder.Flv.Pipeline.ProcessingDelegate, System.IServiceProvider, BililiveRecorder.Flv.Pipeline.ProcessingDelegate> rule);
BililiveRecorder.Flv.Pipeline.ProcessingDelegate Build();
}
public static class IProcessingPipelineBuilderExtensions
{
public static BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder Add<T>(this BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder builder)
public static BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder AddDefaultRules(this BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder builder) { }
public static BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder AddRemoveFillerDataRule(this BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder builder) { }
public static BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder AddRule<T>(this BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder builder)
where T : BililiveRecorder.Flv.Pipeline.IProcessingRule { }
public static BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder Add<T>(this BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder builder, T instance)
public static BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder AddRule<T>(this BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder builder, T instance)
where T : BililiveRecorder.Flv.Pipeline.IProcessingRule { }
public static BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder AddDefault(this BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder builder) { }
public static BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder AddRemoveFillerData(this BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder builder) { }
public static BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder ConfigureServices(this BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder builder, System.Action<Microsoft.Extensions.DependencyInjection.IServiceCollection> configure) { }
}
public interface IProcessingRule { }
public interface ISimpleProcessingRule : BililiveRecorder.Flv.Pipeline.IProcessingRule
@ -754,11 +755,17 @@ namespace BililiveRecorder.Flv.Pipeline
public delegate void ProcessingDelegate(BililiveRecorder.Flv.Pipeline.FlvProcessingContext context);
public class ProcessingPipelineBuilder : BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder
{
public ProcessingPipelineBuilder(System.IServiceProvider serviceProvider) { }
public System.IServiceProvider ServiceProvider { get; }
public BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder Add(System.Func<BililiveRecorder.Flv.Pipeline.ProcessingDelegate, BililiveRecorder.Flv.Pipeline.ProcessingDelegate> rule) { }
public ProcessingPipelineBuilder() { }
public ProcessingPipelineBuilder(Microsoft.Extensions.DependencyInjection.IServiceCollection servicesCollection) { }
public Microsoft.Extensions.DependencyInjection.IServiceCollection ServiceCollection { get; }
public BililiveRecorder.Flv.Pipeline.IProcessingPipelineBuilder AddRule(System.Func<BililiveRecorder.Flv.Pipeline.ProcessingDelegate, System.IServiceProvider, BililiveRecorder.Flv.Pipeline.ProcessingDelegate> rule) { }
public BililiveRecorder.Flv.Pipeline.ProcessingDelegate Build() { }
}
public class ProcessingPipelineSettings
{
public ProcessingPipelineSettings() { }
public bool SplitOnScriptTag { get; set; }
}
}
namespace BililiveRecorder.Flv.Pipeline.Rules
{
@ -786,7 +793,7 @@ namespace BililiveRecorder.Flv.Pipeline.Rules
}
public class HandleNewScriptRule : BililiveRecorder.Flv.Pipeline.IProcessingRule, BililiveRecorder.Flv.Pipeline.ISimpleProcessingRule
{
public HandleNewScriptRule() { }
public HandleNewScriptRule(BililiveRecorder.Flv.Pipeline.ProcessingPipelineSettings? processingPipelineSettings) { }
public void Run(BililiveRecorder.Flv.Pipeline.FlvProcessingContext context, System.Action next) { }
}
public class RemoveDuplicatedChunkRule : BililiveRecorder.Flv.Pipeline.IProcessingRule, BililiveRecorder.Flv.Pipeline.ISimpleProcessingRule

View File

@ -4,7 +4,6 @@ using BililiveRecorder.Flv.Amf;
using BililiveRecorder.Flv.Pipeline;
using BililiveRecorder.Flv.Pipeline.Actions;
using BililiveRecorder.Flv.Pipeline.Rules;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace BililiveRecorder.Flv.Tests.RuleTests
@ -16,7 +15,7 @@ namespace BililiveRecorder.Flv.Tests.RuleTests
public void ShouldDetectEndTag(bool expectEndTag, PipelineAction pipelineAction)
{
var rule = new FfmpegDetectionRule();
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).Add(rule).Build();
var pipeline = new ProcessingPipelineBuilder().AddRule(rule).Build();
var context = new FlvProcessingContext(pipelineAction, new Dictionary<object, object?>());
@ -46,7 +45,7 @@ namespace BililiveRecorder.Flv.Tests.RuleTests
public void ShouldDetectLvafEncoder(bool expectedValue, string metadataJson)
{
var rule = new FfmpegDetectionRule();
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).Add(rule).Build();
var pipeline = new ProcessingPipelineBuilder().AddRule(rule).Build();
var action = new PipelineScriptAction(new Tag
{

View File

@ -6,7 +6,6 @@ using BililiveRecorder.Flv.Pipeline;
using BililiveRecorder.Flv.Pipeline.Rules;
using BililiveRecorder.Flv.Writer;
using BililiveRecorder.Flv.Xml;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace BililiveRecorder.Flv.Tests.RuleTests
@ -18,7 +17,7 @@ namespace BililiveRecorder.Flv.Tests.RuleTests
var writer = new FlvProcessingContextWriter(tagWriter: output, allowMissingHeader: true, disableKeyframes: true, logger: null);
var session = new Dictionary<object, object?>();
var context = new FlvProcessingContext();
var pipeline = new ProcessingPipelineBuilder(new ServiceCollection().BuildServiceProvider()).Add<FfmpegDetectionRule>().AddDefault().AddRemoveFillerData().Build();
var pipeline = new ProcessingPipelineBuilder().AddRule<FfmpegDetectionRule>().AddDefaultRules().AddRemoveFillerDataRule().Build();
while (true)
{