Merge branch 'v1.5.x' into v1.5.x-feature-view-follow

croire 3 years ago
commit bb760b238d

@ -36,11 +36,11 @@ namespace DownKyi.Core.Aria2cNet.Client.Entity
[JsonProperty("dir")]
public string Dir { get; set; }
[JsonProperty("header")]
public string Header { get; set; }
//[JsonProperty("header")]
//public string Header { get; set; }
[JsonProperty("use-head")]
public string UseHead { get; set; }
//[JsonProperty("use-head")]
//public string UseHead { get; set; }
[JsonProperty("user-agent")]
public string UserAgent { get; set; }

@ -90,6 +90,7 @@ namespace DownKyi.Core.Aria2cNet.Server
ExcuteProcess("aria2c.exe",
$"--enable-rpc --rpc-listen-all=true --rpc-allow-origin-all=true " +
$"--check-certificate=false " + // 解决问题 SSL/TLS handshake failure
$"--rpc-listen-port={config.ListenPort} " +
$"--rpc-secret={config.Token} " +
$"--input-file=\"{sessionFile}\" --save-session=\"{sessionFile}\" " +

@ -52,8 +52,8 @@
<Reference Include="Brotli.Core, Version=2.1.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Brotli.NET.2.1.1\lib\net45\Brotli.Core.dll</HintPath>
</Reference>
<Reference Include="Google.Protobuf, Version=3.19.1.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604, processorArchitecture=MSIL">
<HintPath>..\packages\Google.Protobuf.3.19.1\lib\net45\Google.Protobuf.dll</HintPath>
<Reference Include="Google.Protobuf, Version=3.20.1.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604, processorArchitecture=MSIL">
<HintPath>..\packages\Google.Protobuf.3.20.1\lib\net45\Google.Protobuf.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
@ -290,6 +290,7 @@
<Compile Include="Logging\LogLevel.cs" />
<Compile Include="Logging\LogManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Settings\Downloader.cs" />
<Compile Include="Settings\Models\VideoContentSettings.cs" />
<Compile Include="Settings\OrderFormat.cs" />
<Compile Include="Settings\ParseScope.cs" />
@ -376,10 +377,10 @@
</PropertyGroup>
<Error Condition="!Exists('..\packages\Brotli.NET.2.1.1\build\Brotli.NET.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Brotli.NET.2.1.1\build\Brotli.NET.targets'))" />
<Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.112.2\build\net40\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.112.2\build\net40\System.Data.SQLite.Core.targets'))" />
<Error Condition="!Exists('..\packages\Google.Protobuf.Tools.3.19.1\build\Google.Protobuf.Tools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Google.Protobuf.Tools.3.19.1\build\Google.Protobuf.Tools.targets'))" />
<Error Condition="!Exists('..\packages\WebPSharp.0.5.1\build\WebPSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\WebPSharp.0.5.1\build\WebPSharp.targets'))" />
<Error Condition="!Exists('..\packages\Google.Protobuf.Tools.3.20.1\build\Google.Protobuf.Tools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Google.Protobuf.Tools.3.20.1\build\Google.Protobuf.Tools.targets'))" />
</Target>
<Import Project="..\packages\System.Data.SQLite.Core.1.0.112.2\build\net40\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.112.2\build\net40\System.Data.SQLite.Core.targets')" />
<Import Project="..\packages\Google.Protobuf.Tools.3.19.1\build\Google.Protobuf.Tools.targets" Condition="Exists('..\packages\Google.Protobuf.Tools.3.19.1\build\Google.Protobuf.Tools.targets')" />
<Import Project="..\packages\WebPSharp.0.5.1\build\WebPSharp.targets" Condition="Exists('..\packages\WebPSharp.0.5.1\build\WebPSharp.targets')" />
<Import Project="..\packages\Google.Protobuf.Tools.3.20.1\build\Google.Protobuf.Tools.targets" Condition="Exists('..\packages\Google.Protobuf.Tools.3.20.1\build\Google.Protobuf.Tools.targets')" />
</Project>

@ -385,10 +385,34 @@ namespace DownKyi.Core.Downloader
/// <summary>
/// 开始下载
/// </summary>
public void Start()
public void StartAsync()
{
//Task th = new Task(CreateFirstPartitions);
//th.Start();
StartAsync(false);
}
/// <summary>
/// 开始下载
/// </summary>
/// <param name="isWait"></param>
public void StartAsync(bool isWait)
{
Task th = new Task(CreateFirstPartitions);
th.Start();
if (isWait)
{
th.Wait();
}
}
/// <summary>
/// 开始下载
/// </summary>
public void Start()
{
CreateFirstPartitions();
}
/// <summary>

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

@ -0,0 +1,9 @@
namespace DownKyi.Core.Settings
{
public enum Downloader
{
NOT_SET = 0,
BUILT_IN,
ARIA,
}
}

@ -8,9 +8,20 @@ namespace DownKyi.Core.Settings.Models
public class NetworkSettings
{
public AllowStatus IsLiftingOfRegion { get; set; } = AllowStatus.NONE;
public Downloader Downloader { get; set; } = Downloader.NOT_SET;
public int MaxCurrentDownloads { get; set; } = -1;
#region built-in
public int Split { get; set; } = -1;
public AllowStatus IsHttpProxy { get; set; } = AllowStatus.NONE;
public string HttpProxy { get; set; } = null;
public int HttpProxyListenPort { get; set; } = -1;
#endregion
#region Aria
public int AriaListenPort { get; set; } = -1;
public AriaConfigLogLevel AriaLogLevel { get; set; } = AriaConfigLogLevel.NOT_SET;
public int AriaMaxConcurrentDownloads { get; set; } = -1;
public int AriaSplit { get; set; } = -1;
public int AriaMaxOverallDownloadLimit { get; set; } = -1;
public int AriaMaxDownloadLimit { get; set; } = -1;
@ -19,5 +30,6 @@ namespace DownKyi.Core.Settings.Models
public AllowStatus IsAriaHttpProxy { get; set; } = AllowStatus.NONE;
public string AriaHttpProxy { get; set; } = null;
public int AriaHttpProxyListenPort { get; set; } = -1;
#endregion
}
}

@ -7,15 +7,26 @@ namespace DownKyi.Core.Settings
// 是否开启解除地区限制
private readonly AllowStatus isLiftingOfRegion = AllowStatus.YES;
// 下载器
private readonly Downloader downloader = Downloader.ARIA;
// 最大同时下载数(任务数)
private readonly int maxCurrentDownloads = 3;
// 单文件最大线程数
private readonly int split = 8;
// HttpProxy代理
private readonly AllowStatus isHttpProxy = AllowStatus.NO;
private readonly string httpProxy = "";
private readonly int httpProxyListenPort = 0;
// Aria服务器端口号
private readonly int ariaListenPort = 6801;
private readonly int ariaListenPort = 6800;
// Aria日志等级
private readonly AriaConfigLogLevel ariaLogLevel = AriaConfigLogLevel.INFO;
// Aria最大同时下载数(任务数)
private readonly int ariaMaxConcurrentDownloads = 3;
// Aria单文件最大线程数
private readonly int ariaSplit = 5;
@ -60,6 +71,168 @@ namespace DownKyi.Core.Settings
return SetSettings();
}
/// <summary>
/// 获取下载器
/// </summary>
/// <returns></returns>
public Downloader GetDownloader()
{
appSettings = GetSettings();
if (appSettings.Network.Downloader == Downloader.NOT_SET)
{
// 第一次获取,先设置默认值
SetDownloader(downloader);
return downloader;
}
return appSettings.Network.Downloader;
}
/// <summary>
/// 设置下载器
/// </summary>
/// <param name="downloader"></param>
/// <returns></returns>
public bool SetDownloader(Downloader downloader)
{
appSettings.Network.Downloader = downloader;
return SetSettings();
}
/// <summary>
/// 获取最大同时下载数(任务数)
/// </summary>
/// <returns></returns>
public int GetMaxCurrentDownloads()
{
appSettings = GetSettings();
if (appSettings.Network.MaxCurrentDownloads == -1)
{
// 第一次获取,先设置默认值
SetMaxCurrentDownloads(maxCurrentDownloads);
return maxCurrentDownloads;
}
return appSettings.Network.MaxCurrentDownloads;
}
/// <summary>
/// 设置最大同时下载数(任务数)
/// </summary>
/// <param name="ariaMaxConcurrentDownloads"></param>
/// <returns></returns>
public bool SetMaxCurrentDownloads(int maxCurrentDownloads)
{
appSettings.Network.MaxCurrentDownloads = maxCurrentDownloads;
return SetSettings();
}
/// <summary>
/// 获取单文件最大线程数
/// </summary>
/// <returns></returns>
public int GetSplit()
{
appSettings = GetSettings();
if (appSettings.Network.Split == -1)
{
// 第一次获取,先设置默认值
SetSplit(split);
return split;
}
return appSettings.Network.Split;
}
/// <summary>
/// 设置单文件最大线程数
/// </summary>
/// <param name="split"></param>
/// <returns></returns>
public bool SetSplit(int split)
{
appSettings.Network.Split = split;
return SetSettings();
}
/// <summary>
/// 获取是否开启Http代理
/// </summary>
/// <returns></returns>
public AllowStatus IsHttpProxy()
{
appSettings = GetSettings();
if (appSettings.Network.IsHttpProxy == AllowStatus.NONE)
{
// 第一次获取,先设置默认值
IsHttpProxy(isHttpProxy);
return isHttpProxy;
}
return appSettings.Network.IsHttpProxy;
}
/// <summary>
/// 设置是否开启Http代理
/// </summary>
/// <param name="isHttpProxy"></param>
/// <returns></returns>
public bool IsHttpProxy(AllowStatus isHttpProxy)
{
appSettings.Network.IsHttpProxy = isHttpProxy;
return SetSettings();
}
/// <summary>
/// 获取Http代理的地址
/// </summary>
/// <returns></returns>
public string GetHttpProxy()
{
appSettings = GetSettings();
if (appSettings.Network.HttpProxy == null)
{
// 第一次获取,先设置默认值
SetHttpProxy(httpProxy);
return httpProxy;
}
return appSettings.Network.HttpProxy;
}
/// <summary>
/// 设置Aria的http代理的地址
/// </summary>
/// <param name="httpProxy"></param>
/// <returns></returns>
public bool SetHttpProxy(string httpProxy)
{
appSettings.Network.HttpProxy = httpProxy;
return SetSettings();
}
/// <summary>
/// 获取Http代理的端口
/// </summary>
/// <returns></returns>
public int GetHttpProxyListenPort()
{
appSettings = GetSettings();
if (appSettings.Network.HttpProxyListenPort == -1)
{
// 第一次获取,先设置默认值
SetHttpProxyListenPort(httpProxyListenPort);
return httpProxyListenPort;
}
return appSettings.Network.HttpProxyListenPort;
}
/// <summary>
/// 设置Http代理的端口
/// </summary>
/// <param name="httpProxyListenPort"></param>
/// <returns></returns>
public bool SetHttpProxyListenPort(int httpProxyListenPort)
{
appSettings.Network.HttpProxyListenPort = httpProxyListenPort;
return SetSettings();
}
/// <summary>
/// 获取Aria服务器的端口号
/// </summary>
@ -114,33 +287,6 @@ namespace DownKyi.Core.Settings
return SetSettings();
}
/// <summary>
/// 获取Aria最大同时下载数(任务数)
/// </summary>
/// <returns></returns>
public int GetAriaMaxConcurrentDownloads()
{
appSettings = GetSettings();
if (appSettings.Network.AriaMaxConcurrentDownloads == -1)
{
// 第一次获取,先设置默认值
SetAriaMaxConcurrentDownloads(ariaMaxConcurrentDownloads);
return ariaMaxConcurrentDownloads;
}
return appSettings.Network.AriaMaxConcurrentDownloads;
}
/// <summary>
/// 设置Aria最大同时下载数(任务数)
/// </summary>
/// <param name="ariaMaxConcurrentDownloads"></param>
/// <returns></returns>
public bool SetAriaMaxConcurrentDownloads(int ariaMaxConcurrentDownloads)
{
appSettings.Network.AriaMaxConcurrentDownloads = ariaMaxConcurrentDownloads;
return SetSettings();
}
/// <summary>
/// 获取Aria单文件最大线程数
/// </summary>

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Brotli.NET" version="2.1.1" targetFramework="net472" />
<package id="Google.Protobuf" version="3.19.1" targetFramework="net472" />
<package id="Google.Protobuf.Tools" version="3.19.1" targetFramework="net472" />
<package id="Google.Protobuf" version="3.20.1" targetFramework="net472" />
<package id="Google.Protobuf.Tools" version="3.20.1" targetFramework="net472" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net472" />
<package id="QRCoder" version="1.4.3" targetFramework="net472" />
<package id="System.Buffers" version="4.5.1" targetFramework="net472" />

