合并主要开发分支,解决冲突

croire 2 years ago
commit 0cb78dc396

@ -16,7 +16,12 @@ namespace DownKyi.Core.Aria2cNet.Client
public static class AriaClient public static class AriaClient
{ {
private static readonly string JSONRPC = "2.0"; 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;
/// <summary> /// <summary>
/// This method adds a new download. /// This method adds a new download.
@ -41,7 +46,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
uris, uris,
option option
}; };
@ -91,7 +96,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
torrent, torrent,
uris, uris,
option option
@ -136,7 +141,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
metalink, metalink,
uris, uris,
option option
@ -168,7 +173,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -193,7 +198,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -220,7 +225,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -242,7 +247,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -266,7 +271,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -288,7 +293,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -311,7 +316,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -333,7 +338,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -360,7 +365,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -384,7 +389,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -408,7 +413,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -433,7 +438,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -457,7 +462,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -480,7 +485,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -520,7 +525,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
offset, offset,
num num
}; };
@ -552,7 +557,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
offset, offset,
num num
}; };
@ -591,7 +596,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid, gid,
pos, pos,
how.ToString("G") how.ToString("G")
@ -635,7 +640,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid, gid,
fileIndex, fileIndex,
delUris, delUris,
@ -669,7 +674,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
@ -705,7 +710,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid, gid,
option option
}; };
@ -735,7 +740,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -770,7 +775,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
option option
}; };
@ -793,7 +798,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -814,7 +819,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -836,7 +841,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN, "token:" + token,
gid gid
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
@ -858,7 +863,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -881,7 +886,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -902,7 +907,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -926,7 +931,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -947,7 +952,7 @@ namespace DownKyi.Core.Aria2cNet.Client
{ {
List<object> ariaParams = new List<object> List<object> ariaParams = new List<object>
{ {
"token:" + TOKEN "token:" + token,
}; };
AriaSendData ariaSend = new AriaSendData AriaSendData ariaSend = new AriaSendData
{ {
@ -1018,13 +1023,36 @@ namespace DownKyi.Core.Aria2cNet.Client
return await GetRpcResponseAsync<SystemListNotifications>(ariaSend); return await GetRpcResponseAsync<SystemListNotifications>(ariaSend);
} }
/// <summary>
/// 设置aria token
/// </summary>
/// <param name="token"></param>
public static void SetToken(string token = TOKEN)
{
AriaClient.token = token;
}
/// <summary>
/// 设置aria host
/// </summary>
/// <param name="host"></param>
public static void SetHost(string host = LOCAL_HOST)
{
AriaClient.host = host;
}
public static void SetListenPort(int listenPort = LISTEN_PORT)
{
AriaClient.listenPort = listenPort;
}
/// <summary> /// <summary>
/// 获取jsonrpc的地址 /// 获取jsonrpc的地址
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private static string GetRpcUri(int listenPort = 6800) private static string GetRpcUri()
{ {
return $"http://localhost:{listenPort}/jsonrpc"; return $"{host}:{AriaClient.listenPort}/jsonrpc";
} }
/// <summary> /// <summary>

@ -19,12 +19,20 @@ namespace DownKyi.Core.BiliApi.BiliUtils
new Quality { Name = "360P 流畅", Id = 16 }, new Quality { Name = "360P 流畅", Id = 16 },
}; };
private static readonly List<Quality> codecIds = new List<Quality>
{
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<Quality> qualities = new List<Quality> private static readonly List<Quality> qualities = new List<Quality>
{ {
new Quality { Name = "64K", Id = 30216 }, new Quality { Name = "64K", Id = 30216 },
new Quality { Name = "132K", Id = 30232 }, new Quality { Name = "132K", Id = 30232 },
new Quality { Name = "192K", Id = 30280 }, new Quality { Name = "192K", Id = 30280 },
new Quality { Name = "Dolby Atmos", Id = 30250 }, new Quality { Name = "Dolby Atmos", Id = 30250 },
new Quality { Name = "Hi-Res无损", Id = 30251 },
}; };
/// <summary> /// <summary>
@ -39,6 +47,18 @@ namespace DownKyi.Core.BiliApi.BiliUtils
return new List<Quality>(resolutions); return new List<Quality>(resolutions);
} }
/// <summary>
/// 获取视频编码代码
/// </summary>
/// <returns></returns>
public static List<Quality> GetCodecIds()
{
// 使用深复制,
// 保证外部修改list后
// 不会影响其他调用处
return new List<Quality>(codecIds);
}
/// <summary> /// <summary>
/// 获取支持的视频音质 /// 获取支持的视频音质
/// </summary> /// </summary>

@ -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<long> Aids { get; set; }
[JsonProperty("archives")]
public List<SpaceSeasonsSeriesArchives> Archives { get; set; }
[JsonProperty("meta")]
public SpaceSeasonsMeta Meta { get; set; }
[JsonProperty("page")]
public SpaceSeasonsSeriesPage Page { get; set; }
}
}

@ -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<SpaceSeasonsSeriesArchives> Archives { get; set; }
[JsonProperty("meta")]
public SpaceSeasonsMeta Meta { get; set; }
[JsonProperty("recent_aids")]
public List<long> RecentAids { get; set; }
}
public class SpaceSeries : BaseModel
{
[JsonProperty("archives")]
public List<SpaceSeasonsSeriesArchives> Archives { get; set; }
[JsonProperty("meta")]
public SpaceSeriesMeta Meta { get; set; }
[JsonProperty("recent_aids")]
public List<long> RecentAids { get; set; }
}
}

@ -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
}
}

@ -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<string> 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; }
}
}

@ -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<SpaceSeasons> SeasonsList { get; set; }
[JsonProperty("series_list")]
public List<SpaceSeries> SeriesList { get; set; }
}
}

@ -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;
}
}

@ -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; }
}
}

@ -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<long> Aids { get; set; }
// page
[JsonProperty("archives")]
public List<SpaceSeasonsSeriesArchives> Archives { get; set; }
}
}

@ -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<long> RecentAids { get; set; }
}
}

@ -232,20 +232,125 @@ namespace DownKyi.Core.BiliApi.Users
#region 合集和列表 #region 合集和列表
// TODO /// <summary>
// https://api.bilibili.com/x/polymer/space/seasons_series_list?mid=27899754&page_num=1&page_size=18 /// 查询用户的合集和列表
// page_size最大值为20 /// </summary>
/// <param name="mid"></param>
/// <param name="pageNum">第几页</param>
/// <param name="pageSize">每页的数量最大值为20</param>
/// <returns></returns>
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);
try
{
SpaceSeasonsSeriesOrigin origin = JsonConvert.DeserializeObject<SpaceSeasonsSeriesOrigin>(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;
}
}
/// <summary>
/// 查询用户的合集的视频详情
/// </summary>
/// <param name="mid"></param>
/// <param name="seasonId"></param>
/// <param name="pageNum"></param>
/// <param name="pageSize"></param>
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 // 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 try
// https://api.bilibili.com/x/series/archives?mid=27899754&series_id=1253087&only_normal=true&sort=asc&pn=1&ps=30 {
SpaceSeasonsDetailOrigin origin = JsonConvert.DeserializeObject<SpaceSeasonsDetailOrigin>(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;
}
}
/// <summary>
/// 查询用户的列表元数据
/// </summary>
/// <param name="seriesId"></param>
/// <returns></returns>
public static SpaceSeriesMetaData GetSeriesMeta(long seriesId)
{
// https://api.bilibili.com/x/series/series?series_id=1253087 // 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<SpaceSeriesMetaOrigin>(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;
}
}
/// <summary>
/// 查询用户的列表的视频详情
/// </summary>
/// <param name="mid"></param>
/// <param name="seriesId"></param>
/// <param name="pn"></param>
/// <param name="ps"></param>
/// <returns></returns>
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<SpaceSeriesDetailOrigin>(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 #endregion
#region 课程 #region 课程
/// <summary> /// <summary>
/// 查询用户发布的课程列表 /// 查询用户发布的课程列表
/// </summary> /// </summary>

@ -18,5 +18,7 @@ namespace DownKyi.Core.BiliApi.VideoStream.Models
public List<PlayUrlDashVideo> Audio { get; set; } public List<PlayUrlDashVideo> Audio { get; set; }
[JsonProperty("dolby")] [JsonProperty("dolby")]
public PlayUrlDashDolby Dolby { get; set; } public PlayUrlDashDolby Dolby { get; set; }
[JsonProperty("flac")]
public PlayUrlDashFlac Flac { get; set; }
} }
} }

@ -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; }
}
}

@ -30,6 +30,7 @@ namespace DownKyi.Core.BiliApi.VideoStream.Models
// start_with_sap // start_with_sap
// SegmentBase // SegmentBase
// segment_base // segment_base
// codecid [JsonProperty("codecid")]
public int CodecId { get; set; }
} }
} }

