From 77de9cccecf8924fe7ff22a1d3f69b40334dfa82 Mon Sep 17 00:00:00 2001 From: nilaoda Date: Sun, 10 Nov 2024 19:24:58 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=83=A8=E5=88=86=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=A0=BC=E5=BC=8F,=20=E7=AE=80=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BBDown.Core/AppHelper.cs | 972 ++++++------ BBDown.Core/Config.cs | 47 +- BBDown.Core/DanmakuUtil.cs | 389 +++-- BBDown.Core/Entity/Entity.cs | 389 +++-- BBDown.Core/Entity/ParsedResult.cs | 34 +- BBDown.Core/Entity/VInfo.cs | 91 +- BBDown.Core/Fetcher/BangumiInfoFetcher.cs | 123 +- BBDown.Core/Fetcher/CheeseInfoFetcher.cs | 95 +- BBDown.Core/Fetcher/FavListFetcher.cs | 167 +- BBDown.Core/Fetcher/IntlBangumiInfoFetcher.cs | 187 ++- BBDown.Core/Fetcher/MediaListFetcher.cs | 101 +- BBDown.Core/Fetcher/NormalInfoFetcher.cs | 209 ++- BBDown.Core/Fetcher/SeriesListFetcher.cs | 103 +- BBDown.Core/Fetcher/SpaceVideoFetcher.cs | 117 +- BBDown.Core/FetcherFactory.cs | 76 +- BBDown.Core/IFetcher.cs | 11 +- BBDown.Core/Logger.cs | 95 +- BBDown.Core/Parser.cs | 711 +++++---- BBDown.Core/Util/BilibiliBvConverter.cs | 118 +- BBDown.Core/Util/HTTPUtil.cs | 172 +- BBDown.Core/Util/SubUtil.cs | 823 +++++----- BBDown/BBDownApiServer.cs | 14 +- BBDown/BBDownAria2c.cs | 49 +- BBDown/BBDownConfigParser.cs | 72 +- BBDown/BBDownDownloadUtil.cs | 354 +++-- BBDown/BBDownLoginUtil.cs | 201 ++- BBDown/BBDownMuxer.cs | 339 ++-- BBDown/BBDownUtil.cs | 940 ++++++----- BBDown/CommandLineInvoker.cs | 414 +++-- BBDown/ConsoleQRCode.cs | 41 +- BBDown/MyOption.cs | 127 +- BBDown/Program.Methods.cs | 805 +++++----- BBDown/Program.cs | 1391 ++++++++--------- BBDown/ProgressBar.cs | 245 ++- README.md | 2 +- 35 files changed, 4972 insertions(+), 5052 deletions(-) diff --git a/BBDown.Core/AppHelper.cs b/BBDown.Core/AppHelper.cs index 9b84704f4..69b544b75 100644 --- a/BBDown.Core/AppHelper.cs +++ b/BBDown.Core/AppHelper.cs @@ -2,583 +2,581 @@ using Google.Protobuf; using System.Buffers.Binary; using System.IO.Compression; -using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using static BBDown.Core.Util.HTTPUtil; using static BBDown.Core.Logger; -namespace BBDown.Core +namespace BBDown.Core; + +static class AppHelper { - class AppHelper + private static readonly string API = "https://grpc.biliapi.net/bilibili.app.playurl.v1.PlayURL/PlayView"; + private static readonly string API2 = "https://app.bilibili.com/bilibili.pgc.gateway.player.v2.PlayURL/PlayView"; + private static readonly string dalvikVer = "2.1.0"; + private static readonly string osVer = "11"; + private static readonly string brand = "M2012K11AC"; + private static readonly string model = "Build/RKQ1.200826.002"; + private static readonly string appVer = "7.32.0"; + private static readonly int build = 7320200; // 新版才能抓到配音 + private static readonly string channel = "xiaomi_cn_tv.danmaku.bili_zm20200902"; + private static readonly Network.Types.TYPE networkType = Network.Types.TYPE.Wifi; + private static readonly string networkOid = "46007"; + private static readonly string cronet = "1.36.1"; + private static readonly string buvid = ""; + private static readonly string mobiApp = "android"; + private static readonly string appKey = "android64"; + private static readonly string sessionId = "dedf8669"; + private static readonly string platform = "android"; + private static readonly string env = "prod"; + private static readonly int appId = 1; + private static readonly string region = "CN"; + private static readonly string language = "zh"; + + private static PlayViewReq.Types.CodeType GetVideoCodeType(string code) { - private static readonly string API = "https://grpc.biliapi.net/bilibili.app.playurl.v1.PlayURL/PlayView"; - private static readonly string API2 = "https://app.bilibili.com/bilibili.pgc.gateway.player.v2.PlayURL/PlayView"; - private static readonly string dalvikVer = "2.1.0"; - private static readonly string osVer = "11"; - private static readonly string brand = "M2012K11AC"; - private static readonly string model = "Build/RKQ1.200826.002"; - private static readonly string appVer = "7.32.0"; - private static readonly int build = 7320200; // 新版才能抓到配音 - private static readonly string channel = "xiaomi_cn_tv.danmaku.bili_zm20200902"; - private static readonly Network.Types.TYPE networkType = Network.Types.TYPE.Wifi; - private static readonly string networkOid = "46007"; - private static readonly string cronet = "1.36.1"; - private static readonly string buvid = ""; - private static readonly string mobiApp = "android"; - private static readonly string appKey = "android64"; - private static readonly string sessionId = "dedf8669"; - private static readonly string platform = "android"; - private static readonly string env = "prod"; - private static readonly int appId = 1; - private static readonly string region = "CN"; - private static readonly string language = "zh"; - - private static PlayViewReq.Types.CodeType GetVideoCodeType(string code) - { - return code switch - { - "AVC" => PlayViewReq.Types.CodeType.Code264, - "HEVC" => PlayViewReq.Types.CodeType.Code265, - "AV1" => PlayViewReq.Types.CodeType.Codeav1, - _ => PlayViewReq.Types.CodeType.Code265 - }; - } - - /// - /// 发起请求并返回响应报文(protobuf -> json) - /// - /// - /// - /// - /// - /// - public static async Task DoReqAsync(string aid, string cid, string epId, string qn, bool bangumi, string encoding, string appkey = "") + return code switch { + "AVC" => PlayViewReq.Types.CodeType.Code264, + "HEVC" => PlayViewReq.Types.CodeType.Code265, + "AV1" => PlayViewReq.Types.CodeType.Codeav1, + _ => PlayViewReq.Types.CodeType.Code265 + }; + } - var headers = GetHeader(appkey); - LogDebug("App-Req-Headers: {0}", JsonSerializer.Serialize(headers, JsonContext.Default.DictionaryStringString)); - byte[] data; - // 只有pgc接口才有配音和片头尾信息 - if (bangumi) - { - if (!(string.IsNullOrEmpty(encoding) || encoding == "HEVC")) - LogWarn("APP的番剧不支持 HEVC 以外的编码"); - var body = GetPayload(Convert.ToInt64(epId), Convert.ToInt64(cid), Convert.ToInt64(qn), PlayViewReq.Types.CodeType.Code265); - data = await GetPostResponseAsync(API2, body, headers); - } - else - { - var body = GetPayload(Convert.ToInt64(aid), Convert.ToInt64(cid), Convert.ToInt64(qn), GetVideoCodeType(encoding)); - data = await GetPostResponseAsync(API, body, headers); - } - var resp = new MessageParser(() => new PlayViewReply()).ParseFrom(ReadMessage(data)); + /// + /// 发起请求并返回响应报文(protobuf -> json) + /// + /// + /// + /// + /// + /// + public static async Task DoReqAsync(string aid, string cid, string epId, string qn, bool bangumi, string encoding, string appkey = "") + { - LogDebug("PlayViewReplyPlain: {0}", JsonSerializer.Serialize(resp, JsonContext.Default.PlayViewReply)); - return ConvertToDashJson(resp); + var headers = GetHeader(appkey); + LogDebug("App-Req-Headers: {0}", JsonSerializer.Serialize(headers, JsonContext.Default.DictionaryStringString)); + byte[] data; + // 只有pgc接口才有配音和片头尾信息 + if (bangumi) + { + if (!(string.IsNullOrEmpty(encoding) || encoding == "HEVC")) + LogWarn("APP的番剧不支持 HEVC 以外的编码"); + var body = GetPayload(Convert.ToInt64(epId), Convert.ToInt64(cid), Convert.ToInt64(qn), PlayViewReq.Types.CodeType.Code265); + data = await GetPostResponseAsync(API2, body, headers); } - - /// - /// 将protobuf转换成网页那种json 这样就不用修改之前的解析逻辑了 - /// - /// - /// - private static string ConvertToDashJson(object data) + else { - var resp = (PlayViewReply)data; - var videos = new List(); - var audios = new List(); - var clips = new List(); - - if (resp.VideoInfo.StreamList != null) - { - foreach (var item in resp.VideoInfo.StreamList) - { - if (item.DashVideo != null) - { - videos.Add(new AudioInfoWitCodecId( - item.StreamInfo.Quality, - item.DashVideo.BaseUrl, - item.DashVideo.BackupUrl.ToList(), - (uint)(item.DashVideo.Size * 8 / (resp.VideoInfo.Timelength / 1000)), - item.DashVideo.Codecid - )); - } - } - } - - if (resp.VideoInfo.DashAudio != null) - { - audios.AddRange(resp.VideoInfo.DashAudio.Select(item => new AudioInfoWithCodecName( - item.Id, - item.BaseUrl, - item.BackupUrl.ToList(), - item.Bandwidth, - "M4A" - ))); - } - - if (resp.VideoInfo.Flac != null && resp.VideoInfo.Flac.Audio != null) - { - audios.Add(new AudioInfoWithCodecName( - resp.VideoInfo.Flac.Audio.Id, - resp.VideoInfo.Flac.Audio.BaseUrl, - resp.VideoInfo.Flac.Audio.BackupUrl.ToList(), - resp.VideoInfo.Flac.Audio.Bandwidth, - "FLAC" - )); - } + var body = GetPayload(Convert.ToInt64(aid), Convert.ToInt64(cid), Convert.ToInt64(qn), GetVideoCodeType(encoding)); + data = await GetPostResponseAsync(API, body, headers); + } + var resp = new MessageParser(() => new PlayViewReply()).ParseFrom(ReadMessage(data)); - if (resp.VideoInfo.Dolby != null && resp.VideoInfo.Dolby.Audio != null) - { - audios.Add(new AudioInfoWithCodecName( - resp.VideoInfo.Dolby.Audio.Id, - resp.VideoInfo.Dolby.Audio.BaseUrl, - resp.VideoInfo.Dolby.Audio.BackupUrl.ToList(), - resp.VideoInfo.Dolby.Audio.Bandwidth, - "E-AC-3" - )); - } + LogDebug("PlayViewReplyPlain: {0}", JsonSerializer.Serialize(resp, JsonContext.Default.PlayViewReply)); + return ConvertToDashJson(resp); + } - if (resp.Business != null && resp.Business.ClipInfo != null) - { - clips.AddRange(resp.Business.ClipInfo.Select(clip => new DashClip( - clip.Start, - clip.End, - clip.ToastText - ))); - } + /// + /// 将protobuf转换成网页那种json 这样就不用修改之前的解析逻辑了 + /// + /// + /// + private static string ConvertToDashJson(object data) + { + var resp = (PlayViewReply)data; + var videos = new List(); + var audios = new List(); + var clips = new List(); - var backgroundAudios = new List(); - var roles = new List(); - if (resp.PlayExtInfo != null && resp.PlayExtInfo.PlayDubbingInfo != null && resp.PlayExtInfo.PlayDubbingInfo.BackgroundAudio != null) + if (resp.VideoInfo.StreamList != null) + { + foreach (var item in resp.VideoInfo.StreamList) { - var dubInfo = resp.PlayExtInfo.PlayDubbingInfo; - - backgroundAudios.AddRange(dubInfo.BackgroundAudio.Audio.Select(item => new AudioInfoWithCodecName( - item.Id, - item.BaseUrl, - item.BackupUrl.ToList(), - item.Bandwidth, - "M4A" - ))); - - foreach (var item in dubInfo.RoleAudioList) + if (item.DashVideo != null) { - foreach (var role in item.AudioMaterialList) - { - List roleAudios = role.Audio.Select(item => new AudioInfoWithCodecName( - item.Id, - item.BaseUrl, - item.BackupUrl.ToList(), - item.Bandwidth, - "M4A" - )).Cast().ToList(); - - roles.Add(new AudioMaterial( - role.AudioId, - role.Title ?? role.AudioId, - role.PersonName ?? role.Edition ?? "", - roleAudios - )); - } + videos.Add(new AudioInfoWitCodecId( + item.StreamInfo.Quality, + item.DashVideo.BaseUrl, + item.DashVideo.BackupUrl.ToList(), + (uint)(item.DashVideo.Size * 8 / (resp.VideoInfo.Timelength / 1000)), + item.DashVideo.Codecid + )); } } - - var json = new DashJson( - 0, - "0", - 1, - new DashData( - resp.VideoInfo.Timelength, - new DashInfo( - videos, - audios - ), - clips - ), - new DubbingInfo( - backgroundAudios, - roles - ) - ); - - return JsonSerializer.Serialize(json, JsonContext.Default.DashJson); } - private static byte[] GetPayload(long aid, long cid, long qn, PlayViewReq.Types.CodeType codec) + if (resp.VideoInfo.DashAudio != null) { - var obj = new PlayViewReq - { - EpId = aid, - Cid = cid, - //obj.Qn = qn; - Qn = 127, - Fnval = 4048, - Fourk = true, - Spmid = "main.ugc-video-detail.0.0", - FromSpmid = "main.my-history.0.0", - PreferCodecType = codec, - Download = 0, //0:播放 1:flv下载 2:dash下载 - ForceHost = 2 //0:允许使用ip 1:使用http 2:使用https - }; - LogDebug("PayLoadPlain: {0}", JsonSerializer.Serialize(obj, JsonContext.Default.PlayViewReq)); - return PackMessage(obj.ToByteArray()); + audios.AddRange(resp.VideoInfo.DashAudio.Select(item => new AudioInfoWithCodecName( + item.Id, + item.BaseUrl, + item.BackupUrl.ToList(), + item.Bandwidth, + "M4A" + ))); } - - #region 生成Headers相关方法 - - private static Dictionary GetHeader(string appkey) + if (resp.VideoInfo.Flac != null && resp.VideoInfo.Flac.Audio != null) { - return new Dictionary() - { - ["Host"] = "grpc.biliapi.net", - ["user-agent"] = $"Dalvik/{dalvikVer} (Linux; U; Android {osVer}; {brand} {model}) {appVer} os/android model/{brand} mobi_app/android build/{build} channel/{channel} innerVer/{build} osVer/{osVer} network/2 grpc-java-cronet/{cronet}", - ["te"] = "trailers", - ["x-bili-fawkes-req-bin"] = GenerateFawkesReqBin(), - ["x-bili-metadata-bin"] = GenerateMetadataBin(appkey), - ["authorization"] = $"identify_v1 {Config.TOKEN}", - ["x-bili-device-bin"] = GenerateDeviceBin(), - ["x-bili-network-bin"] = GenerateNetworkBin(), - ["x-bili-restriction-bin"] = "", - ["x-bili-locale-bin"] = GenerateLocaleBin(), - ["x-bili-exps-bin"] = "", - ["grpc-encoding"] = "gzip", - ["grpc-accept-encoding"] = "identity,gzip", - ["grpc-timeout"] = "17996161u", - }; + audios.Add(new AudioInfoWithCodecName( + resp.VideoInfo.Flac.Audio.Id, + resp.VideoInfo.Flac.Audio.BaseUrl, + resp.VideoInfo.Flac.Audio.BackupUrl.ToList(), + resp.VideoInfo.Flac.Audio.Bandwidth, + "FLAC" + )); } - private static string GenerateLocaleBin() + if (resp.VideoInfo.Dolby != null && resp.VideoInfo.Dolby.Audio != null) { - var obj = new Locale - { - CLocale = new Locale.Types.LocaleIds - { - Language = language, - Region = region - } - }; - return Convert.ToBase64String(obj.ToByteArray()); + audios.Add(new AudioInfoWithCodecName( + resp.VideoInfo.Dolby.Audio.Id, + resp.VideoInfo.Dolby.Audio.BaseUrl, + resp.VideoInfo.Dolby.Audio.BackupUrl.ToList(), + resp.VideoInfo.Dolby.Audio.Bandwidth, + "E-AC-3" + )); } - private static string GenerateNetworkBin() + if (resp.Business != null && resp.Business.ClipInfo != null) { - var obj = new Network - { - Type = networkType, - Oid = networkOid - }; - return Convert.ToBase64String(obj.ToByteArray()); + clips.AddRange(resp.Business.ClipInfo.Select(clip => new DashClip( + clip.Start, + clip.End, + clip.ToastText + ))); } - private static string GenerateDeviceBin() + var backgroundAudios = new List(); + var roles = new List(); + if (resp.PlayExtInfo != null && resp.PlayExtInfo.PlayDubbingInfo != null && resp.PlayExtInfo.PlayDubbingInfo.BackgroundAudio != null) { - var obj = new Device - { - AppId = appId, - Build = build, - Buvid = buvid, - MobiApp = mobiApp, - Platform = platform, - Channel = channel, - Brand = brand, - Model = model, - Osver = osVer - }; - return Convert.ToBase64String(obj.ToByteArray()); - } + var dubInfo = resp.PlayExtInfo.PlayDubbingInfo; - private static string GenerateMetadataBin(string appkey) - { - var obj = new Metadata - { - AccessKey = appkey, - MobiApp = mobiApp, - Build = build, - Channel = channel, - Buvid = buvid, - Platform = platform - }; - return Convert.ToBase64String(obj.ToByteArray()); - } + backgroundAudios.AddRange(dubInfo.BackgroundAudio.Audio.Select(item => new AudioInfoWithCodecName( + item.Id, + item.BaseUrl, + item.BackupUrl.ToList(), + item.Bandwidth, + "M4A" + ))); - private static string GenerateFawkesReqBin() - { - var obj = new FawkesReq + foreach (var item in dubInfo.RoleAudioList) { - Appkey = appKey, - Env = env, - SessionId = sessionId - }; - return Convert.ToBase64String(obj.ToByteArray()); + foreach (var role in item.AudioMaterialList) + { + List roleAudios = role.Audio.Select(item => new AudioInfoWithCodecName( + item.Id, + item.BaseUrl, + item.BackupUrl.ToList(), + item.Bandwidth, + "M4A" + )).Cast().ToList(); + + roles.Add(new AudioMaterial( + role.AudioId, + role.Title ?? role.AudioId, + role.PersonName ?? role.Edition ?? "", + roleAudios + )); + } + } } - #endregion + var json = new DashJson( + 0, + "0", + 1, + new DashData( + resp.VideoInfo.Timelength, + new DashInfo( + videos, + audios + ), + clips + ), + new DubbingInfo( + backgroundAudios, + roles + ) + ); + + return JsonSerializer.Serialize(json, JsonContext.Default.DashJson); + } - /// - /// 读取gRPC响应流 通过前5字节信息 解析/解压后面的报文体 - /// - /// - /// 字节流 - public static byte[] ReadMessage(byte[] data) + private static byte[] GetPayload(long aid, long cid, long qn, PlayViewReq.Types.CodeType codec) + { + var obj = new PlayViewReq { - byte first; - int size; - (first, size) = ReadInfo(data); - return first == 1 ? GzipDecompress(data[5..]) : data[5..(5 + size)]; - } + EpId = aid, + Cid = cid, + //obj.Qn = qn; + Qn = 127, + Fnval = 4048, + Fourk = true, + Spmid = "main.ugc-video-detail.0.0", + FromSpmid = "main.my-history.0.0", + PreferCodecType = codec, + Download = 0, //0:播放 1:flv下载 2:dash下载 + ForceHost = 2 //0:允许使用ip 1:使用http 2:使用https + }; + LogDebug("PayLoadPlain: {0}", JsonSerializer.Serialize(obj, JsonContext.Default.PlayViewReq)); + return PackMessage(obj.ToByteArray()); + } - /// - /// 读取报文长度 - /// - /// - /// - private static (byte first, int size) ReadInfo(byte[] data) - { - var value1 = data[0]; - var value2 = data[1..5]; - return (value1, BinaryPrimitives.ReadInt32BigEndian(value2)); - } + #region 生成Headers相关方法 - /// - /// 给请求载荷添加头部信息 - /// - /// - /// - public static byte[] PackMessage(byte[] input) + private static Dictionary GetHeader(string appkey) + { + return new Dictionary() { - using var stream = new MemoryStream(); - using (var writer = new BinaryWriter(stream)) - { - var comp = GzipCompress(input); - var reverse = (stackalloc byte[4]); - writer.Write((byte)1); - BinaryPrimitives.WriteInt32BigEndian(reverse, comp.Length); - writer.Write(reverse); - writer.Write(comp); - } - return stream.ToArray(); - } + ["Host"] = "grpc.biliapi.net", + ["user-agent"] = $"Dalvik/{dalvikVer} (Linux; U; Android {osVer}; {brand} {model}) {appVer} os/android model/{brand} mobi_app/android build/{build} channel/{channel} innerVer/{build} osVer/{osVer} network/2 grpc-java-cronet/{cronet}", + ["te"] = "trailers", + ["x-bili-fawkes-req-bin"] = GenerateFawkesReqBin(), + ["x-bili-metadata-bin"] = GenerateMetadataBin(appkey), + ["authorization"] = $"identify_v1 {Config.TOKEN}", + ["x-bili-device-bin"] = GenerateDeviceBin(), + ["x-bili-network-bin"] = GenerateNetworkBin(), + ["x-bili-restriction-bin"] = "", + ["x-bili-locale-bin"] = GenerateLocaleBin(), + ["x-bili-exps-bin"] = "", + ["grpc-encoding"] = "gzip", + ["grpc-accept-encoding"] = "identity,gzip", + ["grpc-timeout"] = "17996161u", + }; + } - /// - /// gzip压缩 - /// - /// - /// - private static byte[] GzipCompress(byte[] data) + private static string GenerateLocaleBin() + { + var obj = new Locale { - using var output = new MemoryStream(); - using (var comp = new GZipStream(output, CompressionMode.Compress)) + CLocale = new Locale.Types.LocaleIds { - comp.Write(data, 0, data.Length); + Language = language, + Region = region } - return output.ToArray(); - } + }; + return Convert.ToBase64String(obj.ToByteArray()); + } - /// - /// gzip解压 - /// - /// - /// - private static byte[] GzipDecompress(byte[] data) + private static string GenerateNetworkBin() + { + var obj = new Network { - using var output = new MemoryStream(); - using (var input = new MemoryStream(data)) - { - using var decomp = new GZipStream(input, CompressionMode.Decompress); - decomp.CopyTo(output); - } - return output.ToArray(); - } + Type = networkType, + Oid = networkOid + }; + return Convert.ToBase64String(obj.ToByteArray()); } + private static string GenerateDeviceBin() + { + var obj = new Device + { + AppId = appId, + Build = build, + Buvid = buvid, + MobiApp = mobiApp, + Platform = platform, + Channel = channel, + Brand = brand, + Model = model, + Osver = osVer + }; + return Convert.ToBase64String(obj.ToByteArray()); + } - [JsonSerializable(typeof(AudioMaterial))] - [JsonSerializable(typeof(DubbingInfo))] - [JsonSerializable(typeof(DashClip))] - [JsonSerializable(typeof(AudioInfoWithCodecName))] - [JsonSerializable(typeof(AudioInfoWitCodecId))] - [JsonSerializable(typeof(DashJson))] - [JsonSerializable(typeof(PlayViewReq))] - [JsonSerializable(typeof(PlayViewReply))] - [JsonSerializable(typeof(Dictionary))] - internal partial class JsonContext : JsonSerializerContext { } - - internal class AudioMaterial + private static string GenerateMetadataBin(string appkey) { - [JsonPropertyName("audio_id")] - public string AudioId { get; } - [JsonPropertyName("title")] - public string Title { get; } - [JsonPropertyName("person_name")] - public string PersonName { get; } - [JsonPropertyName("audio")] - public List Audio { get; } - - public AudioMaterial(string audio_id, string title, string person_name, List audio) + var obj = new Metadata { - AudioId = audio_id; - Title = title; - PersonName = person_name; - Audio = audio; - } + AccessKey = appkey, + MobiApp = mobiApp, + Build = build, + Channel = channel, + Buvid = buvid, + Platform = platform + }; + return Convert.ToBase64String(obj.ToByteArray()); + } - public override bool Equals(object? obj) => obj is AudioMaterial other && AudioId == other.AudioId && Title == other.Title && PersonName == other.PersonName && Audio == other.Audio; - public override int GetHashCode() => HashCode.Combine(Title, Audio); + private static string GenerateFawkesReqBin() + { + var obj = new FawkesReq + { + Appkey = appKey, + Env = env, + SessionId = sessionId + }; + return Convert.ToBase64String(obj.ToByteArray()); } - internal class DubbingInfo + #endregion + + /// + /// 读取gRPC响应流 通过前5字节信息 解析/解压后面的报文体 + /// + /// + /// 字节流 + public static byte[] ReadMessage(byte[] data) { - [JsonPropertyName("background_audio")] - public List BackgroundAudio { get; } - [JsonPropertyName("role_audio_list")] - public List RoleAudioList { get; } + byte first; + int size; + (first, size) = ReadInfo(data); + return first == 1 ? GzipDecompress(data[5..]) : data[5..(5 + size)]; + } - public DubbingInfo(List background_audio, List role_audio_list) - { - BackgroundAudio = background_audio; - RoleAudioList = role_audio_list; - } + /// + /// 读取报文长度 + /// + /// + /// + private static (byte first, int size) ReadInfo(byte[] data) + { + var value1 = data[0]; + var value2 = data[1..5]; - public override bool Equals(object? obj) => obj is DubbingInfo other && BackgroundAudio == other.BackgroundAudio && RoleAudioList == other.RoleAudioList; - public override int GetHashCode() => HashCode.Combine(BackgroundAudio, RoleAudioList); + return (value1, BinaryPrimitives.ReadInt32BigEndian(value2)); } - internal class DashClip + /// + /// 给请求载荷添加头部信息 + /// + /// + /// + public static byte[] PackMessage(byte[] input) { - [JsonPropertyName("start")] - public int Start { get; } - [JsonPropertyName("end")] - public int End { get; } - [JsonPropertyName("toastText")] - public string ToastText { get; } - - public DashClip(int start, int end, string toastText) + using var stream = new MemoryStream(); + using (var writer = new BinaryWriter(stream)) { - Start = start; - End = end; - ToastText = toastText; + var comp = GzipCompress(input); + var reverse = (stackalloc byte[4]); + writer.Write((byte)1); + BinaryPrimitives.WriteInt32BigEndian(reverse, comp.Length); + writer.Write(reverse); + writer.Write(comp); } - - public override bool Equals(object? obj) => obj is DashClip other && Start == other.Start && End == other.End && ToastText == other.ToastText; - public override int GetHashCode() => HashCode.Combine(Start, End, ToastText); + return stream.ToArray(); } - internal class AudioInfoWithCodecName + /// + /// gzip压缩 + /// + /// + /// + private static byte[] GzipCompress(byte[] data) { - [JsonPropertyName("id")] - public uint Id { get; } - [JsonPropertyName("base_url")] - public string BaseUrl { get; } - [JsonPropertyName("backup_url")] - public List BackupUrl { get; } - [JsonPropertyName("bandwidth")] - public uint Bandwidth { get; } - [JsonPropertyName("codecs")] - public string Codecs { get; } - - public AudioInfoWithCodecName(uint id, string base_url, List backup_url, uint bandwidth, string codecs) + using var output = new MemoryStream(); + using (var comp = new GZipStream(output, CompressionMode.Compress)) { - Id = id; - BaseUrl = base_url; - BackupUrl = backup_url; - Bandwidth = bandwidth; - Codecs = codecs; + comp.Write(data, 0, data.Length); } - - public override bool Equals(object? obj) => obj is AudioInfoWithCodecName other && Id == other.Id && BaseUrl == other.BaseUrl && BackupUrl.SequenceEqual(other.BackupUrl) && Bandwidth == other.Bandwidth && Codecs == other.Codecs; - public override int GetHashCode() => HashCode.Combine(Id, BaseUrl, BackupUrl, Bandwidth, Codecs); + return output.ToArray(); } - internal class AudioInfoWitCodecId + /// + /// gzip解压 + /// + /// + /// + private static byte[] GzipDecompress(byte[] data) { - [JsonPropertyName("id")] - public uint Id { get; } - [JsonPropertyName("base_url")] - public string BaseUrl { get; } - [JsonPropertyName("backup_url")] - public List BackupUrl { get; } - [JsonPropertyName("bandwidth")] - public uint Bandwidth { get; } - [JsonPropertyName("codecid")] - public uint Codecid { get; } - - public AudioInfoWitCodecId(uint id, string base_url, List backup_url, uint bandwidth, uint codecid) + using var output = new MemoryStream(); + using (var input = new MemoryStream(data)) { - Id = id; - BaseUrl = base_url; - BackupUrl = backup_url; - Bandwidth = bandwidth; - Codecid = codecid; + using var decomp = new GZipStream(input, CompressionMode.Decompress); + decomp.CopyTo(output); } + return output.ToArray(); + } +} + - public override bool Equals(object? obj) => obj is AudioInfoWitCodecId other && Id == other.Id && BaseUrl == other.BaseUrl && Bandwidth == other.Bandwidth && Codecid == other.Codecid; - public override int GetHashCode() => HashCode.Combine(Id, BaseUrl, Bandwidth, Codecid); +[JsonSerializable(typeof(AudioMaterial))] +[JsonSerializable(typeof(DubbingInfo))] +[JsonSerializable(typeof(DashClip))] +[JsonSerializable(typeof(AudioInfoWithCodecName))] +[JsonSerializable(typeof(AudioInfoWitCodecId))] +[JsonSerializable(typeof(DashJson))] +[JsonSerializable(typeof(PlayViewReq))] +[JsonSerializable(typeof(PlayViewReply))] +[JsonSerializable(typeof(Dictionary))] +internal partial class JsonContext : JsonSerializerContext { } + +internal class AudioMaterial +{ + [JsonPropertyName("audio_id")] + public string AudioId { get; } + [JsonPropertyName("title")] + public string Title { get; } + [JsonPropertyName("person_name")] + public string PersonName { get; } + [JsonPropertyName("audio")] + public List Audio { get; } + + public AudioMaterial(string audio_id, string title, string person_name, List audio) + { + AudioId = audio_id; + Title = title; + PersonName = person_name; + Audio = audio; } - internal class DashInfo + public override bool Equals(object? obj) => obj is AudioMaterial other && AudioId == other.AudioId && Title == other.Title && PersonName == other.PersonName && Audio == other.Audio; + public override int GetHashCode() => HashCode.Combine(Title, Audio); +} + +internal class DubbingInfo +{ + [JsonPropertyName("background_audio")] + public List BackgroundAudio { get; } + [JsonPropertyName("role_audio_list")] + public List RoleAudioList { get; } + + public DubbingInfo(List background_audio, List role_audio_list) { - [JsonPropertyName("video")] - public List Video { get; } - [JsonPropertyName("audio")] - public List Audio { get; } + BackgroundAudio = background_audio; + RoleAudioList = role_audio_list; + } - public DashInfo(List video, List audio) - { - Video = video; - Audio = audio; - } + public override bool Equals(object? obj) => obj is DubbingInfo other && BackgroundAudio == other.BackgroundAudio && RoleAudioList == other.RoleAudioList; + public override int GetHashCode() => HashCode.Combine(BackgroundAudio, RoleAudioList); +} - public override bool Equals(object? obj) => obj is DashInfo other && EqualityComparer>.Default.Equals(Video, other.Video) && EqualityComparer>.Default.Equals(Audio, other.Audio); - public override int GetHashCode() => HashCode.Combine(Video, Audio); +internal class DashClip +{ + [JsonPropertyName("start")] + public int Start { get; } + [JsonPropertyName("end")] + public int End { get; } + [JsonPropertyName("toastText")] + public string ToastText { get; } + + public DashClip(int start, int end, string toastText) + { + Start = start; + End = end; + ToastText = toastText; } - internal class DashData + public override bool Equals(object? obj) => obj is DashClip other && Start == other.Start && End == other.End && ToastText == other.ToastText; + public override int GetHashCode() => HashCode.Combine(Start, End, ToastText); +} + +internal class AudioInfoWithCodecName +{ + [JsonPropertyName("id")] + public uint Id { get; } + [JsonPropertyName("base_url")] + public string BaseUrl { get; } + [JsonPropertyName("backup_url")] + public List BackupUrl { get; } + [JsonPropertyName("bandwidth")] + public uint Bandwidth { get; } + [JsonPropertyName("codecs")] + public string Codecs { get; } + + public AudioInfoWithCodecName(uint id, string base_url, List backup_url, uint bandwidth, string codecs) { - [JsonPropertyName("timelength")] - public ulong TimeLength { get; } - [JsonPropertyName("dash")] - public DashInfo Dash { get; } - [JsonPropertyName("clip_info_list")] - public List ClipList { get; } - - public DashData(ulong timelength, DashInfo dash, List clipList) - { - TimeLength = timelength; - Dash = dash; - ClipList = clipList; - } + Id = id; + BaseUrl = base_url; + BackupUrl = backup_url; + Bandwidth = bandwidth; + Codecs = codecs; + } + + public override bool Equals(object? obj) => obj is AudioInfoWithCodecName other && Id == other.Id && BaseUrl == other.BaseUrl && BackupUrl.SequenceEqual(other.BackupUrl) && Bandwidth == other.Bandwidth && Codecs == other.Codecs; + public override int GetHashCode() => HashCode.Combine(Id, BaseUrl, BackupUrl, Bandwidth, Codecs); +} - public override bool Equals(object? obj) => obj is DashData other && TimeLength == other.TimeLength && EqualityComparer.Default.Equals(Dash, other.Dash) && EqualityComparer>.Default.Equals(ClipList, other.ClipList); - public override int GetHashCode() => HashCode.Combine(TimeLength, Dash, ClipList); +internal class AudioInfoWitCodecId +{ + [JsonPropertyName("id")] + public uint Id { get; } + [JsonPropertyName("base_url")] + public string BaseUrl { get; } + [JsonPropertyName("backup_url")] + public List BackupUrl { get; } + [JsonPropertyName("bandwidth")] + public uint Bandwidth { get; } + [JsonPropertyName("codecid")] + public uint Codecid { get; } + + public AudioInfoWitCodecId(uint id, string base_url, List backup_url, uint bandwidth, uint codecid) + { + Id = id; + BaseUrl = base_url; + BackupUrl = backup_url; + Bandwidth = bandwidth; + Codecid = codecid; } - internal class DashJson + public override bool Equals(object? obj) => obj is AudioInfoWitCodecId other && Id == other.Id && BaseUrl == other.BaseUrl && Bandwidth == other.Bandwidth && Codecid == other.Codecid; + public override int GetHashCode() => HashCode.Combine(Id, BaseUrl, Bandwidth, Codecid); +} + +internal class DashInfo +{ + [JsonPropertyName("video")] + public List Video { get; } + [JsonPropertyName("audio")] + public List Audio { get; } + + public DashInfo(List video, List audio) { - [JsonPropertyName("code")] - public int Code { get; } - [JsonPropertyName("message")] - public string Message { get; } - [JsonPropertyName("ttl")] - public int Ttl { get; } - [JsonPropertyName("data")] - public DashData Data { get; } - [JsonPropertyName("dubbing_info")] - public DubbingInfo DubbingInfo { get; } - - public DashJson(int code, string message, int ttl, DashData data, DubbingInfo dubbingInfo) - { - Code = code; - Message = message; - Ttl = ttl; - Data = data; - DubbingInfo = dubbingInfo; - } + Video = video; + Audio = audio; + } + + public override bool Equals(object? obj) => obj is DashInfo other && EqualityComparer>.Default.Equals(Video, other.Video) && EqualityComparer>.Default.Equals(Audio, other.Audio); + public override int GetHashCode() => HashCode.Combine(Video, Audio); +} - public override bool Equals(object? obj) => obj is DashJson other && Code == other.Code && Message == other.Message && Ttl == other.Ttl && EqualityComparer.Default.Equals(Data, other.Data); - public override int GetHashCode() => HashCode.Combine(Code, Message, Ttl, Data, DubbingInfo); +internal class DashData +{ + [JsonPropertyName("timelength")] + public ulong TimeLength { get; } + [JsonPropertyName("dash")] + public DashInfo Dash { get; } + [JsonPropertyName("clip_info_list")] + public List ClipList { get; } + + public DashData(ulong timelength, DashInfo dash, List clipList) + { + TimeLength = timelength; + Dash = dash; + ClipList = clipList; } + + public override bool Equals(object? obj) => obj is DashData other && TimeLength == other.TimeLength && EqualityComparer.Default.Equals(Dash, other.Dash) && EqualityComparer>.Default.Equals(ClipList, other.ClipList); + public override int GetHashCode() => HashCode.Combine(TimeLength, Dash, ClipList); } + +internal class DashJson +{ + [JsonPropertyName("code")] + public int Code { get; } + [JsonPropertyName("message")] + public string Message { get; } + [JsonPropertyName("ttl")] + public int Ttl { get; } + [JsonPropertyName("data")] + public DashData Data { get; } + [JsonPropertyName("dubbing_info")] + public DubbingInfo DubbingInfo { get; } + + public DashJson(int code, string message, int ttl, DashData data, DubbingInfo dubbingInfo) + { + Code = code; + Message = message; + Ttl = ttl; + Data = data; + DubbingInfo = dubbingInfo; + } + + public override bool Equals(object? obj) => obj is DashJson other && Code == other.Code && Message == other.Message && Ttl == other.Ttl && EqualityComparer.Default.Equals(Data, other.Data); + public override int GetHashCode() => HashCode.Combine(Code, Message, Ttl, Data, DubbingInfo); +} \ No newline at end of file diff --git a/BBDown.Core/Config.cs b/BBDown.Core/Config.cs index 8d8cc553d..5b73680b9 100644 --- a/BBDown.Core/Config.cs +++ b/BBDown.Core/Config.cs @@ -1,27 +1,26 @@ -namespace BBDown.Core +namespace BBDown.Core; + +public static class Config { - public class Config - { - //For WEB - public static string COOKIE { get; set; } = ""; - //For APP/TV - public static string TOKEN { get; set; } = ""; - //日志级别 - public static bool DEBUG_LOG { get; set; } = false; - //BiliPlus Host - public static string HOST { get; set; } = "api.bilibili.com"; - //BiliPlus EP Host - public static string EPHOST { get; set; } = "api.bilibili.com"; - //BiliPlus Area - public static string AREA { get; set; } = ""; + //For WEB + public static string COOKIE { get; set; } = ""; + //For APP/TV + public static string TOKEN { get; set; } = ""; + //日志级别 + public static bool DEBUG_LOG { get; set; } = false; + //BiliPlus Host + public static string HOST { get; set; } = "api.bilibili.com"; + //BiliPlus EP Host + public static string EPHOST { get; set; } = "api.bilibili.com"; + //BiliPlus Area + public static string AREA { get; set; } = ""; - public static string WBI { get; set; } = ""; + public static string WBI { get; set; } = ""; - public static readonly Dictionary qualitys = new() { - {"127","8K 超高清" }, {"126","杜比视界" }, {"125","HDR 真彩" }, {"120","4K 超清" }, {"116","1080P 高帧率" }, - {"112","1080P 高码率" }, {"100","智能修复" }, {"80","1080P 高清" }, {"74","720P 高帧率" }, - {"64","720P 高清" }, {"48","720P 高清" }, {"32","480P 清晰" }, {"16","360P 流畅" }, - {"5","144P 流畅" }, {"6","240P 流畅" } - }; - } -} + public static readonly Dictionary qualitys = new() { + {"127","8K 超高清" }, {"126","杜比视界" }, {"125","HDR 真彩" }, {"120","4K 超清" }, {"116","1080P 高帧率" }, + {"112","1080P 高码率" }, {"100","智能修复" }, {"80","1080P 高清" }, {"74","720P 高帧率" }, + {"64","720P 高清" }, {"48","720P 高清" }, {"32","480P 清晰" }, {"16","360P 流畅" }, + {"5","144P 流畅" }, {"6","240P 流畅" } + }; +} \ No newline at end of file diff --git a/BBDown.Core/DanmakuUtil.cs b/BBDown.Core/DanmakuUtil.cs index 2f5f41200..3bea5754d 100644 --- a/BBDown.Core/DanmakuUtil.cs +++ b/BBDown.Core/DanmakuUtil.cs @@ -2,241 +2,240 @@ using System.Text; using System.Xml; -namespace BBDown.Core +namespace BBDown.Core; + +public static class DanmakuUtil { - public class DanmakuUtil + private const int MONITOR_WIDTH = 1920; //渲染字幕时的渲染范围的高度 + private const int MONITOR_HEIGHT = 1080; //渲染字幕时的渲染范围的高度 + private const int FONT_SIZE = 40; //字体大小 + private const double MOVE_SPEND_TIME = 8.00; //单条条滚动弹幕存在时间(控制速度) + private const double TOP_SPEND_TIME = 4.00; //单条顶部或底部弹幕存在时间 + private const int PROTECT_LENGTH = 50; //滚动弹幕屏占百分比 + public static readonly DanmakuComparer comparer = new(); + + /*public static async Task DownloadAsync(Page p, string xmlPath, bool aria2c, string aria2cProxy) { - private const int MONITOR_WIDTH = 1920; //渲染字幕时的渲染范围的高度 - private const int MONITOR_HEIGHT = 1080; //渲染字幕时的渲染范围的高度 - private const int FONT_SIZE = 40; //字体大小 - private const double MOVE_SPEND_TIME = 8.00; //单条条滚动弹幕存在时间(控制速度) - private const double TOP_SPEND_TIME = 4.00; //单条顶部或底部弹幕存在时间 - private const int PROTECT_LENGTH = 50; //滚动弹幕屏占百分比 - public static readonly DanmakuComparer comparer = new(); + string danmakuUrl = "https://comment.bilibili.com/" + p.cid + ".xml"; + await DownloadFile(danmakuUrl, xmlPath, aria2c, aria2cProxy); + }*/ - /*public static async Task DownloadAsync(Page p, string xmlPath, bool aria2c, string aria2cProxy) + public static DanmakuItem[]? ParseXml(string xmlPath) + { + // 解析xml文件 + XmlDocument xmlFile = new(); + XmlReaderSettings settings = new() { - string danmakuUrl = "https://comment.bilibili.com/" + p.cid + ".xml"; - await DownloadFile(danmakuUrl, xmlPath, aria2c, aria2cProxy); - }*/ - - public static DanmakuItem[]? ParseXml(string xmlPath) + IgnoreComments = true//忽略文档里面的注释 + }; + var danmakus = new List(); + using (var reader = XmlReader.Create(xmlPath, settings)) { - // 解析xml文件 - XmlDocument xmlFile = new(); - XmlReaderSettings settings = new() + try { - IgnoreComments = true//忽略文档里面的注释 - }; - var danmakus = new List(); - using (var reader = XmlReader.Create(xmlPath, settings)) - { - try - { - xmlFile.Load(reader); - } - catch (Exception ex) - { - LogDebug("解析字幕xml时出现异常: {0}", ex.ToString()); - return null; - } + xmlFile.Load(reader); + } + catch (Exception ex) + { + LogDebug("解析字幕xml时出现异常: {0}", ex.ToString()); + return null; } + } - XmlNode? rootNode = xmlFile.SelectSingleNode("i"); - if (rootNode != null) + XmlNode? rootNode = xmlFile.SelectSingleNode("i"); + if (rootNode != null) + { + XmlElement rootElement = (XmlElement)rootNode; + XmlNodeList? dNodeList = rootElement.SelectNodes("d"); + if (dNodeList != null) { - XmlElement rootElement = (XmlElement)rootNode; - XmlNodeList? dNodeList = rootElement.SelectNodes("d"); - if (dNodeList != null) + foreach (XmlNode node in dNodeList) { - foreach (XmlNode node in dNodeList) + XmlElement dElement = (XmlElement)node; + string attr = dElement.GetAttribute("p").ToString(); + if (attr != null) { - XmlElement dElement = (XmlElement)node; - string attr = dElement.GetAttribute("p").ToString(); - if (attr != null) + string[] vs = attr.Split(','); + if (vs.Length >= 8) { - string[] vs = attr.Split(','); - if (vs.Length >= 8) - { - DanmakuItem danmaku = new(vs, dElement.InnerText); - danmakus.Add(danmaku); - } + DanmakuItem danmaku = new(vs, dElement.InnerText); + danmakus.Add(danmaku); } } } } - return danmakus.ToArray(); } + return danmakus.ToArray(); + } - /// - /// 保存为ASS字幕文件 - /// - /// 弹幕 - /// 保存路径 - /// - public static async Task SaveAsAssAsync(DanmakuItem[] danmakus, string outputPath) - { - var sb = new StringBuilder(); - // ASS字幕文件头 - sb.AppendLine("[Script Info]"); - sb.AppendLine("Script Updated By: BBDown(https://github.com/nilaoda/BBDown)"); - sb.AppendLine("ScriptType: v4.00+"); - sb.AppendLine($"PlayResX: {MONITOR_WIDTH}"); - sb.AppendLine($"PlayResY: {MONITOR_HEIGHT}"); - sb.AppendLine($"Aspect Ratio: {MONITOR_WIDTH}:{MONITOR_HEIGHT}"); - sb.AppendLine("Collisions: Normal"); - sb.AppendLine("WrapStyle: 2"); - sb.AppendLine("ScaledBorderAndShadow: yes"); - sb.AppendLine("YCbCr Matrix: TV.601"); - sb.AppendLine("[V4+ Styles]"); - sb.AppendLine("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"); - sb.AppendLine($"Style: BBDOWN_Style, 黑体, {FONT_SIZE}, &H00FFFFFF, &H00FFFFFF, &H00000000, &H00000000, 0, 0, 0, 0, 100, 100, 0.00, 0.00, 1, 2, 0, 7, 0, 0, 0, 0"); - sb.AppendLine("[Events]"); - sb.AppendLine("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"); + /// + /// 保存为ASS字幕文件 + /// + /// 弹幕 + /// 保存路径 + /// + public static async Task SaveAsAssAsync(DanmakuItem[] danmakus, string outputPath) + { + var sb = new StringBuilder(); + // ASS字幕文件头 + sb.AppendLine("[Script Info]"); + sb.AppendLine("Script Updated By: BBDown(https://github.com/nilaoda/BBDown)"); + sb.AppendLine("ScriptType: v4.00+"); + sb.AppendLine($"PlayResX: {MONITOR_WIDTH}"); + sb.AppendLine($"PlayResY: {MONITOR_HEIGHT}"); + sb.AppendLine($"Aspect Ratio: {MONITOR_WIDTH}:{MONITOR_HEIGHT}"); + sb.AppendLine("Collisions: Normal"); + sb.AppendLine("WrapStyle: 2"); + sb.AppendLine("ScaledBorderAndShadow: yes"); + sb.AppendLine("YCbCr Matrix: TV.601"); + sb.AppendLine("[V4+ Styles]"); + sb.AppendLine("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"); + sb.AppendLine($"Style: BBDOWN_Style, 黑体, {FONT_SIZE}, &H00FFFFFF, &H00FFFFFF, &H00000000, &H00000000, 0, 0, 0, 0, 100, 100, 0.00, 0.00, 1, 2, 0, 7, 0, 0, 0, 0"); + sb.AppendLine("[Events]"); + sb.AppendLine("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"); - PositionController controller = new(); // 弹幕位置控制器 - Array.Sort(danmakus, comparer); - foreach (DanmakuItem danmaku in danmakus) + PositionController controller = new(); // 弹幕位置控制器 + Array.Sort(danmakus, comparer); + foreach (DanmakuItem danmaku in danmakus) + { + int height = controller.UpdatePosition(danmaku.DanmakuMode, danmaku.Second, danmaku.Content.Length); + if (height == -1) continue; + string effect = ""; + effect += danmaku.DanmakuMode switch { - int height = controller.UpdatePosition(danmaku.DanmakuMode, danmaku.Second, danmaku.Content.Length); - if (height == -1) continue; - string effect = ""; - effect += danmaku.DanmakuMode switch - { - 3 => $"\\an8\\pos({MONITOR_WIDTH / 2}, {MONITOR_HEIGHT - FONT_SIZE - height})", - 2 => $"\\an8\\pos({MONITOR_WIDTH / 2}, {height})", - _ => $"\\move({MONITOR_WIDTH}, {height}, {-danmaku.Content.Length * FONT_SIZE}, {height})", - }; - if (danmaku.Color != "FFFFFF") - { - effect += $"\\c&{danmaku.Color}&"; - } - sb.AppendLine($"Dialogue: 2,{danmaku.StartTime},{danmaku.EndTime},BBDOWN_Style,,0000,0000,0000,,{{{effect}}}{danmaku.Content}"); + 3 => $"\\an8\\pos({MONITOR_WIDTH / 2}, {MONITOR_HEIGHT - FONT_SIZE - height})", + 2 => $"\\an8\\pos({MONITOR_WIDTH / 2}, {height})", + _ => $"\\move({MONITOR_WIDTH}, {height}, {-danmaku.Content.Length * FONT_SIZE}, {height})", + }; + if (danmaku.Color != "FFFFFF") + { + effect += $"\\c&{danmaku.Color}&"; } - - await File.WriteAllTextAsync(outputPath, sb.ToString(), Encoding.UTF8); + sb.AppendLine($"Dialogue: 2,{danmaku.StartTime},{danmaku.EndTime},BBDOWN_Style,,0000,0000,0000,,{{{effect}}}{danmaku.Content}"); } - protected class PositionController - { - readonly int maxLine = MONITOR_HEIGHT * PROTECT_LENGTH / FONT_SIZE / 100; //总行数 - // 三个位置的弹幕队列,记录弹幕结束时间 + await File.WriteAllTextAsync(outputPath, sb.ToString(), Encoding.UTF8); + } - readonly List moveQueue = new(); - readonly List topQueue = new(); - readonly List bottomQueue = new(); + protected class PositionController + { + readonly int maxLine = MONITOR_HEIGHT * PROTECT_LENGTH / FONT_SIZE / 100; //总行数 + // 三个位置的弹幕队列,记录弹幕结束时间 - public PositionController() - { - for (int i = 0; i < maxLine; i++) - { - moveQueue.Add(0.00); - topQueue.Add(0.00); - bottomQueue.Add(0.00); - } - } + readonly List moveQueue = new(); + readonly List topQueue = new(); + readonly List bottomQueue = new(); - public int UpdatePosition(int type, double time, int length) + public PositionController() + { + for (int i = 0; i < maxLine; i++) { - // 获取可用位置 - List vs; - double displayTime = TOP_SPEND_TIME; - if (type == POS_BOTTOM) - { - vs = bottomQueue; - } - else if (type == POS_TOP) - { - vs = topQueue; - } - else - { - vs = moveQueue; - displayTime = MOVE_SPEND_TIME * (length + 5) * FONT_SIZE / (MONITOR_WIDTH + (length * MOVE_SPEND_TIME)); - } - for (int i = 0; i < maxLine; i++) - { - if (time >= vs[i]) - { // 此条弹幕已结束,更新该位置信息 - vs[i] = time + displayTime; - return i * FONT_SIZE; - } - } - return -1; + moveQueue.Add(0.00); + topQueue.Add(0.00); + bottomQueue.Add(0.00); } } - public class DanmakuItem + public int UpdatePosition(int type, double time, int length) { - public DanmakuItem(string[] attrs, string content) + // 获取可用位置 + List vs; + double displayTime = TOP_SPEND_TIME; + if (type == POS_BOTTOM) { - DanmakuMode = attrs[1] switch - { - "4" => POS_BOTTOM, - "5" => POS_TOP, - _ => POS_MOVE, - }; - try - { - double second = double.Parse(attrs[0]); - Second = second; - StartTime = ComputeTime(second); - EndTime = ComputeTime(second + (DanmakuMode == 1 ? MOVE_SPEND_TIME : TOP_SPEND_TIME)); - } - catch (Exception e) - { - Log(e.Message); - } - FontSize = attrs[2]; - try - { - int colorD = int.Parse(attrs[3]); - Color = string.Format("{0:X6}", colorD); - } - catch (FormatException e) - { - Log(e.Message); - } - Timestamp = attrs[4]; - Content = content; + vs = bottomQueue; } - private static string ComputeTime(double second) + else if (type == POS_TOP) { - int hour = (int)second / 3600; - int minute = (int)(second - (hour * 3600)) / 60; - second -= (hour * 3600) + (minute * 60); - return hour.ToString() + string.Format(":{0:D2}:", minute) + string.Format("{0:00.00}", second); + vs = topQueue; + } + else + { + vs = moveQueue; + displayTime = MOVE_SPEND_TIME * (length + 5) * FONT_SIZE / (MONITOR_WIDTH + (length * MOVE_SPEND_TIME)); + } + for (int i = 0; i < maxLine; i++) + { + if (time >= vs[i]) + { // 此条弹幕已结束,更新该位置信息 + vs[i] = time + displayTime; + return i * FONT_SIZE; + } } - public string Content { get; set; } = ""; - // 弹幕内容 - public string StartTime { get; set; } = ""; - // 出现时间 - public double Second { get; set; } = 0.00; - // 出现时间(秒为单位) - public string EndTime { get; set; } = ""; - // 消失时间 - public int DanmakuMode { get; set; } = POS_MOVE; - // 弹幕类型 - public string FontSize { get; set; } = ""; - // 字号 - public string Color { get; set; } = ""; - // 颜色 - public string Timestamp { get; set; } = ""; - // 时间戳 + return -1; } + } - public class DanmakuComparer : IComparer + public class DanmakuItem + { + public DanmakuItem(string[] attrs, string content) { - public int Compare(DanmakuItem? x, DanmakuItem? y) + DanmakuMode = attrs[1] switch { - if (x == null) return -1; - if (y == null) return 1; - return x.Second.CompareTo(y.Second); + "4" => POS_BOTTOM, + "5" => POS_TOP, + _ => POS_MOVE, + }; + try + { + double second = double.Parse(attrs[0]); + Second = second; + StartTime = ComputeTime(second); + EndTime = ComputeTime(second + (DanmakuMode == 1 ? MOVE_SPEND_TIME : TOP_SPEND_TIME)); } + catch (Exception e) + { + Log(e.Message); + } + FontSize = attrs[2]; + try + { + int colorD = int.Parse(attrs[3]); + Color = string.Format("{0:X6}", colorD); + } + catch (FormatException e) + { + Log(e.Message); + } + Timestamp = attrs[4]; + Content = content; + } + private static string ComputeTime(double second) + { + int hour = (int)second / 3600; + int minute = (int)(second - (hour * 3600)) / 60; + second -= (hour * 3600) + (minute * 60); + return hour.ToString() + string.Format(":{0:D2}:", minute) + string.Format("{0:00.00}", second); } + public string Content { get; set; } = ""; + // 弹幕内容 + public string StartTime { get; set; } = ""; + // 出现时间 + public double Second { get; set; } = 0.00; + // 出现时间(秒为单位) + public string EndTime { get; set; } = ""; + // 消失时间 + public int DanmakuMode { get; set; } = POS_MOVE; + // 弹幕类型 + public string FontSize { get; set; } = ""; + // 字号 + public string Color { get; set; } = ""; + // 颜色 + public string Timestamp { get; set; } = ""; + // 时间戳 + } - private const int POS_MOVE = 1; //滚动弹幕 - private const int POS_TOP = 2; //顶部弹幕 - private const int POS_BOTTOM = 3; //底部弹幕 + public class DanmakuComparer : IComparer + { + public int Compare(DanmakuItem? x, DanmakuItem? y) + { + if (x == null) return -1; + if (y == null) return 1; + return x.Second.CompareTo(y.Second); + } } -} + + private const int POS_MOVE = 1; //滚动弹幕 + private const int POS_TOP = 2; //顶部弹幕 + private const int POS_BOTTOM = 3; //底部弹幕 +} \ No newline at end of file diff --git a/BBDown.Core/Entity/Entity.cs b/BBDown.Core/Entity/Entity.cs index a156fffee..0dd4d32a6 100644 --- a/BBDown.Core/Entity/Entity.cs +++ b/BBDown.Core/Entity/Entity.cs @@ -1,224 +1,223 @@ using BBDown.Core.Util; using System.Diagnostics.CodeAnalysis; -namespace BBDown.Core.Entity +namespace BBDown.Core.Entity; + +public static class Entity { - public class Entity + public class Page { - public class Page + public required int index; + public required string aid; + public required string cid; + public required string epid; + public required string title; + public required int dur; + public required string res; + public required long pubTime; + public string? cover; + public string? desc; + public string? ownerName; + public string? ownerMid; + public string bvid + { + get => BilibiliBvConverter.Encode(long.Parse(aid)); + } + public List points = new(); + + [SetsRequiredMembers] + public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime) + { + this.aid = aid; + this.index = index; + this.cid = cid; + this.epid = epid; + this.title = title; + this.dur = dur; + this.res = res; + this.pubTime = pubTime; + } + + [SetsRequiredMembers] + public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime, string cover) + { + this.aid = aid; + this.index = index; + this.cid = cid; + this.epid = epid; + this.title = title; + this.dur = dur; + this.res = res; + this.pubTime = pubTime; + this.cover = cover; + } + + [SetsRequiredMembers] + public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime, string cover, string desc) + { + this.aid = aid; + this.index = index; + this.cid = cid; + this.epid = epid; + this.title = title; + this.dur = dur; + this.res = res; + this.pubTime = pubTime; + this.cover = cover; + this.desc = desc; + } + + [SetsRequiredMembers] + public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime, string cover, string desc, string ownerName, string ownerMid) + { + this.aid = aid; + this.index = index; + this.cid = cid; + this.epid = epid; + this.title = title; + this.dur = dur; + this.res = res; + this.pubTime = pubTime; + this.cover = cover; + this.desc = desc; + this.ownerName = ownerName; + this.ownerMid = ownerMid; + } + + [SetsRequiredMembers] + public Page(int index, Page page) + { + this.index = index; + this.aid = page.aid; + this.cid = page.cid; + this.epid = page.epid; + this.title = page.title; + this.dur = page.dur; + this.res = page.res; + this.pubTime = page.pubTime; + this.cover = page.cover; + this.ownerName = page.ownerName; + this.ownerMid = page.ownerMid; + } + + public override bool Equals(object? obj) { - public required int index; - public required string aid; - public required string cid; - public required string epid; - public required string title; - public required int dur; - public required string res; - public required long pubTime; - public string? cover; - public string? desc; - public string? ownerName; - public string? ownerMid; - public string bvid - { - get => BilibiliBvConverter.Encode(long.Parse(aid)); - } - public List points = new(); - - [SetsRequiredMembers] - public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime) - { - this.aid = aid; - this.index = index; - this.cid = cid; - this.epid = epid; - this.title = title; - this.dur = dur; - this.res = res; - this.pubTime = pubTime; - } - - [SetsRequiredMembers] - public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime, string cover) - { - this.aid = aid; - this.index = index; - this.cid = cid; - this.epid = epid; - this.title = title; - this.dur = dur; - this.res = res; - this.pubTime = pubTime; - this.cover = cover; - } - - [SetsRequiredMembers] - public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime, string cover, string desc) - { - this.aid = aid; - this.index = index; - this.cid = cid; - this.epid = epid; - this.title = title; - this.dur = dur; - this.res = res; - this.pubTime = pubTime; - this.cover = cover; - this.desc = desc; - } - - [SetsRequiredMembers] - public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime, string cover, string desc, string ownerName, string ownerMid) - { - this.aid = aid; - this.index = index; - this.cid = cid; - this.epid = epid; - this.title = title; - this.dur = dur; - this.res = res; - this.pubTime = pubTime; - this.cover = cover; - this.desc = desc; - this.ownerName = ownerName; - this.ownerMid = ownerMid; - } - - [SetsRequiredMembers] - public Page(int index, Page page) - { - this.index = index; - this.aid = page.aid; - this.cid = page.cid; - this.epid = page.epid; - this.title = page.title; - this.dur = page.dur; - this.res = page.res; - this.pubTime = page.pubTime; - this.cover = page.cover; - this.ownerName = page.ownerName; - this.ownerMid = page.ownerMid; - } - - public override bool Equals(object? obj) - { - return obj is Page page && - aid == page.aid && - cid == page.cid && - epid == page.epid; - } - - public override int GetHashCode() - { - return HashCode.Combine(aid, cid, epid); - } + return obj is Page page && + aid == page.aid && + cid == page.cid && + epid == page.epid; } - public class ViewPoint + public override int GetHashCode() { - public required string title; - public required int start; - public required int end; + return HashCode.Combine(aid, cid, epid); } + } - public class Video + public class ViewPoint + { + public required string title; + public required int start; + public required int end; + } + + public class Video + { + public required string id; + public required string dfn; + public required string baseUrl; + public string? res; + public string? fps; + public required string codecs; + public long bandwith; + public int dur; + public double size; + + public override bool Equals(object? obj) { - public required string id; - public required string dfn; - public required string baseUrl; - public string? res; - public string? fps; - public required string codecs; - public long bandwith; - public int dur; - public double size; - - public override bool Equals(object? obj) - { - return obj is Video video && - id == video.id && - dfn == video.dfn && - res == video.res && - fps == video.fps && - codecs == video.codecs && - bandwith == video.bandwith && - dur == video.dur; - } - - public override int GetHashCode() - { - return HashCode.Combine(id, dfn, res, fps, codecs, bandwith, dur); - } + return obj is Video video && + id == video.id && + dfn == video.dfn && + res == video.res && + fps == video.fps && + codecs == video.codecs && + bandwith == video.bandwith && + dur == video.dur; } - public class Audio + public override int GetHashCode() { - public required string id; - public required string dfn; - public required string baseUrl; - public required string codecs; - public required long bandwith; - public required int dur; - - public override bool Equals(object? obj) - { - return obj is Audio audio && - id == audio.id && - dfn == audio.dfn && - codecs == audio.codecs && - bandwith == audio.bandwith && - dur == audio.dur; - } - - public override int GetHashCode() - { - return HashCode.Combine(id, dfn, codecs, bandwith, dur); - } + return HashCode.Combine(id, dfn, res, fps, codecs, bandwith, dur); } + } - public class Subtitle + public class Audio + { + public required string id; + public required string dfn; + public required string baseUrl; + public required string codecs; + public required long bandwith; + public required int dur; + + public override bool Equals(object? obj) { - public required string lan; - public required string url; - public required string path; + return obj is Audio audio && + id == audio.id && + dfn == audio.dfn && + codecs == audio.codecs && + bandwith == audio.bandwith && + dur == audio.dur; } - public class Clip + public override int GetHashCode() { - public required int index; - public required long from; - public required long to; + return HashCode.Combine(id, dfn, codecs, bandwith, dur); } + } - public class AudioMaterial + public class Subtitle + { + public required string lan; + public required string url; + public required string path; + } + + public class Clip + { + public required int index; + public required long from; + public required long to; + } + + public class AudioMaterial + { + public required string title; + public required string personName; + public required string path; + + [SetsRequiredMembers] + public AudioMaterial(string title, string personName, string path) { - public required string title; - public required string personName; - public required string path; - - [SetsRequiredMembers] - public AudioMaterial(string title, string personName, string path) - { - this.title = title; - this.personName = personName; - this.path = path; - } - - [SetsRequiredMembers] - public AudioMaterial(AudioMaterialInfo audioMaterialInfo) - { - this.title = audioMaterialInfo.title; - this.personName = audioMaterialInfo.personName; - this.path = audioMaterialInfo.path; - } + this.title = title; + this.personName = personName; + this.path = path; } - public class AudioMaterialInfo + [SetsRequiredMembers] + public AudioMaterial(AudioMaterialInfo audioMaterialInfo) { - public required string title; - public required string personName; - public required string path; - public required List