From 06ae0e5712f6628a850178faaafbdd74d26ea960 Mon Sep 17 00:00:00 2001
From: Tao <magicdawn@qq.com>
Date: Mon, 11 Nov 2024 08:38:34 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0`--download-danmaku-formats`?=
 =?UTF-8?q?=E9=80=89=E9=A1=B9=E4=BB=A5=E6=8E=A7=E5=88=B6=E5=BC=B9=E5=B9=95?=
 =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E6=A0=BC=E5=BC=8F=20(#950)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feat: add -ddf,--download-danmaku-format option to keep only xml/ass file

* chore: fix review advice

* chore: fix ident

* chore: fix flags usage

* chore: use enum.ToString()

* Update BBDown/Program.cs

Co-authored-by: nilaoda <nilaoda@live.com>

* chore: rename to plural & add desc help info

* chore: manual format

---------

Co-authored-by: nilaoda <nilaoda@live.com>
---
 .editorconfig                | 12 ++++++++++++
 BBDown/BBDownApiServer.cs    |  4 ++--
 BBDown/BBDownEnums.cs        | 29 +++++++++++++++++++++++++++++
 BBDown/CommandLineInvoker.cs |  3 +++
 BBDown/MyOption.cs           |  1 +
 BBDown/Program.Methods.cs    | 14 ++++++++++++++
 BBDown/Program.cs            | 34 ++++++++++++++++++++++++----------
 7 files changed, 85 insertions(+), 12 deletions(-)
 create mode 100644 .editorconfig
 create mode 100644 BBDown/BBDownEnums.cs

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<bool> SkipCover = new(["--skip-cover"], "跳过封面下载");
     private static readonly Option<bool> ForceHttp = new(["--force-http"], "下载音视频时强制使用HTTP协议替换HTTPS(默认开启)");
     private static readonly Option<bool> DownloadDanmaku = new(["--download-danmaku", "-dd"], "下载弹幕");
+    private static readonly Option<string> DownloadDanmakuFormats = new(["--download-danmaku-formats", "-ddf"], $"指定需下载的弹幕格式, 用逗号分隔, 可选 {string.Join('/', BBDownDanmakuFormatInfo.AllFormatNames)}, 默认: \"{string.Join(',', BBDownDanmakuFormatInfo.AllFormatNames)}\"");
     private static readonly Option<bool> SkipAi = new(["--skip-ai"], description: "跳过AI字幕下载(默认开启)");
     private static readonly Option<bool> VideoAscending = new(["--video-ascending"], "视频升序(最小体积优先)");
     private static readonly Option<bool> 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<MyOption, Task> 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<string, byte> 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();
+    }
+
     /// <summary>
     /// 解析用户输入的清晰度规格优先级
     /// </summary>
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<string, byte> encodingPriority, Dictionary<string, int> 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<string, byte> encodingPriority, Dictionary<string, int
         HTTPUtil.UserAgent = string.IsNullOrEmpty(myOption.UserAgent) ? HTTPUtil.UserAgent : myOption.UserAgent;
 
         bool downloadDanmaku = myOption.DownloadDanmaku || myOption.DanmakuOnly;
+        BBDownDanmakuFormat[] downloadDanmakuFormats = ParseDownloadDanmakuFormats(myOption);
+
         string input = myOption.Url;
         string savePathFormat = myOption.FilePattern;
         string lang = myOption.Language;
@@ -220,7 +222,7 @@ public static (Dictionary<string, byte> encodingPriority, Dictionary<string, int
 
         LogDebug("AppDirectory: {0}", APP_DIR);
         LogDebug("运行参数:{0}", JsonSerializer.Serialize(myOption, MyOptionJsonContext.Default.MyOption));
-        return (encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, input, savePathFormat, lang, aidOri, delay);
+        return (encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, downloadDanmakuFormats, input, savePathFormat, lang, aidOri, delay);
     }
 
     public static async Task<(string fetchedAid, VInfo vInfo, string apiType)> GetVideoInfoAsync(MyOption myOption, string aidOri, string input)
@@ -320,7 +322,7 @@ public static (Dictionary<string, byte> encodingPriority, Dictionary<string, int
     }
 
     public static async Task DownloadPagesAsync(MyOption myOption, VInfo vInfo, Dictionary<string, byte> encodingPriority, Dictionary<string, int> 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<Page> 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<Page> selectedPagesInfo, Dictionary<string, byte> encodingPriority, Dictionary<string, int> 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)