@ -16,7 +16,6 @@ using DownKyi.Views.Settings;
using DownKyi.Views.Toolbox;
using DownKyi.Views.UserSpace;
using Prism.Ioc;
using Prism.Services.Dialogs;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@ -124,8 +123,22 @@ namespace DownKyi
});
// 启动下载服务
downloadService = new AriaDownloadService(DownloadingList, DownloadedList);
downloadService.Start();
var download = SettingsManager.GetInstance().GetDownloader();
switch (download)
{
case Downloader.NOT_SET:
break;
case Downloader.BUILT_IN:
downloadService = new BuiltinDownloadService(DownloadingList, DownloadedList);
break;
case Downloader.ARIA:
downloadService = new AriaDownloadService(DownloadingList, DownloadedList);
break;
}
if (downloadService != null)
{
downloadService.Start();
}
return Container.Resolve<MainWindow>();
}

@ -117,6 +117,7 @@
<Compile Include="Models\OrderFormatDisplay.cs" />
<Compile Include="Services\AlertService.cs" />
<Compile Include="Services\Download\AddToDownloadService.cs" />
<Compile Include="Services\Download\BuiltinDownloadService.cs" />
<Compile Include="Services\Download\DownloadStorageService.cs" />
<Compile Include="Services\SearchService.cs" />
<Compile Include="ViewModels\Friend\ViewFollowerViewModel.cs" />

@ -188,6 +188,9 @@
<system:String x:Key="AutoDownloadAll">解析后自动下载已解析视频</system:String>
<system:String x:Key="Network">网络</system:String>
<system:String x:Key="SelectDownloader">选择下载器(重启生效):</system:String>
<system:String x:Key="BuiltinDownloader">内建下载器</system:String>
<system:String x:Key="Aria2cDownloader">Aria2下载器</system:String>
<system:String x:Key="AriaServerPort">Aria服务器端口</system:String>
<system:String x:Key="AriaLogLevel">Aria日志等级</system:String>
<system:String x:Key="AriaMaxConcurrentDownloads">Aria同时下载数</system:String>
@ -195,10 +198,12 @@
<system:String x:Key="AriaDownloadLimit">Aria下载速度限制(KB/s)</system:String>
<system:String x:Key="AriaMaxOverallDownloadLimit">全局下载速度限制[0: 无限制]</system:String>
<system:String x:Key="AriaMaxDownloadLimit">单任务下载速度限制[0: 无限制]</system:String>
<system:String x:Key="IsAriaHttpProxy">使用Http代理</system:String>
<system:String x:Key="AriaHttpProxy">代理地址:</system:String>
<system:String x:Key="AriaHttpProxyPort">端口:</system:String>
<system:String x:Key="AriaFileAllocation">Aria文件预分配</system:String>
<system:String x:Key="IsHttpProxy">使用Http代理</system:String>
<system:String x:Key="HttpProxy">代理地址:</system:String>
<system:String x:Key="HttpProxyPort">端口:</system:String>
<system:String x:Key="MaxCurrentDownloads">同时下载数:</system:String>
<system:String x:Key="Split">最大线程数:</system:String>
<system:String x:Key="Video">视频</system:String>
<system:String x:Key="FirstVideoCodecs">优先下载的视频编码:</system:String>
@ -305,6 +310,7 @@
<system:String x:Key="Allow">确定</system:String>
<system:String x:Key="Cancel">取消</system:String>
<system:String x:Key="ConfirmReboot">此项需重启生效,您确定要重新启动吗?</system:String>
<system:String x:Key="ConfirmDelete">您确定要删除吗?</system:String>
<system:String x:Key="SelectDirectory">请选择文件夹</system:String>

@ -3,12 +3,12 @@
public class AppInfo
{
public string Name { get; } = "哔哩下载姬";
public int VersionCode { get; } = 508;
public int VersionCode { get; } = 509;
#if DEBUG
public string VersionName { get; } = "1.5.1 Debug";
public string VersionName { get; } = "1.5.2 Debug";
#else
public string VersionName { get; } = "1.5.1";
public string VersionName { get; } = "1.5.2";
#endif
}

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

