From d8ed94cbac6c401846d19258613ea62709fbff18 Mon Sep 17 00:00:00 2001 From: nilaoda Date: Thu, 1 Jun 2023 16:40:52 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8D=95=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=A4=9A=E7=BA=BF=E7=A8=8B=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs | 2 +- .../DownloadManager/SimpleDownloadManager.cs | 8 +- .../Util/LargeSingleFileSplitUtil.cs | 122 ++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 src/N_m3u8DL-RE/Util/LargeSingleFileSplitUtil.cs diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index b466068..73e89b8 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -18,7 +18,7 @@ namespace N_m3u8DL_RE.CommandLine { internal partial class CommandInvoker { - public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20230412"; + public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20230601"; [GeneratedRegex("((best|worst)\\d*|all)")] private static partial Regex ForStrRegex(); diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs index e2cb7f5..05b281c 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs @@ -107,7 +107,13 @@ namespace N_m3u8DL_RE.DownloadManager var segments = streamSpec.Playlist?.MediaParts.SelectMany(m => m.MediaSegments); if (segments == null || !segments.Any()) return false; - if (segments.Count() == 1) speedContainer.SingleSegment = true; + //单分段尝试切片并行下载 + if (segments.Count() == 1) + { + var splitSegments = await LargeSingleFileSplitUtil.SplitUrlAsync(segments.First(), DownloaderConfig.Headers); + if (splitSegments != null) segments = splitSegments; + else speedContainer.SingleSegment = true; + } var type = streamSpec.MediaType ?? Common.Enum.MediaType.VIDEO; var dirName = $"{DownloaderConfig.MyOptions.SaveName ?? NowDateTime.ToString("yyyy-MM-dd_HH-mm-ss")}_{task.Id}_{OtherUtil.GetValidFileName(streamSpec.GroupId ?? "", "-")}_{streamSpec.Codecs}_{streamSpec.Bandwidth}_{streamSpec.Language}"; diff --git a/src/N_m3u8DL-RE/Util/LargeSingleFileSplitUtil.cs b/src/N_m3u8DL-RE/Util/LargeSingleFileSplitUtil.cs new file mode 100644 index 0000000..2be5f91 --- /dev/null +++ b/src/N_m3u8DL-RE/Util/LargeSingleFileSplitUtil.cs @@ -0,0 +1,122 @@ +using N_m3u8DL_RE.Common.Entity; +using N_m3u8DL_RE.Common.Log; +using N_m3u8DL_RE.Common.Util; +using NiL.JS.Expressions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace N_m3u8DL_RE.Util +{ + internal class LargeSingleFileSplitUtil + { + class Clip + { + public required int index; + public required long from; + public required long to; + } + + /// + /// URL大文件切片处理 + /// + /// + /// + /// + /// + public static async Task?> SplitUrlAsync(MediaSegment segment, Dictionary headers) + { + var url = segment.Url; + if (!await CanSplitAsync(url, headers)) return null; + + if (segment.StartRange != null) return null; + + long fileSize = await GetFileSizeAsync(url, headers); + if (fileSize == 0) return null; + + List allClips = GetAllClips(url, fileSize); + var splitSegments = new List(); + foreach (Clip clip in allClips) + { + splitSegments.Add(new MediaSegment() + { + Index = clip.index, + Url = url, + StartRange = clip.from, + ExpectLength = clip.to == -1 ? null : clip.to - clip.from + 1, + EncryptInfo = segment.EncryptInfo, + }); + } + + return splitSegments; + } + + public static async Task CanSplitAsync(string url, Dictionary headers) + { + try + { + var request = new HttpRequestMessage(HttpMethod.Head, url); + var response = (await HTTPUtil.AppHttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)).EnsureSuccessStatusCode(); + bool supportsRangeRequests = response.Headers.Contains("Accept-Ranges"); + + return supportsRangeRequests; + } + catch (Exception ex) + { + Logger.DebugMarkUp(ex.Message); + return false; + } + } + + private static async Task GetFileSizeAsync(string url, Dictionary headers) + { + using var httpRequestMessage = new HttpRequestMessage(); + httpRequestMessage.RequestUri = new(url); + foreach (var header in headers) + { + httpRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + var response = (await HTTPUtil.AppHttpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead)).EnsureSuccessStatusCode(); + long totalSizeBytes = response.Content.Headers.ContentLength ?? 0; + + return totalSizeBytes; + } + + //此函数主要是切片下载逻辑 + private static List GetAllClips(string url, long fileSize) + { + List clips = new(); + int index = 0; + long counter = 0; + int perSize = 10 * 1024 * 1024; + while (fileSize > 0) + { + Clip c = new() + { + index = index, + from = counter, + to = counter + perSize + }; + //没到最后 + if (fileSize - perSize > 0) + { + fileSize -= perSize; + counter += perSize + 1; + index++; + clips.Add(c); + } + //已到最后 + else + { + c.to = -1; + clips.Add(c); + break; + } + } + return clips; + } + } +}