diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..bfc2e2194 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +charset = utf-8 +# end_of_line = crlf +# trim_trailing_whitespace = false +# insert_final_newline = false \ No newline at end of file diff --git a/BBDown/BBDownApiServer.cs b/BBDown/BBDownApiServer.cs index c9df66bc5..af7d5151a 100644 --- a/BBDown/BBDownApiServer.cs +++ b/BBDown/BBDownApiServer.cs @@ -98,12 +98,12 @@ private async Task AddDownloadTaskAsync(MyOption option) runningTasks.Add(task); try { - var (encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, input, savePathFormat, lang, aidOri, delay) = Program.SetUpWork(option); + var (encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, downloadDanmakuFormats, input, savePathFormat, lang, aidOri, delay) = Program.SetUpWork(option); var (fetchedAid, vInfo, apiType) = await Program.GetVideoInfoAsync(option, aidOri, input); task.Title = vInfo.Title; task.Pic = vInfo.Pic; task.VideoPubTime = vInfo.PubTime; - await Program.DownloadPagesAsync(option, vInfo, encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, + await Program.DownloadPagesAsync(option, vInfo, encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, downloadDanmakuFormats, input, savePathFormat, lang, fetchedAid, delay, apiType, task); task.IsSuccessful = true; } diff --git a/BBDown/BBDownEnums.cs b/BBDown/BBDownEnums.cs new file mode 100644 index 000000000..3836c3bf9 --- /dev/null +++ b/BBDown/BBDownEnums.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; + +namespace BBDown; + +public enum BBDownDanmakuFormat +{ + Xml, + Ass, +} + +public static class BBDownDanmakuFormatInfo +{ + // 默认 + public static BBDownDanmakuFormat[] DefaultFormats = [BBDownDanmakuFormat.Xml, BBDownDanmakuFormat.Ass]; + public static string[] DefaultFormatsNames = DefaultFormats.Select(f => f.ToString().ToLower()).ToArray(); + // 可选项 + public static string[] AllFormatNames = Enum.GetNames(typeof(BBDownDanmakuFormat)).Select(f => f.ToLower()).ToArray(); + + public static BBDownDanmakuFormat FromFormatName(string formatName) + { + return formatName switch + { + "xml" => BBDownDanmakuFormat.Xml, + "ass" => BBDownDanmakuFormat.Ass, + _ => BBDownDanmakuFormat.Xml, + }; + } +} diff --git a/BBDown/CommandLineInvoker.cs b/BBDown/CommandLineInvoker.cs index 3cc645037..445dcc8f1 100644 --- a/BBDown/CommandLineInvoker.cs +++ b/BBDown/CommandLineInvoker.cs @@ -35,6 +35,7 @@ internal static class CommandLineInvoker private static readonly Option SkipCover = new(["--skip-cover"], "跳过封面下载"); private static readonly Option ForceHttp = new(["--force-http"], "下载音视频时强制使用HTTP协议替换HTTPS(默认开启)"); private static readonly Option DownloadDanmaku = new(["--download-danmaku", "-dd"], "下载弹幕"); + private static readonly Option DownloadDanmakuFormats = new(["--download-danmaku-formats", "-ddf"], $"指定需下载的弹幕格式, 用逗号分隔, 可选 {string.Join('/', BBDownDanmakuFormatInfo.AllFormatNames)}, 默认: \"{string.Join(',', BBDownDanmakuFormatInfo.AllFormatNames)}\""); private static readonly Option SkipAi = new(["--skip-ai"], description: "跳过AI字幕下载(默认开启)"); private static readonly Option VideoAscending = new(["--video-ascending"], "视频升序(最小体积优先)"); private static readonly Option AudioAscending = new(["--audio-ascending"], "音频升序(最小体积优先)"); @@ -120,6 +121,7 @@ protected override MyOption GetBoundValue(BindingContext bindingContext) if (bindingContext.ParseResult.HasOption(SkipCover)) option.SkipCover = bindingContext.ParseResult.GetValueForOption(SkipCover)!; if (bindingContext.ParseResult.HasOption(ForceHttp)) option.ForceHttp = bindingContext.ParseResult.GetValueForOption(ForceHttp)!; if (bindingContext.ParseResult.HasOption(DownloadDanmaku)) option.DownloadDanmaku = bindingContext.ParseResult.GetValueForOption(DownloadDanmaku)!; + if (bindingContext.ParseResult.HasOption(DownloadDanmakuFormats)) option.DownloadDanmakuFormats = bindingContext.ParseResult.GetValueForOption(DownloadDanmakuFormats)!; if (bindingContext.ParseResult.HasOption(SkipAi)) option.SkipAi = bindingContext.ParseResult.GetValueForOption(SkipAi)!; if (bindingContext.ParseResult.HasOption(VideoAscending)) option.VideoAscending = bindingContext.ParseResult.GetValueForOption(VideoAscending)!; if (bindingContext.ParseResult.HasOption(AudioAscending)) option.AudioAscending = bindingContext.ParseResult.GetValueForOption(AudioAscending)!; @@ -183,6 +185,7 @@ public static RootCommand GetRootCommand(Func action) SkipCover, ForceHttp, DownloadDanmaku, + DownloadDanmakuFormats, SkipAi, VideoAscending, AudioAscending, diff --git a/BBDown/MyOption.cs b/BBDown/MyOption.cs index c5ab7bf32..a7ca92ddc 100644 --- a/BBDown/MyOption.cs +++ b/BBDown/MyOption.cs @@ -27,6 +27,7 @@ internal class MyOption public bool SkipCover { get; set; } public bool ForceHttp { get; set; } = true; public bool DownloadDanmaku { get; set; } = false; + public string? DownloadDanmakuFormats { get; set; } public bool SkipAi { get; set; } = true; public bool VideoAscending { get; set; } = false; public bool AudioAscending { get; set; } = false; diff --git a/BBDown/Program.Methods.cs b/BBDown/Program.Methods.cs index 3bdd0ebc2..c6df075ae 100644 --- a/BBDown/Program.Methods.cs +++ b/BBDown/Program.Methods.cs @@ -93,6 +93,20 @@ private static Dictionary ParseEncodingPriority(MyOption myOption, return encodingPriority; } + private static BBDownDanmakuFormat[] ParseDownloadDanmakuFormats(MyOption myOption) + { + if (string.IsNullOrEmpty(myOption.DownloadDanmakuFormats)) return BBDownDanmakuFormatInfo.DefaultFormats; + + var formats = myOption.DownloadDanmakuFormats.Replace(",", ",").ToLower().Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + if (formats.Any(format => !BBDownDanmakuFormatInfo.AllFormatNames.Contains(format))) + { + LogError($"包含不支持的下载弹幕格式:{myOption.DownloadDanmakuFormats}"); + return BBDownDanmakuFormatInfo.DefaultFormats; + } + + return formats.Select(BBDownDanmakuFormatInfo.FromFormatName).ToArray(); + } + /// /// 解析用户输入的清晰度规格优先级 /// diff --git a/BBDown/Program.cs b/BBDown/Program.cs index e1e71c4ed..9036d7137 100644 --- a/BBDown/Program.cs +++ b/BBDown/Program.cs @@ -183,7 +183,7 @@ private static void StartServer(string? listenUrl) } public static (Dictionary encodingPriority, Dictionary dfnPriority, string? firstEncoding, - bool downloadDanmaku, string input, string savePathFormat, string lang, string aidOri, int delay) + bool downloadDanmaku, BBDownDanmakuFormat[] downloadDanmakuFormats, string input, string savePathFormat, string lang, string aidOri, int delay) SetUpWork(MyOption myOption) { //处理废弃选项 @@ -206,6 +206,8 @@ public static (Dictionary encodingPriority, Dictionary encodingPriority, Dictionary GetVideoInfoAsync(MyOption myOption, string aidOri, string input) @@ -320,7 +322,7 @@ public static (Dictionary encodingPriority, Dictionary encodingPriority, Dictionary dfnPriority, - string? firstEncoding, bool downloadDanmaku, string input, string savePathFormat, string lang, string aidOri, int delay, string apiType, DownloadTask? relatedTask = null) + string? firstEncoding, bool downloadDanmaku, BBDownDanmakuFormat[] downloadDanmakuFormats, string input, string savePathFormat, string lang, string aidOri, int delay, string apiType, DownloadTask? relatedTask = null) { List pagesInfo = vInfo.PagesInfo; bool bangumi = vInfo.IsBangumi; @@ -365,7 +367,7 @@ public static async Task DownloadPagesAsync(MyOption myOption, VInfo vInfo, Dict } await DownloadPageAsync(p, myOption, vInfo, pagesInfo, encodingPriority, dfnPriority, firstEncoding, - downloadDanmaku, input, savePathFormat, lang, aidOri, apiType, relatedTask); + downloadDanmaku, downloadDanmakuFormats, input, savePathFormat, lang, aidOri, apiType, relatedTask); if (myOption.SaveArchivesToFile) { @@ -377,7 +379,7 @@ await DownloadPageAsync(p, myOption, vInfo, pagesInfo, encodingPriority, dfnPrio } private static async Task DownloadPageAsync(Page p, MyOption myOption, VInfo vInfo, List selectedPagesInfo, Dictionary encodingPriority, Dictionary dfnPriority, - string? firstEncoding, bool downloadDanmaku, string input, string savePathFormat, string lang, string aidOri, string apiType, DownloadTask? relatedTask = null) + string? firstEncoding, bool downloadDanmaku, BBDownDanmakuFormat[] downloadDanmakuFormats, string input, string savePathFormat, string lang, string aidOri, string apiType, DownloadTask? relatedTask = null) { string desc = string.IsNullOrEmpty(p.desc) ? vInfo.Desc : p.desc; bool bangumi = vInfo.IsBangumi; @@ -555,16 +557,28 @@ private static async Task DownloadPageAsync(Page p, MyOption myOption, VInfo vIn string danmakuUrl = $"https://comment.bilibili.com/{p.cid}.xml"; await DownloadFile(danmakuUrl, danmakuXmlPath, downloadConfig); var danmakus = DanmakuUtil.ParseXml(danmakuXmlPath); - if (danmakus != null) + if (danmakus == null) + { + Log("弹幕Xml解析失败, 删除Xml..."); + File.Delete(danmakuXmlPath); + } + else if (danmakus.Length == 0) + { + Log("当前视频没有弹幕, 删除Xml..."); + File.Delete(danmakuXmlPath); + } + else if (downloadDanmakuFormats.Contains(BBDownDanmakuFormat.Ass)) { Log("正在保存弹幕Ass文件..."); await DanmakuUtil.SaveAsAssAsync(danmakus, danmakuAssPath); } - else + + // delete xml if possible + if (!downloadDanmakuFormats.Contains(BBDownDanmakuFormat.Xml) && File.Exists(danmakuXmlPath)) { - Log("弹幕Xml解析失败, 删除Xml..."); File.Delete(danmakuXmlPath); } + if (myOption.DanmakuOnly) { if (Directory.Exists(p.aid)) @@ -785,10 +799,10 @@ private static async Task DoWorkAsync(MyOption myOption) { try { - var (encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, + var (encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, downloadDanmakuFormats, input, savePathFormat, lang, aidOri, delay) = SetUpWork(myOption); var (fetchedAid, vInfo, apiType) = await GetVideoInfoAsync(myOption, aidOri, input); - await DownloadPagesAsync(myOption, vInfo, encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, + await DownloadPagesAsync(myOption, vInfo, encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, downloadDanmakuFormats, input, savePathFormat, lang, fetchedAid, delay, apiType); } catch (Exception e)