From f05d8c3c0eadce9560f278d96e1753b7b14fd499 Mon Sep 17 00:00:00 2001 From: AquanJSW <62047911+AquanJSW@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:55:48 +0800 Subject: [PATCH] feat(cli/core): add TitleFilterPatterns option (#619) * add custom title filter * Use regex instead of string matching * feat(ui): add title filter settings for recording conditions --------- Co-authored-by: genteure --- .../Configure/ConfigInstructions.gen.cs | 6 ++- BililiveRecorder.Core/Config/V3/Config.gen.cs | 18 +++++++++ BililiveRecorder.Core/Room.cs | 39 ++++++++++++++++++- .../Controls/PerRoomSettingsDialog.xaml | 9 +++++ BililiveRecorder.WPF/Pages/SettingsPage.xaml | 7 ++++ BililiveRecorder.Web/Models/Config.gen.cs | 11 ++++++ configV3.schema.json | 32 +++++++++++++++ config_gen/data.ts | 7 ++++ 8 files changed, 127 insertions(+), 2 deletions(-) diff --git a/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs b/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs index e0caee2..f8d8df5 100644 --- a/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs +++ b/BililiveRecorder.Cli/Configure/ConfigInstructions.gen.cs @@ -28,6 +28,7 @@ namespace BililiveRecorder.Cli.Configure FileNameRecordTemplate, FlvProcessorSplitOnScriptTag, FlvWriteMetadata, + TitleFilterPatterns, WebHookUrls, WebHookUrlsV2, WpfShowTitleAndArea, @@ -65,7 +66,8 @@ namespace BililiveRecorder.Cli.Configure RecordDanmakuGuard, SaveStreamCover, RecordingQuality, - FlvProcessorSplitOnScriptTag + FlvProcessorSplitOnScriptTag, + TitleFilterPatterns } public static class ConfigInstructions { @@ -88,6 +90,7 @@ namespace BililiveRecorder.Cli.Configure GlobalConfig.Add(GlobalConfigProperties.FileNameRecordTemplate, new ConfigInstruction(config => config.HasFileNameRecordTemplate = false, (config, value) => config.FileNameRecordTemplate = value) { Name = "FileNameRecordTemplate", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.FlvProcessorSplitOnScriptTag, new ConfigInstruction(config => config.HasFlvProcessorSplitOnScriptTag = false, (config, value) => config.FlvProcessorSplitOnScriptTag = value) { Name = "FlvProcessorSplitOnScriptTag", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.FlvWriteMetadata, new ConfigInstruction(config => config.HasFlvWriteMetadata = false, (config, value) => config.FlvWriteMetadata = value) { Name = "FlvWriteMetadata", CanBeOptional = true }); + GlobalConfig.Add(GlobalConfigProperties.TitleFilterPatterns, new ConfigInstruction(config => config.HasTitleFilterPatterns = false, (config, value) => config.TitleFilterPatterns = value) { Name = "TitleFilterPatterns", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.WebHookUrls, new ConfigInstruction(config => config.HasWebHookUrls = false, (config, value) => config.WebHookUrls = value) { Name = "WebHookUrls", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.WebHookUrlsV2, new ConfigInstruction(config => config.HasWebHookUrlsV2 = false, (config, value) => config.WebHookUrlsV2 = value) { Name = "WebHookUrlsV2", CanBeOptional = true }); GlobalConfig.Add(GlobalConfigProperties.WpfShowTitleAndArea, new ConfigInstruction(config => config.HasWpfShowTitleAndArea = false, (config, value) => config.WpfShowTitleAndArea = value) { Name = "WpfShowTitleAndArea", CanBeOptional = true }); @@ -122,6 +125,7 @@ namespace BililiveRecorder.Cli.Configure RoomConfig.Add(RoomConfigProperties.SaveStreamCover, new ConfigInstruction(config => config.HasSaveStreamCover = false, (config, value) => config.SaveStreamCover = value) { Name = "SaveStreamCover", CanBeOptional = true }); RoomConfig.Add(RoomConfigProperties.RecordingQuality, new ConfigInstruction(config => config.HasRecordingQuality = false, (config, value) => config.RecordingQuality = value) { Name = "RecordingQuality", CanBeOptional = true }); RoomConfig.Add(RoomConfigProperties.FlvProcessorSplitOnScriptTag, new ConfigInstruction(config => config.HasFlvProcessorSplitOnScriptTag = false, (config, value) => config.FlvProcessorSplitOnScriptTag = value) { Name = "FlvProcessorSplitOnScriptTag", CanBeOptional = true }); + RoomConfig.Add(RoomConfigProperties.TitleFilterPatterns, new ConfigInstruction(config => config.HasTitleFilterPatterns = false, (config, value) => config.TitleFilterPatterns = value) { Name = "TitleFilterPatterns", CanBeOptional = true }); } } diff --git a/BililiveRecorder.Core/Config/V3/Config.gen.cs b/BililiveRecorder.Core/Config/V3/Config.gen.cs index 70716bf..3272834 100644 --- a/BililiveRecorder.Core/Config/V3/Config.gen.cs +++ b/BililiveRecorder.Core/Config/V3/Config.gen.cs @@ -125,6 +125,14 @@ namespace BililiveRecorder.Core.Config.V3 [JsonProperty(nameof(FlvProcessorSplitOnScriptTag)), EditorBrowsable(EditorBrowsableState.Never)] public Optional OptionalFlvProcessorSplitOnScriptTag { get => this.GetPropertyValueOptional(nameof(this.FlvProcessorSplitOnScriptTag)); set => this.SetPropertyValueOptional(value, nameof(this.FlvProcessorSplitOnScriptTag)); } + /// + /// 不录制的标题匹配正则 + /// + public string? TitleFilterPatterns { get => this.GetPropertyValue(); set => this.SetPropertyValue(value); } + public bool HasTitleFilterPatterns { get => this.GetPropertyHasValue(nameof(this.TitleFilterPatterns)); set => this.SetPropertyHasValue(value, nameof(this.TitleFilterPatterns)); } + [JsonProperty(nameof(TitleFilterPatterns)), EditorBrowsable(EditorBrowsableState.Never)] + public Optional OptionalTitleFilterPatterns { get => this.GetPropertyValueOptional(nameof(this.TitleFilterPatterns)); set => this.SetPropertyValueOptional(value, nameof(this.TitleFilterPatterns)); } + /// /// 录制文件名模板 /// @@ -347,6 +355,14 @@ namespace BililiveRecorder.Core.Config.V3 [JsonProperty(nameof(FlvWriteMetadata)), EditorBrowsable(EditorBrowsableState.Never)] public Optional OptionalFlvWriteMetadata { get => this.GetPropertyValueOptional(nameof(this.FlvWriteMetadata)); set => this.SetPropertyValueOptional(value, nameof(this.FlvWriteMetadata)); } + /// + /// 不录制的标题匹配正则 + /// + public string? TitleFilterPatterns { get => this.GetPropertyValue(); set => this.SetPropertyValue(value); } + public bool HasTitleFilterPatterns { get => this.GetPropertyHasValue(nameof(this.TitleFilterPatterns)); set => this.SetPropertyHasValue(value, nameof(this.TitleFilterPatterns)); } + [JsonProperty(nameof(TitleFilterPatterns)), EditorBrowsable(EditorBrowsableState.Never)] + public Optional OptionalTitleFilterPatterns { get => this.GetPropertyValueOptional(nameof(this.TitleFilterPatterns)); set => this.SetPropertyValueOptional(value, nameof(this.TitleFilterPatterns)); } + /// /// WebhookV1 /// @@ -534,6 +550,8 @@ namespace BililiveRecorder.Core.Config.V3 public bool FlvWriteMetadata => true; + public string TitleFilterPatterns => @""; + public string WebHookUrls => @""; public string WebHookUrlsV2 => @""; diff --git a/BililiveRecorder.Core/Room.cs b/BililiveRecorder.Core/Room.cs index b84684d..c24c73f 100644 --- a/BililiveRecorder.Core/Room.cs +++ b/BililiveRecorder.Core/Room.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.IO; using System.Net.Http; using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BililiveRecorder.Core.Api; @@ -228,6 +229,27 @@ namespace BililiveRecorder.Core } } + private static readonly TimeSpan TitleRegexMatchTimeout = TimeSpan.FromSeconds(0.5); + + /// + /// + private bool DoesTitleAllowRecord() + { + // 按新行分割的正则表达式 + var patterns = this.RoomConfig.TitleFilterPatterns?.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + + if (patterns is null || patterns.Length == 0) + return true; + + foreach (var pattern in patterns) + { + if (Regex.IsMatch(input: this.Title, pattern: pattern, options: RegexOptions.None, matchTimeout: TitleRegexMatchTimeout)) + return false; + } + + return true; + } + /// private void CreateAndStartNewRecordTask(bool skipFetchRoomInfo = false) { @@ -242,6 +264,19 @@ namespace BililiveRecorder.Core if (this.recordTask != null) return; + try + { + if (!this.DoesTitleAllowRecord()) + { + this.logger.Information("标题匹配到跳过录制设置中的规则,不录制"); + return; + } + } + catch (Exception ex) + { + this.logger.Warning(ex, "检查标题是否匹配跳过录制正则表达式时出错"); + } + var task = this.recordTaskFactory.CreateRecordTask(this); task.IOStats += this.RecordTask_IOStats; task.RecordingStats += this.RecordTask_RecordingStats; @@ -653,6 +688,7 @@ retry: } } + private void Room_PropertyChanged(object? sender, PropertyChangedEventArgs e) { switch (e.PropertyName) @@ -670,7 +706,8 @@ retry: } break; case nameof(this.Title): - if (this.RoomConfig.CuttingByTitle){ + if (this.RoomConfig.CuttingByTitle) + { this.SplitOutput(); } break; diff --git a/BililiveRecorder.WPF/Controls/PerRoomSettingsDialog.xaml b/BililiveRecorder.WPF/Controls/PerRoomSettingsDialog.xaml index 6c81752..ea0a3c7 100644 --- a/BililiveRecorder.WPF/Controls/PerRoomSettingsDialog.xaml +++ b/BililiveRecorder.WPF/Controls/PerRoomSettingsDialog.xaml @@ -113,6 +113,15 @@ + + + + + + + + diff --git a/BililiveRecorder.WPF/Pages/SettingsPage.xaml b/BililiveRecorder.WPF/Pages/SettingsPage.xaml index 523105c..5c801ec 100644 --- a/BililiveRecorder.WPF/Pages/SettingsPage.xaml +++ b/BililiveRecorder.WPF/Pages/SettingsPage.xaml @@ -131,6 +131,13 @@ + + + + + + diff --git a/BililiveRecorder.Web/Models/Config.gen.cs b/BililiveRecorder.Web/Models/Config.gen.cs index 83b9fff..bbbc248 100644 --- a/BililiveRecorder.Web/Models/Config.gen.cs +++ b/BililiveRecorder.Web/Models/Config.gen.cs @@ -25,6 +25,7 @@ namespace BililiveRecorder.Web.Models public Optional? OptionalSaveStreamCover { get; set; } public Optional? OptionalRecordingQuality { get; set; } public Optional? OptionalFlvProcessorSplitOnScriptTag { get; set; } + public Optional? OptionalTitleFilterPatterns { get; set; } public void ApplyTo(RoomConfig config) { @@ -41,6 +42,7 @@ namespace BililiveRecorder.Web.Models if (this.OptionalSaveStreamCover.HasValue) config.OptionalSaveStreamCover = this.OptionalSaveStreamCover.Value; if (this.OptionalRecordingQuality.HasValue) config.OptionalRecordingQuality = this.OptionalRecordingQuality.Value; if (this.OptionalFlvProcessorSplitOnScriptTag.HasValue) config.OptionalFlvProcessorSplitOnScriptTag = this.OptionalFlvProcessorSplitOnScriptTag.Value; + if (this.OptionalTitleFilterPatterns.HasValue) config.OptionalTitleFilterPatterns = this.OptionalTitleFilterPatterns.Value; } } @@ -60,6 +62,7 @@ namespace BililiveRecorder.Web.Models public Optional? OptionalFileNameRecordTemplate { get; set; } public Optional? OptionalFlvProcessorSplitOnScriptTag { get; set; } public Optional? OptionalFlvWriteMetadata { get; set; } + public Optional? OptionalTitleFilterPatterns { get; set; } public Optional? OptionalWebHookUrls { get; set; } public Optional? OptionalWebHookUrlsV2 { get; set; } public Optional? OptionalWpfShowTitleAndArea { get; set; } @@ -96,6 +99,7 @@ namespace BililiveRecorder.Web.Models if (this.OptionalFileNameRecordTemplate.HasValue) config.OptionalFileNameRecordTemplate = this.OptionalFileNameRecordTemplate.Value; if (this.OptionalFlvProcessorSplitOnScriptTag.HasValue) config.OptionalFlvProcessorSplitOnScriptTag = this.OptionalFlvProcessorSplitOnScriptTag.Value; if (this.OptionalFlvWriteMetadata.HasValue) config.OptionalFlvWriteMetadata = this.OptionalFlvWriteMetadata.Value; + if (this.OptionalTitleFilterPatterns.HasValue) config.OptionalTitleFilterPatterns = this.OptionalTitleFilterPatterns.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; @@ -137,6 +141,7 @@ namespace BililiveRecorder.Web.Models.Rest public Optional OptionalSaveStreamCover { get; set; } public Optional OptionalRecordingQuality { get; set; } public Optional OptionalFlvProcessorSplitOnScriptTag { get; set; } + public Optional OptionalTitleFilterPatterns { get; set; } } public class GlobalConfigDto @@ -155,6 +160,7 @@ namespace BililiveRecorder.Web.Models.Rest public Optional OptionalFileNameRecordTemplate { get; set; } public Optional OptionalFlvProcessorSplitOnScriptTag { get; set; } public Optional OptionalFlvWriteMetadata { get; set; } + public Optional OptionalTitleFilterPatterns { get; set; } public Optional OptionalWebHookUrls { get; set; } public Optional OptionalWebHookUrlsV2 { get; set; } public Optional OptionalWpfShowTitleAndArea { get; set; } @@ -198,6 +204,7 @@ namespace BililiveRecorder.Web.Models.Graphql this.Field(x => x.OptionalSaveStreamCover, type: typeof(HierarchicalOptionalType)); this.Field(x => x.OptionalRecordingQuality, type: typeof(HierarchicalOptionalType)); this.Field(x => x.OptionalFlvProcessorSplitOnScriptTag, type: typeof(HierarchicalOptionalType)); + this.Field(x => x.OptionalTitleFilterPatterns, type: typeof(HierarchicalOptionalType)); } } @@ -219,6 +226,7 @@ namespace BililiveRecorder.Web.Models.Graphql this.Field(x => x.OptionalFileNameRecordTemplate, type: typeof(HierarchicalOptionalType)); this.Field(x => x.OptionalFlvProcessorSplitOnScriptTag, type: typeof(HierarchicalOptionalType)); this.Field(x => x.OptionalFlvWriteMetadata, type: typeof(HierarchicalOptionalType)); + this.Field(x => x.OptionalTitleFilterPatterns, type: typeof(HierarchicalOptionalType)); this.Field(x => x.OptionalWebHookUrls, type: typeof(HierarchicalOptionalType)); this.Field(x => x.OptionalWebHookUrlsV2, type: typeof(HierarchicalOptionalType)); this.Field(x => x.OptionalWpfShowTitleAndArea, type: typeof(HierarchicalOptionalType)); @@ -259,6 +267,7 @@ namespace BililiveRecorder.Web.Models.Graphql this.Field(x => x.FileNameRecordTemplate); this.Field(x => x.FlvProcessorSplitOnScriptTag); this.Field(x => x.FlvWriteMetadata); + this.Field(x => x.TitleFilterPatterns); this.Field(x => x.WebHookUrls); this.Field(x => x.WebHookUrlsV2); this.Field(x => x.WpfShowTitleAndArea); @@ -298,6 +307,7 @@ namespace BililiveRecorder.Web.Models.Graphql this.Field(x => x.OptionalSaveStreamCover, nullable: true, type: typeof(HierarchicalOptionalInputType)); this.Field(x => x.OptionalRecordingQuality, nullable: true, type: typeof(HierarchicalOptionalInputType)); this.Field(x => x.OptionalFlvProcessorSplitOnScriptTag, nullable: true, type: typeof(HierarchicalOptionalInputType)); + this.Field(x => x.OptionalTitleFilterPatterns, nullable: true, type: typeof(HierarchicalOptionalInputType)); } } @@ -319,6 +329,7 @@ namespace BililiveRecorder.Web.Models.Graphql this.Field(x => x.OptionalFileNameRecordTemplate, nullable: true, type: typeof(HierarchicalOptionalInputType)); this.Field(x => x.OptionalFlvProcessorSplitOnScriptTag, nullable: true, type: typeof(HierarchicalOptionalInputType)); this.Field(x => x.OptionalFlvWriteMetadata, nullable: true, type: typeof(HierarchicalOptionalInputType)); + this.Field(x => x.OptionalTitleFilterPatterns, nullable: true, type: typeof(HierarchicalOptionalInputType)); this.Field(x => x.OptionalWebHookUrls, nullable: true, type: typeof(HierarchicalOptionalInputType)); this.Field(x => x.OptionalWebHookUrlsV2, nullable: true, type: typeof(HierarchicalOptionalInputType)); this.Field(x => x.OptionalWpfShowTitleAndArea, nullable: true, type: typeof(HierarchicalOptionalInputType)); diff --git a/configV3.schema.json b/configV3.schema.json index 25a0b62..5b25b08 100644 --- a/configV3.schema.json +++ b/configV3.schema.json @@ -577,6 +577,22 @@ "default": false } } + }, + "TitleFilterPatterns": { + "description": "不录制的标题匹配正则\n默认: ", + "markdownDescription": "不录制的标题匹配正则 \n默认: ` `\n\n", + "type": "object", + "additionalProperties": false, + "properties": { + "HasValue": { + "type": "boolean", + "default": true + }, + "Value": { + "type": "string", + "default": "" + } + } } } }, @@ -822,6 +838,22 @@ "default": false } } + }, + "TitleFilterPatterns": { + "description": "不录制的标题匹配正则\n默认: ", + "markdownDescription": "不录制的标题匹配正则 \n默认: ` `\n\n", + "type": "object", + "additionalProperties": false, + "properties": { + "HasValue": { + "type": "boolean", + "default": true + }, + "Value": { + "type": "string", + "default": "" + } + } } } } diff --git a/config_gen/data.ts b/config_gen/data.ts index cc4b267..eddf8b8 100644 --- a/config_gen/data.ts +++ b/config_gen/data.ts @@ -115,6 +115,13 @@ export const data: Array = [ advancedConfig: true, default: true }, + { + id: "TitleFilterPatterns", + name: "不录制的标题匹配正则", + type: "string?", + configType: "room", + default: "" + }, { id: "WebHookUrls", name: "WebhookV1",