新增支持并发下载

This commit is contained in:
nilaoda 2022-08-26 22:19:12 +08:00
parent 5f179e4694
commit 8eee867cff
7 changed files with 59 additions and 22 deletions

View File

@ -48,6 +48,7 @@ namespace N_m3u8DL_RE.Common.Resource
public static string cmd_uiLanguage { get => GetText("cmd_uiLanguage"); } public static string cmd_uiLanguage { get => GetText("cmd_uiLanguage"); }
public static string cmd_urlProcessorArgs { get => GetText("cmd_urlProcessorArgs"); } public static string cmd_urlProcessorArgs { get => GetText("cmd_urlProcessorArgs"); }
public static string cmd_useShakaPackager { get => GetText("cmd_useShakaPackager"); } public static string cmd_useShakaPackager { get => GetText("cmd_useShakaPackager"); }
public static string cmd_concurrentDownload { get => GetText("cmd_concurrentDownload"); }
public static string cmd_useMkvmerge { get => GetText("cmd_useMkvmerge"); } public static string cmd_useMkvmerge { get => GetText("cmd_useMkvmerge"); }
public static string cmd_muxAfterDone { get => GetText("cmd_muxAfterDone"); } public static string cmd_muxAfterDone { get => GetText("cmd_muxAfterDone"); }
public static string cmd_muxToMp4 { get => GetText("cmd_muxToMp4"); } public static string cmd_muxToMp4 { get => GetText("cmd_muxToMp4"); }

View File

