From 85178d9b57dc433000081357d3e55f30520cf0d1 Mon Sep 17 00:00:00 2001
From: leiurayer <1432593898@qq.com>
Date: Sat, 14 May 2022 22:32:32 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=86=85=E5=BB=BA=E4=B8=8B?=
=?UTF-8?q?=E8=BD=BD=E5=99=A8=EF=BC=8C=E6=96=B0=E5=A2=9E=E4=B8=8B=E8=BD=BD?=
=?UTF-8?q?=E5=99=A8=E5=88=87=E6=8D=A2=E8=AE=BE=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/DownKyi.Core/DownKyi.Core.csproj | 1 +
.../Downloader/MultiThreadDownloader.cs | 26 +-
src/DownKyi.Core/Settings/Downloader.cs | 9 +
.../Settings/Models/NetworkSettings.cs | 14 +-
.../Settings/SettingsManager.Network.cs | 206 ++++-
src/DownKyi/App.xaml.cs | 19 +-
src/DownKyi/DownKyi.csproj | 1 +
src/DownKyi/Languages/Default.xaml | 12 +-
.../Services/Download/AriaDownloadService.cs | 722 +---------------
.../Download/BuiltinDownloadService.cs | 363 +++++++++
.../Services/Download/DownloadService.cs | 770 +++++++++++++++++-
.../Settings/ViewNetworkViewModel.cs | 281 ++++++-
src/DownKyi/Views/Settings/ViewNetwork.xaml | 511 ++++++++----
13 files changed, 2005 insertions(+), 930 deletions(-)
create mode 100644 src/DownKyi.Core/Settings/Downloader.cs
create mode 100644 src/DownKyi/Services/Download/BuiltinDownloadService.cs
diff --git a/src/DownKyi.Core/DownKyi.Core.csproj b/src/DownKyi.Core/DownKyi.Core.csproj
index 1411cfd..a9875b5 100644
--- a/src/DownKyi.Core/DownKyi.Core.csproj
+++ b/src/DownKyi.Core/DownKyi.Core.csproj
@@ -290,6 +290,7 @@
+
diff --git a/src/DownKyi.Core/Downloader/MultiThreadDownloader.cs b/src/DownKyi.Core/Downloader/MultiThreadDownloader.cs
index c4d8a0b..84490f7 100644
--- a/src/DownKyi.Core/Downloader/MultiThreadDownloader.cs
+++ b/src/DownKyi.Core/Downloader/MultiThreadDownloader.cs
@@ -385,10 +385,34 @@ namespace DownKyi.Core.Downloader
///
/// 开始下载
///
- public void Start()
+ public void StartAsync()
+ {
+ //Task th = new Task(CreateFirstPartitions);
+ //th.Start();
+ StartAsync(false);
+ }
+
+ ///
+ /// 开始下载
+ ///
+ ///
+ public void StartAsync(bool isWait)
{
Task th = new Task(CreateFirstPartitions);
th.Start();
+
+ if (isWait)
+ {
+ th.Wait();
+ }
+ }
+
+ ///
+ /// 开始下载
+ ///
+ public void Start()
+ {
+ CreateFirstPartitions();
}
///
diff --git a/src/DownKyi.Core/Settings/Downloader.cs b/src/DownKyi.Core/Settings/Downloader.cs
new file mode 100644
index 0000000..ff5cb8c
--- /dev/null
+++ b/src/DownKyi.Core/Settings/Downloader.cs
@@ -0,0 +1,9 @@
+namespace DownKyi.Core.Settings
+{
+ public enum Downloader
+ {
+ NOT_SET = 0,
+ BUILT_IN,
+ ARIA,
+ }
+}
diff --git a/src/DownKyi.Core/Settings/Models/NetworkSettings.cs b/src/DownKyi.Core/Settings/Models/NetworkSettings.cs
index 6b283f8..1e98ceb 100644
--- a/src/DownKyi.Core/Settings/Models/NetworkSettings.cs
+++ b/src/DownKyi.Core/Settings/Models/NetworkSettings.cs
@@ -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
}
}
diff --git a/src/DownKyi.Core/Settings/SettingsManager.Network.cs b/src/DownKyi.Core/Settings/SettingsManager.Network.cs
index f7786d2..302846d 100644
--- a/src/DownKyi.Core/Settings/SettingsManager.Network.cs
+++ b/src/DownKyi.Core/Settings/SettingsManager.Network.cs
@@ -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 = 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();
}
+ ///
+ /// 获取下载器
+ ///
+ ///
+ public Downloader GetDownloader()
+ {
+ appSettings = GetSettings();
+ if (appSettings.Network.Downloader == Downloader.NOT_SET)
+ {
+ // 第一次获取,先设置默认值
+ SetDownloader(downloader);
+ return downloader;
+ }
+ return appSettings.Network.Downloader;
+ }
+
+ ///
+ /// 设置下载器
+ ///
+ ///
+ ///
+ public bool SetDownloader(Downloader downloader)
+ {
+ appSettings.Network.Downloader = downloader;
+ return SetSettings();
+ }
+
+ ///
+ /// 获取最大同时下载数(任务数)
+ ///
+ ///
+ public int GetMaxCurrentDownloads()
+ {
+ appSettings = GetSettings();
+ if (appSettings.Network.MaxCurrentDownloads == -1)
+ {
+ // 第一次获取,先设置默认值
+ SetMaxCurrentDownloads(maxCurrentDownloads);
+ return maxCurrentDownloads;
+ }
+ return appSettings.Network.MaxCurrentDownloads;
+ }
+
+ ///
+ /// 设置最大同时下载数(任务数)
+ ///
+ ///
+ ///
+ public bool SetMaxCurrentDownloads(int maxCurrentDownloads)
+ {
+ appSettings.Network.MaxCurrentDownloads = maxCurrentDownloads;
+ return SetSettings();
+ }
+
+ ///
+ /// 获取单文件最大线程数
+ ///
+ ///
+ public int GetSplit()
+ {
+ appSettings = GetSettings();
+ if (appSettings.Network.Split == -1)
+ {
+ // 第一次获取,先设置默认值
+ SetSplit(split);
+ return split;
+ }
+ return appSettings.Network.Split;
+ }
+
+ ///
+ /// 设置单文件最大线程数
+ ///
+ ///
+ ///
+ public bool SetSplit(int split)
+ {
+ appSettings.Network.Split = split;
+ return SetSettings();
+ }
+
+ ///
+ /// 获取是否开启Http代理
+ ///
+ ///
+ public AllowStatus IsHttpProxy()
+ {
+ appSettings = GetSettings();
+ if (appSettings.Network.IsHttpProxy == AllowStatus.NONE)
+ {
+ // 第一次获取,先设置默认值
+ IsHttpProxy(isHttpProxy);
+ return isHttpProxy;
+ }
+ return appSettings.Network.IsHttpProxy;
+ }
+
+ ///
+ /// 设置是否开启Http代理
+ ///
+ ///
+ ///
+ public bool IsHttpProxy(AllowStatus isHttpProxy)
+ {
+ appSettings.Network.IsHttpProxy = isHttpProxy;
+ return SetSettings();
+ }
+
+ ///
+ /// 获取Http代理的地址
+ ///
+ ///
+ public string GetHttpProxy()
+ {
+ appSettings = GetSettings();
+ if (appSettings.Network.HttpProxy == null)
+ {
+ // 第一次获取,先设置默认值
+ SetHttpProxy(httpProxy);
+ return httpProxy;
+ }
+ return appSettings.Network.HttpProxy;
+ }
+
+ ///
+ /// 设置Aria的http代理的地址
+ ///
+ ///
+ ///
+ public bool SetHttpProxy(string httpProxy)
+ {
+ appSettings.Network.HttpProxy = httpProxy;
+ return SetSettings();
+ }
+
+ ///
+ /// 获取Http代理的端口
+ ///
+ ///
+ public int GetHttpProxyListenPort()
+ {
+ appSettings = GetSettings();
+ if (appSettings.Network.HttpProxyListenPort == -1)
+ {
+ // 第一次获取,先设置默认值
+ SetHttpProxyListenPort(httpProxyListenPort);
+ return httpProxyListenPort;
+ }
+ return appSettings.Network.HttpProxyListenPort;
+ }
+
+ ///
+ /// 设置Http代理的端口
+ ///
+ ///
+ ///
+ public bool SetHttpProxyListenPort(int httpProxyListenPort)
+ {
+ appSettings.Network.HttpProxyListenPort = httpProxyListenPort;
+ return SetSettings();
+ }
+
///
/// 获取Aria服务器的端口号
///
@@ -114,33 +287,6 @@ namespace DownKyi.Core.Settings
return SetSettings();
}
- ///
- /// 获取Aria最大同时下载数(任务数)
- ///
- ///
- public int GetAriaMaxConcurrentDownloads()
- {
- appSettings = GetSettings();
- if (appSettings.Network.AriaMaxConcurrentDownloads == -1)
- {
- // 第一次获取,先设置默认值
- SetAriaMaxConcurrentDownloads(ariaMaxConcurrentDownloads);
- return ariaMaxConcurrentDownloads;
- }
- return appSettings.Network.AriaMaxConcurrentDownloads;
- }
-
- ///
- /// 设置Aria最大同时下载数(任务数)
- ///
- ///
- ///
- public bool SetAriaMaxConcurrentDownloads(int ariaMaxConcurrentDownloads)
- {
- appSettings.Network.AriaMaxConcurrentDownloads = ariaMaxConcurrentDownloads;
- return SetSettings();
- }
-
///
/// 获取Aria单文件最大线程数
///
diff --git a/src/DownKyi/App.xaml.cs b/src/DownKyi/App.xaml.cs
index b3723d8..cae6342 100644
--- a/src/DownKyi/App.xaml.cs
+++ b/src/DownKyi/App.xaml.cs
@@ -14,7 +14,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;
@@ -122,8 +121,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();
}
diff --git a/src/DownKyi/DownKyi.csproj b/src/DownKyi/DownKyi.csproj
index 789105a..328074d 100644
--- a/src/DownKyi/DownKyi.csproj
+++ b/src/DownKyi/DownKyi.csproj
@@ -117,6 +117,7 @@
+
diff --git a/src/DownKyi/Languages/Default.xaml b/src/DownKyi/Languages/Default.xaml
index 0204679..f5527a3 100644
--- a/src/DownKyi/Languages/Default.xaml
+++ b/src/DownKyi/Languages/Default.xaml
@@ -183,6 +183,9 @@
解析后自动下载已解析视频
网络
+ 选择下载器(重启生效):
+ 内建下载器
+ Aria2下载器
Aria服务器端口:
Aria日志等级:
Aria同时下载数:
@@ -190,10 +193,12 @@
Aria下载速度限制(KB/s)
全局下载速度限制[0: 无限制]
单任务下载速度限制[0: 无限制]
- 使用Http代理
- 代理地址:
- 端口:
Aria文件预分配:
+ 使用Http代理
+ 代理地址:
+ 端口:
+ 同时下载数:
+ 最大线程数:
视频
优先下载的视频编码:
@@ -300,6 +305,7 @@
确定
取消
+ 此项需重启生效,您确定要重新启动吗?
您确定要删除吗?
请选择文件夹
diff --git a/src/DownKyi/Services/Download/AriaDownloadService.cs b/src/DownKyi/Services/Download/AriaDownloadService.cs
index 9981b2f..2ffe7df 100644
--- a/src/DownKyi/Services/Download/AriaDownloadService.cs
+++ b/src/DownKyi/Services/Download/AriaDownloadService.cs
@@ -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
///
public class AriaDownloadService : DownloadService, IDownloadService
{
- private Task workTask;
- private CancellationTokenSource tokenSource;
- private CancellationToken cancellationToken;
- private List downloadingTasks = new List();
-
- private readonly int retry = 5;
- private readonly string nullMark = "";
-
public AriaDownloadService(ObservableCollection downloadingList, ObservableCollection downloadedList) : base(downloadingList, downloadedList)
{
Tag = "AriaDownloadService";
@@ -50,40 +36,9 @@ namespace DownKyi.Services.Download
///
///
///
- 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
///
///
///
- 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
/// 下载封面
///
///
- 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);
}
///
/// 下载弹幕
///
///
- 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);
}
///
/// 下载字幕
///
///
- public List DownloadSubtitle(DownloadingItem downloading)
+ public override List DownloadSubtitle(DownloadingItem downloading)
{
- // 更新状态显示
- downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
- downloading.DownloadContent = DictionaryResource.GetString("DownloadingSubtitle");
- // 下载大小
- downloading.DownloadingFileSize = string.Empty;
- // 下载速度
- downloading.SpeedDisplay = string.Empty;
-
- List srtFiles = new List();
-
- 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);
}
///
@@ -352,84 +164,22 @@ namespace DownKyi.Services.Download
///
///
///
- 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);
}
///
/// 解析视频流的下载链接
///
///
- 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);
}
///
@@ -437,48 +187,8 @@ namespace DownKyi.Services.Download
///
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);
- }
-
- ///
- /// 执行任务
- ///
- 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, "任务结束超时");
- }
- }
-
- ///
- /// 下载一个视频
- ///
- ///
- ///
- 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 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)
- {
- }
- }
-
- ///
- /// 下载失败后的处理
- ///
- ///
- 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");
- }
-
- ///
- /// 下载完成后的操作
- ///
- 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;
- }
- }
-
- ///
- /// 获取图片的扩展名
- ///
- ///
- ///
- private string GetImageExtension(string coverUrl)
- {
- if (coverUrl == null)
- {
- return string.Empty;
- }
-
- // 图片的扩展名
- string[] temp = coverUrl.Split('.');
- string fileExtension = temp[temp.Length - 1];
- return fileExtension;
+ // 启动基本服务
+ BaseStart();
}
///
/// 强制暂停
///
///
- private void Pause(DownloadingItem downloading)
+ ///
+ 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
}
///
@@ -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)
{
diff --git a/src/DownKyi/Services/Download/BuiltinDownloadService.cs b/src/DownKyi/Services/Download/BuiltinDownloadService.cs
new file mode 100644
index 0000000..7c20f1b
--- /dev/null
+++ b/src/DownKyi/Services/Download/BuiltinDownloadService.cs
@@ -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 downloadingList, ObservableCollection downloadedList) : base(downloadingList, downloadedList)
+ {
+ Tag = "BuiltinDownloadService";
+ }
+
+ #region 音视频
+
+ ///
+ /// 下载音频,返回下载文件路径
+ ///
+ ///
+ ///
+ public override string DownloadAudio(DownloadingItem downloading)
+ {
+ PlayUrlDashVideo downloadAudio = BaseDownloadAudio(downloading);
+
+ return DownloadVideo(downloading, downloadAudio);
+ }
+
+ ///
+ /// 下载视频,返回下载文件路径
+ ///
+ ///
+ ///
+ public override string DownloadVideo(DownloadingItem downloading)
+ {
+ PlayUrlDashVideo downloadVideo = BaseDownloadVideo(downloading);
+
+ return DownloadVideo(downloading, downloadVideo);
+ }
+
+ ///
+ /// 将下载音频和视频的函数中相同代码抽象出来
+ ///
+ ///
+ ///
+ ///
+ private string DownloadVideo(DownloadingItem downloading, PlayUrlDashVideo downloadVideo)
+ {
+ // 如果为空,说明没有匹配到可下载的音频视频
+ if (downloadVideo == null) { return null; }
+
+ // 下载链接
+ List urls = new List();
+ if (downloadVideo.BaseUrl != null) { urls.Add(downloadVideo.BaseUrl); }
+ if (downloadVideo.BackupUrl != null) { urls.AddRange(downloadVideo.BackupUrl); }
+
+ // 路径
+ downloading.DownloadBase.FilePath = downloading.DownloadBase.FilePath.Replace("\\", "/");
+ string[] temp = downloading.DownloadBase.FilePath.Split('/');
+ //string path = downloading.DownloadBase.FilePath.Replace(temp[temp.Length - 1], "");
+ string path = downloading.DownloadBase.FilePath.TrimEnd(temp[temp.Length - 1].ToCharArray());
+
+ // 下载文件名
+ string fileName = Guid.NewGuid().ToString("N");
+ string key = $"{downloadVideo.Id}_{downloadVideo.Codecs}";
+
+ // 老版本数据库没有这一项,会变成null
+ if (downloading.Downloading.DownloadedFiles == null)
+ {
+ downloading.Downloading.DownloadedFiles = new List();
+ }
+
+ if (downloading.Downloading.DownloadFiles.ContainsKey(key))
+ {
+ // 如果存在,表示下载过,
+ // 则继续使用上次下载的文件名
+ fileName = downloading.Downloading.DownloadFiles[key];
+
+ // 还要检查一下文件有没有被人删掉,删掉的话重新下载
+ // 如果下载视频之后音频文件被人删了。此时gid还是视频的,会下错文件
+ if (downloading.Downloading.DownloadedFiles.Contains(key) && File.Exists(Path.Combine(path, fileName)))
+ {
+ return Path.Combine(path, fileName);
+ }
+ }
+ else
+ {
+ // 记录本次下载的文件
+ try
+ {
+ downloading.Downloading.DownloadFiles.Add(key, fileName);
+ }
+ catch (ArgumentException) { }
+ // Gid最好能是每个文件单独存储,现在复用有可能会混
+ // 不过好消息是下载是按固定顺序的,而且下载了两个音频会混流不过
+ downloading.Downloading.Gid = null;
+ }
+
+ // 开始下载
+ 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
+
+ ///
+ /// 下载封面
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override string DownloadCover(DownloadingItem downloading, string coverUrl, string fileName)
+ {
+ return BaseDownloadCover(downloading, coverUrl, fileName);
+ }
+
+ ///
+ /// 下载弹幕
+ ///
+ ///
+ ///
+ public override string DownloadDanmaku(DownloadingItem downloading)
+ {
+ return BaseDownloadDanmaku(downloading);
+ }
+
+ ///
+ /// 下载字幕
+ ///
+ ///
+ ///
+ public override List DownloadSubtitle(DownloadingItem downloading)
+ {
+ return BaseDownloadSubtitle(downloading);
+ }
+
+ ///
+ /// 混流音频和视频
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override string MixedFlow(DownloadingItem downloading, string audioUid, string videoUid)
+ {
+ return BaseMixedFlow(downloading, audioUid, videoUid);
+ }
+
+ ///
+ /// 解析视频流的下载链接
+ ///
+ ///
+ public override void Parse(DownloadingItem downloading)
+ {
+ BaseParse(downloading);
+ }
+
+ ///
+ /// 停止下载服务(转换await和Task.Wait两种调用形式)
+ ///
+ private async Task EndTask()
+ {
+ // 停止基本任务
+ await BaseEndTask();
+ }
+
+ ///
+ /// 停止下载服务
+ ///
+ public void End()
+ {
+ Task.Run(EndTask).Wait();
+ }
+
+ public void Start()
+ {
+ // 启动基本服务
+ BaseStart();
+ }
+
+ ///
+ /// 强制暂停
+ ///
+ ///
+ ///
+ protected override void Pause(DownloadingItem downloading)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ downloading.DownloadStatusTitle = DictionaryResource.GetString("Pausing");
+ if (downloading.Downloading.DownloadStatus == DownloadStatus.PAUSE)
+ {
+ throw new OperationCanceledException("Stop thread by pause");
+ }
+ // 是否存在
+ var isExist = IsExist(downloading);
+ if (!isExist)
+ {
+ throw new OperationCanceledException("Task is deleted");
+ }
+ }
+
+ ///
+ /// 是否存在于下载列表中
+ ///
+ ///
+ ///
+ private bool IsExist(DownloadingItem downloading)
+ {
+ bool isExist = downloadingList.Contains(downloading);
+ if (isExist)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ #region 内建下载器
+
+ ///
+ /// 下载文件
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private bool DownloadByBuiltin(DownloadingItem downloading, List 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
+
+ }
+}
diff --git a/src/DownKyi/Services/Download/DownloadService.cs b/src/DownKyi/Services/Download/DownloadService.cs
index f8612a8..9123866 100644
--- a/src/DownKyi/Services/Download/DownloadService.cs
+++ b/src/DownKyi/Services/Download/DownloadService.cs
@@ -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 downloadingList;
protected ObservableCollection downloadedList;
+ protected Task workTask;
+ protected CancellationTokenSource tokenSource;
+ protected CancellationToken cancellationToken;
+ protected List downloadingTasks = new List();
+
+ protected readonly int retry = 5;
+ protected readonly string nullMark = "";
+
///
/// 初始化
///
@@ -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 BaseDownloadSubtitle(DownloadingItem downloading)
+ {
+ // 更新状态显示
+ downloading.DownloadStatusTitle = DictionaryResource.GetString("WhileDownloading");
+ downloading.DownloadContent = DictionaryResource.GetString("DownloadingSubtitle");
+ // 下载大小
+ downloading.DownloadingFileSize = string.Empty;
+ // 下载速度
+ downloading.SpeedDisplay = string.Empty;
+
+ List srtFiles = new List();
+
+ 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;
+ }
+ }
+
+ ///
+ /// 执行任务
+ ///
+ 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()", "任务结束超时");
+ }
+ }
+
+ ///
+ /// 下载一个视频
+ ///
+ ///
+ ///
+ 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 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);
+ }
+ }
+
+ ///
+ /// 下载失败后的处理
+ ///
+ ///
+ 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");
+ }
+
+ ///
+ /// 获取图片的扩展名
+ ///
+ ///
+ ///
+ protected string GetImageExtension(string coverUrl)
+ {
+ if (coverUrl == null)
+ {
+ return string.Empty;
+ }
+
+ // 图片的扩展名
+ string[] temp = coverUrl.Split('.');
+ string fileExtension = temp[temp.Length - 1];
+ return fileExtension;
+ }
+
+ ///
+ /// 下载完成后的操作
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// 停止基本下载服务(转换await和Task.Wait两种调用形式)
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// 启动基本下载服务
+ ///
+ 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 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
}
}
diff --git a/src/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs b/src/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs
index fedbd87..0aa3468 100644
--- a/src/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs
+++ b/src/DownKyi/ViewModels/Settings/ViewNetworkViewModel.cs
@@ -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 maxCurrentDownloads;
+ public List MaxCurrentDownloads
+ {
+ get => maxCurrentDownloads;
+ set => SetProperty(ref maxCurrentDownloads, value);
+ }
+
+ private int selectedMaxCurrentDownload;
+ public int SelectedMaxCurrentDownload
+ {
+ get => selectedMaxCurrentDownload;
+ set => SetProperty(ref selectedMaxCurrentDownload, value);
+ }
+
+ private List splits;
+ public List 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 ariaLogLevels;
public List 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 ariaMaxConcurrentDownloads;
public List 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 ariaSplits;
public List 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 ariaFileAllocations;
public List 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();
+ for (int i = 1; i <= 10; i++) { MaxCurrentDownloads.Add(i); }
+
+ // builtin最大线程数
+ Splits = new List();
+ for (int i = 1; i <= 10; i++) { Splits.Add(i); }
+
// Aria的日志等级
AriaLogLevels = new List
{
@@ -154,7 +227,7 @@ namespace DownKyi.ViewModels.Settings
}
///
- /// 导航到VideoDetail页面时执行
+ /// 导航到页面时执行
///
///
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 selectDownloaderCommand;
+ public DelegateCommand SelectDownloaderCommand => selectDownloaderCommand ?? (selectDownloaderCommand = new DelegateCommand(ExecuteSelectDownloaderCommand));
+
+ ///
+ /// 下载器选择事件
+ ///
+ ///
+ 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