@ -217,6 +217,15 @@
<Compile Include="BiliApi\Users\Models\SpacePublicationListTypeVideoZone.cs" /> <Compile Include="BiliApi\Users\Models\SpacePublicationListTypeVideoZone.cs" />
<Compile Include="BiliApi\Users\Models\SpacePublicationListVideo.cs" /> <Compile Include="BiliApi\Users\Models\SpacePublicationListVideo.cs" />
<Compile Include="BiliApi\Users\Models\SpacePublicationPage.cs" /> <Compile Include="BiliApi\Users\Models\SpacePublicationPage.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSeasonsDetail.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSeasonsSeries.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSeasonsSeriesArchives.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSeasonsSeriesMeta.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSeasonsSeriesOrigin.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSeasonsSeriesPage.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSeasonsSeriesStat.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSeriesDetail.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSeriesMeta.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSettings.cs" /> <Compile Include="BiliApi\Users\Models\SpaceSettings.cs" />
<Compile Include="BiliApi\Users\Models\SpaceSettingsToutu.cs" /> <Compile Include="BiliApi\Users\Models\SpaceSettingsToutu.cs" />
<Compile Include="BiliApi\Users\Models\UpStat.cs" /> <Compile Include="BiliApi\Users\Models\UpStat.cs" />
@ -240,6 +249,7 @@
<Compile Include="BiliApi\VideoStream\Models\PlayUrl.cs" /> <Compile Include="BiliApi\VideoStream\Models\PlayUrl.cs" />
<Compile Include="BiliApi\VideoStream\Models\PlayUrlDash.cs" /> <Compile Include="BiliApi\VideoStream\Models\PlayUrlDash.cs" />
<Compile Include="BiliApi\VideoStream\Models\PlayUrlDashDolby.cs" /> <Compile Include="BiliApi\VideoStream\Models\PlayUrlDashDolby.cs" />
<Compile Include="BiliApi\VideoStream\Models\PlayUrlDashFlac.cs" />
<Compile Include="BiliApi\VideoStream\Models\PlayUrlDashVideo.cs" /> <Compile Include="BiliApi\VideoStream\Models\PlayUrlDashVideo.cs" />
<Compile Include="BiliApi\VideoStream\Models\PlayUrlDurl.cs" /> <Compile Include="BiliApi\VideoStream\Models\PlayUrlDurl.cs" />
<Compile Include="BiliApi\VideoStream\Models\PlayUrlSupportFormat.cs" /> <Compile Include="BiliApi\VideoStream\Models\PlayUrlSupportFormat.cs" />

@ -34,15 +34,19 @@ namespace DownKyi.Core.FFmpeg
/// <param name="destVideo"></param> /// <param name="destVideo"></param>
public static bool MergeVideo(string video1, string video2, string destVideo) 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)) 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)) 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; } if (!File.Exists(video1) && !File.Exists(video2)) { return false; }
// 如果存在 // 如果存在

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示: //通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.1.3.0")] [assembly: AssemblyVersion("2.1.4.0")]
[assembly: AssemblyFileVersion("2.1.3.0")] [assembly: AssemblyFileVersion("2.1.4.0")]

@ -5,5 +5,6 @@
NOT_SET = 0, NOT_SET = 0,
BUILT_IN, BUILT_IN,
ARIA, ARIA,
CUSTOM_ARIA,
} }
} }

@ -22,6 +22,8 @@ namespace DownKyi.Core.Settings.Models
#endregion #endregion
#region Aria #region Aria
public string AriaToken { get; set; } = null;
public string AriaHost { get; set; } = null;
public int AriaListenPort { get; set; } = -1; public int AriaListenPort { get; set; } = -1;
public AriaConfigLogLevel AriaLogLevel { get; set; } = AriaConfigLogLevel.NOT_SET; public AriaConfigLogLevel AriaLogLevel { get; set; } = AriaConfigLogLevel.NOT_SET;
public int AriaSplit { get; set; } = -1; public int AriaSplit { get; set; } = -1;

@ -8,7 +8,7 @@ namespace DownKyi.Core.Settings.Models
/// </summary> /// </summary>
public class VideoSettings 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 Quality { get; set; } = -1; // 画质
public int AudioQuality { get; set; } = -1; // 音质 public int AudioQuality { get; set; } = -1; // 音质
public AllowStatus IsTranscodingFlvToMp4 { get; set; } = AllowStatus.NONE; // 是否将flv转为mp4 public AllowStatus IsTranscodingFlvToMp4 { get; set; } = AllowStatus.NONE; // 是否将flv转为mp4

@ -24,6 +24,12 @@ namespace DownKyi.Core.Settings
private readonly string httpProxy = ""; private readonly string httpProxy = "";
private readonly int httpProxyListenPort = 0; private readonly int httpProxyListenPort = 0;
// Aria服务器token
private readonly string ariaToken = "downkyi";
// Aria服务器host
private readonly string ariaHost = "http://localhost";
// Aria服务器端口号 // Aria服务器端口号
private readonly int ariaListenPort = 6800; private readonly int ariaListenPort = 6800;
@ -263,6 +269,60 @@ namespace DownKyi.Core.Settings
return SetSettings(); return SetSettings();
} }
/// <summary>
/// 获取Aria服务器的token
/// </summary>
/// <returns></returns>
public string GetAriaToken()
{
appSettings = GetSettings();
if (appSettings.Network.AriaToken == null)
{
// 第一次获取,先设置默认值
SetHttpProxy(ariaToken);
return ariaToken;
}
return appSettings.Network.AriaToken;
}
/// <summary>
/// 设置Aria服务器的token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public bool SetAriaToken(string token)
{
appSettings.Network.AriaToken = token;
return SetSettings();
}
/// <summary>
/// 获取Aria服务器的host
/// </summary>
/// <returns></returns>
public string GetAriaHost()
{
appSettings = GetSettings();
if (appSettings.Network.AriaHost == null)
{
// 第一次获取,先设置默认值
SetHttpProxy(ariaHost);
return ariaHost;
}
return appSettings.Network.AriaHost;
}
/// <summary>
/// 设置Aria服务器的host
/// </summary>
/// <param name="host"></param>
/// <returns></returns>
public bool SetAriaHost(string host)
{
appSettings.Network.AriaHost = host;
return SetSettings();
}
/// <summary> /// <summary>
/// 获取Aria服务器的端口号 /// 获取Aria服务器的端口号
/// </summary> /// </summary>

