feat: add option to save stream cover/thumbnail

close #298
This commit is contained in:
genteure 2022-08-27 18:07:09 +08:00
parent bcbd1597c6
commit d6c642de6e
8 changed files with 114 additions and 0 deletions

View File

@ -22,6 +22,7 @@ namespace BililiveRecorder.Cli.Configure
RecordDanmakuSuperChat,
RecordDanmakuGift,
RecordDanmakuGuard,
SaveStreamCover,
RecordingQuality,
FileNameRecordTemplate,
FlvProcessorSplitOnScriptTag,
@ -58,6 +59,7 @@ namespace BililiveRecorder.Cli.Configure
RecordDanmakuSuperChat,
RecordDanmakuGift,
RecordDanmakuGuard,
SaveStreamCover,
RecordingQuality,
FlvProcessorSplitOnScriptTag
}
@ -76,6 +78,7 @@ namespace BililiveRecorder.Cli.Configure
GlobalConfig.Add(GlobalConfigProperties.RecordDanmakuSuperChat, new ConfigInstruction<GlobalConfig, bool>(config => config.HasRecordDanmakuSuperChat = false, (config, value) => config.RecordDanmakuSuperChat = value) { Name = "RecordDanmakuSuperChat", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.RecordDanmakuGift, new ConfigInstruction<GlobalConfig, bool>(config => config.HasRecordDanmakuGift = false, (config, value) => config.RecordDanmakuGift = value) { Name = "RecordDanmakuGift", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.RecordDanmakuGuard, new ConfigInstruction<GlobalConfig, bool>(config => config.HasRecordDanmakuGuard = false, (config, value) => config.RecordDanmakuGuard = value) { Name = "RecordDanmakuGuard", CanBeOptional = true });
GlobalConfig.Add(GlobalConfigProperties.SaveStreamCover, new ConfigInstruction<GlobalConfig, bool>(config => config.HasSaveStreamCover = false, (config, value) => config.SaveStreamCover = value) { Name = "SaveStreamCover", 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 });
@ -108,6 +111,7 @@ namespace BililiveRecorder.Cli.Configure
RoomConfig.Add(RoomConfigProperties.RecordDanmakuSuperChat, new ConfigInstruction<RoomConfig, bool>(config => config.HasRecordDanmakuSuperChat = false, (config, value) => config.RecordDanmakuSuperChat = value) { Name = "RecordDanmakuSuperChat", CanBeOptional = true });
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.SaveStreamCover, new ConfigInstruction<RoomConfig, bool>(config => config.HasSaveStreamCover = false, (config, value) => config.SaveStreamCover = value) { Name = "SaveStreamCover", 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

@ -93,6 +93,14 @@ namespace BililiveRecorder.Core.Config.V3
[JsonProperty(nameof(RecordDanmakuGuard)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<bool> OptionalRecordDanmakuGuard { get => this.GetPropertyValueOptional<bool>(nameof(this.RecordDanmakuGuard)); set => this.SetPropertyValueOptional(value, nameof(this.RecordDanmakuGuard)); }
/// <summary>
/// 保存直播封面
/// </summary>
public bool SaveStreamCover { get => this.GetPropertyValue<bool>(); set => this.SetPropertyValue(value); }
public bool HasSaveStreamCover { get => this.GetPropertyHasValue(nameof(this.SaveStreamCover)); set => this.SetPropertyHasValue<bool>(value, nameof(this.SaveStreamCover)); }
[JsonProperty(nameof(SaveStreamCover)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<bool> OptionalSaveStreamCover { get => this.GetPropertyValueOptional<bool>(nameof(this.SaveStreamCover)); set => this.SetPropertyValueOptional(value, nameof(this.SaveStreamCover)); }
/// <summary>
/// 直播画质
/// </summary>
@ -273,6 +281,14 @@ namespace BililiveRecorder.Core.Config.V3
[JsonProperty(nameof(RecordDanmakuGuard)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<bool> OptionalRecordDanmakuGuard { get => this.GetPropertyValueOptional<bool>(nameof(this.RecordDanmakuGuard)); set => this.SetPropertyValueOptional(value, nameof(this.RecordDanmakuGuard)); }
/// <summary>
/// 保存直播封面
/// </summary>
public bool SaveStreamCover { get => this.GetPropertyValue<bool>(); set => this.SetPropertyValue(value); }
public bool HasSaveStreamCover { get => this.GetPropertyHasValue(nameof(this.SaveStreamCover)); set => this.SetPropertyHasValue<bool>(value, nameof(this.SaveStreamCover)); }
[JsonProperty(nameof(SaveStreamCover)), EditorBrowsable(EditorBrowsableState.Never)]
public Optional<bool> OptionalSaveStreamCover { get => this.GetPropertyValueOptional<bool>(nameof(this.SaveStreamCover)); set => this.SetPropertyValueOptional(value, nameof(this.SaveStreamCover)); }
/// <summary>
/// 直播画质
/// </summary>
@ -464,6 +480,8 @@ namespace BililiveRecorder.Core.Config.V3
public bool RecordDanmakuGuard => true;
public bool SaveStreamCover => false;
public string RecordingQuality => @"10000";
public string FileNameRecordTemplate => @"{{ roomId }}-{{ name }}/录制-{{ roomId }}-{{ ""now"" | time_zone: ""Asia/Shanghai"" | format_date: ""yyyyMMdd-HHmmss-fff"" }}-{{ title }}.flv";

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@ -420,6 +421,39 @@ namespace BililiveRecorder.Core
else
this.basicDanmakuWriter.Disable();
if (this.RoomConfig.SaveStreamCover)
{
// 保存直播间封面
_ = Task.Run(async () =>
{
try
{
var coverUrl = this.RawBilibiliApiJsonData?["room_info"]?["cover"]?.ToObject<string>();
if (string.IsNullOrWhiteSpace(coverUrl))
{
this.logger.Information("没有直播间封面信息");
return;
}
var targetPath = Path.ChangeExtension(e.FullPath, "cover" + Path.GetExtension(coverUrl));
using var http = new HttpClient();
http.DefaultRequestHeaders.UserAgent.Clear();
var stream = await http.GetStreamAsync(coverUrl).ConfigureAwait(false);
using var file = new FileStream(targetPath, FileMode.Create, FileAccess.Write, FileShare.None);
await stream.CopyToAsync(file).ConfigureAwait(false);
this.logger.Debug("直播间封面已成功从 {CoverUrl} 保存到 {FilePath}", coverUrl, targetPath);
}
catch (Exception ex)
{
this.logger.Warning(ex, "保存直播间封面时出错");
}
});
}
RecordFileOpening?.Invoke(this, e);
}

View File

@ -98,6 +98,11 @@
</local:SettingWithDefault>
</StackPanel>
</GroupBox>
<GroupBox Header="直播封面">
<local:SettingWithDefault IsSettingNotUsingDefault="{Binding HasSaveStreamCover}">
<ui:ToggleSwitch IsOn="{Binding SaveStreamCover}" OnContent="录制时同时保存直播封面" OffContent="录制时同时保存直播封面"/>
</local:SettingWithDefault>
</GroupBox>
<GroupBox Header="录制画质">
<StackPanel>
<TextBlock Text="逗号分割的录制画质ID"/>

View File

@ -80,6 +80,9 @@
</StackPanel>
</StackPanel>
</GroupBox>
<GroupBox Header="直播封面">
<ui:ToggleSwitch IsOn="{Binding SaveStreamCover}" OnContent="录制时同时保存直播封面" OffContent="录制时同时保存直播封面"/>
</GroupBox>
<GroupBox Header="{l:Loc Settings_FileName_Title}">
<StackPanel MaxWidth="500" HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">

View File

@ -21,6 +21,7 @@ namespace BililiveRecorder.Web.Models
public Optional<bool>? OptionalRecordDanmakuSuperChat { get; set; }
public Optional<bool>? OptionalRecordDanmakuGift { get; set; }
public Optional<bool>? OptionalRecordDanmakuGuard { get; set; }
public Optional<bool>? OptionalSaveStreamCover { get; set; }
public Optional<string?>? OptionalRecordingQuality { get; set; }
public Optional<bool>? OptionalFlvProcessorSplitOnScriptTag { get; set; }
@ -35,6 +36,7 @@ namespace BililiveRecorder.Web.Models
if (this.OptionalRecordDanmakuSuperChat.HasValue) config.OptionalRecordDanmakuSuperChat = this.OptionalRecordDanmakuSuperChat.Value;
if (this.OptionalRecordDanmakuGift.HasValue) config.OptionalRecordDanmakuGift = this.OptionalRecordDanmakuGift.Value;
if (this.OptionalRecordDanmakuGuard.HasValue) config.OptionalRecordDanmakuGuard = this.OptionalRecordDanmakuGuard.Value;
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;
}
@ -50,6 +52,7 @@ namespace BililiveRecorder.Web.Models
public Optional<bool>? OptionalRecordDanmakuSuperChat { get; set; }
public Optional<bool>? OptionalRecordDanmakuGift { get; set; }
public Optional<bool>? OptionalRecordDanmakuGuard { get; set; }
public Optional<bool>? OptionalSaveStreamCover { get; set; }
public Optional<string?>? OptionalRecordingQuality { get; set; }
public Optional<string?>? OptionalFileNameRecordTemplate { get; set; }
public Optional<bool>? OptionalFlvProcessorSplitOnScriptTag { get; set; }
@ -82,6 +85,7 @@ namespace BililiveRecorder.Web.Models
if (this.OptionalRecordDanmakuSuperChat.HasValue) config.OptionalRecordDanmakuSuperChat = this.OptionalRecordDanmakuSuperChat.Value;
if (this.OptionalRecordDanmakuGift.HasValue) config.OptionalRecordDanmakuGift = this.OptionalRecordDanmakuGift.Value;
if (this.OptionalRecordDanmakuGuard.HasValue) config.OptionalRecordDanmakuGuard = this.OptionalRecordDanmakuGuard.Value;
if (this.OptionalSaveStreamCover.HasValue) config.OptionalSaveStreamCover = this.OptionalSaveStreamCover.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;
@ -121,6 +125,7 @@ namespace BililiveRecorder.Web.Models.Rest
public Optional<bool> OptionalRecordDanmakuSuperChat { get; set; }
public Optional<bool> OptionalRecordDanmakuGift { get; set; }
public Optional<bool> OptionalRecordDanmakuGuard { get; set; }
public Optional<bool> OptionalSaveStreamCover { get; set; }
public Optional<string?> OptionalRecordingQuality { get; set; }
public Optional<bool> OptionalFlvProcessorSplitOnScriptTag { get; set; }
}
@ -135,6 +140,7 @@ namespace BililiveRecorder.Web.Models.Rest
public Optional<bool> OptionalRecordDanmakuSuperChat { get; set; }
public Optional<bool> OptionalRecordDanmakuGift { get; set; }
public Optional<bool> OptionalRecordDanmakuGuard { get; set; }
public Optional<bool> OptionalSaveStreamCover { get; set; }
public Optional<string?> OptionalRecordingQuality { get; set; }
public Optional<string?> OptionalFileNameRecordTemplate { get; set; }
public Optional<bool> OptionalFlvProcessorSplitOnScriptTag { get; set; }
@ -176,6 +182,7 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.OptionalRecordDanmakuSuperChat, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalRecordDanmakuGift, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalRecordDanmakuGuard, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalSaveStreamCover, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalRecordingQuality, type: typeof(HierarchicalOptionalType<string>));
this.Field(x => x.OptionalFlvProcessorSplitOnScriptTag, type: typeof(HierarchicalOptionalType<bool>));
}
@ -193,6 +200,7 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.OptionalRecordDanmakuSuperChat, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalRecordDanmakuGift, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalRecordDanmakuGuard, type: typeof(HierarchicalOptionalType<bool>));
this.Field(x => x.OptionalSaveStreamCover, 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>));
@ -229,6 +237,7 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.RecordDanmakuSuperChat);
this.Field(x => x.RecordDanmakuGift);
this.Field(x => x.RecordDanmakuGuard);
this.Field(x => x.SaveStreamCover);
this.Field(x => x.RecordingQuality);
this.Field(x => x.FileNameRecordTemplate);
this.Field(x => x.FlvProcessorSplitOnScriptTag);
@ -266,6 +275,7 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.OptionalRecordDanmakuSuperChat, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
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.OptionalSaveStreamCover, 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>));
}
@ -283,6 +293,7 @@ namespace BililiveRecorder.Web.Models.Graphql
this.Field(x => x.OptionalRecordDanmakuSuperChat, nullable: true, type: typeof(HierarchicalOptionalInputType<bool>));
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.OptionalSaveStreamCover, 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>));