@ -3,15 +3,10 @@ using DownKyi.Core.Aria2cNet.Client;
using DownKyi.Core.Aria2cNet.Client.Entity;
using DownKyi.Core.Aria2cNet.Server;
using DownKyi.Core.BiliApi.Login;
using DownKyi.Core.BiliApi.VideoStream;
using DownKyi.Core.BiliApi.VideoStream.Models;
using DownKyi.Core.Danmaku2Ass;
using DownKyi.Core.FFmpeg;
using DownKyi.Core.Logging;
using DownKyi.Core.Settings;
using DownKyi.Core.Storage;
using DownKyi.Core.Utils;
using DownKyi.Images;
using DownKyi.Models;
using DownKyi.Utils;
using DownKyi.ViewModels.DownloadManager;
@ -20,7 +15,6 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace DownKyi.Services.Download
@ -30,14 +24,6 @@ namespace DownKyi.Services.Download
/// </summary>
public class AriaDownloadService : DownloadService, IDownloadService
{
private Task workTask;
private CancellationTokenSource tokenSource;
private CancellationToken cancellationToken;
private List<Task> downloadingTasks = new List<Task>();
private readonly int retry = 5;
private readonly string nullMark = "<null>";
public AriaDownloadService(ObservableCollection<DownloadingItem> downloadingList, ObservableCollection<DownloadedItem> downloadedList) : base(downloadingList, downloadedList)
{
Tag = "AriaDownloadService";
@ -50,40 +36,9 @@ namespace DownKyi.Services.Download
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
public string DownloadAudio(DownloadingItem downloading)
public override string DownloadAudio(DownloadingItem downloading)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingAudio");
// 如果没有Dash返回null
if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; }
// 如果audio列表没有内容则返回null
if (downloading.PlayUrl.Dash.Audio == null) { return null; }
else if (downloading.PlayUrl.Dash.Audio.Count == 0) { return null; }
// 根据音频id匹配
PlayUrlDashVideo downloadAudio = null;
foreach (PlayUrlDashVideo audio in downloading.PlayUrl.Dash.Audio)
{
if (audio.Id == downloading.AudioCodec.Id)
{
downloadAudio = audio;
break;
}
}
// 避免Dolby==null及其它未知情况直接使用异常捕获
try
{
// Dolby Atmos
if (downloading.AudioCodec.Id == 30250)
{
downloadAudio = downloading.PlayUrl.Dash.Dolby.Audio[0];
}
}
catch (Exception) { }
PlayUrlDashVideo downloadAudio = BaseDownloadAudio(downloading);
return DownloadVideo(downloading, downloadAudio);
}
@ -93,29 +48,9 @@ namespace DownKyi.Services.Download
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
public string DownloadVideo(DownloadingItem downloading)
public override string DownloadVideo(DownloadingItem downloading)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo");
// 如果没有Dash返回null
if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; }
// 如果Video列表没有内容则返回null
if (downloading.PlayUrl.Dash.Video == null) { return null; }
else if (downloading.PlayUrl.Dash.Video.Count == 0) { return null; }
// 根据视频编码匹配
PlayUrlDashVideo downloadVideo = null;
foreach (PlayUrlDashVideo video in downloading.PlayUrl.Dash.Video)
{
if (video.Id == downloading.Resolution.Id && Utils.GetVideoCodecName(video.Codecs) == downloading.VideoCodecName)
{
downloadVideo = video;
break;
}
}
PlayUrlDashVideo downloadVideo = BaseDownloadVideo(downloading);
return DownloadVideo(downloading, downloadVideo);
}
@ -199,150 +134,27 @@ namespace DownKyi.Services.Download
/// 下载封面
/// </summary>
/// <param name="downloading"></param>
public string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName)
public override string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingCover");
// 下载大小
downloading.DownloadingFileSize = string.Empty;
// 下载速度
downloading.SpeedDisplay = string.Empty;
// 查询、保存封面
StorageCover storageCover = new StorageCover();
string cover = storageCover.GetCover(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, coverUrl);
if (cover == null)
{
return null;
}
// 复制图片到指定位置
try
{
File.Copy(cover, fileName, true);
// 记录本次下载的文件
if (!downloading.Downloading.DownloadFiles.ContainsKey(coverUrl))
{
downloading.Downloading.DownloadFiles.Add(coverUrl, fileName);
}
return fileName;
}
catch (Exception e)
{
Core.Utils.Debugging.Console.PrintLine(e);
LogManager.Error(Tag, e);
}
return null;
return BaseDownloadCover(downloading, coverUrl, fileName);
}
/// <summary>
/// 下载弹幕
/// </summary>
/// <param name="downloading"></param>
public string DownloadDanmaku(DownloadingItem downloading)
public override string DownloadDanmaku(DownloadingItem downloading)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingDanmaku");
// 下载大小
downloading.DownloadingFileSize = string.Empty;
// 下载速度
downloading.SpeedDisplay = string.Empty;
string title = $"{downloading.Name}";
string assFile = $"{downloading.DownloadBase.FilePath}.ass";
// 记录本次下载的文件
if (!downloading.Downloading.DownloadFiles.ContainsKey("danmaku"))
{
downloading.Downloading.DownloadFiles.Add("danmaku", assFile);
}
int screenWidth = SettingsManager.GetInstance().GetDanmakuScreenWidth();
int screenHeight = SettingsManager.GetInstance().GetDanmakuScreenHeight();
//if (SettingsManager.GetInstance().IsCustomDanmakuResolution() != AllowStatus.YES)
//{
// if (downloadingEntity.Width > 0 && downloadingEntity.Height > 0)
// {
// screenWidth = downloadingEntity.Width;
// screenHeight = downloadingEntity.Height;
// }
//}
// 字幕配置
Config subtitleConfig = new Config
{
Title = title,
ScreenWidth = screenWidth,
ScreenHeight = screenHeight,
FontName = SettingsManager.GetInstance().GetDanmakuFontName(),
BaseFontSize = SettingsManager.GetInstance().GetDanmakuFontSize(),
LineCount = SettingsManager.GetInstance().GetDanmakuLineCount(),
LayoutAlgorithm = SettingsManager.GetInstance().GetDanmakuLayoutAlgorithm().ToString("G").ToLower(), // async/sync
TuneDuration = 0,
DropOffset = 0,
BottomMargin = 0,
CustomOffset = 0
};
Core.Danmaku2Ass.Bilibili.GetInstance()
.SetTopFilter(SettingsManager.GetInstance().GetDanmakuTopFilter() == AllowStatus.YES)
.SetBottomFilter(SettingsManager.GetInstance().GetDanmakuBottomFilter() == AllowStatus.YES)
.SetScrollFilter(SettingsManager.GetInstance().GetDanmakuScrollFilter() == AllowStatus.YES)
.Create(downloading.DownloadBase.Avid, downloading.DownloadBase.Cid, subtitleConfig, assFile);
return assFile;
return BaseDownloadDanmaku(downloading);
}
/// <summary>
/// 下载字幕
/// </summary>
/// <param name="downloading"></param>
public List<string> DownloadSubtitle(DownloadingItem downloading)
public override List<string> DownloadSubtitle(DownloadingItem downloading)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingSubtitle");
// 下载大小
downloading.DownloadingFileSize = string.Empty;
// 下载速度
downloading.SpeedDisplay = string.Empty;
List<string> srtFiles = new List<string>();
var subRipTexts = VideoStream.GetSubtitle(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid);
if (subRipTexts == null)
{
return null;
}
foreach (var subRip in subRipTexts)
{
string srtFile = $"{downloading.DownloadBase.FilePath}_{subRip.LanDoc}.srt";
try
{
File.WriteAllText(srtFile, subRip.SrtString);
// 记录本次下载的文件
if (!downloading.Downloading.DownloadFiles.ContainsKey("subtitle"))
{
downloading.Downloading.DownloadFiles.Add("subtitle", srtFile);
}
srtFiles.Add(srtFile);
}
catch (Exception e)
{
Core.Utils.Debugging.Console.PrintLine("DownloadSubtitle()发生异常: {0}", e);
LogManager.Error("DownloadSubtitle()", e);
}
}
return srtFiles;
return BaseDownloadSubtitle(downloading);
}
/// <summary>
@ -352,84 +164,22 @@ namespace DownKyi.Services.Download
/// <param name="audioUid"></param>
/// <param name="videoUid"></param>
/// <returns></returns>
public string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid)
public override string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("MixedFlow");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo");
// 下载大小
downloading.DownloadingFileSize = string.Empty;
// 下载速度
downloading.SpeedDisplay = string.Empty;
if (videoUid == nullMark)
{
return null;
}
string finalFile = $"{downloading.DownloadBase.FilePath}.mp4";
if (videoUid == null)
{
finalFile = $"{downloading.DownloadBase.FilePath}.aac";
}
// 合并音视频
FFmpegHelper.MergeVideo(audioUid, videoUid, finalFile);
// 获取文件大小
if (File.Exists(finalFile))
{
FileInfo info = new FileInfo(finalFile);
downloading.FileSize = Format.FormatFileSize(info.Length);
}
else
{
downloading.FileSize = Format.FormatFileSize(0);
}
return finalFile;
return BaseMixedFlow(downloading, audioUid, videoUid);
}
/// <summary>
/// 解析视频流的下载链接
/// </summary>
/// <param name="downloading"></param>
public void Parse(DownloadingItem downloading)
public override void Parse(DownloadingItem downloading)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("Parsing");
downloading.DownloadContent = string.Empty;
// 下载大小
downloading.DownloadingFileSize = string.Empty;
// 下载速度
downloading.SpeedDisplay = string.Empty;
if (downloading.PlayUrl != null && downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED)
{
// 设置下载状态
downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING;
return;
}
// 设置下载状态
downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING;
// 解析
switch (downloading.Downloading.PlayStreamType)
{
case PlayStreamType.VIDEO:
downloading.PlayUrl = VideoStream.GetVideoPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid);
break;
case PlayStreamType.BANGUMI:
downloading.PlayUrl = VideoStream.GetBangumiPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid);
break;
case PlayStreamType.CHEESE:
downloading.PlayUrl = VideoStream.GetCheesePlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, downloading.DownloadBase.EpisodeId);
break;
default:
break;
}
BaseParse(downloading);
}
/// <summary>
@ -437,48 +187,8 @@ namespace DownKyi.Services.Download
/// </summary>
private async Task EndTask()
{
// 结束任务
tokenSource.Cancel();
await workTask;
//先简单等待一下
// 下载数据存储服务
DownloadStorageService downloadStorageService = new DownloadStorageService();
// 保存数据
foreach (DownloadingItem item in downloadingList)
{
switch (item.Downloading.DownloadStatus)
{
case DownloadStatus.NOT_STARTED:
break;
case DownloadStatus.WAIT_FOR_DOWNLOAD:
break;
case DownloadStatus.PAUSE_STARTED:
break;
case DownloadStatus.PAUSE:
break;
case DownloadStatus.DOWNLOADING:
// TODO 添加设置让用户选择重启后是否自动开始下载
item.Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD;
//item.Downloading.DownloadStatus = DownloadStatus.PAUSE;
break;
case DownloadStatus.DOWNLOAD_SUCCEED:
case DownloadStatus.DOWNLOAD_FAILED:
break;
default:
break;
}
item.Progress = 0;
downloadStorageService.UpdateDownloading(item);
}
foreach (DownloadedItem item in downloadedList)
{
downloadStorageService.UpdateDownloaded(item);
}
// 停止基本任务
await BaseEndTask();
// 关闭Aria服务器
await CloseAriaServer();
@ -500,395 +210,16 @@ namespace DownKyi.Services.Download
// 启动Aria服务器
StartAriaServer();
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
workTask = Task.Run(DoWork);
}
/// <summary>
/// 执行任务
/// </summary>
private async Task DoWork()
{
// 上次循环时正在下载的数量
int lastDownloadingCount = 0;
while (true)
{
int maxDownloading = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads();
int downloadingCount = 0;
try
{
downloadingTasks.RemoveAll((m) => m.IsCompleted);
foreach (DownloadingItem downloading in downloadingList)
{
if (downloading.Downloading.DownloadStatus == DownloadStatus.DOWNLOADING)
{
downloadingCount++;
}
}
foreach (DownloadingItem downloading in downloadingList)
{
if (downloadingCount >= maxDownloading)
{
break;
}
// 开始下载
if (downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED || downloading.Downloading.DownloadStatus == DownloadStatus.WAIT_FOR_DOWNLOAD)
{
//这里需要立刻设置状态否则如果SingleDownload没有及时执行会重复创建任务
downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING;
downloadingTasks.Add(SingleDownload(downloading));
downloadingCount++;
}
}
}
catch (InvalidOperationException e)
{
Core.Utils.Debugging.Console.PrintLine("Start DoWork()发生InvalidOperationException异常: {0}", e);
LogManager.Error("Start DoWork() InvalidOperationException", e);
}
catch (Exception e)
{
Core.Utils.Debugging.Console.PrintLine("Start DoWork()发生异常: {0}", e);
LogManager.Error("Start DoWork()", e);
}
// 判断是否该结束线程若为true跳出while循环
if (cancellationToken.IsCancellationRequested)
{
Core.Utils.Debugging.Console.PrintLine("AriaDownloadService: 下载服务结束跳出while循环");
LogManager.Debug(Tag, "下载服务结束");
break;
}
// 判断下载列表中的视频是否全部下载完成
if (lastDownloadingCount > 0 && downloadingList.Count == 0 && downloadedList.Count > 0)
{
AfterDownload();
}
lastDownloadingCount = downloadingList.Count;
// 降低CPU占用
await Task.Delay(500);
}
await Task.WhenAny(Task.WhenAll(downloadingTasks), Task.Delay(30000));
foreach (Task tsk in downloadingTasks.FindAll((m) => !m.IsCompleted))
{
Core.Utils.Debugging.Console.PrintLine("AriaDownloadService: 任务结束超时");
LogManager.Debug(Tag, "任务结束超时");
}
}
/// <summary>
/// 下载一个视频
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
private async Task SingleDownload(DownloadingItem downloading)
{
// 路径
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());
// 路径不存在则创建
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
try
{
await Task.Run(new Action(() =>
{
// 初始化
downloading.DownloadStatusTitle = string.Empty;
downloading.DownloadContent = string.Empty;
//downloading.Downloading.DownloadFiles.Clear();
// 解析并依次下载音频、视频、弹幕、字幕、封面等内容
Parse(downloading);
// 暂停
Pause(downloading);
string audioUid = null;
// 如果需要下载音频
if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"])
{
//audioUid = DownloadAudio(downloading);
for (int i = 0; i < retry; i++)
{
audioUid = DownloadAudio(downloading);
if (audioUid != null && audioUid != nullMark)
{
break;
}
}
}
if (audioUid == nullMark)
{
DownloadFailed(downloading);
return;
}
// 暂停
Pause(downloading);
string videoUid = null;
// 如果需要下载视频
if (downloading.DownloadBase.NeedDownloadContent["downloadVideo"])
{
//videoUid = DownloadVideo(downloading);
for (int i = 0; i < retry; i++)
{
videoUid = DownloadVideo(downloading);
if (videoUid != null && videoUid != nullMark)
{
break;
}
}
}
if (videoUid == nullMark)
{
DownloadFailed(downloading);
return;
}
// 暂停
Pause(downloading);
string outputDanmaku = null;
// 如果需要下载弹幕
if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"])
{
outputDanmaku = DownloadDanmaku(downloading);
}
// 暂停
Pause(downloading);
List<string> outputSubtitles = null;
// 如果需要下载字幕
if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"])
{
outputSubtitles = DownloadSubtitle(downloading);
}
// 暂停
Pause(downloading);
string outputCover = null;
string outputPageCover = null;
// 如果需要下载封面
if (downloading.DownloadBase.NeedDownloadContent["downloadCover"])
{
string fileName = $"{downloading.DownloadBase.FilePath}.{GetImageExtension(downloading.DownloadBase.PageCoverUrl)}";
// page的封面
outputPageCover = DownloadCover(downloading, downloading.DownloadBase.PageCoverUrl, fileName);
// 封面
outputCover = DownloadCover(downloading, downloading.DownloadBase.CoverUrl, $"{path}/Cover.{GetImageExtension(downloading.DownloadBase.CoverUrl)}");
}
// 暂停
Pause(downloading);
// 混流
string outputMedia = string.Empty;
if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"])
{
outputMedia = MixedFlow(downloading, audioUid, videoUid);
}
// 这里本来只有IsExist没有pause不知道怎么处理
// 是否存在
//isExist = IsExist(downloading);
//if (!isExist.Result)
//{
// return;
//}
// 检测音频、视频是否下载成功
bool isMediaSuccess = true;
if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"])
{
// 只有下载音频不下载视频时才输出aac
// 只要下载视频就输出mp4
if (File.Exists(outputMedia))
{
// 成功
isMediaSuccess = true;
}
else
{
isMediaSuccess = false;
}
}
// 检测弹幕是否下载成功
bool isDanmakuSuccess = true;
if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"])
{
if (File.Exists(outputDanmaku))
{
// 成功
isDanmakuSuccess = true;
}
else
{
isDanmakuSuccess = false;
}
}
// 检测字幕是否下载成功
bool isSubtitleSuccess = true;
if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"])
{
if (outputSubtitles == null)
{
// 为null时表示不存在字幕
}
else
{
foreach (string subtitle in outputSubtitles)
{
if (!File.Exists(subtitle))
{
// 如果有一个不存在则失败
isSubtitleSuccess = false;
}
}
}
}
// 检测封面是否下载成功
bool isCover = true;
if (downloading.DownloadBase.NeedDownloadContent["downloadCover"])
{
if (File.Exists(outputCover) || File.Exists(outputPageCover))
{
// 成功
isCover = true;
}
else
{
isCover = false;
}
}
if (!isMediaSuccess || !isDanmakuSuccess || !isSubtitleSuccess || !isCover)
{
DownloadFailed(downloading);
return;
}
// 下载完成后处理
Downloaded downloaded = new Downloaded
{
MaxSpeedDisplay = Format.FormatSpeed(downloading.Downloading.MaxSpeed),
};
// 设置完成时间
downloaded.SetFinishedTimestamp(new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds());
DownloadedItem downloadedItem = new DownloadedItem
{
DownloadBase = downloading.DownloadBase,
Downloaded = downloaded
};
App.PropertyChangeAsync(new Action(() =>
{
// 加入到下载完成list中并从下载中list去除
downloadedList.Add(downloadedItem);
downloadingList.Remove(downloading);
// 下载完成列表排序
DownloadFinishedSort finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort();
App.SortDownloadedList(finishedSort);
}));
}));
}
catch (OperationCanceledException)
{
}
}
/// <summary>
/// 下载失败后的处理
/// </summary>
/// <param name="downloading"></param>
private void DownloadFailed(DownloadingItem downloading)
{
downloading.DownloadStatusTitle = DictionaryResource.GetString("DownloadFailed");
downloading.DownloadContent = string.Empty;
downloading.DownloadingFileSize = string.Empty;
downloading.SpeedDisplay = string.Empty;
downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOAD_FAILED;
downloading.StartOrPause = ButtonIcon.Instance().Retry;
downloading.StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary");
}
/// <summary>
/// 下载完成后的操作
/// </summary>
private void AfterDownload()
{
AfterDownloadOperation operation = SettingsManager.GetInstance().GetAfterDownloadOperation();
switch (operation)
{
case AfterDownloadOperation.NONE:
// 没有操作
break;
case AfterDownloadOperation.OPEN_FOLDER:
// 打开文件夹
break;
case AfterDownloadOperation.CLOSE_APP:
// 关闭程序
App.PropertyChangeAsync(() =>
{
System.Windows.Application.Current.Shutdown();
});
break;
case AfterDownloadOperation.CLOSE_SYSTEM:
// 关机
System.Diagnostics.Process.Start("shutdown.exe", "-s");
break;
default:
break;
}
}
/// <summary>
/// 获取图片的扩展名
/// </summary>
/// <param name="coverUrl"></param>
/// <returns></returns>
private string GetImageExtension(string coverUrl)
{
if (coverUrl == null)
{
return string.Empty;
}
// 图片的扩展名
string[] temp = coverUrl.Split('.');
string fileExtension = temp[temp.Length - 1];
return fileExtension;
// 启动基本服务
BaseStart();
}
/// <summary>
/// 强制暂停
/// </summary>
/// <param name="downloading"></param>
private void Pause(DownloadingItem downloading)
/// <exception cref="OperationCanceledException"></exception>
protected override void Pause(DownloadingItem downloading)
{
cancellationToken.ThrowIfCancellationRequested();
@ -951,8 +282,8 @@ namespace DownKyi.Services.Download
ListenPort = SettingsManager.GetInstance().GetAriaListenPort(),
Token = "downkyi",
LogLevel = SettingsManager.GetInstance().GetAriaLogLevel(),
MaxConcurrentDownloads = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads(),
MaxConnectionPerServer = 16, // 最大取16
MaxConcurrentDownloads = SettingsManager.GetInstance().GetMaxCurrentDownloads(),
MaxConnectionPerServer = 8, // 最大取16
Split = SettingsManager.GetInstance().GetAriaSplit(),
//MaxTries = 5,
MinSplitSize = 10, // 10MB
@ -983,11 +314,15 @@ namespace DownKyi.Services.Download
{
// 暂停所有下载
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>
@ -1025,8 +360,8 @@ namespace DownKyi.Services.Download
//HttpProxy = $"http://{Settings.GetAriaHttpProxy()}:{Settings.GetAriaHttpProxyListenPort()}",
Dir = path,
Out = localFileName,
Header = $"cookie: {LoginHelper.GetLoginInfoCookiesString()}\nreferer: https://www.bilibili.com",
UseHead = "true",
//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",
};
@ -1088,6 +423,7 @@ namespace DownKyi.Services.Download
if (video == null) { return; }
// 下载进度百分比
float percent = 0;
if (totalLength != 0)
{

@ -0,0 +1,363 @@
using DownKyi.Core.BiliApi.Login;
using DownKyi.Core.BiliApi.VideoStream.Models;
using DownKyi.Core.Downloader;
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.Net;
using System.Threading;
using System.Threading.Tasks;
namespace DownKyi.Services.Download
{
public class BuiltinDownloadService : DownloadService, IDownloadService
{
public BuiltinDownloadService(ObservableCollection<DownloadingItem> downloadingList, ObservableCollection<DownloadedItem> downloadedList) : base(downloadingList, downloadedList)
{
Tag = "BuiltinDownloadService";
}
#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;
}
// 开始下载
var downloadStatus = DownloadByBuiltin(downloading, urls, path, fileName);
if (downloadStatus)
{
downloading.Downloading.DownloadedFiles.Add(key);
downloading.Downloading.Gid = null;
return Path.Combine(path, fileName);
}
else
{
return nullMark;
}
}
#endregion
/// <summary>
/// 下载封面
/// </summary>
/// <param name="downloading"></param>
/// <param name="coverUrl"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public override string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName)
{
return BaseDownloadCover(downloading, coverUrl, fileName);
}
/// <summary>
/// 下载弹幕
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
public override string DownloadDanmaku(DownloadingItem downloading)
{
return BaseDownloadDanmaku(downloading);
}
/// <summary>
/// 下载字幕
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
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)
{
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();
}
/// <summary>
/// 停止下载服务
/// </summary>
public void End()
{
Task.Run(EndTask).Wait();
}
public void Start()
{
// 启动基本服务
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)
{
throw new OperationCanceledException("Task is deleted");
}
}
/// <summary>
/// 是否存在于下载列表中
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
private bool IsExist(DownloadingItem downloading)
{
bool isExist = downloadingList.Contains(downloading);
if (isExist)
{
return true;
}
else
{
return false;
}
}
#region 内建下载器
/// <summary>
/// 下载文件
/// </summary>
/// <param name="downloading"></param>
/// <param name="urls"></param>
/// <param name="path"></param>
/// <param name="localFileName"></param>
/// <returns></returns>
private bool DownloadByBuiltin(DownloadingItem downloading, List<string> urls, string path, string localFileName)
{
// path已斜杠结尾去掉斜杠
path = path.TrimEnd('/').TrimEnd('\\');
foreach (var url in urls)
{
// 创建下载器
var mtd = new MultiThreadDownloader(url,
Environment.GetEnvironmentVariable("temp"),
Path.Combine(path, localFileName),
SettingsManager.GetInstance().GetSplit());
// 配置网络请求
mtd.Configure(req =>
{
req.CookieContainer = LoginHelper.GetLoginInfoCookies();
req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36";
req.Referer = "https://www.bilibili.com";
req.Headers.Add("Origin", "https://www.bilibili.com");
if (SettingsManager.GetInstance().IsHttpProxy() == AllowStatus.YES)
{
req.Proxy = new WebProxy(SettingsManager.GetInstance().GetHttpProxy(),
SettingsManager.GetInstance().GetHttpProxyListenPort());
}
});
// 下载进度回调
mtd.TotalProgressChanged += (sender, e) =>
{
try
{
// 状态更新
var downloader = sender as MultiThreadDownloader;
// 下载进度百分比
float percent = downloader.TotalProgress;
// 根据进度判断本次是否需要更新UI
if (Math.Abs(percent - downloading.Progress) < 0.01) { return; }
if (Math.Abs(percent - downloading.Progress) > 5) { return; }
// 下载进度
downloading.Progress = percent;
// 下载大小
downloading.DownloadingFileSize = Format.FormatFileSize(downloader.TotalBytesReceived) + "/" + Format.FormatFileSize(downloader.Size);
// 下载速度
long speed = (long)downloader.TotalSpeedInBytes;
// 下载速度显示
downloading.SpeedDisplay = Format.FormatSpeed(speed);
// 最大下载速度
if (downloading.Downloading.MaxSpeed < speed)
{
downloading.Downloading.MaxSpeed = speed;
}
}
catch (InvalidOperationException ex)
{
Core.Utils.Debugging.Console.PrintLine($"{Tag}.DownloadByBuiltin()发生InvalidOperationException异常: {0}", ex);
LogManager.Error($"{Tag}.DownloadByBuiltin()", ex);
}
catch (Exception ex)
{
Core.Utils.Debugging.Console.PrintLine($"{Tag}.DownloadByBuiltin()发生异常: {0}", ex);
LogManager.Error($"{Tag}.DownloadByBuiltin()", ex);
}
};
// 文件合并完成回调
bool isComplete = false;
mtd.FileMergedComplete += (sender, e) =>
{
// 跳出循环
if (File.Exists(Path.Combine(path, localFileName))) { isComplete = true; }
};
// 开始下载
mtd.StartAsync();
// 阻塞当前任务,监听暂停事件
while (!isComplete)
{
cancellationToken.ThrowIfCancellationRequested();
switch (downloading.Downloading.DownloadStatus)
{
case DownloadStatus.PAUSE:
// 暂停下载
mtd.Pause();
// 通知UI并阻塞当前线程
Pause(downloading);
break;
case DownloadStatus.DOWNLOADING:
break;
}
Thread.Sleep(100);
}
return isComplete;
}
return false;
}
#endregion
}
}

