From 0a26704f2ac4112423cf8601d724dc0ae7d9581f Mon Sep 17 00:00:00 2001 From: croire <1432593898@qq.com> Date: Wed, 15 Dec 2021 21:42:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=8F=91=E4=B8=8B=E8=BD=BD=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E4=B8=AD=EF=BC=8C=E4=BF=9D=E5=AD=98=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=EF=BC=8C20211215?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BiliApi/VideoStream/VideoStream.cs | 1 + DownKyi.Core/BiliApi/Zone/VideoZone.cs | 60 ++- DownKyi.Core/BiliApi/Zone/VideoZoneIcon.cs | 85 +++ DownKyi.Core/BiliApi/Zone/ZoneImage.xaml | 293 +++++++++++ DownKyi.Core/Danmaku2Ass/Studio.cs | 10 +- DownKyi.Core/DownKyi.Core.csproj | 8 +- DownKyi.Core/FFmpeg/FFmpegHelper.cs | 8 +- DownKyi/App.xaml | 1 + DownKyi/App.xaml.cs | 73 ++- DownKyi/Images/ButtonIcon.cs | 39 ++ DownKyi/Images/LogoIcon.cs | 2 +- DownKyi/Languages/Default.xaml | 17 + DownKyi/Models/DownloadStatus.cs | 1 + DownKyi/Models/DownloadingItem.cs | 220 +++++++- .../Services/Download/AriaDownloadService.cs | 485 +++++++++++++++--- DownKyi/Services/Download/IDownloadService.cs | 9 +- DownKyi/Themes/ColorBrush.xaml | 2 + DownKyi/Themes/Colors/ColorDefault.xaml | 2 + DownKyi/Utils/DictionaryResource.cs | 2 +- .../ViewDownloadingViewModel.cs | 53 +- .../DownloadManager/ViewDownloading.xaml | 302 ++++++++++- 21 files changed, 1549 insertions(+), 124 deletions(-) create mode 100644 DownKyi.Core/BiliApi/Zone/VideoZoneIcon.cs create mode 100644 DownKyi.Core/BiliApi/Zone/ZoneImage.xaml diff --git a/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs b/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs index 2508f24..6ae5ca0 100644 --- a/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs +++ b/DownKyi.Core/BiliApi/VideoStream/VideoStream.cs @@ -49,6 +49,7 @@ namespace DownKyi.Core.BiliApi.VideoStream // 获取播放器信息 var player = PlayerV2(avid, bvid, cid); + if (player == null) { return subRipTexts; } foreach (var subtitle in player.Subtitle.Subtitles) { diff --git a/DownKyi.Core/BiliApi/Zone/VideoZone.cs b/DownKyi.Core/BiliApi/Zone/VideoZone.cs index 741a918..9474086 100644 --- a/DownKyi.Core/BiliApi/Zone/VideoZone.cs +++ b/DownKyi.Core/BiliApi/Zone/VideoZone.cs @@ -2,7 +2,6 @@ namespace DownKyi.Core.BiliApi.Zone { - public class VideoZone { private static VideoZone that; @@ -59,7 +58,7 @@ namespace DownKyi.Core.BiliApi.Zone zones.Add(new ZoneAttr(30, "vocaloid", "VOCALOID·UTAU", 3)); //以雅马哈Vocaloid和UTAU引擎为基础,包含其他调教引擎,运用各类音源进行的歌曲创作内容 zones.Add(new ZoneAttr(194, "electronic", "电音", 3)); //以电子合成器、音乐软体等产生的电子声响制作的音乐 zones.Add(new ZoneAttr(59, "perform", "演奏", 3)); //传统或非传统乐器及器材的演奏作品 - zones.Add(new ZoneAttr(193, "mv", "MV", 3)); //音乐录影带,为搭配音乐而拍摄的短片 + zones.Add(new ZoneAttr(193, "mv", "MV", 3)); //音乐录影带,为搭配音乐而拍摄或制作的视频 zones.Add(new ZoneAttr(29, "live", "音乐现场", 3)); //音乐实况表演视频 zones.Add(new ZoneAttr(130, "other", "音乐综合", 3)); //收录无法定义到其他音乐子分区的音乐视频 @@ -84,37 +83,48 @@ namespace DownKyi.Core.BiliApi.Zone zones.Add(new ZoneAttr(19, "mugen", "Mugen", 4)); //以Mugen引擎为平台制作、或与Mugen相关的游戏视频 //知识 - zones.Add(new ZoneAttr(36, "technology", "知识")); // 主分区 + zones.Add(new ZoneAttr(36, "knowledge", "知识")); // 主分区 zones.Add(new ZoneAttr(201, "science", "科学科普", 36)); //回答你的十万个为什么 - zones.Add(new ZoneAttr(124, "fun", "社科人文", 36)); //聊聊互联网社会法律,看看历史趣闻艺术,品品文化心理人物 - zones.Add(new ZoneAttr(207, "finance", "财经", 36)); //宏观经济分析,证券市场动态,商业帝国故事,知识与财富齐飞~ - zones.Add(new ZoneAttr(208, "campus", "校园学习", 36)); //老师很有趣,同学多人才,我们都爱搞学习 - zones.Add(new ZoneAttr(209, "career", "职业职场", 36)); //职场加油站,成为最有料的职场人 - zones.Add(new ZoneAttr(122, "wild", "野生技术协会", 36)); //炫酷技能大集合,是时候展现真正的技术了 - - //数码 - zones.Add(new ZoneAttr(188, "digital", "数码")); // 主分区 - zones.Add(new ZoneAttr(95, "mobile", "手机平板", 188)); //手机平板、app 和产品教程等相关视频 - zones.Add(new ZoneAttr(189, "pc", "电脑装机", 188)); //电脑、笔记本、装机配件、外设和软件教程等相关视频 - zones.Add(new ZoneAttr(190, "photography", "摄影摄像", 188)); //摄影摄像器材、拍摄剪辑技巧、拍摄作品分享等相关视频 - zones.Add(new ZoneAttr(191, "intelligence_av", "影音智能", 188)); //影音设备、智能硬件、生活家电等相关视频 + zones.Add(new ZoneAttr(124, "social_science", "社科·法律·心理", 36)); //基于社会科学、法学、心理学展开或个人观点输出的知识视频 + zones.Add(new ZoneAttr(228, "humanity_history", "人文历史", 36)); //看看古今人物,聊聊历史过往,品品文学典籍 + zones.Add(new ZoneAttr(207, "business", "财经商业", 36)); //说金融市场,谈宏观经济,一起畅聊商业故事 + zones.Add(new ZoneAttr(208, "campus", "校园学习", 36)); //老师很有趣,学生也有才,我们一起搞学习 + zones.Add(new ZoneAttr(209, "career", "职业职场", 36)); //职业分享、升级指南,一起成为最有料的职场人 + zones.Add(new ZoneAttr(229, "design", "设计·创意", 36)); //天马行空,创意设计,都在这里 + zones.Add(new ZoneAttr(122, "skill", "野生技能协会", 36)); //技能党集合,是时候展示真正的技术了 + + //科技 + zones.Add(new ZoneAttr(188, "tech", "科技")); // 主分区 + zones.Add(new ZoneAttr(95, "digital", "数码", 188)); //科技数码产品大全,一起来做发烧友 + zones.Add(new ZoneAttr(230, "application", "软件应用", 188)); //超全软件应用指南 + zones.Add(new ZoneAttr(231, "computer_tech", "计算机技术", 188)); //研究分析、教学演示、经验分享......有关计算机技术的都在这里 + zones.Add(new ZoneAttr(232, "industry", "工业·工程·机械", 188)); //前方高能,机甲重工即将出没 + zones.Add(new ZoneAttr(233, "diy", "极客DIY", 188)); //炫酷技能,极客文化,硬核技巧,准备好你的惊讶 + + //运动 + zones.Add(new ZoneAttr(234, "sports", "运动")); // 主分区 + zones.Add(new ZoneAttr(235, "basketballfootball", "篮球·足球", 234)); //与篮球、足球相关的视频,包括但不限于篮足球赛事、教学、评述、剪辑、剧情等相关内容 + zones.Add(new ZoneAttr(164, "aerobics", "健身", 234)); //与健身相关的视频,包括但不限于瑜伽、CrossFit、健美、力量举、普拉提、街健等相关内容 + zones.Add(new ZoneAttr(236, "athletic", "竞技体育", 234)); //与竞技体育相关的视频,包括但不限于乒乓、羽毛球、排球、赛车等竞技项目的赛事、评述、剪辑、剧情等相关内容 + zones.Add(new ZoneAttr(237, "culture", "运动文化", 234)); //与运动文化相关的视频,包络但不限于球鞋、球衣、球星卡等运动衍生品的分享、解读,体育产业的分析、科普等相关内容 + zones.Add(new ZoneAttr(238, "comprehensive", "运动综合", 234)); //与运动综合相关的视频,包括但不限于钓鱼、骑行、滑板等日常运动分享、教学、Vlog等相关内容 //汽车 zones.Add(new ZoneAttr(223, "car", "汽车")); // 主分区 zones.Add(new ZoneAttr(176, "life", "汽车生活", 223)); //分享汽车及出行相关的生活体验类视频 zones.Add(new ZoneAttr(224, "culture", "汽车文化", 223)); //车迷的精神圣地,包括汽车赛事、品牌历史、汽车改装、经典车型和汽车模型等 zones.Add(new ZoneAttr(225, "geek", "汽车极客", 223)); //汽车硬核达人聚集地,包括DIY造车、专业评测和技术知识分享 + zones.Add(new ZoneAttr(240, "motorcycle", "摩托车", 223)); //骑士们集合啦 zones.Add(new ZoneAttr(226, "smart", "智能出行", 223)); //探索新能源汽车和未来智能出行的前沿阵地 zones.Add(new ZoneAttr(227, "strategy", "购车攻略", 223)); //丰富详实的购车建议和新车体验 //生活 zones.Add(new ZoneAttr(160, "life", "生活")); // 主分区 zones.Add(new ZoneAttr(138, "funny", "搞笑", 160)); //各种沙雕有趣的搞笑剪辑,挑战,表演,配音等视频 - zones.Add(new ZoneAttr(21, "daily", "日常", 160)); //记录日常生活,分享生活故事 + zones.Add(new ZoneAttr(239, "home", "家居房产", 160)); //与买房、装修、居家生活相关的分享 zones.Add(new ZoneAttr(161, "handmake", "手工", 160)); //手工制品的制作过程或成品展示、教程、测评类视频 zones.Add(new ZoneAttr(162, "painting", "绘画", 160)); //绘画过程或绘画教程,以及绘画相关的所有视频 - zones.Add(new ZoneAttr(163, "sports", "运动", 160)); //运动相关的记录、教程、装备评测和精彩瞬间剪辑视频 - zones.Add(new ZoneAttr(174, "other", "其他", 160)); //对分区归属不明的视频进行归纳整合的特定分区 + zones.Add(new ZoneAttr(21, "daily", "日常", 160)); //记录日常生活,分享生活故事 //美食 zones.Add(new ZoneAttr(211, "food", "美食")); // 主分区 @@ -143,11 +153,9 @@ namespace DownKyi.Core.BiliApi.Zone //时尚 zones.Add(new ZoneAttr(155, "fashion", "时尚")); // 主分区 - zones.Add(new ZoneAttr(157, "makeup", "美妆", 155)); //涵盖妆容、发型、美甲等教程,彩妆、护肤相关产品测评、分享等 - zones.Add(new ZoneAttr(158, "clothing", "服饰", 155)); //服饰风格、搭配技巧相关的展示和教程视频 - zones.Add(new ZoneAttr(164, "aerobics", "健身", 155)); //器械、有氧、拉伸运动等,以达到强身健体、减肥瘦身、形体塑造目的 - zones.Add(new ZoneAttr(159, "catwalk", "T台", 155)); //发布会走秀现场及模特相关时尚片、采访、后台花絮 - zones.Add(new ZoneAttr(192, "trends", "风尚标", 155)); //时尚明星专访、街拍、时尚购物相关知识科普 + zones.Add(new ZoneAttr(157, "makeup", "美妆护肤", 155)); //彩妆护肤、美甲美发、仿妆、医美相关内容分享或产品测评 + zones.Add(new ZoneAttr(158, "clothing", "穿搭", 155)); //穿搭风格、穿搭技巧的展示分享,涵盖衣服、鞋靴、箱包配件、配饰(帽子、钟表、珠宝首饰)等 + zones.Add(new ZoneAttr(159, "trend", "时尚潮流", 155)); //时尚街拍、时装周、时尚大片,时尚品牌、潮流等行业相关记录及知识科普 //资讯 zones.Add(new ZoneAttr(202, "information", "资讯")); // 主分区 @@ -158,8 +166,10 @@ namespace DownKyi.Core.BiliApi.Zone //娱乐 zones.Add(new ZoneAttr(5, "ent", "娱乐")); // 主分区 - zones.Add(new ZoneAttr(71, "variety", "综艺", 5)); //国内外有趣的综艺和综艺相关精彩剪辑 - zones.Add(new ZoneAttr(137, "star", "明星", 5)); //娱乐圈动态、明星资讯相关 + zones.Add(new ZoneAttr(71, "variety", "综艺", 5)); //所有综艺相关,全部一手掌握! + zones.Add(new ZoneAttr(241, "talker", "娱乐杂谈", 5)); //娱乐人物解读、娱乐热点点评、娱乐行业分析 + zones.Add(new ZoneAttr(242, "fans", "粉丝创作", 5)); //粉丝向创作视频 + zones.Add(new ZoneAttr(137, "celebrity", "明星综合", 5)); //娱乐圈动态、明星资讯相关 //影视 zones.Add(new ZoneAttr(181, "cinephile", "影视")); // 主分区 diff --git a/DownKyi.Core/BiliApi/Zone/VideoZoneIcon.cs b/DownKyi.Core/BiliApi/Zone/VideoZoneIcon.cs new file mode 100644 index 0000000..45e29aa --- /dev/null +++ b/DownKyi.Core/BiliApi/Zone/VideoZoneIcon.cs @@ -0,0 +1,85 @@ +namespace DownKyi.Core.BiliApi.Zone +{ + /// + /// 视频分区图标 + /// + public class VideoZoneIcon + { + private static VideoZoneIcon instance; + + /// + /// 获取VideoZoneIcon实例 + /// + /// + public static VideoZoneIcon Instance() + { + if (instance == null) + { + instance = new VideoZoneIcon(); + } + return instance; + } + + /// + /// 隐藏VideoZoneIcon()方法,必须使用单例模式 + /// + private VideoZoneIcon() { } + + /// + /// 根据tid,获取视频分区图标 + /// + /// + /// + public string GetZoneImageKey(int tid) + { + switch (tid) + { + case 1: + return "Zone.dougaDrawingImage"; + case 13: + return "Zone.animeDrawingImage"; + case 167: + return "Zone.guochuangDrawingImage"; + case 3: + return "Zone.musicDrawingImage"; + case 129: + return "Zone.danceDrawingImage"; + case 4: + return "Zone.gameDrawingImage"; + case 36: + return "Zone.techDrawingImage"; + case 188: + return "Zone.digitalDrawingImage"; + case 234: + return "Zone.sportsDrawingImage"; + case 223: + return "Zone.carDrawingImage"; + case 160: + return "Zone.lifeDrawingImage"; + case 211: + return "Zone.foodDrawingImage"; + case 217: + return "Zone.animalDrawingImage"; + case 119: + return "Zone.kichikuDrawingImage"; + case 155: + return "Zone.fashionDrawingImage"; + case 202: + return "Zone.informationDrawingImage"; + case 5: + return "Zone.entDrawingImage"; + case 181: + return "Zone.cinephileDrawingImage"; + case 177: + return "Zone.documentaryDrawingImage"; + case 23: + return "Zone.movieDrawingImage"; + case 11: + return "Zone.teleplayDrawingImage"; + default: + return "videoUpDrawingImage"; + } + } + + } +} diff --git a/DownKyi.Core/BiliApi/Zone/ZoneImage.xaml b/DownKyi.Core/BiliApi/Zone/ZoneImage.xaml new file mode 100644 index 0000000..1cdfdfb --- /dev/null +++ b/DownKyi.Core/BiliApi/Zone/ZoneImage.xaml @@ -0,0 +1,293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DownKyi.Core/Danmaku2Ass/Studio.cs b/DownKyi.Core/Danmaku2Ass/Studio.cs index 97acc48..630e323 100644 --- a/DownKyi.Core/Danmaku2Ass/Studio.cs +++ b/DownKyi.Core/Danmaku2Ass/Studio.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; @@ -67,7 +68,12 @@ namespace DownKyi.Core.Danmaku2Ass public void CreateFile(string fileName, string text) { - File.WriteAllText(fileName, text); + try + { + File.WriteAllText(fileName, text); + } + catch (Exception) + { } } public Dictionary Report() diff --git a/DownKyi.Core/DownKyi.Core.csproj b/DownKyi.Core/DownKyi.Core.csproj index 834a58f..131523f 100644 --- a/DownKyi.Core/DownKyi.Core.csproj +++ b/DownKyi.Core/DownKyi.Core.csproj @@ -194,6 +194,7 @@ + @@ -264,7 +265,12 @@ - + + + MSBuild:Compile + Designer + + diff --git a/DownKyi.Core/FFmpeg/FFmpegHelper.cs b/DownKyi.Core/FFmpeg/FFmpegHelper.cs index b77bf99..e94b204 100644 --- a/DownKyi.Core/FFmpeg/FFmpegHelper.cs +++ b/DownKyi.Core/FFmpeg/FFmpegHelper.cs @@ -7,13 +7,13 @@ namespace DownKyi.Core.FFmpeg { public static class FFmpegHelper { - private const string Tag = "PageToolboxDelogo"; + private const string Tag = "FFmpegHelper"; /// /// 合并音频和视频 /// - /// - /// + /// 音频 + /// 视频 /// public static bool MergeVideo(string video1, string video2, string destVideo) { @@ -24,7 +24,7 @@ namespace DownKyi.Core.FFmpeg } if (video2 == null || !File.Exists(video2)) { - param = $"-i \"{video1}\" -acodec copy -vcodec copy -f mp4 \"{destVideo}\""; + param = $"-i \"{video1}\" -acodec copy -f aac \"{destVideo}\""; } if (!File.Exists(video1) && !File.Exists(video2)) { return false; } diff --git a/DownKyi/App.xaml b/DownKyi/App.xaml index 34ef81c..7835032 100644 --- a/DownKyi/App.xaml +++ b/DownKyi/App.xaml @@ -9,6 +9,7 @@ + diff --git a/DownKyi/App.xaml.cs b/DownKyi/App.xaml.cs index 7ac3eaf..f39e512 100644 --- a/DownKyi/App.xaml.cs +++ b/DownKyi/App.xaml.cs @@ -40,10 +40,81 @@ namespace DownKyi //DictionaryResource.LoadLanguage("en_US"); // 初始化数据 - // TODO 从数据库读取 DownloadingList = new ObservableCollection(); DownloadedList = new ObservableCollection(); + // test DownloadingList data + DownloadingList.Add(new DownloadingItem + { + MainTitle = "Test1", + Name = "name1=name1=name1=name1=name1=name1=name1=name1=name1=name1=name1", + ZoneImage = (System.Windows.Media.DrawingImage)Current.Resources[Core.BiliApi.Zone.VideoZoneIcon.Instance().GetZoneImageKey(4)], + Resolution = new Resolution + { + Name = "1080P", + Id = 64 + }, + AudioCodecName = "194K", + }); + DownloadingList.Add(new DownloadingItem + { + MainTitle = "Test2", + Name = "name2", + ZoneImage = (System.Windows.Media.DrawingImage)Current.Resources[Core.BiliApi.Zone.VideoZoneIcon.Instance().GetZoneImageKey(3)], + Resolution = new Resolution + { + Name = "1080P", + Id = 64 + }, + Duration = "1h23m50s", + VideoCodecName = "HEVC", + AudioCodecName = "194K", + }); + DownloadingList.Add(new DownloadingItem + { + MainTitle = "Test3", + Name = "name3", + ZoneImage = (System.Windows.Media.DrawingImage)Current.Resources[Core.BiliApi.Zone.VideoZoneIcon.Instance().GetZoneImageKey(3)], + Resolution = new Resolution + { + Name = "720P", + Id = 64 + }, + Duration = "23m50s", + VideoCodecName = "AVC", + AudioCodecName = "194K", + }); + DownloadingList.Add(new DownloadingItem + { + MainTitle = "Test4", + Name = "name4", + ZoneImage = (System.Windows.Media.DrawingImage)Current.Resources[Core.BiliApi.Zone.VideoZoneIcon.Instance().GetZoneImageKey(3)], + Resolution = new Resolution + { + Name = "720P", + Id = 64 + }, + Duration = "23m50s", + VideoCodecName = "AVC", + AudioCodecName = "194K", + }); + DownloadingList.Add(new DownloadingItem + { + MainTitle = "Test5", + Name = "name5", + ZoneImage = (System.Windows.Media.DrawingImage)Current.Resources[Core.BiliApi.Zone.VideoZoneIcon.Instance().GetZoneImageKey(5)], + Resolution = new Resolution + { + Name = "720P", + Id = 64 + }, + Duration = "23m50s", + VideoCodecName = "AVC", + AudioCodecName = "194K", + }); + + // TODO 从数据库读取 + // 启动下载服务 downloadService = new AriaDownloadService(DownloadingList, DownloadedList); downloadService.Start(); diff --git a/DownKyi/Images/ButtonIcon.cs b/DownKyi/Images/ButtonIcon.cs index 73caefb..d747936 100644 --- a/DownKyi/Images/ButtonIcon.cs +++ b/DownKyi/Images/ButtonIcon.cs @@ -68,6 +68,41 @@ V8.5z M5.5,10v15h21V10H5.5z", Fill = "#FF000000" }; + + Delete = new VectorImage + { + Height = 18, + Width = 18, + Data = @"M634.29 513.52 l364.34 -363.32 q25.37 -27.4 25.37 -60.89 q0 -33.49 -26.38 -59.88 q-26.38 -26.39 -59.88 -26.39 + q-33.49 0 -60.9 25.38 l-363.32 364.33 l-363.32 -372.45 q-28.42 -20.3 -65.46 -20.3 q-37.04 0 -64.44 20.3 + q-20.3 27.4 -20.3 64.44 q0 37.04 20.3 65.46 l372.45 363.32 l-364.33 363.32 q-25.38 27.41 -25.38 60.9 q0 33.49 26.39 59.88 + q26.39 26.38 59.88 26.38 q33.49 0 60.89 -25.37 l363.32 -364.34 l363.32 364.34 q27.41 25.37 60.9 25.37 q33.49 0 59.88 -26.38 + q26.38 -26.38 26.38 -59.88 q0 -33.49 -25.37 -60.9 l-364.34 -363.32 Z", + Fill = "#FF000000" + }; + + Start = new VectorImage + { + Height = 20, + Width = 17, + Data = @"M895.12 402.34 l-633.28 -383.81 q-30.16 -17.82 -64.43 -18.51 q-34.27 -0.69 -65.11 16.45 q-30.84 17.13 -47.97 47.29 + q-17.13 30.16 -17.13 64.42 l0 767.62 q0 34.27 17.13 63.74 q17.14 29.47 47.97 47.29 q30.84 17.82 65.11 17.13 + q34.27 -0.69 64.43 -18.5 l633.28 -383.81 q28.79 -17.82 45.24 -46.6 q16.45 -28.79 16.45 -63.06 q0 -34.27 -16.45 -63.05 + q-16.45 -28.79 -45.24 -46.61 Z", + Fill = "#FF000000" + }; + + Pause = new VectorImage + { + Height = 20, + Width = 15, + Data = @"M255.66 0 q-53.75 0 -90.97 37.21 q-37.21 37.21 -37.21 90.96 l0 769.04 q1.38 55.12 37.21 90.95 q35.84 35.84 90.28 35.84 + q54.44 0 90.96 -35.84 q36.52 -35.83 37.9 -90.95 l0 -769.04 q-1.38 -53.75 -38.59 -90.96 q-37.21 -37.21 -89.58 -37.21 + ZM768.34 0 q-52.37 0 -89.58 37.21 q-37.21 37.21 -38.59 90.96 l0 769.04 q1.38 55.12 37.9 90.95 q36.52 35.84 90.96 35.84 + q54.44 0 90.28 -35.84 q35.83 -35.83 37.21 -90.95 l0 -769.04 q0 -53.75 -37.21 -90.96 q-37.22 -37.21 -90.97 -37.21 Z", + Fill = "#FF000000" + }; + } public VectorImage GeneralSearch { get; private set; } @@ -75,5 +110,9 @@ public VectorImage DownloadManage { get; private set; } public VectorImage Toolbox { get; private set; } + public VectorImage Delete { get; private set; } + public VectorImage Start { get; private set; } + public VectorImage Pause { get; private set; } + } } diff --git a/DownKyi/Images/LogoIcon.cs b/DownKyi/Images/LogoIcon.cs index 853394f..6e074bc 100644 --- a/DownKyi/Images/LogoIcon.cs +++ b/DownKyi/Images/LogoIcon.cs @@ -1,6 +1,6 @@ namespace DownKyi.Images { - class LogoIcon + public class LogoIcon { private static LogoIcon instance; public static LogoIcon Instance() diff --git a/DownKyi/Languages/Default.xaml b/DownKyi/Languages/Default.xaml index 559fef5..ab0dcf4 100644 --- a/DownKyi/Languages/Default.xaml +++ b/DownKyi/Languages/Default.xaml @@ -68,6 +68,23 @@ 正在下载 已下载 + 音频 + 视频 + 弹幕 + 字幕 + 封面 + 正在解析…… + 下载中…… + 混流中…… + 暂停中…… + 等待中…… + + 正在下载 + 个视频! + 全部暂停 + 全部开始 + 全部删除 + 按回车键应用设置 diff --git a/DownKyi/Models/DownloadStatus.cs b/DownKyi/Models/DownloadStatus.cs index a462076..8aa0bd7 100644 --- a/DownKyi/Models/DownloadStatus.cs +++ b/DownKyi/Models/DownloadStatus.cs @@ -4,6 +4,7 @@ { NOT_STARTED, // 未开始,从未开始下载 WAIT_FOR_DOWNLOAD, // 等待下载,下载过,但是启动本次下载周期未开始,如重启程序后未开始 + PAUSE_STARTED, // 暂停启动下载 PAUSE, // 暂停 DOWNLOADING, // 下载中 DOWNLOAD_SUCCEED, // 下载成功 diff --git a/DownKyi/Models/DownloadingItem.cs b/DownKyi/Models/DownloadingItem.cs index eacb533..c2aeb73 100644 --- a/DownKyi/Models/DownloadingItem.cs +++ b/DownKyi/Models/DownloadingItem.cs @@ -1,20 +1,64 @@ using DownKyi.Core.BiliApi.VideoStream.Models; +using DownKyi.Images; +using DownKyi.Utils; +using Prism.Commands; using Prism.Mvvm; +using System; +using System.Collections.Generic; +using System.Windows.Media; namespace DownKyi.Models { public class DownloadingItem : BindableBase { + public DownloadingItem() + { + // 唯一id + Uuid = Guid.NewGuid().ToString("N"); + + // 初始化下载的文件列表 + DownloadFiles = new List(); + + // 初始化需要下载的内容 + NeedDownloadContent = new Dictionary + { + { "downloadAudio", true }, + { "downloadVideo", true }, + { "downloadDanmaku", true }, + { "downloadSubtitle", true }, + { "downloadCover", true } + }; + + // 暂停继续按钮 + StartOrPause = ButtonIcon.Instance().Pause; + StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); + + // 删除按钮 + Delete = ButtonIcon.Instance().Delete; + Delete.Fill = DictionaryResource.GetColor("ColorPrimary"); + } + public PlayUrl PlayUrl { get; set; } // 此条下载项的id public string Uuid { get; } + // Aria相关 + public string Gid { get; set; } + // 文件路径,不包含扩展名,所有内容均以此路径下载 public string FilePath { get; set; } - // Aria相关 - public string Gid { get; set; } + // 下载的文件 + public List DownloadFiles { get; private set; } + + // 文件大小 + private string fileSize; + public string FileSize + { + get => fileSize; + set => SetProperty(ref fileSize, value); + } // 视频类别 public PlayStreamType PlayStreamType { get; set; } @@ -28,28 +72,43 @@ namespace DownKyi.Models // 视频封面的url public string CoverUrl { get; set; } + private DrawingImage zoneImage; + public DrawingImage ZoneImage + { + get => zoneImage; + set => SetProperty(ref zoneImage, value); + } + // 视频序号 private int order; public int Order { - get { return order; } - set { SetProperty(ref order, value); } + get => order; + set => SetProperty(ref order, value); } // 视频主标题 private string mainTitle; public string MainTitle { - get { return mainTitle; } - set { SetProperty(ref mainTitle, value); } + get => mainTitle; + set => SetProperty(ref mainTitle, value); } // 视频标题 private string name; public string Name { - get { return name; } - set { SetProperty(ref name, value); } + get => name; + set => SetProperty(ref name, value); + } + + // 时长 + private string duration; + public string Duration + { + get => duration; + set => SetProperty(ref duration, value); } // 音频编码 @@ -57,35 +116,160 @@ namespace DownKyi.Models private string audioCodecName; public string AudioCodecName { - get { return audioCodecName; } - set { SetProperty(ref audioCodecName, value); } + get => audioCodecName; + set => SetProperty(ref audioCodecName, value); } // 视频编码 - public int VideoCodecId { get; set; } + // "hev1.2.4.L156.90" + // "avc1.640034" + public string VideoCodecId { get; set; } + + // 视频编码名称,AVC、HEVC private string videoCodecName; public string VideoCodecName { - get { return videoCodecName; } - set { SetProperty(ref videoCodecName, value); } + get => videoCodecName; + set => SetProperty(ref videoCodecName, value); + } + + // 视频画质 + private Resolution resolution; + public Resolution Resolution + { + get => resolution; + set => SetProperty(ref resolution, value); } - // 下载内容(音频、视频、弹幕、字幕、封面) + // 需要下载的内容 + public Dictionary NeedDownloadContent { get; private set; } + + // 正在下载内容(音频、视频、弹幕、字幕、封面) private string downloadContent; public string DownloadContent { - get { return downloadContent; } - set { SetProperty(ref downloadContent, value); } + get => downloadContent; + set => SetProperty(ref downloadContent, value); } // 下载状态 public DownloadStatus DownloadStatus { get; set; } + + // 下载状态显示 private string downloadStatusTitle; public string DownloadStatusTitle { - get { return downloadStatusTitle; } - set { SetProperty(ref downloadStatusTitle, value); } + get => downloadStatusTitle; + set => SetProperty(ref downloadStatusTitle, value); + } + + // 下载进度 + private float progress; + public float Progress + { + get => progress; + set => SetProperty(ref progress, value); + } + + // 已下载大小/文件大小 + private string downloadingFileSize; + public string DownloadingFileSize + { + get => downloadingFileSize; + set => SetProperty(ref downloadingFileSize, value); + } + + // 下载的最高速度 + public long MaxSpeed { get; set; } + + // 下载速度 + private string speedDisplay; + public string SpeedDisplay + { + get => speedDisplay; + set => SetProperty(ref speedDisplay, value); + } + + + #region 控制按钮 + + private VectorImage startOrPause; + public VectorImage StartOrPause + { + get => startOrPause; + set => SetProperty(ref startOrPause, value); } + private VectorImage delete; + public VectorImage Delete + { + get => delete; + set => SetProperty(ref delete, value); + } + + #endregion + + #region 命令申明 + + // 下载列表暂停继续事件 + private DelegateCommand startOrPauseCommand; + public DelegateCommand StartOrPauseCommand => startOrPauseCommand ?? (startOrPauseCommand = new DelegateCommand(ExecuteStartOrPauseCommand)); + + /// + /// 下载列表暂停继续事件 + /// + private void ExecuteStartOrPauseCommand() + { + switch (DownloadStatus) + { + case DownloadStatus.NOT_STARTED: + case DownloadStatus.WAIT_FOR_DOWNLOAD: + DownloadStatus = DownloadStatus.PAUSE_STARTED; + StartOrPause = ButtonIcon.Instance().Start; + StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); + break; + case DownloadStatus.PAUSE_STARTED: + DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + StartOrPause = ButtonIcon.Instance().Pause; + StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); + break; + case DownloadStatus.PAUSE: + DownloadStatus = DownloadStatus.DOWNLOADING; + StartOrPause = ButtonIcon.Instance().Pause; + StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); + break; + case DownloadStatus.DOWNLOADING: + DownloadStatus = DownloadStatus.PAUSE; + StartOrPause = ButtonIcon.Instance().Start; + StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); + break; + case DownloadStatus.DOWNLOAD_SUCCEED: + // 下载成功后会从下载列表中删除 + // 不会出现此分支 + break; + case DownloadStatus.DOWNLOAD_FAILED: + DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD; + StartOrPause = ButtonIcon.Instance().Pause; + StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary"); + break; + default: + break; + } + } + + // 下载列表删除事件 + private DelegateCommand deleteCommand; + public DelegateCommand DeleteCommand => deleteCommand ?? (deleteCommand = new DelegateCommand(ExecuteDeleteCommand)); + + /// + /// 下载列表删除事件 + /// + private void ExecuteDeleteCommand() + { + App.DownloadingList.Remove(this); + } + + #endregion + } } diff --git a/DownKyi/Services/Download/AriaDownloadService.cs b/DownKyi/Services/Download/AriaDownloadService.cs index dab3dff..3e47673 100644 --- a/DownKyi/Services/Download/AriaDownloadService.cs +++ b/DownKyi/Services/Download/AriaDownloadService.cs @@ -4,15 +4,20 @@ using DownKyi.Core.Aria2cNet.Client.Entity; using DownKyi.Core.Aria2cNet.Server; using DownKyi.Core.BiliApi.Login; using DownKyi.Core.BiliApi.VideoStream; +using DownKyi.Core.BiliApi.VideoStream.Models; using DownKyi.Core.Danmaku2Ass; +using DownKyi.Core.FFmpeg; using DownKyi.Core.Logging; using DownKyi.Core.Settings; using DownKyi.Core.Storage; +using DownKyi.Core.Utils; using DownKyi.Models; +using DownKyi.Utils; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -30,31 +35,164 @@ namespace DownKyi.Services.Download Tag = "AriaDownloadService"; } + #region 音视频 + + /// + /// 下载音频,返回下载文件路径 + /// + /// + /// public string DownloadAudio(DownloadingItem downloading) { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingAudio"); + // 如果没有Dash,返回null - if(downloading.PlayUrl.Dash == null) { return null; } + if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; } // 如果audio列表没有内容,则返回null if (downloading.PlayUrl.Dash.Audio == null) { return null; } else if (downloading.PlayUrl.Dash.Audio.Count == 0) { return null; } + // 根据音频id匹配 + PlayUrlDashVideo downloadAudio = null; + foreach (PlayUrlDashVideo audio in downloading.PlayUrl.Dash.Audio) + { + if (audio.Id == downloading.AudioCodecId) + { + downloadAudio = audio; + break; + } + } + + return DownloadVideo(downloading, downloadAudio); + //// 如果音频为空,说明没有匹配到可下载的音频 + //if (downloadAudio == null) { return null; } + + //// 下载链接 + //List audioUrls = new List(); + //if (downloadAudio.BaseUrl != null) { audioUrls.Add(downloadAudio.BaseUrl); } + //if (downloadAudio.BackupUrl != null) { audioUrls.AddRange(downloadAudio.BackupUrl); } + + //// 路径 + //string[] temp = downloading.FilePath.Split('/'); + //string path = downloading.FilePath.Replace(temp[temp.Length - 1], ""); + + //// 下载文件名 + //string fileName = Guid.NewGuid().ToString("N"); + //fileName = Path.Combine(path, fileName); + + //// 记录本次下载的文件 + //downloading.DownloadFiles.Add(fileName); + + //// 开始下载 + //DownloadResult downloadStatus = DownloadByAria(downloading, audioUrls, path, fileName); + //switch (downloadStatus) + //{ + // case DownloadResult.SUCCESS: + // return fileName; + // case DownloadResult.FAILED: + // return null; + // case DownloadResult.ABORT: + // return null; + // default: + // return null; + //} + } + + /// + /// 下载视频,返回下载文件路径 + /// + /// + /// + public string DownloadVideo(DownloadingItem downloading) + { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); + + // 如果没有Dash,返回null + if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; } - throw new NotImplementedException(); + // 如果Video列表没有内容,则返回null + if (downloading.PlayUrl.Dash.Video == null) { return null; } + else if (downloading.PlayUrl.Dash.Video.Count == 0) { return null; } + + // 根据视频编码匹配 + PlayUrlDashVideo downloadVideo = null; + foreach (PlayUrlDashVideo video in downloading.PlayUrl.Dash.Video) + { + if (video.Id == downloading.Resolution.Id && video.Codecs == downloading.VideoCodecId) + { + downloadVideo = video; + break; + } + } + + return DownloadVideo(downloading, downloadVideo); } + /// + /// 将下载音频和视频的函数中相同代码抽象出来 + /// + /// + /// + /// + private string DownloadVideo(DownloadingItem downloading, PlayUrlDashVideo downloadVideo) + { + // 如果为空,说明没有匹配到可下载的音频视频 + if (downloadVideo == null) { return null; } + + // 下载链接 + List urls = new List(); + if (downloadVideo.BaseUrl != null) { urls.Add(downloadVideo.BaseUrl); } + if (downloadVideo.BackupUrl != null) { urls.AddRange(downloadVideo.BackupUrl); } + + // 路径 + string[] temp = downloading.FilePath.Split('/'); + string path = downloading.FilePath.Replace(temp[temp.Length - 1], ""); + + // 下载文件名 + string fileName = Guid.NewGuid().ToString("N"); + fileName = Path.Combine(path, fileName); + + // 记录本次下载的文件 + downloading.DownloadFiles.Add(fileName); + + // 开始下载 + DownloadResult downloadStatus = DownloadByAria(downloading, urls, path, fileName); + switch (downloadStatus) + { + case DownloadResult.SUCCESS: + return fileName; + case DownloadResult.FAILED: + return null; + case DownloadResult.ABORT: + return null; + default: + return null; + } + } + + #endregion + /// /// 下载封面 /// /// - public void DownloadCover(DownloadingItem downloading) + public string DownloadCover(DownloadingItem downloading) { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingCover"); + // 查询、保存封面 StorageCover storageCover = new StorageCover(); string cover = storageCover.GetCover(downloading.Avid, downloading.Bvid, downloading.Cid, downloading.CoverUrl); if (cover == null) { - return; + return null; } // 图片的扩展名 @@ -67,24 +205,39 @@ namespace DownKyi.Services.Download // 复制图片到指定位置 try { - File.Copy(coverPath, $"{downloading.FilePath}.{fileExtension}"); + string fileName = $"{downloading.FilePath}.{fileExtension}"; + File.Copy(coverPath, fileName); + + // 记录本次下载的文件 + downloading.DownloadFiles.Add(fileName); + + return fileName; } catch (Exception e) { Core.Utils.Debugging.Console.PrintLine(e); LogManager.Error(Tag, e); } + + return null; } /// /// 下载弹幕 /// /// - public void DownloadDanmaku(DownloadingItem downloading) + public string DownloadDanmaku(DownloadingItem downloading) { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingDanmaku"); + string title = $"{downloading.Name}"; string assFile = $"{downloading.FilePath}.ass"; + // 记录本次下载的文件 + downloading.DownloadFiles.Add(assFile); + int screenWidth = SettingsManager.GetInstance().GetDanmakuScreenWidth(); int screenHeight = SettingsManager.GetInstance().GetDanmakuScreenHeight(); //if (SettingsManager.GetInstance().IsCustomDanmakuResolution() != AllowStatus.YES) @@ -117,14 +270,22 @@ namespace DownKyi.Services.Download .SetBottomFilter(SettingsManager.GetInstance().GetDanmakuBottomFilter() == AllowStatus.YES) .SetScrollFilter(SettingsManager.GetInstance().GetDanmakuScrollFilter() == AllowStatus.YES) .Create(downloading.Avid, downloading.Cid, subtitleConfig, assFile); + + return assFile; } /// /// 下载字幕 /// /// - public void DownloadSubtitle(DownloadingItem downloading) + public List DownloadSubtitle(DownloadingItem downloading) { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingSubtitle"); + + List srtFiles = new List(); + var subRipTexts = VideoStream.GetSubtitle(downloading.Avid, downloading.Bvid, downloading.Cid); foreach (var subRip in subRipTexts) { @@ -132,6 +293,11 @@ namespace DownKyi.Services.Download try { File.WriteAllText(srtFile, subRip.SrtString); + + // 记录本次下载的文件 + downloading.DownloadFiles.Add(srtFile); + + srtFiles.Add(srtFile); } catch (Exception e) { @@ -139,30 +305,44 @@ namespace DownKyi.Services.Download LogManager.Error("DownloadSubtitle()", e); } } - } - public string DownloadVideo(DownloadingItem downloading) - { - throw new NotImplementedException(); + return srtFiles; } /// - /// 停止下载服务 + /// 混流音频和视频 /// - public void End() + /// + /// + /// + /// + public string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid) { - // TODO something + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("MixedFlow"); + downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo"); - // 关闭Aria服务器 - CloseAriaServer(); + string finalFile = $"{downloading.FilePath}.mp4"; + if (videoUid == null) + { + finalFile = $"{downloading.FilePath}.aac"; + } - // 结束任务 - tokenSource.Cancel(); - } + // 合并音视频 + FFmpegHelper.MergeVideo(audioUid, videoUid, finalFile); - public void MixedFlow(DownloadingItem downloading, string audioUid, string videoUid) - { - throw new NotImplementedException(); + // 获取文件大小 + if (File.Exists(finalFile)) + { + FileInfo info = new FileInfo(finalFile); + downloading.FileSize = Format.FormatFileSize(info.Length); + } + else + { + downloading.FileSize = Format.FormatFileSize(0); + } + + return finalFile; } /// @@ -171,11 +351,16 @@ namespace DownKyi.Services.Download /// public void Parse(DownloadingItem downloading) { + // 更新状态显示 + downloading.DownloadStatusTitle = DictionaryResource.GetString("Parsing"); + downloading.DownloadContent = string.Empty; + if (downloading.PlayUrl != null && downloading.DownloadStatus == DownloadStatus.NOT_STARTED) { return; } + // 解析 switch (downloading.PlayStreamType) { case PlayStreamType.VIDEO: @@ -192,6 +377,21 @@ namespace DownKyi.Services.Download } } + /// + /// 停止下载服务 + /// + public void End() + { + // TODO + // 保存数据 + + // 关闭Aria服务器 + CloseAriaServer(); + + // 结束任务 + tokenSource.Cancel(); + } + /// /// 启动下载服务 /// @@ -206,58 +406,34 @@ namespace DownKyi.Services.Download /// /// 执行任务 /// - private async void DoWork() + private void DoWork() { CancellationToken cancellationToken = tokenSource.Token; while (true) { int maxDownloading = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(); - int countDownloading = 0; - bool isOutOfMaxDownloading = false; + int downloadingCount = 0; foreach (DownloadingItem downloading in downloadingList) { - // 对正在下载的元素计数 if (downloading.DownloadStatus == DownloadStatus.DOWNLOADING) { - countDownloading++; + downloadingCount++; } + } - // 正在下载数量等于最大下载数量,退出本次循环 - if (countDownloading == maxDownloading) + foreach (DownloadingItem downloading in downloadingList) + { + if (downloadingCount >= maxDownloading) { break; } - // 正在下载数量大于最大下载数量 - if (countDownloading > maxDownloading) + // 开始下载 + if (downloading.DownloadStatus == DownloadStatus.NOT_STARTED || downloading.DownloadStatus == DownloadStatus.WAIT_FOR_DOWNLOAD) { - isOutOfMaxDownloading = true; + SingleDownload(downloading); + downloadingCount++; } - - // 将超过下载数量的元素暂停 - if (isOutOfMaxDownloading) - { - if (downloading.DownloadStatus == DownloadStatus.DOWNLOADING) - { - downloading.DownloadStatus = DownloadStatus.PAUSE; - } - } - - await Task.Run(new Action(() => - { - downloading.DownloadStatus = DownloadStatus.DOWNLOADING; - - // 依次下载音频、视频、弹幕、字幕、封面等内容 - Parse(downloading); - string audioUid = DownloadAudio(downloading); - string videoUid = DownloadVideo(downloading); - DownloadDanmaku(downloading); - DownloadSubtitle(downloading); - DownloadCover(downloading); - MixedFlow(downloading, audioUid, videoUid); - }), - (tokenSource = new CancellationTokenSource()).Token); - } // 判断是否该结束线程,若为true,跳出while循环 @@ -269,8 +445,165 @@ namespace DownKyi.Services.Download } // 降低CPU占用 - Thread.Sleep(500); + Thread.Sleep(100); + } + } + + /// + /// 下载一个视频 + /// + /// + /// + private async void SingleDownload(DownloadingItem downloading) + { + await Task.Run(new Action(() => + { + downloading.DownloadStatus = DownloadStatus.DOWNLOADING; + + // 初始化 + downloading.DownloadStatusTitle = string.Empty; + downloading.DownloadContent = string.Empty; + downloading.DownloadFiles.Clear(); + + // 解析并依次下载音频、视频、弹幕、字幕、封面等内容 + Parse(downloading); + + // 暂停 + Pause(downloading); + + string audioUid = null; + // 如果需要下载音频 + if (downloading.NeedDownloadContent["downloadAudio"]) + { + audioUid = DownloadAudio(downloading); + } + + // 暂停 + Pause(downloading); + + string videoUid = null; + // 如果需要下载视频 + if (downloading.NeedDownloadContent["downloadVideo"]) + { + videoUid = DownloadVideo(downloading); + } + + // 暂停 + Pause(downloading); + + string outputDanmaku = null; + // 如果需要下载弹幕 + if (downloading.NeedDownloadContent["downloadDanmaku"]) + { + outputDanmaku = DownloadDanmaku(downloading); + } + + // 暂停 + Pause(downloading); + + List outputSubtitles = null; + // 如果需要下载字幕 + if (downloading.NeedDownloadContent["downloadSubtitle"]) + { + outputSubtitles = DownloadSubtitle(downloading); + } + + // 暂停 + Pause(downloading); + + string outputCover = null; + // 如果需要下载封面 + if (downloading.NeedDownloadContent["downloadCover"]) + { + outputCover = DownloadCover(downloading); + } + + // 暂停 + Pause(downloading); + + // 混流 + string outputMedia = MixedFlow(downloading, audioUid, videoUid); + + // 暂停 + Pause(downloading); + + // 检测音频、视频是否下载成功 + if (downloading.NeedDownloadContent["downloadAudio"] || downloading.NeedDownloadContent["downloadVideo"]) + { + // 只有下载音频不下载视频时才输出aac + // 只要下载视频就输出mp4 + if (File.Exists(outputMedia)) + { + // 成功 + } + } + + // 检测弹幕是否下载成功 + if (downloading.NeedDownloadContent["downloadDanmaku"] && File.Exists(outputDanmaku)) + { + // 成功 + } + + // 检测字幕是否下载成功 + if (downloading.NeedDownloadContent["downloadSubtitle"]) + { + foreach (string subtitle in outputSubtitles) + { + if (File.Exists(subtitle)) + { + // 成功 + } + } + } + + // 检测封面是否下载成功 + if (downloading.NeedDownloadContent["downloadCover"] && File.Exists(outputCover)) + { + // 成功 + } + + // TODO + // 将下载结果写入数据库 + // 包括下载请求的DownloadingItem对象, + // 下载结果是否成功等 + // 对是否成功的判断:只要outputMedia存在则成功,否则失败 + + })); + } + + /// + /// 强制暂停 + /// + /// + private void Pause(DownloadingItem downloading) + { + string oldStatus = downloading.DownloadStatusTitle; + downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing"); + while (downloading.DownloadStatus == DownloadStatus.PAUSE) + { + // 降低CPU占用 + Thread.Sleep(100); } + downloading.DownloadStatusTitle = DictionaryResource.GetString("Waiting"); + + int maxDownloading = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(); + int downloadingCount; + do + { + downloadingCount = 0; + foreach (DownloadingItem item in downloadingList) + { + if (item.DownloadStatus == DownloadStatus.DOWNLOADING) + { + downloadingCount++; + } + } + + // 降低CPU占用 + Thread.Sleep(100); + } while (downloadingCount > maxDownloading); + + downloading.DownloadStatusTitle = oldStatus; } /// @@ -340,11 +673,8 @@ namespace DownKyi.Services.Download /// /// /// - private DownloadResult DownloadByAria(DownloadingItem downloading, List urls, string localFileName) + private DownloadResult DownloadByAria(DownloadingItem downloading, List urls, string path, string localFileName) { - string[] temp = downloading.FilePath.Split('/'); - string path = downloading.FilePath.Replace(temp[temp.Length - 1], ""); - AriaSendOption option = new AriaSendOption { //HttpProxy = $"http://{Settings.GetAriaHttpProxy()}:{Settings.GetAriaHttpProxyListenPort()}", @@ -394,12 +724,39 @@ namespace DownKyi.Services.Download private void AriaTellStatus(long totalLength, long completedLength, long speed, string gid) { - throw new NotImplementedException(); + // 当前的下载视频 + DownloadingItem video = downloadingList.FirstOrDefault(it => it.Gid == gid); + + float percent = 0; + if (totalLength != 0) + { + percent = (float)completedLength / totalLength * 100; + } + + // 根据进度判断本次是否需要更新UI + // TODO 小于多少需要测试 + if (percent - video.Progress < 0.01) { return; } + + // 下载进度 + video.Progress = percent; + + // 下载大小 + video.DownloadingFileSize = Format.FormatFileSize(completedLength) + "/" + Format.FormatFileSize(totalLength); + + // 下载速度 + video.SpeedDisplay = Format.FormatSpeed(speed); + + // 最大下载速度 + if (video.MaxSpeed < speed) + { + video.MaxSpeed = speed; + } + } private void AriaDownloadFinish(bool isSuccess, string downloadPath, string gid, string msg) { - throw new NotImplementedException(); + //throw new NotImplementedException(); } } } diff --git a/DownKyi/Services/Download/IDownloadService.cs b/DownKyi/Services/Download/IDownloadService.cs index a3632b1..8facfb4 100644 --- a/DownKyi/Services/Download/IDownloadService.cs +++ b/DownKyi/Services/Download/IDownloadService.cs @@ -1,4 +1,5 @@ using DownKyi.Models; +using System.Collections.Generic; namespace DownKyi.Services.Download { @@ -7,10 +8,10 @@ namespace DownKyi.Services.Download void Parse(DownloadingItem downloading); string DownloadAudio(DownloadingItem downloading); string DownloadVideo(DownloadingItem downloading); - void DownloadDanmaku(DownloadingItem downloading); - void DownloadSubtitle(DownloadingItem downloading); - void DownloadCover(DownloadingItem downloading); - void MixedFlow(DownloadingItem downloading, string audioUid, string videoUid); + string DownloadDanmaku(DownloadingItem downloading); + List DownloadSubtitle(DownloadingItem downloading); + string DownloadCover(DownloadingItem downloading); + string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid); void Start(); void End(); diff --git a/DownKyi/Themes/ColorBrush.xaml b/DownKyi/Themes/ColorBrush.xaml index bc81eda..181e106 100644 --- a/DownKyi/Themes/ColorBrush.xaml +++ b/DownKyi/Themes/ColorBrush.xaml @@ -23,6 +23,7 @@ + @@ -34,6 +35,7 @@ + diff --git a/DownKyi/Themes/Colors/ColorDefault.xaml b/DownKyi/Themes/Colors/ColorDefault.xaml index fd949c1..7ce177c 100644 --- a/DownKyi/Themes/Colors/ColorDefault.xaml +++ b/DownKyi/Themes/Colors/ColorDefault.xaml @@ -23,6 +23,7 @@ #FFFFFFFF #FF00A1D6 #FFBDBDBD + #FFE4E4E4 #FFBDBDBD #C8BDBDBD #7FBDBDBD @@ -34,6 +35,7 @@ #FFF4F4F4 #FF999999 + #7F999999 #7FD0D0D0 #7FD0D0D0 diff --git a/DownKyi/Utils/DictionaryResource.cs b/DownKyi/Utils/DictionaryResource.cs index 93290d8..71147da 100644 --- a/DownKyi/Utils/DictionaryResource.cs +++ b/DownKyi/Utils/DictionaryResource.cs @@ -26,7 +26,7 @@ namespace DownKyi.Utils /// public static string GetString(string resourceKey) { - return (string)Application.Current.Resources[resourceKey]; + return Application.Current == null ? "" : (string)Application.Current.Resources[resourceKey]; } /// diff --git a/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs b/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs index d46ce89..7972fe8 100644 --- a/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs +++ b/DownKyi/ViewModels/DownloadManager/ViewDownloadingViewModel.cs @@ -5,6 +5,7 @@ using Prism.Mvvm; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Linq; namespace DownKyi.ViewModels.DownloadManager @@ -18,15 +19,63 @@ namespace DownKyi.ViewModels.DownloadManager private ObservableCollection downloadingList; public ObservableCollection DownloadingList { - get { return downloadingList; } - set { SetProperty(ref downloadingList, value); } + get => downloadingList; + set => SetProperty(ref downloadingList, value); } #endregion public ViewDownloadingViewModel(IEventAggregator eventAggregator) : base(eventAggregator) { + // 初始化DownloadingList DownloadingList = App.DownloadingList; + + + //// 下载列表发生变化时执行的任务 + //DownloadingList.CollectionChanged += new NotifyCollectionChangedEventHandler((object sender, NotifyCollectionChangedEventArgs e) => + //{ + // // save the downloading list and finished list. + // //SaveHistory(); + //}); + + } + + #region 命令申明 + + // 暂停所有下载事件 + private DelegateCommand pauseAllDownloadingCommand; + public DelegateCommand PauseAllDownloadingCommand => pauseAllDownloadingCommand ?? (pauseAllDownloadingCommand = new DelegateCommand(ExecutePauseAllDownloadingCommand)); + + /// + /// 暂停所有下载事件 + /// + private void ExecutePauseAllDownloadingCommand() + { } + + // 继续所有下载事件 + private DelegateCommand continueAllDownloadingCommand; + public DelegateCommand ContinueAllDownloadingCommand => continueAllDownloadingCommand ?? (continueAllDownloadingCommand = new DelegateCommand(ExecuteContinueAllDownloadingCommand)); + + /// + /// 继续所有下载事件 + /// + private void ExecuteContinueAllDownloadingCommand() + { + } + + // 删除所有下载事件 + private DelegateCommand deleteAllDownloadingCommand; + public DelegateCommand DeleteAllDownloadingCommand => deleteAllDownloadingCommand ?? (deleteAllDownloadingCommand = new DelegateCommand(ExecuteDeleteAllDownloadingCommand)); + + /// + /// 删除所有下载事件 + /// + private void ExecuteDeleteAllDownloadingCommand() + { + } + + #endregion + } } diff --git a/DownKyi/Views/DownloadManager/ViewDownloading.xaml b/DownKyi/Views/DownloadManager/ViewDownloading.xaml index eeb1677..4313e88 100644 --- a/DownKyi/Views/DownloadManager/ViewDownloading.xaml +++ b/DownKyi/Views/DownloadManager/ViewDownloading.xaml @@ -2,9 +2,309 @@ x:Class="DownKyi.Views.DownloadManager.ViewDownloading" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - 正在下载 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +