diff --git a/DownKyi.Core/BiliApi/Video/VideoInfo.cs b/DownKyi.Core/BiliApi/Video/VideoInfo.cs index 18762f3..ed65ced 100644 --- a/DownKyi.Core/BiliApi/Video/VideoInfo.cs +++ b/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/DownKyi.Core/BiliApi/WebClient.cs b/DownKyi.Core/BiliApi/WebClient.cs index 2fa1c28..9a514e0 100644 --- a/DownKyi.Core/BiliApi/WebClient.cs +++ b/DownKyi.Core/BiliApi/WebClient.cs @@ -49,7 +49,7 @@ 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"; + request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 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"; diff --git a/DownKyi.Core/Storage/Database/Download/DownloadDb.cs b/DownKyi.Core/Storage/Database/Download/DownloadDb.cs index a467891..7d1b0c5 100644 --- a/DownKyi.Core/Storage/Database/Download/DownloadDb.cs +++ b/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/DownKyi/App.xaml.cs b/DownKyi/App.xaml.cs index ace8540..41e07d7 100644 --- a/DownKyi/App.xaml.cs +++ b/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/DownKyi/DownKyi.csproj b/DownKyi/DownKyi.csproj index d3f6769..405275c 100644 --- a/DownKyi/DownKyi.csproj +++ b/DownKyi/DownKyi.csproj @@ -92,6 +92,7 @@ + diff --git a/DownKyi/Models/DownloadStatus.cs b/DownKyi/Models/DownloadStatus.cs index 8aa0bd7..a9a0753 100644 --- a/DownKyi/Models/DownloadStatus.cs +++ b/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/DownKyi/Services/Download/AddToDownloadService.cs b/DownKyi/Services/Download/AddToDownloadService.cs new file mode 100644 index 0000000..d9ce014 --- /dev/null +++ b/DownKyi/Services/Download/AddToDownloadService.cs @@ -0,0 +1,377 @@ +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() + } + }; + } + } + + /// + /// 解析视频流 + /// + /// + 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.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" : ""); + 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/DownKyi/Services/Download/AriaDownloadService.cs b/DownKyi/Services/Download/AriaDownloadService.cs index 3cf1351..c563825 100644 --- a/DownKyi/Services/Download/AriaDownloadService.cs +++ b/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"; @@ -136,7 +139,11 @@ namespace DownKyi.Services.Download else { // 记录本次下载的文件 - downloading.Downloading.DownloadFiles.Add(key, fileName); + try + { + downloading.Downloading.DownloadFiles.Add(key, fileName); + } + catch (ArgumentException) { } } // 开始下载 @@ -147,9 +154,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 +328,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 +372,9 @@ namespace DownKyi.Services.Download if (downloading.PlayUrl != null && downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED) { + // 设置下载状态 + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + return; } @@ -453,28 +467,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 +513,7 @@ namespace DownKyi.Services.Download } // 降低CPU占用 - Thread.Sleep(200); + Thread.Sleep(500); } } @@ -515,6 +543,10 @@ namespace DownKyi.Services.Download // 解析并依次下载音频、视频、弹幕、字幕、封面等内容 Parse(downloading); + // 设置下载状态 + // 必须在解析之后设置为DOWNLOADING + downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + // 暂停 Pause(downloading); // 是否存在 @@ -524,14 +556,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 +589,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 +647,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 +741,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 +754,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 +785,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"); + } + /// /// 获取图片的扩展名 /// @@ -933,7 +997,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/DownKyi/Services/Download/DownloadStorageService.cs b/DownKyi/Services/Download/DownloadStorageService.cs index e13deec..d0bab9e 100644 --- a/DownKyi/Services/Download/DownloadStorageService.cs +++ b/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/DownKyi/Services/SearchService.cs b/DownKyi/Services/SearchService.cs index f1426da..8ead7bd 100644 --- a/DownKyi/Services/SearchService.cs +++ b/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/DownKyi/Services/Utils.cs b/DownKyi/Services/Utils.cs index b0f55b3..7489c1c 100644 --- a/DownKyi/Services/Utils.cs +++ b/DownKyi/Services/Utils.cs @@ -97,7 +97,13 @@ namespace DownKyi.Services private static List GetAudioQualityFormatList(PlayUrl playUrl, int defaultAudioQuality) { List audioQualityFormatList = new List(); - foreach (var audio in playUrl.Dash.Audio) + + if (playUrl.Dash.Audio == null) + { + return audioQualityFormatList; + } + + foreach (PlayUrlDashVideo audio in playUrl.Dash.Audio) { // 音质id大于设置画质时,跳过 if (audio.Id > defaultAudioQuality) { continue; } @@ -126,7 +132,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 +151,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 +160,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 +183,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 +198,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 +221,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") ? "dolby" : ""; } } diff --git a/DownKyi/Services/VideoInfoService.cs b/DownKyi/Services/VideoInfoService.cs index 5659cca..3bec45f 100644 --- a/DownKyi/Services/VideoInfoService.cs +++ b/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/DownKyi/Utils/DialogUtils.cs b/DownKyi/Utils/DialogUtils.cs index 2376ae7..41b08d7 100644 --- a/DownKyi/Utils/DialogUtils.cs +++ b/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/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs b/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs index 454f94b..4586b8a 100644 --- a/DownKyi/ViewModels/DownloadManager/DownloadBaseItem.cs +++ b/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/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs b/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs index 899df6f..dc51226 100644 --- a/DownKyi/ViewModels/DownloadManager/DownloadingItem.cs +++ b/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/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs b/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs index 614e524..c4d94a1 100644 --- a/DownKyi/ViewModels/DownloadManager/ViewDownloadFinishedViewModel.cs +++ b/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/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs b/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs index 1985f53..a30c30b 100644 --- a/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs +++ b/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/DownKyi/ViewModels/PageViewModels/Favorites.cs b/DownKyi/ViewModels/PageViewModels/Favorites.cs index 96f1db8..ae77f79 100644 --- a/DownKyi/ViewModels/PageViewModels/Favorites.cs +++ b/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/DownKyi/ViewModels/ViewIndexViewModel.cs b/DownKyi/ViewModels/ViewIndexViewModel.cs index c052ef1..ff0a93d 100644 --- a/DownKyi/ViewModels/ViewIndexViewModel.cs +++ b/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/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs b/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs index 87dc5ce..40d5013 100644 --- a/DownKyi/ViewModels/ViewPublicFavoritesViewModel.cs +++ b/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/DownKyi/ViewModels/ViewVideoDetailViewModel.cs b/DownKyi/ViewModels/ViewVideoDetailViewModel.cs index 3937afb..f6f5db7 100644 --- a/DownKyi/ViewModels/ViewVideoDetailViewModel.cs +++ b/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/DownKyi/Views/DownloadManager/ViewDownloadFinished.xaml b/DownKyi/Views/DownloadManager/ViewDownloadFinished.xaml index 27bbf81..357014f 100644 --- a/DownKyi/Views/DownloadManager/ViewDownloadFinished.xaml +++ b/DownKyi/Views/DownloadManager/ViewDownloadFinished.xaml @@ -20,7 +20,7 @@ - + + ItemsSource="{Binding DownloadedList}" + VirtualizingPanel.IsVirtualizing="True" + VirtualizingPanel.IsVirtualizingWhenGrouping="True">