diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs index 0f49c04..097cb9a 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs @@ -65,6 +65,7 @@ public class ResString public static string cmd_skipDownload => GetText("cmd_skipDownload"); public static string cmd_noDateInfo => GetText("cmd_noDateInfo"); public static string cmd_noLog => GetText("cmd_noLog"); + public static string cmd_allowHlsMultiExtMap => GetText("cmd_allowHlsMultiExtMap"); public static string cmd_skipMerge => GetText("cmd_skipMerge"); public static string cmd_subFormat => GetText("cmd_subFormat"); public static string cmd_subOnly => GetText("cmd_subOnly"); @@ -111,6 +112,7 @@ public class ResString public static string liveFound => GetText("liveFound"); public static string loadingUrl => GetText("loadingUrl"); public static string masterM3u8Found => GetText("masterM3u8Found"); + public static string allowHlsMultiExtMap => GetText("allowHlsMultiExtMap"); public static string matchDASH => GetText("matchDASH"); public static string matchMSS => GetText("matchMSS"); public static string matchTS => GetText("matchTS"); diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index bdcbd71..d424059 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -172,6 +172,12 @@ internal class StaticText zhTW: "關閉日誌文件輸出", enUS: "Disable log file output" ), + ["cmd_allowHlsMultiExtMap"] = new TextContainer + ( + zhCN: "允许HLS中的多个#EXT-X-MAP(实验性)", + zhTW: "允許HLS中的多個#EXT-X-MAP(實驗性)", + enUS: "Allow multiple #EXT-X-MAP in HLS (experimental)" + ), ["cmd_appendUrlParams"] = new TextContainer ( zhCN: "将输入Url的Params添加至分片, 对某些网站很有用, 例如 kakao.com", @@ -820,6 +826,12 @@ internal class StaticText zhTW: "檢測到Master列表,開始解析全部流訊息", enUS: "Master List detected, try parse all streams" ), + ["allowHlsMultiExtMap"] = new TextContainer + ( + zhCN: "已经允许识别多个#EXT-X-MAP标签, 本软件可能无法正确处理, 请手动确认内容完整性", + zhTW: "已經允許識別多個#EXT-X-MAP標籤, 本軟件可能無法正確處理, 請手動確認內容完整性", + enUS: "Multiple #EXT-X-MAP tags are now allowed for detection. However, this software may not handle them correctly. Please manually verify the content's integrity" + ), ["matchTS"] = new TextContainer ( zhCN: "内容匹配: [white on green3]HTTP Live MPEG2-TS[/]", diff --git a/src/N_m3u8DL-RE.Parser/Config/ParserConfig.cs b/src/N_m3u8DL-RE.Parser/Config/ParserConfig.cs index fc21386..f04e242 100644 --- a/src/N_m3u8DL-RE.Parser/Config/ParserConfig.cs +++ b/src/N_m3u8DL-RE.Parser/Config/ParserConfig.cs @@ -12,8 +12,10 @@ public class ParserConfig public string OriginalUrl { get; set; } public string BaseUrl { get; set; } + + public Dictionary CustomParserArgs { get; } = new(); - public Dictionary Headers { get; set; } = new Dictionary(); + public Dictionary Headers { get; init; } = new(); /// /// 内容前置处理器. 调用顺序与列表顺序相同 diff --git a/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs b/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs index 3f60fe5..61553a5 100644 --- a/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs +++ b/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs @@ -221,7 +221,13 @@ internal class HLSExtractor : IExtractor { // 标记是否已清除广告分片 bool hasAd = false; - + ; + bool allowHlsMultiExtMap = ParserConfig.CustomParserArgs.TryGetValue("AllowHlsMultiExtMap", out var allMultiExtMap) && allMultiExtMap == "true"; + if (allowHlsMultiExtMap) + { + Logger.WarnMarkUp($"[darkorange3_1]{ResString.allowHlsMultiExtMap}[/]"); + } + using StringReader sr = new StringReader(M3u8Content); string? line; bool expectSegment = false; @@ -395,8 +401,11 @@ internal class HLSExtractor : IExtractor }); } segments = new(); - isEndlist = true; - break; + if (!allowHlsMultiExtMap) + { + isEndlist = true; + break; + } } } // 评论行不解析 diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index 9b4d724..b50c915 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -17,7 +17,7 @@ namespace N_m3u8DL_RE.CommandLine; internal partial class CommandInvoker { - public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20241117"; + public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20241123"; [GeneratedRegex("((best|worst)\\d*|all)")] private static partial Regex ForStrRegex(); @@ -61,6 +61,7 @@ internal partial class CommandInvoker private static readonly Option BaseUrl = new(["--base-url"], description: ResString.cmd_baseUrl); private static readonly Option ConcurrentDownload = new(["-mt", "--concurrent-download"], description: ResString.cmd_concurrentDownload, getDefaultValue: () => false); private static readonly Option NoLog = new(["--no-log"], description: ResString.cmd_noLog, getDefaultValue: () => false); + private static readonly Option AllowHlsMultiExtMap = new(["--allow-hls-multi-ext-map"], description: ResString.cmd_allowHlsMultiExtMap, getDefaultValue: () => false); private static readonly Option AdKeywords = new(["--ad-keyword"], description: ResString.cmd_adKeyword) { ArgumentHelpName = "REG" }; private static readonly Option MaxSpeed = new(["-R", "--max-speed"], description: ResString.cmd_maxSpeed, parseArgument: ParseSpeedLimit) { ArgumentHelpName = "SPEED" }; @@ -550,6 +551,7 @@ internal partial class CommandInvoker LiveTakeCount = bindingContext.ParseResult.GetValueForOption(LiveTakeCount), NoDateInfo = bindingContext.ParseResult.GetValueForOption(NoDateInfo), NoLog = bindingContext.ParseResult.GetValueForOption(NoLog), + AllowHlsMultiExtMap = bindingContext.ParseResult.GetValueForOption(AllowHlsMultiExtMap), AdKeywords = bindingContext.ParseResult.GetValueForOption(AdKeywords), MaxSpeed = bindingContext.ParseResult.GetValueForOption(MaxSpeed), }; @@ -618,7 +620,7 @@ internal partial class CommandInvoker MuxAfterDone, CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt, LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LivePipeMux, LiveFixVttByAudio, LiveRecordLimit, LiveWaitTime, LiveTakeCount, - MuxImports, VideoFilter, AudioFilter, SubtitleFilter, DropVideoFilter, DropAudioFilter, DropSubtitleFilter, AdKeywords, DisableUpdateCheck, MoreHelp + MuxImports, VideoFilter, AudioFilter, SubtitleFilter, DropVideoFilter, DropAudioFilter, DropSubtitleFilter, AdKeywords, DisableUpdateCheck, AllowHlsMultiExtMap, MoreHelp }; rootCommand.TreatUnmatchedTokensAsErrors = true; diff --git a/src/N_m3u8DL-RE/CommandLine/MyOption.cs b/src/N_m3u8DL-RE/CommandLine/MyOption.cs index 2b60f0b..4a783d8 100644 --- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs +++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs @@ -53,6 +53,10 @@ internal class MyOption /// public bool NoLog { get; set; } /// + /// See: . + /// + public bool AllowHlsMultiExtMap { get; set; } + /// /// See: . /// public bool AutoSelect { get; set; } diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs index 404d3b3..e9493f1 100644 --- a/src/N_m3u8DL-RE/Program.cs +++ b/src/N_m3u8DL-RE/Program.cs @@ -106,21 +106,20 @@ internal class Program // 检查互斥的选项 - if (!option.MuxAfterDone && option.MuxImports != null && option.MuxImports.Count > 0) + if (option is { MuxAfterDone: false, MuxImports.Count: > 0 }) { throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!"); } // LivePipeMux开启时 LiveRealTimeMerge必须开启 - if (option.LivePipeMux && !option.LiveRealTimeMerge) + if (option is { LivePipeMux: true, LiveRealTimeMerge: false }) { Logger.WarnMarkUp("LivePipeMux detected, forced enable LiveRealTimeMerge"); option.LiveRealTimeMerge = true; } // 预先检查ffmpeg - if (option.FFmpegBinaryPath == null) - option.FFmpegBinaryPath = GlobalUtil.FindExecutable("ffmpeg"); + option.FFmpegBinaryPath ??= GlobalUtil.FindExecutable("ffmpeg"); if (string.IsNullOrEmpty(option.FFmpegBinaryPath) || !File.Exists(option.FFmpegBinaryPath)) { @@ -132,8 +131,7 @@ internal class Program // 预先检查mkvmerge if (option.MuxOptions != null && option.MuxOptions.UseMkvmerge && option.MuxAfterDone) { - if (option.MkvmergeBinaryPath == null) - option.MkvmergeBinaryPath = GlobalUtil.FindExecutable("mkvmerge"); + option.MkvmergeBinaryPath ??= GlobalUtil.FindExecutable("mkvmerge"); if (string.IsNullOrEmpty(option.MkvmergeBinaryPath) || !File.Exists(option.MkvmergeBinaryPath)) { throw new FileNotFoundException("mkvmerge not found"); @@ -142,7 +140,7 @@ internal class Program } // 预先检查 - if ((option.Keys != null && option.Keys.Length > 0) || option.KeyTextFile != null) + if (option.Keys is { Length: > 0 } || option.KeyTextFile != null) { if (string.IsNullOrEmpty(option.DecryptionBinaryPath)) { @@ -193,6 +191,11 @@ internal class Program CustomeIV = option.CustomHLSIv, }; + if (option.AllowHlsMultiExtMap) + { + parserConfig.CustomParserArgs.Add("AllowHlsMultiExtMap", "true"); + } + // demo1 parserConfig.ContentProcessors.Insert(0, new DemoProcessor()); // demo2 @@ -225,13 +228,13 @@ internal class Program // 全部媒体 - var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth).ThenByDescending(GetOrder); + var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth).ThenByDescending(GetOrder).ToList(); // 基本流 - var basicStreams = lists.Where(x => x.MediaType == null || x.MediaType == MediaType.VIDEO); + var basicStreams = lists.Where(x => x.MediaType is null or MediaType.VIDEO).ToList(); // 可选音频轨道 - var audios = lists.Where(x => x.MediaType == MediaType.AUDIO); + var audios = lists.Where(x => x.MediaType == MediaType.AUDIO).ToList(); // 可选字幕轨道 - var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES); + var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES).ToList(); // 尝试从URL或文件读取文件名 if (string.IsNullOrEmpty(option.SaveName)) @@ -246,7 +249,7 @@ internal class Program // 写出文件 await WriteRawFilesAsync(option, extractor, tmpDir); - Logger.Info(ResString.streamsInfo, lists.Count(), basicStreams.Count(), audios.Count(), subs.Count()); + Logger.Info(ResString.streamsInfo, lists.Count, basicStreams.Count, audios.Count, subs.Count); foreach (var item in lists) { @@ -259,7 +262,7 @@ internal class Program basicStreams = FilterUtil.DoFilterDrop(basicStreams, option.DropVideoFilter); audios = FilterUtil.DoFilterDrop(audios, option.DropAudioFilter); subs = FilterUtil.DoFilterDrop(subs, option.DropSubtitleFilter); - lists = basicStreams.Concat(audios).Concat(subs).OrderBy(x => true); + lists = basicStreams.Concat(audios).Concat(subs).ToList(); } if (option.DropVideoFilter != null) Logger.Extra($"DropVideoFilter => {option.DropVideoFilter}");