@ -9,7 +9,7 @@ namespace DownKyi.Core.Settings
public partial class SettingsManager public partial class SettingsManager
{ {
// 设置优先下载的视频编码 // 设置优先下载的视频编码
private readonly VideoCodecs videoCodecs = VideoCodecs.AVC; private readonly int videoCodecs = 7;
// 设置优先下载画质 // 设置优先下载画质
private readonly int quality = 120; private readonly int quality = 120;
@ -58,10 +58,10 @@ namespace DownKyi.Core.Settings
/// 获取优先下载的视频编码 /// 获取优先下载的视频编码
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public VideoCodecs GetVideoCodecs() public int GetVideoCodecs()
{ {
appSettings = GetSettings(); appSettings = GetSettings();
if (appSettings.Video.VideoCodecs == VideoCodecs.NONE) if (appSettings.Video.VideoCodecs == -1)
{ {
// 第一次获取,先设置默认值 // 第一次获取,先设置默认值
SetVideoCodecs(videoCodecs); SetVideoCodecs(videoCodecs);
@ -75,7 +75,7 @@ namespace DownKyi.Core.Settings
/// </summary> /// </summary>
/// <param name="videoCodecs"></param> /// <param name="videoCodecs"></param>
/// <returns></returns> /// <returns></returns>
public bool SetVideoCodecs(VideoCodecs videoCodecs) public bool SetVideoCodecs(int videoCodecs)
{ {
appSettings.Video.VideoCodecs = videoCodecs; appSettings.Video.VideoCodecs = videoCodecs;
return SetSettings(); return SetSettings();

@ -134,6 +134,9 @@ namespace DownKyi
case Downloader.ARIA: case Downloader.ARIA:
downloadService = new AriaDownloadService(DownloadingList, DownloadedList); downloadService = new AriaDownloadService(DownloadingList, DownloadedList);
break; break;
case Downloader.CUSTOM_ARIA:
downloadService = new CustomAriaDownloadService(DownloadingList, DownloadedList);
break;
} }
if (downloadService != null) if (downloadService != null)
{ {
@ -182,6 +185,7 @@ namespace DownKyi
containerRegistry.RegisterForNavigation<ViewUserSpace>(ViewUserSpaceViewModel.Tag); containerRegistry.RegisterForNavigation<ViewUserSpace>(ViewUserSpaceViewModel.Tag);
containerRegistry.RegisterForNavigation<ViewPublication>(ViewPublicationViewModel.Tag); containerRegistry.RegisterForNavigation<ViewPublication>(ViewPublicationViewModel.Tag);
containerRegistry.RegisterForNavigation<Views.ViewChannel>(ViewModels.ViewChannelViewModel.Tag); containerRegistry.RegisterForNavigation<Views.ViewChannel>(ViewModels.ViewChannelViewModel.Tag);
containerRegistry.RegisterForNavigation<Views.ViewSeasonsSeries>(ViewModels.ViewSeasonsSeriesViewModel.Tag);
containerRegistry.RegisterForNavigation<ViewFriend>(ViewFriendViewModel.Tag); containerRegistry.RegisterForNavigation<ViewFriend>(ViewFriendViewModel.Tag);
containerRegistry.RegisterForNavigation<ViewMySpace>(ViewMySpaceViewModel.Tag); containerRegistry.RegisterForNavigation<ViewMySpace>(ViewMySpaceViewModel.Tag);
@ -213,6 +217,7 @@ namespace DownKyi
// UserSpace // UserSpace
containerRegistry.RegisterForNavigation<ViewArchive>(ViewArchiveViewModel.Tag); containerRegistry.RegisterForNavigation<ViewArchive>(ViewArchiveViewModel.Tag);
containerRegistry.RegisterForNavigation<Views.UserSpace.ViewChannel>(ViewModels.UserSpace.ViewChannelViewModel.Tag); containerRegistry.RegisterForNavigation<Views.UserSpace.ViewChannel>(ViewModels.UserSpace.ViewChannelViewModel.Tag);
containerRegistry.RegisterForNavigation<Views.UserSpace.ViewSeasonsSeries>(ViewModels.UserSpace.ViewSeasonsSeriesViewModel.Tag);
// dialogs // dialogs
containerRegistry.RegisterDialog<ViewAlertDialog>(ViewAlertDialogViewModel.Tag); containerRegistry.RegisterDialog<ViewAlertDialog>(ViewAlertDialogViewModel.Tag);

@ -117,6 +117,7 @@
<Compile Include="Models\OrderFormatDisplay.cs" /> <Compile Include="Models\OrderFormatDisplay.cs" />
<Compile Include="Services\AlertService.cs" /> <Compile Include="Services\AlertService.cs" />
<Compile Include="Services\Download\AddToDownloadService.cs" /> <Compile Include="Services\Download\AddToDownloadService.cs" />
<Compile Include="Services\Download\CustomAriaDownloadService.cs" />
<Compile Include="Services\Download\BuiltinDownloadService.cs" /> <Compile Include="Services\Download\BuiltinDownloadService.cs" />
<Compile Include="Services\Download\DownloadStorageService.cs" /> <Compile Include="Services\Download\DownloadStorageService.cs" />
<Compile Include="Services\SearchService.cs" /> <Compile Include="Services\SearchService.cs" />
@ -166,12 +167,15 @@
<Compile Include="ViewModels\DownloadManager\ViewDownloadingViewModel.cs" /> <Compile Include="ViewModels\DownloadManager\ViewDownloadingViewModel.cs" />
<Compile Include="ViewModels\Toolbox\ViewBiliHelperViewModel.cs" /> <Compile Include="ViewModels\Toolbox\ViewBiliHelperViewModel.cs" />
<Compile Include="ViewModels\Toolbox\ViewDelogoViewModel.cs" /> <Compile Include="ViewModels\Toolbox\ViewDelogoViewModel.cs" />
<Compile Include="ViewModels\UserSpace\SeasonsSeries.cs" />
<Compile Include="ViewModels\UserSpace\Channel.cs" /> <Compile Include="ViewModels\UserSpace\Channel.cs" />
<Compile Include="ViewModels\UserSpace\PublicationZone.cs" /> <Compile Include="ViewModels\UserSpace\PublicationZone.cs" />
<Compile Include="ViewModels\UserSpace\ViewSeasonsSeriesViewModel.cs" />
<Compile Include="ViewModels\UserSpace\ViewChannelViewModel.cs" /> <Compile Include="ViewModels\UserSpace\ViewChannelViewModel.cs" />
<Compile Include="ViewModels\UserSpace\ViewArchiveViewModel.cs" /> <Compile Include="ViewModels\UserSpace\ViewArchiveViewModel.cs" />
<Compile Include="ViewModels\UserSpace\TabLeftBanner.cs" /> <Compile Include="ViewModels\UserSpace\TabLeftBanner.cs" />
<Compile Include="ViewModels\UserSpace\TabRightBanner.cs" /> <Compile Include="ViewModels\UserSpace\TabRightBanner.cs" />
<Compile Include="ViewModels\ViewSeasonsSeriesViewModel.cs" />
<Compile Include="ViewModels\ViewChannelViewModel.cs" /> <Compile Include="ViewModels\ViewChannelViewModel.cs" />
<Compile Include="ViewModels\ViewDownloadManagerViewModel.cs" /> <Compile Include="ViewModels\ViewDownloadManagerViewModel.cs" />
<Compile Include="ViewModels\Toolbox\ViewExtractMediaViewModel.cs" /> <Compile Include="ViewModels\Toolbox\ViewExtractMediaViewModel.cs" />
@ -243,9 +247,15 @@
<Compile Include="Views\UserSpace\ViewArchive.xaml.cs"> <Compile Include="Views\UserSpace\ViewArchive.xaml.cs">
<DependentUpon>ViewArchive.xaml</DependentUpon> <DependentUpon>ViewArchive.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\UserSpace\ViewSeasonsSeries.xaml.cs">
<DependentUpon>ViewSeasonsSeries.xaml</DependentUpon>
</Compile>
<Compile Include="Views\UserSpace\ViewChannel.xaml.cs"> <Compile Include="Views\UserSpace\ViewChannel.xaml.cs">
<DependentUpon>ViewChannel.xaml</DependentUpon> <DependentUpon>ViewChannel.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\ViewSeasonsSeries.xaml.cs">
<DependentUpon>ViewSeasonsSeries.xaml</DependentUpon>
</Compile>
<Compile Include="Views\ViewChannel.xaml.cs"> <Compile Include="Views\ViewChannel.xaml.cs">
<DependentUpon>ViewChannel.xaml</DependentUpon> <DependentUpon>ViewChannel.xaml</DependentUpon>
</Compile> </Compile>
@ -446,10 +456,18 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Views\UserSpace\ViewSeasonsSeries.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\UserSpace\ViewChannel.xaml"> <Page Include="Views\UserSpace\ViewChannel.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Views\ViewSeasonsSeries.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\ViewChannel.xaml"> <Page Include="Views\ViewChannel.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

@ -1,4 +1,6 @@
namespace DownKyi.Images using DownKyi.ViewModels.UserSpace;
namespace DownKyi.Images
{ {
public class NormalIcon public class NormalIcon
{ {
@ -267,6 +269,46 @@
Fill = "#FF000000" 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 PlatformIpad = new VectorImage
{ {
Height = 16, Height = 16,
@ -357,6 +399,8 @@
public VectorImage VideoUp { get; private set; } public VectorImage VideoUp { get; private set; }
public VectorImage Channel { 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 PlatformIpad { get; private set; }
public VectorImage PlatformMobile { get; private set; } public VectorImage PlatformMobile { get; private set; }

@ -63,6 +63,7 @@
<system:String x:Key="AllPublicationZones">全部</system:String> <system:String x:Key="AllPublicationZones">全部</system:String>
<system:String x:Key="Publication">投稿视频</system:String> <system:String x:Key="Publication">投稿视频</system:String>
<system:String x:Key="Channel">频道</system:String> <system:String x:Key="Channel">频道</system:String>
<system:String x:Key="SeasonsSeries">合集和列表</system:String>
<system:String x:Key="UserSpaceWait">请稍等,马上就好~</system:String> <system:String x:Key="UserSpaceWait">请稍等,马上就好~</system:String>
<!-- PublicFavorites --> <!-- PublicFavorites -->
@ -192,7 +193,10 @@
<system:String x:Key="SelectDownloader">选择下载器(重启生效):</system:String> <system:String x:Key="SelectDownloader">选择下载器(重启生效):</system:String>
<system:String x:Key="BuiltinDownloader">内建下载器(测试)</system:String> <system:String x:Key="BuiltinDownloader">内建下载器(测试)</system:String>
<system:String x:Key="Aria2cDownloader">Aria2下载器</system:String> <system:String x:Key="Aria2cDownloader">Aria2下载器</system:String>
<system:String x:Key="CustomAria2cDownloader">自定义Aria2下载器</system:String>
<system:String x:Key="AriaServerHost">Aria服务器地址</system:String>
<system:String x:Key="AriaServerPort">Aria服务器端口</system:String> <system:String x:Key="AriaServerPort">Aria服务器端口</system:String>
<system:String x:Key="AriaServerToken">Aria服务器Token</system:String>
<system:String x:Key="AriaLogLevel">Aria日志等级</system:String> <system:String x:Key="AriaLogLevel">Aria日志等级</system:String>
<system:String x:Key="AriaMaxConcurrentDownloads">Aria同时下载数</system:String> <system:String x:Key="AriaMaxConcurrentDownloads">Aria同时下载数</system:String>
<system:String x:Key="AriaSplit">Aria最大线程数</system:String> <system:String x:Key="AriaSplit">Aria最大线程数</system:String>

@ -3,12 +3,12 @@
public class AppInfo public class AppInfo
{ {
public string Name { get; } = "哔哩下载姬"; public string Name { get; } = "哔哩下载姬";
public int VersionCode { get; } = 510; public int VersionCode { get; } = 511;
#if DEBUG #if DEBUG
public string VersionName { get; } = "1.5.3 Debug"; public string VersionName { get; } = "1.5.4 Debug";
#else #else
public string VersionName { get; } = "1.5.3"; public string VersionName { get; } = "1.5.4";
#endif #endif
} }

@ -51,5 +51,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.5.3.0")] [assembly: AssemblyVersion("1.5.4.0")]
[assembly: AssemblyFileVersion("1.5.3.0")] [assembly: AssemblyFileVersion("1.5.4.0")]

@ -232,6 +232,13 @@ namespace DownKyi.Services.Download
/// </summary> /// </summary>
public void Start() public void Start()
{ {
// 设置aria token
AriaClient.SetToken();
// 设置aria host
AriaClient.SetHost();
// 设置aria listenPort
AriaClient.SetListenPort();
// 启动Aria服务器 // 启动Aria服务器
StartAriaServer(); StartAriaServer();

@ -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
{
/// <summary>
/// 音视频采用Aria下载其余采用WebClient下载
/// </summary>
public class CustomAriaDownloadService : DownloadService, IDownloadService
{
public CustomAriaDownloadService(ObservableCollection<DownloadingItem> downloadingList, ObservableCollection<DownloadedItem> downloadedList) : base(downloadingList, downloadedList)
{
Tag = "AriaDownloadService";
}
#region 音视频
/// <summary>
/// 下载音频,返回下载文件路径
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
public override string DownloadAudio(DownloadingItem downloading)
{
PlayUrlDashVideo downloadAudio = BaseDownloadAudio(downloading);
return DownloadVideo(downloading, downloadAudio);
}
/// <summary>
/// 下载视频,返回下载文件路径
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
public override string DownloadVideo(DownloadingItem downloading)
{
PlayUrlDashVideo downloadVideo = BaseDownloadVideo(downloading);
return DownloadVideo(downloading, downloadVideo);
}
/// <summary>
/// 将下载音频和视频的函数中相同代码抽象出来
/// </summary>
/// <param name="downloading"></param>
/// <param name="downloadVideo"></param>
/// <returns></returns>
private string DownloadVideo(DownloadingItem downloading, PlayUrlDashVideo downloadVideo)
{
// 如果为空,说明没有匹配到可下载的音频视频
if (downloadVideo == null) { return null; }
// 下载链接
List<string> urls = new List<string>();
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<string>();
}
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
/// <summary>
/// 下载封面
/// </summary>
/// <param name="downloading"></param>
public override string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName)
{
return BaseDownloadCover(downloading, coverUrl, fileName);
}
/// <summary>
/// 下载弹幕
/// </summary>
/// <param name="downloading"></param>
public override string DownloadDanmaku(DownloadingItem downloading)
{
return BaseDownloadDanmaku(downloading);
}
/// <summary>
/// 下载字幕
/// </summary>
/// <param name="downloading"></param>
public override List<string> DownloadSubtitle(DownloadingItem downloading)
{
return BaseDownloadSubtitle(downloading);
}
/// <summary>
/// 混流音频和视频
/// </summary>
/// <param name="downloading"></param>
/// <param name="audioUid"></param>
/// <param name="videoUid"></param>
/// <returns></returns>
public override string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid)
{
if (videoUid == nullMark)
{
return null;
}
return BaseMixedFlow(downloading, audioUid, videoUid);
}
/// <summary>
/// 解析视频流的下载链接
/// </summary>
/// <param name="downloading"></param>
public override void Parse(DownloadingItem downloading)
{
BaseParse(downloading);
}
/// <summary>
/// 停止下载服务(转换await和Task.Wait两种调用形式)
/// </summary>
private async Task EndTask()
{
// 停止基本任务
await BaseEndTask();
// 关闭Aria服务器
await CloseAriaServer();
}
/// <summary>
/// 停止下载服务
/// </summary>
public void End()
{
Task.Run(EndTask).Wait();
}
/// <summary>
/// 启动下载服务
/// </summary>
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();
}
/// <summary>
/// 强制暂停
/// </summary>
/// <param name="downloading"></param>
/// <exception cref="OperationCanceledException"></exception>
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");
}
}
/// <summary>
/// 是否存在于下载列表中
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
private async Task<bool> 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;
}
}
/// <summary>
/// 关闭Aria服务器
/// </summary>
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
}
/// <summary>
/// 采用Aria下载文件
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
private DownloadResult DownloadByAria(DownloadingItem downloading, List<string> urls, string path, string localFileName)
{
// path已斜杠结尾去掉斜杠
path = path.TrimEnd('/').TrimEnd('\\');
//检查gid对应任务如果已创建那么直接使用
//但是代理设置会出现不能随时更新的问题
if (downloading.Downloading.Gid != null)
{
Task<AriaTellStatus> 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> 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<AriaPause> 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> 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();
}
}
}

@ -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.BiliApi.VideoStream.Models;
using DownKyi.Core.Danmaku2Ass; using DownKyi.Core.Danmaku2Ass;
using DownKyi.Core.FFmpeg; using DownKyi.Core.FFmpeg;
@ -14,6 +15,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -82,6 +84,11 @@ namespace DownKyi.Services.Download
{ {
downloadAudio = downloading.PlayUrl.Dash.Dolby.Audio[0]; downloadAudio = downloading.PlayUrl.Dash.Dolby.Audio[0];
} }
// Hi-Res无损
if (downloading.AudioCodec.Id == 30251)
{
downloadAudio = downloading.PlayUrl.Dash.Flac.Audio;
}
} }
catch (Exception) { } catch (Exception) { }
@ -110,7 +117,8 @@ namespace DownKyi.Services.Download
PlayUrlDashVideo downloadVideo = null; PlayUrlDashVideo downloadVideo = null;
foreach (PlayUrlDashVideo video in downloading.PlayUrl.Dash.Video) 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; downloadVideo = video;
break; break;

@ -30,7 +30,7 @@ namespace DownKyi.Services
// 获取设置 // 获取设置
UserInfoSettings userInfo = SettingsManager.GetInstance().GetUserInfo(); UserInfoSettings userInfo = SettingsManager.GetInstance().GetUserInfo();
int defaultQuality = SettingsManager.GetInstance().GetQuality(); int defaultQuality = SettingsManager.GetInstance().GetQuality();
VideoCodecs videoCodecs = SettingsManager.GetInstance().GetVideoCodecs(); int videoCodecs = SettingsManager.GetInstance().GetVideoCodecs();
int defaultAudioQuality = SettingsManager.GetInstance().GetAudioQuality(); int defaultAudioQuality = SettingsManager.GetInstance().GetAudioQuality();
// 未登录时最高仅720P // 未登录时最高仅720P
@ -95,7 +95,7 @@ namespace DownKyi.Services
/// <param name="playUrl"></param> /// <param name="playUrl"></param>
/// <param name="defaultAudioQuality"></param> /// <param name="defaultAudioQuality"></param>
/// <returns></returns> /// <returns></returns>
private static ObservableCollection<string> GetAudioQualityFormatList(PlayUrl playUrl, int defaultAudioQuality) private static ObservableCollection<string> GetAudioQualityFormatList_old(PlayUrl playUrl, int defaultAudioQuality)
{ {
List<string> audioQualityFormatList = new List<string>(); List<string> audioQualityFormatList = new List<string>();
@ -122,6 +122,55 @@ namespace DownKyi.Services
return new ObservableCollection<string>(audioQualityFormatList); return new ObservableCollection<string>(audioQualityFormatList);
} }
/// <summary>
/// 设置音质
/// </summary>
/// <param name="playUrl"></param>
/// <param name="defaultAudioQuality"></param>
/// <returns></returns>
private static ObservableCollection<string> GetAudioQualityFormatList(PlayUrl playUrl, int defaultAudioQuality)
{
List<string> audioQualityFormatList = new List<string>();
List<Quality> 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<string>());
audioQualityFormatList.Reverse();
return new ObservableCollection<string>(audioQualityFormatList);
}
/// <summary> /// <summary>
/// 设置画质 & 视频编码 /// 设置画质 & 视频编码
/// </summary> /// </summary>
@ -130,9 +179,10 @@ namespace DownKyi.Services
/// <param name="userInfo"></param> /// <param name="userInfo"></param>
/// <param name="videoCodecs"></param> /// <param name="videoCodecs"></param>
/// <returns></returns> /// <returns></returns>
private static List<VideoQuality> GetVideoQualityList(PlayUrl playUrl, UserInfoSettings userInfo, int defaultQuality, VideoCodecs videoCodecs) private static List<VideoQuality> GetVideoQualityList(PlayUrl playUrl, UserInfoSettings userInfo, int defaultQuality, int videoCodecs)
{ {
List<VideoQuality> videoQualityList = new List<VideoQuality>(); List<VideoQuality> videoQualityList = new List<VideoQuality>();
List<Quality> codeIds = Constant.GetCodecIds();
if (playUrl.Dash.Video == null) 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); VideoQuality videoQualityExist = videoQualityList.FirstOrDefault(t => t.Quality == video.Id);
if (videoQualityExist == null) if (videoQualityExist == null)
{ {
@ -191,7 +242,7 @@ namespace DownKyi.Services
// 设置选中的视频编码 // 设置选中的视频编码
VideoQuality selectedVideoQuality = videoQualityList.FirstOrDefault(t => t.Quality == video.Id); 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) if (videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].VideoCodecList.Count == 1)
{ {
@ -200,24 +251,29 @@ namespace DownKyi.Services
} }
// 设置选中的视频编码 // 设置选中的视频编码
switch (videoCodecs) //switch (videoCodecs)
{ //{
case VideoCodecs.AVC: // case VideoCodecs.AVC:
if (videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].VideoCodecList.Contains("H.264/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))
{ {
videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].SelectedVideoCodec = "H.264/AVC"; videoQualityList[videoQualityList.IndexOf(selectedVideoQuality)].SelectedVideoCodec = videoCodecsName;
}
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;
} }
} }
@ -230,10 +286,10 @@ namespace DownKyi.Services
/// </summary> /// </summary>
/// <param name="origin"></param> /// <param name="origin"></param>
/// <returns></returns> /// <returns></returns>
internal static string GetVideoCodecName(string origin) //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" : ""; // return origin.Contains("avc") ? "H.264/AVC" : origin.Contains("hev") ? "H.265/HEVC" : origin.Contains("dvh") || origin.Contains("hvc") ? "Dolby Vision" : "";
} //}
} }
} }

@ -37,5 +37,21 @@ namespace DownKyi.Utils
return showDialog == true ? dialog.FileName : ""; return showDialog == true ? dialog.FileName : "";
} }
/// <summary>
/// 选择多个视频dialog
/// </summary>
/// <returns></returns>
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];
}
} }
} }