@ -244,6 +244,12 @@ namespace N_m3u8DL_RE.Common.Resource
zhTW: "混流时使用mkvmerge替代ffmpeg", zhTW: "混流时使用mkvmerge替代ffmpeg",
enUS: "Use mkvmerge instead of ffmpeg to mux" enUS: "Use mkvmerge instead of ffmpeg to mux"
), ),
["cmd_concurrentDownload"] = new TextContainer
(
zhCN: "并发下载已选择的音频、视频和字幕",
zhTW: "並發下載已選擇的音訊、影片和字幕",
enUS: "Concurrently download the selected audio, video and subtitles"
),
["cmd_muxAfterDone"] = new TextContainer ["cmd_muxAfterDone"] = new TextContainer
( (
zhCN: "所有工作完成时尝试混流分离的音视频. 你能够以:分隔形式指定如下参数:\r\n\r\n" + zhCN: "所有工作完成时尝试混流分离的音视频. 你能够以:分隔形式指定如下参数:\r\n\r\n" +

View File

@ -3,6 +3,7 @@ using N_m3u8DL_RE.Entity;
using Spectre.Console; using Spectre.Console;
using Spectre.Console.Rendering; using Spectre.Console.Rendering;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -12,37 +13,40 @@ namespace N_m3u8DL_RE.Column
{ {
internal sealed class DownloadSpeedColumn : ProgressColumn internal sealed class DownloadSpeedColumn : ProgressColumn
{ {
private string DateTimeString = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); private ConcurrentDictionary<int, string> DateTimeStringDic = new();
private string Speed = "0Bps"; private ConcurrentDictionary<int, string> SpeedDic = new();
protected override bool NoWrap => true; protected override bool NoWrap => true;
public SpeedContainer SpeedContainer { get; set; } public ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic { get; set; }
public DownloadSpeedColumn(SpeedContainer SpeedContainer) public DownloadSpeedColumn(ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic)
{ {
this.SpeedContainer = SpeedContainer; this.SpeedContainerDic = SpeedContainerDic;
} }
public Style MyStyle { get; set; } = new Style(foreground: Color.Green); public Style MyStyle { get; set; } = new Style(foreground: Color.Green);
public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime) public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
{ {
var taskId = task.Id;
var speedContainer = SpeedContainerDic[taskId];
var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
var flag = task.IsFinished || !task.IsStarted; var flag = task.IsFinished || !task.IsStarted;
//单文件下载汇报进度 //单文件下载汇报进度
if (!flag && SpeedContainer.SingleSegment && SpeedContainer.ResponseLength != null) if (!flag && speedContainer.SingleSegment && speedContainer.ResponseLength != null)
{ {
task.MaxValue = (double)SpeedContainer.ResponseLength; task.MaxValue = (double)speedContainer.ResponseLength;
task.Value = SpeedContainer.RDownloaded; task.Value = speedContainer.RDownloaded;
} }
//一秒汇报一次即可 //一秒汇报一次即可
if (DateTimeString != now) if (DateTimeStringDic.TryGetValue(taskId, out var oldTime) && oldTime != now)
{ {
Speed = FormatFileSize(SpeedContainer.Downloaded); SpeedDic[taskId] = FormatFileSize(speedContainer.Downloaded);
SpeedContainer.Reset(); speedContainer.Reset();
DateTimeString = now;
} }
DateTimeStringDic[taskId] = now;
var style = flag ? Style.Plain : MyStyle; var style = flag ? Style.Plain : MyStyle;
return flag ? new Text("-", style).Centered() : new Text(Speed, style).Centered(); SpeedDic.TryGetValue(taskId, out var speed);
return flag ? new Text("-", style).Centered() : new Text(speed ?? "0Bps", style).Centered();
} }
private static string FormatFileSize(double fileSize) private static string FormatFileSize(double fileSize)

View File

@ -42,6 +42,7 @@ namespace N_m3u8DL_RE.CommandLine
private readonly static Option<string?> DecryptionBinaryPath = new(new string[] { "--decryption-binary-path" }, description: ResString.cmd_decryptionBinaryPath); private readonly static Option<string?> DecryptionBinaryPath = new(new string[] { "--decryption-binary-path" }, description: ResString.cmd_decryptionBinaryPath);
private readonly static Option<string?> FFmpegBinaryPath = new(new string[] { "--ffmpeg-binary-path" }, description: ResString.cmd_ffmpegBinaryPath); private readonly static Option<string?> FFmpegBinaryPath = new(new string[] { "--ffmpeg-binary-path" }, description: ResString.cmd_ffmpegBinaryPath);
private readonly static Option<string?> BaseUrl = new(new string[] { "--base-url" }, description: ResString.cmd_baseUrl); private readonly static Option<string?> BaseUrl = new(new string[] { "--base-url" }, description: ResString.cmd_baseUrl);
private readonly static Option<bool> ConcurrentDownload = new(new string[] { "--concurrent-download" }, description: ResString.cmd_concurrentDownload, getDefaultValue: () => false);
//复杂命令行如下 //复杂命令行如下
private readonly static Option<MuxOptions?> MuxAfterDone = new(new string[] { "-M", "--mux-after-done" }, description: ResString.cmd_muxAfterDone, parseArgument: ParseMuxAfterDone) { ArgumentHelpName = "OPTIONS" }; private readonly static Option<MuxOptions?> MuxAfterDone = new(new string[] { "-M", "--mux-after-done" }, description: ResString.cmd_muxAfterDone, parseArgument: ParseMuxAfterDone) { ArgumentHelpName = "OPTIONS" };
@ -187,6 +188,7 @@ namespace N_m3u8DL_RE.CommandLine
DownloadRetryCount = bindingContext.ParseResult.GetValueForOption(DownloadRetryCount), DownloadRetryCount = bindingContext.ParseResult.GetValueForOption(DownloadRetryCount),
BaseUrl = bindingContext.ParseResult.GetValueForOption(BaseUrl), BaseUrl = bindingContext.ParseResult.GetValueForOption(BaseUrl),
MuxImports = bindingContext.ParseResult.GetValueForOption(MuxImports), MuxImports = bindingContext.ParseResult.GetValueForOption(MuxImports),
ConcurrentDownload = bindingContext.ParseResult.GetValueForOption(ConcurrentDownload),
}; };
@ -218,10 +220,10 @@ namespace N_m3u8DL_RE.CommandLine
public static async Task<int> InvokeArgs(string[] args, Func<MyOption, Task> action) public static async Task<int> InvokeArgs(string[] args, Func<MyOption, Task> action)
{ {
var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220825") var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220826")
{ {
Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount, Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
BinaryMerge, DelAfterDone, WriteMetaJson, AppendUrlParams, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix, BinaryMerge, DelAfterDone, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
FFmpegBinaryPath, FFmpegBinaryPath,
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption, LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
MuxAfterDone, MuxImports MuxAfterDone, MuxImports

View File

@ -103,6 +103,10 @@ namespace N_m3u8DL_RE.CommandLine
/// </summary> /// </summary>
public bool UseMkvmerge { get; set; } public bool UseMkvmerge { get; set; }
/// <summary> /// <summary>
/// See: <see cref="CommandInvoker.ConcurrentDownload"/>.
/// </summary>
public bool ConcurrentDownload { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.SubtitleFormat"/>. /// See: <see cref="CommandInvoker.SubtitleFormat"/>.
/// </summary> /// </summary>
public SubtitleFormat SubtitleFormat { get; set; } public SubtitleFormat SubtitleFormat { get; set; }

View File

@ -110,6 +110,7 @@ namespace N_m3u8DL_RE.DownloadManager
} }
} }
private async Task<bool> DownloadStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer) private async Task<bool> DownloadStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer)
{ {
speedContainer.ResetVars(); speedContainer.ResetVars();
@ -549,7 +550,7 @@ namespace N_m3u8DL_RE.DownloadManager
public async Task<bool> StartDownloadAsync(IEnumerable<StreamSpec> streamSpecs) public async Task<bool> StartDownloadAsync(IEnumerable<StreamSpec> streamSpecs)
{ {
SpeedContainer speedContainer = new SpeedContainer(); //速度计算 ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic = new(); //速度计算
ConcurrentDictionary<StreamSpec, bool?> Results = new(); ConcurrentDictionary<StreamSpec, bool?> Results = new();
var progress = AnsiConsole.Progress().AutoClear(true); var progress = AnsiConsole.Progress().AutoClear(true);
@ -560,7 +561,7 @@ namespace N_m3u8DL_RE.DownloadManager
new TaskDescriptionColumn() { Alignment = Justify.Left }, new TaskDescriptionColumn() { Alignment = Justify.Left },
new ProgressBarColumn(), new ProgressBarColumn(),
new PercentageColumn(), new PercentageColumn(),
new DownloadSpeedColumn(speedContainer), //速度计算 new DownloadSpeedColumn(SpeedContainerDic), //速度计算
new RemainingTimeColumn(), new RemainingTimeColumn(),
new SpinnerColumn(), new SpinnerColumn(),
}); });
@ -571,15 +572,30 @@ namespace N_m3u8DL_RE.DownloadManager
var dic = streamSpecs.Select(item => var dic = streamSpecs.Select(item =>
{ {
var task = ctx.AddTask(item.ToShortString(), autoStart: false); var task = ctx.AddTask(item.ToShortString(), autoStart: false);
SpeedContainerDic[task.Id] = new SpeedContainer(); //速度计算
return (item, task); return (item, task);
}).ToDictionary(item => item.item, item => item.task); }).ToDictionary(item => item.item, item => item.task);
if (!DownloaderConfig.MyOptions.ConcurrentDownload)
{
//遍历,顺序下载 //遍历,顺序下载
foreach (var kp in dic) foreach (var kp in dic)
{ {
var task = kp.Value; var task = kp.Value;
var result = await DownloadStreamAsync(kp.Key, task, speedContainer); var result = await DownloadStreamAsync(kp.Key, task, SpeedContainerDic[task.Id]);
Results[kp.Key] = result; Results[kp.Key] = result;
} }
}
else
{
//并发下载
await Parallel.ForEachAsync(dic, async (kp, _) =>
{
var task = kp.Value;
var result = await DownloadStreamAsync(kp.Key, task, SpeedContainerDic[task.Id]);
Results[kp.Key] = result;
});
}
}); });
var success = Results.Values.All(v => v == true); var success = Results.Values.All(v => v == true);

View File

@ -238,6 +238,10 @@ namespace N_m3u8DL_RE.Util
break; break;
} }
} }
//有的播放器不识别zho统一转为chi
if (outputFile.LangCode == "zho") outputFile.LangCode = "chi";
if (outputFile.LangCode == "cmn") outputFile.LangCode = "chi";
if (outputFile.LangCode == "yue") outputFile.LangCode = "chi";
} }
} }
} }