diff --git a/src/N_m3u8DL-RE/Column/DownloadSpeedColumn.cs b/src/N_m3u8DL-RE/Column/DownloadSpeedColumn.cs new file mode 100644 index 0000000..c53f571 --- /dev/null +++ b/src/N_m3u8DL-RE/Column/DownloadSpeedColumn.cs @@ -0,0 +1,54 @@ +using N_m3u8DL_RE.Entity; +using Spectre.Console; +using Spectre.Console.Rendering; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace N_m3u8DL_RE.Column +{ + internal sealed class DownloadSpeedColumn : ProgressColumn + { + private TimeSpan CalcTimeSpan = TimeSpan.FromSeconds(0); + private string Speed = "0Bps"; + protected override bool NoWrap => true; + public SpeedContainer SpeedContainer { get; set; } + + public DownloadSpeedColumn(SpeedContainer SpeedContainer) + { + this.SpeedContainer = SpeedContainer; + } + + public Style MyStyle { get; set; } = new Style(foreground: Color.Green); + + public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime) + { + CalcTimeSpan = CalcTimeSpan.Add(deltaTime); + //一秒汇报一次即可 + if (CalcTimeSpan.TotalSeconds > 1) + { + Speed = FormatFileSize(SpeedContainer.Downloaded / CalcTimeSpan.TotalSeconds); + SpeedContainer.Reset(); + CalcTimeSpan = TimeSpan.FromSeconds(0); + } + var percentage = (int)task.Percentage; + var flag = percentage == 100 || percentage == 0; + var style = flag ? Style.Plain : MyStyle; + return flag ? new Text("-", style).Centered() : new Text(Speed, style).Centered(); + } + + private static string FormatFileSize(double fileSize) + { + return fileSize switch + { + < 0 => throw new ArgumentOutOfRangeException(nameof(fileSize)), + >= 1024 * 1024 * 1024 => string.Format("{0:########0.00}GBps", (double)fileSize / (1024 * 1024 * 1024)), + >= 1024 * 1024 => string.Format("{0:####0.00}MBps", (double)fileSize / (1024 * 1024)), + >= 1024 => string.Format("{0:####0.00}KBps", (double)fileSize / 1024), + _ => string.Format("{0}Bps", fileSize) + }; + } + } +} diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs index 0b9be68..523e560 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs @@ -1,4 +1,5 @@ using Mp4SubtitleParser; +using N_m3u8DL_RE.Column; using N_m3u8DL_RE.Common.Entity; using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Common.Log; @@ -84,7 +85,7 @@ namespace N_m3u8DL_RE.DownloadManager } } - private async Task DownloadStreamAsync(StreamSpec streamSpec, ProgressTask task) + private async Task DownloadStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer) { bool useAACFilter = false; //ffmpeg合并flag ConcurrentDictionary FileDic = new(); @@ -143,7 +144,7 @@ namespace N_m3u8DL_RE.DownloadManager } var path = Path.Combine(tmpDir, "_init.mp4.tmp"); - var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, headers); + var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers); FileDic[streamSpec.Playlist.MediaInit] = result; if (result == null) { @@ -193,7 +194,7 @@ namespace N_m3u8DL_RE.DownloadManager var index = seg.Index; var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp"); - var result = await Downloader.DownloadSegmentAsync(seg, path, headers); + var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers); FileDic[seg] = result; task.Increment(1); //实时解密 @@ -232,7 +233,7 @@ namespace N_m3u8DL_RE.DownloadManager { var index = seg.Index; var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp"); - var result = await Downloader.DownloadSegmentAsync(seg, path, headers); + var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers); FileDic[seg] = result; task.Increment(1); //实时解密 @@ -512,6 +513,7 @@ namespace N_m3u8DL_RE.DownloadManager public async Task StartDownloadAsync(IEnumerable streamSpecs) { + SpeedContainer speedContainer = new SpeedContainer(); //速度计算 ConcurrentDictionary Results = new(); var progress = AnsiConsole.Progress().AutoClear(true); @@ -522,6 +524,7 @@ namespace N_m3u8DL_RE.DownloadManager new TaskDescriptionColumn() { Alignment = Justify.Left }, new ProgressBarColumn(), new PercentageColumn(), + new DownloadSpeedColumn(speedContainer), //速度计算 new RemainingTimeColumn(), new SpinnerColumn(), }); @@ -538,7 +541,7 @@ namespace N_m3u8DL_RE.DownloadManager foreach (var kp in dic) { var task = kp.Value; - var result = await DownloadStreamAsync(kp.Key, task); + var result = await DownloadStreamAsync(kp.Key, task, speedContainer); Results[kp.Key] = result; } }); diff --git a/src/N_m3u8DL-RE/Downloader/IDownloader.cs b/src/N_m3u8DL-RE/Downloader/IDownloader.cs index 4282e65..9db7289 100644 --- a/src/N_m3u8DL-RE/Downloader/IDownloader.cs +++ b/src/N_m3u8DL-RE/Downloader/IDownloader.cs @@ -10,6 +10,6 @@ namespace N_m3u8DL_RE.Downloader { internal interface IDownloader { - Task DownloadSegmentAsync(MediaSegment segment, string savePath, Dictionary? headers = null); + Task DownloadSegmentAsync(MediaSegment segment, string savePath, SpeedContainer speedContainer, Dictionary? headers = null); } } diff --git a/src/N_m3u8DL-RE/Downloader/SimpleDownloader.cs b/src/N_m3u8DL-RE/Downloader/SimpleDownloader.cs index ef248f8..f9f8c5b 100644 --- a/src/N_m3u8DL-RE/Downloader/SimpleDownloader.cs +++ b/src/N_m3u8DL-RE/Downloader/SimpleDownloader.cs @@ -27,10 +27,10 @@ namespace N_m3u8DL_RE.Downloader DownloaderConfig = config; } - public async Task DownloadSegmentAsync(MediaSegment segment, string savePath, Dictionary? headers = null) + public async Task DownloadSegmentAsync(MediaSegment segment, string savePath, SpeedContainer speedContainer, Dictionary? headers = null) { var url = segment.Url; - var dResult = await DownClipAsync(url, savePath, segment.StartRange, segment.StopRange, headers, DownloaderConfig.DownloadRetryCount); + var dResult = await DownClipAsync(url, savePath, speedContainer, segment.StartRange, segment.StopRange, headers, DownloaderConfig.DownloadRetryCount); if (dResult != null && dResult.Success && segment.EncryptInfo != null) { if (segment.EncryptInfo.Method == EncryptMethod.AES_128) @@ -53,7 +53,7 @@ namespace N_m3u8DL_RE.Downloader return dResult; } - private async Task DownClipAsync(string url, string path, long? fromPosition, long? toPosition, Dictionary? headers = null, int retryCount = 3) + private async Task DownClipAsync(string url, string path, SpeedContainer speedContainer, long? fromPosition, long? toPosition, Dictionary? headers = null, int retryCount = 3) { retry: try @@ -64,7 +64,7 @@ namespace N_m3u8DL_RE.Downloader { return new DownloadResult() { ActualContentLength = 0, ActualFilePath = des }; } - var result = await DownloadUtil.DownloadToFileAsync(url, path, headers, fromPosition, toPosition); + var result = await DownloadUtil.DownloadToFileAsync(url, path, speedContainer, headers, fromPosition, toPosition); //下载完成后改名 if (result.Success || !DownloaderConfig.CheckContentLength) { diff --git a/src/N_m3u8DL-RE/Entity/SpeedContainer.cs b/src/N_m3u8DL-RE/Entity/SpeedContainer.cs new file mode 100644 index 0000000..16815d8 --- /dev/null +++ b/src/N_m3u8DL-RE/Entity/SpeedContainer.cs @@ -0,0 +1,27 @@ +using NiL.JS.Statements; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace N_m3u8DL_RE.Entity +{ + internal class SpeedContainer + { + private long _downloaded = 0; + public long Downloaded { get => _downloaded; } + + public long Add(long size) + { + return Interlocked.Add(ref _downloaded, size); + } + + public void Reset() + { + Interlocked.Exchange(ref _downloaded, 0); + } + } +} diff --git a/src/N_m3u8DL-RE/Util/DownloadUtil.cs b/src/N_m3u8DL-RE/Util/DownloadUtil.cs index faedb0c..be518b5 100644 --- a/src/N_m3u8DL-RE/Util/DownloadUtil.cs +++ b/src/N_m3u8DL-RE/Util/DownloadUtil.cs @@ -24,7 +24,7 @@ namespace N_m3u8DL_RE.Util Timeout = TimeSpan.FromMinutes(2) }; - public static async Task DownloadToFileAsync(string url, string path, Dictionary? headers = null, long? fromPosition = null, long? toPosition = null) + public static async Task DownloadToFileAsync(string url, string path, SpeedContainer speedContainer, Dictionary? headers = null, long? fromPosition = null, long? toPosition = null) { Logger.Debug(ResString.fetch + url); using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(url)); @@ -46,7 +46,7 @@ namespace N_m3u8DL_RE.Util if (respHeaders != null && respHeaders.Location != null) { var redirectedUrl = respHeaders.Location.AbsoluteUri; - return await DownloadToFileAsync(redirectedUrl, path, headers); + return await DownloadToFileAsync(redirectedUrl, path, speedContainer, headers, fromPosition, toPosition); } } response.EnsureSuccessStatusCode(); @@ -57,6 +57,7 @@ namespace N_m3u8DL_RE.Util var size = 0; while ((size = await responseStream.ReadAsync(buffer)) > 0) { + speedContainer.Add(size); await stream.WriteAsync(buffer, 0, size); }