@ -91,32 +91,32 @@ namespace DownKyi.ViewModels.PageViewModels
/// </summary> /// </summary>
private void ExecuteVideoQualitySelectedCommand() private void ExecuteVideoQualitySelectedCommand()
{ {
// 杜比视界 //// 杜比视界
string dolby = string.Empty; //string dolby = string.Empty;
try //try
{ //{
var qualities = Constant.GetAudioQualities(); // var qualities = Constant.GetAudioQualities();
dolby = qualities[3].Name; // dolby = qualities[3].Name;
} //}
catch (Exception e) //catch (Exception e)
{ //{
Core.Utils.Debugging.Console.PrintLine("ExecuteVideoQualitySelectedCommand()发生异常: {0}", e); // Core.Utils.Debugging.Console.PrintLine("ExecuteVideoQualitySelectedCommand()发生异常: {0}", e);
LogManager.Error("ExecuteVideoQualitySelectedCommand", e); // LogManager.Error("ExecuteVideoQualitySelectedCommand", e);
} //}
if (VideoQuality != null && VideoQuality.Quality == 126 && PlayUrl != null && PlayUrl.Dash != null && PlayUrl.Dash.Dolby != null) //if (VideoQuality != null && VideoQuality.Quality == 126 && PlayUrl != null && PlayUrl.Dash != null && PlayUrl.Dash.Dolby != null)
{ //{
ListHelper.AddUnique(AudioQualityFormatList, dolby); // ListHelper.AddUnique(AudioQualityFormatList, dolby);
AudioQualityFormat = dolby; // AudioQualityFormat = dolby;
} //}
else //else
{ //{
if (AudioQualityFormatList.Contains(dolby)) // if (AudioQualityFormatList.Contains(dolby))
{ // {
AudioQualityFormatList.Remove(dolby); // AudioQualityFormatList.Remove(dolby);
AudioQualityFormat = AudioQualityFormatList[0]; // AudioQualityFormat = AudioQualityFormatList[0];
} // }
} //}
} }
#endregion #endregion