View File

@ -482,6 +482,22 @@
}
}
},
"SaveStreamCover": {
"description": "保存直播封面\n默认: false",
"markdownDescription": "保存直播封面 \n默认: `false `\n\n",
"type": "object",
"additionalProperties": false,
"properties": {
"HasValue": {
"type": "boolean",
"default": true
},
"Value": {
"type": "boolean",
"default": false
}
}
},
"RecordingQuality": {
"description": "直播画质\n默认: 10000",
"markdownDescription": "直播画质 \n默认: `10000 `\n\n",
@ -695,6 +711,22 @@
}
}
},
"SaveStreamCover": {
"description": "保存直播封面\n默认: false",
"markdownDescription": "保存直播封面 \n默认: `false `\n\n",
"type": "object",
"additionalProperties": false,
"properties": {
"HasValue": {
"type": "boolean",
"default": true
},
"Value": {
"type": "boolean",
"default": false
}
}
},
"RecordingQuality": {
"description": "直播画质\n默认: 10000",
"markdownDescription": "直播画质 \n默认: `10000 `\n\n",

View File

@ -72,6 +72,13 @@ export const data: Array<ConfigEntry> = [
configType: "room",
default: true
},
{
id: "SaveStreamCover",
name: "保存直播封面",
type: "bool",
configType: "room",
default: false
},
{
id: "RecordingQuality",
name: "直播画质",