@ -1,15 +1,39 @@
using DownKyi.ViewModels.DownloadManager;
using DownKyi.Core.BiliApi.VideoStream;
using DownKyi.Core.BiliApi.VideoStream.Models;
using DownKyi.Core.Danmaku2Ass;
using DownKyi.Core.FFmpeg;
using DownKyi.Core.Logging;
using DownKyi.Core.Settings;
using DownKyi.Core.Storage;
using DownKyi.Core.Utils;
using DownKyi.Images;
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.Threading;
using System.Threading.Tasks;
namespace DownKyi.Services.Download
{
public class DownloadService
public abstract class DownloadService
{
protected string Tag = "DownloadService";
protected ObservableCollection<DownloadingItem> downloadingList;
protected ObservableCollection<DownloadedItem> downloadedList;
protected Task workTask;
protected CancellationTokenSource tokenSource;
protected CancellationToken cancellationToken;
protected List<Task> downloadingTasks = new List<Task>();
protected readonly int retry = 5;
protected readonly string nullMark = "<null>";
/// <summary>
/// 初始化
/// </summary>
@ -21,5 +45,747 @@ namespace DownKyi.Services.Download
this.downloadedList = downloadedList;
}
protected PlayUrlDashVideo BaseDownloadAudio(DownloadingItem downloading)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingAudio");
// 下载大小
downloading.DownloadingFileSize = string.Empty;
downloading.Progress = 0;
// 下载速度
downloading.SpeedDisplay = string.Empty;
// 如果没有Dash返回null
if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; }
// 如果audio列表没有内容则返回null
if (downloading.PlayUrl.Dash.Audio == null) { return null; }
else if (downloading.PlayUrl.Dash.Audio.Count == 0) { return null; }
// 根据音频id匹配
PlayUrlDashVideo downloadAudio = null;
foreach (PlayUrlDashVideo audio in downloading.PlayUrl.Dash.Audio)
{
if (audio.Id == downloading.AudioCodec.Id)
{
downloadAudio = audio;
break;
}
}
// 避免Dolby==null及其它未知情况直接使用异常捕获
try
{
// Dolby Atmos
if (downloading.AudioCodec.Id == 30250)
{
downloadAudio = downloading.PlayUrl.Dash.Dolby.Audio[0];
}
}
catch (Exception) { }
return downloadAudio;
}
protected PlayUrlDashVideo BaseDownloadVideo(DownloadingItem downloading)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo");
// 下载大小
downloading.DownloadingFileSize = string.Empty;
downloading.Progress = 0;
// 下载速度
downloading.SpeedDisplay = string.Empty;
// 如果没有Dash返回null
if (downloading.PlayUrl == null || downloading.PlayUrl.Dash == null) { return null; }
// 如果Video列表没有内容则返回null
if (downloading.PlayUrl.Dash.Video == null) { return null; }
else if (downloading.PlayUrl.Dash.Video.Count == 0) { return null; }
// 根据视频编码匹配
PlayUrlDashVideo downloadVideo = null;
foreach (PlayUrlDashVideo video in downloading.PlayUrl.Dash.Video)
{
if (video.Id == downloading.Resolution.Id && Utils.GetVideoCodecName(video.Codecs) == downloading.VideoCodecName)
{
downloadVideo = video;
break;
}
}
return downloadVideo;
}
protected string BaseDownloadCover(DownloadingItem downloading, string coverUrl, string fileName)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingCover");
// 下载大小
downloading.DownloadingFileSize = string.Empty;
// 下载速度
downloading.SpeedDisplay = string.Empty;
// 查询、保存封面
StorageCover storageCover = new StorageCover();
string cover = storageCover.GetCover(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, coverUrl);
if (cover == null)
{
return null;
}
// 复制图片到指定位置
try
{
File.Copy(cover, fileName, true);
// 记录本次下载的文件
if (!downloading.Downloading.DownloadFiles.ContainsKey(coverUrl))
{
downloading.Downloading.DownloadFiles.Add(coverUrl, fileName);
}
return fileName;
}
catch (Exception e)
{
Core.Utils.Debugging.Console.PrintLine($"{Tag}.DownloadCover()发生异常: {0}", e);
LogManager.Error($"{Tag}.DownloadCover()", e);
}
return null;
}
protected string BaseDownloadDanmaku(DownloadingItem downloading)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingDanmaku");
// 下载大小
downloading.DownloadingFileSize = string.Empty;
// 下载速度
downloading.SpeedDisplay = string.Empty;
string title = $"{downloading.Name}";
string assFile = $"{downloading.DownloadBase.FilePath}.ass";
// 记录本次下载的文件
if (!downloading.Downloading.DownloadFiles.ContainsKey("danmaku"))
{
downloading.Downloading.DownloadFiles.Add("danmaku", assFile);
}
int screenWidth = SettingsManager.GetInstance().GetDanmakuScreenWidth();
int screenHeight = SettingsManager.GetInstance().GetDanmakuScreenHeight();
//if (SettingsManager.GetInstance().IsCustomDanmakuResolution() != AllowStatus.YES)
//{
// if (downloadingEntity.Width > 0 && downloadingEntity.Height > 0)
// {
// screenWidth = downloadingEntity.Width;
// screenHeight = downloadingEntity.Height;
// }
//}
// 字幕配置
Config subtitleConfig = new Config
{
Title = title,
ScreenWidth = screenWidth,
ScreenHeight = screenHeight,
FontName = SettingsManager.GetInstance().GetDanmakuFontName(),
BaseFontSize = SettingsManager.GetInstance().GetDanmakuFontSize(),
LineCount = SettingsManager.GetInstance().GetDanmakuLineCount(),
LayoutAlgorithm = SettingsManager.GetInstance().GetDanmakuLayoutAlgorithm().ToString("G").ToLower(), // async/sync
TuneDuration = 0,
DropOffset = 0,
BottomMargin = 0,
CustomOffset = 0
};
Core.Danmaku2Ass.Bilibili.GetInstance()
.SetTopFilter(SettingsManager.GetInstance().GetDanmakuTopFilter() == AllowStatus.YES)
.SetBottomFilter(SettingsManager.GetInstance().GetDanmakuBottomFilter() == AllowStatus.YES)
.SetScrollFilter(SettingsManager.GetInstance().GetDanmakuScrollFilter() == AllowStatus.YES)
.Create(downloading.DownloadBase.Avid, downloading.DownloadBase.Cid, subtitleConfig, assFile);
return assFile;
}
protected List<string> BaseDownloadSubtitle(DownloadingItem downloading)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingSubtitle");
// 下载大小
downloading.DownloadingFileSize = string.Empty;
// 下载速度
downloading.SpeedDisplay = string.Empty;
List<string> srtFiles = new List<string>();
var subRipTexts = VideoStream.GetSubtitle(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid);
if (subRipTexts == null)
{
return null;
}
foreach (var subRip in subRipTexts)
{
string srtFile = $"{downloading.DownloadBase.FilePath}_{subRip.LanDoc}.srt";
try
{
File.WriteAllText(srtFile, subRip.SrtString);
// 记录本次下载的文件
if (!downloading.Downloading.DownloadFiles.ContainsKey("subtitle"))
{
downloading.Downloading.DownloadFiles.Add("subtitle", srtFile);
}
srtFiles.Add(srtFile);
}
catch (Exception e)
{
Core.Utils.Debugging.Console.PrintLine($"{Tag}.DownloadSubtitle()发生异常: {0}", e);
LogManager.Error($"{Tag}.DownloadSubtitle()", e);
}
}
return srtFiles;
}
protected string BaseMixedFlow(DownloadingItem downloading, string audioUid, string videoUid)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("MixedFlow");
downloading.DownloadContent = DictionaryResource.GetString("DownloadingVideo");
// 下载大小
downloading.DownloadingFileSize = string.Empty;
// 下载速度
downloading.SpeedDisplay = string.Empty;
//if (videoUid == nullMark)
//{
// return null;
//}
string finalFile = $"{downloading.DownloadBase.FilePath}.mp4";
if (videoUid == null)
{
finalFile = $"{downloading.DownloadBase.FilePath}.aac";
}
// 合并音视频
FFmpegHelper.MergeVideo(audioUid, videoUid, finalFile);
// 获取文件大小
if (File.Exists(finalFile))
{
FileInfo info = new FileInfo(finalFile);
downloading.FileSize = Format.FormatFileSize(info.Length);
}
else
{
downloading.FileSize = Format.FormatFileSize(0);
}
return finalFile;
}
protected void BaseParse(DownloadingItem downloading)
{
// 更新状态显示
downloading.DownloadStatusTitle = DictionaryResource.GetString("Parsing");
downloading.DownloadContent = string.Empty;
// 下载大小
downloading.DownloadingFileSize = string.Empty;
downloading.Progress = 0;
// 下载速度
downloading.SpeedDisplay = string.Empty;
if (downloading.PlayUrl != null && downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED)
{
// 设置下载状态
downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING;
return;
}
// 设置下载状态
downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING;
// 解析
switch (downloading.Downloading.PlayStreamType)
{
case PlayStreamType.VIDEO:
downloading.PlayUrl = VideoStream.GetVideoPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid);
break;
case PlayStreamType.BANGUMI:
downloading.PlayUrl = VideoStream.GetBangumiPlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid);
break;
case PlayStreamType.CHEESE:
downloading.PlayUrl = VideoStream.GetCheesePlayUrl(downloading.DownloadBase.Avid, downloading.DownloadBase.Bvid, downloading.DownloadBase.Cid, downloading.DownloadBase.EpisodeId);
break;
default:
break;
}
}
/// <summary>
/// 执行任务
/// </summary>
protected async Task DoWork()
{
// 上次循环时正在下载的数量
int lastDownloadingCount = 0;
while (true)
{
int maxDownloading = SettingsManager.GetInstance().GetMaxCurrentDownloads();
int downloadingCount = 0;
try
{
downloadingTasks.RemoveAll((m) => m.IsCompleted);
foreach (DownloadingItem downloading in downloadingList)
{
if (downloading.Downloading.DownloadStatus == DownloadStatus.DOWNLOADING)
{
downloadingCount++;
}
}
foreach (DownloadingItem downloading in downloadingList)
{
if (downloadingCount >= maxDownloading)
{
break;
}
// 开始下载
if (downloading.Downloading.DownloadStatus == DownloadStatus.NOT_STARTED || downloading.Downloading.DownloadStatus == DownloadStatus.WAIT_FOR_DOWNLOAD)
{
//这里需要立刻设置状态否则如果SingleDownload没有及时执行会重复创建任务
downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOADING;
downloadingTasks.Add(SingleDownload(downloading));
downloadingCount++;
}
}
}
catch (InvalidOperationException e)
{
Core.Utils.Debugging.Console.PrintLine($"{Tag}.DoWork()发生InvalidOperationException异常: {0}", e);
LogManager.Error($"{Tag}.DoWork() InvalidOperationException", e);
}
catch (Exception e)
{
Core.Utils.Debugging.Console.PrintLine($"{Tag}.DoWork()发生异常: {0}", e);
LogManager.Error($"{Tag}.DoWork()", e);
}
// 判断是否该结束线程若为true跳出while循环
if (cancellationToken.IsCancellationRequested)
{
Core.Utils.Debugging.Console.PrintLine($"{Tag}.DoWork() 下载服务结束跳出while循环");
LogManager.Debug($"{Tag}.DoWork()", "下载服务结束");
break;
}
// 判断下载列表中的视频是否全部下载完成
if (lastDownloadingCount > 0 && downloadingList.Count == 0 && downloadedList.Count > 0)
{
AfterDownload();
}
lastDownloadingCount = downloadingList.Count;
// 降低CPU占用
await Task.Delay(500);
}
await Task.WhenAny(Task.WhenAll(downloadingTasks), Task.Delay(30000));
foreach (Task tsk in downloadingTasks.FindAll((m) => !m.IsCompleted))
{
Core.Utils.Debugging.Console.PrintLine($"{Tag}.DoWork() 任务结束超时");
LogManager.Debug($"{Tag}.DoWork()", "任务结束超时");
}
}
/// <summary>
/// 下载一个视频
/// </summary>
/// <param name="downloading"></param>
/// <returns></returns>
private async Task SingleDownload(DownloadingItem downloading)
{
// 路径
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());
// 路径不存在则创建
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
try
{
await Task.Run(new Action(() =>
{
// 初始化
downloading.DownloadStatusTitle = string.Empty;
downloading.DownloadContent = string.Empty;
//downloading.Downloading.DownloadFiles.Clear();
// 解析并依次下载音频、视频、弹幕、字幕、封面等内容
Parse(downloading);
// 暂停
Pause(downloading);
string audioUid = null;
// 如果需要下载音频
if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"])
{
//audioUid = DownloadAudio(downloading);
for (int i = 0; i < retry; i++)
{
audioUid = DownloadAudio(downloading);
if (audioUid != null && audioUid != nullMark)
{
break;
}
}
}
if (audioUid == nullMark)
{
DownloadFailed(downloading);
return;
}
// 暂停
Pause(downloading);
string videoUid = null;
// 如果需要下载视频
if (downloading.DownloadBase.NeedDownloadContent["downloadVideo"])
{
//videoUid = DownloadVideo(downloading);
for (int i = 0; i < retry; i++)
{
videoUid = DownloadVideo(downloading);
if (videoUid != null && videoUid != nullMark)
{
break;
}
}
}
if (videoUid == nullMark)
{
DownloadFailed(downloading);
return;
}
// 暂停
Pause(downloading);
string outputDanmaku = null;
// 如果需要下载弹幕
if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"])
{
outputDanmaku = DownloadDanmaku(downloading);
}
// 暂停
Pause(downloading);
List<string> outputSubtitles = null;
// 如果需要下载字幕
if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"])
{
outputSubtitles = DownloadSubtitle(downloading);
}
// 暂停
Pause(downloading);
string outputCover = null;
string outputPageCover = null;
// 如果需要下载封面
if (downloading.DownloadBase.NeedDownloadContent["downloadCover"])
{
string fileName = $"{downloading.DownloadBase.FilePath}.{GetImageExtension(downloading.DownloadBase.PageCoverUrl)}";
// page的封面
outputPageCover = DownloadCover(downloading, downloading.DownloadBase.PageCoverUrl, fileName);
// 封面
outputCover = DownloadCover(downloading, downloading.DownloadBase.CoverUrl, $"{path}/Cover.{GetImageExtension(downloading.DownloadBase.CoverUrl)}");
}
// 暂停
Pause(downloading);
// 混流
string outputMedia = string.Empty;
if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"])
{
outputMedia = MixedFlow(downloading, audioUid, videoUid);
}
// 这里本来只有IsExist没有pause不知道怎么处理
// 是否存在
//isExist = IsExist(downloading);
//if (!isExist.Result)
//{
// return;
//}
// 检测音频、视频是否下载成功
bool isMediaSuccess = true;
if (downloading.DownloadBase.NeedDownloadContent["downloadAudio"] || downloading.DownloadBase.NeedDownloadContent["downloadVideo"])
{
// 只有下载音频不下载视频时才输出aac
// 只要下载视频就输出mp4
if (File.Exists(outputMedia))
{
// 成功
isMediaSuccess = true;
}
else
{
isMediaSuccess = false;
}
}
// 检测弹幕是否下载成功
bool isDanmakuSuccess = true;
if (downloading.DownloadBase.NeedDownloadContent["downloadDanmaku"])
{
if (File.Exists(outputDanmaku))
{
// 成功
isDanmakuSuccess = true;
}
else
{
isDanmakuSuccess = false;
}
}
// 检测字幕是否下载成功
bool isSubtitleSuccess = true;
if (downloading.DownloadBase.NeedDownloadContent["downloadSubtitle"])
{
if (outputSubtitles == null)
{
// 为null时表示不存在字幕
}
else
{
foreach (string subtitle in outputSubtitles)
{
if (!File.Exists(subtitle))
{
// 如果有一个不存在则失败
isSubtitleSuccess = false;
}
}
}
}
// 检测封面是否下载成功
bool isCover = true;
if (downloading.DownloadBase.NeedDownloadContent["downloadCover"])
{
if (File.Exists(outputCover) || File.Exists(outputPageCover))
{
// 成功
isCover = true;
}
else
{
isCover = false;
}
}
if (!isMediaSuccess || !isDanmakuSuccess || !isSubtitleSuccess || !isCover)
{
DownloadFailed(downloading);
return;
}
// 下载完成后处理
Downloaded downloaded = new Downloaded
{
MaxSpeedDisplay = Format.FormatSpeed(downloading.Downloading.MaxSpeed),
};
// 设置完成时间
downloaded.SetFinishedTimestamp(new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds());
DownloadedItem downloadedItem = new DownloadedItem
{
DownloadBase = downloading.DownloadBase,
Downloaded = downloaded
};
App.PropertyChangeAsync(new Action(() =>
{
// 加入到下载完成list中并从下载中list去除
downloadedList.Add(downloadedItem);
downloadingList.Remove(downloading);
// 下载完成列表排序
DownloadFinishedSort finishedSort = SettingsManager.GetInstance().GetDownloadFinishedSort();
App.SortDownloadedList(finishedSort);
}));
}));
}
catch (OperationCanceledException e)
{
Core.Utils.Debugging.Console.PrintLine(Tag, e.ToString());
LogManager.Debug(Tag, e.Message);
}
}
/// <summary>
/// 下载失败后的处理
/// </summary>
/// <param name="downloading"></param>
protected void DownloadFailed(DownloadingItem downloading)
{
downloading.DownloadStatusTitle = DictionaryResource.GetString("DownloadFailed");
downloading.DownloadContent = string.Empty;
downloading.DownloadingFileSize = string.Empty;
downloading.SpeedDisplay = string.Empty;
downloading.Progress = 0;
downloading.Downloading.DownloadStatus = DownloadStatus.DOWNLOAD_FAILED;
downloading.StartOrPause = ButtonIcon.Instance().Retry;
downloading.StartOrPause.Fill = DictionaryResource.GetColor("ColorPrimary");
}
/// <summary>
/// 获取图片的扩展名
/// </summary>
/// <param name="coverUrl"></param>
/// <returns></returns>
protected string GetImageExtension(string coverUrl)
{
if (coverUrl == null)
{
return string.Empty;
}
// 图片的扩展名
string[] temp = coverUrl.Split('.');
string fileExtension = temp[temp.Length - 1];
return fileExtension;
}
/// <summary>
/// 下载完成后的操作
/// </summary>
protected void AfterDownload()
{
AfterDownloadOperation operation = SettingsManager.GetInstance().GetAfterDownloadOperation();
switch (operation)
{
case AfterDownloadOperation.NONE:
// 没有操作
break;
case AfterDownloadOperation.OPEN_FOLDER:
// 打开文件夹
break;
case AfterDownloadOperation.CLOSE_APP:
// 关闭程序
App.PropertyChangeAsync(() =>
{
System.Windows.Application.Current.Shutdown();
});
break;
case AfterDownloadOperation.CLOSE_SYSTEM:
// 关机
System.Diagnostics.Process.Start("shutdown.exe", "-s");
break;
default:
break;
}
}
/// <summary>
/// 停止基本下载服务(转换await和Task.Wait两种调用形式)
/// </summary>
protected async Task BaseEndTask()
{
// 结束任务
tokenSource.Cancel();
await workTask;
//先简单等待一下
// 下载数据存储服务
DownloadStorageService downloadStorageService = new DownloadStorageService();
// 保存数据
foreach (DownloadingItem item in downloadingList)
{
switch (item.Downloading.DownloadStatus)
{
case DownloadStatus.NOT_STARTED:
break;
case DownloadStatus.WAIT_FOR_DOWNLOAD:
break;
case DownloadStatus.PAUSE_STARTED:
break;
case DownloadStatus.PAUSE:
break;
case DownloadStatus.DOWNLOADING:
// TODO 添加设置让用户选择重启后是否自动开始下载
item.Downloading.DownloadStatus = DownloadStatus.WAIT_FOR_DOWNLOAD;
//item.Downloading.DownloadStatus = DownloadStatus.PAUSE;
break;
case DownloadStatus.DOWNLOAD_SUCCEED:
case DownloadStatus.DOWNLOAD_FAILED:
break;
default:
break;
}
item.Progress = 0;
downloadStorageService.UpdateDownloading(item);
}
foreach (DownloadedItem item in downloadedList)
{
downloadStorageService.UpdateDownloaded(item);
}
}
/// <summary>
/// 启动基本下载服务
/// </summary>
protected void BaseStart()
{
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
workTask = Task.Run(DoWork);
}
#region 抽象接口函数
public abstract void Parse(DownloadingItem downloading);
public abstract string DownloadAudio(DownloadingItem downloading);
public abstract string DownloadVideo(DownloadingItem downloading);
public abstract string DownloadDanmaku(DownloadingItem downloading);
public abstract List<string> DownloadSubtitle(DownloadingItem downloading);
public abstract string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName);
public abstract string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid);
protected abstract void Pause(DownloadingItem downloading);
#endregion
}
}

