From be6b0d47a56b147142f55909f42e87d86af78d51 Mon Sep 17 00:00:00 2001 From: croire <1432593898@qq.com> Date: Fri, 19 Nov 2021 00:08:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=8F=91=E4=B8=8B=E8=BD=BD=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E4=B8=AD=EF=BC=8C=E4=BF=9D=E5=AD=98=E5=B7=A5=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DownKyi.Core.Test/BiliApi/UnitTest1.cs | 175 ++++++++ DownKyi.Core.Test/DownKyi.Core.Test.csproj | 8 + DownKyi.Core.Test/packages.config | 1 + DownKyi.Core/Aria2cNet/AriaManager.cs | 20 +- .../{DownloadStatus.cs => DownloadResult.cs} | 2 +- .../BiliApi/Models/Json/SubRipText.cs | 14 + DownKyi.Core/BiliApi/Models/Json/Subtitle.cs | 16 + .../BiliApi/Models/Json/SubtitleJson.cs | 69 +++ .../BiliApi/VideoStream/Models/PlayerV2.cs | 34 ++ .../BiliApi/VideoStream/Models/Subtitle.cs | 25 ++ .../VideoStream/Models/SubtitleInfo.cs | 18 + .../BiliApi/VideoStream/VideoStream.cs | 73 +++- DownKyi.Core/DownKyi.Core.csproj | 11 +- DownKyi.Core/FileName/FileName.cs | 117 +++++ DownKyi.Core/FileName/FileNamePart.cs | 34 ++ DownKyi.Core/FileName/HyphenSeparated.cs | 29 ++ DownKyi/App.xaml.cs | 28 +- DownKyi/DownKyi.csproj | 7 + DownKyi/Models/DownloadStatus.cs | 12 + DownKyi/Models/DownloadedItem.cs | 13 + DownKyi/Models/DownloadingItem.cs | 91 ++++ DownKyi/Models/PlayStreamType.cs | 9 + DownKyi/Services/BangumiInfoService.cs | 38 +- .../Services/Download/AriaDownloadService.cs | 405 ++++++++++++++++++ DownKyi/Services/Download/DownloadService.cs | 25 ++ DownKyi/Services/Download/IDownloadService.cs | 18 + .../ViewDownloadFinishedViewModel.cs | 17 +- .../ViewDownloadingViewModel.cs | 17 +- DownKyi/ViewModels/MainWindowViewModel.cs | 3 + 29 files changed, 1301 insertions(+), 28 deletions(-) create mode 100644 DownKyi.Core.Test/BiliApi/UnitTest1.cs rename DownKyi.Core/Aria2cNet/{DownloadStatus.cs => DownloadResult.cs} (83%) create mode 100644 DownKyi.Core/BiliApi/Models/Json/SubRipText.cs create mode 100644 DownKyi.Core/BiliApi/Models/Json/Subtitle.cs create mode 100644 DownKyi.Core/BiliApi/Models/Json/SubtitleJson.cs create mode 100644 DownKyi.Core/BiliApi/VideoStream/Models/PlayerV2.cs create mode 100644 DownKyi.Core/BiliApi/VideoStream/Models/Subtitle.cs create mode 100644 DownKyi.Core/BiliApi/VideoStream/Models/SubtitleInfo.cs create mode 100644 DownKyi.Core/FileName/FileName.cs create mode 100644 DownKyi.Core/FileName/FileNamePart.cs create mode 100644 DownKyi.Core/FileName/HyphenSeparated.cs create mode 100644 DownKyi/Models/DownloadStatus.cs create mode 100644 DownKyi/Models/DownloadedItem.cs create mode 100644 DownKyi/Models/DownloadingItem.cs create mode 100644 DownKyi/Models/PlayStreamType.cs create mode 100644 DownKyi/Services/Download/AriaDownloadService.cs create mode 100644 DownKyi/Services/Download/DownloadService.cs create mode 100644 DownKyi/Services/Download/IDownloadService.cs diff --git a/DownKyi.Core.Test/BiliApi/UnitTest1.cs b/DownKyi.Core.Test/BiliApi/UnitTest1.cs new file mode 100644 index 0000000..455829d --- /dev/null +++ b/DownKyi.Core.Test/BiliApi/UnitTest1.cs @@ -0,0 +1,175 @@ +using Brotli; +using DownKyi.Core.BiliApi.Login; +using DownKyi.Core.BiliApi.Models.Json; +using DownKyi.Core.BiliApi.VideoStream; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Text; + +namespace DownKyi.Core.Test.BiliApi +{ + [TestClass] + public class UnitTest1 + { + [TestMethod] + public void TestMethod1() + { + var player = VideoStream.PlayerV2(464087531, "BV1HL411u765", 439599721); + + foreach (var subtitle in player.Subtitle.Subtitles) + { + string referer = "https://www.bilibili.com"; + string response = RequestWeb($"https:{subtitle.SubtitleUrl}", referer); + + try + { + //Console.WriteLine(subtitle.SubtitleUrl); + + //string json = @"D:\test.json"; + //WebClient mywebclient = new WebClient(); + //mywebclient.DownloadFile($"https:{subtitle.SubtitleUrl}", json); + + //StreamReader streamReader = File.OpenText(json); + //string jsonWordTemplate = streamReader.ReadToEnd(); + //streamReader.Close(); + + var subtitleJson = JsonConvert.DeserializeObject(response); + if (subtitleJson == null) { return; } + + string srt = subtitleJson.ToSubRip(); + File.WriteAllText($"D:/{subtitle.LanDoc}.srt", srt); + } + catch (Exception e) + { + Utils.Debugging.Console.PrintLine("PlayerV2()发生异常: {0}", e); + } + } + + } + + + public string RequestWeb(string url, string referer = null, string method = "GET", Dictionary parameters = null, int retry = 3) + { + // 重试次数 + if (retry <= 0) { return ""; } + + // post请求,发送参数 + if (method == "POST" && parameters != null) + { + StringBuilder builder = new StringBuilder(); + int i = 0; + foreach (var item in parameters) + { + if (i > 0) + { + builder.Append("&"); + } + + builder.AppendFormat("{0}={1}", item.Key, item.Value); + i++; + } + + url += "?" + builder.ToString(); + } + + try + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); + request.Method = method; + request.Timeout = 30 * 1000; + request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"; + //request.ContentType = "application/json,text/html,application/xhtml+xml,application/xml;charset=UTF-8"; + request.Headers["accept-language"] = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"; + request.Headers["accept-encoding"] = "gzip, deflate, br"; + + // referer + if (referer != null) + { + request.Referer = referer; + } + + // 构造cookie + if (!url.Contains("getLogin")) + { + request.Headers["origin"] = "https://www.bilibili.com"; + + CookieContainer cookies = LoginHelper.GetLoginInfoCookies(); + if (cookies != null) + { + request.CookieContainer = cookies; + } + } + + string html = string.Empty; + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) + { + if (response.ContentEncoding.ToLower().Contains("gzip")) + { + using (GZipStream stream = new GZipStream(response.GetResponseStream(), CompressionMode.Decompress)) + { + using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) + { + html = reader.ReadToEnd(); + } + } + } + else if (response.ContentEncoding.ToLower().Contains("deflate")) + { + using (DeflateStream stream = new DeflateStream(response.GetResponseStream(), CompressionMode.Decompress)) + { + using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) + { + html = reader.ReadToEnd(); + } + } + } + else if (response.ContentEncoding.ToLower().Contains("br")) + { + using (BrotliStream stream = new BrotliStream(response.GetResponseStream(), CompressionMode.Decompress)) + { + using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) + { + html = reader.ReadToEnd(); + } + } + } + else + { + using (Stream stream = response.GetResponseStream()) + { + using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) + { + html = reader.ReadToEnd(); + } + } + } + } + + return html; + } + catch (WebException e) + { + Console.WriteLine("RequestWeb()发生Web异常: {0}", e); + Logging.LogManager.Error(e); + return RequestWeb(url, referer, method, parameters, retry - 1); + } + catch (IOException e) + { + Console.WriteLine("RequestWeb()发生IO异常: {0}", e); + Logging.LogManager.Error(e); + return RequestWeb(url, referer, method, parameters, retry - 1); + } + catch (Exception e) + { + Console.WriteLine("RequestWeb()发生其他异常: {0}", e); + Logging.LogManager.Error(e); + return RequestWeb(url, referer, method, parameters, retry - 1); + } + } + } +} diff --git a/DownKyi.Core.Test/DownKyi.Core.Test.csproj b/DownKyi.Core.Test/DownKyi.Core.Test.csproj index 355c91e..71fb1f9 100644 --- a/DownKyi.Core.Test/DownKyi.Core.Test.csproj +++ b/DownKyi.Core.Test/DownKyi.Core.Test.csproj @@ -45,10 +45,14 @@ ..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + + @@ -60,6 +64,10 @@ + + {3107CD63-E257-455E-AE81-1FD3582B067A} + Brotli.Core + {4fde0364-f65b-4812-bfe8-34e886624fbd} DownKyi.Core diff --git a/DownKyi.Core.Test/packages.config b/DownKyi.Core.Test/packages.config index 7079f81..5cc7691 100644 --- a/DownKyi.Core.Test/packages.config +++ b/DownKyi.Core.Test/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/DownKyi.Core/Aria2cNet/AriaManager.cs b/DownKyi.Core/Aria2cNet/AriaManager.cs index 59fd6e3..c40e2cc 100644 --- a/DownKyi.Core/Aria2cNet/AriaManager.cs +++ b/DownKyi.Core/Aria2cNet/AriaManager.cs @@ -1,5 +1,6 @@ using DownKyi.Core.Aria2cNet.Client; using DownKyi.Core.Logging; +using System; using System.Threading; namespace DownKyi.Core.Aria2cNet @@ -32,10 +33,14 @@ namespace DownKyi.Core.Aria2cNet /// /// 获取gid下载项的状态 + /// + /// TODO + /// 对于下载的不同状态的返回值的测试 /// /// + /// /// - public DownloadStatus GetDownloadStatus(string gid) + public DownloadResult GetDownloadStatus(string gid, Action action = null) { string filePath = ""; while (true) @@ -48,7 +53,7 @@ namespace DownKyi.Core.Aria2cNet if (status.Result.Error.Message.Contains("is not found")) { OnDownloadFinish(false, null, gid, status.Result.Error.Message); - return DownloadStatus.ABORT; + return DownloadResult.ABORT; } } @@ -60,9 +65,16 @@ namespace DownKyi.Core.Aria2cNet long totalLength = long.Parse(status.Result.Result.TotalLength); long completedLength = long.Parse(status.Result.Result.CompletedLength); long speed = long.Parse(status.Result.Result.DownloadSpeed); + // 回调 OnTellStatus(totalLength, completedLength, speed, gid); + // 在外部执行 + if (action != null) + { + action.Invoke(); + } + if (status.Result.Result.Status == "complete") { break; @@ -86,14 +98,14 @@ namespace DownKyi.Core.Aria2cNet // 返回回调信息,退出函数 OnDownloadFinish(false, null, gid, status.Result.Result.ErrorMessage); - return DownloadStatus.FAILED; + return DownloadResult.FAILED; } // 降低CPU占用 Thread.Sleep(100); } OnDownloadFinish(true, filePath, gid, null); - return DownloadStatus.SUCCESS; + return DownloadResult.SUCCESS; } /// diff --git a/DownKyi.Core/Aria2cNet/DownloadStatus.cs b/DownKyi.Core/Aria2cNet/DownloadResult.cs similarity index 83% rename from DownKyi.Core/Aria2cNet/DownloadStatus.cs rename to DownKyi.Core/Aria2cNet/DownloadResult.cs index a8c1d73..17f6ab2 100644 --- a/DownKyi.Core/Aria2cNet/DownloadStatus.cs +++ b/DownKyi.Core/Aria2cNet/DownloadResult.cs @@ -3,7 +3,7 @@ /// /// 下载状态 /// - public enum DownloadStatus + public enum DownloadResult { SUCCESS = 1, FAILED, diff --git a/DownKyi.Core/BiliApi/Models/Json/SubRipText.cs b/DownKyi.Core/BiliApi/Models/Json/SubRipText.cs new file mode 100644 index 0000000..bfa9928 --- /dev/null +++ b/DownKyi.Core/BiliApi/Models/Json/SubRipText.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace DownKyi.Core.BiliApi.Models.Json +{ + public class SubRipText : BaseModel + { + [JsonProperty("lan")] + public string Lan { get; set; } + [JsonProperty("lan_doc")] + public string LanDoc { get; set; } + [JsonProperty("srtString")] + public string SrtString { get; set; } + } +} diff --git a/DownKyi.Core/BiliApi/Models/Json/Subtitle.cs b/DownKyi.Core/BiliApi/Models/Json/Subtitle.cs new file mode 100644 index 0000000..80f9c94 --- /dev/null +++ b/DownKyi.Core/BiliApi/Models/Json/Subtitle.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace DownKyi.Core.BiliApi.Models.Json +{ + public class Subtitle : BaseModel + { + [JsonProperty("from")] + public float From { get; set; } + [JsonProperty("to")] + public float To { get; set; } + [JsonProperty("location")] + public int Location { get; set; } + [JsonProperty("content")] + public string Content { get; set; } + } +} diff --git a/DownKyi.Core/BiliApi/Models/Json/SubtitleJson.cs b/DownKyi.Core/BiliApi/Models/Json/SubtitleJson.cs new file mode 100644 index 0000000..6437610 --- /dev/null +++ b/DownKyi.Core/BiliApi/Models/Json/SubtitleJson.cs @@ -0,0 +1,69 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace DownKyi.Core.BiliApi.Models.Json +{ + public class SubtitleJson : BaseModel + { + [JsonProperty("font_size")] + public float FontSize { get; set; } + [JsonProperty("font_color")] + public string FontColor { get; set; } + [JsonProperty("background_alpha")] + public float BackgroundAlpha { get; set; } + [JsonProperty("background_color")] + public string BackgroundColor { get; set; } + [JsonProperty("Stroke")] + public string Stroke { get; set; } + [JsonProperty("body")] + public List Body { get; set; } + + /// + /// srt格式字幕 + /// + /// + public string ToSubRip() + { + string subRip = string.Empty; + for (int i = 0; i < Body.Count; i++) + { + subRip += $"{i + 1}\n"; + subRip += $"{Second2hms(Body[i].From)} --> {Second2hms(Body[i].To)}\n"; + subRip += $"{Body[i].Content}\n"; + subRip += "\n"; + } + + return subRip; + } + + /// + /// 秒数转 时:分:秒 格式 + /// + /// + /// + private static string Second2hms(float seconds) + { + if (seconds < 0) + { + return "00:00:00,000"; + } + + int i = (int)Math.Floor(seconds / 1.0); + int dec = (int)(Math.Round(seconds % 1.0f, 2) * 100); + if (dec >= 100) + { + dec = 99; + } + + int min = (int)Math.Floor(i / 60.0); + int second = (int)(i % 60.0f); + + int hour = (int)Math.Floor(min / 60.0); + min = (int)Math.Floor(min % 60.0f); + + return $"{hour:D2}:{min:D2}:{second:D2},{dec:D3}"; + } + + } +} diff --git a/DownKyi.Core/BiliApi/VideoStream/Models/PlayerV2.cs b/DownKyi.Core/BiliApi/VideoStream/Models/PlayerV2.cs new file mode 100644 index 0000000..9ed962d --- /dev/null +++ b/DownKyi.Core/BiliApi/VideoStream/Models/PlayerV2.cs @@ -0,0 +1,34 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; + +namespace DownKyi.Core.BiliApi.VideoStream.Models +{ + // https://api.bilibili.com/x/player/v2?cid={cid}&aid={avid}&bvid={bvid} + public class PlayerV2Origin : BaseModel + { + //[JsonProperty("code")] + //public int Code { get; set; } + //[JsonProperty("message")] + //public string Message { get; set; } + //[JsonProperty("ttl")] + //public int Ttl { get; set; } + [JsonProperty("data")] + public PlayerV2 Data { get; set; } + } + + public class PlayerV2 : BaseModel + { + [JsonProperty("aid")] + public long Aid { get; set; } + [JsonProperty("bvid")] + public string Bvid { get; set; } + // allow_bp + // no_share + [JsonProperty("cid")] + public long Cid { get; set; } + // ... + [JsonProperty("subtitle")] + public SubtitleInfo Subtitle { get; set; } + } + +} diff --git a/DownKyi.Core/BiliApi/VideoStream/Models/Subtitle.cs b/DownKyi.Core/BiliApi/VideoStream/Models/Subtitle.cs new file mode 100644 index 0000000..a0861b1 --- /dev/null +++ b/DownKyi.Core/BiliApi/VideoStream/Models/Subtitle.cs @@ -0,0 +1,25 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; + +namespace DownKyi.Core.BiliApi.VideoStream.Models +{ + public class Subtitle : BaseModel + { + [JsonProperty("id")] + public long Id { get; set; } + [JsonProperty("lan")] + public string Lan { get; set; } + [JsonProperty("lan_doc")] + public string LanDoc { get; set; } + [JsonProperty("is_lock")] + public bool IsLock { get; set; } + [JsonProperty("author_mid")] + public long AuthorMid { get; set; } + [JsonProperty("subtitle_url")] + public string SubtitleUrl { get; set; } + [JsonProperty("type")] + public int Type { get; set; } + [JsonProperty("id_str")] + public string IdStr { get; set; } + } +} diff --git a/DownKyi.Core/BiliApi/VideoStream/Models/SubtitleInfo.cs b/DownKyi.Core/BiliApi/VideoStream/Models/SubtitleInfo.cs new file mode 100644 index 0000000..27b3078 --- /dev/null +++ b/DownKyi.Core/BiliApi/VideoStream/Models/SubtitleInfo.cs @@ -0,0 +1,18 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace DownKyi.Core.BiliApi.VideoStream.Models +{ + public class SubtitleInfo : BaseModel + { + [JsonProperty("allow_submit")] + public bool AllowSubmit { get; set; } + [JsonProperty("lan")] + public string Lan { get; set; } + [JsonProperty("lan_doc")] + public string LanDoc { get; set; } + [JsonProperty("subtitles")] + public List Subtitles { get; set; } + } +} diff --git a/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs b/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs index b41e84d..2508f24 100644 --- a/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs +++ b/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs @@ -1,13 +1,82 @@ -using DownKyi.Core.BiliApi.VideoStream.Models; +using DownKyi.Core.BiliApi.Models.Json; +using DownKyi.Core.BiliApi.VideoStream.Models; using DownKyi.Core.Logging; using Newtonsoft.Json; using System; +using System.Collections.Generic; namespace DownKyi.Core.BiliApi.VideoStream { public static class VideoStream { + /// + /// 获取播放器信息(web端) + /// + /// + /// + /// + /// + public static PlayerV2 PlayerV2(long avid, string bvid, long cid) + { + string url = $"https://api.bilibili.com/x/player/v2?cid={cid}&aid={avid}&bvid={bvid}"; + string referer = "https://www.bilibili.com"; + string response = WebClient.RequestWeb(url, referer); + + try + { + var playUrl = JsonConvert.DeserializeObject(response); + return playUrl?.Data; + } + catch (Exception e) + { + Utils.Debugging.Console.PrintLine("PlayerV2()发生异常: {0}", e); + LogManager.Error("PlayerV2()", e); + return null; + } + } + + /// + /// 获取所有字幕 + /// + /// + /// + /// + /// + public static List GetSubtitle(long avid, string bvid, long cid) + { + List subRipTexts = new List(); + + // 获取播放器信息 + var player = PlayerV2(avid, bvid, cid); + + foreach (var subtitle in player.Subtitle.Subtitles) + { + string referer = "https://www.bilibili.com"; + string response = WebClient.RequestWeb($"https:{subtitle.SubtitleUrl}", referer); + + try + { + var subtitleJson = JsonConvert.DeserializeObject(response); + if (subtitleJson == null) { continue; } + + subRipTexts.Add(new SubRipText + { + Lan = subtitle.Lan, + LanDoc = subtitle.LanDoc, + SrtString = subtitleJson.ToSubRip() + }); + } + catch (Exception e) + { + Utils.Debugging.Console.PrintLine("GetSubtitle()发生异常: {0}", e); + LogManager.Error("GetSubtitle()", e); + } + } + + return subRipTexts; + } + /// /// 获取普通视频的视频流 /// @@ -92,7 +161,7 @@ namespace DownKyi.Core.BiliApi.VideoStream catch (Exception e) { Utils.Debugging.Console.PrintLine("GetPlayUrl()发生异常: {0}", e); - LogManager.Error("GetPlayUrl", e); + LogManager.Error("GetPlayUrl()", e); return null; } } diff --git a/DownKyi.Core/DownKyi.Core.csproj b/DownKyi.Core/DownKyi.Core.csproj index 9ca3132..834a58f 100644 --- a/DownKyi.Core/DownKyi.Core.csproj +++ b/DownKyi.Core/DownKyi.Core.csproj @@ -111,7 +111,7 @@ - + @@ -160,13 +160,19 @@ + + + + + + @@ -203,6 +209,9 @@ + + + diff --git a/DownKyi.Core/FileName/FileName.cs b/DownKyi.Core/FileName/FileName.cs new file mode 100644 index 0000000..ea411d9 --- /dev/null +++ b/DownKyi.Core/FileName/FileName.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; + +namespace DownKyi.Core.FileName +{ + public class FileName + { + private readonly List nameParts; + private int order = -1; + private string mainTitle = "MAIN_TITLE"; + private string pageTitle = "PAGE_TITLE"; + private string videoZone = "VIDEO_ZONE"; + private string audioQuality = "AUDIO_QUALITY"; + private string videoQuality = "VIDEO_QUALITY"; + private string videoCodec = "VIDEO_CODEC"; + + private FileName(List nameParts) + { + this.nameParts = nameParts; + } + + public static FileName Builder(List nameParts) + { + return new FileName(nameParts); + } + + public FileName SetOrder(int order) + { + this.order = order; + return this; + } + + public FileName SetMainTitle(string mainTitle) + { + this.mainTitle = mainTitle; + return this; + } + + public FileName SetPageTitle(string pageTitle) + { + this.pageTitle = pageTitle; + return this; + } + + public FileName SetVideoZone(string videoZone) + { + this.videoZone = videoZone; + return this; + } + + public FileName SetAudioQuality(string audioQuality) + { + this.audioQuality = audioQuality; + return this; + } + + public FileName SetVideoQuality(string videoQuality) + { + this.videoQuality = videoQuality; + return this; + } + + public FileName SetVideoCodec(string videoCodec) + { + this.videoCodec = videoCodec; + return this; + } + + public string RelativePath() + { + string path = string.Empty; + + foreach (FileNamePart part in nameParts) + { + switch (part) + { + case FileNamePart.ORDER: + if (order != -1) + { + path += order; + } + else + { + path += "ORDER"; + } + break; + case FileNamePart.MAIN_TITLE: + path += mainTitle; + break; + case FileNamePart.PAGE_TITLE: + path += pageTitle; + break; + case FileNamePart.VIDEO_ZONE: + path += videoZone; + break; + case FileNamePart.AUDIO_QUALITY: + path += audioQuality; + break; + case FileNamePart.VIDEO_QUALITY: + path += videoQuality; + break; + case FileNamePart.VIDEO_CODEC: + path += videoCodec; + break; + } + + if (((int)part) >= 100) + { + path += HyphenSeparated.Hyphen[(int)part]; + } + } + + // 避免以斜杠开头和结尾的情况 + return path.TrimEnd('/').TrimStart('/'); + } + + } +} diff --git a/DownKyi.Core/FileName/FileNamePart.cs b/DownKyi.Core/FileName/FileNamePart.cs new file mode 100644 index 0000000..515377e --- /dev/null +++ b/DownKyi.Core/FileName/FileNamePart.cs @@ -0,0 +1,34 @@ +namespace DownKyi.Core.FileName +{ + public enum FileNamePart + { + // Video + ORDER = 1, + MAIN_TITLE, + PAGE_TITLE, + VIDEO_ZONE, + AUDIO_QUALITY, + VIDEO_QUALITY, + VIDEO_CODEC, + + // 斜杠 + SLASH = 100, + + // HyphenSeparated + UNDERSCORE = 101, // 下划线 + HYPHEN, // 连字符 + PLUS, // 加号 + COMMA, // 逗号 + PERIOD, // 句号 + AND, // and + NUMBER, // # + OPEN_PAREN, // 左圆括号 + CLOSE_PAREN, // 右圆括号 + OPEN_BRACKET, // 左方括号 + CLOSE_BRACKET, // 右方括号 + OPEN_BRACE, // 左花括号 + CLOSE_brace, // 右花括号 + BLANK, // 空白符 + + } +} diff --git a/DownKyi.Core/FileName/HyphenSeparated.cs b/DownKyi.Core/FileName/HyphenSeparated.cs new file mode 100644 index 0000000..e935d64 --- /dev/null +++ b/DownKyi.Core/FileName/HyphenSeparated.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace DownKyi.Core.FileName +{ + /// + /// 文件名的分隔符 + /// + public static class HyphenSeparated + { + public static Dictionary Hyphen = new Dictionary() + { + { 101, "/" }, + { 101, "_" }, + { 102, "-" }, + { 103, "+" }, + { 104, "," }, + { 105, "." }, + { 106, "&" }, + { 107, "#" }, + { 108, "(" }, + { 109, ")" }, + { 110, "[" }, + { 111, "]" }, + { 112, "{" }, + { 113, "}" }, + { 114, " " }, + }; + } +} diff --git a/DownKyi/App.xaml.cs b/DownKyi/App.xaml.cs index bfad80b..7ac3eaf 100644 --- a/DownKyi/App.xaml.cs +++ b/DownKyi/App.xaml.cs @@ -1,4 +1,6 @@ -using DownKyi.Utils; +using DownKyi.Models; +using DownKyi.Services.Download; +using DownKyi.Utils; using DownKyi.ViewModels; using DownKyi.ViewModels.Dialogs; using DownKyi.ViewModels.DownloadManager; @@ -11,6 +13,7 @@ using DownKyi.Views.Settings; using DownKyi.Views.Toolbox; using Prism.Ioc; using System; +using System.Collections.ObjectModel; using System.Windows; namespace DownKyi @@ -20,6 +23,12 @@ namespace DownKyi /// public partial class App { + public static ObservableCollection DownloadingList { get; set; } + public static ObservableCollection DownloadedList { get; set; } + + // 下载服务 + private IDownloadService downloadService; + protected override Window CreateShell() { // 设置主题 @@ -30,9 +39,26 @@ namespace DownKyi DictionaryResource.LoadLanguage("Default"); //DictionaryResource.LoadLanguage("en_US"); + // 初始化数据 + // TODO 从数据库读取 + DownloadingList = new ObservableCollection(); + DownloadedList = new ObservableCollection(); + + // 启动下载服务 + downloadService = new AriaDownloadService(DownloadingList, DownloadedList); + downloadService.Start(); + return Container.Resolve(); } + protected override void OnExit(ExitEventArgs e) + { + // 关闭下载服务 + downloadService.End(); + + base.OnExit(e); + } + protected override void RegisterTypes(IContainerRegistry containerRegistry) { // pages diff --git a/DownKyi/DownKyi.csproj b/DownKyi/DownKyi.csproj index d2b905c..e14c4f8 100644 --- a/DownKyi/DownKyi.csproj +++ b/DownKyi/DownKyi.csproj @@ -87,9 +87,13 @@ + + + + @@ -98,7 +102,10 @@ + + + diff --git a/DownKyi/Models/DownloadStatus.cs b/DownKyi/Models/DownloadStatus.cs new file mode 100644 index 0000000..a462076 --- /dev/null +++ b/DownKyi/Models/DownloadStatus.cs @@ -0,0 +1,12 @@ +namespace DownKyi.Models +{ + public enum DownloadStatus + { + NOT_STARTED, // 未开始,从未开始下载 + WAIT_FOR_DOWNLOAD, // 等待下载,下载过,但是启动本次下载周期未开始,如重启程序后未开始 + PAUSE, // 暂停 + DOWNLOADING, // 下载中 + DOWNLOAD_SUCCEED, // 下载成功 + DOWNLOAD_FAILED, // 下载失败 + } +} diff --git a/DownKyi/Models/DownloadedItem.cs b/DownKyi/Models/DownloadedItem.cs new file mode 100644 index 0000000..e13b160 --- /dev/null +++ b/DownKyi/Models/DownloadedItem.cs @@ -0,0 +1,13 @@ +using Prism.Mvvm; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DownKyi.Models +{ + public class DownloadedItem : BindableBase + { + } +} diff --git a/DownKyi/Models/DownloadingItem.cs b/DownKyi/Models/DownloadingItem.cs new file mode 100644 index 0000000..eacb533 --- /dev/null +++ b/DownKyi/Models/DownloadingItem.cs @@ -0,0 +1,91 @@ +using DownKyi.Core.BiliApi.VideoStream.Models; +using Prism.Mvvm; + +namespace DownKyi.Models +{ + public class DownloadingItem : BindableBase + { + public PlayUrl PlayUrl { get; set; } + + // 此条下载项的id + public string Uuid { get; } + + // 文件路径,不包含扩展名,所有内容均以此路径下载 + public string FilePath { get; set; } + + // Aria相关 + public string Gid { get; set; } + + // 视频类别 + public PlayStreamType PlayStreamType { get; set; } + + // 视频的id + public string Bvid { get; set; } + public long Avid { get; set; } + public long Cid { get; set; } + public long EpisodeId { get; set; } + + // 视频封面的url + public string CoverUrl { get; set; } + + // 视频序号 + private int order; + public int Order + { + get { return order; } + set { SetProperty(ref order, value); } + } + + // 视频主标题 + private string mainTitle; + public string MainTitle + { + get { return mainTitle; } + set { SetProperty(ref mainTitle, value); } + } + + // 视频标题 + private string name; + public string Name + { + get { return name; } + set { SetProperty(ref name, value); } + } + + // 音频编码 + public int AudioCodecId { get; set; } + private string audioCodecName; + public string AudioCodecName + { + get { return audioCodecName; } + set { SetProperty(ref audioCodecName, value); } + } + + // 视频编码 + public int VideoCodecId { get; set; } + private string videoCodecName; + public string VideoCodecName + { + get { return videoCodecName; } + set { SetProperty(ref videoCodecName, value); } + } + + // 下载内容(音频、视频、弹幕、字幕、封面) + private string downloadContent; + public string DownloadContent + { + get { return downloadContent; } + set { SetProperty(ref downloadContent, value); } + } + + // 下载状态 + public DownloadStatus DownloadStatus { get; set; } + private string downloadStatusTitle; + public string DownloadStatusTitle + { + get { return downloadStatusTitle; } + set { SetProperty(ref downloadStatusTitle, value); } + } + + } +} diff --git a/DownKyi/Models/PlayStreamType.cs b/DownKyi/Models/PlayStreamType.cs new file mode 100644 index 0000000..959d7b0 --- /dev/null +++ b/DownKyi/Models/PlayStreamType.cs @@ -0,0 +1,9 @@ +namespace DownKyi.Models +{ + public enum PlayStreamType + { + VIDEO = 1, // 普通视频 + BANGUMI, // 番剧、电影、电视剧等 + CHEESE, // 课程 + } +} diff --git a/DownKyi/Services/BangumiInfoService.cs b/DownKyi/Services/BangumiInfoService.cs index 17f0d87..e9e0e52 100644 --- a/DownKyi/Services/BangumiInfoService.cs +++ b/DownKyi/Services/BangumiInfoService.cs @@ -8,6 +8,7 @@ using DownKyi.Models; using DownKyi.Utils; using System; using System.Collections.Generic; +using System.Text.RegularExpressions; using System.Windows.Media.Imaging; namespace DownKyi.Services @@ -63,21 +64,28 @@ namespace DownKyi.Services string name; // 判断title是否为数字,如果是,则将share_copy作为name,否则将title作为name - if (int.TryParse(episode.Title, out int result)) - { - name = episode.ShareCopy; - } - else - { - if (episode.LongTitle != null && episode.LongTitle != "") - { - name = $"{episode.Title} {episode.LongTitle}"; - } - else - { - name = episode.Title; - } - } + //if (int.TryParse(episode.Title, out int result)) + //{ + // name = Regex.Replace(episode.ShareCopy, @"《.*?》", ""); + // //name = episode.ShareCopy; + //} + //else + //{ + // if (episode.LongTitle != null && episode.LongTitle != "") + // { + // name = $"{episode.Title} {episode.LongTitle}"; + // } + // else + // { + // name = episode.Title; + // } + //} + + // 将share_copy作为name,删除《》中的标题 + name = Regex.Replace(episode.ShareCopy, @"^《.*?》", ""); + + // 删除前后空白符 + name = name.Trim(); VideoPage page = new VideoPage { diff --git a/DownKyi/Services/Download/AriaDownloadService.cs b/DownKyi/Services/Download/AriaDownloadService.cs new file mode 100644 index 0000000..dab3dff --- /dev/null +++ b/DownKyi/Services/Download/AriaDownloadService.cs @@ -0,0 +1,405 @@ +using DownKyi.Core.Aria2cNet; +using DownKyi.Core.Aria2cNet.Client; +using DownKyi.Core.Aria2cNet.Client.Entity; +using DownKyi.Core.Aria2cNet.Server; +using DownKyi.Core.BiliApi.Login; +using DownKyi.Core.BiliApi.VideoStream; +using DownKyi.Core.Danmaku2Ass; +using DownKyi.Core.Logging; +using DownKyi.Core.Settings; +using DownKyi.Core.Storage; +using DownKyi.Models; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace DownKyi.Services.Download +{ + /// + /// 音视频采用Aria下载,其余采用WebClient下载 + /// + public class AriaDownloadService : DownloadService, IDownloadService + { + private CancellationTokenSource tokenSource; + + public AriaDownloadService(ObservableCollection downloadingList, ObservableCollection downloadedList) : base(downloadingList, downloadedList) + { + Tag = "AriaDownloadService"; + } + + public string DownloadAudio(DownloadingItem downloading) + { + // 如果没有Dash,返回null + if(downloading.PlayUrl.Dash == null) { return null; } + + // 如果audio列表没有内容,则返回null + if (downloading.PlayUrl.Dash.Audio == null) { return null; } + else if (downloading.PlayUrl.Dash.Audio.Count == 0) { return null; } + + + throw new NotImplementedException(); + } + + /// + /// 下载封面 + /// + /// + public void DownloadCover(DownloadingItem downloading) + { + // 查询、保存封面 + StorageCover storageCover = new StorageCover(); + string cover = storageCover.GetCover(downloading.Avid, downloading.Bvid, downloading.Cid, downloading.CoverUrl); + if (cover == null) + { + return; + } + + // 图片的扩展名 + string[] temp = downloading.CoverUrl.Split('.'); + string fileExtension = temp[temp.Length - 1]; + + // 图片的地址 + string coverPath = $"{StorageManager.GetCover()}/{cover}"; + + // 复制图片到指定位置 + try + { + File.Copy(coverPath, $"{downloading.FilePath}.{fileExtension}"); + } + catch (Exception e) + { + Core.Utils.Debugging.Console.PrintLine(e); + LogManager.Error(Tag, e); + } + } + + /// + /// 下载弹幕 + /// + /// + public void DownloadDanmaku(DownloadingItem downloading) + { + string title = $"{downloading.Name}"; + string assFile = $"{downloading.FilePath}.ass"; + + int screenWidth = SettingsManager.GetInstance().GetDanmakuScreenWidth(); + int screenHeight = SettingsManager.GetInstance().GetDanmakuScreenHeight(); + //if (SettingsManager.GetInstance().IsCustomDanmakuResolution() != AllowStatus.YES) + //{ + // if (downloadingEntity.Width > 0 && downloadingEntity.Height > 0) + // { + // screenWidth = downloadingEntity.Width; + // screenHeight = downloadingEntity.Height; + // } + //} + + // 字幕配置 + var subtitleConfig = new Config + { + Title = title, + ScreenWidth = screenWidth, + ScreenHeight = screenHeight, + FontName = SettingsManager.GetInstance().GetDanmakuFontName(), + BaseFontSize = SettingsManager.GetInstance().GetDanmakuFontSize(), + LineCount = SettingsManager.GetInstance().GetDanmakuLineCount(), + LayoutAlgorithm = SettingsManager.GetInstance().GetDanmakuLayoutAlgorithm().ToString("G").ToLower(), // async/sync + TuneDuration = 0, + DropOffset = 0, + BottomMargin = 0, + CustomOffset = 0 + }; + + Core.Danmaku2Ass.Bilibili.GetInstance() + .SetTopFilter(SettingsManager.GetInstance().GetDanmakuTopFilter() == AllowStatus.YES) + .SetBottomFilter(SettingsManager.GetInstance().GetDanmakuBottomFilter() == AllowStatus.YES) + .SetScrollFilter(SettingsManager.GetInstance().GetDanmakuScrollFilter() == AllowStatus.YES) + .Create(downloading.Avid, downloading.Cid, subtitleConfig, assFile); + } + + /// + /// 下载字幕 + /// + /// + public void DownloadSubtitle(DownloadingItem downloading) + { + var subRipTexts = VideoStream.GetSubtitle(downloading.Avid, downloading.Bvid, downloading.Cid); + foreach (var subRip in subRipTexts) + { + string srtFile = $"{downloading.FilePath}_{subRip.LanDoc}.srt"; + try + { + File.WriteAllText(srtFile, subRip.SrtString); + } + catch (Exception e) + { + Core.Utils.Debugging.Console.PrintLine("DownloadSubtitle()发生异常: {0}", e); + LogManager.Error("DownloadSubtitle()", e); + } + } + } + + public string DownloadVideo(DownloadingItem downloading) + { + throw new NotImplementedException(); + } + + /// + /// 停止下载服务 + /// + public void End() + { + // TODO something + + // 关闭Aria服务器 + CloseAriaServer(); + + // 结束任务 + tokenSource.Cancel(); + } + + public void MixedFlow(DownloadingItem downloading, string audioUid, string videoUid) + { + throw new NotImplementedException(); + } + + /// + /// 解析视频流的下载链接 + /// + /// + public void Parse(DownloadingItem downloading) + { + if (downloading.PlayUrl != null && downloading.DownloadStatus == DownloadStatus.NOT_STARTED) + { + return; + } + + switch (downloading.PlayStreamType) + { + case PlayStreamType.VIDEO: + downloading.PlayUrl = VideoStream.GetVideoPlayUrl(downloading.Avid, downloading.Bvid, downloading.Cid); + break; + case PlayStreamType.BANGUMI: + downloading.PlayUrl = VideoStream.GetBangumiPlayUrl(downloading.Avid, downloading.Bvid, downloading.Cid); + break; + case PlayStreamType.CHEESE: + downloading.PlayUrl = VideoStream.GetCheesePlayUrl(downloading.Avid, downloading.Bvid, downloading.Cid, downloading.EpisodeId); + break; + default: + break; + } + } + + /// + /// 启动下载服务 + /// + public async void Start() + { + // 启动Aria服务器 + StartAriaServer(); + + await Task.Run(DoWork, (tokenSource = new CancellationTokenSource()).Token); + } + + /// + /// 执行任务 + /// + private async void DoWork() + { + CancellationToken cancellationToken = tokenSource.Token; + while (true) + { + int maxDownloading = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(); + int countDownloading = 0; + bool isOutOfMaxDownloading = false; + foreach (DownloadingItem downloading in downloadingList) + { + // 对正在下载的元素计数 + if (downloading.DownloadStatus == DownloadStatus.DOWNLOADING) + { + countDownloading++; + } + + // 正在下载数量等于最大下载数量,退出本次循环 + if (countDownloading == maxDownloading) + { + break; + } + + // 正在下载数量大于最大下载数量 + if (countDownloading > maxDownloading) + { + isOutOfMaxDownloading = true; + } + + // 将超过下载数量的元素暂停 + if (isOutOfMaxDownloading) + { + if (downloading.DownloadStatus == DownloadStatus.DOWNLOADING) + { + downloading.DownloadStatus = DownloadStatus.PAUSE; + } + } + + await Task.Run(new Action(() => + { + downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + + // 依次下载音频、视频、弹幕、字幕、封面等内容 + Parse(downloading); + string audioUid = DownloadAudio(downloading); + string videoUid = DownloadVideo(downloading); + DownloadDanmaku(downloading); + DownloadSubtitle(downloading); + DownloadCover(downloading); + MixedFlow(downloading, audioUid, videoUid); + }), + (tokenSource = new CancellationTokenSource()).Token); + + } + + // 判断是否该结束线程,若为true,跳出while循环 + if (cancellationToken.IsCancellationRequested) + { + Core.Utils.Debugging.Console.PrintLine("AriaDownloadService: 下载服务结束,跳出while循环"); + LogManager.Debug(Tag, "下载服务结束"); + break; + } + + // 降低CPU占用 + Thread.Sleep(500); + } + } + + /// + /// 启动Aria服务器 + /// + private async void StartAriaServer() + { + List header = new List + { + $"Cookie: {LoginHelper.GetLoginInfoCookiesString()}", + $"Origin: https://www.bilibili.com", + $"Referer: https://www.bilibili.com", + $"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 91.0.4472.77 Safari / 537.36" + }; + + AriaConfig config = new AriaConfig() + { + ListenPort = SettingsManager.GetInstance().GetAriaListenPort(), + Token = "downkyi", + LogLevel = SettingsManager.GetInstance().GetAriaLogLevel(), + MaxConcurrentDownloads = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(), + MaxConnectionPerServer = 16, // 最大取16 + Split = SettingsManager.GetInstance().GetAriaSplit(), + //MaxTries = 5, + MinSplitSize = 10, // 10MB + MaxOverallDownloadLimit = SettingsManager.GetInstance().GetAriaMaxOverallDownloadLimit() * 1024L, // 输入的单位是KB/s,所以需要乘以1024 + MaxDownloadLimit = SettingsManager.GetInstance().GetAriaMaxDownloadLimit() * 1024L, // 输入的单位是KB/s,所以需要乘以1024 + MaxOverallUploadLimit = 0, + MaxUploadLimit = 0, + ContinueDownload = true, + FileAllocation = SettingsManager.GetInstance().GetAriaFileAllocation(), + Headers = header + }; + var task = await AriaServer.StartServerAsync(config); + if (task) { Console.WriteLine("Start ServerAsync Completed"); } + Console.WriteLine("Start ServerAsync end"); + + // 恢复所有下载 + //var ariaPause = await AriaClient.UnpauseAllAsync(); + //if (ariaPause != null) + //{ + // Core.Utils.Debugging.Console.PrintLine(ariaPause.ToString()); + //} + } + + /// + /// 关闭Aria服务器 + /// + private void CloseAriaServer() + { + new Thread(() => + { + // 暂停所有下载 + var ariaPause = AriaClient.PauseAllAsync(); + Core.Utils.Debugging.Console.PrintLine(ariaPause.ToString()); + + // 关闭服务器 + bool close = AriaServer.CloseServer(); + Core.Utils.Debugging.Console.PrintLine(close); + }) + { IsBackground = false } + .Start(); + } + + /// + /// 采用Aria下载文件 + /// + /// + /// + private DownloadResult DownloadByAria(DownloadingItem downloading, List urls, string localFileName) + { + string[] temp = downloading.FilePath.Split('/'); + string path = downloading.FilePath.Replace(temp[temp.Length - 1], ""); + + AriaSendOption option = new AriaSendOption + { + //HttpProxy = $"http://{Settings.GetAriaHttpProxy()}:{Settings.GetAriaHttpProxyListenPort()}", + Dir = path, + Out = localFileName + //Header = $"cookie: {Login.GetLoginInfoCookiesString()}\nreferer: https://www.bilibili.com", + //UseHead = "true", + //UserAgent = Utils.GetUserAgent() + }; + + // 如果设置了代理,则增加HttpProxy + if (SettingsManager.GetInstance().IsAriaHttpProxy() == AllowStatus.YES) + { + option.HttpProxy = $"http://{SettingsManager.GetInstance().GetAriaHttpProxy()}:{SettingsManager.GetInstance().GetAriaHttpProxyListenPort()}"; + } + + // 添加一个下载 + var ariaAddUri = AriaClient.AddUriAsync(urls, option); + if (ariaAddUri == null || ariaAddUri.Result == null || ariaAddUri.Result.Result == null) + { + return DownloadResult.FAILED; + } + + // 保存gid + string gid = ariaAddUri.Result.Result; + downloading.Gid = gid; + + // 管理下载 + AriaManager ariaManager = new AriaManager(); + ariaManager.TellStatus += AriaTellStatus; + ariaManager.DownloadFinish += AriaDownloadFinish; + return ariaManager.GetDownloadStatus(gid, new Action(() => + { + switch (downloading.DownloadStatus) + { + case DownloadStatus.PAUSE: + var ariaPause = AriaClient.PauseAsync(downloading.Gid); + // TODO + break; + case DownloadStatus.DOWNLOADING: + var ariaUnpause = AriaClient.UnpauseAsync(downloading.Gid); + // TODO + break; + } + })); + } + + private void AriaTellStatus(long totalLength, long completedLength, long speed, string gid) + { + throw new NotImplementedException(); + } + + private void AriaDownloadFinish(bool isSuccess, string downloadPath, string gid, string msg) + { + throw new NotImplementedException(); + } + } +} diff --git a/DownKyi/Services/Download/DownloadService.cs b/DownKyi/Services/Download/DownloadService.cs new file mode 100644 index 0000000..07288ee --- /dev/null +++ b/DownKyi/Services/Download/DownloadService.cs @@ -0,0 +1,25 @@ +using DownKyi.Models; +using System.Collections.ObjectModel; + +namespace DownKyi.Services.Download +{ + public class DownloadService + { + protected string Tag = "DownloadService"; + + protected ObservableCollection downloadingList; + protected ObservableCollection downloadedList; + + /// + /// 初始化 + /// + /// + /// + public DownloadService(ObservableCollection downloadingList, ObservableCollection downloadedList) + { + this.downloadingList = downloadingList; + this.downloadedList = downloadedList; + } + + } +} diff --git a/DownKyi/Services/Download/IDownloadService.cs b/DownKyi/Services/Download/IDownloadService.cs new file mode 100644 index 0000000..a3632b1 --- /dev/null +++ b/DownKyi/Services/Download/IDownloadService.cs @@ -0,0 +1,18 @@ +using DownKyi.Models; + +namespace DownKyi.Services.Download +{ + public interface IDownloadService + { + void Parse(DownloadingItem downloading); + string DownloadAudio(DownloadingItem downloading); + string DownloadVideo(DownloadingItem downloading); + void DownloadDanmaku(DownloadingItem downloading); + void DownloadSubtitle(DownloadingItem downloading); + void DownloadCover(DownloadingItem downloading); + void MixedFlow(DownloadingItem downloading, string audioUid, string videoUid); + + void Start(); + void End(); + } +} diff --git a/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs b/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs index 16f97fe..a305a98 100644 --- a/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs +++ b/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs @@ -1,8 +1,10 @@ -using Prism.Commands; +using DownKyi.Models; +using Prism.Commands; using Prism.Events; using Prism.Mvvm; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace DownKyi.ViewModels.DownloadManager @@ -11,9 +13,20 @@ namespace DownKyi.ViewModels.DownloadManager { public const string Tag = "PageDownloadManagerDownloadFinished"; - public ViewDownloadFinishedViewModel(IEventAggregator eventAggregator) : base(eventAggregator) + #region 页面属性申明 + + private ObservableCollection downloadedList; + public ObservableCollection DownloadedList { + get { return downloadedList; } + set { SetProperty(ref downloadedList, value); } + } + #endregion + + public ViewDownloadFinishedViewModel(IEventAggregator eventAggregator) : base(eventAggregator) + { + DownloadedList = App.DownloadedList; } } } diff --git a/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs b/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs index ba87677..d46ce89 100644 --- a/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs +++ b/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs @@ -1,8 +1,10 @@ -using Prism.Commands; +using DownKyi.Models; +using Prism.Commands; using Prism.Events; using Prism.Mvvm; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace DownKyi.ViewModels.DownloadManager @@ -11,9 +13,20 @@ namespace DownKyi.ViewModels.DownloadManager { public const string Tag = "PageDownloadManagerDownloading"; - public ViewDownloadingViewModel(IEventAggregator eventAggregator) : base(eventAggregator) + #region 页面属性申明 + + private ObservableCollection downloadingList; + public ObservableCollection DownloadingList { + get { return downloadingList; } + set { SetProperty(ref downloadingList, value); } + } + #endregion + + public ViewDownloadingViewModel(IEventAggregator eventAggregator) : base(eventAggregator) + { + DownloadingList = App.DownloadingList; } } } diff --git a/DownKyi/ViewModels/MainWindowViewModel.cs b/DownKyi/ViewModels/MainWindowViewModel.cs index 2f9f1fa..8066cf0 100644 --- a/DownKyi/ViewModels/MainWindowViewModel.cs +++ b/DownKyi/ViewModels/MainWindowViewModel.cs @@ -304,6 +304,7 @@ namespace DownKyi.ViewModels /// private void OnClipboardUpdated(object sender, EventArgs e) { + #region 执行第二遍时跳过 times += 1; DispatcherTimer timer = new DispatcherTimer { @@ -319,6 +320,8 @@ namespace DownKyi.ViewModels return; } + #endregion + AllowStatus isListenClipboard = SettingsManager.GetInstance().IsListenClipboard(); if (isListenClipboard != AllowStatus.YES) {