using DownKyi.Core.BiliApi.BiliUtils; 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.Utils; using DownKyi.ViewModels.Dialogs; using Prism.Commands; using Prism.Events; using Prism.Regions; 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; using System.Windows.Media; namespace DownKyi.ViewModels { public class ViewVideoDetailViewModel : BaseViewModel { public const string Tag = "PageVideoDetail"; private readonly IDialogService dialogService; #region 页面属性申明 private VectorImage arrowBack; public VectorImage ArrowBack { get => arrowBack; set => SetProperty(ref arrowBack, value); } private string inputText; public string InputText { get => inputText; set => SetProperty(ref inputText, value); } private GifImage loading; public GifImage Loading { get => loading; set => SetProperty(ref loading, value); } private Visibility loadingVisibility; public Visibility LoadingVisibility { get => loadingVisibility; set => SetProperty(ref loadingVisibility, value); } private VectorImage downloadManage; public VectorImage DownloadManage { get => downloadManage; set => SetProperty(ref downloadManage, value); } private VideoInfoView videoInfoView; public VideoInfoView VideoInfoView { get => videoInfoView; set => SetProperty(ref videoInfoView, value); } private ObservableCollection videoSections; public ObservableCollection VideoSections { get => videoSections; set => SetProperty(ref videoSections, value); } private bool isSelectAll; public bool IsSelectAll { get => isSelectAll; set => SetProperty(ref isSelectAll, value); } private Visibility contentVisibility; public Visibility ContentVisibility { get => contentVisibility; set => SetProperty(ref contentVisibility, value); } private Visibility noDataVisibility; public Visibility NoDataVisibility { get => noDataVisibility; set => SetProperty(ref noDataVisibility, value); } #endregion public ViewVideoDetailViewModel(IEventAggregator eventAggregator, IDialogService dialogService) : base(eventAggregator) { this.dialogService = dialogService; #region 属性初始化 Loading = new GifImage(Properties.Resources.loading); Loading.StartAnimate(); LoadingVisibility = Visibility.Collapsed; ArrowBack = NavigationIcon.Instance().ArrowBack; ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); DownloadManage = ButtonIcon.Instance().DownloadManage; DownloadManage.Height = 24; DownloadManage.Width = 24; DownloadManage.Fill = DictionaryResource.GetColor("ColorPrimary"); VideoSections = new ObservableCollection(); #endregion } #region 命令申明 // 返回 private DelegateCommand backSpaceCommand; public DelegateCommand BackSpaceCommand => backSpaceCommand ?? (backSpaceCommand = new DelegateCommand(ExecuteBackSpace)); /// /// 返回 /// private void ExecuteBackSpace() { NavigationParam parameter = new NavigationParam { ViewName = ParentView, ParentViewName = null, Parameter = null }; eventAggregator.GetEvent().Publish(parameter); } // 前往下载管理页面 private DelegateCommand downloadManagerCommand; public DelegateCommand DownloadManagerCommand => downloadManagerCommand ?? (downloadManagerCommand = new DelegateCommand(ExecuteDownloadManagerCommand)); /// /// 前往下载管理页面 /// private void ExecuteDownloadManagerCommand() { NavigationParam parameter = new NavigationParam { ViewName = ViewDownloadManagerViewModel.Tag, ParentViewName = Tag, Parameter = null }; eventAggregator.GetEvent().Publish(parameter); } // 输入确认事件 private DelegateCommand inputCommand; public DelegateCommand InputCommand => inputCommand ?? (inputCommand = new DelegateCommand(ExecuteInputCommand, CanExecuteInputCommand)); /// /// 处理输入事件 /// private async void ExecuteInputCommand() { InitView(); try { await Task.Run(() => { if (InputText == null || InputText == string.Empty) { return; } LogManager.Debug(Tag, $"InputText: {InputText}"); // 更新页面 UnityUpdateView(UpdateView, InputText, null); // 是否自动解析视频 if (SettingsManager.GetInstance().IsAutoParseVideo() == AllowStatus.YES) { PropertyChangeAsync(ExecuteParseAllVideoCommand); } }); } catch (Exception e) { Core.Utils.Debugging.Console.PrintLine("InputCommand()发生异常: {0}", e); LogManager.Error(Tag, e); LoadingVisibility = Visibility.Collapsed; ContentVisibility = Visibility.Collapsed; NoDataVisibility = Visibility.Visible; } } /// /// 输入事件是否允许执行 /// /// private bool CanExecuteInputCommand() { return LoadingVisibility != Visibility.Visible; } // 复制封面事件 private DelegateCommand copyCoverCommand; public DelegateCommand CopyCoverCommand => copyCoverCommand ?? (copyCoverCommand = new DelegateCommand(ExecuteCopyCoverCommand)); /// /// 复制封面事件 /// private void ExecuteCopyCoverCommand() { // 复制封面图片到剪贴板 Clipboard.SetImage(VideoInfoView.Cover); LogManager.Info(Tag, "复制封面图片到剪贴板"); } // 复制封面URL事件 private DelegateCommand copyCoverUrlCommand; public DelegateCommand CopyCoverUrlCommand => copyCoverUrlCommand ?? (copyCoverUrlCommand = new DelegateCommand(ExecuteCopyCoverUrlCommand)); /// /// 复制封面URL事件 /// private void ExecuteCopyCoverUrlCommand() { // 复制封面url到剪贴板 Clipboard.SetText(VideoInfoView.CoverUrl); LogManager.Info(Tag, "复制封面url到剪贴板"); } // 前往UP主页事件 private DelegateCommand upperCommand; public DelegateCommand UpperCommand => upperCommand ?? (upperCommand = new DelegateCommand(ExecuteUpperCommand)); /// /// 前往UP主页事件 /// private void ExecuteUpperCommand() { NavigateToView.NavigateToViewUserSpace(eventAggregator, Tag, VideoInfoView.UpperMid); } // 视频章节选择事件 private DelegateCommand videoSectionsCommand; public DelegateCommand VideoSectionsCommand => videoSectionsCommand ?? (videoSectionsCommand = new DelegateCommand(ExecuteVideoSectionsCommand)); /// /// 视频章节选择事件 /// /// private void ExecuteVideoSectionsCommand(object parameter) { if (!(parameter is VideoSection section)) { return; } bool isSelectAll = true; foreach (VideoPage page in section.VideoPages) { if (!page.IsSelected) { isSelectAll = false; break; } } IsSelectAll = section.VideoPages.Count != 0 && isSelectAll; } // 视频page选择事件 private DelegateCommand videoPagesCommand; public DelegateCommand VideoPagesCommand => videoPagesCommand ?? (videoPagesCommand = new DelegateCommand(ExecuteVideoPagesCommand)); /// /// 视频page选择事件 /// /// private void ExecuteVideoPagesCommand(object parameter) { if (!(parameter is ObservableCollection videoPages)) { return; } VideoSection section = VideoSections.FirstOrDefault(item => item.IsSelected); if (section == null) { return; } IsSelectAll = section.VideoPages.Count == videoPages.Count && section.VideoPages.Count != 0; } // Ctrl+A 全选事件 private DelegateCommand keySelectAllCommand; public DelegateCommand KeySelectAllCommand => keySelectAllCommand ?? (keySelectAllCommand = new DelegateCommand(ExecuteKeySelectAllCommand)); /// /// Ctrl+A 全选事件 /// private void ExecuteKeySelectAllCommand(object parameter) { if (!(parameter is VideoSection section)) { return; } foreach (VideoPage page in section.VideoPages) { page.IsSelected = true; } } // 全选事件 private DelegateCommand selectAllCommand; public DelegateCommand SelectAllCommand => selectAllCommand ?? (selectAllCommand = new DelegateCommand(ExecuteSelectAllCommand)); /// /// 全选事件 /// /// private void ExecuteSelectAllCommand(object parameter) { if (!(parameter is VideoSection section)) { return; } if (IsSelectAll) { foreach (VideoPage page in section.VideoPages) { page.IsSelected = true; } } else { foreach (VideoPage page in section.VideoPages) { page.IsSelected = false; } } } // 解析视频流事件 private DelegateCommand parseCommand; public DelegateCommand ParseCommand => parseCommand ?? (parseCommand = new DelegateCommand(ExecuteParseCommand, CanExecuteParseCommand)); /// /// 解析视频流事件 /// /// private async void ExecuteParseCommand(object parameter) { if (!(parameter is VideoPage videoPage)) { return; } LoadingVisibility = Visibility.Visible; try { await Task.Run(() => { LogManager.Debug(Tag, $"Video Page: {videoPage.Cid}"); UnityUpdateView(ParseVideo, null, videoPage); }); } catch (Exception e) { Core.Utils.Debugging.Console.PrintLine("ParseCommand()发生异常: {0}", e); LogManager.Error(Tag, e); LoadingVisibility = Visibility.Collapsed; } LoadingVisibility = Visibility.Collapsed; } /// /// 解析视频流事件是否允许执行 /// /// /// private bool CanExecuteParseCommand(object parameter) { return LoadingVisibility != Visibility.Visible; } // 解析所有视频流事件 private DelegateCommand parseAllVideoCommand; public DelegateCommand ParseAllVideoCommand => parseAllVideoCommand ?? (parseAllVideoCommand = new DelegateCommand(ExecuteParseAllVideoCommand, CanExecuteParseAllVideoCommand)); /// /// 解析所有视频流事件 /// private async void ExecuteParseAllVideoCommand() { LoadingVisibility = Visibility.Visible; // 解析范围 ParseScope parseScope = SettingsManager.GetInstance().GetParseScope(); // 是否选择了解析范围 if (parseScope == ParseScope.NONE) { // 打开解析选择器 dialogService.ShowDialog(ViewParsingSelectorViewModel.Tag, null, result => { if (result.Result == ButtonResult.OK) { // 选择的解析范围 parseScope = result.Parameters.GetValue("parseScope"); } }); } LogManager.Debug(Tag, $"ParseScope: {parseScope:G}"); try { await Task.Run(() => { LogManager.Debug(Tag, "Parse video"); switch (parseScope) { case ParseScope.NONE: break; case ParseScope.SELECTED_ITEM: foreach (VideoSection section in VideoSections) { foreach (VideoPage page in section.VideoPages) { VideoPage videoPage = section.VideoPages.FirstOrDefault(t => t == page); if (videoPage.IsSelected) { // 执行解析任务 UnityUpdateView(ParseVideo, null, videoPage); } } } break; case ParseScope.CURRENT_SECTION: foreach (VideoSection section in VideoSections) { if (section.IsSelected) { foreach (VideoPage page in section.VideoPages) { VideoPage videoPage = section.VideoPages.FirstOrDefault(t => t == page); // 执行解析任务 UnityUpdateView(ParseVideo, null, videoPage); } } } break; case ParseScope.ALL: foreach (VideoSection section in VideoSections) { foreach (VideoPage page in section.VideoPages) { VideoPage videoPage = section.VideoPages.FirstOrDefault(t => t == page); // 执行解析任务 UnityUpdateView(ParseVideo, null, videoPage); } } break; default: break; } }); } catch (Exception e) { Core.Utils.Debugging.Console.PrintLine("ParseCommand()发生异常: {0}", e); LogManager.Error(Tag, e); LoadingVisibility = Visibility.Collapsed; } LoadingVisibility = Visibility.Collapsed; } /// /// 解析所有视频流事件是否允许执行 /// /// private bool CanExecuteParseAllVideoCommand() { return LoadingVisibility != Visibility.Visible; } // 添加到下载列表事件 private DelegateCommand addToDownloadCommand; public DelegateCommand AddToDownloadCommand => addToDownloadCommand ?? (addToDownloadCommand = new DelegateCommand(ExecuteAddToDownloadCommand, CanExecuteAddToDownloadCommand)); /// /// 添加到下载列表事件 /// private 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) { 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; } // 文件夹不存在则创建 if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } // 添加视频计数 int i = 0; // 添加到下载 foreach (VideoSection section in VideoSections) { foreach (VideoPage page in section.VideoPages) { // 只下载选中项,跳过未选中项 if (!page.IsSelected) { continue; } // 没有解析的也跳过 if (page.PlayUrl == null) { continue; } // 判断是否同一个视频,需要cid、画质、音质、视频编码都相同 // 如果存在正在下载列表,则跳过,并提示 foreach (DownloadingItem item in App.DownloadingList) { if (item.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.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; } // 如果不存在,直接添加到下载列表 DownloadingItem downloading = new DownloadingItem { PlayUrl = page.PlayUrl, Bvid = page.Bvid, Avid = page.Avid, Cid = page.Cid, EpisodeId = page.EpisodeId, CoverUrl = VideoInfoView.CoverUrl, PageCoverUrl = page.FirstFrame, ZoneImage = (DrawingImage)Application.Current.Resources[VideoZoneIcon.Instance().GetZoneImageKey(zoneId)], 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; }), FilePath = filePath, PlayStreamType = playStreamType, DownloadStatus = DownloadStatus.NOT_STARTED, }; // 需要下载的内容 downloading.NeedDownloadContent["downloadAudio"] = downloadAudio; downloading.NeedDownloadContent["downloadVideo"] = downloadVideo; downloading.NeedDownloadContent["downloadDanmaku"] = downloadDanmaku; downloading.NeedDownloadContent["downloadSubtitle"] = downloadSubtitle; downloading.NeedDownloadContent["downloadCover"] = downloadCover; // 添加到下载列表 App.DownloadingList.Add(downloading); i++; } } // 通知用户添加到下载列表的结果 if (i == 0) { eventAggregator.GetEvent().Publish(DictionaryResource.GetString("TipAddDownloadingZero")); } else { eventAggregator.GetEvent().Publish($"{DictionaryResource.GetString("TipAddDownloadingFinished1")}{i}{DictionaryResource.GetString("TipAddDownloadingFinished2")}"); } } /// /// 添加到下载列表事件是否允许执行 /// /// private bool CanExecuteAddToDownloadCommand() { return LoadingVisibility != Visibility.Visible; } #endregion #region 业务逻辑 /// /// 初始化页面元素 /// private void InitView() { LogManager.Debug(Tag, "初始化页面元素"); LoadingVisibility = Visibility.Visible; ContentVisibility = Visibility.Collapsed; NoDataVisibility = Visibility.Collapsed; VideoSections.Clear(); } /// /// 更新页面的统一方法 /// /// /// /// private void UnityUpdateView(Action action, string input, VideoPage page) { // 视频 if (ParseEntrance.IsAvUrl(InputText) || ParseEntrance.IsBvUrl(InputText)) { action(new VideoInfoService(input), page); } // 番剧(电影、电视剧) if (ParseEntrance.IsBangumiSeasonUrl(InputText) || ParseEntrance.IsBangumiEpisodeUrl(InputText) || ParseEntrance.IsBangumiMediaUrl(InputText)) { action(new BangumiInfoService(input), page); } // 课程 if (ParseEntrance.IsCheeseSeasonUrl(InputText) || ParseEntrance.IsCheeseEpisodeUrl(InputText)) { action(new CheeseInfoService(input), page); } } /// /// 更新页面 /// /// private void UpdateView(IInfoService videoInfoService, VideoPage param) { VideoInfoView = videoInfoService.GetVideoView(); if (VideoInfoView == null) { LogManager.Debug(Tag, "VideoInfoView is null."); LoadingVisibility = Visibility.Collapsed; ContentVisibility = Visibility.Collapsed; NoDataVisibility = Visibility.Visible; return; } else { LoadingVisibility = Visibility.Collapsed; ContentVisibility = Visibility.Visible; NoDataVisibility = Visibility.Collapsed; } List videoSections = videoInfoService.GetVideoSections(); if (videoSections == null) { LogManager.Debug(Tag, "videoSections is not exist."); List pages = videoInfoService.GetVideoPages(); PropertyChangeAsync(new Action(() => { VideoSections.Add(new VideoSection { Id = 0, Title = "default", IsSelected = true, VideoPages = pages }); })); } else { PropertyChangeAsync(new Action(() => { VideoSections.AddRange(videoSections); })); } } /// /// 解析视频流 /// /// private void ParseVideo(IInfoService videoInfoService, VideoPage videoPage) { videoInfoService.GetVideoStream(videoPage); } #endregion /// /// 导航到VideoDetail页面时执行 /// /// public override void OnNavigatedTo(NavigationContext navigationContext) { base.OnNavigatedTo(navigationContext); DownloadManage = ButtonIcon.Instance().DownloadManage; DownloadManage.Height = 24; DownloadManage.Width = 24; DownloadManage.Fill = DictionaryResource.GetColor("ColorPrimary"); // Parent参数为null时,表示是从下一个页面返回到本页面,不需要执行任务 if (navigationContext.Parameters.GetValue("Parent") != null) { // 正在执行任务时不开启新任务 if (LoadingVisibility != Visibility.Visible) { InputText = navigationContext.Parameters.GetValue("Parameter"); PropertyChangeAsync(ExecuteInputCommand); } } } } }