@ -1,4 +1,5 @@
using DownKyi.Core.Settings;
using DownKyi.Core.Logging;
using DownKyi.Core.Settings;
using DownKyi.Services;
using DownKyi.Utils;
using Prism.Commands;
@ -40,21 +41,12 @@ namespace DownKyi.ViewModels.DownloadManager
{
// 初始化DownloadedList
DownloadedList = App.DownloadedList;
DownloadedList.CollectionChanged += new NotifyCollectionChangedEventHandler(async (object sender, NotifyCollectionChangedEventArgs e) =>
DownloadedList.CollectionChanged += new NotifyCollectionChangedEventHandler((sender, e) =>
{
await Task.Run(() =>
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in DownloadedList)
{
if (item != null && item.DialogService == null)
{
item.DialogService = dialogService;
}
}
}
});
SetDialogService();
}
});
SetDialogService();
@ -144,16 +136,24 @@ namespace DownKyi.ViewModels.DownloadManager
private async void SetDialogService()
{
await Task.Run(() =>
try
{
foreach (var item in DownloadedList)
await Task.Run(() =>
{
if (item != null && item.DialogService == null)
foreach (var item in DownloadedList)
{
item.DialogService = dialogService;
if (item != null && item.DialogService == null)
{
item.DialogService = dialogService;
}
}
}
});
});
}
catch (Exception e)
{
Core.Utils.Debugging.Console.PrintLine("SetDialogService()发生异常: {0}", e);
LogManager.Error($"{Tag}.SetDialogService()", e);
}
}
public override void OnNavigatedFrom(NavigationContext navigationContext)

