diff --git a/DownKyi.Core/Aria2cNet/Client/AriaClient.cs b/DownKyi.Core/Aria2cNet/Client/AriaClient.cs index 77083a4..86bf74a 100644 --- a/DownKyi.Core/Aria2cNet/Client/AriaClient.cs +++ b/DownKyi.Core/Aria2cNet/Client/AriaClient.cs @@ -16,7 +16,12 @@ namespace DownKyi.Core.Aria2cNet.Client public static class AriaClient { private static readonly string JSONRPC = "2.0"; - private static readonly string TOKEN = "downkyi"; + private const string LOCAL_HOST = "http://localhost"; + private const string TOKEN = "downkyi"; + private const int LISTEN_PORT = 6800; + private static string host = LOCAL_HOST; + private static string token = TOKEN; + private static int listenPort = LISTEN_PORT; /// /// This method adds a new download. @@ -41,7 +46,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, uris, option }; @@ -91,7 +96,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, torrent, uris, option @@ -136,7 +141,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, metalink, uris, option @@ -168,7 +173,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -193,7 +198,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -220,7 +225,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -242,7 +247,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -266,7 +271,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -288,7 +293,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -311,7 +316,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -333,7 +338,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -360,7 +365,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -384,7 +389,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -408,7 +413,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -433,7 +438,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -457,7 +462,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -480,7 +485,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -520,7 +525,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, offset, num }; @@ -552,7 +557,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, offset, num }; @@ -591,7 +596,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid, pos, how.ToString("G") @@ -635,7 +640,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid, fileIndex, delUris, @@ -669,7 +674,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; @@ -705,7 +710,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid, option }; @@ -735,7 +740,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData @@ -770,7 +775,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, option }; @@ -793,7 +798,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -814,7 +819,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -836,7 +841,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN, + "token:" + token, gid }; AriaSendData ariaSend = new AriaSendData @@ -858,7 +863,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -881,7 +886,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -902,7 +907,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -926,7 +931,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -947,7 +952,7 @@ namespace DownKyi.Core.Aria2cNet.Client { List ariaParams = new List { - "token:" + TOKEN + "token:" + token, }; AriaSendData ariaSend = new AriaSendData { @@ -1018,13 +1023,36 @@ namespace DownKyi.Core.Aria2cNet.Client return await GetRpcResponseAsync(ariaSend); } + /// + /// 设置aria token + /// + /// + public static void SetToken(string token = TOKEN) + { + AriaClient.token = token; + } + + /// + /// 设置aria host + /// + /// + public static void SetHost(string host = LOCAL_HOST) + { + AriaClient.host = host; + } + + public static void SetListenPort(int listenPort = LISTEN_PORT) + { + AriaClient.listenPort = listenPort; + } + /// /// 获取jsonrpc的地址 /// /// - private static string GetRpcUri(int listenPort = 6800) + private static string GetRpcUri() { - return $"http://localhost:{listenPort}/jsonrpc"; + return $"{host}:{AriaClient.listenPort}/jsonrpc"; } /// diff --git a/DownKyi.Core/BiliApi/BiliUtils/Constant.cs b/DownKyi.Core/BiliApi/BiliUtils/Constant.cs index 23dba62..c8a3fa2 100644 --- a/DownKyi.Core/BiliApi/BiliUtils/Constant.cs +++ b/DownKyi.Core/BiliApi/BiliUtils/Constant.cs @@ -19,12 +19,20 @@ namespace DownKyi.Core.BiliApi.BiliUtils new Quality { Name = "360P 流畅", Id = 16 }, }; + private static readonly List codecIds = new List + { + new Quality { Name = "H.264/AVC", Id = 7 }, + new Quality { Name = "H.265/HEVC", Id = 12 }, + new Quality { Name = "AV1", Id = 13 }, + }; + private static readonly List qualities = new List { new Quality { Name = "64K", Id = 30216 }, new Quality { Name = "132K", Id = 30232 }, new Quality { Name = "192K", Id = 30280 }, new Quality { Name = "Dolby Atmos", Id = 30250 }, + new Quality { Name = "Hi-Res无损", Id = 30251 }, }; /// @@ -39,6 +47,18 @@ namespace DownKyi.Core.BiliApi.BiliUtils return new List(resolutions); } + /// + /// 获取视频编码代码 + /// + /// + public static List GetCodecIds() + { + // 使用深复制, + // 保证外部修改list后, + // 不会影响其他调用处 + return new List(codecIds); + } + /// /// 获取支持的视频音质 /// diff --git a/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsDetail.cs b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsDetail.cs new file mode 100644 index 0000000..a540679 --- /dev/null +++ b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsDetail.cs @@ -0,0 +1,32 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace DownKyi.Core.BiliApi.Users.Models +{ + // https://api.bilibili.com/x/polymer/space/seasons_archives_list?mid={mid}&season_id={seasonId}&page_num={pageNum}&page_size={pageSize}&sort_reverse=false + public class SpaceSeasonsDetailOrigin : BaseModel + { + //[JsonProperty("code")] + //public int Code { get; set; } + //[JsonProperty("message")] + //public string Message { get; set; } + //[JsonProperty("ttl")] + //public int Ttl { get; set; } + [JsonProperty("data")] + public SpaceSeasonsDetail Data { get; set; } + } + + public class SpaceSeasonsDetail : BaseModel + { + [JsonProperty("aids")] + public List Aids { get; set; } + [JsonProperty("archives")] + public List Archives { get; set; } + [JsonProperty("meta")] + public SpaceSeasonsMeta Meta { get; set; } + [JsonProperty("page")] + public SpaceSeasonsSeriesPage Page { get; set; } + } + +} diff --git a/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeries.cs b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeries.cs new file mode 100644 index 0000000..9a97f24 --- /dev/null +++ b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeries.cs @@ -0,0 +1,26 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace DownKyi.Core.BiliApi.Users.Models +{ + public class SpaceSeasons : BaseModel + { + [JsonProperty("archives")] + public List Archives { get; set; } + [JsonProperty("meta")] + public SpaceSeasonsMeta Meta { get; set; } + [JsonProperty("recent_aids")] + public List RecentAids { get; set; } + } + + public class SpaceSeries : BaseModel + { + [JsonProperty("archives")] + public List Archives { get; set; } + [JsonProperty("meta")] + public SpaceSeriesMeta Meta { get; set; } + [JsonProperty("recent_aids")] + public List RecentAids { get; set; } + } +} diff --git a/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesArchives.cs b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesArchives.cs new file mode 100644 index 0000000..38daebe --- /dev/null +++ b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesArchives.cs @@ -0,0 +1,29 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; + +namespace DownKyi.Core.BiliApi.Users.Models +{ + public class SpaceSeasonsSeriesArchives : BaseModel + { + [JsonProperty("aid")] + public long Aid { get; set; } + [JsonProperty("bvid")] + public string Bvid { get; set; } + [JsonProperty("ctime")] + public long Ctime { get; set; } + [JsonProperty("duration")] + public long Duration { get; set; } + [JsonProperty("interactive_video")] + public bool InteractiveVideo { get; set; } + [JsonProperty("pic")] + public string Pic { get; set; } + [JsonProperty("pubdate")] + public long Pubdate { get; set; } + [JsonProperty("stat")] + public SpaceSeasonsSeriesStat Stat { get; set; } + // state + [JsonProperty("title")] + public string Title { get; set; } + // ugc_pay + } +} diff --git a/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesMeta.cs b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesMeta.cs new file mode 100644 index 0000000..dacbeb6 --- /dev/null +++ b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesMeta.cs @@ -0,0 +1,51 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace DownKyi.Core.BiliApi.Users.Models +{ + public class SpaceSeasonsSeriesMeta : BaseModel + { + [JsonProperty("category")] + public int Category { get; set; } + [JsonProperty("cover")] + public string Cover { get; set; } + [JsonProperty("description")] + public string Description { get; set; } + [JsonProperty("mid")] + public long Mid { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("total")] + public int Total { get; set; } + } + + public class SpaceSeasonsMeta : SpaceSeasonsSeriesMeta + { + [JsonProperty("ptime")] + public long Ptime { get; set; } + [JsonProperty("season_id")] + public long SeasonId { get; set; } + } + + public class SpaceSeriesMeta : SpaceSeasonsSeriesMeta + { + [JsonProperty("creator")] + public string Creator { get; set; } + [JsonProperty("ctime")] + public long Ctime { get; set; } + [JsonProperty("keywords")] + public List Keywords { get; set; } + [JsonProperty("last_update_ts")] + public long LastUpdate { get; set; } + [JsonProperty("mtime")] + public long Mtime { get; set; } + [JsonProperty("raw_keywords")] + public string RawKeywords { get; set; } + [JsonProperty("series_id")] + public long SeriesId { get; set; } + [JsonProperty("state")] + public int State { get; set; } + } + +} diff --git a/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesOrigin.cs b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesOrigin.cs new file mode 100644 index 0000000..435ba0b --- /dev/null +++ b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesOrigin.cs @@ -0,0 +1,36 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace DownKyi.Core.BiliApi.Users.Models +{ + // https://api.bilibili.com/x/space/channel/video?mid={mid}&page_num={pageNum}&page_size={pageSize} + public class SpaceSeasonsSeriesOrigin : BaseModel + { + //[JsonProperty("code")] + //public int Code { get; set; } + //[JsonProperty("message")] + //public string Message { get; set; } + //[JsonProperty("ttl")] + //public int Ttl { get; set; } + [JsonProperty("data")] + public SpaceSeasonsSeriesData Data { get; set; } + } + + public class SpaceSeasonsSeriesData : BaseModel + { + [JsonProperty("items_lists")] + public SpaceSeasonsSeries ItemsLists { get; set; } + } + + public class SpaceSeasonsSeries : BaseModel + { + [JsonProperty("page")] + public SpaceSeasonsSeriesPage Page { get; set; } + [JsonProperty("seasons_list")] + public List SeasonsList { get; set; } + [JsonProperty("series_list")] + public List SeriesList { get; set; } + } + +} diff --git a/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesPage.cs b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesPage.cs new file mode 100644 index 0000000..0f651bf --- /dev/null +++ b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesPage.cs @@ -0,0 +1,15 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; + +namespace DownKyi.Core.BiliApi.Users.Models +{ + public class SpaceSeasonsSeriesPage : BaseModel + { + [JsonProperty("page_num")] + public int PageNum; + [JsonProperty("page_size")] + public int PageSize; + [JsonProperty("total")] + public int Total; + } +} diff --git a/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesStat.cs b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesStat.cs new file mode 100644 index 0000000..ef53bd2 --- /dev/null +++ b/DownKyi.Core/BiliApi/Users/Models/SpaceSeasonsSeriesStat.cs @@ -0,0 +1,11 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; + +namespace DownKyi.Core.BiliApi.Users.Models +{ + public class SpaceSeasonsSeriesStat : BaseModel + { + [JsonProperty("view")] + public long View { get; set; } + } +} diff --git a/DownKyi.Core/BiliApi/Users/Models/SpaceSeriesDetail.cs b/DownKyi.Core/BiliApi/Users/Models/SpaceSeriesDetail.cs new file mode 100644 index 0000000..d77643f --- /dev/null +++ b/DownKyi.Core/BiliApi/Users/Models/SpaceSeriesDetail.cs @@ -0,0 +1,29 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace DownKyi.Core.BiliApi.Users.Models +{ + // https://api.bilibili.com/x/series/archives?mid={mid}&series_id={seriesId}&only_normal=true&sort=desc&pn={pn}&ps={ps} + public class SpaceSeriesDetailOrigin : BaseModel + { + //[JsonProperty("code")] + //public int Code { get; set; } + //[JsonProperty("message")] + //public string Message { get; set; } + //[JsonProperty("ttl")] + //public int Ttl { get; set; } + [JsonProperty("data")] + public SpaceSeriesDetail Data { get; set; } + } + + public class SpaceSeriesDetail : BaseModel + { + [JsonProperty("aids")] + public List Aids { get; set; } + // page + [JsonProperty("archives")] + public List Archives { get; set; } + } + +} diff --git a/DownKyi.Core/BiliApi/Users/Models/SpaceSeriesMeta.cs b/DownKyi.Core/BiliApi/Users/Models/SpaceSeriesMeta.cs new file mode 100644 index 0000000..8377df1 --- /dev/null +++ b/DownKyi.Core/BiliApi/Users/Models/SpaceSeriesMeta.cs @@ -0,0 +1,28 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace DownKyi.Core.BiliApi.Users.Models +{ + // https://api.bilibili.com/x/series/series?series_id={seriesId} + public class SpaceSeriesMetaOrigin : BaseModel + { + //[JsonProperty("code")] + //public int Code { get; set; } + //[JsonProperty("message")] + //public string Message { get; set; } + //[JsonProperty("ttl")] + //public int Ttl { get; set; } + [JsonProperty("data")] + public SpaceSeriesMetaData Data { get; set; } + } + + public class SpaceSeriesMetaData : BaseModel + { + [JsonProperty("meta")] + public SpaceSeriesMeta Meta { get; set; } + [JsonProperty("recent_aids")] + public List RecentAids { get; set; } + } + +} diff --git a/DownKyi.Core/BiliApi/Users/UserSpace.cs b/DownKyi.Core/BiliApi/Users/UserSpace.cs index a0dee12..e7c5c18 100644 --- a/DownKyi.Core/BiliApi/Users/UserSpace.cs +++ b/DownKyi.Core/BiliApi/Users/UserSpace.cs @@ -232,20 +232,125 @@ namespace DownKyi.Core.BiliApi.Users #region 合集和列表 - // TODO - // https://api.bilibili.com/x/polymer/space/seasons_series_list?mid=27899754&page_num=1&page_size=18 - // page_size最大值为20 + /// + /// 查询用户的合集和列表 + /// + /// + /// 第几页 + /// 每页的数量;最大值为20 + /// + public static SpaceSeasonsSeries GetSeasonsSeries(long mid, int pageNum, int pageSize) + { + // https://api.bilibili.com/x/polymer/space/seasons_series_list?mid=49246269&page_num=1&page_size=18 + string url = $"https://api.bilibili.com/x/polymer/space/seasons_series_list?mid={mid}&page_num={pageNum}&page_size={pageSize}"; + string referer = "https://www.bilibili.com"; + string response = WebClient.RequestWeb(url, referer); - // https://api.bilibili.com/x/polymer/space/seasons_archives_list?mid=23947287&season_id=665&sort_reverse=false&page_num=1&page_size=30 + try + { + SpaceSeasonsSeriesOrigin origin = JsonConvert.DeserializeObject(response); + if (origin == null || origin.Data == null || origin.Data.ItemsLists == null) + { return null; } + return origin.Data.ItemsLists; + } + catch (Exception e) + { + Utils.Debugging.Console.PrintLine("GetSeasonsSeries()发生异常: {0}", e); + LogManager.Error("UserSpace", e); + return null; + } + } + + /// + /// 查询用户的合集的视频详情 + /// + /// + /// + /// + /// + public static SpaceSeasonsDetail GetSeasonsDetail(long mid, long seasonId, int pageNum, int pageSize) + { + // https://api.bilibili.com/x/polymer/space/seasons_archives_list?mid=23947287&season_id=665&sort_reverse=false&page_num=1&page_size=30 + string url = $"https://api.bilibili.com/x/polymer/space/seasons_archives_list?mid={mid}&season_id={seasonId}&page_num={pageNum}&page_size={pageSize}&sort_reverse=false"; + string referer = "https://www.bilibili.com"; + string response = WebClient.RequestWeb(url, referer); - // https://api.bilibili.com/x/series/archives?mid=27899754&series_id=1253087&only_normal=true&sort=desc&pn=1&ps=30 - // https://api.bilibili.com/x/series/archives?mid=27899754&series_id=1253087&only_normal=true&sort=asc&pn=1&ps=30 - // https://api.bilibili.com/x/series/series?series_id=1253087 + try + { + SpaceSeasonsDetailOrigin origin = JsonConvert.DeserializeObject(response); + if (origin == null || origin.Data == null) + { return null; } + return origin.Data; + } + catch (Exception e) + { + Utils.Debugging.Console.PrintLine("GetSeasonsDetail()发生异常: {0}", e); + LogManager.Error("UserSpace", e); + return null; + } + } + + /// + /// 查询用户的列表元数据 + /// + /// + /// + public static SpaceSeriesMetaData GetSeriesMeta(long seriesId) + { + // https://api.bilibili.com/x/series/series?series_id=1253087 + string url = $"https://api.bilibili.com/x/series/series?series_id={seriesId}"; + string referer = "https://www.bilibili.com"; + string response = WebClient.RequestWeb(url, referer); + + try + { + SpaceSeriesMetaOrigin origin = JsonConvert.DeserializeObject(response); + if (origin == null || origin.Data == null) + { return null; } + return origin.Data; + } + catch (Exception e) + { + Utils.Debugging.Console.PrintLine("GetSeriesMeta()发生异常: {0}", e); + LogManager.Error("UserSpace", e); + return null; + } + } + + /// + /// 查询用户的列表的视频详情 + /// + /// + /// + /// + /// + /// + public static SpaceSeriesDetail GetSeriesDetail(long mid, long seriesId, int pn, int ps) + { + // https://api.bilibili.com/x/series/archives?mid=27899754&series_id=1253087&only_normal=true&sort=desc&pn=1&ps=30 + + string url = $"https://api.bilibili.com/x/series/archives?mid={mid}&series_id={seriesId}&only_normal=true&sort=desc&pn={pn}&ps={ps}"; + string referer = "https://www.bilibili.com"; + string response = WebClient.RequestWeb(url, referer); + + try + { + SpaceSeriesDetailOrigin origin = JsonConvert.DeserializeObject(response); + if (origin == null || origin.Data == null) + { return null; } + return origin.Data; + } + catch (Exception e) + { + Utils.Debugging.Console.PrintLine("GetSeriesDetail()发生异常: {0}", e); + LogManager.Error("UserSpace", e); + return null; + } + } #endregion #region 课程 - /// /// 查询用户发布的课程列表 /// diff --git a/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDash.cs b/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDash.cs index ff545d1..e81a9ff 100644 --- a/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDash.cs +++ b/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDash.cs @@ -18,5 +18,7 @@ namespace DownKyi.Core.BiliApi.VideoStream.Models public List Audio { get; set; } [JsonProperty("dolby")] public PlayUrlDashDolby Dolby { get; set; } + [JsonProperty("flac")] + public PlayUrlDashFlac Flac { get; set; } } } diff --git a/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashFlac.cs b/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashFlac.cs new file mode 100644 index 0000000..4c54786 --- /dev/null +++ b/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashFlac.cs @@ -0,0 +1,12 @@ +using DownKyi.Core.BiliApi.Models; +using Newtonsoft.Json; + +namespace DownKyi.Core.BiliApi.VideoStream.Models +{ + public class PlayUrlDashFlac : BaseModel + { + [JsonProperty("audio")] + public PlayUrlDashVideo Audio { get; set; } + //bool display { get; set; } + } +} diff --git a/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashVideo.cs b/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashVideo.cs index 098f9bc..fab8044 100644 --- a/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashVideo.cs +++ b/DownKyi.Core/BiliApi/VideoStream/Models/PlayUrlDashVideo.cs @@ -30,6 +30,7 @@ namespace DownKyi.Core.BiliApi.VideoStream.Models // start_with_sap // SegmentBase // segment_base - // codecid + [JsonProperty("codecid")] + public int CodecId { get; set; } } } diff --git a/DownKyi.Core/DownKyi.Core.csproj b/DownKyi.Core/DownKyi.Core.csproj index a9875b5..0c61a49 100644 --- a/DownKyi.Core/DownKyi.Core.csproj +++ b/DownKyi.Core/DownKyi.Core.csproj @@ -217,6 +217,15 @@ + + + + + + + + + @@ -240,6 +249,7 @@ + diff --git a/DownKyi.Core/FFmpeg/FFmpegHelper.cs b/DownKyi.Core/FFmpeg/FFmpegHelper.cs index 1eb34fb..32d2395 100644 --- a/DownKyi.Core/FFmpeg/FFmpegHelper.cs +++ b/DownKyi.Core/FFmpeg/FFmpegHelper.cs @@ -34,15 +34,19 @@ namespace DownKyi.Core.FFmpeg /// public static bool MergeVideo(string video1, string video2, string destVideo) { - string param = $"-y -i \"{video1}\" -i \"{video2}\" -acodec copy -vcodec copy -f mp4 \"{destVideo}\""; + string param = $"-y -i \"{video1}\" -i \"{video2}\" -strict -2 -acodec copy -vcodec copy -f mp4 \"{destVideo}\""; if (video1 == null || !File.Exists(video1)) { - param = $"-y -i \"{video2}\" -acodec copy -vcodec copy -f mp4 \"{destVideo}\""; + param = $"-y -i \"{video2}\" -strict -2 -acodec copy -vcodec copy -f mp4 \"{destVideo}\""; } if (video2 == null || !File.Exists(video2)) { - param = $"-y -i \"{video1}\" -acodec copy \"{destVideo}\""; + param = $"-y -i \"{video1}\" -strict -2 -acodec copy \"{destVideo}\""; } + + // 支持flac格式音频 + //param += " -strict -2"; + if (!File.Exists(video1) && !File.Exists(video2)) { return false; } // 如果存在 diff --git a/DownKyi.Core/Properties/AssemblyInfo.cs b/DownKyi.Core/Properties/AssemblyInfo.cs index 26cbfc9..67f29b8 100644 --- a/DownKyi.Core/Properties/AssemblyInfo.cs +++ b/DownKyi.Core/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 //通过使用 "*",如下所示: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.1.3.0")] -[assembly: AssemblyFileVersion("2.1.3.0")] +[assembly: AssemblyVersion("2.1.4.0")] +[assembly: AssemblyFileVersion("2.1.4.0")] diff --git a/DownKyi.Core/Settings/Downloader.cs b/DownKyi.Core/Settings/Downloader.cs index ff5cb8c..0008bbe 100644 --- a/DownKyi.Core/Settings/Downloader.cs +++ b/DownKyi.Core/Settings/Downloader.cs @@ -5,5 +5,6 @@ NOT_SET = 0, BUILT_IN, ARIA, + CUSTOM_ARIA, } } diff --git a/DownKyi.Core/Settings/Models/NetworkSettings.cs b/DownKyi.Core/Settings/Models/NetworkSettings.cs index bf26ede..d745442 100644 --- a/DownKyi.Core/Settings/Models/NetworkSettings.cs +++ b/DownKyi.Core/Settings/Models/NetworkSettings.cs @@ -22,6 +22,8 @@ namespace DownKyi.Core.Settings.Models #endregion #region Aria + public string AriaToken { get; set; } = null; + public string AriaHost { get; set; } = null; public int AriaListenPort { get; set; } = -1; public AriaConfigLogLevel AriaLogLevel { get; set; } = AriaConfigLogLevel.NOT_SET; public int AriaSplit { get; set; } = -1; diff --git a/DownKyi.Core/Settings/Models/VideoSettings.cs b/DownKyi.Core/Settings/Models/VideoSettings.cs index 0cf431e..0eeec39 100644 --- a/DownKyi.Core/Settings/Models/VideoSettings.cs +++ b/DownKyi.Core/Settings/Models/VideoSettings.cs @@ -8,7 +8,7 @@ namespace DownKyi.Core.Settings.Models /// public class VideoSettings { - public VideoCodecs VideoCodecs { get; set; } = VideoCodecs.NONE; // AVC or HEVC + public int VideoCodecs { get; set; } = -1; // AVC or HEVC public int Quality { get; set; } = -1; // 画质 public int AudioQuality { get; set; } = -1; // 音质 public AllowStatus IsTranscodingFlvToMp4 { get; set; } = AllowStatus.NONE; // 是否将flv转为mp4 diff --git a/DownKyi.Core/Settings/SettingsManager.Network.cs b/DownKyi.Core/Settings/SettingsManager.Network.cs index 8ffb9e7..2e2a468 100644 --- a/DownKyi.Core/Settings/SettingsManager.Network.cs +++ b/DownKyi.Core/Settings/SettingsManager.Network.cs @@ -24,6 +24,12 @@ namespace DownKyi.Core.Settings private readonly string httpProxy = ""; private readonly int httpProxyListenPort = 0; + // Aria服务器token + private readonly string ariaToken = "downkyi"; + + // Aria服务器host + private readonly string ariaHost = "http://localhost"; + // Aria服务器端口号 private readonly int ariaListenPort = 6800; @@ -263,6 +269,60 @@ namespace DownKyi.Core.Settings return SetSettings(); } + /// + /// 获取Aria服务器的token + /// + /// + public string GetAriaToken() + { + appSettings = GetSettings(); + if (appSettings.Network.AriaToken == null) + { + // 第一次获取,先设置默认值 + SetHttpProxy(ariaToken); + return ariaToken; + } + return appSettings.Network.AriaToken; + } + + /// + /// 设置Aria服务器的token + /// + /// + /// + public bool SetAriaToken(string token) + { + appSettings.Network.AriaToken = token; + return SetSettings(); + } + + /// + /// 获取Aria服务器的host + /// + /// + public string GetAriaHost() + { + appSettings = GetSettings(); + if (appSettings.Network.AriaHost == null) + { + // 第一次获取,先设置默认值 + SetHttpProxy(ariaHost); + return ariaHost; + } + return appSettings.Network.AriaHost; + } + + /// + /// 设置Aria服务器的host + /// + /// + /// + public bool SetAriaHost(string host) + { + appSettings.Network.AriaHost = host; + return SetSettings(); + } + /// /// 获取Aria服务器的端口号 /// diff --git a/DownKyi.Core/Settings/SettingsManager.Video.cs b/DownKyi.Core/Settings/SettingsManager.Video.cs index 7a88cc4..c8133f8 100644 --- a/DownKyi.Core/Settings/SettingsManager.Video.cs +++ b/DownKyi.Core/Settings/SettingsManager.Video.cs @@ -9,7 +9,7 @@ namespace DownKyi.Core.Settings public partial class SettingsManager { // 设置优先下载的视频编码 - private readonly VideoCodecs videoCodecs = VideoCodecs.AVC; + private readonly int videoCodecs = 7; // 设置优先下载画质 private readonly int quality = 120; @@ -58,10 +58,10 @@ namespace DownKyi.Core.Settings /// 获取优先下载的视频编码 /// /// - public VideoCodecs GetVideoCodecs() + public int GetVideoCodecs() { appSettings = GetSettings(); - if (appSettings.Video.VideoCodecs == VideoCodecs.NONE) + if (appSettings.Video.VideoCodecs == -1) { // 第一次获取,先设置默认值 SetVideoCodecs(videoCodecs); @@ -75,7 +75,7 @@ namespace DownKyi.Core.Settings /// /// /// - public bool SetVideoCodecs(VideoCodecs videoCodecs) + public bool SetVideoCodecs(int videoCodecs) { appSettings.Video.VideoCodecs = videoCodecs; return SetSettings(); diff --git a/DownKyi/App.xaml.cs b/DownKyi/App.xaml.cs index 91687fc..42f74b4 100644 --- a/DownKyi/App.xaml.cs +++ b/DownKyi/App.xaml.cs @@ -134,6 +134,9 @@ namespace DownKyi case Downloader.ARIA: downloadService = new AriaDownloadService(DownloadingList, DownloadedList); break; + case Downloader.CUSTOM_ARIA: + downloadService = new CustomAriaDownloadService(DownloadingList, DownloadedList); + break; } if (downloadService != null) { @@ -182,6 +185,7 @@ namespace DownKyi containerRegistry.RegisterForNavigation(ViewUserSpaceViewModel.Tag); containerRegistry.RegisterForNavigation(ViewPublicationViewModel.Tag); containerRegistry.RegisterForNavigation(ViewModels.ViewChannelViewModel.Tag); + containerRegistry.RegisterForNavigation(ViewModels.ViewSeasonsSeriesViewModel.Tag); containerRegistry.RegisterForNavigation(ViewFriendViewModel.Tag); containerRegistry.RegisterForNavigation(ViewMySpaceViewModel.Tag); @@ -213,6 +217,7 @@ namespace DownKyi // UserSpace containerRegistry.RegisterForNavigation(ViewArchiveViewModel.Tag); containerRegistry.RegisterForNavigation(ViewModels.UserSpace.ViewChannelViewModel.Tag); + containerRegistry.RegisterForNavigation(ViewModels.UserSpace.ViewSeasonsSeriesViewModel.Tag); // dialogs containerRegistry.RegisterDialog(ViewAlertDialogViewModel.Tag); diff --git a/DownKyi/DownKyi.csproj b/DownKyi/DownKyi.csproj index 2ebe700..788877d 100644 --- a/DownKyi/DownKyi.csproj +++ b/DownKyi/DownKyi.csproj @@ -117,6 +117,7 @@ + @@ -166,12 +167,15 @@ + + + @@ -243,9 +247,15 @@ ViewArchive.xaml + + ViewSeasonsSeries.xaml + ViewChannel.xaml + + ViewSeasonsSeries.xaml + ViewChannel.xaml @@ -446,10 +456,18 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/DownKyi/Images/NormalIcon.cs b/DownKyi/Images/NormalIcon.cs index 7977bf2..669c798 100644 --- a/DownKyi/Images/NormalIcon.cs +++ b/DownKyi/Images/NormalIcon.cs @@ -1,4 +1,6 @@ -namespace DownKyi.Images +using DownKyi.ViewModels.UserSpace; + +namespace DownKyi.Images { public class NormalIcon { @@ -267,6 +269,46 @@ Fill = "#FF000000" }; + Channel1 = new VectorImage + { + Height = 24, + Width = 24, + Data = @"M116.57 0 q-30.86 0 -57.71 16 q-26.85 16 -42.85 42.85 q-16 26.85 -16 57.71 l0 468.57 q0 30.86 16 57.72 + q16 26.86 42.85 42.86 q26.85 16 57.71 16 l643.43 0 q32 0 58.86 -16 q26.86 -16 42.86 -42.86 + q16 -26.86 16 -57.72 l0 -468.57 q0 -30.86 -16 -57.71 q-16 -26.85 -42.86 -42.85 q-26.86 -16 -58.86 -16 + l-643.43 0 ZM262.86 848 q-40 0 -72.57 -25.71 q-32.58 -25.72 -39.43 -62.29 l697.14 0 q36.57 0 62.29 -25.71 + q25.71 -25.72 25.71 -62.29 l0 -521.14 q37.71 11.43 62.86 42.28 q25.14 30.86 25.14 69.72 l0 468.57 + q0 30.86 -16 57.72 q-16 26.86 -42.86 42.86 q-26.86 16 -58.86 16 l-643.43 0 ZM88 174.86 + q0 -36.57 25.14 -62.29 q25.14 -25.71 61.72 -25.71 l526.85 0 q36.58 0 62.29 25.71 q25.71 25.72 25.71 62.29 + l0 350.85 q0 36.58 -25.71 62.29 q-25.71 25.71 -62.29 25.71 l-526.85 0 q-36.57 0 -61.72 -25.71 + q-25.14 -25.71 -25.14 -62.29 l0 -350.85 ZM609.14 374.86 q11.43 -6.86 11.43 -22.29 q0 -15.43 -11.43 -26.86 + l-214.85 -146.28 q-13.72 -10.29 -28.57 -1.72 q-14.86 8.58 -14.86 26.86 l0 297.14 q0 18.29 14.86 26.86 + q14.85 8.57 28.57 -2.86 l214.85 -150.85 Z", + Fill = "#FF000000" + }; + + SeasonsSeries = new VectorImage + { + Height = 24, + Width = 24, + Data = @"M974.2 382.22 l-401.47 177.37 q-28.88 13.75 -60.5 13.75 q-31.62 0 -60.5 -13.75 l-400.09 -177.37 + q-20.63 -11 -33 -30.25 q-12.38 -19.25 -13.06 -43.31 q-0.68 -24.06 10.32 -45.38 q11 -21.32 31.62 -35.07 + l402.85 -210.36 q28.87 -17.87 63.25 -17.87 q34.38 0 63.25 19.25 l398.72 207.61 q22 12.37 33.69 34.37 + q11.68 22 10.31 46.06 q-1.38 24.06 -13.06 44 q-11.69 19.94 -32.31 30.94 ZM939.83 292.86 l-400.1 -207.62 + q-11 -8.25 -24.75 -9.62 q-16.5 1.37 -30.25 9.62 l-400.1 207.62 q-2.75 6.87 -2.75 12.37 q0 5.5 4.13 9.62 + l397.35 175.99 q20.62 9.63 28.18 9.63 q7.56 0 26.81 -8.25 l404.22 -178.74 q1.38 -11 -4.12 -20.62 + l1.38 0 ZM20.01 512.84 q8.25 -4.12 19.25 -4.12 q11 0 19.25 5.5 q16.5 9.62 39.87 20.62 l413.85 186.99 + l394.6 -178.74 l60.49 -30.25 q9.63 -4.12 19.94 -4.12 q10.31 0 19.25 5.5 q8.94 5.5 13.75 15.12 + q4.81 9.63 3.43 19.25 q-1.37 23.37 -21.99 31.62 l-64.62 31.62 l-424.85 193.87 l-422.1 -192.49 + q-35.74 -12.38 -68.74 -33 q-9.63 -5.5 -15.13 -14.43 q-5.5 -8.94 -6.18 -19.25 q-0.69 -10.32 4.82 -19.25 + q5.5 -8.94 15.12 -14.44 l0 0 ZM58.51 732.83 q17.87 9.62 41.25 20.62 l412.47 186.99 l394.6 -178.74 + l60.49 -30.25 q9.63 -5.5 19.94 -4.81 q10.31 0.69 19.25 6.18 q8.94 5.5 13.75 15.12 q4.81 9.63 3.43 19.94 + q-1.37 10.32 -6.87 18.57 q-5.5 8.25 -15.12 12.37 l-64.62 31.62 l-424.85 192.49 l-422.1 -191.11 + q-35.74 -13.75 -68.74 -34.37 q-13.75 -6.88 -17.88 -22 q-4.13 -15.13 3.44 -28.87 q7.56 -13.75 22.68 -17.87 + q15.13 -4.13 28.88 4.13 Z", + Fill = "#FF000000" + }; + PlatformIpad = new VectorImage { Height = 16, @@ -357,6 +399,8 @@ public VectorImage VideoUp { get; private set; } public VectorImage Channel { get; private set; } + public VectorImage Channel1 { get; private set; } + public VectorImage SeasonsSeries { get; private set; } public VectorImage PlatformIpad { get; private set; } public VectorImage PlatformMobile { get; private set; } diff --git a/DownKyi/Languages/Default.xaml b/DownKyi/Languages/Default.xaml index 52a6fed..cc01956 100644 --- a/DownKyi/Languages/Default.xaml +++ b/DownKyi/Languages/Default.xaml @@ -63,6 +63,7 @@ 全部 投稿视频 频道 + 合集和列表 请稍等,马上就好~ @@ -192,7 +193,10 @@ 选择下载器(重启生效): 内建下载器(测试) Aria2下载器 + 自定义Aria2下载器 + Aria服务器地址: Aria服务器端口: + Aria服务器Token: Aria日志等级: Aria同时下载数: Aria最大线程数: diff --git a/DownKyi/Models/AppInfo.cs b/DownKyi/Models/AppInfo.cs index 22c0c6d..f610f8a 100644 --- a/DownKyi/Models/AppInfo.cs +++ b/DownKyi/Models/AppInfo.cs @@ -3,12 +3,12 @@ public class AppInfo { public string Name { get; } = "哔哩下载姬"; - public int VersionCode { get; } = 510; + public int VersionCode { get; } = 511; #if DEBUG - public string VersionName { get; } = "1.5.3 Debug"; + public string VersionName { get; } = "1.5.4 Debug"; #else - public string VersionName { get; } = "1.5.3"; + public string VersionName { get; } = "1.5.4"; #endif } diff --git a/DownKyi/Properties/AssemblyInfo.cs b/DownKyi/Properties/AssemblyInfo.cs index cbf3fe1..5a996db 100644 --- a/DownKyi/Properties/AssemblyInfo.cs +++ b/DownKyi/Properties/AssemblyInfo.cs @@ -51,5 +51,5 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.5.3.0")] -[assembly: AssemblyFileVersion("1.5.3.0")] +[assembly: AssemblyVersion("1.5.4.0")] +[assembly: AssemblyFileVersion("1.5.4.0")] diff --git a/DownKyi/Services/Download/AriaDownloadService.cs b/DownKyi/Services/Download/AriaDownloadService.cs index 34ef063..03925b2 100644 --- a/DownKyi/Services/Download/AriaDownloadService.cs +++ b/DownKyi/Services/Download/AriaDownloadService.cs @@ -232,6 +232,13 @@ namespace DownKyi.Services.Download /// public void Start() { + // 设置aria token + AriaClient.SetToken(); + // 设置aria host + AriaClient.SetHost(); + // 设置aria listenPort + AriaClient.SetListenPort(); + // 启动Aria服务器 StartAriaServer(); diff --git a/DownKyi/Services/Download/CustomAriaDownloadService.cs b/DownKyi/Services/Download/CustomAriaDownloadService.cs new file mode 100644 index 0000000..5dbf0c4 --- /dev/null +++ b/DownKyi/Services/Download/CustomAriaDownloadService.cs @@ -0,0 +1,442 @@ +using DownKyi.Core.Aria2cNet; +using DownKyi.Core.Aria2cNet.Client; +using DownKyi.Core.Aria2cNet.Client.Entity; +using DownKyi.Core.Aria2cNet.Server; +using DownKyi.Core.BiliApi.VideoStream.Models; +using DownKyi.Core.Logging; +using DownKyi.Core.Settings; +using DownKyi.Core.Utils; +using DownKyi.Models; +using DownKyi.Utils; +using DownKyi.ViewModels.DownloadManager; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace DownKyi.Services.Download +{ + /// + /// 音视频采用Aria下载,其余采用WebClient下载 + /// + public class CustomAriaDownloadService : DownloadService, IDownloadService + { + public CustomAriaDownloadService(ObservableCollection downloadingList, ObservableCollection downloadedList) : base(downloadingList, downloadedList) + { + Tag = "AriaDownloadService"; + } + + #region 音视频 + + /// + /// 下载音频,返回下载文件路径 + /// + /// + /// + public override string DownloadAudio(DownloadingItem downloading) + { + PlayUrlDashVideo downloadAudio = BaseDownloadAudio(downloading); + + return DownloadVideo(downloading, downloadAudio); + } + + /// + /// 下载视频,返回下载文件路径 + /// + /// + /// + public override string DownloadVideo(DownloadingItem downloading) + { + PlayUrlDashVideo downloadVideo = BaseDownloadVideo(downloading); + + 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); } + + // 路径 + downloading.DownloadBase.FilePath = downloading.DownloadBase.FilePath.Replace("\\", "/"); + string[] temp = downloading.DownloadBase.FilePath.Split('/'); + //string path = downloading.DownloadBase.FilePath.Replace(temp[temp.Length - 1], ""); + string path = downloading.DownloadBase.FilePath.TrimEnd(temp[temp.Length - 1].ToCharArray()); + + // 下载文件名 + string fileName = Guid.NewGuid().ToString("N"); + string key = $"{downloadVideo.Id}_{downloadVideo.Codecs}"; + + // 老版本数据库没有这一项,会变成null + if (downloading.Downloading.DownloadedFiles == null) + { + downloading.Downloading.DownloadedFiles = new List(); + } + + if (downloading.Downloading.DownloadFiles.ContainsKey(key)) + { + // 如果存在,表示下载过, + // 则继续使用上次下载的文件名 + fileName = downloading.Downloading.DownloadFiles[key]; + + // 还要检查一下文件有没有被人删掉,删掉的话重新下载 + // 如果下载视频之后音频文件被人删了。此时gid还是视频的,会下错文件 + if (downloading.Downloading.DownloadedFiles.Contains(key) && File.Exists(Path.Combine(path, fileName))) + { + return Path.Combine(path, fileName); + } + } + else + { + // 记录本次下载的文件 + try + { + downloading.Downloading.DownloadFiles.Add(key, fileName); + } + catch (ArgumentException) { } + // Gid最好能是每个文件单独存储,现在复用有可能会混 + // 不过好消息是下载是按固定顺序的,而且下载了两个音频会混流不过 + downloading.Downloading.Gid = null; + } + + // 启用https + AllowStatus useSSL = SettingsManager.GetInstance().UseSSL(); + if (useSSL == AllowStatus.YES) + { + for (int i = 0; i < urls.Count; i++) + { + string url = urls[i]; + if (url.StartsWith("http://")) + { + urls[i] = url.Replace("http://", "https://"); + } + } + } + else + { + for (int i = 0; i < urls.Count; i++) + { + string url = urls[i]; + if (url.StartsWith("https://")) + { + urls[i] = url.Replace("https://", "http://"); + } + } + } + + // 开始下载 + DownloadResult downloadStatus = DownloadByAria(downloading, urls, path, fileName); + switch (downloadStatus) + { + case DownloadResult.SUCCESS: + downloading.Downloading.DownloadedFiles.Add(key); + downloading.Downloading.Gid = null; + return Path.Combine(path, fileName); + case DownloadResult.FAILED: + case DownloadResult.ABORT: + default: + return nullMark; + } + } + + #endregion + + /// + /// 下载封面 + /// + /// + public override string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName) + { + return BaseDownloadCover(downloading, coverUrl, fileName); + } + + /// + /// 下载弹幕 + /// + /// + public override string DownloadDanmaku(DownloadingItem downloading) + { + return BaseDownloadDanmaku(downloading); + } + + /// + /// 下载字幕 + /// + /// + public override List DownloadSubtitle(DownloadingItem downloading) + { + return BaseDownloadSubtitle(downloading); + } + + /// + /// 混流音频和视频 + /// + /// + /// + /// + /// + public override string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid) + { + if (videoUid == nullMark) + { + return null; + } + return BaseMixedFlow(downloading, audioUid, videoUid); + } + + /// + /// 解析视频流的下载链接 + /// + /// + public override void Parse(DownloadingItem downloading) + { + BaseParse(downloading); + } + + /// + /// 停止下载服务(转换await和Task.Wait两种调用形式) + /// + private async Task EndTask() + { + // 停止基本任务 + await BaseEndTask(); + + // 关闭Aria服务器 + await CloseAriaServer(); + } + + /// + /// 停止下载服务 + /// + public void End() + { + Task.Run(EndTask).Wait(); + } + + /// + /// 启动下载服务 + /// + public void Start() + { + // 设置aria token + AriaClient.SetToken(SettingsManager.GetInstance().GetAriaToken()); + // 设置aria host + AriaClient.SetHost(SettingsManager.GetInstance().GetAriaHost()); + // 设置aria listenPort + AriaClient.SetListenPort(SettingsManager.GetInstance().GetAriaListenPort()); + + // 启动基本服务 + BaseStart(); + } + + /// + /// 强制暂停 + /// + /// + /// + protected override void Pause(DownloadingItem downloading) + { + cancellationToken.ThrowIfCancellationRequested(); + + downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing"); + if (downloading.Downloading.DownloadStatus == DownloadStatus.PAUSE) + { + throw new OperationCanceledException("Stop thread by pause"); + } + // 是否存在 + var isExist = IsExist(downloading); + if (!isExist.Result) + { + throw new OperationCanceledException("Task is deleted"); + } + } + + /// + /// 是否存在于下载列表中 + /// + /// + /// + private async Task IsExist(DownloadingItem downloading) + { + bool isExist = downloadingList.Contains(downloading); + if (isExist) + { + return true; + } + else + { + // 先恢复为waiting状态,暂停状态下Remove会导致文件重新下载,原因暂不清楚 + await AriaClient.UnpauseAsync(downloading.Downloading.Gid); + // 移除下载项 + var ariaRemove = await AriaClient.RemoveAsync(downloading.Downloading.Gid); + if (ariaRemove == null || ariaRemove.Result == downloading.Downloading.Gid) + { + // 从内存中删除下载项 + await AriaClient.RemoveDownloadResultAsync(downloading.Downloading.Gid); + } + + return false; + } + } + + /// + /// 关闭Aria服务器 + /// + private async Task CloseAriaServer() + { + // 暂停所有下载 + var ariaPause = await AriaClient.PauseAllAsync(); +#if DEBUG + Core.Utils.Debugging.Console.PrintLine(ariaPause.ToString()); +#endif + + // 关闭服务器 + bool close = AriaServer.CloseServer(); +#if DEBUG + Core.Utils.Debugging.Console.PrintLine(close); +#endif + } + + /// + /// 采用Aria下载文件 + /// + /// + /// + private DownloadResult DownloadByAria(DownloadingItem downloading, List urls, string path, string localFileName) + { + // path已斜杠结尾,去掉斜杠 + path = path.TrimEnd('/').TrimEnd('\\'); + + //检查gid对应任务,如果已创建那么直接使用 + //但是代理设置会出现不能随时更新的问题 + + if (downloading.Downloading.Gid != null) + { + Task status = AriaClient.TellStatus(downloading.Downloading.Gid); + if (status == null || status.Result == null) + downloading.Downloading.Gid = null; + else if (status.Result.Result == null && status.Result.Error != null) + { + if (status.Result.Error.Message.Contains("is not found")) + { + downloading.Downloading.Gid = null; + } + } + + } + + if (downloading.Downloading.Gid == null) + { + AriaSendOption option = new AriaSendOption + { + //HttpProxy = $"http://{Settings.GetAriaHttpProxy()}:{Settings.GetAriaHttpProxyListenPort()}", + Dir = path, + Out = localFileName, + //Header = $"cookie: {LoginHelper.GetLoginInfoCookiesString()}\nreferer: https://www.bilibili.com", + //UseHead = "true", + UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36", + }; + + //// 如果设置了代理,则增加HttpProxy + //if (SettingsManager.GetInstance().IsAriaHttpProxy() == AllowStatus.YES) + //{ + // option.HttpProxy = $"http://{SettingsManager.GetInstance().GetAriaHttpProxy()}:{SettingsManager.GetInstance().GetAriaHttpProxyListenPort()}"; + //} + + // 添加一个下载 + Task ariaAddUri = AriaClient.AddUriAsync(urls, option); + if (ariaAddUri == null || ariaAddUri.Result == null || ariaAddUri.Result.Result == null) + { + return DownloadResult.FAILED; + } + + // 保存gid + string gid = ariaAddUri.Result.Result; + downloading.Downloading.Gid = gid; + } + else + { + Task ariaUnpause = AriaClient.UnpauseAsync(downloading.Downloading.Gid); + } + + // 管理下载 + AriaManager ariaManager = new AriaManager(); + ariaManager.TellStatus += AriaTellStatus; + ariaManager.DownloadFinish += AriaDownloadFinish; + return ariaManager.GetDownloadStatus(downloading.Downloading.Gid, new Action(() => + { + cancellationToken.ThrowIfCancellationRequested(); + switch (downloading.Downloading.DownloadStatus) + { + case DownloadStatus.PAUSE: + Task ariaPause = AriaClient.PauseAsync(downloading.Downloading.Gid); + // 通知UI,并阻塞当前线程 + Pause(downloading); + break; + case DownloadStatus.DOWNLOADING: + break; + } + })); + } + + private void AriaTellStatus(long totalLength, long completedLength, long speed, string 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; + if (totalLength != 0) + { + percent = (float)completedLength / totalLength * 100; + } + + // 根据进度判断本次是否需要更新UI + if (Math.Abs(percent - video.Progress) < 0.01) { return; } + + // 下载进度 + video.Progress = percent; + + // 下载大小 + video.DownloadingFileSize = Format.FormatFileSize(completedLength) + "/" + Format.FormatFileSize(totalLength); + + // 下载速度 + video.SpeedDisplay = Format.FormatSpeed(speed); + + // 最大下载速度 + if (video.Downloading.MaxSpeed < speed) + { + video.Downloading.MaxSpeed = speed; + } + } + + private void AriaDownloadFinish(bool isSuccess, string downloadPath, string gid, string msg) + { + //throw new NotImplementedException(); + } + } +} diff --git a/DownKyi/Services/Download/DownloadService.cs b/DownKyi/Services/Download/DownloadService.cs index 2af551b..49a7c60 100644 --- a/DownKyi/Services/Download/DownloadService.cs +++ b/DownKyi/Services/Download/DownloadService.cs @@ -1,4 +1,5 @@ -using DownKyi.Core.BiliApi.VideoStream; +using DownKyi.Core.BiliApi.BiliUtils; +using DownKyi.Core.BiliApi.VideoStream; using DownKyi.Core.BiliApi.VideoStream.Models; using DownKyi.Core.Danmaku2Ass; using DownKyi.Core.FFmpeg; @@ -14,6 +15,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -82,6 +84,11 @@ namespace DownKyi.Services.Download { downloadAudio = downloading.PlayUrl.Dash.Dolby.Audio[0]; } + // Hi-Res无损 + if (downloading.AudioCodec.Id == 30251) + { + downloadAudio = downloading.PlayUrl.Dash.Flac.Audio; + } } catch (Exception) { } @@ -110,7 +117,8 @@ namespace DownKyi.Services.Download PlayUrlDashVideo downloadVideo = null; foreach (PlayUrlDashVideo video in downloading.PlayUrl.Dash.Video) { - if (video.Id == downloading.Resolution.Id && Utils.GetVideoCodecName(video.Codecs) == downloading.VideoCodecName) + Quality codecs = Constant.GetCodecIds().FirstOrDefault(t => t.Id == video.CodecId); + if (video.Id == downloading.Resolution.Id && codecs.Name == downloading.VideoCodecName) { downloadVideo = video; break; diff --git a/DownKyi/Services/Utils.cs b/DownKyi/Services/Utils.cs index 13f1206..b3e055a 100644 --- a/DownKyi/Services/Utils.cs +++ b/DownKyi/Services/Utils.cs @@ -30,7 +30,7 @@ namespace DownKyi.Services // 获取设置 UserInfoSettings userInfo = SettingsManager.GetInstance().GetUserInfo(); int defaultQuality = SettingsManager.GetInstance().GetQuality(); - VideoCodecs videoCodecs = SettingsManager.GetInstance().GetVideoCodecs(); + int videoCodecs = SettingsManager.GetInstance().GetVideoCodecs(); int defaultAudioQuality = SettingsManager.GetInstance().GetAudioQuality(); // 未登录时,最高仅720P @@ -95,7 +95,7 @@ namespace DownKyi.Services /// /// /// - private static ObservableCollection GetAudioQualityFormatList(PlayUrl playUrl, int defaultAudioQuality) + private static ObservableCollection GetAudioQualityFormatList_old(PlayUrl playUrl, int defaultAudioQuality) { List audioQualityFormatList = new List(); @@ -122,6 +122,55 @@ namespace DownKyi.Services return new ObservableCollection(audioQualityFormatList); } + + /// + /// 设置音质 + /// + /// + /// + /// + private static ObservableCollection GetAudioQualityFormatList(PlayUrl playUrl, int defaultAudioQuality) + { + List audioQualityFormatList = new List(); + List audioQualities = Constant.GetAudioQualities(); + + if (playUrl.Dash.Audio != null && playUrl.Dash.Audio.Count > 0) + { + foreach (PlayUrlDashVideo audio in playUrl.Dash.Audio) + { + // 音质id大于设置音质时,跳过 + if (audio.Id > defaultAudioQuality) { continue; } + + Quality audioQuality = audioQualities.FirstOrDefault(t => { return t.Id == audio.Id; }); + if (audioQuality != null) + { + ListHelper.AddUnique(audioQualityFormatList, audioQuality.Name); + } + } + } + + if (audioQualities[3].Id <= defaultAudioQuality - 1000 && playUrl.Dash.Dolby != null) + { + if (playUrl.Dash.Dolby.Audio != null && playUrl.Dash.Dolby.Audio.Count > 0) + { + ListHelper.AddUnique(audioQualityFormatList, audioQualities[3].Name); + } + } + + if (audioQualities[4].Id <= defaultAudioQuality - 1000 && playUrl.Dash.Flac != null) + { + if (playUrl.Dash.Flac.Audio != null) + { + ListHelper.AddUnique(audioQualityFormatList, audioQualities[4].Name); + } + } + + audioQualityFormatList.Sort(new StringLogicalComparer()); + audioQualityFormatList.Reverse(); + + return new ObservableCollection(audioQualityFormatList); + } + /// /// 设置画质 & 视频编码 /// @@ -130,9 +179,10 @@ namespace DownKyi.Services /// /// /// - private static List GetVideoQualityList(PlayUrl playUrl, UserInfoSettings userInfo, int defaultQuality, VideoCodecs videoCodecs) + private static List GetVideoQualityList(PlayUrl playUrl, UserInfoSettings userInfo, int defaultQuality, int videoCodecs) { List videoQualityList = new List(); + List codeIds = Constant.GetCodecIds(); if (playUrl.Dash.Video == null) { @@ -160,7 +210,8 @@ namespace DownKyi.Services // 寻找是否已存在这个画质 // 不存在则添加,存在则修改 - string codecName = GetVideoCodecName(video.Codecs); + //string codecName = GetVideoCodecName(video.Codecs); + string codecName = codeIds.FirstOrDefault(t => t.Id == video.CodecId).Name; VideoQuality videoQualityExist = videoQualityList.FirstOrDefault(t => t.Quality == video.Id); if (videoQualityExist == null) { @@ -191,7 +242,7 @@ namespace DownKyi.Services // 设置选中的视频编码 VideoQuality selectedVideoQuality = videoQualityList.FirstOrDefault(t => t.Quality == video.Id); - if(selectedVideoQuality == null) { continue; } + if (selectedVideoQuality == null) { continue; } if (videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].VideoCodecList.Count == 1) { @@ -200,24 +251,29 @@ namespace DownKyi.Services } // 设置选中的视频编码 - switch (videoCodecs) + //switch (videoCodecs) + //{ + // case VideoCodecs.AVC: + // if (videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].VideoCodecList.Contains("H.264/AVC")) + // { + // videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].SelectedVideoCodec = "H.264/AVC"; + // } + // break; + // case VideoCodecs.HEVC: + // if (videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].VideoCodecList.Contains("H.265/HEVC")) + // { + // videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].SelectedVideoCodec = "H.265/HEVC"; + // } + // break; + // case VideoCodecs.NONE: + // break; + // default: + // break; + //} + string videoCodecsName = codeIds.FirstOrDefault(t => t.Id == videoCodecs).Name; + if (videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].VideoCodecList.Contains(videoCodecsName)) { - case VideoCodecs.AVC: - if (videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].VideoCodecList.Contains("H.264/AVC")) - { - videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].SelectedVideoCodec = "H.264/AVC"; - } - break; - case VideoCodecs.HEVC: - if (videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].VideoCodecList.Contains("H.265/HEVC")) - { - videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].SelectedVideoCodec = "H.265/HEVC"; - } - break; - case VideoCodecs.NONE: - break; - default: - break; + videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].SelectedVideoCodec = videoCodecsName; } } @@ -230,10 +286,10 @@ namespace DownKyi.Services /// /// /// - internal static string GetVideoCodecName(string origin) - { - return origin.Contains("avc") ? "H.264/AVC" : origin.Contains("hev") ? "H.265/HEVC" : origin.Contains("dvh") || origin.Contains("hvc") ? "Dolby Vision" : ""; - } + //internal static string GetVideoCodecName(string origin) + //{ + // return origin.Contains("avc") ? "H.264/AVC" : origin.Contains("hev") ? "H.265/HEVC" : origin.Contains("dvh") || origin.Contains("hvc") ? "Dolby Vision" : ""; + //} } } diff --git a/DownKyi/Utils/DialogUtils.cs b/DownKyi/Utils/DialogUtils.cs index 41b08d7..9852eb4 100644 --- a/DownKyi/Utils/DialogUtils.cs +++ b/DownKyi/Utils/DialogUtils.cs @@ -37,5 +37,21 @@ namespace DownKyi.Utils return showDialog == true ? dialog.FileName : ""; } + + /// + /// 选择多个视频dialog + /// + /// + public static string[] SelectMultiVideoFile() + { + // 选择文件 + var dialog = new Microsoft.Win32.OpenFileDialog + { + Filter = "mp4 (*.mp4)|*.mp4", + Multiselect = true + }; + var showDialog = dialog.ShowDialog(); + return showDialog == true ? dialog.FileNames : new string[0]; + } } } diff --git a/DownKyi/ViewModels/PageViewModels/VideoPage.cs b/DownKyi/ViewModels/PageViewModels/VideoPage.cs index f04787b..2eb1b07 100644 --- a/DownKyi/ViewModels/PageViewModels/VideoPage.cs +++ b/DownKyi/ViewModels/PageViewModels/VideoPage.cs @@ -91,32 +91,32 @@ namespace DownKyi.ViewModels.PageViewModels /// private void ExecuteVideoQualitySelectedCommand() { - // 杜比视界 - string dolby = string.Empty; - try - { - var qualities = Constant.GetAudioQualities(); - dolby = qualities[3].Name; - } - catch (Exception e) - { - Core.Utils.Debugging.Console.PrintLine("ExecuteVideoQualitySelectedCommand()发生异常: {0}", e); - LogManager.Error("ExecuteVideoQualitySelectedCommand", e); - } - - if (VideoQuality != null && VideoQuality.Quality == 126 && PlayUrl != null && PlayUrl.Dash != null && PlayUrl.Dash.Dolby != null) - { - ListHelper.AddUnique(AudioQualityFormatList, dolby); - AudioQualityFormat = dolby; - } - else - { - if (AudioQualityFormatList.Contains(dolby)) - { - AudioQualityFormatList.Remove(dolby); - AudioQualityFormat = AudioQualityFormatList[0]; - } - } + //// 杜比视界 + //string dolby = string.Empty; + //try + //{ + // var qualities = Constant.GetAudioQualities(); + // dolby = qualities[3].Name; + //} + //catch (Exception e) + //{ + // Core.Utils.Debugging.Console.PrintLine("ExecuteVideoQualitySelectedCommand()发生异常: {0}", e); + // LogManager.Error("ExecuteVideoQualitySelectedCommand", e); + //} + + //if (VideoQuality != null && VideoQuality.Quality == 126 && PlayUrl != null && PlayUrl.Dash != null && PlayUrl.Dash.Dolby != null) + //{ + // ListHelper.AddUnique(AudioQualityFormatList, dolby); + // AudioQualityFormat = dolby; + //} + //else + //{ + // if (AudioQualityFormatList.Contains(dolby)) + // { + // AudioQualityFormatList.Remove(dolby); + // AudioQualityFormat = AudioQualityFormatList[0]; + // } + //} } #endregion diff --git a/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs b/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs index a46ce58..98c40c3 100644 --- a/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs +++ b/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs @@ -41,6 +41,13 @@ namespace DownKyi.ViewModels.Settings set => SetProperty(ref aria2c, value); } + private bool customAria2c; + public bool CustomAria2c + { + get => customAria2c; + set => SetProperty(ref customAria2c, value); + } + private List maxCurrentDownloads; public List MaxCurrentDownloads { @@ -90,6 +97,13 @@ namespace DownKyi.ViewModels.Settings set => SetProperty(ref httpProxyPort, value); } + private string ariaHost; + public string AriaHost + { + get => ariaHost; + set => SetProperty(ref ariaHost, value); + } + private int ariaListenPort; public int AriaListenPort { @@ -97,6 +111,13 @@ namespace DownKyi.ViewModels.Settings set => SetProperty(ref ariaListenPort, value); } + private string ariaToken; + public string AriaToken + { + get => ariaToken; + set => SetProperty(ref ariaToken, value); + } + private List ariaLogLevels; public List AriaLogLevels { @@ -259,6 +280,9 @@ namespace DownKyi.ViewModels.Settings case Downloader.ARIA: Aria2c = true; break; + case Downloader.CUSTOM_ARIA: + CustomAria2c = true; + break; } // builtin同时下载数 @@ -277,9 +301,15 @@ namespace DownKyi.ViewModels.Settings // builtin的http代理的端口 HttpProxyPort = SettingsManager.GetInstance().GetHttpProxyListenPort(); + // Aria服务器host + AriaHost = SettingsManager.GetInstance().GetAriaHost(); + // Aria服务器端口 AriaListenPort = SettingsManager.GetInstance().GetAriaListenPort(); + // Aria服务器Token + AriaToken = SettingsManager.GetInstance().GetAriaToken(); + // Aria的日志等级 AriaConfigLogLevel ariaLogLevel = SettingsManager.GetInstance().GetAriaLogLevel(); SelectedAriaLogLevel = ariaLogLevel.ToString("G"); @@ -349,6 +379,9 @@ namespace DownKyi.ViewModels.Settings case "Aria2c": downloader = Downloader.ARIA; break; + case "CustomAria2c": + downloader = Downloader.CUSTOM_ARIA; + break; default: downloader = SettingsManager.GetInstance().GetDownloader(); break; @@ -444,6 +477,21 @@ namespace DownKyi.ViewModels.Settings PublishTip(isSucceed); } + // Aria服务器host事件 + private DelegateCommand ariaHostCommand; + public DelegateCommand AriaHostCommand => ariaHostCommand ?? (ariaHostCommand = new DelegateCommand(ExecuteAriaHostCommand)); + + /// + /// Aria服务器host事件 + /// + /// + private void ExecuteAriaHostCommand(string parameter) + { + AriaHost = parameter; + bool isSucceed = SettingsManager.GetInstance().SetAriaHost(AriaHost); + PublishTip(isSucceed); + } + // Aria服务器端口事件 private DelegateCommand ariaListenPortCommand; public DelegateCommand AriaListenPortCommand => ariaListenPortCommand ?? (ariaListenPortCommand = new DelegateCommand(ExecuteAriaListenPortCommand)); @@ -461,6 +509,21 @@ namespace DownKyi.ViewModels.Settings PublishTip(isSucceed); } + // Aria服务器token事件 + private DelegateCommand ariaTokenCommand; + public DelegateCommand AriaTokenCommand => ariaTokenCommand ?? (ariaTokenCommand = new DelegateCommand(ExecuteAriaTokenCommand)); + + /// + /// Aria服务器token事件 + /// + /// + private void ExecuteAriaTokenCommand(string parameter) + { + AriaToken = parameter; + bool isSucceed = SettingsManager.GetInstance().SetAriaToken(AriaToken); + PublishTip(isSucceed); + } + // Aria的日志等级事件 private DelegateCommand ariaLogLevelsCommand; public DelegateCommand AriaLogLevelsCommand => ariaLogLevelsCommand ?? (ariaLogLevelsCommand = new DelegateCommand(ExecuteAriaLogLevelsCommand)); diff --git a/DownKyi/ViewModels/Settings/ViewVideoViewModel.cs b/DownKyi/ViewModels/Settings/ViewVideoViewModel.cs index e38dc5f..7bb76fb 100644 --- a/DownKyi/ViewModels/Settings/ViewVideoViewModel.cs +++ b/DownKyi/ViewModels/Settings/ViewVideoViewModel.cs @@ -24,15 +24,15 @@ namespace DownKyi.ViewModels.Settings #region 页面属性申明 - private List videoCodecs; - public List VideoCodecs + private List videoCodecs; + public List VideoCodecs { get => videoCodecs; set => SetProperty(ref videoCodecs, value); } - private string selectedVideoCodec; - public string SelectedVideoCodec + private Quality selectedVideoCodec; + public Quality SelectedVideoCodec { get => selectedVideoCodec; set => SetProperty(ref selectedVideoCodec, value); @@ -186,18 +186,21 @@ namespace DownKyi.ViewModels.Settings #region 属性初始化 // 优先下载的视频编码 - VideoCodecs = new List - { - "H.264/AVC", - "H.265/HEVC", - }; + VideoCodecs = Constant.GetCodecIds(); + //VideoCodecs = new List + //{ + // "H.264/AVC", + // "H.265/HEVC", + //}; // 优先下载画质 VideoQualityList = Constant.GetResolutions(); // 优先下载音质 AudioQualityList = Constant.GetAudioQualities(); - AudioQualityList.RemoveAt(3); + //AudioQualityList.RemoveAt(3); + AudioQualityList[3].Id = AudioQualityList[3].Id + 1000; + AudioQualityList[4].Id = AudioQualityList[4].Id + 1000; // 文件命名格式 SelectedFileName = new ObservableCollection(); @@ -253,8 +256,9 @@ namespace DownKyi.ViewModels.Settings isOnNavigatedTo = true; // 优先下载的视频编码 - VideoCodecs videoCodecs = SettingsManager.GetInstance().GetVideoCodecs(); - SelectedVideoCodec = GetVideoCodecsString(videoCodecs); + int videoCodecs = SettingsManager.GetInstance().GetVideoCodecs(); + //SelectedVideoCodec = GetVideoCodecsString(videoCodecs); + SelectedVideoCodec = VideoCodecs.FirstOrDefault(t => { return t.Id == videoCodecs; }); // 优先下载画质 int quality = SettingsManager.GetInstance().GetQuality(); @@ -315,18 +319,20 @@ namespace DownKyi.ViewModels.Settings #region 命令申明 // 优先下载的视频编码事件 - private DelegateCommand videoCodecsCommand; - public DelegateCommand VideoCodecsCommand => videoCodecsCommand ?? (videoCodecsCommand = new DelegateCommand(ExecuteVideoCodecsCommand)); + private DelegateCommand videoCodecsCommand; + public DelegateCommand VideoCodecsCommand => videoCodecsCommand ?? (videoCodecsCommand = new DelegateCommand(ExecuteVideoCodecsCommand)); /// /// 优先下载的视频编码事件 /// /// - private void ExecuteVideoCodecsCommand(string parameter) + private void ExecuteVideoCodecsCommand(object parameter) { - VideoCodecs videoCodecs = GetVideoCodecs(parameter); + //VideoCodecs videoCodecs = GetVideoCodecs(parameter); - bool isSucceed = SettingsManager.GetInstance().SetVideoCodecs(videoCodecs); + if (!(parameter is Quality videoCodecs)) { return; } + + bool isSucceed = SettingsManager.GetInstance().SetVideoCodecs(videoCodecs.Id); PublishTip(isSucceed); } @@ -674,49 +680,49 @@ namespace DownKyi.ViewModels.Settings /// /// /// - private string GetVideoCodecsString(VideoCodecs videoCodecs) - { - string codec; - switch (videoCodecs) - { - case Core.Settings.VideoCodecs.NONE: - codec = ""; - break; - case Core.Settings.VideoCodecs.AVC: - codec = "H.264/AVC"; - break; - case Core.Settings.VideoCodecs.HEVC: - codec = "H.265/HEVC"; - break; - default: - codec = ""; - break; - } - return codec; - } + //private string GetVideoCodecsString(VideoCodecs videoCodecs) + //{ + // string codec; + // switch (videoCodecs) + // { + // case Core.Settings.VideoCodecs.NONE: + // codec = ""; + // break; + // case Core.Settings.VideoCodecs.AVC: + // codec = "H.264/AVC"; + // break; + // case Core.Settings.VideoCodecs.HEVC: + // codec = "H.265/HEVC"; + // break; + // default: + // codec = ""; + // break; + // } + // return codec; + //} /// /// 返回VideoCodecs /// /// /// - private VideoCodecs GetVideoCodecs(string str) - { - VideoCodecs videoCodecs; - switch (str) - { - case "H.264/AVC": - videoCodecs = Core.Settings.VideoCodecs.AVC; - break; - case "H.265/HEVC": - videoCodecs = Core.Settings.VideoCodecs.HEVC; - break; - default: - videoCodecs = Core.Settings.VideoCodecs.NONE; - break; - } - return videoCodecs; - } + //private VideoCodecs GetVideoCodecs(string str) + //{ + // VideoCodecs videoCodecs; + // switch (str) + // { + // case "H.264/AVC": + // videoCodecs = Core.Settings.VideoCodecs.AVC; + // break; + // case "H.265/HEVC": + // videoCodecs = Core.Settings.VideoCodecs.HEVC; + // break; + // default: + // videoCodecs = Core.Settings.VideoCodecs.NONE; + // break; + // } + // return videoCodecs; + //} /// /// 保存下载视频内容到设置 diff --git a/DownKyi/ViewModels/Toolbox/ViewExtractMediaViewModel.cs b/DownKyi/ViewModels/Toolbox/ViewExtractMediaViewModel.cs index bd8e1e7..cbd4cd6 100644 --- a/DownKyi/ViewModels/Toolbox/ViewExtractMediaViewModel.cs +++ b/DownKyi/ViewModels/Toolbox/ViewExtractMediaViewModel.cs @@ -18,11 +18,25 @@ namespace DownKyi.ViewModels.Toolbox #region 页面属性申明 - private string videoPath; - public string VideoPath + private string videoPathsStr; + public string VideoPathsStr { - get { return videoPath; } - set { SetProperty(ref videoPath, value); } + get => videoPathsStr; + set + { + SetProperty(ref videoPathsStr, value); + } + } + + private string[] videoPaths; + public string[] VideoPaths + { + get => videoPaths; + set + { + videoPaths = value; + VideoPathsStr = string.Join(Environment.NewLine, value); + } } private string status; @@ -38,7 +52,7 @@ namespace DownKyi.ViewModels.Toolbox { #region 属性初始化 - VideoPath = string.Empty; + VideoPaths = new string[0]; #endregion } @@ -60,7 +74,7 @@ namespace DownKyi.ViewModels.Toolbox return; } - VideoPath = DialogUtils.SelectVideoFile(); + VideoPaths = DialogUtils.SelectMultiVideoFile(); } // 提取音频事件 @@ -78,24 +92,27 @@ namespace DownKyi.ViewModels.Toolbox return; } - if (VideoPath == "") + if (VideoPaths.Length <= 0) { eventAggregator.GetEvent().Publish(DictionaryResource.GetString("TipNoSeletedVideo")); return; } - // 音频文件名 - string audioFileName = VideoPath.Remove(VideoPath.Length - 4, 4) + ".aac"; Status = string.Empty; await Task.Run(() => { - // 执行提取音频程序 isExtracting = true; - FFmpegHelper.ExtractAudio(VideoPath, audioFileName, new Action((output) => + foreach (var item in VideoPaths) { - Status += output + "\n"; - })); + // 音频文件名 + string audioFileName = item.Remove(item.Length - 4, 4) + ".aac"; + // 执行提取音频程序 + FFmpegHelper.ExtractAudio(item, audioFileName, new Action((output) => + { + Status += output + "\n"; + })); + } isExtracting = false; }); } @@ -115,30 +132,34 @@ namespace DownKyi.ViewModels.Toolbox return; } - if (VideoPath == "") + if (VideoPaths.Length <= 0) { eventAggregator.GetEvent().Publish(DictionaryResource.GetString("TipNoSeletedVideo")); return; } - // 视频文件名 - string videoFileName = VideoPath.Remove(VideoPath.Length - 4, 4) + "_onlyVideo.mp4"; Status = string.Empty; await Task.Run(() => { - // 执行提取视频程序 isExtracting = true; - FFmpegHelper.ExtractVideo(VideoPath, videoFileName, new Action((output) => + foreach (var item in VideoPaths) { - Status += output + "\n"; - })); + // 视频文件名 + string videoFileName = item.Remove(item.Length - 4, 4) + "_onlyVideo.mp4"; + // 执行提取视频程序 + FFmpegHelper.ExtractVideo(item, videoFileName, new Action((output) => + { + Status += output + "\n"; + })); + } isExtracting = false; }); } // Status改变事件 private DelegateCommand statusCommand; + public DelegateCommand StatusCommand => statusCommand ?? (statusCommand = new DelegateCommand(ExecuteStatusCommand)); /// diff --git a/DownKyi/ViewModels/UserSpace/SeasonsSeries.cs b/DownKyi/ViewModels/UserSpace/SeasonsSeries.cs new file mode 100644 index 0000000..0641112 --- /dev/null +++ b/DownKyi/ViewModels/UserSpace/SeasonsSeries.cs @@ -0,0 +1,47 @@ +using DownKyi.Images; +using Prism.Mvvm; +using System.Windows.Media; + +namespace DownKyi.ViewModels.UserSpace +{ + public class SeasonsSeries : BindableBase + { + public long Id { get; set; } + + private ImageSource cover; + public ImageSource Cover + { + get => cover; + set => SetProperty(ref cover, value); + } + + private VectorImage typeImage; + public VectorImage TypeImage + { + get => typeImage; + set => SetProperty(ref typeImage, value); + } + + private string name; + public string Name + { + get => name; + set => SetProperty(ref name, value); + } + + private int count; + public int Count + { + get => count; + set => SetProperty(ref count, value); + } + + private string ctime; + public string Ctime + { + get => ctime; + set => SetProperty(ref ctime, value); + } + + } +} diff --git a/DownKyi/ViewModels/UserSpace/ViewSeasonsSeriesViewModel.cs b/DownKyi/ViewModels/UserSpace/ViewSeasonsSeriesViewModel.cs new file mode 100644 index 0000000..b8f783b --- /dev/null +++ b/DownKyi/ViewModels/UserSpace/ViewSeasonsSeriesViewModel.cs @@ -0,0 +1,199 @@ +using DownKyi.Core.BiliApi.Users.Models; +using DownKyi.Core.Storage; +using DownKyi.Events; +using DownKyi.Images; +using Prism.Commands; +using Prism.Events; +using Prism.Regions; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace DownKyi.ViewModels.UserSpace +{ + public class ViewSeasonsSeriesViewModel : BaseViewModel + { + public const string Tag = "PageUserSpaceSeasonsSeries"; + + private long mid = -1; + + #region 页面属性申明 + + private ObservableCollection seasonsSeries; + public ObservableCollection SeasonsSeries + { + get => seasonsSeries; + set => SetProperty(ref seasonsSeries, value); + } + + private int selectedItem; + public int SelectedItem + { + get => selectedItem; + set => SetProperty(ref selectedItem, value); + } + + #endregion + + public ViewSeasonsSeriesViewModel(IEventAggregator eventAggregator) : base(eventAggregator) + { + #region 属性初始化 + + SeasonsSeries = new ObservableCollection(); + + #endregion + } + + #region 命令申明 + + // 视频选择事件 + private DelegateCommand seasonsSeriesCommand; + public DelegateCommand SeasonsSeriesCommand => seasonsSeriesCommand ?? (seasonsSeriesCommand = new DelegateCommand(ExecuteSeasonsSeriesCommand)); + + /// + /// 视频选择事件 + /// + /// + private void ExecuteSeasonsSeriesCommand(object parameter) + { + if (!(parameter is SeasonsSeries seasonsSeries)) { return; } + + // 应该用枚举的,偷懒直接用数字 + int type = 0; + if (seasonsSeries.TypeImage == NormalIcon.Instance().SeasonsSeries) + { + type = 1; + } + else if (seasonsSeries.TypeImage == NormalIcon.Instance().Channel1) + { + type = 2; + } + Dictionary data = new Dictionary + { + { "mid", mid }, + { "id", seasonsSeries.Id }, + { "name", seasonsSeries.Name }, + { "count", seasonsSeries.Count }, + { "type", type } + }; + + // 进入视频页面 + NavigationParam param = new NavigationParam + { + ViewName = ViewModels.ViewSeasonsSeriesViewModel.Tag, + ParentViewName = ViewUserSpaceViewModel.Tag, + Parameter = data + }; + eventAggregator.GetEvent().Publish(param); + + SelectedItem = -1; + } + + #endregion + + public override void OnNavigatedFrom(NavigationContext navigationContext) + { + base.OnNavigatedFrom(navigationContext); + + SeasonsSeries.Clear(); + SelectedItem = -1; + } + + /// + /// 接收mid参数 + /// + /// + public async override void OnNavigatedTo(NavigationContext navigationContext) + { + base.OnNavigatedTo(navigationContext); + + SeasonsSeries.Clear(); + SelectedItem = -1; + + // 根据传入参数不同执行不同任务 + var parameter = navigationContext.Parameters.GetValue("object"); + if (parameter == null) + { + return; + } + + // 传入mid + mid = navigationContext.Parameters.GetValue("mid"); + + foreach (var item in parameter.SeasonsList) + { + if (item.Meta.Total <= 0) { continue; } + + BitmapImage image = null; + if (item.Meta.Cover == null || item.Meta.Cover == "") + { + image = new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png")); + } + else + { + StorageCover storageCover = new StorageCover(); + string cover = null; + await Task.Run(() => + { + cover = storageCover.GetCover(item.Meta.Cover); + }); + image = storageCover.GetCoverThumbnail(cover, 190, 190); + } + + // 当地时区 + DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); + DateTime dateCTime = startTime.AddSeconds(item.Meta.Ptime); + string mtime = dateCTime.ToString("yyyy-MM-dd"); + + SeasonsSeries.Add(new SeasonsSeries + { + Id = item.Meta.SeasonId, + Cover = image, + TypeImage = NormalIcon.Instance().SeasonsSeries, + Name = item.Meta.Name, + Count = item.Meta.Total, + Ctime = mtime + }); + } + + foreach (var item in parameter.SeriesList) + { + if (item.Meta.Total <= 0) { continue; } + + BitmapImage image = null; + if (item.Meta.Cover == null || item.Meta.Cover == "") + { + image = new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png")); + } + else + { + StorageCover storageCover = new StorageCover(); + string cover = null; + await Task.Run(() => + { + cover = storageCover.GetCover(item.Meta.Cover); + }); + image = storageCover.GetCoverThumbnail(cover, 190, 190); + } + + // 当地时区 + DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); + DateTime dateCTime = startTime.AddSeconds(item.Meta.Mtime); + string mtime = dateCTime.ToString("yyyy-MM-dd"); + + SeasonsSeries.Add(new SeasonsSeries + { + Id = item.Meta.SeriesId, + Cover = image, + TypeImage = NormalIcon.Instance().Channel1, + Name = item.Meta.Name, + Count = item.Meta.Total, + Ctime = mtime + }); + } + + } + } +} diff --git a/DownKyi/ViewModels/ViewIndexViewModel.cs b/DownKyi/ViewModels/ViewIndexViewModel.cs index ee1fbc8..9eaeb10 100644 --- a/DownKyi/ViewModels/ViewIndexViewModel.cs +++ b/DownKyi/ViewModels/ViewIndexViewModel.cs @@ -12,6 +12,7 @@ using Prism.Regions; using Prism.Services.Dialogs; using System; using System.IO; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Imaging; @@ -214,7 +215,7 @@ namespace DownKyi.ViewModels } LogManager.Debug(Tag, $"InputText: {InputText}"); - + InputText = Regex.Replace(InputText, @"[【]*[^【]*[^】]*[】 ]", ""); SearchService searchService = new SearchService(); bool isSupport = searchService.BiliInput(InputText, Tag, eventAggregator); if (!isSupport) diff --git a/DownKyi/ViewModels/ViewSeasonsSeriesViewModel.cs b/DownKyi/ViewModels/ViewSeasonsSeriesViewModel.cs new file mode 100644 index 0000000..0c4377f --- /dev/null +++ b/DownKyi/ViewModels/ViewSeasonsSeriesViewModel.cs @@ -0,0 +1,672 @@ +using DownKyi.Core.BiliApi.VideoStream; +using DownKyi.Core.Storage; +using DownKyi.Core.Utils; +using DownKyi.CustomControl; +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; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media.Imaging; + +namespace DownKyi.ViewModels +{ + public class ViewSeasonsSeriesViewModel : BaseViewModel + { + public const string Tag = "PageSeasonsSeries"; + + private readonly IDialogService dialogService; + + private CancellationTokenSource tokenSource; + + private long mid = -1; + private long id = -1; + private int type = 0; + + // 每页视频数量,暂时在此写死,以后在设置中增加选项 + private readonly int VideoNumberInPage = 30; + + #region 页面属性申明 + + private string pageName = Tag; + public string PageName + { + get => pageName; + set => SetProperty(ref pageName, 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 Visibility noDataVisibility; + public Visibility NoDataVisibility + { + get => noDataVisibility; + set => SetProperty(ref noDataVisibility, value); + } + + private VectorImage arrowBack; + public VectorImage ArrowBack + { + get => arrowBack; + set => SetProperty(ref arrowBack, value); + } + + private VectorImage downloadManage; + public VectorImage DownloadManage + { + get => downloadManage; + set => SetProperty(ref downloadManage, value); + } + + private string title; + public string Title + { + get => title; + set => SetProperty(ref title, value); + } + + private bool isEnabled = true; + public bool IsEnabled + { + get => isEnabled; + set => SetProperty(ref isEnabled, value); + } + + private CustomPagerViewModel pager; + public CustomPagerViewModel Pager + { + get => pager; + set => SetProperty(ref pager, value); + } + + private ObservableCollection medias; + public ObservableCollection Medias + { + get => medias; + set => SetProperty(ref medias, value); + } + + private bool isSelectAll; + public bool IsSelectAll + { + get => isSelectAll; + set => SetProperty(ref isSelectAll, value); + } + + #endregion + + public ViewSeasonsSeriesViewModel(IEventAggregator eventAggregator, IDialogService dialogService) : base(eventAggregator) + { + this.dialogService = dialogService; + + #region 属性初始化 + + // 初始化loading gif + Loading = new GifImage(Properties.Resources.loading); + Loading.StartAnimate(); + LoadingVisibility = Visibility.Collapsed; + NoDataVisibility = 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"); + + Medias = new ObservableCollection(); + + #endregion + } + + #region 命令申明 + + // 返回事件 + private DelegateCommand backSpaceCommand; + public DelegateCommand BackSpaceCommand => backSpaceCommand ?? (backSpaceCommand = new DelegateCommand(ExecuteBackSpace)); + + /// + /// 返回事件 + /// + private void ExecuteBackSpace() + { + ArrowBack.Fill = DictionaryResource.GetColor("ColorText"); + + // 结束任务 + tokenSource?.Cancel(); + + 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 selectAllCommand; + public DelegateCommand SelectAllCommand => selectAllCommand ?? (selectAllCommand = new DelegateCommand(ExecuteSelectAllCommand)); + + /// + /// 全选按钮点击事件 + /// + /// + private void ExecuteSelectAllCommand(object parameter) + { + if (IsSelectAll) + { + foreach (var item in Medias) + { + item.IsSelected = true; + } + } + else + { + foreach (var item in Medias) + { + item.IsSelected = false; + } + } + } + + // 列表选择事件 + private DelegateCommand mediasCommand; + public DelegateCommand MediasCommand => mediasCommand ?? (mediasCommand = new DelegateCommand(ExecuteMediasCommand)); + + /// + /// 列表选择事件 + /// + /// + private void ExecuteMediasCommand(object parameter) + { + if (!(parameter is IList selectedMedia)) { return; } + + if (selectedMedia.Count == Medias.Count) + { + IsSelectAll = true; + } + else + { + IsSelectAll = false; + } + } + + // 添加选中项到下载列表事件 + private DelegateCommand addToDownloadCommand; + public DelegateCommand AddToDownloadCommand => addToDownloadCommand ?? (addToDownloadCommand = new DelegateCommand(ExecuteAddToDownloadCommand)); + + /// + /// 添加选中项到下载列表事件 + /// + private void ExecuteAddToDownloadCommand() + { + AddToDownload(true); + } + + // 添加所有视频到下载列表事件 + private DelegateCommand addAllToDownloadCommand; + public DelegateCommand AddAllToDownloadCommand => addAllToDownloadCommand ?? (addAllToDownloadCommand = new DelegateCommand(ExecuteAddAllToDownloadCommand)); + + /// + /// 添加所有视频到下载列表事件 + /// + private void ExecuteAddAllToDownloadCommand() + { + AddToDownload(false); + } + + #endregion + + /// + /// 添加到下载 + /// + /// + private async void AddToDownload(bool isOnlySelected) + { + // 频道里只有视频 + AddToDownloadService addToDownloadService = new AddToDownloadService(PlayStreamType.VIDEO); + + // 选择文件夹 + string directory = addToDownloadService.SetDirectory(dialogService); + + // 视频计数 + int i = 0; + await Task.Run(() => + { + // 为了避免执行其他操作时, + // Medias变化导致的异常 + var list = Medias.ToList(); + + // 添加到下载 + foreach (var media in list) + { + // 只下载选中项,跳过未选中项 + 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 (directory == null) + { + return; + } + + // 通知用户添加到下载列表的结果 + if (i <= 0) + { + eventAggregator.GetEvent().Publish(DictionaryResource.GetString("TipAddDownloadingZero")); + } + else + { + eventAggregator.GetEvent().Publish($"{DictionaryResource.GetString("TipAddDownloadingFinished1")}{i}{DictionaryResource.GetString("TipAddDownloadingFinished2")}"); + } + } + + private void OnCountChanged_Pager(int count) { } + + private bool OnCurrentChanged_Pager(int old, int current) + { + if (!IsEnabled) + { + //Pager.Current = old; + return false; + } + + Medias.Clear(); + IsSelectAll = false; + LoadingVisibility = Visibility.Visible; + NoDataVisibility = Visibility.Collapsed; + + //UpdateChannel(current); + + if (type == 1) { UpdateSeasons(current); } + if (type == 2) { UpdateSeries(current); } + + return true; + } + + //private async void UpdateChannel(int current) + //{ + // // 是否正在获取数据 + // // 在所有的退出分支中都需要设为true + // IsEnabled = false; + + // await Task.Run(() => + // { + // CancellationToken cancellationToken = tokenSource.Token; + + // var channels = Core.BiliApi.Users.UserSpace.GetChannelVideoList(mid, cid, current, VideoNumberInPage); + // if (channels == null || channels.Count == 0) + // { + // // 没有数据,UI提示 + // LoadingVisibility = Visibility.Collapsed; + // NoDataVisibility = Visibility.Visible; + // return; + // } + + // foreach (var video in channels) + // { + // if (video.Cid == 0) + // { + // continue; + // } + + // // 查询、保存封面 + // string coverUrl = video.Pic; + // BitmapImage cover; + // if (coverUrl == null || coverUrl == "") + // { + // cover = null; // new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png")); + // } + // else + // { + // if (!coverUrl.ToLower().StartsWith("http")) + // { + // coverUrl = $"https:{video.Pic}"; + // } + + // StorageCover storageCover = new StorageCover(); + // cover = storageCover.GetCoverThumbnail(video.Aid, video.Bvid, -1, coverUrl, 200, 125); + // } + + // // 播放数 + // string play = string.Empty; + // if (video.Stat != null) + // { + // if (video.Stat.View > 0) + // { + // play = Format.FormatNumber(video.Stat.View); + // } + // else + // { + // play = "--"; + // } + // } + // else + // { + // play = "--"; + // } + + // DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); // 当地时区 + // DateTime dateCTime = startTime.AddSeconds(video.Ctime); + // string ctime = dateCTime.ToString("yyyy-MM-dd"); + + // App.PropertyChangeAsync(new Action(() => + // { + // ChannelMedia media = new ChannelMedia(eventAggregator) + // { + // Avid = video.Aid, + // Bvid = video.Bvid, + // Cover = cover ?? new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png")), + // Duration = Format.FormatDuration3(video.Duration), + // Title = video.Title, + // PlayNumber = play, + // CreateTime = ctime + // }; + // Medias.Add(media); + + // LoadingVisibility = Visibility.Collapsed; + // NoDataVisibility = Visibility.Collapsed; + // })); + + // // 判断是否该结束线程,若为true,跳出循环 + // if (cancellationToken.IsCancellationRequested) + // { + // break; + // } + // } + + // }, (tokenSource = new CancellationTokenSource()).Token); + + // IsEnabled = true; + //} + + private async void UpdateSeasons(int current) + { + // 是否正在获取数据 + // 在所有的退出分支中都需要设为true + IsEnabled = false; + + await Task.Run(() => + { + CancellationToken cancellationToken = tokenSource.Token; + + var seasons = Core.BiliApi.Users.UserSpace.GetSeasonsDetail(mid, id, current, VideoNumberInPage); + if (seasons == null || seasons.Meta.Total == 0) + { + // 没有数据,UI提示 + LoadingVisibility = Visibility.Collapsed; + NoDataVisibility = Visibility.Visible; + return; + } + + foreach (var video in seasons.Archives) + { + //if (video.Cid == 0) + //{ + // continue; + //} + + // 查询、保存封面 + string coverUrl = video.Pic; + BitmapImage cover; + if (coverUrl == null || coverUrl == "") + { + cover = null; // new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png")); + } + else + { + if (!coverUrl.ToLower().StartsWith("http")) + { + coverUrl = $"https:{video.Pic}"; + } + + StorageCover storageCover = new StorageCover(); + cover = storageCover.GetCoverThumbnail(video.Aid, video.Bvid, -1, coverUrl, 200, 125); + } + + // 播放数 + string play = string.Empty; + if (video.Stat != null) + { + if (video.Stat.View > 0) + { + play = Format.FormatNumber(video.Stat.View); + } + else + { + play = "--"; + } + } + else + { + play = "--"; + } + + DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); // 当地时区 + DateTime dateCTime = startTime.AddSeconds(video.Ctime); + string ctime = dateCTime.ToString("yyyy-MM-dd"); + + App.PropertyChangeAsync(new Action(() => + { + ChannelMedia media = new ChannelMedia(eventAggregator) + { + Avid = video.Aid, + Bvid = video.Bvid, + Cover = cover ?? new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png")), + Duration = Format.FormatDuration3(video.Duration), + Title = video.Title, + PlayNumber = play, + CreateTime = ctime + }; + Medias.Add(media); + + LoadingVisibility = Visibility.Collapsed; + NoDataVisibility = Visibility.Collapsed; + })); + + // 判断是否该结束线程,若为true,跳出循环 + if (cancellationToken.IsCancellationRequested) + { + break; + } + } + + }, (tokenSource = new CancellationTokenSource()).Token); + + IsEnabled = true; + } + + private async void UpdateSeries(int current) + { + // 是否正在获取数据 + // 在所有的退出分支中都需要设为true + IsEnabled = false; + + await Task.Run(() => + { + CancellationToken cancellationToken = tokenSource.Token; + + var meta = Core.BiliApi.Users.UserSpace.GetSeriesMeta(id); + var series = Core.BiliApi.Users.UserSpace.GetSeriesDetail(mid, id, current, VideoNumberInPage); + if (series == null || meta.Meta.Total == 0) + { + // 没有数据,UI提示 + LoadingVisibility = Visibility.Collapsed; + NoDataVisibility = Visibility.Visible; + return; + } + + foreach (var video in series.Archives) + { + //if (video.Cid == 0) + //{ + // continue; + //} + + // 查询、保存封面 + string coverUrl = video.Pic; + BitmapImage cover; + if (coverUrl == null || coverUrl == "") + { + cover = null; // new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png")); + } + else + { + if (!coverUrl.ToLower().StartsWith("http")) + { + coverUrl = $"https:{video.Pic}"; + } + + StorageCover storageCover = new StorageCover(); + cover = storageCover.GetCoverThumbnail(video.Aid, video.Bvid, -1, coverUrl, 200, 125); + } + + // 播放数 + string play = string.Empty; + if (video.Stat != null) + { + if (video.Stat.View > 0) + { + play = Format.FormatNumber(video.Stat.View); + } + else + { + play = "--"; + } + } + else + { + play = "--"; + } + + DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1)); // 当地时区 + DateTime dateCTime = startTime.AddSeconds(video.Ctime); + string ctime = dateCTime.ToString("yyyy-MM-dd"); + + App.PropertyChangeAsync(new Action(() => + { + ChannelMedia media = new ChannelMedia(eventAggregator) + { + Avid = video.Aid, + Bvid = video.Bvid, + Cover = cover ?? new BitmapImage(new Uri($"pack://application:,,,/Resources/video-placeholder.png")), + Duration = Format.FormatDuration3(video.Duration), + Title = video.Title, + PlayNumber = play, + CreateTime = ctime + }; + Medias.Add(media); + + LoadingVisibility = Visibility.Collapsed; + NoDataVisibility = Visibility.Collapsed; + })); + + // 判断是否该结束线程,若为true,跳出循环 + if (cancellationToken.IsCancellationRequested) + { + break; + } + } + + }, (tokenSource = new CancellationTokenSource()).Token); + + IsEnabled = true; + } + + /// + /// 导航到VideoDetail页面时执行 + /// + /// + public override void OnNavigatedTo(NavigationContext navigationContext) + { + base.OnNavigatedTo(navigationContext); + + ArrowBack.Fill = DictionaryResource.GetColor("ColorTextDark"); + + DownloadManage = ButtonIcon.Instance().DownloadManage; + DownloadManage.Height = 24; + DownloadManage.Width = 24; + DownloadManage.Fill = DictionaryResource.GetColor("ColorPrimary"); + + // 根据传入参数不同执行不同任务 + var parameter = navigationContext.Parameters.GetValue>("Parameter"); + if (parameter == null) + { + return; + } + + Medias.Clear(); + IsSelectAll = false; + + mid = (long)parameter["mid"]; + id = (long)parameter["id"]; + type = (int)parameter["type"]; + Title = (string)parameter["name"]; + int count = (int)parameter["count"]; + + // 页面选择 + Pager = new CustomPagerViewModel(1, (int)Math.Ceiling((double)count / VideoNumberInPage)); + Pager.CurrentChanged += OnCurrentChanged_Pager; + Pager.CountChanged += OnCountChanged_Pager; + Pager.Current = 1; + } + + } +} diff --git a/DownKyi/ViewModels/ViewUserSpaceViewModel.cs b/DownKyi/ViewModels/ViewUserSpaceViewModel.cs index 6bef7cb..830fba7 100644 --- a/DownKyi/ViewModels/ViewUserSpaceViewModel.cs +++ b/DownKyi/ViewModels/ViewUserSpaceViewModel.cs @@ -228,12 +228,15 @@ namespace DownKyi.ViewModels switch (banner.Id) { - case 0: + case 0: // 投稿 regionManager.RequestNavigate("UserSpaceContentRegion", ViewArchiveViewModel.Tag, param); break; - case 1: + case 1: // 频道(弃用) regionManager.RequestNavigate("UserSpaceContentRegion", UserSpace.ViewChannelViewModel.Tag, param); break; + case 2: // 合集和列表 + regionManager.RequestNavigate("UserSpaceContentRegion", UserSpace.ViewSeasonsSeriesViewModel.Tag, param); + break; } } @@ -427,20 +430,38 @@ namespace DownKyi.ViewModels } // 频道 - List channelList = null; + //List channelList = null; + //await Task.Run(() => + //{ + // channelList = Core.BiliApi.Users.UserSpace.GetChannelList(mid); + //}); + //if (channelList != null && channelList.Count > 0) + //{ + // TabLeftBanners.Add(new TabLeftBanner + // { + // Object = channelList, + // Id = 1, + // Icon = NormalIcon.Instance().Channel, + // IconColor = "#FF23C9ED", + // Title = DictionaryResource.GetString("Channel") + // }); + //} + + // 合集和列表 + SpaceSeasonsSeries seasonsSeries = null; await Task.Run(() => { - channelList = Core.BiliApi.Users.UserSpace.GetChannelList(mid); + seasonsSeries = Core.BiliApi.Users.UserSpace.GetSeasonsSeries(mid, 1, 20); }); - if (channelList != null && channelList.Count > 0) + if (seasonsSeries != null && seasonsSeries.Page.Total > 0) { TabLeftBanners.Add(new TabLeftBanner { - Object = channelList, - Id = 1, + Object = seasonsSeries, + Id = 2, Icon = NormalIcon.Instance().Channel, IconColor = "#FF23C9ED", - Title = DictionaryResource.GetString("Channel") + Title = DictionaryResource.GetString("SeasonsSeries") }); } diff --git a/DownKyi/ViewModels/ViewVideoDetailViewModel.cs b/DownKyi/ViewModels/ViewVideoDetailViewModel.cs index 738f4b5..5d7551b 100644 --- a/DownKyi/ViewModels/ViewVideoDetailViewModel.cs +++ b/DownKyi/ViewModels/ViewVideoDetailViewModel.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows; @@ -184,7 +185,7 @@ namespace DownKyi.ViewModels if (InputText == null || InputText == string.Empty) { return; } LogManager.Debug(Tag, $"InputText: {InputText}"); - + InputText = Regex.Replace(InputText, @"[【]*[^【]*[^】]*[】 ]", ""); input = InputText; // 更新页面 diff --git a/DownKyi/Views/Settings/ViewNetwork.xaml b/DownKyi/Views/Settings/ViewNetwork.xaml index 23a3e49..884df8e 100644 --- a/DownKyi/Views/Settings/ViewNetwork.xaml +++ b/DownKyi/Views/Settings/ViewNetwork.xaml @@ -56,6 +56,15 @@ Foreground="{DynamicResource BrushTextDark}" IsChecked="{Binding Aria2c}" Style="{StaticResource RadioStyle}" /> + @@ -208,6 +217,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DownKyi/Views/Settings/ViewVideo.xaml b/DownKyi/Views/Settings/ViewVideo.xaml index 4511b48..10eca51 100644 --- a/DownKyi/Views/Settings/ViewVideo.xaml +++ b/DownKyi/Views/Settings/ViewVideo.xaml @@ -23,6 +23,7 @@ Name="nameVideoCodecs" Width="120" VerticalContentAlignment="Center" + DisplayMemberPath="Name" ItemsSource="{Binding VideoCodecs}" SelectedValue="{Binding SelectedVideoCodec}"> diff --git a/DownKyi/Views/Toolbox/ViewExtractMedia.xaml b/DownKyi/Views/Toolbox/ViewExtractMedia.xaml index b47bb26..b3d01b2 100644 --- a/DownKyi/Views/Toolbox/ViewExtractMedia.xaml +++ b/DownKyi/Views/Toolbox/ViewExtractMedia.xaml @@ -9,7 +9,7 @@ - + @@ -27,12 +27,12 @@ Orientation="Horizontal"> + Text="{Binding VideoPathsStr, Mode=TwoWay}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +