From 537d0df17bfb111149c983c9d3730a351c8d203a Mon Sep 17 00:00:00 2001 From: leiurayer <1432593898@qq.com> Date: Wed, 2 Feb 2022 10:19:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9Bbug?= =?UTF-8?q?=EF=BC=9B=E6=94=AF=E6=8C=81=E6=9D=9C=E6=AF=94=E5=85=A8=E6=99=AF?= =?UTF-8?q?=E5=A3=B0=E3=80=81=E6=9D=9C=E6=AF=94=E8=A7=86=E7=95=8C=EF=BC=8C?= =?UTF-8?q?=E6=9C=80=E9=AB=988K=E7=94=BB=E8=B4=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DownKyi.Core/Aria2cNet/AriaManager.cs | 20 +- .../Aria2cNet/Client/AriaClient.cs | 9 +- .../BiliApi/BiliUtils/Constant.cs | 3 + src/DownKyi.Core/BiliApi/Video/VideoInfo.cs | 1 + .../BiliApi/VideoStream/Models/PlayUrlDash.cs | 2 + .../VideoStream/Models/PlayUrlDashDolby.cs | 13 + .../VideoStream/Models/PlayUrlDashVideo.cs | 4 +- .../BiliApi/VideoStream/VideoStream.cs | 6 +- src/DownKyi.Core/BiliApi/WebClient.cs | 8 +- src/DownKyi.Core/DownKyi.Core.csproj | 1 + .../Storage/Database/Download/DownloadDb.cs | 7 +- src/DownKyi.Core/Storage/StorageCover.cs | 14 +- src/DownKyi.Core/Utils/Format.cs | 4 +- src/DownKyi.Core/Utils/ListHelper.cs | 16 + src/DownKyi/App.xaml.cs | 63 +-- src/DownKyi/DownKyi.csproj | 1 + src/DownKyi/Models/DownloadStatus.cs | 1 + .../Services/Download/AddToDownloadService.cs | 389 ++++++++++++++++++ .../Services/Download/AriaDownloadService.cs | 159 +++++-- .../Download/DownloadStorageService.cs | 16 + src/DownKyi/Services/FavoritesService.cs | 12 +- src/DownKyi/Services/SearchService.cs | 12 +- src/DownKyi/Services/Utils.cs | 37 +- src/DownKyi/Services/VideoInfoService.cs | 2 +- src/DownKyi/Utils/DialogUtils.cs | 6 +- .../DownloadManager/DownloadBaseItem.cs | 21 +- .../DownloadManager/DownloadingItem.cs | 10 +- .../ViewDownloadFinishedViewModel.cs | 21 +- .../ViewDownloadingViewModel.cs | 35 +- .../ViewModels/PageViewModels/Favorites.cs | 52 ++- .../ViewModels/PageViewModels/VideoPage.cs | 42 +- .../ViewModels/Settings/ViewVideoViewModel.cs | 1 + src/DownKyi/ViewModels/ViewIndexViewModel.cs | 11 - .../ViewPublicFavoritesViewModel.cs | 95 +++-- .../ViewModels/ViewVideoDetailViewModel.cs | 238 ++--------- .../DownloadManager/ViewDownloadFinished.xaml | 12 +- .../DownloadManager/ViewDownloading.xaml | 12 +- src/DownKyi/Views/ViewPublicFavorites.xaml | 33 +- src/DownKyi/Views/ViewVideoDetail.xaml | 12 +- 39 files changed, 980 insertions(+), 421 deletions(-) create mode 100644 src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashDolby.cs create mode 100644 src/DownKyi/Services/Download/AddToDownloadService.cs diff --git a/src/DownKyi.Core/Aria2cNet/AriaManager.cs b/src/DownKyi.Core/Aria2cNet/AriaManager.cs index c40e2cc..da2b555 100644 --- a/src/DownKyi.Core/Aria2cNet/AriaManager.cs +++ b/src/DownKyi.Core/Aria2cNet/AriaManager.cs @@ -1,7 +1,9 @@ using DownKyi.Core.Aria2cNet.Client; +using DownKyi.Core.Aria2cNet.Client.Entity; using DownKyi.Core.Logging; using System; using System.Threading; +using System.Threading.Tasks; namespace DownKyi.Core.Aria2cNet { @@ -45,7 +47,7 @@ namespace DownKyi.Core.Aria2cNet string filePath = ""; while (true) { - var status = AriaClient.TellStatus(gid); + Task status = AriaClient.TellStatus(gid); if (status == null || status.Result == null) { continue; } if (status.Result.Result == null && status.Result.Error != null) @@ -81,8 +83,11 @@ namespace DownKyi.Core.Aria2cNet } if (status.Result.Result.ErrorCode != null && status.Result.Result.ErrorCode != "0") { - Utils.Debugging.Console.PrintLine("ErrorMessage: " + status.Result.Result.ErrorMessage); - LogManager.Error("AriaManager", status.Result.Result.ErrorMessage); + if (status.Result != null) + { + Utils.Debugging.Console.PrintLine("ErrorMessage: " + status.Result.Result.ErrorMessage); + LogManager.Error("AriaManager", status.Result.Result.ErrorMessage); + } //// 如果返回状态码不是200,则继续 //if (status.Result.Result.ErrorMessage.Contains("The response status is not successful")) @@ -92,9 +97,12 @@ namespace DownKyi.Core.Aria2cNet //} // aira中删除记录 - var ariaRemove1 = AriaClient.RemoveDownloadResultAsync(gid); + Task ariaRemove1 = AriaClient.RemoveDownloadResultAsync(gid); Utils.Debugging.Console.PrintLine(ariaRemove1); - LogManager.Debug("AriaManager", ariaRemove1.Result.Result); + if (ariaRemove1.Result != null) + { + LogManager.Debug("AriaManager", ariaRemove1.Result.Result); + } // 返回回调信息,退出函数 OnDownloadFinish(false, null, gid, status.Result.Result.ErrorMessage); @@ -116,7 +124,7 @@ namespace DownKyi.Core.Aria2cNet while (true) { // 查询全局status - var globalStatus = await AriaClient.GetGlobalStatAsync(); + AriaGetGlobalStat globalStatus = await AriaClient.GetGlobalStatAsync(); if (globalStatus == null || globalStatus.Result == null) { continue; } long globalSpeed = long.Parse(globalStatus.Result.DownloadSpeed); diff --git a/src/DownKyi.Core/Aria2cNet/Client/AriaClient.cs b/src/DownKyi.Core/Aria2cNet/Client/AriaClient.cs index a9852cb..77083a4 100644 --- a/src/DownKyi.Core/Aria2cNet/Client/AriaClient.cs +++ b/src/DownKyi.Core/Aria2cNet/Client/AriaClient.cs @@ -1093,8 +1093,9 @@ namespace DownKyi.Core.Aria2cNet.Client } catch (WebException e) { - Utils.Debugging.Console.PrintLine("Request()发生Web异常: {0}", e); - LogManager.Error("AriaClient", e); + //Utils.Debugging.Console.PrintLine("Request()发生Web异常: {0}", e); + //LogManager.Error("AriaClient", e); + //return Request(url, parameters, retry - 1); string html = string.Empty; @@ -1108,8 +1109,8 @@ namespace DownKyi.Core.Aria2cNet.Client } } - Console.WriteLine($"本次请求使用的参数:{parameters}"); - Console.WriteLine($"返回的web数据:{html}"); + //Console.WriteLine($"本次请求使用的参数:{parameters}"); + //Console.WriteLine($"返回的web数据:{html}"); return html; } catch (IOException e) diff --git a/src/DownKyi.Core/BiliApi/BiliUtils/Constant.cs b/src/DownKyi.Core/BiliApi/BiliUtils/Constant.cs index 5e82b9c..070adfb 100644 --- a/src/DownKyi.Core/BiliApi/BiliUtils/Constant.cs +++ b/src/DownKyi.Core/BiliApi/BiliUtils/Constant.cs @@ -6,6 +6,8 @@ namespace DownKyi.Core.BiliApi.BiliUtils { private static readonly List resolutions = new List { + new Quality { Name = "超高清 8K", Id = 127 }, + new Quality { Name = "杜比视界", Id = 126 }, new Quality { Name = "HDR 真彩", Id = 125 }, new Quality { Name = "4K 超清", Id = 120 }, new Quality { Name = "1080P 60帧", Id = 116 }, @@ -22,6 +24,7 @@ namespace DownKyi.Core.BiliApi.BiliUtils new Quality { Name = "64K", Id = 30216 }, new Quality { Name = "132K", Id = 30232 }, new Quality { Name = "192K", Id = 30280 }, + new Quality { Name = "Dolby Atmos", Id = 30250 }, }; /// diff --git a/src/DownKyi.Core/BiliApi/Video/VideoInfo.cs b/src/DownKyi.Core/BiliApi/Video/VideoInfo.cs index 18762f3..ed65ced 100644 --- a/src/DownKyi.Core/BiliApi/Video/VideoInfo.cs +++ b/src/DownKyi.Core/BiliApi/Video/VideoInfo.cs @@ -16,6 +16,7 @@ namespace DownKyi.Core.BiliApi.Video /// public static VideoView VideoViewInfo(string bvid = null, long aid = -1) { + // https://api.bilibili.com/x/web-interface/view/detail?bvid=BV1Sg411F7cb&aid=969147110&need_operation_card=1&web_rm_repeat=1&need_elec=1&out_referer=https%3A%2F%2Fspace.bilibili.com%2F42018135%2Ffavlist%3Ffid%3D94341835 string baseUrl = "https://api.bilibili.com/x/web-interface/view"; string referer = "https://www.bilibili.com"; string url; diff --git a/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDash.cs b/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDash.cs index 7b65a05..ff545d1 100644 --- a/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDash.cs +++ b/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDash.cs @@ -16,5 +16,7 @@ namespace DownKyi.Core.BiliApi.VideoStream.Models public List Video { get; set; } [JsonProperty("audio")] public List Audio { get; set; } + [JsonProperty("dolby")] + public PlayUrlDashDolby Dolby { get; set; } } } diff --git a/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashDolby.cs b/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashDolby.cs new file mode 100644 index 0000000..9629c8d --- /dev/null +++ b/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashDolby.cs @@ -0,0 +1,13 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace DownKyi.Core.BiliApi.VideoStream.Models +{ + public class PlayUrlDashDolby : BaseModel + { + // type + [JsonProperty("audio")] + public List Audio { get; set; } + } +} diff --git a/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashVideo.cs b/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashVideo.cs index 9c5cf20..098f9bc 100644 --- a/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashVideo.cs +++ b/src/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashVideo.cs @@ -8,9 +8,9 @@ namespace DownKyi.Core.BiliApi.VideoStream.Models { [JsonProperty("id")] public int Id { get; set; } - [JsonProperty("baseUrl")] + [JsonProperty("base_url")] public string BaseUrl { get; set; } - [JsonProperty("backupUrl")] + [JsonProperty("backup_url")] public List BackupUrl { get; set; } // bandwidth [JsonProperty("mimeType")] diff --git a/src/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs b/src/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs index 8490aea..5764fcd 100644 --- a/src/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs +++ b/src/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs @@ -93,7 +93,7 @@ namespace DownKyi.Core.BiliApi.VideoStream /// public static PlayUrl GetVideoPlayUrl(long avid, string bvid, long cid, int quality = 125) { - string baseUrl = $"https://api.bilibili.com/x/player/playurl?cid={cid}&qn={quality}&fourk=1&fnver=0&fnval=80"; + string baseUrl = $"https://api.bilibili.com/x/player/playurl?cid={cid}&qn={quality}&fourk=1&fnver=0&fnval=4048"; string url; if (bvid != null) { url = $"{baseUrl}&bvid={bvid}"; } else if (avid > -1) { url = $"{baseUrl}&aid={avid}"; } @@ -112,7 +112,7 @@ namespace DownKyi.Core.BiliApi.VideoStream /// public static PlayUrl GetBangumiPlayUrl(long avid, string bvid, long cid, int quality = 125) { - string baseUrl = $"https://api.bilibili.com/pgc/player/web/playurl?cid={cid}&qn={quality}&fourk=1&fnver=0&fnval=80"; + string baseUrl = $"https://api.bilibili.com/pgc/player/web/playurl?cid={cid}&qn={quality}&fourk=1&fnver=0&fnval=4048"; string url; if (bvid != null) { url = $"{baseUrl}&bvid={bvid}"; } else if (avid > -1) { url = $"{baseUrl}&aid={avid}"; } @@ -131,7 +131,7 @@ namespace DownKyi.Core.BiliApi.VideoStream /// public static PlayUrl GetCheesePlayUrl(long avid, string bvid, long cid, long episodeId, int quality = 125) { - string baseUrl = $"https://api.bilibili.com/pugv/player/web/playurl?cid={cid}&qn={quality}&fourk=1&fnver=0&fnval=80"; + string baseUrl = $"https://api.bilibili.com/pugv/player/web/playurl?cid={cid}&qn={quality}&fourk=1&fnver=0&fnval=4048"; string url; if (bvid != null) { url = $"{baseUrl}&bvid={bvid}"; } else if (avid > -1) { url = $"{baseUrl}&aid={avid}"; } diff --git a/src/DownKyi.Core/BiliApi/WebClient.cs b/src/DownKyi.Core/BiliApi/WebClient.cs index 2fa1c28..7d3a676 100644 --- a/src/DownKyi.Core/BiliApi/WebClient.cs +++ b/src/DownKyi.Core/BiliApi/WebClient.cs @@ -49,7 +49,13 @@ namespace DownKyi.Core.BiliApi 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"; + + // MacOS Safari + string safari = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15"; + // Windows 10 Chrome + string chrome = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36"; + request.UserAgent = chrome; + //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"; diff --git a/src/DownKyi.Core/DownKyi.Core.csproj b/src/DownKyi.Core/DownKyi.Core.csproj index a5cb204..4fda17f 100644 --- a/src/DownKyi.Core/DownKyi.Core.csproj +++ b/src/DownKyi.Core/DownKyi.Core.csproj @@ -211,6 +211,7 @@ + diff --git a/src/DownKyi.Core/Storage/Database/Download/DownloadDb.cs b/src/DownKyi.Core/Storage/Database/Download/DownloadDb.cs index a467891..7d1b0c5 100644 --- a/src/DownKyi.Core/Storage/Database/Download/DownloadDb.cs +++ b/src/DownKyi.Core/Storage/Database/Download/DownloadDb.cs @@ -11,9 +11,14 @@ namespace DownKyi.Core.Storage.Database.Download public class DownloadDb { private const string key = "bdb8eb69-3698-4af9-b722-9312d0fba623"; - private readonly DbHelper dbHelper = new DbHelper(StorageManager.GetDownload(), key); protected string tableName = "download"; +#if DEBUG + private readonly DbHelper dbHelper = new DbHelper(StorageManager.GetDownload().Replace(".db","_debug.db")); +#else + private readonly DbHelper dbHelper = new DbHelper(StorageManager.GetDownload(), key); +#endif + /// /// 关闭数据库连接 /// diff --git a/src/DownKyi.Core/Storage/StorageCover.cs b/src/DownKyi.Core/Storage/StorageCover.cs index 49e58c5..28b7dfb 100644 --- a/src/DownKyi.Core/Storage/StorageCover.cs +++ b/src/DownKyi.Core/Storage/StorageCover.cs @@ -30,7 +30,7 @@ namespace DownKyi.Core.Storage { string header = GetCover(avid, bvid, cid, url); - return GetGetCoverThumbnail(header, width, height); + return GetCoverThumbnail(header, width, height); } /// @@ -40,10 +40,10 @@ namespace DownKyi.Core.Storage /// /// /// - public BitmapImage GetGetCoverThumbnail(string cover, int width, int height) + public BitmapImage GetCoverThumbnail(string cover, int width, int height) { - var bitmap = new Bitmap(cover); - var thumbnail = bitmap.GetThumbnailImage(width, height, null, IntPtr.Zero); + Bitmap bitmap = new Bitmap(cover); + Image thumbnail = bitmap.GetThumbnailImage(width, height, null, IntPtr.Zero); return StorageUtils.BitmapToBitmapImage(new Bitmap(thumbnail)); } @@ -69,7 +69,7 @@ namespace DownKyi.Core.Storage public string GetCover(long avid, string bvid, long cid, string url) { CoverDb coverDb = new CoverDb(); - var cover = coverDb.QueryByUrl(url); + Cover cover = coverDb.QueryByUrl(url); // 如果存在,直接返回 // 如果不存在,则先下载 @@ -195,7 +195,7 @@ namespace DownKyi.Core.Storage /// public bool IsLocal(CoverDb coverDb, string url) { - var cover = coverDb.QueryByUrl(url); + Cover cover = coverDb.QueryByUrl(url); return cover != null; } @@ -207,7 +207,7 @@ namespace DownKyi.Core.Storage /// public string LocalCover(CoverDb coverDb, string url) { - var cover = coverDb.QueryByUrl(url); + Cover cover = coverDb.QueryByUrl(url); return cover.Md5; } diff --git a/src/DownKyi.Core/Utils/Format.cs b/src/DownKyi.Core/Utils/Format.cs index c3a611c..910cafd 100644 --- a/src/DownKyi.Core/Utils/Format.cs +++ b/src/DownKyi.Core/Utils/Format.cs @@ -174,6 +174,7 @@ namespace DownKyi.Core.Utils public static string FormatFileName(string originName) { string destName = originName; + // Windows中不能作为文件名的字符 destName = destName.Replace("\\", " "); destName = destName.Replace("/", " "); @@ -197,7 +198,8 @@ namespace DownKyi.Core.Utils // 控制字符 destName = Regex.Replace(destName, @"\p{C}+", string.Empty); - return destName.Trim(); + // 移除前导和尾部的空白字符、dot符 + return destName.Trim().TrimStart('.').TrimEnd('.'); } } diff --git a/src/DownKyi.Core/Utils/ListHelper.cs b/src/DownKyi.Core/Utils/ListHelper.cs index bee195d..b7f8a28 100644 --- a/src/DownKyi.Core/Utils/ListHelper.cs +++ b/src/DownKyi.Core/Utils/ListHelper.cs @@ -1,15 +1,31 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; namespace DownKyi.Core.Utils { public static class ListHelper { + /// + /// 判断ObservableCollection中是否存在,不存在则添加 + /// + /// + /// + /// + public static void AddUnique(ObservableCollection list, T item) + { + if (!list.Contains(item)) + { + list.Add(item); + } + } + /// /// 判断List中是否存在,不存在则添加 /// /// /// + /// public static void AddUnique(List list, T item) { if (!list.Exists(t => t.Equals(item))) diff --git a/src/DownKyi/App.xaml.cs b/src/DownKyi/App.xaml.cs index ace8540..41e07d7 100644 --- a/src/DownKyi/App.xaml.cs +++ b/src/DownKyi/App.xaml.cs @@ -18,6 +18,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows; namespace DownKyi @@ -58,57 +59,63 @@ namespace DownKyi DownloadedList.AddRange(downloadedItems); // 下载列表发生变化时执行的任务 - DownloadingList.CollectionChanged += new NotifyCollectionChangedEventHandler((object sender, NotifyCollectionChangedEventArgs e) => + DownloadingList.CollectionChanged += new NotifyCollectionChangedEventHandler(async (object sender, NotifyCollectionChangedEventArgs e) => { - if (e.Action == NotifyCollectionChangedAction.Add) + await Task.Run(() => { - foreach (object item in e.NewItems) + if (e.Action == NotifyCollectionChangedAction.Add) { - if (item is DownloadingItem downloading) + foreach (object item in e.NewItems) { - //Console.WriteLine("DownloadingList添加"); - downloadStorageService.AddDownloading(downloading); + if (item is DownloadingItem downloading) + { + //Console.WriteLine("DownloadingList添加"); + downloadStorageService.AddDownloading(downloading); + } } } - } - if (e.Action == NotifyCollectionChangedAction.Remove) - { - foreach (object item in e.OldItems) + if (e.Action == NotifyCollectionChangedAction.Remove) { - if (item is DownloadingItem downloading) + foreach (object item in e.OldItems) { - //Console.WriteLine("DownloadingList移除"); - downloadStorageService.RemoveDownloading(downloading); + if (item is DownloadingItem downloading) + { + //Console.WriteLine("DownloadingList移除"); + downloadStorageService.RemoveDownloading(downloading); + } } } - } + }); }); // 下载完成列表发生变化时执行的任务 - DownloadedList.CollectionChanged += new NotifyCollectionChangedEventHandler((object sender, NotifyCollectionChangedEventArgs e) => + DownloadedList.CollectionChanged += new NotifyCollectionChangedEventHandler(async (object sender, NotifyCollectionChangedEventArgs e) => { - if (e.Action == NotifyCollectionChangedAction.Add) + await Task.Run(() => { - foreach (object item in e.NewItems) + if (e.Action == NotifyCollectionChangedAction.Add) { - if (item is DownloadedItem downloaded) + foreach (object item in e.NewItems) { - //Console.WriteLine("DownloadedList添加"); - downloadStorageService.AddDownloaded(downloaded); + if (item is DownloadedItem downloaded) + { + //Console.WriteLine("DownloadedList添加"); + downloadStorageService.AddDownloaded(downloaded); + } } } - } - if (e.Action == NotifyCollectionChangedAction.Remove) - { - foreach (object item in e.OldItems) + if (e.Action == NotifyCollectionChangedAction.Remove) { - if (item is DownloadedItem downloaded) + foreach (object item in e.OldItems) { - //Console.WriteLine("DownloadedList移除"); - downloadStorageService.RemoveDownloaded(downloaded); + if (item is DownloadedItem downloaded) + { + //Console.WriteLine("DownloadedList移除"); + downloadStorageService.RemoveDownloaded(downloaded); + } } } - } + }); }); // 启动下载服务 diff --git a/src/DownKyi/DownKyi.csproj b/src/DownKyi/DownKyi.csproj index d3f6769..405275c 100644 --- a/src/DownKyi/DownKyi.csproj +++ b/src/DownKyi/DownKyi.csproj @@ -92,6 +92,7 @@ + diff --git a/src/DownKyi/Models/DownloadStatus.cs b/src/DownKyi/Models/DownloadStatus.cs index 8aa0bd7..a9a0753 100644 --- a/src/DownKyi/Models/DownloadStatus.cs +++ b/src/DownKyi/Models/DownloadStatus.cs @@ -6,6 +6,7 @@ WAIT_FOR_DOWNLOAD, // 等待下载,下载过,但是启动本次下载周期未开始,如重启程序后未开始 PAUSE_STARTED, // 暂停启动下载 PAUSE, // 暂停 + //PAUSE_TO_WAIT, // 暂停后等待 DOWNLOADING, // 下载中 DOWNLOAD_SUCCEED, // 下载成功 DOWNLOAD_FAILED, // 下载失败 diff --git a/src/DownKyi/Services/Download/AddToDownloadService.cs b/src/DownKyi/Services/Download/AddToDownloadService.cs new file mode 100644 index 0000000..939dcf6 --- /dev/null +++ b/src/DownKyi/Services/Download/AddToDownloadService.cs @@ -0,0 +1,389 @@ +using DownKyi.Core.BiliApi.BiliUtils; +using DownKyi.Core.BiliApi.VideoStream; +using DownKyi.Core.BiliApi.Zone; +using DownKyi.Core.FileName; +using DownKyi.Core.Logging; +using DownKyi.Core.Settings; +using DownKyi.Core.Utils; +using DownKyi.Events; +using DownKyi.Models; +using DownKyi.Utils; +using DownKyi.ViewModels.Dialogs; +using DownKyi.ViewModels.DownloadManager; +using DownKyi.ViewModels.PageViewModels; +using Prism.Events; +using Prism.Services.Dialogs; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; + +namespace DownKyi.Services.Download +{ + /// + /// 添加到下载列表服务 + /// + public class AddToDownloadService + { + private readonly string Tag = "AddToDownloadService"; + private IInfoService videoInfoService; + private VideoInfoView videoInfoView; + private List videoSections; + + // 下载内容 + private bool downloadAudio = true; + private bool downloadVideo = true; + private bool downloadDanmaku = true; + private bool downloadSubtitle = true; + private bool downloadCover = true; + + /// + /// 添加下载 + /// + /// + public AddToDownloadService(PlayStreamType streamType) + { + switch (streamType) + { + case PlayStreamType.VIDEO: + videoInfoService = new VideoInfoService(null); + break; + case PlayStreamType.BANGUMI: + videoInfoService = new BangumiInfoService(null); + break; + case PlayStreamType.CHEESE: + videoInfoService = new CheeseInfoService(null); + break; + default: + break; + } + } + + /// + /// 添加下载 + /// + /// + /// + public AddToDownloadService(string id, PlayStreamType streamType) + { + switch (streamType) + { + case PlayStreamType.VIDEO: + videoInfoService = new VideoInfoService(id); + break; + case PlayStreamType.BANGUMI: + videoInfoService = new BangumiInfoService(id); + break; + case PlayStreamType.CHEESE: + videoInfoService = new CheeseInfoService(id); + break; + default: + break; + } + } + + public void SetVideoInfoService(IInfoService videoInfoService) + { + this.videoInfoService = videoInfoService; + } + + public void GetVideo(VideoInfoView videoInfoView, List videoSections) + { + this.videoInfoView = videoInfoView; + this.videoSections = videoSections; + } + + public void GetVideo() + { + videoInfoView = videoInfoService.GetVideoView(); + if (videoInfoView == null) + { + LogManager.Debug(Tag, "VideoInfoView is null."); + return; + } + + videoSections = videoInfoService.GetVideoSections(); + if (videoSections == null) + { + LogManager.Debug(Tag, "videoSections is not exist."); + + videoSections = new List + { + new VideoSection + { + Id = 0, + Title = "default", + IsSelected = true, + VideoPages = videoInfoService.GetVideoPages() + } + }; + } + + // 将所有视频设置为选中 + foreach (VideoSection section in videoSections) + { + foreach (var item in section.VideoPages) + { + item.IsSelected = true; + } + } + } + + /// + /// 解析视频流 + /// + /// + public void ParseVideo(IInfoService videoInfoService) + { + if (videoSections == null) { return; } + + foreach (VideoSection section in videoSections) + { + foreach (VideoPage page in section.VideoPages) + { + // 执行解析任务 + videoInfoService.GetVideoStream(page); + } + } + } + + /// + /// 选择文件夹和下载项 + /// + /// + public string SetDirectory(IDialogService dialogService) + { + // 选择的下载文件夹 + string directory = string.Empty; + + // 是否使用默认下载目录 + if (SettingsManager.GetInstance().IsUseSaveVideoRootPath() == AllowStatus.YES) + { + directory = SettingsManager.GetInstance().GetSaveVideoRootPath(); + } + else + { + // 打开文件夹选择器 + dialogService.ShowDialog(ViewDownloadSetterViewModel.Tag, null, result => + { + if (result.Result == ButtonResult.OK) + { + // 选择的下载文件夹 + directory = result.Parameters.GetValue("directory"); + + // 下载内容 + downloadAudio = result.Parameters.GetValue("downloadAudio"); + downloadVideo = result.Parameters.GetValue("downloadVideo"); + downloadDanmaku = result.Parameters.GetValue("downloadDanmaku"); + downloadSubtitle = result.Parameters.GetValue("downloadSubtitle"); + downloadCover = result.Parameters.GetValue("downloadCover"); + } + }); + } + + // 下载设置dialog中如果点击取消或者关闭窗口, + // 会返回空字符串, + // 这时直接退出 + if (directory == string.Empty) { return null; } + + // 文件夹不存在则创建 + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + return directory; + } + + public int AddToDownload(IEventAggregator eventAggregator, string directory) + { + if (directory == null || directory == string.Empty) { return -1; } + if (videoSections == null) { return -1; } + + // 视频计数 + int i = 0; + + // 添加到下载 + foreach (VideoSection section in videoSections) + { + foreach (VideoPage page in section.VideoPages) + { + // 只下载选中项,跳过未选中项 + if (!page.IsSelected) { continue; } + + // 没有解析的也跳过 + if (page.PlayUrl == null) { continue; } + + // 判断VideoQuality + int retry = 0; + while (page.VideoQuality == null && retry < 5) + { + // 执行解析任务 + videoInfoService.GetVideoStream(page); + retry++; + } + if (page.VideoQuality == null) { continue; } + + // 判断是否同一个视频,需要cid、画质、音质、视频编码都相同 + + // 如果存在正在下载列表,则跳过,并提示 + bool isDownloading = false; + foreach (DownloadingItem item in App.DownloadingList) + { + if (item.DownloadBase == null) { continue; } + + if (item.DownloadBase.Cid == page.Cid && item.Resolution.Id == page.VideoQuality.Quality && item.AudioCodec.Name == page.AudioQualityFormat && item.VideoCodecName == page.VideoQuality.SelectedVideoCodec) + { + eventAggregator.GetEvent().Publish($"{page.Name}{DictionaryResource.GetString("TipAlreadyToAddDownloading")}"); + isDownloading = true; + break; + } + } + if (isDownloading) { continue; } + + // 如果存在下载完成列表,弹出选择框是否再次下载 + bool isDownloaded = false; + foreach (DownloadedItem item in App.DownloadedList) + { + if (item.DownloadBase == null) { continue; } + + if (item.DownloadBase.Cid == page.Cid && item.Resolution.Id == page.VideoQuality.Quality && item.AudioCodec.Name == page.AudioQualityFormat && item.VideoCodecName == page.VideoQuality.SelectedVideoCodec) + { + eventAggregator.GetEvent().Publish($"{page.Name}{DictionaryResource.GetString("TipAlreadyToAddDownloaded")}"); + isDownloaded = true; + break; + } + } + if (isDownloaded) { continue; } + + // 视频分区 + int zoneId = -1; + List zoneList = VideoZone.Instance().GetZones(); + ZoneAttr zone = zoneList.Find(it => it.Id == videoInfoView.TypeId); + if (zone != null) + { + if (zone.ParentId == 0) + { + zoneId = zone.Id; + } + else + { + ZoneAttr zoneParent = zoneList.Find(it => it.Id == zone.ParentId); + if (zoneParent != null) + { + zoneId = zoneParent.Id; + } + } + } + + // 如果只有一个视频章节,则不在命名中出现 + string sectionName = string.Empty; + if (videoSections.Count > 1) + { + sectionName = section.Title; + } + + // 文件路径 + List fileNameParts = SettingsManager.GetInstance().GetFileNameParts(); + FileName fileName = FileName.Builder(fileNameParts) + .SetOrder(page.Order) + .SetSection(Format.FormatFileName(sectionName)) + .SetMainTitle(Format.FormatFileName(videoInfoView.Title)) + .SetPageTitle(Format.FormatFileName(page.Name)) + .SetVideoZone(videoInfoView.VideoZone.Split('>')[0]) + .SetAudioQuality(page.AudioQualityFormat) + .SetVideoQuality(page.VideoQuality == null ? "" : page.VideoQuality.QualityFormat) + .SetVideoCodec(page.VideoQuality == null ? "" : page.VideoQuality.SelectedVideoCodec.Contains("AVC") ? "AVC" : page.VideoQuality.SelectedVideoCodec.Contains("HEVC") ? "HEVC" : page.VideoQuality.SelectedVideoCodec.Contains("Dolby") ? "Dolby Vision" : ""); + string filePath = Path.Combine(directory, fileName.RelativePath()); + + // 视频类别 + PlayStreamType playStreamType; + switch (videoInfoView.TypeId) + { + case -10: + playStreamType = PlayStreamType.CHEESE; + break; + case 13: + case 23: + case 177: + case 167: + case 11: + playStreamType = PlayStreamType.BANGUMI; + break; + case 1: + case 3: + case 129: + case 4: + case 36: + case 188: + case 234: + case 223: + case 160: + case 211: + case 217: + case 119: + case 155: + case 202: + case 5: + case 181: + default: + playStreamType = PlayStreamType.VIDEO; + break; + } + + // 如果不存在,直接添加到下载列表 + DownloadBase downloadBase = new DownloadBase + { + Bvid = page.Bvid, + Avid = page.Avid, + Cid = page.Cid, + EpisodeId = page.EpisodeId, + CoverUrl = videoInfoView.CoverUrl, + PageCoverUrl = page.FirstFrame, + ZoneId = zoneId, + FilePath = filePath, + + Order = page.Order, + MainTitle = videoInfoView.Title, + Name = page.Name, + Duration = page.Duration, + VideoCodecName = page.VideoQuality.SelectedVideoCodec, + Resolution = new Quality { Name = page.VideoQuality.QualityFormat, Id = page.VideoQuality.Quality }, + AudioCodec = Constant.GetAudioQualities().FirstOrDefault(t => { return t.Name == page.AudioQualityFormat; }), + }; + Downloading downloading = new Downloading + { + PlayStreamType = playStreamType, + DownloadStatus = DownloadStatus.NOT_STARTED, + }; + + // 需要下载的内容 + downloadBase.NeedDownloadContent["downloadAudio"] = downloadAudio; + downloadBase.NeedDownloadContent["downloadVideo"] = downloadVideo; + downloadBase.NeedDownloadContent["downloadDanmaku"] = downloadDanmaku; + downloadBase.NeedDownloadContent["downloadSubtitle"] = downloadSubtitle; + downloadBase.NeedDownloadContent["downloadCover"] = downloadCover; + + DownloadingItem downloadingItem = new DownloadingItem + { + DownloadBase = downloadBase, + Downloading = downloading, + PlayUrl = page.PlayUrl, + }; + + // 添加到下载列表 + App.PropertyChangeAsync(new Action(() => + { + App.DownloadingList.Add(downloadingItem); + Thread.Sleep(10); + })); + i++; + } + } + + return i; + } + + } +} \ No newline at end of file diff --git a/src/DownKyi/Services/Download/AriaDownloadService.cs b/src/DownKyi/Services/Download/AriaDownloadService.cs index 3cf1351..fa9cea8 100644 --- a/src/DownKyi/Services/Download/AriaDownloadService.cs +++ b/src/DownKyi/Services/Download/AriaDownloadService.cs @@ -32,6 +32,9 @@ namespace DownKyi.Services.Download { private CancellationTokenSource tokenSource; + private int retry = 5; + private string nullMark = ""; + public AriaDownloadService(ObservableCollection downloadingList, ObservableCollection downloadedList) : base(downloadingList, downloadedList) { Tag = "AriaDownloadService"; @@ -68,6 +71,17 @@ namespace DownKyi.Services.Download } } + // 避免Dolby==null及其它未知情况,直接使用异常捕获 + try + { + // Dolby Atmos + if (downloading.AudioCodec.Id == 30250) + { + downloadAudio = downloading.PlayUrl.Dash.Dolby.Audio[0]; + } + } + catch (Exception) { } + return DownloadVideo(downloading, downloadAudio); } @@ -136,7 +150,11 @@ namespace DownKyi.Services.Download else { // 记录本次下载的文件 - downloading.Downloading.DownloadFiles.Add(key, fileName); + try + { + downloading.Downloading.DownloadFiles.Add(key, fileName); + } + catch (ArgumentException) { } } // 开始下载 @@ -147,9 +165,8 @@ namespace DownKyi.Services.Download return Path.Combine(path, fileName); case DownloadResult.FAILED: case DownloadResult.ABORT: - return null; default: - return null; + return nullMark; } } @@ -322,6 +339,11 @@ namespace DownKyi.Services.Download // 下载速度 downloading.SpeedDisplay = string.Empty; + if (videoUid == nullMark) + { + return null; + } + string finalFile = $"{downloading.DownloadBase.FilePath}.mp4"; if (videoUid == null) { @@ -361,6 +383,9 @@ namespace DownKyi.Services.Download if (downloading.PlayUrl != null && downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED) { + // 设置下载状态 + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + return; } @@ -453,28 +478,42 @@ namespace DownKyi.Services.Download { int maxDownloading = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(); int downloadingCount = 0; - foreach (DownloadingItem downloading in downloadingList) - { - if (downloading.Downloading.DownloadStatus == DownloadStatus.DOWNLOADING) - { - downloadingCount++; - } - } - foreach (DownloadingItem downloading in downloadingList) + try { - if (downloadingCount >= maxDownloading) + foreach (DownloadingItem downloading in downloadingList) { - break; + if (downloading.Downloading.DownloadStatus == DownloadStatus.DOWNLOADING) + { + downloadingCount++; + } } - // 开始下载 - if (downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED || downloading.Downloading.DownloadStatus == DownloadStatus.WAIT_FOR_DOWNLOAD) + foreach (DownloadingItem downloading in downloadingList) { - SingleDownload(downloading); - downloadingCount++; + if (downloadingCount >= maxDownloading) + { + break; + } + + // 开始下载 + if (downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED || downloading.Downloading.DownloadStatus == DownloadStatus.WAIT_FOR_DOWNLOAD) + { + SingleDownload(downloading); + downloadingCount++; + } } } + catch (InvalidOperationException e) + { + Core.Utils.Debugging.Console.PrintLine("Start DoWork()发生InvalidOperationException异常: {0}", e); + LogManager.Error("Start DoWork() InvalidOperationException", e); + } + catch (Exception e) + { + Core.Utils.Debugging.Console.PrintLine("Start DoWork()发生异常: {0}", e); + LogManager.Error("Start DoWork()", e); + } // 判断是否该结束线程,若为true,跳出while循环 if (cancellationToken.IsCancellationRequested) @@ -485,7 +524,7 @@ namespace DownKyi.Services.Download } // 降低CPU占用 - Thread.Sleep(200); + Thread.Sleep(500); } } @@ -515,6 +554,10 @@ namespace DownKyi.Services.Download // 解析并依次下载音频、视频、弹幕、字幕、封面等内容 Parse(downloading); + // 设置下载状态 + // 必须在解析之后设置为DOWNLOADING + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + // 暂停 Pause(downloading); // 是否存在 @@ -524,14 +567,24 @@ namespace DownKyi.Services.Download return; } - // 设置下载状态 - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; - string audioUid = null; // 如果需要下载音频 if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"]) { - audioUid = DownloadAudio(downloading); + //audioUid = DownloadAudio(downloading); + for (int i = 0; i < retry; i++) + { + audioUid = DownloadAudio(downloading); + if (audioUid != null && audioUid != "") + { + break; + } + } + } + if (audioUid == "") + { + DownloadFailed(downloading); + return; } // 暂停 @@ -547,7 +600,20 @@ namespace DownKyi.Services.Download // 如果需要下载视频 if (downloading.DownloadBase.NeedDownloadContent["downloadVideo"]) { - videoUid = DownloadVideo(downloading); + //videoUid = DownloadVideo(downloading); + for (int i = 0; i < retry; i++) + { + videoUid = DownloadVideo(downloading); + if (videoUid != null && videoUid != "") + { + break; + } + } + } + if (videoUid == "") + { + DownloadFailed(downloading); + return; } // 暂停 @@ -592,15 +658,16 @@ namespace DownKyi.Services.Download } string outputCover = null; + string outputPageCover = null; // 如果需要下载封面 if (downloading.DownloadBase.NeedDownloadContent["downloadCover"]) { string fileName = $"{downloading.DownloadBase.FilePath}.{GetImageExtension(downloading.DownloadBase.PageCoverUrl)}"; // page的封面 - outputCover = DownloadCover(downloading, downloading.DownloadBase.PageCoverUrl, fileName); + outputPageCover = DownloadCover(downloading, downloading.DownloadBase.PageCoverUrl, fileName); // 封面 - DownloadCover(downloading, downloading.DownloadBase.CoverUrl, $"{path}/Cover.{GetImageExtension(downloading.DownloadBase.CoverUrl)}"); + outputCover = DownloadCover(downloading, downloading.DownloadBase.CoverUrl, $"{path}/Cover.{GetImageExtension(downloading.DownloadBase.CoverUrl)}"); } // 暂停 @@ -685,7 +752,7 @@ namespace DownKyi.Services.Download bool isCover = true; if (downloading.DownloadBase.NeedDownloadContent["downloadCover"]) { - if (File.Exists(outputCover)) + if (File.Exists(outputCover) || File.Exists(outputPageCover)) { // 成功 isCover = true; @@ -698,19 +765,11 @@ namespace DownKyi.Services.Download if (!isMediaSuccess || !isDanmakuSuccess || !isSubtitleSuccess || !isCover) { - downloading.DownloadStatusTitle = DictionaryResource.GetString("DownloadFailed"); - downloading.DownloadContent = string.Empty; - downloading.DownloadingFileSize = string.Empty; - downloading.SpeedDisplay = string.Empty; - - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOAD_FAILED; - downloading.StartOrPause = ButtonIcon.Instance().Retry; - downloading.StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); + DownloadFailed(downloading); return; } // 下载完成后处理 - Downloaded downloaded = new Downloaded { MaxSpeedDisplay = Format.FormatSpeed(downloading.Downloading.MaxSpeed), @@ -737,6 +796,22 @@ namespace DownKyi.Services.Download })); } + /// + /// 下载失败后的处理 + /// + /// + private void DownloadFailed(DownloadingItem downloading) + { + downloading.DownloadStatusTitle = DictionaryResource.GetString("DownloadFailed"); + downloading.DownloadContent = string.Empty; + downloading.DownloadingFileSize = string.Empty; + downloading.SpeedDisplay = string.Empty; + + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOAD_FAILED; + downloading.StartOrPause = ButtonIcon.Instance().Retry; + downloading.StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); + } + /// /// 获取图片的扩展名 /// @@ -828,7 +903,7 @@ namespace DownKyi.Services.Download $"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" + $"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36" }; AriaConfig config = new AriaConfig() @@ -933,7 +1008,17 @@ namespace DownKyi.Services.Download private void AriaTellStatus(long totalLength, long completedLength, long speed, string gid) { // 当前的下载视频 - DownloadingItem video = downloadingList.FirstOrDefault(it => it.Downloading.Gid == gid); + DownloadingItem video = null; + try + { + video = downloadingList.FirstOrDefault(it => it.Downloading.Gid == gid); + } + catch (InvalidOperationException e) + { + Core.Utils.Debugging.Console.PrintLine("AriaTellStatus()发生异常: {0}", e); + LogManager.Error("AriaTellStatus()", e); + } + if (video == null) { return; } float percent = 0; diff --git a/src/DownKyi/Services/Download/DownloadStorageService.cs b/src/DownKyi/Services/Download/DownloadStorageService.cs index e13deec..d0bab9e 100644 --- a/src/DownKyi/Services/Download/DownloadStorageService.cs +++ b/src/DownKyi/Services/Download/DownloadStorageService.cs @@ -15,6 +15,8 @@ namespace DownKyi.Services.Download /// public void AddDownloading(DownloadingItem downloadingItem) { + if (downloadingItem == null || downloadingItem.DownloadBase == null) { return; } + AddDownloadBase(downloadingItem.DownloadBase); DownloadingDb downloadingDb = new DownloadingDb(); @@ -32,6 +34,8 @@ namespace DownKyi.Services.Download /// public void RemoveDownloading(DownloadingItem downloadingItem) { + if (downloadingItem == null || downloadingItem.DownloadBase == null) { return; } + RemoveDownloadBase(downloadingItem.DownloadBase.Uuid); DownloadingDb downloadingDb = new DownloadingDb(); @@ -75,6 +79,8 @@ namespace DownKyi.Services.Download /// public void UpdateDownloading(DownloadingItem downloadingItem) { + if (downloadingItem == null || downloadingItem.DownloadBase == null) { return; } + UpdateDownloadBase(downloadingItem.DownloadBase); DownloadingDb downloadingDb = new DownloadingDb(); @@ -92,6 +98,8 @@ namespace DownKyi.Services.Download /// public void AddDownloaded(DownloadedItem downloadedItem) { + if (downloadedItem == null || downloadedItem.DownloadBase == null) { return; } + AddDownloadBase(downloadedItem.DownloadBase); DownloadedDb downloadedDb = new DownloadedDb(); @@ -109,6 +117,8 @@ namespace DownKyi.Services.Download /// public void RemoveDownloaded(DownloadedItem downloadedItem) { + if (downloadedItem == null || downloadedItem.DownloadBase == null) { return; } + RemoveDownloadBase(downloadedItem.DownloadBase.Uuid); DownloadedDb downloadedDb = new DownloadedDb(); @@ -152,6 +162,8 @@ namespace DownKyi.Services.Download /// public void UpdateDownloaded(DownloadedItem downloadedItem) { + if (downloadedItem == null || downloadedItem.DownloadBase == null) { return; } + UpdateDownloadBase(downloadedItem.DownloadBase); DownloadedDb downloadedDb = new DownloadedDb(); @@ -169,6 +181,8 @@ namespace DownKyi.Services.Download /// private void AddDownloadBase(DownloadBase downloadBase) { + if (downloadBase == null) { return; } + DownloadBaseDb downloadBaseDb = new DownloadBaseDb(); object obj = downloadBaseDb.QueryById(downloadBase.Uuid); if (obj == null) @@ -208,6 +222,8 @@ namespace DownKyi.Services.Download /// private void UpdateDownloadBase(DownloadBase downloadBase) { + if (downloadBase == null) { return; } + DownloadBaseDb downloadBaseDb = new DownloadBaseDb(); downloadBaseDb.Update(downloadBase.Uuid, downloadBase); downloadBaseDb.Close(); diff --git a/src/DownKyi/Services/FavoritesService.cs b/src/DownKyi/Services/FavoritesService.cs index 2363556..958dfd7 100644 --- a/src/DownKyi/Services/FavoritesService.cs +++ b/src/DownKyi/Services/FavoritesService.cs @@ -26,7 +26,7 @@ namespace DownKyi.Services // 查询、保存封面 StorageCover storageCover = new StorageCover(); string coverUrl = favoritesMetaInfo.Cover; - string cover = storageCover.GetCover(favoritesMetaInfo.Id, "Favorites", favoritesMetaInfo.Mid, coverUrl); + BitmapImage cover = storageCover.GetCoverThumbnail(favoritesMetaInfo.Id, "Favorites", favoritesMetaInfo.Mid, coverUrl, 300, 188); // 获取用户头像 string upName; @@ -49,7 +49,7 @@ namespace DownKyi.Services { favorites.CoverUrl = coverUrl; - favorites.Cover = cover == null ? null : new BitmapImage(new Uri(cover)); + favorites.Cover = cover; favorites.Title = favoritesMetaInfo.Title; DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); // 当地时区 @@ -93,12 +93,14 @@ namespace DownKyi.Services int order = 0; foreach (var media in medias) { + if (media.Title == "已失效视频") { continue; } + order++; // 查询、保存封面 StorageCover storageCover = new StorageCover(); string coverUrl = media.Cover; - string cover = storageCover.GetCover(media.Id, media.Bvid, -1, coverUrl); + BitmapImage cover = storageCover.GetCoverThumbnail(media.Id, media.Bvid, -1, coverUrl, 200, 125); App.PropertyChangeAsync(new Action(() => { @@ -107,7 +109,7 @@ namespace DownKyi.Services Avid = media.Id, Bvid = media.Bvid, Order = order, - Cover = cover == null ? null : new BitmapImage(new Uri(cover)), + Cover = cover, Title = media.Title, PlayNumber = media.CntInfo != null ? Format.FormatNumber(media.CntInfo.Play) : "0", DanmakuNumber = media.CntInfo != null ? Format.FormatNumber(media.CntInfo.Danmaku) : "0", @@ -120,7 +122,7 @@ namespace DownKyi.Services if (!result.ToList().Exists(t => t.Avid == newMedia.Avid)) { result.Add(newMedia); - Thread.Sleep(50); + Thread.Sleep(10); } })); } diff --git a/src/DownKyi/Services/SearchService.cs b/src/DownKyi/Services/SearchService.cs index f1426da..8ead7bd 100644 --- a/src/DownKyi/Services/SearchService.cs +++ b/src/DownKyi/Services/SearchService.cs @@ -8,7 +8,17 @@ namespace DownKyi.Services public class SearchService { /// - /// 解析支持的输入 + /// 解析支持的输入, + /// 支持的格式有: + /// av号:av170001, AV170001, https://www.bilibili.com/video/av170001 + /// BV号:BV17x411w7KC, https://www.bilibili.com/video/BV17x411w7KC + /// 番剧(电影、电视剧)ss号:ss32982, SS32982, https://www.bilibili.com/bangumi/play/ss32982 + /// 番剧(电影、电视剧)ep号:ep317925, EP317925, https://www.bilibili.com/bangumi/play/ep317925 + /// 番剧(电影、电视剧)md号:md28228367, MD28228367, https://www.bilibili.com/bangumi/media/md28228367 + /// 课程ss号:https://www.bilibili.com/cheese/play/ss205 + /// 课程ep号:https://www.bilibili.com/cheese/play/ep3489 + /// 收藏夹:ml1329019876, ML1329019876, https://www.bilibili.com/medialist/detail/ml1329019876 + /// 用户空间:uid928123, UID928123, uid:928123, UID:928123, https://space.bilibili.com/928123 /// /// /// diff --git a/src/DownKyi/Services/Utils.cs b/src/DownKyi/Services/Utils.cs index b0f55b3..25f9725 100644 --- a/src/DownKyi/Services/Utils.cs +++ b/src/DownKyi/Services/Utils.cs @@ -5,6 +5,7 @@ using DownKyi.Core.Settings.Models; using DownKyi.Core.Utils; using DownKyi.ViewModels.PageViewModels; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace DownKyi.Services @@ -94,10 +95,16 @@ namespace DownKyi.Services /// /// /// - private static List GetAudioQualityFormatList(PlayUrl playUrl, int defaultAudioQuality) + private static ObservableCollection GetAudioQualityFormatList(PlayUrl playUrl, int defaultAudioQuality) { List audioQualityFormatList = new List(); - foreach (var audio in playUrl.Dash.Audio) + + if (playUrl.Dash.Audio == null) + { + return new ObservableCollection(); + } + + foreach (PlayUrlDashVideo audio in playUrl.Dash.Audio) { // 音质id大于设置画质时,跳过 if (audio.Id > defaultAudioQuality) { continue; } @@ -112,7 +119,7 @@ namespace DownKyi.Services audioQualityFormatList.Sort(new StringLogicalComparer()); audioQualityFormatList.Reverse(); - return audioQualityFormatList; + return new ObservableCollection(audioQualityFormatList); } /// @@ -126,7 +133,13 @@ namespace DownKyi.Services private static List GetVideoQualityList(PlayUrl playUrl, UserInfoSettings userInfo, int defaultQuality, VideoCodecs videoCodecs) { List videoQualityList = new List(); - foreach (var video in playUrl.Dash.Video) + + if (playUrl.Dash.Video == null) + { + return videoQualityList; + } + + foreach (PlayUrlDashVideo video in playUrl.Dash.Video) { // 画质id大于设置画质时,跳过 if (video.Id > defaultQuality) { continue; } @@ -139,7 +152,7 @@ namespace DownKyi.Services } string qualityFormat = string.Empty; - var selectedQuality = playUrl.SupportFormats.FirstOrDefault(t => t.Quality == video.Id); + PlayUrlSupportFormat selectedQuality = playUrl.SupportFormats.FirstOrDefault(t => t.Quality == video.Id); if (selectedQuality != null) { qualityFormat = selectedQuality.NewDescription; @@ -148,13 +161,13 @@ namespace DownKyi.Services // 寻找是否已存在这个画质 // 不存在则添加,存在则修改 string codecName = GetVideoCodecName(video.Codecs); - var videoQualityExist = videoQualityList.FirstOrDefault(t => t.Quality == video.Id); + VideoQuality videoQualityExist = videoQualityList.FirstOrDefault(t => t.Quality == video.Id); if (videoQualityExist == null) { - var videoCodecList = new List(); + List videoCodecList = new List(); ListHelper.AddUnique(videoCodecList, codecName); - var videoQuality = new VideoQuality() + VideoQuality videoQuality = new VideoQuality() { Quality = video.Id, QualityFormat = qualityFormat, @@ -171,7 +184,7 @@ namespace DownKyi.Services } // 设置选中的视频编码 - var selectedVideoQuality = videoQualityList.FirstOrDefault(t => t.Quality == video.Id); + VideoQuality selectedVideoQuality = videoQualityList.FirstOrDefault(t => t.Quality == video.Id); switch (videoCodecs) { case VideoCodecs.AVC: @@ -186,6 +199,10 @@ namespace DownKyi.Services videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].SelectedVideoCodec = "H.265/HEVC"; } break; + case VideoCodecs.NONE: + break; + default: + break; } if (videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].VideoCodecList.Count == 1) @@ -205,7 +222,7 @@ namespace DownKyi.Services /// internal static string GetVideoCodecName(string origin) { - return origin.Contains("avc") ? "H.264/AVC" : origin.Contains("hev") ? "H.265/HEVC" : ""; + return origin.Contains("avc") ? "H.264/AVC" : origin.Contains("hev") ? "H.265/HEVC" : origin.Contains("dvh") || origin.Contains("hvc") ? "Dolby Vision" : ""; } } diff --git a/src/DownKyi/Services/VideoInfoService.cs b/src/DownKyi/Services/VideoInfoService.cs index 5659cca..3bec45f 100644 --- a/src/DownKyi/Services/VideoInfoService.cs +++ b/src/DownKyi/Services/VideoInfoService.cs @@ -102,7 +102,7 @@ namespace DownKyi.Services List videoSections = new List(); - foreach (var section in videoView.UgcSeason.Sections) + foreach (UgcSection section in videoView.UgcSeason.Sections) { List pages = new List(); int order = 0; diff --git a/src/DownKyi/Utils/DialogUtils.cs b/src/DownKyi/Utils/DialogUtils.cs index 2376ae7..41b08d7 100644 --- a/src/DownKyi/Utils/DialogUtils.cs +++ b/src/DownKyi/Utils/DialogUtils.cs @@ -34,11 +34,7 @@ namespace DownKyi.Utils Filter = "mp4 (*.mp4)|*.mp4" }; var showDialog = dialog.ShowDialog(); - if (showDialog == true) - { - return dialog.FileName; - } - else { return ""; } + return showDialog == true ? dialog.FileName : ""; } } diff --git a/src/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs b/src/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs index 454f94b..4586b8a 100644 --- a/src/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs +++ b/src/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs @@ -18,7 +18,10 @@ namespace DownKyi.ViewModels.DownloadManager { downloadBase = value; - ZoneImage = (DrawingImage)Application.Current.Resources[VideoZoneIcon.Instance().GetZoneImageKey(DownloadBase.ZoneId)]; + if (value != null) + { + ZoneImage = (DrawingImage)Application.Current.Resources[VideoZoneIcon.Instance().GetZoneImageKey(DownloadBase.ZoneId)]; + } } } @@ -33,7 +36,7 @@ namespace DownKyi.ViewModels.DownloadManager // 视频序号 public int Order { - get => DownloadBase.Order; + get => DownloadBase == null ? 0 : DownloadBase.Order; set { DownloadBase.Order = value; @@ -44,7 +47,7 @@ namespace DownKyi.ViewModels.DownloadManager // 视频主标题 public string MainTitle { - get => DownloadBase.MainTitle; + get => DownloadBase == null ? "" : DownloadBase.MainTitle; set { DownloadBase.MainTitle = value; @@ -55,7 +58,7 @@ namespace DownKyi.ViewModels.DownloadManager // 视频标题 public string Name { - get => DownloadBase.Name; + get => DownloadBase == null ? "" : DownloadBase.Name; set { DownloadBase.Name = value; @@ -66,7 +69,7 @@ namespace DownKyi.ViewModels.DownloadManager // 时长 public string Duration { - get => DownloadBase.Duration; + get => DownloadBase == null ? "" : DownloadBase.Duration; set { DownloadBase.Duration = value; @@ -77,7 +80,7 @@ namespace DownKyi.ViewModels.DownloadManager // 视频编码名称,AVC、HEVC public string VideoCodecName { - get => DownloadBase.VideoCodecName; + get => DownloadBase == null ? "" : DownloadBase.VideoCodecName; set { DownloadBase.VideoCodecName = value; @@ -88,7 +91,7 @@ namespace DownKyi.ViewModels.DownloadManager // 视频画质 public Quality Resolution { - get => DownloadBase.Resolution; + get => DownloadBase == null ? null : DownloadBase.Resolution; set { DownloadBase.Resolution = value; @@ -99,7 +102,7 @@ namespace DownKyi.ViewModels.DownloadManager // 音频编码 public Quality AudioCodec { - get => DownloadBase.AudioCodec; + get => DownloadBase == null ? null : DownloadBase.AudioCodec; set { DownloadBase.AudioCodec = value; @@ -110,7 +113,7 @@ namespace DownKyi.ViewModels.DownloadManager // 文件大小 public string FileSize { - get => DownloadBase.FileSize; + get => DownloadBase == null ? "" : DownloadBase.FileSize; set { DownloadBase.FileSize = value; diff --git a/src/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs b/src/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs index 899df6f..dc51226 100644 --- a/src/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs +++ b/src/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs @@ -23,7 +23,7 @@ namespace DownKyi.ViewModels.DownloadManager private Downloading downloading; public Downloading Downloading { - get { return downloading; } + get => downloading; set { downloading = value; @@ -164,21 +164,26 @@ namespace DownKyi.ViewModels.DownloadManager case DownloadStatus.NOT_STARTED: case DownloadStatus.WAIT_FOR_DOWNLOAD: Downloading.DownloadStatus = DownloadStatus.PAUSE_STARTED; + DownloadStatusTitle = DictionaryResource.GetString("Pausing"); StartOrPause = ButtonIcon.Instance().Start; StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); break; case DownloadStatus.PAUSE_STARTED: Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + DownloadStatusTitle = DictionaryResource.GetString("Waiting"); StartOrPause = ButtonIcon.Instance().Pause; StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); break; case DownloadStatus.PAUSE: - Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + DownloadStatusTitle = DictionaryResource.GetString("Waiting"); StartOrPause = ButtonIcon.Instance().Pause; StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); break; + //case DownloadStatus.PAUSE_TO_WAIT: case DownloadStatus.DOWNLOADING: Downloading.DownloadStatus = DownloadStatus.PAUSE; + DownloadStatusTitle = DictionaryResource.GetString("Pausing"); StartOrPause = ButtonIcon.Instance().Start; StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); break; @@ -188,6 +193,7 @@ namespace DownKyi.ViewModels.DownloadManager break; case DownloadStatus.DOWNLOAD_FAILED: Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + DownloadStatusTitle = DictionaryResource.GetString("Waiting"); StartOrPause = ButtonIcon.Instance().Pause; StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); break; diff --git a/src/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs b/src/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs index 614e524..c4d94a1 100644 --- a/src/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs +++ b/src/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs @@ -1,7 +1,11 @@ using DownKyi.Core.Settings; using Prism.Commands; using Prism.Events; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; namespace DownKyi.ViewModels.DownloadManager { @@ -89,9 +93,22 @@ namespace DownKyi.ViewModels.DownloadManager /// /// 清空下载完成列表事件 /// - private void ExecuteClearAllDownloadedCommand() + private async void ExecuteClearAllDownloadedCommand() { - DownloadedList.Clear(); + // 使用Clear()不能触发NotifyCollectionChangedAction.Remove事件 + // 因此遍历删除 + // DownloadingList中元素被删除后不能继续遍历 + await Task.Run(() => + { + List list = DownloadedList.ToList(); + foreach (DownloadedItem item in list) + { + App.PropertyChangeAsync(new Action(() => + { + App.DownloadedList.Remove(item); + })); + } + }); } #endregion diff --git a/src/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs b/src/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs index 1985f53..a30c30b 100644 --- a/src/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs +++ b/src/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs @@ -3,7 +3,11 @@ using DownKyi.Models; using DownKyi.Utils; using Prism.Commands; using Prism.Events; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; namespace DownKyi.ViewModels.DownloadManager { @@ -45,7 +49,8 @@ namespace DownKyi.ViewModels.DownloadManager { case DownloadStatus.NOT_STARTED: case DownloadStatus.WAIT_FOR_DOWNLOAD: - downloading.Downloading.DownloadStatus = DownloadStatus.PAUSE_STARTED; + downloading.Downloading.DownloadStatus = DownloadStatus.PAUSE; + downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing"); downloading.StartOrPause = ButtonIcon.Instance().Start; downloading.StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); break; @@ -53,8 +58,10 @@ namespace DownKyi.ViewModels.DownloadManager break; case DownloadStatus.PAUSE: break; + //case DownloadStatus.PAUSE_TO_WAIT: case DownloadStatus.DOWNLOADING: downloading.Downloading.DownloadStatus = DownloadStatus.PAUSE; + downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing"); downloading.StartOrPause = ButtonIcon.Instance().Start; downloading.StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); break; @@ -85,13 +92,19 @@ namespace DownKyi.ViewModels.DownloadManager { case DownloadStatus.NOT_STARTED: case DownloadStatus.WAIT_FOR_DOWNLOAD: + downloading.Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + downloading.DownloadStatusTitle = DictionaryResource.GetString("Waiting"); break; case DownloadStatus.PAUSE_STARTED: downloading.Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + downloading.DownloadStatusTitle = DictionaryResource.GetString("Waiting"); break; case DownloadStatus.PAUSE: - downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + downloading.Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + downloading.DownloadStatusTitle = DictionaryResource.GetString("Waiting"); break; + //case DownloadStatus.PAUSE_TO_WAIT: + // break; case DownloadStatus.DOWNLOADING: break; case DownloadStatus.DOWNLOAD_SUCCEED: @@ -100,6 +113,7 @@ namespace DownKyi.ViewModels.DownloadManager break; case DownloadStatus.DOWNLOAD_FAILED: downloading.Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + downloading.DownloadStatusTitle = DictionaryResource.GetString("Waiting"); break; default: break; @@ -117,9 +131,22 @@ namespace DownKyi.ViewModels.DownloadManager /// /// 删除所有下载事件 /// - private void ExecuteDeleteAllDownloadingCommand() + private async void ExecuteDeleteAllDownloadingCommand() { - DownloadingList.Clear(); + // 使用Clear()不能触发NotifyCollectionChangedAction.Remove事件 + // 因此遍历删除 + // DownloadingList中元素被删除后不能继续遍历 + await Task.Run(() => + { + List list = DownloadingList.ToList(); + foreach (DownloadingItem item in list) + { + App.PropertyChangeAsync(new Action(() => + { + App.DownloadingList.Remove(item); + })); + } + }); } #endregion diff --git a/src/DownKyi/ViewModels/PageViewModels/Favorites.cs b/src/DownKyi/ViewModels/PageViewModels/Favorites.cs index 96f1db8..ae77f79 100644 --- a/src/DownKyi/ViewModels/PageViewModels/Favorites.cs +++ b/src/DownKyi/ViewModels/PageViewModels/Favorites.cs @@ -1,4 +1,6 @@ -using Prism.Mvvm; +using DownKyi.Images; +using DownKyi.Utils; +using Prism.Mvvm; using System.Windows.Media.Imaging; namespace DownKyi.ViewModels.PageViewModels @@ -57,6 +59,34 @@ namespace DownKyi.ViewModels.PageViewModels set => SetProperty(ref shareNumber, value); } + private VectorImage play; + public VectorImage Play + { + get => play; + set => SetProperty(ref play, value); + } + + private VectorImage like; + public VectorImage Like + { + get => like; + set => SetProperty(ref like, value); + } + + private VectorImage favorite; + public VectorImage Favorite + { + get => favorite; + set => SetProperty(ref favorite, value); + } + + private VectorImage share; + public VectorImage Share + { + get => share; + set => SetProperty(ref share, value); + } + private string description; public string Description { @@ -84,5 +114,25 @@ namespace DownKyi.ViewModels.PageViewModels get => upHeader; set => SetProperty(ref upHeader, value); } + + public Favorites() + { + #region 属性初始化 + + Play = NormalIcon.Instance().Play; + Play.Fill = DictionaryResource.GetColor("ColorTextGrey2"); + + Like = NormalIcon.Instance().Like; + Like.Fill = DictionaryResource.GetColor("ColorTextGrey2"); + + Favorite = NormalIcon.Instance().Favorite; + Favorite.Fill = DictionaryResource.GetColor("ColorTextGrey2"); + + Share = NormalIcon.Instance().Share; + Share.Fill = DictionaryResource.GetColor("ColorTextGrey2"); + + #endregion + } + } } diff --git a/src/DownKyi/ViewModels/PageViewModels/VideoPage.cs b/src/DownKyi/ViewModels/PageViewModels/VideoPage.cs index 6d7704e..08b9373 100644 --- a/src/DownKyi/ViewModels/PageViewModels/VideoPage.cs +++ b/src/DownKyi/ViewModels/PageViewModels/VideoPage.cs @@ -1,6 +1,10 @@ -using DownKyi.Core.BiliApi.VideoStream.Models; +using DownKyi.Core.BiliApi.BiliUtils; +using DownKyi.Core.BiliApi.VideoStream.Models; +using DownKyi.Core.Utils; +using Prism.Commands; using Prism.Mvvm; using System.Collections.Generic; +using System.Collections.ObjectModel; namespace DownKyi.ViewModels.PageViewModels { @@ -43,8 +47,8 @@ namespace DownKyi.ViewModels.PageViewModels set => SetProperty(ref duration, value); } - private List audioQualityFormatList; - public List AudioQualityFormatList + private ObservableCollection audioQualityFormatList; + public ObservableCollection AudioQualityFormatList { get => audioQualityFormatList; set => SetProperty(ref audioQualityFormatList, value); @@ -71,5 +75,37 @@ namespace DownKyi.ViewModels.PageViewModels set => SetProperty(ref videoQuality, value); } + + #region + + // 视频画质选择事件 + private DelegateCommand videoQualitySelectedCommand; + public DelegateCommand VideoQualitySelectedCommand => videoQualitySelectedCommand ?? (videoQualitySelectedCommand = new DelegateCommand(ExecuteVideoQualitySelectedCommand)); + + /// + /// 视频画质选择事件 + /// + private void ExecuteVideoQualitySelectedCommand() + { + // 杜比视界 + string dolby = Constant.GetAudioQualities()[3].Name; + if (VideoQuality.Quality == 126) + { + ListHelper.AddUnique(AudioQualityFormatList, dolby); + AudioQualityFormat = dolby; + } + else + { + if (AudioQualityFormatList.Contains(dolby)) + { + AudioQualityFormatList.Remove(dolby); + AudioQualityFormat = AudioQualityFormatList[0]; + } + } + + } + + #endregion + } } diff --git a/src/DownKyi/ViewModels/Settings/ViewVideoViewModel.cs b/src/DownKyi/ViewModels/Settings/ViewVideoViewModel.cs index f2cd1f0..a36ac6d 100644 --- a/src/DownKyi/ViewModels/Settings/ViewVideoViewModel.cs +++ b/src/DownKyi/ViewModels/Settings/ViewVideoViewModel.cs @@ -125,6 +125,7 @@ namespace DownKyi.ViewModels.Settings // 优先下载音质 AudioQualityList = Constant.GetAudioQualities(); + AudioQualityList.RemoveAt(3); // 文件命名格式 SelectedFileName = new ObservableCollection(); diff --git a/src/DownKyi/ViewModels/ViewIndexViewModel.cs b/src/DownKyi/ViewModels/ViewIndexViewModel.cs index c052ef1..ff0a93d 100644 --- a/src/DownKyi/ViewModels/ViewIndexViewModel.cs +++ b/src/DownKyi/ViewModels/ViewIndexViewModel.cs @@ -198,23 +198,12 @@ namespace DownKyi.ViewModels #endregion - #region 业务逻辑 /// /// 进入B站链接的处理逻辑, /// 只负责处理输入,并跳转到视频详情页。 /// 不是支持的格式,则进入搜索页面。 - /// 支持的格式有: - /// av号:av170001, AV170001, https://www.bilibili.com/video/av170001 - /// BV号:BV17x411w7KC, https://www.bilibili.com/video/BV17x411w7KC - /// 番剧(电影、电视剧)ss号:ss32982, SS32982, https://www.bilibili.com/bangumi/play/ss32982 - /// 番剧(电影、电视剧)ep号:ep317925, EP317925, https://www.bilibili.com/bangumi/play/ep317925 - /// 番剧(电影、电视剧)md号:md28228367, MD28228367, https://www.bilibili.com/bangumi/media/md28228367 - /// 课程ss号:https://www.bilibili.com/cheese/play/ss205 - /// 课程ep号:https://www.bilibili.com/cheese/play/ep3489 - /// 收藏夹:ml1329019876, ML1329019876, https://www.bilibili.com/medialist/detail/ml1329019876 - /// 用户空间:uid928123, UID928123, uid:928123, UID:928123, https://space.bilibili.com/928123 /// private void EnterBili() { diff --git a/src/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs b/src/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs index 87dc5ce..40d5013 100644 --- a/src/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs +++ b/src/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs @@ -1,12 +1,15 @@ -using DownKyi.Core.Logging; +using DownKyi.Core.BiliApi.VideoStream; +using DownKyi.Core.Logging; using DownKyi.Events; using DownKyi.Images; using DownKyi.Services; +using DownKyi.Services.Download; using DownKyi.Utils; using DownKyi.ViewModels.PageViewModels; using Prism.Commands; using Prism.Events; using Prism.Regions; +using Prism.Services.Dialogs; using System; using System.Collections.ObjectModel; using System.Threading.Tasks; @@ -18,6 +21,8 @@ namespace DownKyi.ViewModels { public const string Tag = "PagePublicFavorites"; + private readonly IDialogService dialogService; + #region 页面属性申明 private string pageName = Tag; @@ -34,34 +39,6 @@ namespace DownKyi.ViewModels set => SetProperty(ref arrowBack, value); } - private VectorImage play; - public VectorImage Play - { - get => play; - set => SetProperty(ref play, value); - } - - private VectorImage like; - public VectorImage Like - { - get => like; - set => SetProperty(ref like, value); - } - - private VectorImage favorite; - public VectorImage Favorite - { - get => favorite; - set => SetProperty(ref favorite, value); - } - - private VectorImage share; - public VectorImage Share - { - get => share; - set => SetProperty(ref share, value); - } - private Favorites favorites; public Favorites Favorites { @@ -92,25 +69,15 @@ namespace DownKyi.ViewModels #endregion - public ViewPublicFavoritesViewModel(IEventAggregator eventAggregator) : base(eventAggregator) + public ViewPublicFavoritesViewModel(IEventAggregator eventAggregator, IDialogService dialogService) : base(eventAggregator) { + this.dialogService = dialogService; + #region 属性初始化 ArrowBack = NavigationIcon.Instance().ArrowBack; ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); - Play = NormalIcon.Instance().Play; - Play.Fill = DictionaryResource.GetColor("ColorTextGrey2"); - - Like = NormalIcon.Instance().Like; - Like.Fill = DictionaryResource.GetColor("ColorTextGrey2"); - - Favorite = NormalIcon.Instance().Favorite; - Favorite.Fill = DictionaryResource.GetColor("ColorTextGrey2"); - - Share = NormalIcon.Instance().Share; - Share.Fill = DictionaryResource.GetColor("ColorTextGrey2"); - FavoritesMedias = new ObservableCollection(); #endregion @@ -185,6 +152,7 @@ namespace DownKyi.ViewModels /// private void ExecuteAddToDownloadCommand() { + AddToDownload(true); } // 添加所有视频到下载列表事件 @@ -196,6 +164,7 @@ namespace DownKyi.ViewModels /// private void ExecuteAddAllToDownloadCommand() { + AddToDownload(false); } // 列表选择事件 @@ -212,6 +181,48 @@ namespace DownKyi.ViewModels #endregion + private async void AddToDownload(bool isOnlySelected) + { + // 收藏夹里只有视频 + AddToDownloadService addToDownloadService = new AddToDownloadService(PlayStreamType.VIDEO); + + // 选择文件夹 + string directory = addToDownloadService.SetDirectory(dialogService); + + // 视频计数 + int i = 0; + await Task.Run(() => + { + // 添加到下载 + foreach (FavoritesMedia media in FavoritesMedias) + { + // 只下载选中项,跳过未选中项 + if (isOnlySelected && !media.IsSelected) { continue; } + + /// 有分P的就下载全部 + + // 开启服务 + VideoInfoService videoInfoService = new VideoInfoService(media.Bvid); + + addToDownloadService.SetVideoInfoService(videoInfoService); + addToDownloadService.GetVideo(); + addToDownloadService.ParseVideo(videoInfoService); + // 下载 + i += addToDownloadService.AddToDownload(eventAggregator, directory); + } + }); + + // 通知用户添加到下载列表的结果 + if (i == 0) + { + eventAggregator.GetEvent().Publish(DictionaryResource.GetString("TipAddDownloadingZero")); + } + else + { + eventAggregator.GetEvent().Publish($"{DictionaryResource.GetString("TipAddDownloadingFinished1")}{i}{DictionaryResource.GetString("TipAddDownloadingFinished2")}"); + } + } + /// /// 初始化页面元素 /// diff --git a/src/DownKyi/ViewModels/ViewVideoDetailViewModel.cs b/src/DownKyi/ViewModels/ViewVideoDetailViewModel.cs index 3937afb..f6f5db7 100644 --- a/src/DownKyi/ViewModels/ViewVideoDetailViewModel.cs +++ b/src/DownKyi/ViewModels/ViewVideoDetailViewModel.cs @@ -1,18 +1,14 @@ using DownKyi.Core.BiliApi.BiliUtils; using DownKyi.Core.BiliApi.VideoStream; -using DownKyi.Core.BiliApi.Zone; -using DownKyi.Core.FileName; using DownKyi.Core.Logging; using DownKyi.Core.Settings; -using DownKyi.Core.Utils; using DownKyi.CustomControl; using DownKyi.Events; using DownKyi.Images; -using DownKyi.Models; using DownKyi.Services; +using DownKyi.Services.Download; using DownKyi.Utils; using DownKyi.ViewModels.Dialogs; -using DownKyi.ViewModels.DownloadManager; using DownKyi.ViewModels.PageViewModels; using Prism.Commands; using Prism.Events; @@ -21,7 +17,6 @@ using Prism.Services.Dialogs; using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; @@ -435,12 +430,12 @@ namespace DownKyi.ViewModels { foreach (VideoPage page in section.VideoPages) { - VideoPage videoPage = section.VideoPages.FirstOrDefault(t => t == page); + //VideoPage videoPage = section.VideoPages.FirstOrDefault(t => t == page); - if (videoPage.IsSelected) + if (page.IsSelected) { // 执行解析任务 - UnityUpdateView(ParseVideo, null, videoPage); + UnityUpdateView(ParseVideo, null, page); } } } @@ -452,10 +447,10 @@ namespace DownKyi.ViewModels { foreach (VideoPage page in section.VideoPages) { - VideoPage videoPage = section.VideoPages.FirstOrDefault(t => t == page); + //VideoPage videoPage = section.VideoPages.FirstOrDefault(t => t == page); // 执行解析任务 - UnityUpdateView(ParseVideo, null, videoPage); + UnityUpdateView(ParseVideo, null, page); } } } @@ -465,10 +460,10 @@ namespace DownKyi.ViewModels { foreach (VideoPage page in section.VideoPages) { - VideoPage videoPage = section.VideoPages.FirstOrDefault(t => t == page); + //VideoPage videoPage = section.VideoPages.FirstOrDefault(t => t == page); // 执行解析任务 - UnityUpdateView(ParseVideo, null, videoPage); + UnityUpdateView(ParseVideo, null, page); } } break; @@ -504,211 +499,37 @@ namespace DownKyi.ViewModels /// /// 添加到下载列表事件 /// - private void ExecuteAddToDownloadCommand() + private async void ExecuteAddToDownloadCommand() { - // 选择的下载文件夹 - string directory = string.Empty; - - // 下载内容 - bool downloadAudio = true; - bool downloadVideo = true; - bool downloadDanmaku = true; - bool downloadSubtitle = true; - bool downloadCover = true; - - // 是否使用默认下载目录 - if (SettingsManager.GetInstance().IsUseSaveVideoRootPath() == AllowStatus.YES) + AddToDownloadService addToDownloadService = null; + // 视频 + if (ParseEntrance.IsAvUrl(InputText) || ParseEntrance.IsBvUrl(InputText)) { - directory = SettingsManager.GetInstance().GetSaveVideoRootPath(); + addToDownloadService = new AddToDownloadService(PlayStreamType.VIDEO); } - else + // 番剧(电影、电视剧) + if (ParseEntrance.IsBangumiSeasonUrl(InputText) || ParseEntrance.IsBangumiEpisodeUrl(InputText) || ParseEntrance.IsBangumiMediaUrl(InputText)) { - // 打开文件夹选择器 - dialogService.ShowDialog(ViewDownloadSetterViewModel.Tag, null, result => - { - if (result.Result == ButtonResult.OK) - { - // 选择的下载文件夹 - directory = result.Parameters.GetValue("directory"); - - // 下载内容 - downloadAudio = result.Parameters.GetValue("downloadAudio"); - downloadVideo = result.Parameters.GetValue("downloadVideo"); - downloadDanmaku = result.Parameters.GetValue("downloadDanmaku"); - downloadSubtitle = result.Parameters.GetValue("downloadSubtitle"); - downloadCover = result.Parameters.GetValue("downloadCover"); - } - }); + addToDownloadService = new AddToDownloadService(PlayStreamType.BANGUMI); } - - // 下载设置dialog中如果点击取消或者关闭窗口, - // 会返回空字符串, - // 这时直接退出 - if (directory == string.Empty) { return; } - - // 文件夹不存在则创建 - if (!Directory.Exists(directory)) + // 课程 + if (ParseEntrance.IsCheeseSeasonUrl(InputText) || ParseEntrance.IsCheeseEpisodeUrl(InputText)) { - Directory.CreateDirectory(directory); + addToDownloadService = new AddToDownloadService(PlayStreamType.CHEESE); } - // 添加视频计数 - int i = 0; + // 选择文件夹 + string directory = addToDownloadService.SetDirectory(dialogService); - // 添加到下载 - foreach (VideoSection section in VideoSections) + // 视频计数 + int i = 0; + await Task.Run(() => { - foreach (VideoPage page in section.VideoPages) - { - // 只下载选中项,跳过未选中项 - if (!page.IsSelected) { continue; } - - // 没有解析的也跳过 - if (page.PlayUrl == null) { continue; } - - // 判断是否同一个视频,需要cid、画质、音质、视频编码都相同 - - // 如果存在正在下载列表,则跳过,并提示 - foreach (DownloadingItem item in App.DownloadingList) - { - if (item.DownloadBase.Cid == page.Cid && item.Resolution.Id == page.VideoQuality.Quality && item.AudioCodec.Name == page.AudioQualityFormat && item.VideoCodecName == page.VideoQuality.SelectedVideoCodec) - { - eventAggregator.GetEvent().Publish($"{page.Name}{DictionaryResource.GetString("TipAlreadyToAddDownloading")}"); - continue; - } - } - - // 如果存在下载完成列表,弹出选择框是否再次下载 - foreach (DownloadedItem item in App.DownloadedList) - { - if (item.DownloadBase.Cid == page.Cid && item.Resolution.Id == page.VideoQuality.Quality && item.AudioCodec.Name == page.AudioQualityFormat && item.VideoCodecName == page.VideoQuality.SelectedVideoCodec) - { - eventAggregator.GetEvent().Publish($"{page.Name}{DictionaryResource.GetString("TipAlreadyToAddDownloaded")}"); - continue; - } - } - - // 视频分区 - int zoneId = -1; - List zoneList = VideoZone.Instance().GetZones(); - ZoneAttr zone = zoneList.Find(it => it.Id == VideoInfoView.TypeId); - if (zone != null) - { - if (zone.ParentId == 0) - { - zoneId = zone.Id; - } - else - { - ZoneAttr zoneParent = zoneList.Find(it => it.Id == zone.ParentId); - if (zoneParent != null) - { - zoneId = zoneParent.Id; - } - } - } - - // 如果只有一个视频章节,则不在命名中出现 - string sectionName = string.Empty; - if (VideoSections.Count > 1) - { - sectionName = section.Title; - } - - // 文件路径 - List fileNameParts = SettingsManager.GetInstance().GetFileNameParts(); - FileName fileName = FileName.Builder(fileNameParts) - .SetOrder(page.Order) - .SetSection(Format.FormatFileName(sectionName)) - .SetMainTitle(Format.FormatFileName(VideoInfoView.Title)) - .SetPageTitle(Format.FormatFileName(page.Name)) - .SetVideoZone(VideoInfoView.VideoZone.Split('>')[0]) - .SetAudioQuality(page.AudioQualityFormat) - .SetVideoQuality(page.VideoQuality.QualityFormat) - .SetVideoCodec(page.VideoQuality.SelectedVideoCodec.Contains("AVC") ? "AVC" : page.VideoQuality.SelectedVideoCodec.Contains("HEVC") ? "HEVC" : ""); - string filePath = Path.Combine(directory, fileName.RelativePath()); - - // 视频类别 - PlayStreamType playStreamType; - switch (VideoInfoView.TypeId) - { - case -10: - playStreamType = PlayStreamType.CHEESE; - break; - case 13: - case 23: - case 177: - case 167: - case 11: - playStreamType = PlayStreamType.BANGUMI; - break; - case 1: - case 3: - case 129: - case 4: - case 36: - case 188: - case 234: - case 223: - case 160: - case 211: - case 217: - case 119: - case 155: - case 202: - case 5: - case 181: - default: - playStreamType = PlayStreamType.VIDEO; - break; - } - - // 如果不存在,直接添加到下载列表 - DownloadBase downloadBase = new DownloadBase - { - Bvid = page.Bvid, - Avid = page.Avid, - Cid = page.Cid, - EpisodeId = page.EpisodeId, - CoverUrl = VideoInfoView.CoverUrl, - PageCoverUrl = page.FirstFrame, - ZoneId = zoneId, - FilePath = filePath, - - Order = page.Order, - MainTitle = VideoInfoView.Title, - Name = page.Name, - Duration = page.Duration, - VideoCodecName = page.VideoQuality.SelectedVideoCodec, - Resolution = new Quality { Name = page.VideoQuality.QualityFormat, Id = page.VideoQuality.Quality }, - AudioCodec = Constant.GetAudioQualities().FirstOrDefault(t => { return t.Name == page.AudioQualityFormat; }), - }; - Downloading downloading = new Downloading - { - PlayStreamType = playStreamType, - DownloadStatus = DownloadStatus.NOT_STARTED, - }; - - // 需要下载的内容 - downloadBase.NeedDownloadContent["downloadAudio"] = downloadAudio; - downloadBase.NeedDownloadContent["downloadVideo"] = downloadVideo; - downloadBase.NeedDownloadContent["downloadDanmaku"] = downloadDanmaku; - downloadBase.NeedDownloadContent["downloadSubtitle"] = downloadSubtitle; - downloadBase.NeedDownloadContent["downloadCover"] = downloadCover; - - DownloadingItem downloadingItem = new DownloadingItem - { - DownloadBase = downloadBase, - Downloading = downloading, - PlayUrl = page.PlayUrl, - //ZoneImage = (DrawingImage)Application.Current.Resources[VideoZoneIcon.Instance().GetZoneImageKey(zoneId)], - }; - - // 添加到下载列表 - App.DownloadingList.Add(downloadingItem); - i++; - } - } + // 传递video对象 + addToDownloadService.GetVideo(VideoInfoView, VideoSections.ToList()); + // 下载 + i = addToDownloadService.AddToDownload(eventAggregator, directory); + }); // 通知用户添加到下载列表的结果 if (i == 0) @@ -732,7 +553,6 @@ namespace DownKyi.ViewModels #endregion - #region 业务逻辑 /// diff --git a/src/DownKyi/Views/DownloadManager/ViewDownloadFinished.xaml b/src/DownKyi/Views/DownloadManager/ViewDownloadFinished.xaml index 27bbf81..357014f 100644 --- a/src/DownKyi/Views/DownloadManager/ViewDownloadFinished.xaml +++ b/src/DownKyi/Views/DownloadManager/ViewDownloadFinished.xaml @@ -20,7 +20,7 @@ - + + ItemsSource="{Binding DownloadedList}" + VirtualizingPanel.IsVirtualizing="True" + VirtualizingPanel.IsVirtualizingWhenGrouping="True">