@ -1,4 +1,5 @@
using DownKyi.Images;
using DownKyi.Core.Logging;
using DownKyi.Images;
using DownKyi.Models;
using DownKyi.Services;
using DownKyi.Utils;
@ -34,21 +35,12 @@ namespace DownKyi.ViewModels.DownloadManager
{
// 初始化DownloadingList
DownloadingList = App.DownloadingList;
DownloadingList.CollectionChanged += new NotifyCollectionChangedEventHandler(async (object sender, NotifyCollectionChangedEventArgs e) =>
DownloadingList.CollectionChanged += new NotifyCollectionChangedEventHandler((sender, e) =>
{
await Task.Run(() =>
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var item in DownloadingList)
{
if (item != null && item.DialogService == null)
{
item.DialogService = dialogService;
}
}
}
});
SetDialogService();
}
});
SetDialogService();
@ -182,16 +174,24 @@ namespace DownKyi.ViewModels.DownloadManager
private async void SetDialogService()
{
await Task.Run(() =>
try
{
foreach (var item in DownloadingList)
await Task.Run(() =>
{
if (item != null && item.DialogService == null)
foreach (var item in DownloadingList)
{
item.DialogService = dialogService;
if (item != null && item.DialogService == null)
{
item.DialogService = dialogService;
}
}
}
});
});
}
catch (Exception e)
{
Core.Utils.Debugging.Console.PrintLine("SetDialogService()发生异常: {0}", e);
LogManager.Error($"{Tag}.SetDialogService()", e);
}
}
public override void OnNavigatedFrom(NavigationContext navigationContext)