@ -41,6 +41,13 @@ namespace DownKyi.ViewModels.Settings
set => SetProperty(ref aria2c, value); set => SetProperty(ref aria2c, value);
} }
private bool customAria2c;
public bool CustomAria2c
{
get => customAria2c;
set => SetProperty(ref customAria2c, value);
}
private List<int> maxCurrentDownloads; private List<int> maxCurrentDownloads;
public List<int> MaxCurrentDownloads public List<int> MaxCurrentDownloads
{ {
@ -90,6 +97,13 @@ namespace DownKyi.ViewModels.Settings
set => SetProperty(ref httpProxyPort, value); set => SetProperty(ref httpProxyPort, value);
} }
private string ariaHost;
public string AriaHost
{
get => ariaHost;
set => SetProperty(ref ariaHost, value);
}
private int ariaListenPort; private int ariaListenPort;
public int AriaListenPort public int AriaListenPort
{ {
@ -97,6 +111,13 @@ namespace DownKyi.ViewModels.Settings
set => SetProperty(ref ariaListenPort, value); set => SetProperty(ref ariaListenPort, value);
} }
private string ariaToken;
public string AriaToken
{
get => ariaToken;
set => SetProperty(ref ariaToken, value);
}
private List<string> ariaLogLevels; private List<string> ariaLogLevels;
public List<string> AriaLogLevels public List<string> AriaLogLevels
{ {
@ -259,6 +280,9 @@ namespace DownKyi.ViewModels.Settings
case Downloader.ARIA: case Downloader.ARIA:
Aria2c = true; Aria2c = true;
break; break;
case Downloader.CUSTOM_ARIA:
CustomAria2c = true;
break;
} }
// builtin同时下载数 // builtin同时下载数
@ -277,9 +301,15 @@ namespace DownKyi.ViewModels.Settings
// builtin的http代理的端口 // builtin的http代理的端口
HttpProxyPort = SettingsManager.GetInstance().GetHttpProxyListenPort(); HttpProxyPort = SettingsManager.GetInstance().GetHttpProxyListenPort();
// Aria服务器host
AriaHost = SettingsManager.GetInstance().GetAriaHost();
// Aria服务器端口 // Aria服务器端口
AriaListenPort = SettingsManager.GetInstance().GetAriaListenPort(); AriaListenPort = SettingsManager.GetInstance().GetAriaListenPort();
// Aria服务器Token
AriaToken = SettingsManager.GetInstance().GetAriaToken();
// Aria的日志等级 // Aria的日志等级
AriaConfigLogLevel ariaLogLevel = SettingsManager.GetInstance().GetAriaLogLevel(); AriaConfigLogLevel ariaLogLevel = SettingsManager.GetInstance().GetAriaLogLevel();
SelectedAriaLogLevel = ariaLogLevel.ToString("G"); SelectedAriaLogLevel = ariaLogLevel.ToString("G");
@ -349,6 +379,9 @@ namespace DownKyi.ViewModels.Settings
case "Aria2c": case "Aria2c":
downloader = Downloader.ARIA; downloader = Downloader.ARIA;
break; break;
case "CustomAria2c":
downloader = Downloader.CUSTOM_ARIA;
break;
default: default:
downloader = SettingsManager.GetInstance().GetDownloader(); downloader = SettingsManager.GetInstance().GetDownloader();
break; break;
@ -444,6 +477,21 @@ namespace DownKyi.ViewModels.Settings
PublishTip(isSucceed); PublishTip(isSucceed);
} }
// Aria服务器host事件
private DelegateCommand<string> ariaHostCommand;
public DelegateCommand<string> AriaHostCommand => ariaHostCommand ?? (ariaHostCommand = new DelegateCommand<string>(ExecuteAriaHostCommand));
/// <summary>
/// Aria服务器host事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteAriaHostCommand(string parameter)
{
AriaHost = parameter;
bool isSucceed = SettingsManager.GetInstance().SetAriaHost(AriaHost);
PublishTip(isSucceed);
}
// Aria服务器端口事件 // Aria服务器端口事件
private DelegateCommand<string> ariaListenPortCommand; private DelegateCommand<string> ariaListenPortCommand;
public DelegateCommand<string> AriaListenPortCommand => ariaListenPortCommand ?? (ariaListenPortCommand = new DelegateCommand<string>(ExecuteAriaListenPortCommand)); public DelegateCommand<string> AriaListenPortCommand => ariaListenPortCommand ?? (ariaListenPortCommand = new DelegateCommand<string>(ExecuteAriaListenPortCommand));
@ -461,6 +509,21 @@ namespace DownKyi.ViewModels.Settings
PublishTip(isSucceed); PublishTip(isSucceed);
} }
// Aria服务器token事件
private DelegateCommand<string> ariaTokenCommand;
public DelegateCommand<string> AriaTokenCommand => ariaTokenCommand ?? (ariaTokenCommand = new DelegateCommand<string>(ExecuteAriaTokenCommand));
/// <summary>
/// Aria服务器token事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteAriaTokenCommand(string parameter)
{
AriaToken = parameter;
bool isSucceed = SettingsManager.GetInstance().SetAriaToken(AriaToken);
PublishTip(isSucceed);
}
// Aria的日志等级事件 // Aria的日志等级事件
private DelegateCommand<string> ariaLogLevelsCommand; private DelegateCommand<string> ariaLogLevelsCommand;
public DelegateCommand<string> AriaLogLevelsCommand => ariaLogLevelsCommand ?? (ariaLogLevelsCommand = new DelegateCommand<string>(ExecuteAriaLogLevelsCommand)); public DelegateCommand<string> AriaLogLevelsCommand => ariaLogLevelsCommand ?? (ariaLogLevelsCommand = new DelegateCommand<string>(ExecuteAriaLogLevelsCommand));

@ -24,15 +24,15 @@ namespace DownKyi.ViewModels.Settings
#region 页面属性申明 #region 页面属性申明
private List<string> videoCodecs; private List<Quality> videoCodecs;
public List<string> VideoCodecs public List<Quality> VideoCodecs
{ {
get => videoCodecs; get => videoCodecs;
set => SetProperty(ref videoCodecs, value); set => SetProperty(ref videoCodecs, value);
} }
private string selectedVideoCodec; private Quality selectedVideoCodec;
public string SelectedVideoCodec public Quality SelectedVideoCodec
{ {
get => selectedVideoCodec; get => selectedVideoCodec;
set => SetProperty(ref selectedVideoCodec, value); set => SetProperty(ref selectedVideoCodec, value);
@ -186,18 +186,21 @@ namespace DownKyi.ViewModels.Settings
#region 属性初始化 #region 属性初始化
// 优先下载的视频编码 // 优先下载的视频编码
VideoCodecs = new List<string> VideoCodecs = Constant.GetCodecIds();
{ //VideoCodecs = new List<string>
"H.264/AVC", //{
"H.265/HEVC", // "H.264/AVC",
}; // "H.265/HEVC",
//};
// 优先下载画质 // 优先下载画质
VideoQualityList = Constant.GetResolutions(); VideoQualityList = Constant.GetResolutions();
// 优先下载音质 // 优先下载音质
AudioQualityList = Constant.GetAudioQualities(); 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<DisplayFileNamePart>(); SelectedFileName = new ObservableCollection<DisplayFileNamePart>();
@ -253,8 +256,9 @@ namespace DownKyi.ViewModels.Settings
isOnNavigatedTo = true; isOnNavigatedTo = true;
// 优先下载的视频编码 // 优先下载的视频编码
VideoCodecs videoCodecs = SettingsManager.GetInstance().GetVideoCodecs(); int videoCodecs = SettingsManager.GetInstance().GetVideoCodecs();
SelectedVideoCodec = GetVideoCodecsString(videoCodecs); //SelectedVideoCodec = GetVideoCodecsString(videoCodecs);
SelectedVideoCodec = VideoCodecs.FirstOrDefault(t => { return t.Id == videoCodecs; });
// 优先下载画质 // 优先下载画质
int quality = SettingsManager.GetInstance().GetQuality(); int quality = SettingsManager.GetInstance().GetQuality();
@ -315,18 +319,20 @@ namespace DownKyi.ViewModels.Settings
#region 命令申明 #region 命令申明
// 优先下载的视频编码事件 // 优先下载的视频编码事件
private DelegateCommand<string> videoCodecsCommand; private DelegateCommand<object> videoCodecsCommand;
public DelegateCommand<string> VideoCodecsCommand => videoCodecsCommand ?? (videoCodecsCommand = new DelegateCommand<string>(ExecuteVideoCodecsCommand)); public DelegateCommand<object> VideoCodecsCommand => videoCodecsCommand ?? (videoCodecsCommand = new DelegateCommand<object>(ExecuteVideoCodecsCommand));
/// <summary> /// <summary>
/// 优先下载的视频编码事件 /// 优先下载的视频编码事件
/// </summary> /// </summary>
/// <param name="parameter"></param> /// <param name="parameter"></param>
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); PublishTip(isSucceed);
} }
@ -674,49 +680,49 @@ namespace DownKyi.ViewModels.Settings
/// </summary> /// </summary>
/// <param name="videoCodecs"></param> /// <param name="videoCodecs"></param>
/// <returns></returns> /// <returns></returns>
private string GetVideoCodecsString(VideoCodecs videoCodecs) //private string GetVideoCodecsString(VideoCodecs videoCodecs)
{ //{
string codec; // string codec;
switch (videoCodecs) // switch (videoCodecs)
{ // {
case Core.Settings.VideoCodecs.NONE: // case Core.Settings.VideoCodecs.NONE:
codec = ""; // codec = "";
break; // break;
case Core.Settings.VideoCodecs.AVC: // case Core.Settings.VideoCodecs.AVC:
codec = "H.264/AVC"; // codec = "H.264/AVC";
break; // break;
case Core.Settings.VideoCodecs.HEVC: // case Core.Settings.VideoCodecs.HEVC:
codec = "H.265/HEVC"; // codec = "H.265/HEVC";
break; // break;
default: // default:
codec = ""; // codec = "";
break; // break;
} // }
return codec; // return codec;
} //}
/// <summary> /// <summary>
/// 返回VideoCodecs /// 返回VideoCodecs
/// </summary> /// </summary>
/// <param name="str"></param> /// <param name="str"></param>
/// <returns></returns> /// <returns></returns>
private VideoCodecs GetVideoCodecs(string str) //private VideoCodecs GetVideoCodecs(string str)
{ //{
VideoCodecs videoCodecs; // VideoCodecs videoCodecs;
switch (str) // switch (str)
{ // {
case "H.264/AVC": // case "H.264/AVC":
videoCodecs = Core.Settings.VideoCodecs.AVC; // videoCodecs = Core.Settings.VideoCodecs.AVC;
break; // break;
case "H.265/HEVC": // case "H.265/HEVC":
videoCodecs = Core.Settings.VideoCodecs.HEVC; // videoCodecs = Core.Settings.VideoCodecs.HEVC;
break; // break;
default: // default:
videoCodecs = Core.Settings.VideoCodecs.NONE; // videoCodecs = Core.Settings.VideoCodecs.NONE;
break; // break;
} // }
return videoCodecs; // return videoCodecs;
} //}
/// <summary> /// <summary>
/// 保存下载视频内容到设置 /// 保存下载视频内容到设置

@ -18,11 +18,25 @@ namespace DownKyi.ViewModels.Toolbox
#region 页面属性申明 #region 页面属性申明
private string videoPath; private string videoPathsStr;
public string VideoPath public string VideoPathsStr
{ {
get { return videoPath; } get => videoPathsStr;
set { SetProperty(ref videoPath, value); } 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; private string status;
@ -38,7 +52,7 @@ namespace DownKyi.ViewModels.Toolbox
{ {
#region 属性初始化 #region 属性初始化
VideoPath = string.Empty; VideoPaths = new string[0];
#endregion #endregion
} }
@ -60,7 +74,7 @@ namespace DownKyi.ViewModels.Toolbox
return; return;
} }
VideoPath = DialogUtils.SelectVideoFile(); VideoPaths = DialogUtils.SelectMultiVideoFile();
} }
// 提取音频事件 // 提取音频事件
@ -78,24 +92,27 @@ namespace DownKyi.ViewModels.Toolbox
return; return;
} }
if (VideoPath == "") if (VideoPaths.Length <= 0)
{ {
eventAggregator.GetEvent<MessageEvent>().Publish(DictionaryResource.GetString("TipNoSeletedVideo")); eventAggregator.GetEvent<MessageEvent>().Publish(DictionaryResource.GetString("TipNoSeletedVideo"));
return; return;
} }
// 音频文件名
string audioFileName = VideoPath.Remove(VideoPath.Length - 4, 4) + ".aac";
Status = string.Empty; Status = string.Empty;
await Task.Run(() => await Task.Run(() =>
{ {
// 执行提取音频程序
isExtracting = true; isExtracting = true;
FFmpegHelper.ExtractAudio(VideoPath, audioFileName, new Action<string>((output) => foreach (var item in VideoPaths)
{
// 音频文件名
string audioFileName = item.Remove(item.Length - 4, 4) + ".aac";
// 执行提取音频程序
FFmpegHelper.ExtractAudio(item, audioFileName, new Action<string>((output) =>
{ {
Status += output + "\n"; Status += output + "\n";
})); }));
}
isExtracting = false; isExtracting = false;
}); });
} }
@ -115,30 +132,34 @@ namespace DownKyi.ViewModels.Toolbox
return; return;
} }
if (VideoPath == "") if (VideoPaths.Length <= 0)
{ {
eventAggregator.GetEvent<MessageEvent>().Publish(DictionaryResource.GetString("TipNoSeletedVideo")); eventAggregator.GetEvent<MessageEvent>().Publish(DictionaryResource.GetString("TipNoSeletedVideo"));
return; return;
} }
// 视频文件名
string videoFileName = VideoPath.Remove(VideoPath.Length - 4, 4) + "_onlyVideo.mp4";
Status = string.Empty; Status = string.Empty;
await Task.Run(() => await Task.Run(() =>
{ {
// 执行提取视频程序
isExtracting = true; isExtracting = true;
FFmpegHelper.ExtractVideo(VideoPath, videoFileName, new Action<string>((output) => foreach (var item in VideoPaths)
{
// 视频文件名
string videoFileName = item.Remove(item.Length - 4, 4) + "_onlyVideo.mp4";
// 执行提取视频程序
FFmpegHelper.ExtractVideo(item, videoFileName, new Action<string>((output) =>
{ {
Status += output + "\n"; Status += output + "\n";
})); }));
}
isExtracting = false; isExtracting = false;
}); });
} }
// Status改变事件 // Status改变事件
private DelegateCommand<object> statusCommand; private DelegateCommand<object> statusCommand;
public DelegateCommand<object> StatusCommand => statusCommand ?? (statusCommand = new DelegateCommand<object>(ExecuteStatusCommand)); public DelegateCommand<object> StatusCommand => statusCommand ?? (statusCommand = new DelegateCommand<object>(ExecuteStatusCommand));
/// <summary> /// <summary>

@ -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);
}
}
}

@ -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> seasonsSeries;
public ObservableCollection<SeasonsSeries> 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<SeasonsSeries>();
#endregion
}
#region 命令申明
// 视频选择事件
private DelegateCommand<object> seasonsSeriesCommand;
public DelegateCommand<object> SeasonsSeriesCommand => seasonsSeriesCommand ?? (seasonsSeriesCommand = new DelegateCommand<object>(ExecuteSeasonsSeriesCommand));
/// <summary>
/// 视频选择事件
/// </summary>
/// <param name="parameter"></param>
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<string, object> data = new Dictionary<string, object>
{
{ "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<NavigationEvent>().Publish(param);
SelectedItem = -1;
}
#endregion
public override void OnNavigatedFrom(NavigationContext navigationContext)
{
base.OnNavigatedFrom(navigationContext);
SeasonsSeries.Clear();
SelectedItem = -1;
}
/// <summary>
/// 接收mid参数
/// </summary>
/// <param name="navigationContext"></param>
public async override void OnNavigatedTo(NavigationContext navigationContext)
{
base.OnNavigatedTo(navigationContext);
SeasonsSeries.Clear();
SelectedItem = -1;
// 根据传入参数不同执行不同任务
var parameter = navigationContext.Parameters.GetValue<SpaceSeasonsSeries>("object");
if (parameter == null)
{
return;
}
// 传入mid
mid = navigationContext.Parameters.GetValue<long>("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
});
}
}
}
}