@ -2,10 +2,12 @@
using DownKyi.Core.Settings;
using DownKyi.Core.Utils.Validator;
using DownKyi.Events;
using DownKyi.Services;
using DownKyi.Utils;
using Prism.Commands;
using Prism.Events;
using Prism.Regions;
using Prism.Services.Dialogs;
using System.Collections.Generic;
namespace DownKyi.ViewModels.Settings
@ -18,111 +20,182 @@ namespace DownKyi.ViewModels.Settings
#region 页面属性申明
private bool builtin;
public bool Builtin
{
get => builtin;
set => SetProperty(ref builtin, value);
}
private bool aria2c;
public bool Aria2c
{
get => aria2c;
set => SetProperty(ref aria2c, value);
}
private List<int> maxCurrentDownloads;
public List<int> MaxCurrentDownloads
{
get => maxCurrentDownloads;
set => SetProperty(ref maxCurrentDownloads, value);
}
private int selectedMaxCurrentDownload;
public int SelectedMaxCurrentDownload
{
get => selectedMaxCurrentDownload;
set => SetProperty(ref selectedMaxCurrentDownload, value);
}
private List<int> splits;
public List<int> Splits
{
get => splits;
set => SetProperty(ref splits, value);
}
private int selectedSplit;
public int SelectedSplit
{
get => selectedSplit;
set => SetProperty(ref selectedSplit, value);
}
private bool isHttpProxy;
public bool IsHttpProxy
{
get => isHttpProxy;
set => SetProperty(ref isHttpProxy, value);
}
private string httpProxy;
public string HttpProxy
{
get => httpProxy;
set => SetProperty(ref httpProxy, value);
}
private int httpProxyPort;
public int HttpProxyPort
{
get => httpProxyPort;
set => SetProperty(ref httpProxyPort, value);
}
private int ariaListenPort;
public int AriaListenPort
{
get { return ariaListenPort; }
set { SetProperty(ref ariaListenPort, value); }
get => ariaListenPort;
set => SetProperty(ref ariaListenPort, value);
}
private List<string> ariaLogLevels;
public List<string> AriaLogLevels
{
get { return ariaLogLevels; }
set { SetProperty(ref ariaLogLevels, value); }
get => ariaLogLevels;
set => SetProperty(ref ariaLogLevels, value);
}
private string selectedAriaLogLevel;
public string SelectedAriaLogLevel
{
get { return selectedAriaLogLevel; }
set { SetProperty(ref selectedAriaLogLevel, value); }
get => selectedAriaLogLevel;
set => SetProperty(ref selectedAriaLogLevel, value);
}
private List<int> ariaMaxConcurrentDownloads;
public List<int> AriaMaxConcurrentDownloads
{
get { return ariaMaxConcurrentDownloads; }
set { SetProperty(ref ariaMaxConcurrentDownloads, value); }
get => ariaMaxConcurrentDownloads;
set => SetProperty(ref ariaMaxConcurrentDownloads, value);
}
private int selectedAriaMaxConcurrentDownload;
public int SelectedAriaMaxConcurrentDownload
{
get { return selectedAriaMaxConcurrentDownload; }
set { SetProperty(ref selectedAriaMaxConcurrentDownload, value); }
get => selectedAriaMaxConcurrentDownload;
set => SetProperty(ref selectedAriaMaxConcurrentDownload, value);
}
private List<int> ariaSplits;
public List<int> AriaSplits
{
get { return ariaSplits; }
set { SetProperty(ref ariaSplits, value); }
get => ariaSplits;
set => SetProperty(ref ariaSplits, value);
}
private int selectedAriaSplit;
public int SelectedAriaSplit
{
get { return selectedAriaSplit; }
set { SetProperty(ref selectedAriaSplit, value); }
get => selectedAriaSplit;
set => SetProperty(ref selectedAriaSplit, value);
}
private int ariaMaxOverallDownloadLimit;
public int AriaMaxOverallDownloadLimit
{
get { return ariaMaxOverallDownloadLimit; }
set { SetProperty(ref ariaMaxOverallDownloadLimit, value); }
get => ariaMaxOverallDownloadLimit;
set => SetProperty(ref ariaMaxOverallDownloadLimit, value);
}
private int ariaMaxDownloadLimit;
public int AriaMaxDownloadLimit
{
get { return ariaMaxDownloadLimit; }
set { SetProperty(ref ariaMaxDownloadLimit, value); }
get => ariaMaxDownloadLimit;
set => SetProperty(ref ariaMaxDownloadLimit, value);
}
private bool isAriaHttpProxy;
public bool IsAriaHttpProxy
{
get { return isAriaHttpProxy; }
set { SetProperty(ref isAriaHttpProxy, value); }
get => isAriaHttpProxy;
set => SetProperty(ref isAriaHttpProxy, value);
}
private string ariaHttpProxy;
public string AriaHttpProxy
{
get { return ariaHttpProxy; }
set { SetProperty(ref ariaHttpProxy, value); }
get => ariaHttpProxy;
set => SetProperty(ref ariaHttpProxy, value);
}
private int ariaHttpProxyPort;
public int AriaHttpProxyPort
{
get { return ariaHttpProxyPort; }
set { SetProperty(ref ariaHttpProxyPort, value); }
get => ariaHttpProxyPort;
set => SetProperty(ref ariaHttpProxyPort, value);
}
private List<string> ariaFileAllocations;
public List<string> AriaFileAllocations
{
get { return ariaFileAllocations; }
set { SetProperty(ref ariaFileAllocations, value); }
get => ariaFileAllocations;
set => SetProperty(ref ariaFileAllocations, value);
}
private string selectedAriaFileAllocation;
public string SelectedAriaFileAllocation
{
get { return selectedAriaFileAllocation; }
set { SetProperty(ref selectedAriaFileAllocation, value); }
get => selectedAriaFileAllocation;
set => SetProperty(ref selectedAriaFileAllocation, value);
}
#endregion
public ViewNetworkViewModel(IEventAggregator eventAggregator) : base(eventAggregator)
public ViewNetworkViewModel(IEventAggregator eventAggregator, IDialogService dialogService) : base(eventAggregator, dialogService)
{
#region 属性初始化
// builtin同时下载数
MaxCurrentDownloads = new List<int>();
for (int i = 1; i <= 10; i++) { MaxCurrentDownloads.Add(i); }
// builtin最大线程数
Splits = new List<int>();
for (int i = 1; i <= 10; i++) { Splits.Add(i); }
// Aria的日志等级
AriaLogLevels = new List<string>
{
@ -154,7 +227,7 @@ namespace DownKyi.ViewModels.Settings
}
/// <summary>
/// 导航到VideoDetail页面时执行
/// 导航到页面时执行
/// </summary>
/// <param name="navigationContext"></param>
public override void OnNavigatedTo(NavigationContext navigationContext)
@ -163,6 +236,36 @@ namespace DownKyi.ViewModels.Settings
isOnNavigatedTo = true;
// 选择下载器
var downloader = SettingsManager.GetInstance().GetDownloader();
switch (downloader)
{
case Downloader.NOT_SET:
break;
case Downloader.BUILT_IN:
Builtin = true;
break;
case Downloader.ARIA:
Aria2c = true;
break;
}
// builtin同时下载数
SelectedMaxCurrentDownload = SettingsManager.GetInstance().GetMaxCurrentDownloads();
// builtin最大线程数
SelectedSplit = SettingsManager.GetInstance().GetSplit();
// 是否开启builtin http代理
AllowStatus isHttpProxy = SettingsManager.GetInstance().IsHttpProxy();
IsHttpProxy = isHttpProxy == AllowStatus.YES;
// builtin的http代理的地址
HttpProxy = SettingsManager.GetInstance().GetHttpProxy();
// builtin的http代理的端口
HttpProxyPort = SettingsManager.GetInstance().GetHttpProxyListenPort();
// Aria服务器端口
AriaListenPort = SettingsManager.GetInstance().GetAriaListenPort();
@ -171,7 +274,7 @@ namespace DownKyi.ViewModels.Settings
SelectedAriaLogLevel = ariaLogLevel.ToString("G");
// Aria同时下载数
SelectedAriaMaxConcurrentDownload = SettingsManager.GetInstance().GetAriaMaxConcurrentDownloads();
SelectedAriaMaxConcurrentDownload = SettingsManager.GetInstance().GetMaxCurrentDownloads();
// Aria最大线程数
SelectedAriaSplit = SettingsManager.GetInstance().GetAriaSplit();
@ -201,6 +304,120 @@ namespace DownKyi.ViewModels.Settings
#region 命令申明
// 下载器选择事件
private DelegateCommand<string> selectDownloaderCommand;
public DelegateCommand<string> SelectDownloaderCommand => selectDownloaderCommand ?? (selectDownloaderCommand = new DelegateCommand<string>(ExecuteSelectDownloaderCommand));
/// <summary>
/// 下载器选择事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteSelectDownloaderCommand(string parameter)
{
Downloader downloader;
switch (parameter)
{
case "Builtin":
downloader = Downloader.BUILT_IN;
break;
case "Aria2c":
downloader = Downloader.ARIA;
break;
default:
downloader = SettingsManager.GetInstance().GetDownloader();
break;
}
bool isSucceed = SettingsManager.GetInstance().SetDownloader(downloader);
PublishTip(isSucceed);
AlertService alertService = new AlertService(dialogService);
ButtonResult result = alertService.ShowInfo(DictionaryResource.GetString("ConfirmReboot"));
if (result == ButtonResult.OK)
{
System.Windows.Application.Current.Shutdown();
System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().Location);
}
}
// builtin同时下载数事件
private DelegateCommand<object> maxCurrentDownloadsCommand;
public DelegateCommand<object> MaxCurrentDownloadsCommand => maxCurrentDownloadsCommand ?? (maxCurrentDownloadsCommand = new DelegateCommand<object>(ExecuteMaxCurrentDownloadsCommand));
/// <summary>
/// builtin同时下载数事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteMaxCurrentDownloadsCommand(object parameter)
{
SelectedMaxCurrentDownload = (int)parameter;
bool isSucceed = SettingsManager.GetInstance().SetMaxCurrentDownloads(SelectedMaxCurrentDownload);
PublishTip(isSucceed);
}
// builtin最大线程数事件
private DelegateCommand<object> splitsCommand;
public DelegateCommand<object> SplitsCommand => splitsCommand ?? (splitsCommand = new DelegateCommand<object>(ExecuteSplitsCommand));
/// <summary>
/// builtin最大线程数事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteSplitsCommand(object parameter)
{
SelectedSplit = (int)parameter;
bool isSucceed = SettingsManager.GetInstance().SetSplit(SelectedSplit);
PublishTip(isSucceed);
}
// 是否开启builtin http代理事件
private DelegateCommand isHttpProxyCommand;
public DelegateCommand IsHttpProxyCommand => isHttpProxyCommand ?? (isHttpProxyCommand = new DelegateCommand(ExecuteIsHttpProxyCommand));
/// <summary>
/// 是否开启builtin http代理事件
/// </summary>
private void ExecuteIsHttpProxyCommand()
{
AllowStatus isHttpProxy = IsHttpProxy ? AllowStatus.YES : AllowStatus.NO;
bool isSucceed = SettingsManager.GetInstance().IsHttpProxy(isHttpProxy);
PublishTip(isSucceed);
}
// builtin的http代理的地址事件
private DelegateCommand<string> httpProxyCommand;
public DelegateCommand<string> HttpProxyCommand => httpProxyCommand ?? (httpProxyCommand = new DelegateCommand<string>(ExecuteHttpProxyCommand));
/// <summary>
/// builtin的http代理的地址事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteHttpProxyCommand(string parameter)
{
bool isSucceed = SettingsManager.GetInstance().SetHttpProxy(parameter);
PublishTip(isSucceed);
}
// builtin的http代理的端口事件
private DelegateCommand<string> httpProxyPortCommand;
public DelegateCommand<string> HttpProxyPortCommand => httpProxyPortCommand ?? (httpProxyPortCommand = new DelegateCommand<string>(ExecuteHttpProxyPortCommand));
/// <summary>
/// builtin的http代理的端口事件
/// </summary>
/// <param name="parameter"></param>
private void ExecuteHttpProxyPortCommand(string parameter)
{
int httpProxyPort = (int)Number.GetInt(parameter);
HttpProxyPort = httpProxyPort;
bool isSucceed = SettingsManager.GetInstance().SetHttpProxyListenPort(HttpProxyPort);
PublishTip(isSucceed);
}
// Aria服务器端口事件
private DelegateCommand<string> ariaListenPortCommand;
public DelegateCommand<string> AriaListenPortCommand => ariaListenPortCommand ?? (ariaListenPortCommand = new DelegateCommand<string>(ExecuteAriaListenPortCommand));
@ -267,7 +484,7 @@ namespace DownKyi.ViewModels.Settings
{
SelectedAriaMaxConcurrentDownload = (int)parameter;
bool isSucceed = SettingsManager.GetInstance().SetAriaMaxConcurrentDownloads(SelectedAriaMaxConcurrentDownload);
bool isSucceed = SettingsManager.GetInstance().SetMaxCurrentDownloads(SelectedAriaMaxConcurrentDownload);
PublishTip(isSucceed);
}

@ -188,7 +188,7 @@
Grid.Column="2"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="3.19.1" />
Text="3.20.1" />
<TextBlock
Grid.Row="2"
Grid.Column="3"
@ -323,7 +323,7 @@
Grid.Column="2"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="1.35.0" />
Text="1.36.0" />
<TextBlock
Grid.Row="7"
Grid.Column="3"
@ -350,7 +350,7 @@
Grid.Column="2"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="5.0" />
Text="5.0.1" />
<TextBlock
Grid.Row="8"
Grid.Column="3"

@ -16,244 +16,425 @@
Text="{DynamicResource Network}" />
</StackPanel>
<StackPanel
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<StackPanel Margin="0,20,0,0" Orientation="Vertical">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaServerPort}" />
<TextBox
Name="nameAriaListenPort"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaListenPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaListenPortCommand}"
CommandParameter="{Binding ElementName=nameAriaListenPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
Text="{DynamicResource SelectDownloader}" />
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaLogLevel}" />
<ComboBox
Name="nameAriaLogLevels"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaLogLevels}"
SelectedValue="{Binding SelectedAriaLogLevel}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaLogLevelsCommand}" CommandParameter="{Binding ElementName=nameAriaLogLevels, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<GroupBox
Margin="0,10,0,0"
BorderBrush="{x:Null}"
BorderThickness="0">
<StackPanel Orientation="Horizontal">
<RadioButton
Command="{Binding SelectDownloaderCommand}"
CommandParameter="Builtin"
Content="{DynamicResource BuiltinDownloader}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding Builtin}"
Style="{StaticResource RadioStyle}" />
<RadioButton
Margin="20,0,0,0"
Command="{Binding SelectDownloaderCommand}"
CommandParameter="Aria2c"
Content="{DynamicResource Aria2cDownloader}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding Aria2c}"
Style="{StaticResource RadioStyle}" />
</StackPanel>
</GroupBox>
</StackPanel>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxConcurrentDownloads}" />
<ComboBox
Name="nameAriaMaxConcurrentDownloads"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaMaxConcurrentDownloads}"
SelectedValue="{Binding SelectedAriaMaxConcurrentDownload}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaMaxConcurrentDownloadsCommand}" CommandParameter="{Binding ElementName=nameAriaMaxConcurrentDownloads, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<TextBlock
Height="1"
Margin="0,20,0,0"
Background="{DynamicResource BrushBorder}" />
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaSplit}" />
<ComboBox
Name="nameAriaSplits"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaSplits}"
SelectedValue="{Binding SelectedAriaSplit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaSplitsCommand}" CommandParameter="{Binding ElementName=nameAriaSplits, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<StackPanel x:Name="nameBuiltin">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding Builtin}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Builtin}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<GroupBox
Margin="0,20,0,0"
Padding="10,5"
HorizontalAlignment="Left">
<GroupBox.Header>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaDownloadLimit}" />
</GroupBox.Header>
Text="{DynamicResource MaxCurrentDownloads}" />
<ComboBox
Name="nameMaxCurrentDownloads"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding MaxCurrentDownloads}"
SelectedValue="{Binding SelectedMaxCurrentDownload}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding MaxCurrentDownloadsCommand}" CommandParameter="{Binding ElementName=nameMaxCurrentDownloads, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<StackPanel>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource Split}" />
<ComboBox
Name="nameSplits"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding Splits}"
SelectedValue="{Binding SelectedSplit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SplitsCommand}" CommandParameter="{Binding ElementName=nameSplits, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<CheckBox
Name="nameIsHttpProxy"
Margin="0,20,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Command="{Binding IsHttpProxyCommand}"
Content="{DynamicResource IsHttpProxy}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding IsHttpProxy, Mode=TwoWay}"
Style="{StaticResource CheckBoxStyle}" />
<StackPanel
Name="nameHttpProxyPanel"
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=nameIsHttpProxy, Path=IsChecked}" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=nameIsHttpProxy, Path=IsChecked}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<StackPanel Orientation="Horizontal">
<TextBlock
Width="300"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxOverallDownloadLimit}" />
Text="{DynamicResource HttpProxy}" />
<TextBox
Name="nameAriaMaxOverallDownloadLimit"
Width="100"
Name="nameHttpProxy"
Width="200"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaMaxOverallDownloadLimit}"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
Text="{Binding HttpProxy}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaMaxOverallDownloadLimitCommand}"
CommandParameter="{Binding ElementName=nameAriaMaxOverallDownloadLimit, Path=Text}" />
Command="{Binding HttpProxyCommand}"
CommandParameter="{Binding ElementName=nameHttpProxy, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel Margin="0,10,0,5" Orientation="Horizontal">
<StackPanel Margin="30,0,0,0" Orientation="Horizontal">
<TextBlock
Width="300"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxDownloadLimit}" />
Text="{DynamicResource HttpProxyPort}" />
<TextBox
Name="nameAriaMaxDownloadLimit"
Name="nameHttpProxyPort"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaMaxDownloadLimit}"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
Text="{Binding HttpProxyPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaMaxDownloadLimitCommand}"
CommandParameter="{Binding ElementName=nameAriaMaxDownloadLimit, Path=Text}" />
Command="{Binding HttpProxyPortCommand}"
CommandParameter="{Binding ElementName=nameHttpProxyPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</StackPanel>
</GroupBox>
<CheckBox
Name="nameIsAriaHttpProxy"
Margin="0,20,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Command="{Binding IsAriaHttpProxyCommand}"
Content="{DynamicResource IsAriaHttpProxy}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
IsChecked="{Binding IsAriaHttpProxy, Mode=TwoWay}"
Style="{StaticResource CheckBoxStyle}" />
</StackPanel>
<StackPanel
Name="nameAriaHttpProxyPanel"
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<StackPanel x:Name="nameAria">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=nameIsAriaHttpProxy, Path=IsChecked}" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=nameIsAriaHttpProxy, Path=IsChecked}" Value="true">
<DataTrigger Binding="{Binding Aria2c}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Aria2c}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<StackPanel Orientation="Horizontal">
<StackPanel
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaHttpProxy}" />
Text="{DynamicResource AriaServerPort}" />
<TextBox
Name="nameAriaHttpProxy"
Width="200"
Name="nameAriaListenPort"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaHttpProxy}">
Text="{Binding AriaListenPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaHttpProxyCommand}"
CommandParameter="{Binding ElementName=nameAriaHttpProxy, Path=Text}" />
Command="{Binding AriaListenPortCommand}"
CommandParameter="{Binding ElementName=nameAriaListenPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel Margin="30,0,0,0" Orientation="Horizontal">
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaHttpProxyPort}" />
<TextBox
Name="nameAriaHttpProxyPort"
Text="{DynamicResource AriaLogLevel}" />
<ComboBox
Name="nameAriaLogLevels"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaHttpProxyPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaHttpProxyPortCommand}"
CommandParameter="{Binding ElementName=nameAriaHttpProxyPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
ItemsSource="{Binding AriaLogLevels}"
SelectedValue="{Binding SelectedAriaLogLevel}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaLogLevelsCommand}" CommandParameter="{Binding ElementName=nameAriaLogLevels, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
</StackPanel>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxConcurrentDownloads}" />
<ComboBox
Name="nameAriaMaxConcurrentDownloads"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaMaxConcurrentDownloads}"
SelectedValue="{Binding SelectedAriaMaxConcurrentDownload}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaMaxConcurrentDownloadsCommand}" CommandParameter="{Binding ElementName=nameAriaMaxConcurrentDownloads, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaSplit}" />
<ComboBox
Name="nameAriaSplits"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaSplits}"
SelectedValue="{Binding SelectedAriaSplit}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaSplitsCommand}" CommandParameter="{Binding ElementName=nameAriaSplits, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<GroupBox
Margin="0,20,0,0"
Padding="10,5"
HorizontalAlignment="Left">
<GroupBox.Header>
<TextBlock
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaDownloadLimit}" />
</GroupBox.Header>
<StackPanel>
<StackPanel Margin="0,10,0,0" Orientation="Horizontal">
<TextBlock
Width="300"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxOverallDownloadLimit}" />
<TextBox
Name="nameAriaMaxOverallDownloadLimit"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaMaxOverallDownloadLimit}"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaMaxOverallDownloadLimitCommand}"
CommandParameter="{Binding ElementName=nameAriaMaxOverallDownloadLimit, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel Margin="0,10,0,5" Orientation="Horizontal">
<TextBlock
Width="300"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaMaxDownloadLimit}" />
<TextBox
Name="nameAriaMaxDownloadLimit"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaMaxDownloadLimit}"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaMaxDownloadLimitCommand}"
CommandParameter="{Binding ElementName=nameAriaMaxDownloadLimit, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</StackPanel>
</GroupBox>
<CheckBox
Name="nameIsAriaHttpProxy"
Margin="0,20,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Command="{Binding IsAriaHttpProxyCommand}"
Content="{DynamicResource IsHttpProxy}"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaFileAllocation}" />
<ComboBox
Name="nameAriaFileAllocations"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaFileAllocations}"
SelectedValue="{Binding SelectedAriaFileAllocation}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaFileAllocationsCommand}" CommandParameter="{Binding ElementName=nameAriaFileAllocations, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
IsChecked="{Binding IsAriaHttpProxy, Mode=TwoWay}"
Style="{StaticResource CheckBoxStyle}" />
<StackPanel
Name="nameAriaHttpProxyPanel"
Margin="0,20,0,0"
Orientation="Horizontal"
ToolTip="{DynamicResource PressEnterToApplySettingTip}">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=nameIsAriaHttpProxy, Path=IsChecked}" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=nameIsAriaHttpProxy, Path=IsChecked}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<StackPanel Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource HttpProxy}" />
<TextBox
Name="nameAriaHttpProxy"
Width="200"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaHttpProxy}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaHttpProxyCommand}"
CommandParameter="{Binding ElementName=nameAriaHttpProxy, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
<StackPanel Margin="30,0,0,0" Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource HttpProxyPort}" />
<TextBox
Name="nameAriaHttpProxyPort"
Width="100"
Height="20"
VerticalContentAlignment="Center"
Text="{Binding AriaHttpProxyPort}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding AriaHttpProxyPortCommand}"
CommandParameter="{Binding ElementName=nameAriaHttpProxyPort, Path=Text}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</StackPanel>
<StackPanel Margin="0,20,0,0" Orientation="Horizontal">
<TextBlock
Width="100"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource BrushTextDark}"
Text="{DynamicResource AriaFileAllocation}" />
<ComboBox
Name="nameAriaFileAllocations"
Width="100"
VerticalContentAlignment="Center"
ItemsSource="{Binding AriaFileAllocations}"
SelectedValue="{Binding SelectedAriaFileAllocation}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding AriaFileAllocationsCommand}" CommandParameter="{Binding ElementName=nameAriaFileAllocations, Path=SelectedValue}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
</StackPanel>
<StackPanel Margin="10" />

Loading…
Cancel
Save