@ -12,6 +12,7 @@ using Prism.Regions;
using Prism.Services.Dialogs; using Prism.Services.Dialogs;
using System; using System;
using System.IO; using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
@ -214,7 +215,7 @@ namespace DownKyi.ViewModels
} }
LogManager.Debug(Tag, $"InputText: {InputText}"); LogManager.Debug(Tag, $"InputText: {InputText}");
InputText = Regex.Replace(InputText, @"[【]*[^【]*[^】]*[】 ]", "");
SearchService searchService = new SearchService(); SearchService searchService = new SearchService();
bool isSupport = searchService.BiliInput(InputText, Tag, eventAggregator); bool isSupport = searchService.BiliInput(InputText, Tag, eventAggregator);
if (!isSupport) if (!isSupport)

@ -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<ChannelMedia> medias;
public ObservableCollection<ChannelMedia> 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<ChannelMedia>();
#endregion
}
#region 命令申明
// 返回事件
private DelegateCommand backSpaceCommand;
public DelegateCommand BackSpaceCommand => backSpaceCommand ?? (backSpaceCommand = new DelegateCommand(ExecuteBackSpace));
/// <summary>
/// 返回事件
/// </summary>
private void ExecuteBackSpace()
{
ArrowBack.Fill = DictionaryResource.GetColor("ColorText");
// 结束任务
tokenSource?.Cancel();
NavigationParam parameter = new NavigationParam
{
ViewName = ParentView,
ParentViewName = null,
Parameter = null
};
eventAggregator.GetEvent<NavigationEvent>().Publish(parameter);
}
// 前往下载管理页面
private DelegateCommand downloadManagerCommand;
public DelegateCommand DownloadManagerCommand => downloadManagerCommand ?? (downloadManagerCommand = new DelegateCommand(ExecuteDownloadManagerCommand));
/// <summary>
/// 前往下载管理页面
/// </summary>
private void ExecuteDownloadManagerCommand()
{
NavigationParam parameter = new NavigationParam
{
ViewName = ViewDownloadManagerViewModel.Tag,
ParentViewName = Tag,
Parameter = null
};
eventAggregator.GetEvent<NavigationEvent>().Publish(parameter);
}
// 全选按钮点击事件
private DelegateCommand<object> selectAllCommand;
public DelegateCommand<object> SelectAllCommand => selectAllCommand ?? (selectAllCommand = new DelegateCommand<object>(ExecuteSelectAllCommand));
/// <summary>
/// 全选按钮点击事件
/// </summary>
/// <param name="parameter"></param>
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<object> mediasCommand;
public DelegateCommand<object> MediasCommand => mediasCommand ?? (mediasCommand = new DelegateCommand<object>(ExecuteMediasCommand));
/// <summary>
/// 列表选择事件
/// </summary>
/// <param name="parameter"></param>
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));
/// <summary>
/// 添加选中项到下载列表事件
/// </summary>
private void ExecuteAddToDownloadCommand()
{
AddToDownload(true);
}
// 添加所有视频到下载列表事件
private DelegateCommand addAllToDownloadCommand;
public DelegateCommand AddAllToDownloadCommand => addAllToDownloadCommand ?? (addAllToDownloadCommand = new DelegateCommand(ExecuteAddAllToDownloadCommand));
/// <summary>
/// 添加所有视频到下载列表事件
/// </summary>
private void ExecuteAddAllToDownloadCommand()
{
AddToDownload(false);
}
#endregion
/// <summary>
/// 添加到下载
/// </summary>
/// <param name="isOnlySelected"></param>
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<MessageEvent>().Publish(DictionaryResource.GetString("TipAddDownloadingZero"));
}
else
{
eventAggregator.GetEvent<MessageEvent>().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;
}
/// <summary>
/// 导航到VideoDetail页面时执行
/// </summary>
/// <param name="navigationContext"></param>
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<Dictionary<string, object>>("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;
}
}
}

@ -228,12 +228,15 @@ namespace DownKyi.ViewModels
switch (banner.Id) switch (banner.Id)
{ {
case 0: case 0: // 投稿
regionManager.RequestNavigate("UserSpaceContentRegion", ViewArchiveViewModel.Tag, param); regionManager.RequestNavigate("UserSpaceContentRegion", ViewArchiveViewModel.Tag, param);
break; break;
case 1: case 1: // 频道(弃用)
regionManager.RequestNavigate("UserSpaceContentRegion", UserSpace.ViewChannelViewModel.Tag, param); regionManager.RequestNavigate("UserSpaceContentRegion", UserSpace.ViewChannelViewModel.Tag, param);
break; break;
case 2: // 合集和列表
regionManager.RequestNavigate("UserSpaceContentRegion", UserSpace.ViewSeasonsSeriesViewModel.Tag, param);
break;
} }
} }
@ -427,20 +430,38 @@ namespace DownKyi.ViewModels
} }
// 频道 // 频道
List<SpaceChannelList> channelList = null; //List<SpaceChannelList> 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(() => 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 TabLeftBanners.Add(new TabLeftBanner
{ {
Object = channelList, Object = seasonsSeries,
Id = 1, Id = 2,
Icon = NormalIcon.Instance().Channel, Icon = NormalIcon.Instance().Channel,
IconColor = "#FF23C9ED", IconColor = "#FF23C9ED",
Title = DictionaryResource.GetString("Channel") Title = DictionaryResource.GetString("SeasonsSeries")
}); });
} }

@ -18,6 +18,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
@ -184,7 +185,7 @@ namespace DownKyi.ViewModels
if (InputText == null || InputText == string.Empty) { return; } if (InputText == null || InputText == string.Empty) { return; }
LogManager.Debug(Tag, $"InputText: {InputText}"); LogManager.Debug(Tag, $"InputText: {InputText}");
InputText = Regex.Replace(InputText, @"[【]*[^【]*[^】]*[】 ]", "");
input = InputText; input = InputText;
// 更新页面 // 更新页面

@ -56,6 +56,15 @@
Foreground="{DynamicResource BrushTextDark}" Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding Aria2c}" IsChecked="{Binding Aria2c}"
Style="{StaticResource RadioStyle}" /> Style="{StaticResource RadioStyle}" />
<RadioButton
Margin="20,0,0,0"
Command="{Binding SelectDownloaderCommand}"
CommandParameter="CustomAria2c"
Content="{DynamicResource CustomAria2cDownloader}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding CustomAria2c}"
Style="{StaticResource RadioStyle}" />
</StackPanel> </StackPanel>
</GroupBox> </GroupBox>
</StackPanel> </StackPanel>
@ -208,6 +217,32 @@
</Style> </Style>
</StackPanel.Style> </StackPanel.Style>
<!--<StackPanel
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaServerHost}" />
<TextBox
Name="nameAriaLocalHost"
Width="100"
Height="20"
VerticalContentAlignment="Center"
IsReadOnly="True"
Text="{Binding AriaHost}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaHostCommand}"
CommandParameter="{Binding ElementName=nameAriaLocalHost, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>-->
<StackPanel <StackPanel
Margin="0,20,0,0" Margin="0,20,0,0"
Orientation="Horizontal" Orientation="Horizontal"
@ -449,6 +484,97 @@
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<StackPanel x:Name="nameCustomAria">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding CustomAria2c}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding CustomAria2c}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<StackPanel
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaServerHost}" />
<TextBox
Name="nameAriaHost"
Width="300"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaHost}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaHostCommand}"
CommandParameter="{Binding ElementName=nameAriaHost, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaServerPort}" />
<TextBox
Name="nameAriaPort"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaListenPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaListenPortCommand}"
CommandParameter="{Binding ElementName=nameAriaPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaServerToken}" />
<TextBox
Name="nameAriaToken"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaToken}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaTokenCommand}"
CommandParameter="{Binding ElementName=nameAriaToken, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</StackPanel>
<StackPanel Margin="10" /> <StackPanel Margin="10" />
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

@ -23,6 +23,7 @@
Name="nameVideoCodecs" Name="nameVideoCodecs"
Width="120" Width="120"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
DisplayMemberPath="Name"
ItemsSource="{Binding VideoCodecs}" ItemsSource="{Binding VideoCodecs}"
SelectedValue="{Binding SelectedVideoCodec}"> SelectedValue="{Binding SelectedVideoCodec}">
<i:Interaction.Triggers> <i:Interaction.Triggers>

@ -9,7 +9,7 @@
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled"> <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled">
<Grid Margin="50,0"> <Grid Margin="50,0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="150" /> <RowDefinition Height="200" />
<RowDefinition /> <RowDefinition />
</Grid.RowDefinitions> </Grid.RowDefinitions>
@ -27,12 +27,12 @@
Orientation="Horizontal"> Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{DynamicResource VideoFilePath}" /> <TextBlock VerticalAlignment="Center" Text="{DynamicResource VideoFilePath}" />
<TextBox <TextBox
Width="300" Width="600"
Height="20" Height="65"
Margin="0,10,10,10" Margin="0,10,10,10"
VerticalContentAlignment="Center" VerticalContentAlignment="Top"
IsReadOnly="True" IsReadOnly="True"
Text="{Binding VideoPath, Mode=TwoWay}" /> Text="{Binding VideoPathsStr, Mode=TwoWay}" />
<Button <Button
Width="75" Width="75"

@ -0,0 +1,135 @@
<UserControl
x:Class="DownKyi.Views.UserSpace.ViewSeasonsSeries"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListBox
x:Name="nameSeasonsSeries"
ItemsSource="{Binding SeasonsSeries}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedIndex="{Binding SelectedItem}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SeasonsSeriesCommand}" CommandParameter="{Binding ElementName=nameSeasonsSeries, Path=SelectedItem}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border
x:Name="Bd"
Padding="0"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Focusable="False">
<ItemsPresenter />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Style>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<StackPanel
Grid.Row="0"
Margin="30,0,30,10"
HorizontalAlignment="Left"
Orientation="Vertical">
<Border
Width="190"
Height="119"
HorizontalAlignment="Center"
CornerRadius="5"
Cursor="Hand">
<Border.Background>
<!-- 长宽比1.6 -->
<ImageBrush ImageSource="{Binding Cover}" />
</Border.Background>
<Border
Width="70"
HorizontalAlignment="Right"
Background="{DynamicResource BrushBorderTranslucent100}"
CornerRadius="0 5 5 0">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock
FontSize="16"
Foreground="{DynamicResource BrushText}"
Text="{Binding Count}" />
<!--<Image
Height="18"
Margin="0,10,0,0"
Source="/DownKyi;component/Resources/channel.png" />-->
<ContentControl
Width="20"
Height="20"
Margin="0,10,0,0"
VerticalAlignment="Center">
<Path
Width="20"
Height="20"
Data="{Binding TypeImage.Data}"
Fill="#E5E9EF"
Stretch="Uniform" />
</ContentControl>
</StackPanel>
</Border>
</Border>
<TextBlock
MaxWidth="190"
Margin="0,5,0,0"
HorizontalAlignment="Left"
Cursor="Hand"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis"
ToolTip="{Binding Name}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{DynamicResource BrushPrimary}" />
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="Foreground" Value="{DynamicResource BrushTextDark}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock
Name="nameZoneCount"
Margin="0,5,0,0"
HorizontalAlignment="Left"
Foreground="{DynamicResource BrushTextGrey2}"
Text="{Binding Ctime}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</UserControl>

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DownKyi.Views.UserSpace
{
/// <summary>
/// ViewChannel.xaml 的交互逻辑
/// </summary>
public partial class ViewSeasonsSeries : UserControl
{
public ViewSeasonsSeries()
{
InitializeComponent();
}
}
}

@ -0,0 +1,331 @@
<UserControl
x:Class="DownKyi.Views.ViewSeasonsSeries"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:DownKyi.CustomControl"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
<UserControl.Resources>
<Style x:Key="MediaListStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<StackPanel
Name="nameItemPanel"
Margin="15,15,10,5"
Cursor="Hand"
Orientation="Vertical">
<Border
Name="nameCover"
Width="190"
Height="119"
HorizontalAlignment="Center"
CornerRadius="5">
<Border.Background>
<!-- 长宽比1.6 -->
<ImageBrush ImageSource="{Binding Cover}" />
</Border.Background>
<Grid Width="190" Height="119">
<Image
Name="nameChecked"
Width="24"
Height="24"
Margin="0,10,15,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Source="/DownKyi;component/Resources/checked.png" />
<Border
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Background="{DynamicResource BrushMask}"
CornerRadius="5 0 5 0">
<TextBlock
Name="nameDuration"
Padding="6,0"
Foreground="{DynamicResource BrushText}"
Text="{Binding Duration}" />
</Border>
</Grid>
</Border>
<TextBlock
Name="nameTitle"
Height="35"
MaxWidth="190"
Margin="0,10,0,0"
Foreground="{DynamicResource BrushTextDark}"
Tag="{Binding Bvid}"
Text="{Binding Title}"
TextTrimming="CharacterEllipsis"
TextWrapping="Wrap"
ToolTip="{Binding Title}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding TitleCommand}" CommandParameter="{Binding DataContext.PageName, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<Grid Margin="0,3,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal">
<Image
Width="11"
Height="11"
Source="/DownKyi;component/Resources/play.png" />
<TextBlock
Margin="5,0,0,0"
Foreground="{DynamicResource BrushTextGrey}"
Text="{Binding PlayNumber}" />
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Image
Width="12"
Height="11"
Source="/DownKyi;component/Resources/time.png" />
<TextBlock
Margin="5,0,0,0"
Foreground="{DynamicResource BrushTextGrey}"
Text="{Binding CreateTime}" />
</StackPanel>
</Grid>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="nameChecked" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="nameChecked" Property="Visibility" Value="Hidden" />
</Trigger>
<Trigger SourceName="nameTitle" Property="IsMouseOver" Value="True">
<Setter TargetName="nameTitle" Property="Foreground" Value="{DynamicResource BrushPrimary}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="1" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="0"
Margin="10,5,0,5"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Command="{Binding BackSpaceCommand}"
Style="{StaticResource ImageBtnStyle}">
<StackPanel Orientation="Horizontal">
<ContentControl Width="24" Height="24">
<Path
Width="{Binding ArrowBack.Width}"
Height="{Binding ArrowBack.Height}"
Data="{Binding ArrowBack.Data}"
Fill="{Binding ArrowBack.Fill}"
Stretch="None" />
</ContentControl>
<TextBlock
VerticalAlignment="Center"
FontSize="16"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource SeasonsSeries}" />
<TextBlock
VerticalAlignment="Center"
FontSize="16"
Foreground="{DynamicResource BrushTextDark}"
Text=" - ">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Title}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Title}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock
VerticalAlignment="Center"
FontSize="16"
Foreground="{DynamicResource BrushTextDark}"
Text="{Binding Title}" />
</StackPanel>
</Button>
<Button
Grid.Column="2"
Width="24"
Height="24"
Margin="10,5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding DownloadManagerCommand}"
Style="{StaticResource ImageBtnStyle}"
ToolTip="{DynamicResource DownloadManager}">
<ContentControl>
<Path
Width="{Binding DownloadManage.Width}"
Height="{Binding DownloadManage.Height}"
Data="{Binding DownloadManage.Data}"
Fill="{Binding DownloadManage.Fill}"
Stretch="Uniform" />
</ContentControl>
</Button>
</Grid>
<TextBlock Grid.Row="1" Background="{DynamicResource BrushBorder}" />
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="1" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<ListBox
x:Name="nameMedias"
Grid.Row="0"
BorderThickness="0"
ItemContainerStyle="{StaticResource MediaListStyle}"
ItemsSource="{Binding Medias}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionMode="Extended">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding MediasCommand}" CommandParameter="{Binding ElementName=nameMedias, Path=SelectedItems}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border
x:Name="Bd"
Padding="0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Focusable="False">
<ItemsPresenter />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Style>
</ListBox>
<!-- 加载gif -->
<StackPanel
Grid.Row="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Vertical"
Visibility="{Binding LoadingVisibility}">
<ContentControl
Width="40"
Height="40"
Content="{Binding Loading}" />
<TextBlock
Margin="0,10,0,0"
FontSize="14"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource PublicationWait}" />
</StackPanel>
<!-- 没有数据提示 -->
<Image
Grid.Row="0"
Width="256"
Height="256"
Source="/DownKyi;component/Resources/no-data.png"
Visibility="{Binding NoDataVisibility}" />
<TextBlock Grid.Row="1" Background="{DynamicResource BrushBorder}" />
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="80" />
</Grid.ColumnDefinitions>
<CheckBox
Grid.Column="0"
Margin="10,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Command="{Binding SelectAllCommand}"
CommandParameter="{Binding ElementName=nameMedias, Path=SelectedItem}"
Content="{DynamicResource SelectAll}"
Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding IsSelectAll, Mode=TwoWay}"
Style="{StaticResource CheckBoxStyle}" />
<custom:CustomPager
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DataContext="{Binding Pager}" />
<Button
Grid.Column="2"
Margin="0,0,10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding AddToDownloadCommand}"
Content="{DynamicResource DownloadSelectedPublication}"
FontSize="12"
Foreground="{DynamicResource BrushText}"
Style="{StaticResource BtnStyle}" />
<Button
Grid.Column="3"
Margin="0,0,10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding AddAllToDownloadCommand}"
Content="{DynamicResource DownloadAllPublication}"
FontSize="12"
Foreground="{DynamicResource BrushText}"
Style="{StaticResource BtnStyle}" />
</Grid>
</Grid>
</Grid>
</UserControl>

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DownKyi.Views
{
/// <summary>
/// ViewChannel.xaml 的交互逻辑
/// </summary>
public partial class ViewSeasonsSeries : UserControl
{
public ViewSeasonsSeries()
{
InitializeComponent();
}
}
}
Loading…
Cancel
Save