diff --git a/src/N_m3u8DL-RE.Common/Entity/EncryptInfo.cs b/src/N_m3u8DL-RE.Common/Entity/EncryptInfo.cs index 3ce359b..2d1801c 100644 --- a/src/N_m3u8DL-RE.Common/Entity/EncryptInfo.cs +++ b/src/N_m3u8DL-RE.Common/Entity/EncryptInfo.cs @@ -1,9 +1,4 @@ using N_m3u8DL_RE.Common.Enum; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace N_m3u8DL_RE.Common.Entity; @@ -34,9 +29,6 @@ public class EncryptInfo { return m; } - else - { - return EncryptMethod.UNKNOWN; - } + return EncryptMethod.UNKNOWN; } } \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Entity/MediaPart.cs b/src/N_m3u8DL-RE.Common/Entity/MediaPart.cs index c417524..b2fa255 100644 --- a/src/N_m3u8DL-RE.Common/Entity/MediaPart.cs +++ b/src/N_m3u8DL-RE.Common/Entity/MediaPart.cs @@ -3,5 +3,5 @@ // 主要处理 EXT-X-DISCONTINUITY public class MediaPart { - public List MediaSegments { get; set; } = new List(); + public List MediaSegments { get; set; } = []; } \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Entity/MediaSegment.cs b/src/N_m3u8DL-RE.Common/Entity/MediaSegment.cs index 1606d57..2e6f525 100644 --- a/src/N_m3u8DL-RE.Common/Entity/MediaSegment.cs +++ b/src/N_m3u8DL-RE.Common/Entity/MediaSegment.cs @@ -17,7 +17,7 @@ public class MediaSegment public bool IsEncrypted => EncryptInfo.Method != EncryptMethod.NONE; - public string Url { get; set; } + public string Url { get; set; } = string.Empty; public string? NameFromVar { get; set; } // MPD分段文件名 diff --git a/src/N_m3u8DL-RE.Common/Entity/Playlist.cs b/src/N_m3u8DL-RE.Common/Entity/Playlist.cs index 6cc194a..8d54a65 100644 --- a/src/N_m3u8DL-RE.Common/Entity/Playlist.cs +++ b/src/N_m3u8DL-RE.Common/Entity/Playlist.cs @@ -3,17 +3,18 @@ public class Playlist { // 对应Url信息 - public string Url { get; set; } + public string Url { get; set; } = string.Empty; // 是否直播 public bool IsLive { get; set; } = false; // 直播刷新间隔毫秒(默认15秒) public double RefreshIntervalMs { get; set; } = 15000; // 所有分片时长总和 - public double TotalDuration { get => MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration)); } + public double TotalDuration => MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration)); + // 所有分片中最长时长 public double? TargetDuration { get; set; } // INIT信息 public MediaSegment? MediaInit { get; set; } // 分片信息 - public List MediaParts { get; set; } = new List(); + public List MediaParts { get; set; } = []; } \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs b/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs index 4cec950..18e9019 100644 --- a/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs +++ b/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs @@ -46,12 +46,12 @@ public class StreamSpec /// /// URL /// - public string Url { get; set; } + public string Url { get; set; } = string.Empty; /// /// 原始URL /// - public string OriginalUrl { get; set; } + public string OriginalUrl { get; set; } = string.Empty; public Playlist? Playlist { get; set; } diff --git a/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs b/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs index c3b255f..6b6d284 100644 --- a/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs +++ b/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs @@ -11,10 +11,10 @@ public partial class WebVttSub private static partial Regex TSValueRegex(); [GeneratedRegex("\\s")] private static partial Regex SplitRegex(); - [GeneratedRegex("([\\s\\S]*?)<\\/c>")] + [GeneratedRegex(@"([\s\S]*?)<\/c>")] private static partial Regex VttClassRegex(); - public List Cues { get; set; } = new List(); + public List Cues { get; set; } = []; public long MpegtsTimestamp { get; set; } = 0L; /// @@ -71,47 +71,45 @@ public partial class WebVttSub continue; } - if (needPayload) + if (!needPayload) continue; + + if (string.IsNullOrEmpty(line.Trim())) { - if (string.IsNullOrEmpty(line.Trim())) - { - var payload = string.Join(Environment.NewLine, payloads); - if (string.IsNullOrEmpty(payload.Trim())) continue; // 没获取到payload 跳过添加 + var payload = string.Join(Environment.NewLine, payloads); + if (string.IsNullOrEmpty(payload.Trim())) continue; // 没获取到payload 跳过添加 - var arr = SplitRegex().Split(timeLine.Replace("-->", "")).Where(s => !string.IsNullOrEmpty(s)).ToList(); - var startTime = ConvertToTS(arr[0]); - var endTime = ConvertToTS(arr[1]); - var style = arr.Count > 2 ? string.Join(" ", arr.Skip(2)) : ""; - webSub.Cues.Add(new SubCue() - { - StartTime = startTime, - EndTime = endTime, - Payload = RemoveClassTag(string.Join("", payload.Where(c => c != 8203))), // Remove Zero Width Space! - Settings = style - }); - payloads.Clear(); - needPayload = false; - } - else + var arr = SplitRegex().Split(timeLine.Replace("-->", "")).Where(s => !string.IsNullOrEmpty(s)).ToList(); + var startTime = ConvertToTS(arr[0]); + var endTime = ConvertToTS(arr[1]); + var style = arr.Count > 2 ? string.Join(" ", arr.Skip(2)) : ""; + webSub.Cues.Add(new SubCue() { - payloads.Add(line.Trim()); - } + StartTime = startTime, + EndTime = endTime, + Payload = RemoveClassTag(string.Join("", payload.Where(c => c != 8203))), // Remove Zero Width Space! + Settings = style + }); + payloads.Clear(); + needPayload = false; + } + else + { + payloads.Add(line.Trim()); } } - if (BaseTimestamp != 0) + if (BaseTimestamp == 0) return webSub; + + foreach (var item in webSub.Cues) { - foreach (var item in webSub.Cues) + if (item.StartTime.TotalMilliseconds - BaseTimestamp >= 0) { - if (item.StartTime.TotalMilliseconds - BaseTimestamp >= 0) - { - item.StartTime = TimeSpan.FromMilliseconds(item.StartTime.TotalMilliseconds - BaseTimestamp); - item.EndTime = TimeSpan.FromMilliseconds(item.EndTime.TotalMilliseconds - BaseTimestamp); - } - else - { - break; - } + item.StartTime = TimeSpan.FromMilliseconds(item.StartTime.TotalMilliseconds - BaseTimestamp); + item.EndTime = TimeSpan.FromMilliseconds(item.EndTime.TotalMilliseconds - BaseTimestamp); + } + else + { + break; } } @@ -127,7 +125,7 @@ public partial class WebVttSub return string.Concat(VttClassRegex().Matches(line).Select(x => x.Groups[1].Value + " ")); })).TrimEnd(); } - else return text; + return text; } /// @@ -140,18 +138,17 @@ public partial class WebVttSub FixTimestamp(webSub, this.MpegtsTimestamp); foreach (var item in webSub.Cues) { - if (!this.Cues.Contains(item)) + if (this.Cues.Contains(item)) continue; + + // 如果相差只有1ms,且payload相同,则拼接 + var last = this.Cues.LastOrDefault(); + if (last != null && this.Cues.Count > 0 && (item.StartTime - last.EndTime).TotalMilliseconds <= 1 && item.Payload == last.Payload) { - // 如果相差只有1ms,且payload相同,则拼接 - var last = this.Cues.LastOrDefault(); - if (last != null && this.Cues.Count > 0 && (item.StartTime - last.EndTime).TotalMilliseconds <= 1 && item.Payload == last.Payload) - { - last.EndTime = item.EndTime; - } - else - { - this.Cues.Add(item); - } + last.EndTime = item.EndTime; + } + else + { + this.Cues.Add(item); } } return this; @@ -173,10 +170,10 @@ public partial class WebVttSub // 当前预添加的字幕的起始时间小于实际上已经走过的时间(如offset已经是100秒,而字幕起始却是2秒),才修复 if (sub.Cues.Count > 0 && sub.Cues.First().StartTime < offset) { - for (int i = 0; i < sub.Cues.Count; i++) + foreach (var subCue in sub.Cues) { - sub.Cues[i].StartTime += offset; - sub.Cues[i].EndTime += offset; + subCue.StartTime += offset; + subCue.EndTime += offset; } } } @@ -205,7 +202,7 @@ public partial class WebVttSub str = parts.First(); } var t = str.Split(':').Reverse().ToList(); - for (int i = 0; i < t.Count(); i++) + for (int i = 0; i < t.Count; i++) { time += (long)Math.Pow(60, i) * Convert.ToInt32(t[i]) * 1000; } @@ -214,7 +211,7 @@ public partial class WebVttSub public override string ToString() { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); foreach (var c in GetCues()) // 输出时去除空串 { sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\.fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\.fff") + " " + c.Settings); diff --git a/src/N_m3u8DL-RE.Common/JsonConverter/BytesBase64Converter.cs b/src/N_m3u8DL-RE.Common/JsonConverter/BytesBase64Converter.cs index 4097aed..492025d 100644 --- a/src/N_m3u8DL-RE.Common/JsonConverter/BytesBase64Converter.cs +++ b/src/N_m3u8DL-RE.Common/JsonConverter/BytesBase64Converter.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; -using System.Threading.Tasks; namespace N_m3u8DL_RE.Common.JsonConverter; diff --git a/src/N_m3u8DL-RE.Common/Log/CustomAnsiConsole.cs b/src/N_m3u8DL-RE.Common/Log/CustomAnsiConsole.cs index 9337497..c88f392 100644 --- a/src/N_m3u8DL-RE.Common/Log/CustomAnsiConsole.cs +++ b/src/N_m3u8DL-RE.Common/Log/CustomAnsiConsole.cs @@ -4,7 +4,7 @@ using Spectre.Console; namespace N_m3u8DL_RE.Common.Log; -public class NonAnsiWriter : TextWriter +public partial class NonAnsiWriter : TextWriter { public override Encoding Encoding => Console.OutputEncoding; @@ -28,15 +28,22 @@ public class NonAnsiWriter : TextWriter private void RemoveAnsiEscapeSequences(string? input) { // Use regular expression to remove ANSI escape sequences - string output = Regex.Replace(input ?? "", @"\x1B\[(\d+;?)+m", ""); - output = Regex.Replace(output, @"\[\??\d+[AKlh]", ""); - output = Regex.Replace(output,"[\r\n] +",""); + var output = MyRegex().Replace(input ?? "", ""); + output = MyRegex1().Replace(output, ""); + output = MyRegex2().Replace(output, ""); if (string.IsNullOrWhiteSpace(output)) { return; } Console.Write(output); } + + [GeneratedRegex(@"\x1B\[(\d+;?)+m")] + private static partial Regex MyRegex(); + [GeneratedRegex(@"\[\??\d+[AKlh]")] + private static partial Regex MyRegex1(); + [GeneratedRegex("[\r\n] +")] + private static partial Regex MyRegex2(); } /// diff --git a/src/N_m3u8DL-RE.Common/Log/LogLevel.cs b/src/N_m3u8DL-RE.Common/Log/LogLevel.cs index 11c0387..1a4474f 100644 --- a/src/N_m3u8DL-RE.Common/Log/LogLevel.cs +++ b/src/N_m3u8DL-RE.Common/Log/LogLevel.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace N_m3u8DL_RE.Common.Log; +namespace N_m3u8DL_RE.Common.Log; public enum LogLevel { diff --git a/src/N_m3u8DL-RE.Common/Log/Logger.cs b/src/N_m3u8DL-RE.Common/Log/Logger.cs index d2f7cd8..f6422ab 100644 --- a/src/N_m3u8DL-RE.Common/Log/Logger.cs +++ b/src/N_m3u8DL-RE.Common/Log/Logger.cs @@ -4,7 +4,7 @@ using System.Text.RegularExpressions; namespace N_m3u8DL_RE.Common.Log; -public partial class Logger +public static partial class Logger { [GeneratedRegex("{}")] private static partial Regex VarsRepRegex(); @@ -80,24 +80,23 @@ public partial class Logger Console.WriteLine(subWrite); } - if (IsWriteFile && File.Exists(LogFilePath)) + if (!IsWriteFile || !File.Exists(LogFilePath)) return; + + var plain = write.RemoveMarkup() + subWrite.RemoveMarkup(); + try { - var plain = write.RemoveMarkup() + subWrite.RemoveMarkup(); - try + // 进入写入 + LogWriteLock.EnterWriteLock(); + using (StreamWriter sw = File.AppendText(LogFilePath)) { - // 进入写入 - LogWriteLock.EnterWriteLock(); - using (StreamWriter sw = File.AppendText(LogFilePath)) - { - sw.WriteLine(plain); - } - } - finally - { - // 释放占用 - LogWriteLock.ExitWriteLock(); + sw.WriteLine(plain); } } + finally + { + // 释放占用 + LogWriteLock.ExitWriteLock(); + } } catch (Exception) { @@ -117,82 +116,74 @@ public partial class Logger public static void Info(string data, params object[] ps) { - if (LogLevel >= LogLevel.INFO) - { - data = ReplaceVars(data, ps); - var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : "; - HandleLog(write, data); - } + if (LogLevel < LogLevel.INFO) return; + + data = ReplaceVars(data, ps); + var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : "; + HandleLog(write, data); } public static void InfoMarkUp(string data, params object[] ps) { - if (LogLevel >= LogLevel.INFO) - { - data = ReplaceVars(data, ps); - var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : " + data; - HandleLog(write); - } + if (LogLevel < LogLevel.INFO) return; + + data = ReplaceVars(data, ps); + var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : " + data; + HandleLog(write); } public static void Debug(string data, params object[] ps) { - if (LogLevel >= LogLevel.DEBUG) - { - data = ReplaceVars(data, ps); - var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: "; - HandleLog(write, data); - } + if (LogLevel < LogLevel.DEBUG) return; + + data = ReplaceVars(data, ps); + var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: "; + HandleLog(write, data); } public static void DebugMarkUp(string data, params object[] ps) { - if (LogLevel >= LogLevel.DEBUG) - { - data = ReplaceVars(data, ps); - var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: " + data; - HandleLog(write); - } + if (LogLevel < LogLevel.DEBUG) return; + + data = ReplaceVars(data, ps); + var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: " + data; + HandleLog(write); } public static void Warn(string data, params object[] ps) { - if (LogLevel >= LogLevel.WARN) - { - data = ReplaceVars(data, ps); - var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : "; - HandleLog(write, data); - } + if (LogLevel < LogLevel.WARN) return; + + data = ReplaceVars(data, ps); + var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : "; + HandleLog(write, data); } public static void WarnMarkUp(string data, params object[] ps) { - if (LogLevel >= LogLevel.WARN) - { - data = ReplaceVars(data, ps); - var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : " + data; - HandleLog(write); - } + if (LogLevel < LogLevel.WARN) return; + + data = ReplaceVars(data, ps); + var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : " + data; + HandleLog(write); } public static void Error(string data, params object[] ps) { - if (LogLevel >= LogLevel.ERROR) - { - data = ReplaceVars(data, ps); - var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: "; - HandleLog(write, data); - } + if (LogLevel < LogLevel.ERROR) return; + + data = ReplaceVars(data, ps); + var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: "; + HandleLog(write, data); } public static void ErrorMarkUp(string data, params object[] ps) { - if (LogLevel >= LogLevel.ERROR) - { - data = ReplaceVars(data, ps); - var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: " + data; - HandleLog(write); - } + if (LogLevel < LogLevel.ERROR) return; + + data = ReplaceVars(data, ps); + var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: " + data; + HandleLog(write); } public static void ErrorMarkUp(Exception exception) @@ -213,24 +204,23 @@ public partial class Logger /// public static void Extra(string data, params object[] ps) { - if (IsWriteFile && File.Exists(LogFilePath)) + if (!IsWriteFile || !File.Exists(LogFilePath)) return; + + data = ReplaceVars(data, ps); + var plain = GetCurrTime() + " " + "EXTRA: " + data.RemoveMarkup(); + try { - data = ReplaceVars(data, ps); - var plain = GetCurrTime() + " " + "EXTRA: " + data.RemoveMarkup(); - try + // 进入写入 + LogWriteLock.EnterWriteLock(); + using (StreamWriter sw = File.AppendText(LogFilePath)) { - // 进入写入 - LogWriteLock.EnterWriteLock(); - using (StreamWriter sw = File.AppendText(LogFilePath)) - { - sw.WriteLine(plain, Encoding.UTF8); - } - } - finally - { - // 释放占用 - LogWriteLock.ExitWriteLock(); + sw.WriteLine(plain, Encoding.UTF8); } } + finally + { + // 释放占用 + LogWriteLock.ExitWriteLock(); + } } } \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs index d98e17a..39983ae 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs @@ -2,7 +2,7 @@ public static class ResString { - public static string CurrentLoc = "en-US"; + public static string CurrentLoc { get; set; } = "en-US"; public static readonly string ReLiveTs = ""; public static string singleFileRealtimeDecryptWarn => GetText("singleFileRealtimeDecryptWarn"); @@ -140,13 +140,11 @@ public static class ResString private static string GetText(string key) { - if (!StaticText.LANG_DIC.ContainsKey(key)) + if (!StaticText.LANG_DIC.TryGetValue(key, out var textObj)) return "<...LANG TEXT MISSING...>"; - if (CurrentLoc == "zh-CN" || CurrentLoc == "zh-SG" || CurrentLoc == "zh-Hans") - return StaticText.LANG_DIC[key].ZH_CN; - if (CurrentLoc.StartsWith("zh-")) - return StaticText.LANG_DIC[key].ZH_TW; - return StaticText.LANG_DIC[key].EN_US; + if (CurrentLoc is "zh-CN" or "zh-SG" or "zh-Hans") + return textObj.ZH_CN; + return CurrentLoc.StartsWith("zh-") ? textObj.ZH_TW : textObj.EN_US; } } \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index f184d25..7b3c259 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -1,14 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace N_m3u8DL_RE.Common.Resource; -namespace N_m3u8DL_RE.Common.Resource; - -internal class StaticText +internal static class StaticText { - public static Dictionary LANG_DIC = new() + public static readonly Dictionary LANG_DIC = new() { ["singleFileSplitWarn"] = new TextContainer ( @@ -228,9 +222,9 @@ internal class StaticText ), ["cmd_decryptionBinaryPath"] = new TextContainer ( - zhCN: "MP4解密所用工具的全路径, 例如 C:\\Tools\\mp4decrypt.exe", - zhTW: "MP4解密所用工具的全路徑, 例如 C:\\Tools\\mp4decrypt.exe", - enUS: "Full path to the tool used for MP4 decryption, like C:\\Tools\\mp4decrypt.exe" + zhCN: @"MP4解密所用工具的全路径, 例如 C:\Tools\mp4decrypt.exe", + zhTW: @"MP4解密所用工具的全路徑, 例如 C:\Tools\mp4decrypt.exe", + enUS: @"Full path to the tool used for MP4 decryption, like C:\Tools\mp4decrypt.exe" ), ["cmd_delAfterDone"] = new TextContainer ( @@ -240,15 +234,15 @@ internal class StaticText ), ["cmd_ffmpegBinaryPath"] = new TextContainer ( - zhCN: "ffmpeg可执行程序全路径, 例如 C:\\Tools\\ffmpeg.exe", - zhTW: "ffmpeg可執行程序全路徑, 例如 C:\\Tools\\ffmpeg.exe", - enUS: "Full path to the ffmpeg binary, like C:\\Tools\\ffmpeg.exe" + zhCN: @"ffmpeg可执行程序全路径, 例如 C:\Tools\ffmpeg.exe", + zhTW: @"ffmpeg可執行程序全路徑, 例如 C:\Tools\ffmpeg.exe", + enUS: @"Full path to the ffmpeg binary, like C:\Tools\ffmpeg.exe" ), ["cmd_mkvmergeBinaryPath"] = new TextContainer ( - zhCN: "mkvmerge可执行程序全路径, 例如 C:\\Tools\\mkvmerge.exe", - zhTW: "mkvmerge可執行程序全路徑, 例如 C:\\Tools\\mkvmerge.exe", - enUS: "Full path to the mkvmerge binary, like C:\\Tools\\mkvmerge.exe" + zhCN: @"mkvmerge可执行程序全路径, 例如 C:\Tools\mkvmerge.exe", + zhTW: @"mkvmerge可執行程序全路徑, 例如 C:\Tools\mkvmerge.exe", + enUS: @"Full path to the mkvmerge binary, like C:\Tools\mkvmerge.exe" ), ["cmd_liveFixVttByAudio"] = new TextContainer ( diff --git a/src/N_m3u8DL-RE.Common/Util/GlobalUtil.cs b/src/N_m3u8DL-RE.Common/Util/GlobalUtil.cs index b8e3ba7..f86036a 100644 --- a/src/N_m3u8DL-RE.Common/Util/GlobalUtil.cs +++ b/src/N_m3u8DL-RE.Common/Util/GlobalUtil.cs @@ -67,8 +67,7 @@ public static class GlobalUtil { var fileExt = OperatingSystem.IsWindows() ? ".exe" : ""; var searchPath = new[] { Environment.CurrentDirectory, Path.GetDirectoryName(Environment.ProcessPath) }; - var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? - Array.Empty(); + var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? []; return searchPath.Concat(envPath).Select(p => Path.Combine(p!, name + fileExt)).FirstOrDefault(File.Exists); } } \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Util/HTTPUtil.cs b/src/N_m3u8DL-RE.Common/Util/HTTPUtil.cs index c04fdb4..954c7b7 100644 --- a/src/N_m3u8DL-RE.Common/Util/HTTPUtil.cs +++ b/src/N_m3u8DL-RE.Common/Util/HTTPUtil.cs @@ -99,7 +99,7 @@ public static class HTTPUtil private static bool CheckMPEG2TS(HttpResponseMessage? webResponse) { var mediaType = webResponse?.Content.Headers.ContentType?.MediaType?.ToLower(); - return mediaType == "video/ts" || mediaType == "video/mp2t" || mediaType == "video/mpeg"; + return mediaType is "video/ts" or "video/mp2t" or "video/mpeg"; } /// diff --git a/src/N_m3u8DL-RE.Parser/Config/ParserConfig.cs b/src/N_m3u8DL-RE.Parser/Config/ParserConfig.cs index f04e242..0105ba9 100644 --- a/src/N_m3u8DL-RE.Parser/Config/ParserConfig.cs +++ b/src/N_m3u8DL-RE.Parser/Config/ParserConfig.cs @@ -7,11 +7,11 @@ namespace N_m3u8DL_RE.Parser.Config; public class ParserConfig { - public string Url { get; set; } + public string Url { get; set; } = string.Empty; - public string OriginalUrl { get; set; } + public string OriginalUrl { get; set; } = string.Empty; - public string BaseUrl { get; set; } + public string BaseUrl { get; set; } = string.Empty; public Dictionary CustomParserArgs { get; } = new(); diff --git a/src/N_m3u8DL-RE.Parser/Constants/DASHTags.cs b/src/N_m3u8DL-RE.Parser/Constants/DASHTags.cs index 3bba8fb..bb0f252 100644 --- a/src/N_m3u8DL-RE.Parser/Constants/DASHTags.cs +++ b/src/N_m3u8DL-RE.Parser/Constants/DASHTags.cs @@ -1,15 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace N_m3u8DL_RE.Parser.Constants; -namespace N_m3u8DL_RE.Parser.Constants; - -internal class DASHTags +internal static class DASHTags { - public static string TemplateRepresentationID = "$RepresentationID$"; - public static string TemplateBandwidth = "$Bandwidth$"; - public static string TemplateNumber = "$Number$"; - public static string TemplateTime = "$Time$"; + public const string TemplateRepresentationID = "$RepresentationID$"; + public const string TemplateBandwidth = "$Bandwidth$"; + public const string TemplateNumber = "$Number$"; + public const string TemplateTime = "$Time$"; } \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Parser/Constants/HLSTags.cs b/src/N_m3u8DL-RE.Parser/Constants/HLSTags.cs index ef59755..0dd932f 100644 --- a/src/N_m3u8DL-RE.Parser/Constants/HLSTags.cs +++ b/src/N_m3u8DL-RE.Parser/Constants/HLSTags.cs @@ -1,37 +1,31 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace N_m3u8DL_RE.Parser.Constants; -namespace N_m3u8DL_RE.Parser.Constants; - -internal class HLSTags +internal static class HLSTags { - public static string ext_m3u = "#EXTM3U"; - public static string ext_x_targetduration = "#EXT-X-TARGETDURATION"; - public static string ext_x_media_sequence = "#EXT-X-MEDIA-SEQUENCE"; - public static string ext_x_discontinuity_sequence = "#EXT-X-DISCONTINUITY-SEQUENCE"; - public static string ext_x_program_date_time = "#EXT-X-PROGRAM-DATE-TIME"; - public static string ext_x_media = "#EXT-X-MEDIA"; - public static string ext_x_playlist_type = "#EXT-X-PLAYLIST-TYPE"; - public static string ext_x_key = "#EXT-X-KEY"; - public static string ext_x_stream_inf = "#EXT-X-STREAM-INF"; - public static string ext_x_version = "#EXT-X-VERSION"; - public static string ext_x_allow_cache = "#EXT-X-ALLOW-CACHE"; - public static string ext_x_endlist = "#EXT-X-ENDLIST"; - public static string extinf = "#EXTINF"; - public static string ext_i_frames_only = "#EXT-X-I-FRAMES-ONLY"; - public static string ext_x_byterange = "#EXT-X-BYTERANGE"; - public static string ext_x_i_frame_stream_inf = "#EXT-X-I-FRAME-STREAM-INF"; - public static string ext_x_discontinuity = "#EXT-X-DISCONTINUITY"; - public static string ext_x_cue_out_start = "#EXT-X-CUE-OUT"; - public static string ext_x_cue_out = "#EXT-X-CUE-OUT-CONT"; - public static string ext_is_independent_segments = "#EXT-X-INDEPENDENT-SEGMENTS"; - public static string ext_x_scte35 = "#EXT-OATCLS-SCTE35"; - public static string ext_x_cue_start = "#EXT-X-CUE-OUT"; - public static string ext_x_cue_end = "#EXT-X-CUE-IN"; - public static string ext_x_cue_span = "#EXT-X-CUE-SPAN"; - public static string ext_x_map = "#EXT-X-MAP"; - public static string ext_x_start = "#EXT-X-START"; + public const string ext_m3u = "#EXTM3U"; + public const string ext_x_targetduration = "#EXT-X-TARGETDURATION"; + public const string ext_x_media_sequence = "#EXT-X-MEDIA-SEQUENCE"; + public const string ext_x_discontinuity_sequence = "#EXT-X-DISCONTINUITY-SEQUENCE"; + public const string ext_x_program_date_time = "#EXT-X-PROGRAM-DATE-TIME"; + public const string ext_x_media = "#EXT-X-MEDIA"; + public const string ext_x_playlist_type = "#EXT-X-PLAYLIST-TYPE"; + public const string ext_x_key = "#EXT-X-KEY"; + public const string ext_x_stream_inf = "#EXT-X-STREAM-INF"; + public const string ext_x_version = "#EXT-X-VERSION"; + public const string ext_x_allow_cache = "#EXT-X-ALLOW-CACHE"; + public const string ext_x_endlist = "#EXT-X-ENDLIST"; + public const string extinf = "#EXTINF"; + public const string ext_i_frames_only = "#EXT-X-I-FRAMES-ONLY"; + public const string ext_x_byterange = "#EXT-X-BYTERANGE"; + public const string ext_x_i_frame_stream_inf = "#EXT-X-I-FRAME-STREAM-INF"; + public const string ext_x_discontinuity = "#EXT-X-DISCONTINUITY"; + public const string ext_x_cue_out_start = "#EXT-X-CUE-OUT"; + public const string ext_x_cue_out = "#EXT-X-CUE-OUT-CONT"; + public const string ext_is_independent_segments = "#EXT-X-INDEPENDENT-SEGMENTS"; + public const string ext_x_scte35 = "#EXT-OATCLS-SCTE35"; + public const string ext_x_cue_start = "#EXT-X-CUE-OUT"; + public const string ext_x_cue_end = "#EXT-X-CUE-IN"; + public const string ext_x_cue_span = "#EXT-X-CUE-SPAN"; + public const string ext_x_map = "#EXT-X-MAP"; + public const string ext_x_start = "#EXT-X-START"; } \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Parser/Constants/MSSTags.cs b/src/N_m3u8DL-RE.Parser/Constants/MSSTags.cs index 35b894b..f0aaea9 100644 --- a/src/N_m3u8DL-RE.Parser/Constants/MSSTags.cs +++ b/src/N_m3u8DL-RE.Parser/Constants/MSSTags.cs @@ -1,15 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace N_m3u8DL_RE.Parser.Constants; -namespace N_m3u8DL_RE.Parser.Constants; - -internal class MSSTags +internal static class MSSTags { - public static string Bitrate = "{Bitrate}"; - public static string Bitrate_BK = "{bitrate}"; - public static string StartTime = "{start_time}"; - public static string StartTime_BK = "{start time}"; + public const string Bitrate = "{Bitrate}"; + public const string Bitrate_BK = "{bitrate}"; + public const string StartTime = "{start_time}"; + public const string StartTime_BK = "{start time}"; } \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs b/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs index 603246e..2501867 100644 --- a/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs +++ b/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs @@ -4,19 +4,14 @@ using N_m3u8DL_RE.Common.Util; using N_m3u8DL_RE.Parser.Config; using N_m3u8DL_RE.Parser.Constants; using N_m3u8DL_RE.Parser.Util; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; namespace N_m3u8DL_RE.Parser.Extractor; // https://blog.csdn.net/leek5533/article/details/117750191 -internal class DASHExtractor2 : IExtractor +internal partial class DASHExtractor2 : IExtractor { private static EncryptMethod DEFAULT_METHOD = EncryptMethod.CENC; @@ -37,10 +32,7 @@ internal class DASHExtractor2 : IExtractor private void SetInitUrl() { this.MpdUrl = ParserConfig.Url ?? string.Empty; - if (!string.IsNullOrEmpty(ParserConfig.BaseUrl)) - this.BaseUrl = ParserConfig.BaseUrl; - else - this.BaseUrl = this.MpdUrl; + this.BaseUrl = !string.IsNullOrEmpty(ParserConfig.BaseUrl) ? ParserConfig.BaseUrl : this.MpdUrl; } private string ExtendBaseUrl(XElement element, string oriBaseUrl) @@ -57,13 +49,11 @@ internal class DASHExtractor2 : IExtractor private double? GetFrameRate(XElement element) { var frameRate = element.Attribute("frameRate")?.Value; - if (frameRate != null && frameRate.Contains('/')) - { - var d = Convert.ToDouble(frameRate.Split('/')[0]) / Convert.ToDouble(frameRate.Split('/')[1]); - frameRate = d.ToString("0.000"); - return Convert.ToDouble(frameRate); - } - return null; + if (frameRate == null || !frameRate.Contains('/')) return null; + + var d = Convert.ToDouble(frameRate.Split('/')[0]) / Convert.ToDouble(frameRate.Split('/')[1]); + frameRate = d.ToString("0.000"); + return Convert.ToDouble(frameRate); } public Task> ExtractStreamsAsync(string rawText) @@ -180,7 +170,7 @@ internal class DASHExtractor2 : IExtractor streamSpec.Extension = mTypeSplit.Length == 2 ? mTypeSplit[1] : null; } // 优化字幕场景识别 - if (streamSpec.Codecs == "stpp" || streamSpec.Codecs == "wvtt") + if (streamSpec.Codecs is "stpp" or "wvtt") { streamSpec.MediaType = MediaType.SUBTITLES; } @@ -493,7 +483,7 @@ internal class DASHExtractor2 : IExtractor else { // 修复mp4类型字幕 - if (streamSpec.MediaType == MediaType.SUBTITLES && streamSpec.Extension == "mp4") + if (streamSpec is { MediaType: MediaType.SUBTITLES, Extension: "mp4" }) { streamSpec.Extension = "m4s"; } @@ -513,20 +503,17 @@ internal class DASHExtractor2 : IExtractor } // 为视频设置默认轨道 - var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO); - var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES); - foreach (var item in streamList) + var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO).ToList(); + var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES).ToList(); + foreach (var item in streamList.Where(item => !string.IsNullOrEmpty(item.Resolution))) { - if (!string.IsNullOrEmpty(item.Resolution)) + if (aL.Count != 0) { - if (aL.Any()) - { - item.AudioId = aL.OrderByDescending(x => x.Bandwidth).First().GroupId; - } - if (sL.Any()) - { - item.SubtitleId = sL.OrderByDescending(x => x.Bandwidth).First().GroupId; - } + item.AudioId = aL.OrderByDescending(x => x.Bandwidth).First().GroupId; + } + if (sL.Count != 0) + { + item.SubtitleId = sL.OrderByDescending(x => x.Bandwidth).First().GroupId; } } @@ -541,8 +528,7 @@ internal class DASHExtractor2 : IExtractor private string? FilterLanguage(string? v) { if (v == null) return null; - if (Regex.IsMatch(v, "^[\\w_\\-\\d]+$")) return v; - return "und"; + return LangCodeRegex().IsMatch(v) ? v : "und"; } public async Task RefreshPlayListAsync(List streamSpecs) @@ -581,22 +567,21 @@ internal class DASHExtractor2 : IExtractor private Task ProcessUrlAsync(List streamSpecs) { - for (int i = 0; i < streamSpecs.Count; i++) + foreach (var streamSpec in streamSpecs) { - var playlist = streamSpecs[i].Playlist; - if (playlist != null) + var playlist = streamSpec.Playlist; + if (playlist == null) continue; + + if (playlist.MediaInit != null) { - if (playlist.MediaInit != null) + playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url); + } + for (var ii = 0; ii < playlist!.MediaParts.Count; ii++) + { + var part = playlist.MediaParts[ii]; + foreach (var mediaSegment in part.MediaSegments) { - playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url); - } - for (int ii = 0; ii < playlist!.MediaParts.Count; ii++) - { - var part = playlist.MediaParts[ii]; - for (int iii = 0; iii < part.MediaSegments.Count; iii++) - { - part.MediaSegments[iii].Url = PreProcessUrl(part.MediaSegments[iii].Url); - } + mediaSegment.Url = PreProcessUrl(mediaSegment.Url); } } } @@ -633,4 +618,7 @@ internal class DASHExtractor2 : IExtractor } } } + + [GeneratedRegex(@"^[\w_\-\d]+$")] + private static partial Regex LangCodeRegex(); } \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs b/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs index b188561..3e7668b 100644 --- a/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs +++ b/src/N_m3u8DL-RE.Parser/Extractor/HLSExtractor.cs @@ -5,12 +5,6 @@ using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Resource; using N_m3u8DL_RE.Parser.Util; using N_m3u8DL_RE.Parser.Constants; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; using N_m3u8DL_RE.Common.Util; namespace N_m3u8DL_RE.Parser.Extractor; @@ -26,8 +20,6 @@ internal class HLSExtractor : IExtractor public ParserConfig ParserConfig { get; set; } - private HLSExtractor() { } - public HLSExtractor(ParserConfig parserConfig) { this.ParserConfig = parserConfig; @@ -37,14 +29,7 @@ internal class HLSExtractor : IExtractor private void SetBaseUrl() { - if (!string.IsNullOrEmpty(ParserConfig.BaseUrl)) - { - this.BaseUrl = ParserConfig.BaseUrl; - } - else - { - this.BaseUrl = this.M3u8Url; - } + this.BaseUrl = !string.IsNullOrEmpty(ParserConfig.BaseUrl) ? ParserConfig.BaseUrl : this.M3u8Url; } /// @@ -87,7 +72,7 @@ internal class HLSExtractor : IExtractor { MasterM3u8Flag = true; - List streams = new List(); + List streams = []; using StringReader sr = new StringReader(M3u8Content); string? line; @@ -201,7 +186,7 @@ internal class HLSExtractor : IExtractor streams.Add(streamSpec); } - else if (line.StartsWith("#")) + else if (line.StartsWith('#')) { continue; } @@ -237,22 +222,22 @@ internal class HLSExtractor : IExtractor long startIndex; Playlist playlist = new(); - List mediaParts = new(); + List mediaParts = []; // 当前的加密信息 EncryptInfo currentEncryptInfo = new(); if (ParserConfig.CustomMethod != null) currentEncryptInfo.Method = ParserConfig.CustomMethod.Value; - if (ParserConfig.CustomeKey != null && ParserConfig.CustomeKey.Length > 0) + if (ParserConfig.CustomeKey is { Length: > 0 }) currentEncryptInfo.Key = ParserConfig.CustomeKey; - if (ParserConfig.CustomeIV != null && ParserConfig.CustomeIV.Length > 0) + if (ParserConfig.CustomeIV is { Length: > 0 }) currentEncryptInfo.IV = ParserConfig.CustomeIV; // 上次读取到的加密行,#EXT-X-KEY:…… string lastKeyLine = ""; MediaPart mediaPart = new(); MediaSegment segment = new(); - List segments = new(); + List segments = []; while ((line = sr.ReadLine()) != null) @@ -304,20 +289,19 @@ internal class HLSExtractor : IExtractor // 修复YK去除广告后的遗留问题 if (hasAd && mediaParts.Count > 0) { - segments = mediaParts[mediaParts.Count - 1].MediaSegments; + segments = mediaParts[^1].MediaSegments; mediaParts.RemoveAt(mediaParts.Count - 1); hasAd = false; continue; } // 常规情况的#EXT-X-DISCONTINUITY标记,新建part - if (!hasAd && segments.Count >= 1) + if (hasAd || segments.Count < 1) continue; + + mediaParts.Add(new MediaPart { - mediaParts.Add(new MediaPart() - { - MediaSegments = segments, - }); - segments = new(); - } + MediaSegments = segments, + }); + segments = new(); } // 解析KEY else if (line.StartsWith(HLSTags.ext_x_key)) @@ -382,13 +366,11 @@ internal class HLSExtractor : IExtractor playlist.MediaInit.ExpectLength = n; playlist.MediaInit.StartRange = o ?? 0L; } - // 是否有加密,有的话写入KEY和IV - if (currentEncryptInfo.Method != EncryptMethod.NONE) - { - playlist.MediaInit.EncryptInfo.Method = currentEncryptInfo.Method; - playlist.MediaInit.EncryptInfo.Key = currentEncryptInfo.Key; - playlist.MediaInit.EncryptInfo.IV = currentEncryptInfo.IV ?? HexUtil.HexToBytes(Convert.ToString(segIndex, 16).PadLeft(32, '0')); - } + if (currentEncryptInfo.Method == EncryptMethod.NONE) continue; + // 有加密的话写入KEY和IV + playlist.MediaInit.EncryptInfo.Method = currentEncryptInfo.Method; + playlist.MediaInit.EncryptInfo.Key = currentEncryptInfo.Key; + playlist.MediaInit.EncryptInfo.IV = currentEncryptInfo.IV ?? HexUtil.HexToBytes(Convert.ToString(segIndex, 16).PadLeft(32, '0')); } // 遇到了其他的map,说明已经不是一个视频了,全部丢弃即可 else @@ -409,7 +391,7 @@ internal class HLSExtractor : IExtractor } } // 评论行不解析 - else if (line.StartsWith("#")) continue; + else if (line.StartsWith('#')) continue; // 空白行不解析 else if (line.StartsWith("\r\n")) continue; // 解析分片的地址 @@ -488,15 +470,15 @@ internal class HLSExtractor : IExtractor } var playlist = await ParseListAsync(); - return new List - { + return + [ new() { Url = ParserConfig.Url, Playlist = playlist, Extension = playlist.MediaInit != null ? "mp4" : "ts" } - }; + ]; } private async Task LoadM3u8FromUrlAsync(string url) @@ -539,11 +521,10 @@ internal class HLSExtractor : IExtractor foreach (var l in lists) { var match = newStreams.Where(n => n.ToShortString() == l.ToShortString()).ToList(); - if (match.Any()) - { - Logger.DebugMarkUp($"{l.Url} => {match.First().Url}"); - l.Url = match.First().Url; - } + if (match.Count == 0) continue; + + Logger.DebugMarkUp($"{l.Url} => {match.First().Url}"); + l.Url = match.First().Url; } } @@ -556,7 +537,7 @@ internal class HLSExtractor : IExtractor // 直接重新加载m3u8 await LoadM3u8FromUrlAsync(lists[i].Url!); } - catch (HttpRequestException) when (MasterM3u8Flag == true) + catch (HttpRequestException) when (MasterM3u8Flag) { Logger.WarnMarkUp("Can not load m3u8. Try refreshing url from master url..."); // 当前URL无法加载 尝试从Master链接中刷新URL diff --git a/src/N_m3u8DL-RE.Parser/Extractor/IExtractor.cs b/src/N_m3u8DL-RE.Parser/Extractor/IExtractor.cs index 708f5b9..adf9a8b 100644 --- a/src/N_m3u8DL-RE.Parser/Extractor/IExtractor.cs +++ b/src/N_m3u8DL-RE.Parser/Extractor/IExtractor.cs @@ -1,10 +1,5 @@ using N_m3u8DL_RE.Parser.Config; using N_m3u8DL_RE.Common.Entity; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using N_m3u8DL_RE.Common.Enum; namespace N_m3u8DL_RE.Parser.Extractor; diff --git a/src/N_m3u8DL-RE.Parser/Extractor/MSSExtractor.cs b/src/N_m3u8DL-RE.Parser/Extractor/MSSExtractor.cs index 9c2b5b4..a96dfab 100644 --- a/src/N_m3u8DL-RE.Parser/Extractor/MSSExtractor.cs +++ b/src/N_m3u8DL-RE.Parser/Extractor/MSSExtractor.cs @@ -6,14 +6,7 @@ using N_m3u8DL_RE.Parser.Config; using N_m3u8DL_RE.Parser.Constants; using N_m3u8DL_RE.Parser.Mp4; using N_m3u8DL_RE.Parser.Util; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml; using System.Xml.Linq; namespace N_m3u8DL_RE.Parser.Extractor; @@ -46,10 +39,7 @@ internal partial class MSSExtractor : IExtractor private void SetInitUrl() { this.IsmUrl = ParserConfig.Url ?? string.Empty; - if (!string.IsNullOrEmpty(ParserConfig.BaseUrl)) - this.BaseUrl = ParserConfig.BaseUrl; - else - this.BaseUrl = this.IsmUrl; + this.BaseUrl = !string.IsNullOrEmpty(ParserConfig.BaseUrl) ? ParserConfig.BaseUrl : this.IsmUrl; } public Task> ExtractStreamsAsync(string rawText) @@ -248,20 +238,17 @@ internal partial class MSSExtractor : IExtractor } // 为视频设置默认轨道 - var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO); - var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES); - foreach (var item in streamList) + var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO).ToList(); + var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES).ToList(); + foreach (var item in streamList.Where(item => !string.IsNullOrEmpty(item.Resolution))) { - if (!string.IsNullOrEmpty(item.Resolution)) + if (aL.Count != 0) { - if (aL.Any()) - { - item.AudioId = aL.First().GroupId; - } - if (sL.Any()) - { - item.SubtitleId = sL.First().GroupId; - } + item.AudioId = aL.First().GroupId; + } + if (sL.Count != 0) + { + item.SubtitleId = sL.First().GroupId; } } @@ -318,22 +305,21 @@ internal partial class MSSExtractor : IExtractor private Task ProcessUrlAsync(List streamSpecs) { - for (int i = 0; i < streamSpecs.Count; i++) + foreach (var streamSpec in streamSpecs) { - var playlist = streamSpecs[i].Playlist; - if (playlist != null) + var playlist = streamSpec.Playlist; + if (playlist == null) continue; + + if (playlist.MediaInit != null) { - if (playlist.MediaInit != null) + playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url); + } + for (var ii = 0; ii < playlist!.MediaParts.Count; ii++) + { + var part = playlist.MediaParts[ii]; + foreach (var segment in part.MediaSegments) { - playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url); - } - for (int ii = 0; ii < playlist!.MediaParts.Count; ii++) - { - var part = playlist.MediaParts[ii]; - for (int iii = 0; iii < part.MediaSegments.Count; iii++) - { - part.MediaSegments[iii].Url = PreProcessUrl(part.MediaSegments[iii].Url); - } + segment.Url = PreProcessUrl(segment.Url); } } } diff --git a/src/N_m3u8DL-RE.Parser/Mp4/BinaryReader2.cs b/src/N_m3u8DL-RE.Parser/Mp4/BinaryReader2.cs index 4d66979..c9257e4 100644 --- a/src/N_m3u8DL-RE.Parser/Mp4/BinaryReader2.cs +++ b/src/N_m3u8DL-RE.Parser/Mp4/BinaryReader2.cs @@ -1,70 +1,62 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace Mp4SubtitleParser; -namespace Mp4SubtitleParser +// make BinaryReader in Big Endian +class BinaryReader2 : BinaryReader { - // make BinaryReader in Big Endian - class BinaryReader2 : BinaryReader + public BinaryReader2(System.IO.Stream stream) : base(stream) { } + + public bool HasMoreData() { - public BinaryReader2(System.IO.Stream stream) : base(stream) { } - - public bool HasMoreData() - { - return BaseStream.Position < BaseStream.Length; - } - - public long GetLength() - { - return BaseStream.Length; - } - - public long GetPosition() - { - return BaseStream.Position; - } - - public override int ReadInt32() - { - var data = base.ReadBytes(4); - if (BitConverter.IsLittleEndian) - Array.Reverse(data); - return BitConverter.ToInt32(data, 0); - } - - public override short ReadInt16() - { - var data = base.ReadBytes(2); - if (BitConverter.IsLittleEndian) - Array.Reverse(data); - return BitConverter.ToInt16(data, 0); - } - - public override long ReadInt64() - { - var data = base.ReadBytes(8); - if (BitConverter.IsLittleEndian) - Array.Reverse(data); - return BitConverter.ToInt64(data, 0); - } - - public override uint ReadUInt32() - { - var data = base.ReadBytes(4); - if (BitConverter.IsLittleEndian) - Array.Reverse(data); - return BitConverter.ToUInt32(data, 0); - } - - public override ulong ReadUInt64() - { - var data = base.ReadBytes(8); - if (BitConverter.IsLittleEndian) - Array.Reverse(data); - return BitConverter.ToUInt64(data, 0); - } + return BaseStream.Position < BaseStream.Length; } -} + + public long GetLength() + { + return BaseStream.Length; + } + + public long GetPosition() + { + return BaseStream.Position; + } + + public override int ReadInt32() + { + var data = base.ReadBytes(4); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToInt32(data, 0); + } + + public override short ReadInt16() + { + var data = base.ReadBytes(2); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToInt16(data, 0); + } + + public override long ReadInt64() + { + var data = base.ReadBytes(8); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToInt64(data, 0); + } + + public override uint ReadUInt32() + { + var data = base.ReadBytes(4); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToUInt32(data, 0); + } + + public override ulong ReadUInt64() + { + var data = base.ReadBytes(8); + if (BitConverter.IsLittleEndian) + Array.Reverse(data); + return BitConverter.ToUInt64(data, 0); + } +} \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Parser/Mp4/BinaryWriter2.cs b/src/N_m3u8DL-RE.Parser/Mp4/BinaryWriter2.cs index f185779..0910b12 100644 --- a/src/N_m3u8DL-RE.Parser/Mp4/BinaryWriter2.cs +++ b/src/N_m3u8DL-RE.Parser/Mp4/BinaryWriter2.cs @@ -1,89 +1,83 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Text; -namespace Mp4SubtitleParser +namespace Mp4SubtitleParser; + +// make BinaryWriter in Big Endian +class BinaryWriter2 : BinaryWriter { - // make BinaryWriter in Big Endian - class BinaryWriter2 : BinaryWriter + private static bool IsLittleEndian = BitConverter.IsLittleEndian; + public BinaryWriter2(System.IO.Stream stream) : base(stream) { } + + + public void WriteUInt(decimal n, int offset = 0) { - private static bool IsLittleEndian = BitConverter.IsLittleEndian; - public BinaryWriter2(System.IO.Stream stream) : base(stream) { } - - - public void WriteUInt(decimal n, int offset = 0) - { - var arr = BitConverter.GetBytes((uint)n); - if (IsLittleEndian) - Array.Reverse(arr); - if (offset != 0) - arr = arr[offset..]; - BaseStream.Write(arr); - } - - public override void Write(string text) - { - BaseStream.Write(Encoding.ASCII.GetBytes(text)); - } - - public void WriteInt(decimal n, int offset = 0) - { - var arr = BitConverter.GetBytes((int)n); - if (IsLittleEndian) - Array.Reverse(arr); - if (offset != 0) - arr = arr[offset..]; - BaseStream.Write(arr); - } - - public void WriteULong(decimal n, int offset = 0) - { - var arr = BitConverter.GetBytes((ulong)n); - if (IsLittleEndian) - Array.Reverse(arr); - if (offset != 0) - arr = arr[offset..]; - BaseStream.Write(arr); - } - - public void WriteUShort(decimal n, int padding = 0) - { - var arr = BitConverter.GetBytes((ushort)n); - if (IsLittleEndian) - Array.Reverse(arr); - while (padding > 0) - { - arr = arr.Concat(new byte[] { 0x00 }).ToArray(); - padding--; - } - BaseStream.Write(arr); - } - - public void WriteShort(decimal n, int padding = 0) - { - var arr = BitConverter.GetBytes((short)n); - if (IsLittleEndian) - Array.Reverse(arr); - while (padding > 0) - { - arr = arr.Concat(new byte[] { 0x00 }).ToArray(); - padding--; - } - BaseStream.Write(arr); - } - - public void WriteByte(byte n, int padding = 0) - { - var arr = new byte[] { n }; - while (padding > 0) - { - arr = arr.Concat(new byte[] { 0x00 }).ToArray(); - padding--; - } - BaseStream.Write(arr); - } + var arr = BitConverter.GetBytes((uint)n); + if (IsLittleEndian) + Array.Reverse(arr); + if (offset != 0) + arr = arr[offset..]; + BaseStream.Write(arr); } -} + + public override void Write(string text) + { + BaseStream.Write(Encoding.ASCII.GetBytes(text)); + } + + public void WriteInt(decimal n, int offset = 0) + { + var arr = BitConverter.GetBytes((int)n); + if (IsLittleEndian) + Array.Reverse(arr); + if (offset != 0) + arr = arr[offset..]; + BaseStream.Write(arr); + } + + public void WriteULong(decimal n, int offset = 0) + { + var arr = BitConverter.GetBytes((ulong)n); + if (IsLittleEndian) + Array.Reverse(arr); + if (offset != 0) + arr = arr[offset..]; + BaseStream.Write(arr); + } + + public void WriteUShort(decimal n, int padding = 0) + { + var arr = BitConverter.GetBytes((ushort)n); + if (IsLittleEndian) + Array.Reverse(arr); + while (padding > 0) + { + arr = arr.Concat(new byte[] { 0x00 }).ToArray(); + padding--; + } + BaseStream.Write(arr); + } + + public void WriteShort(decimal n, int padding = 0) + { + var arr = BitConverter.GetBytes((short)n); + if (IsLittleEndian) + Array.Reverse(arr); + while (padding > 0) + { + arr = arr.Concat(new byte[] { 0x00 }).ToArray(); + padding--; + } + BaseStream.Write(arr); + } + + public void WriteByte(byte n, int padding = 0) + { + var arr = new byte[] { n }; + while (padding > 0) + { + arr = arr.Concat(new byte[] { 0x00 }).ToArray(); + padding--; + } + BaseStream.Write(arr); + } +} \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Parser/Mp4/MP4InitUtil.cs b/src/N_m3u8DL-RE.Parser/Mp4/MP4InitUtil.cs index 864672e..c1b2cda 100644 --- a/src/N_m3u8DL-RE.Parser/Mp4/MP4InitUtil.cs +++ b/src/N_m3u8DL-RE.Parser/Mp4/MP4InitUtil.cs @@ -1,5 +1,4 @@ using N_m3u8DL_RE.Common.Util; -using System.Security.Cryptography; namespace Mp4SubtitleParser { @@ -11,10 +10,10 @@ namespace Mp4SubtitleParser public bool isMultiDRM; } - public class MP4InitUtil + public static class MP4InitUtil { - private static readonly byte[] SYSTEM_ID_WIDEVINE = { 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED }; - private static readonly byte[] SYSTEM_ID_PLAYREADY = { 0x9A, 0x04, 0xF0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xAB, 0x92, 0xE6, 0x5B, 0xE0, 0x88, 0x5F, 0x95 }; + private static readonly byte[] SYSTEM_ID_WIDEVINE = [0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED]; + private static readonly byte[] SYSTEM_ID_PLAYREADY = [0x9A, 0x04, 0xF0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xAB, 0x92, 0xE6, 0x5B, 0xE0, 0x88, 0x5F, 0x95]; public static ParsedMP4Info ReadInit(byte[] data) { @@ -28,22 +27,20 @@ namespace Mp4SubtitleParser .Box("minf", MP4Parser.Children) .Box("stbl", MP4Parser.Children) .FullBox("stsd", MP4Parser.SampleDescription) - .FullBox("pssh", (box) => + .FullBox("pssh", box => { - if (!(box.Version == 0 || box.Version == 1)) + if (box.Version is not (0 or 1)) throw new Exception("PSSH version can only be 0 or 1"); var systemId = box.Reader.ReadBytes(16); - if (SYSTEM_ID_WIDEVINE.SequenceEqual(systemId)) - { - var dataSize = box.Reader.ReadUInt32(); - var psshData = box.Reader.ReadBytes((int)dataSize); - info.PSSH = Convert.ToBase64String(psshData); - if (info.KID == "00000000000000000000000000000000") - { - info.KID = HexUtil.BytesToHex(psshData[2..18]).ToLower(); - info.isMultiDRM = true; - } - } + if (!SYSTEM_ID_WIDEVINE.SequenceEqual(systemId)) return; + + var dataSize = box.Reader.ReadUInt32(); + var psshData = box.Reader.ReadBytes((int)dataSize); + info.PSSH = Convert.ToBase64String(psshData); + if (info.KID != "00000000000000000000000000000000") return; + + info.KID = HexUtil.BytesToHex(psshData[2..18]).ToLower(); + info.isMultiDRM = true; }) .FullBox("encv", MP4Parser.AllData(data => ReadBox(data, info))) .FullBox("enca", MP4Parser.AllData(data => ReadBox(data, info))) @@ -57,11 +54,11 @@ namespace Mp4SubtitleParser private static void ReadBox(byte[] data, ParsedMP4Info info) { // find schm - var schmBytes = new byte[4] { 0x73, 0x63, 0x68, 0x6d }; + byte[] schmBytes = [0x73, 0x63, 0x68, 0x6d]; var schmIndex = 0; - for (int i = 0; i < data.Length - 4; i++) + for (var i = 0; i < data.Length - 4; i++) { - if (new byte[4] { data[i], data[i + 1], data[i + 2], data[i + 3] }.SequenceEqual(schmBytes)) + if (new[] { data[i], data[i + 1], data[i + 2], data[i + 3] }.SequenceEqual(schmBytes)) { schmIndex = i; break; @@ -75,11 +72,11 @@ namespace Mp4SubtitleParser // if (info.Scheme != "cenc") return; // find KID - var tencBytes = new byte[4] { 0x74, 0x65, 0x6E, 0x63 }; + byte[] tencBytes = [0x74, 0x65, 0x6E, 0x6]; var tencIndex = -1; for (int i = 0; i < data.Length - 4; i++) { - if (new byte[4] { data[i], data[i + 1], data[i + 2], data[i + 3] }.SequenceEqual(tencBytes)) + if (new[] { data[i], data[i + 1], data[i + 2], data[i + 3] }.SequenceEqual(tencBytes)) { tencIndex = i; break; diff --git a/src/N_m3u8DL-RE.Parser/Mp4/MP4Parser.cs b/src/N_m3u8DL-RE.Parser/Mp4/MP4Parser.cs index e5ae4b5..73d0182 100644 --- a/src/N_m3u8DL-RE.Parser/Mp4/MP4Parser.cs +++ b/src/N_m3u8DL-RE.Parser/Mp4/MP4Parser.cs @@ -9,12 +9,12 @@ namespace Mp4SubtitleParser { class ParsedBox { - public MP4Parser Parser { get; set; } + public required MP4Parser Parser { get; set; } public bool PartialOkay { get; set; } public long Start { get; set; } public uint Version { get; set; } = 1000; public uint Flags { get; set; } = 1000; - public BinaryReader2 Reader { get; set; } + public required BinaryReader2 Reader { get; set; } public bool Has64BitSize { get; set; } } @@ -28,7 +28,7 @@ namespace Mp4SubtitleParser class TRUN { public uint SampleCount { get; set; } - public List SampleData { get; set; } = new List(); + public List SampleData { get; set; } = []; } class Sample @@ -55,7 +55,7 @@ namespace Mp4SubtitleParser public static BoxHandler AllData(DataHandler handler) { - return (box) => + return box => { var all = box.Reader.GetLength() - box.Reader.GetPosition(); handler(box.Reader.ReadBytes((int)all)); @@ -161,7 +161,7 @@ namespace Mp4SubtitleParser } int payloadSize = (int)(end - reader.GetPosition()); - var payload = (payloadSize > 0) ? reader.ReadBytes(payloadSize) : new byte[0]; + var payload = (payloadSize > 0) ? reader.ReadBytes(payloadSize) : []; var box = new ParsedBox() { Parser = this, diff --git a/src/N_m3u8DL-RE.Parser/Mp4/MP4TtmlUtil.cs b/src/N_m3u8DL-RE.Parser/Mp4/MP4TtmlUtil.cs index 378a085..f91f7ca 100644 --- a/src/N_m3u8DL-RE.Parser/Mp4/MP4TtmlUtil.cs +++ b/src/N_m3u8DL-RE.Parser/Mp4/MP4TtmlUtil.cs @@ -3,75 +3,229 @@ using System.Text; using System.Text.RegularExpressions; using System.Xml; -namespace Mp4SubtitleParser +namespace Mp4SubtitleParser; + +class SubEntity { - class SubEntity + public required string Begin { get; set; } + public required string End { get; set; } + public required string Region { get; set; } + public List Contents { get; set; } = []; + public List ContentStrings { get; set; } = []; + + public override bool Equals(object? obj) { - public string Begin { get; set; } - public string End { get; set; } - public string Region { get; set; } - public List Contents { get; set; } = new(); - public List ContentStrings { get; set; } = new(); - - public override bool Equals(object? obj) - { - return obj is SubEntity entity && - Begin == entity.Begin && - End == entity.End && - Region == entity.Region && - ContentStrings.SequenceEqual(entity.ContentStrings); - } - - public override int GetHashCode() - { - return HashCode.Combine(Begin, End, Region, ContentStrings); - } + return obj is SubEntity entity && + Begin == entity.Begin && + End == entity.End && + Region == entity.Region && + ContentStrings.SequenceEqual(entity.ContentStrings); } - public partial class MP4TtmlUtil + public override int GetHashCode() { - [GeneratedRegex(" \\w+:\\w+=\\\"[^\\\"]*\\\"")] - private static partial Regex AttrRegex(); - [GeneratedRegex("(.+?)<\\/p>")] - private static partial Regex LabelFixRegex(); - [GeneratedRegex("\\")] - private static partial Regex MultiElementsFixRegex(); - [GeneratedRegex("\\([\\s\\S]*?)<\\/smpte:image>")] - private static partial Regex ImageRegex(); + return HashCode.Combine(Begin, End, Region, ContentStrings); + } +} - public static bool CheckInit(byte[] data) +public static partial class MP4TtmlUtil +{ + [GeneratedRegex(" \\w+:\\w+=\\\"[^\\\"]*\\\"")] + private static partial Regex AttrRegex(); + [GeneratedRegex("(.+?)<\\/p>")] + private static partial Regex LabelFixRegex(); + [GeneratedRegex(@"\")] + private static partial Regex MultiElementsFixRegex(); + [GeneratedRegex("\\([\\s\\S]*?)<\\/smpte:image>")] + private static partial Regex ImageRegex(); + + public static bool CheckInit(byte[] data) + { + bool sawSTPP = false; + + // parse init + new MP4Parser() + .Box("moov", MP4Parser.Children) + .Box("trak", MP4Parser.Children) + .Box("mdia", MP4Parser.Children) + .Box("minf", MP4Parser.Children) + .Box("stbl", MP4Parser.Children) + .FullBox("stsd", MP4Parser.SampleDescription) + .Box("stpp", box => { + sawSTPP = true; + }) + .Parse(data); + + return sawSTPP; + } + + private static string ShiftTime(string xmlSrc, long segTimeMs, int index) + { + string Add(string xmlTime) { - bool sawSTPP = false; - - // parse init - new MP4Parser() - .Box("moov", MP4Parser.Children) - .Box("trak", MP4Parser.Children) - .Box("mdia", MP4Parser.Children) - .Box("minf", MP4Parser.Children) - .Box("stbl", MP4Parser.Children) - .FullBox("stsd", MP4Parser.SampleDescription) - .Box("stpp", (box) => { - sawSTPP = true; - }) - .Parse(data); - - return sawSTPP; + var dt = DateTime.ParseExact(xmlTime, "HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture); + var ts = TimeSpan.FromMilliseconds(dt.TimeOfDay.TotalMilliseconds + segTimeMs * index); + return $"{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}.{ts.Milliseconds:000}"; } - private static string ShiftTime(string xmlSrc, long segTimeMs, int index) + if (!xmlSrc.Contains("")) return xmlSrc; + var xmlDoc = new XmlDocument(); + XmlNamespaceManager? nsMgr = null; + xmlDoc.LoadXml(xmlSrc); + var ttNode = xmlDoc.LastChild; + if (nsMgr == null) { - string Add(string xmlTime) - { - var dt = DateTime.ParseExact(xmlTime, "HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture); - var ts = TimeSpan.FromMilliseconds(dt.TimeOfDay.TotalMilliseconds + segTimeMs * index); - return string.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds); - } + var ns = ((XmlElement)ttNode!).GetAttribute("xmlns"); + nsMgr = new XmlNamespaceManager(xmlDoc.NameTable); + nsMgr.AddNamespace("ns", ns); + } - if (!xmlSrc.Contains("")) return xmlSrc; - var xmlDoc = new XmlDocument(); - XmlNamespaceManager? nsMgr = null; - xmlDoc.LoadXml(xmlSrc); + var bodyNode = ttNode!.SelectSingleNode("ns:body", nsMgr); + if (bodyNode == null) + return xmlSrc; + + var _div = bodyNode.SelectSingleNode("ns:div", nsMgr); + // Parse

label + foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!) + { + var _begin = _p.GetAttribute("begin"); + var _end = _p.GetAttribute("end"); + // Handle namespace + foreach (XmlAttribute attr in _p.Attributes) + { + if (attr.LocalName == "begin") _begin = attr.Value; + else if (attr.LocalName == "end") _end = attr.Value; + } + _p.SetAttribute("begin", Add(_begin)); + _p.SetAttribute("end", Add(_end)); + // Console.WriteLine($"{_begin} {_p.GetAttribute("begin")}"); + // Console.WriteLine($"{_end} {_p.GetAttribute("begin")}"); + } + + return xmlDoc.OuterXml; + } + + private static string GetTextFromElement(XmlElement node) + { + var sb = new StringBuilder(); + foreach (XmlNode item in node.ChildNodes) + { + if (item.NodeType == XmlNodeType.Text) + { + sb.Append(item.InnerText.Trim()); + } + else if(item is { NodeType: XmlNodeType.Element, Name: "br" }) + { + sb.AppendLine(); + } + } + return sb.ToString(); + } + + private static List SplitMultipleRootElements(string xml) + { + return !MultiElementsFixRegex().IsMatch(xml) ? [] : MultiElementsFixRegex().Matches(xml).Select(m => m.Value).ToList(); + } + + public static WebVttSub ExtractFromMp4(string item, long segTimeMs, long baseTimestamp = 0L) + { + return ExtractFromMp4s([item], segTimeMs, baseTimestamp); + } + + private static WebVttSub ExtractFromMp4s(IEnumerable items, long segTimeMs, long baseTimestamp = 0L) + { + // read ttmls + List xmls = []; + int segIndex = 0; + foreach (var item in items) + { + var dataSeg = File.ReadAllBytes(item); + + var sawMDAT = false; + // parse media + new MP4Parser() + .Box("mdat", MP4Parser.AllData(data => + { + sawMDAT = true; + // Join this to any previous payload, in case the mp4 has multiple + // mdats. + if (segTimeMs != 0) + { + var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data)); + foreach (var item in datas) + { + xmls.Add(ShiftTime(item, segTimeMs, segIndex)); + } + } + else + { + var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data)); + xmls.AddRange(datas); + } + })) + .Parse(dataSeg,/* partialOkay= */ false); + segIndex++; + } + + return ExtractSub(xmls, baseTimestamp); + } + + public static WebVttSub ExtractFromTTML(string item, long segTimeMs, long baseTimestamp = 0L) + { + return ExtractFromTTMLs([item], segTimeMs, baseTimestamp); + } + + public static WebVttSub ExtractFromTTMLs(IEnumerable items, long segTimeMs, long baseTimestamp = 0L) + { + // read ttmls + List xmls = []; + int segIndex = 0; + foreach (var item in items) + { + var xml = File.ReadAllText(item); + xmls.Add(segTimeMs != 0 ? ShiftTime(xml, segTimeMs, segIndex) : xml); + segIndex++; + } + + return ExtractSub(xmls, baseTimestamp); + } + + private static WebVttSub ExtractSub(List xmls, long baseTimestamp) + { + // parsing + var xmlDoc = new XmlDocument(); + var finalSubs = new List(); + XmlNode? headNode = null; + XmlNamespaceManager? nsMgr = null; + var regex = LabelFixRegex(); + var attrRegex = AttrRegex(); + foreach (var item in xmls) + { + var xmlContent = item; + if (!xmlContent.Contains("{inner}

"); + } + catch (Exception) + { + xmlContentFix = xmlContentFix.Replace(m.Groups[1].Value, System.Web.HttpUtility.HtmlEncode(m.Groups[1].Value)); + } + } + } + xmlDoc.LoadXml(xmlContentFix); var ttNode = xmlDoc.LastChild; if (nsMgr == null) { @@ -79,306 +233,142 @@ namespace Mp4SubtitleParser nsMgr = new XmlNamespaceManager(xmlDoc.NameTable); nsMgr.AddNamespace("ns", ns); } + if (headNode == null) + headNode = ttNode!.SelectSingleNode("ns:head", nsMgr); var bodyNode = ttNode!.SelectSingleNode("ns:body", nsMgr); if (bodyNode == null) - return xmlSrc; + continue; var _div = bodyNode.SelectSingleNode("ns:div", nsMgr); + if (_div == null) + continue; + + + // PNG Subs + var imageDic = new Dictionary(); // id, Base64 + if (ImageRegex().IsMatch(xmlDoc.InnerXml)) + { + foreach (Match img in ImageRegex().Matches(xmlDoc.InnerXml)) + { + imageDic.Add(img.Groups[1].Value.Trim(), img.Groups[2].Value.Trim()); + } + } + + // convert
to

+ if (_div!.SelectNodes("ns:p", nsMgr) == null || _div!.SelectNodes("ns:p", nsMgr)!.Count == 0) + { + foreach (XmlElement _tDiv in bodyNode.SelectNodes("ns:div", nsMgr)!) + { + var _p = xmlDoc.CreateDocumentFragment(); + _p.InnerXml = _tDiv.OuterXml.Replace("

", "

"); + _div.AppendChild(_p); + } + } + // Parse

label foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!) { var _begin = _p.GetAttribute("begin"); var _end = _p.GetAttribute("end"); + var _region = _p.GetAttribute("region"); + var _bgImg = _p.GetAttribute("smpte:backgroundImage"); // Handle namespace foreach (XmlAttribute attr in _p.Attributes) { if (attr.LocalName == "begin") _begin = attr.Value; else if (attr.LocalName == "end") _end = attr.Value; + else if (attr.LocalName == "region") _region = attr.Value; } - _p.SetAttribute("begin", Add(_begin)); - _p.SetAttribute("end", Add(_end)); - // Console.WriteLine($"{_begin} {_p.GetAttribute("begin")}"); - // Console.WriteLine($"{_end} {_p.GetAttribute("begin")}"); - } - - return xmlDoc.OuterXml; - } - - private static string GetTextFromElement(XmlElement node) - { - var sb = new StringBuilder(); - foreach (XmlNode item in node.ChildNodes) - { - if (item.NodeType == XmlNodeType.Text) + var sub = new SubEntity { - sb.Append(item.InnerText.Trim()); - } - else if(item.NodeType == XmlNodeType.Element && item.Name == "br") + Begin = _begin, + End = _end, + Region = _region + }; + + if (string.IsNullOrEmpty(_bgImg)) { - sb.AppendLine(); - } - } - return sb.ToString(); - } - - public static List SplitMultipleRootElements(string xml) - { - if (!MultiElementsFixRegex().IsMatch(xml)) return new List(); - return MultiElementsFixRegex().Matches(xml).Select(m => m.Value).ToList(); - } - - public static WebVttSub ExtractFromMp4(string item, long segTimeMs, long baseTimestamp = 0L) - { - return ExtractFromMp4s(new string[] { item }, segTimeMs, baseTimestamp); - } - - public static WebVttSub ExtractFromMp4s(IEnumerable items, long segTimeMs, long baseTimestamp = 0L) - { - // read ttmls - List xmls = new List(); - int segIndex = 0; - foreach (var item in items) - { - var dataSeg = File.ReadAllBytes(item); - - var sawMDAT = false; - // parse media - new MP4Parser() - .Box("mdat", MP4Parser.AllData((data) => + var _spans = _p.ChildNodes; + // Collect + foreach (XmlNode _node in _spans) { - sawMDAT = true; - // Join this to any previous payload, in case the mp4 has multiple - // mdats. - if (segTimeMs != 0) + if (_node.NodeType == XmlNodeType.Element) { - var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data)); - foreach (var item in datas) - { - xmls.Add(ShiftTime(item, segTimeMs, segIndex)); - } + var _span = (XmlElement)_node; + if (string.IsNullOrEmpty(_span.InnerText)) + continue; + sub.Contents.Add(_span); + sub.ContentStrings.Add(_span.OuterXml); } - else - { - var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data)); - xmls.AddRange(datas); - } - })) - .Parse(dataSeg,/* partialOkay= */ false); - segIndex++; - } - - return ExtractSub(xmls, baseTimestamp); - } - - public static WebVttSub ExtractFromTTML(string item, long segTimeMs, long baseTimestamp = 0L) - { - return ExtractFromTTMLs(new string[] { item }, segTimeMs, baseTimestamp); - } - - public static WebVttSub ExtractFromTTMLs(IEnumerable items, long segTimeMs, long baseTimestamp = 0L) - { - // read ttmls - List xmls = new List(); - int segIndex = 0; - foreach (var item in items) - { - var xml = File.ReadAllText(item); - if (segTimeMs != 0) - { - xmls.Add(ShiftTime(xml, segTimeMs, segIndex)); - } - else - { - xmls.Add(xml); - } - segIndex++; - } - - return ExtractSub(xmls, baseTimestamp); - } - - private static WebVttSub ExtractSub(List xmls, long baseTimestamp) - { - // parsing - var xmlDoc = new XmlDocument(); - var finalSubs = new List(); - XmlNode? headNode = null; - XmlNamespaceManager? nsMgr = null; - var regex = LabelFixRegex(); - var attrRegex = AttrRegex(); - foreach (var item in xmls) - { - var xmlContent = item; - if (!xmlContent.Contains("{inner}

"); - } - catch (Exception) - { - xmlContentFix = xmlContentFix.Replace(m.Groups[1].Value, System.Web.HttpUtility.HtmlEncode(m.Groups[1].Value)); - } - } - } - xmlDoc.LoadXml(xmlContentFix); - var ttNode = xmlDoc.LastChild; - if (nsMgr == null) - { - var ns = ((XmlElement)ttNode!).GetAttribute("xmlns"); - nsMgr = new XmlNamespaceManager(xmlDoc.NameTable); - nsMgr.AddNamespace("ns", ns); - } - if (headNode == null) - headNode = ttNode!.SelectSingleNode("ns:head", nsMgr); - - var bodyNode = ttNode!.SelectSingleNode("ns:body", nsMgr); - if (bodyNode == null) - continue; - - var _div = bodyNode.SelectSingleNode("ns:div", nsMgr); - if (_div == null) - continue; - - - // PNG Subs - var imageDic = new Dictionary(); // id, Base64 - if (ImageRegex().IsMatch(xmlDoc.InnerXml)) - { - foreach (Match img in ImageRegex().Matches(xmlDoc.InnerXml)) - { - imageDic.Add(img.Groups[1].Value.Trim(), img.Groups[2].Value.Trim()); - } - } - - // convert
to

- if (_div!.SelectNodes("ns:p", nsMgr) == null || _div!.SelectNodes("ns:p", nsMgr)!.Count == 0) - { - foreach (XmlElement _tDiv in bodyNode.SelectNodes("ns:div", nsMgr)!) - { - var _p = xmlDoc.CreateDocumentFragment(); - _p.InnerXml = _tDiv.OuterXml.Replace("

", "

"); - _div.AppendChild(_p); - } - } - - // Parse

label - foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!) - { - var _begin = _p.GetAttribute("begin"); - var _end = _p.GetAttribute("end"); - var _region = _p.GetAttribute("region"); - var _bgImg = _p.GetAttribute("smpte:backgroundImage"); - // Handle namespace - foreach (XmlAttribute attr in _p.Attributes) - { - if (attr.LocalName == "begin") _begin = attr.Value; - else if (attr.LocalName == "end") _end = attr.Value; - else if (attr.LocalName == "region") _region = attr.Value; - } - var sub = new SubEntity - { - Begin = _begin, - End = _end, - Region = _region - }; - - if (string.IsNullOrEmpty(_bgImg)) - { - var _spans = _p.ChildNodes; - // Collect - foreach (XmlNode _node in _spans) - { - if (_node.NodeType == XmlNodeType.Element) - { - var _span = (XmlElement)_node; - if (string.IsNullOrEmpty(_span.InnerText)) - continue; - sub.Contents.Add(_span); - sub.ContentStrings.Add(_span.OuterXml); - } - else if (_node.NodeType == XmlNodeType.Text) - { - var _span = new XmlDocument().CreateElement("span"); - _span.InnerText = _node.Value!; - sub.Contents.Add(_span); - sub.ContentStrings.Add(_span.OuterXml); - } - } - } - else - { - var id = _bgImg.Replace("#", ""); - if (imageDic.ContainsKey(id)) + else if (_node.NodeType == XmlNodeType.Text) { var _span = new XmlDocument().CreateElement("span"); - _span.InnerText = $"Base64::{imageDic[id]}"; + _span.InnerText = _node.Value!; sub.Contents.Add(_span); sub.ContentStrings.Add(_span.OuterXml); } } - - // Check if one

has been splitted - var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings)); - // Skip empty lines - if (sub.ContentStrings.Count > 0) - { - // Extend

duration - if (index != -1) - finalSubs[index].End = sub.End; - else if (!finalSubs.Contains(sub)) - finalSubs.Add(sub); - } } - } - - - var dic = new Dictionary(); - foreach (var sub in finalSubs) - { - var key = $"{sub.Begin} --> {sub.End}"; - foreach (var item in sub.Contents) + else { - if (dic.ContainsKey(key)) + var id = _bgImg.Replace("#", ""); + if (imageDic.TryGetValue(id, out var value)) { - if (item.GetAttribute("tts:fontStyle") == "italic" || item.GetAttribute("tts:fontStyle") == "oblique") - dic[key] = $"{dic[key]}\r\n{GetTextFromElement(item)}"; - else - dic[key] = $"{dic[key]}\r\n{GetTextFromElement(item)}"; - } - else - { - if (item.GetAttribute("tts:fontStyle") == "italic" || item.GetAttribute("tts:fontStyle") == "oblique") - dic.Add(key, $"{GetTextFromElement(item)}"); - else - dic.Add(key, GetTextFromElement(item)); + var _span = new XmlDocument().CreateElement("span"); + _span.InnerText = $"Base64::{value}"; + sub.Contents.Add(_span); + sub.ContentStrings.Add(_span.OuterXml); } } + + // Check if one

has been splitted + var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings)); + // Skip empty lines + if (sub.ContentStrings.Count <= 0) + continue; + // Extend

duration + if (index != -1) + finalSubs[index].End = sub.End; + else if (!finalSubs.Contains(sub)) + finalSubs.Add(sub); } - - - var vtt = new StringBuilder(); - vtt.AppendLine("WEBVTT"); - foreach (var item in dic) - { - vtt.AppendLine(item.Key); - vtt.AppendLine(item.Value); - vtt.AppendLine(); - } - - return WebVttSub.Parse(vtt.ToString(), baseTimestamp); } + + + var dic = new Dictionary(); + foreach (var sub in finalSubs) + { + var key = $"{sub.Begin} --> {sub.End}"; + foreach (var item in sub.Contents) + { + if (dic.ContainsKey(key)) + { + if (item.GetAttribute("tts:fontStyle") == "italic" || item.GetAttribute("tts:fontStyle") == "oblique") + dic[key] = $"{dic[key]}\r\n{GetTextFromElement(item)}"; + else + dic[key] = $"{dic[key]}\r\n{GetTextFromElement(item)}"; + } + else + { + if (item.GetAttribute("tts:fontStyle") == "italic" || item.GetAttribute("tts:fontStyle") == "oblique") + dic.Add(key, $"{GetTextFromElement(item)}"); + else + dic.Add(key, GetTextFromElement(item)); + } + } + } + + + var vtt = new StringBuilder(); + vtt.AppendLine("WEBVTT"); + foreach (var item in dic) + { + vtt.AppendLine(item.Key); + vtt.AppendLine(item.Value); + vtt.AppendLine(); + } + + return WebVttSub.Parse(vtt.ToString(), baseTimestamp); } -} +} \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Parser/Mp4/MP4VttUtil.cs b/src/N_m3u8DL-RE.Parser/Mp4/MP4VttUtil.cs index d51842f..b75e45a 100644 --- a/src/N_m3u8DL-RE.Parser/Mp4/MP4VttUtil.cs +++ b/src/N_m3u8DL-RE.Parser/Mp4/MP4VttUtil.cs @@ -3,7 +3,7 @@ using System.Text; namespace Mp4SubtitleParser; -public class MP4VttUtil +public static class MP4VttUtil { public static (bool, uint) CheckInit(byte[] data) { @@ -15,16 +15,16 @@ public class MP4VttUtil .Box("moov", MP4Parser.Children) .Box("trak", MP4Parser.Children) .Box("mdia", MP4Parser.Children) - .FullBox("mdhd", (box) => + .FullBox("mdhd", box => { - if (!(box.Version == 0 || box.Version == 1)) + if (box.Version is not (0 or 1)) throw new Exception("MDHD version can only be 0 or 1"); timescale = MP4Parser.ParseMDHD(box.Reader, box.Version); }) .Box("minf", MP4Parser.Children) .Box("stbl", MP4Parser.Children) .FullBox("stsd", MP4Parser.SampleDescription) - .Box("wvtt", (box) => { + .Box("wvtt", _ => { // A valid vtt init segment, though we have no actual subtitles yet. sawWVTT = true; }) @@ -38,7 +38,7 @@ public class MP4VttUtil if (timescale == 0) throw new Exception("Missing timescale for VTT content!"); - List cues = new(); + List cues = []; foreach (var item in files) { @@ -50,27 +50,27 @@ public class MP4VttUtil byte[]? rawPayload = null; ulong baseTime = 0; ulong defaultDuration = 0; - List presentations = new(); + List presentations = []; // parse media new MP4Parser() .Box("moof", MP4Parser.Children) .Box("traf", MP4Parser.Children) - .FullBox("tfdt", (box) => + .FullBox("tfdt", box => { sawTFDT = true; - if (!(box.Version == 0 || box.Version == 1)) + if (box.Version is not (0 or 1)) throw new Exception("TFDT version can only be 0 or 1"); baseTime = MP4Parser.ParseTFDT(box.Reader, box.Version); }) - .FullBox("tfhd", (box) => + .FullBox("tfhd", box => { if (box.Flags == 1000) throw new Exception("A TFHD box should have a valid flags value"); defaultDuration = MP4Parser.ParseTFHD(box.Reader, box.Flags).DefaultSampleDuration; }) - .FullBox("trun", (box) => + .FullBox("trun", box => { sawTRUN = true; if (box.Version == 1000) @@ -79,7 +79,7 @@ public class MP4VttUtil throw new Exception("A TRUN box should have a valid flags value"); presentations = MP4Parser.ParseTRUN(box.Reader, box.Version, box.Flags).SampleData; }) - .Box("mdat", MP4Parser.AllData((data) => + .Box("mdat", MP4Parser.AllData(data => { if (sawMDAT) throw new Exception("VTT cues in mp4 with multiple MDAT are not currently supported"); @@ -139,8 +139,6 @@ public class MP4VttUtil { if (payload != null) { - if (timescale == 0) - throw new Exception("Timescale should not be zero!"); var cue = ParseVTTC( payload, 0 + (double)startTime / timescale, @@ -192,15 +190,15 @@ public class MP4VttUtil string id = string.Empty; string settings = string.Empty; new MP4Parser() - .Box("payl", MP4Parser.AllData((data) => + .Box("payl", MP4Parser.AllData(data => { payload = Encoding.UTF8.GetString(data); })) - .Box("iden", MP4Parser.AllData((data) => + .Box("iden", MP4Parser.AllData(data => { id = Encoding.UTF8.GetString(data); })) - .Box("sttg", MP4Parser.AllData((data) => + .Box("sttg", MP4Parser.AllData(data => { settings = Encoding.UTF8.GetString(data); })) diff --git a/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs b/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs index e8b6571..7e16eb8 100644 --- a/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs +++ b/src/N_m3u8DL-RE.Parser/Mp4/MSSMoovProcessor.cs @@ -13,7 +13,7 @@ namespace N_m3u8DL_RE.Parser.Mp4; public partial class MSSMoovProcessor { - [GeneratedRegex("\\(.*?)\\<")] + [GeneratedRegex(@"\(.*?)\<")] private static partial Regex KIDRegex(); private static string StartCode = "00000001"; @@ -23,7 +23,7 @@ public partial class MSSMoovProcessor private string CodecPrivateData; private int Timesacle; private long Duration; - private string Language { get => StreamSpec.Language ?? "und"; } + private string Language => StreamSpec.Language ?? "und"; private int Width => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').First()); private int Height => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').Last()); private string StreamType; @@ -36,8 +36,8 @@ public partial class MSSMoovProcessor private bool IsProtection; private string ProtectionSystemId; private string ProtectionData; - private string ProtecitonKID; - private string ProtecitonKID_PR; + private string? ProtecitonKID; + private string? ProtecitonKID_PR; private byte[] UnityMatrix { get @@ -60,10 +60,9 @@ public partial class MSSMoovProcessor private static byte TRACK_IN_MOVIE = 0x2; private static byte TRACK_IN_PREVIEW = 0x4; private static byte SELF_CONTAINED = 0x1; - private static List SupportedFourCC = new() - { - "HVC1","HEV1","AACL","AACH","EC-3","H264","AVC1","DAVC","AVC1","TTML","DVHE","DVH1" - }; + + private static List SupportedFourCC = + ["HVC1", "HEV1", "AACL", "AACH", "EC-3", "H264", "AVC1", "DAVC", "AVC1", "TTML", "DVHE", "DVH1"]; public MSSMoovProcessor(StreamSpec streamSpec) { @@ -95,7 +94,7 @@ public partial class MSSMoovProcessor } } - private static string[] HEVC_GENERAL_PROFILE_SPACE_STRINGS = new string[] { "", "A", "B", "C" }; + private static string[] HEVC_GENERAL_PROFILE_SPACE_STRINGS = ["", "A", "B", "C"]; private int SamplingFrequencyIndex(int samplingRate) => samplingRate switch { 96000 => 0x0, @@ -169,8 +168,8 @@ public partial class MSSMoovProcessor // save kid for playready this.ProtecitonKID_PR = HexUtil.BytesToHex(kidBytes); // fix byte order - var reverse1 = new byte[4] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] }; - var reverse2 = new byte[4] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] }; + var reverse1 = new[] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] }; + var reverse2 = new[] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] }; Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length); Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length); this.ProtecitonKID = HexUtil.BytesToHex(kidBytes); @@ -217,13 +216,13 @@ public partial class MSSMoovProcessor var schmPayload = new List(); schmPayload.AddRange(Encoding.ASCII.GetBytes("cenc")); // scheme_type 'cenc' => common encryption - schmPayload.AddRange(new byte[] { 0, 1, 0, 0 }); // scheme_version Major version 1, Minor version 0 + schmPayload.AddRange([0, 1, 0, 0]); // scheme_version Major version 1, Minor version 0 var schmBox = FullBox("schm", 0, 0, schmPayload.ToArray()); sinfPayload.AddRange(schmBox); var tencPayload = new List(); - tencPayload.AddRange(new byte[] { 0, 0 }); + tencPayload.AddRange([0, 0]); tencPayload.Add(0x1); // default_IsProtected tencPayload.Add(0x8); // default_Per_Sample_IV_size tencPayload.AddRange(HexUtil.HexToBytes(ProtecitonKID)); // default_KID @@ -368,7 +367,7 @@ public partial class MSSMoovProcessor } else if (StreamType == "text") { - minfPayload.AddRange(FullBox("sthd", 0, 0, new byte[0])); // Subtitle Media Header + minfPayload.AddRange(FullBox("sthd", 0, 0, [])); // Subtitle Media Header } else { @@ -377,7 +376,7 @@ public partial class MSSMoovProcessor var drefPayload = new List(); drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(1); // entry count - drefPayload.AddRange(FullBox("url ", 0, SELF_CONTAINED, new byte[0])); // Data Entry URL Box + drefPayload.AddRange(FullBox("url ", 0, SELF_CONTAINED, [])); // Data Entry URL Box var dinfPayload = FullBox("dref", 0, 0, drefPayload.ToArray()); // Data Reference Box minfPayload.AddRange(Box("dinf", dinfPayload.ToArray())); // Data Information Box @@ -466,10 +465,7 @@ public partial class MSSMoovProcessor writer.Write(sinfBox); return Box("enca", stream.ToArray()); // Encrypted Audio } - else - { - return Box("mp4a", stream.ToArray()); - } + return Box("mp4a", stream.ToArray()); } if (FourCC == "EC-3") { @@ -479,10 +475,7 @@ public partial class MSSMoovProcessor writer.Write(sinfBox); return Box("enca", stream.ToArray()); // Encrypted Audio } - else - { - return Box("ec-3", stream.ToArray()); - } + return Box("ec-3", stream.ToArray()); } } else if (StreamType == "video") @@ -507,11 +500,11 @@ public partial class MSSMoovProcessor var codecPrivateData = HexUtil.HexToBytes(CodecPrivateData); - if (FourCC == "H264" || FourCC == "AVC1" || FourCC == "DAVC" || FourCC == "AVC1") + if (FourCC is "H264" or "AVC1" or "DAVC") { - var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries); - var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 7).First()); - var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 8).First()); + var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries); + var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 7)); + var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 8)); // make avcC var avcC = GetAvcC(sps, pps); writer.Write(avcC); @@ -521,17 +514,14 @@ public partial class MSSMoovProcessor writer.Write(sinfBox); return Box("encv", stream.ToArray()); // Encrypted Video } - else - { - return Box("avc1", stream.ToArray()); // AVC Simple Entry - } + return Box("avc1", stream.ToArray()); // AVC Simple Entry } - else if (FourCC == "HVC1" || FourCC == "HEV1") + if (FourCC is "HVC1" or "HEV1") { - var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries); - var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First()); - var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First()); - var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First()); + var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries); + var vps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20)); + var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21)); + var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22)); // make hvcC var hvcC = GetHvcC(sps, pps, vps); writer.Write(hvcC); @@ -541,18 +531,15 @@ public partial class MSSMoovProcessor writer.Write(sinfBox); return Box("encv", stream.ToArray()); // Encrypted Video } - else - { - return Box("hvc1", stream.ToArray()); // HEVC Simple Entry - } + return Box("hvc1", stream.ToArray()); // HEVC Simple Entry } // 杜比视界也按照hevc处理 - else if (FourCC == "DVHE" || FourCC == "DVH1") + if (FourCC is "DVHE" or "DVH1") { - var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries); - var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First()); - var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First()); - var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First()); + var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries); + var vps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20)); + var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21)); + var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22)); // make hvcC var hvcC = GetHvcC(sps, pps, vps, "dvh1"); writer.Write(hvcC); @@ -562,15 +549,10 @@ public partial class MSSMoovProcessor writer.Write(sinfBox); return Box("encv", stream.ToArray()); // Encrypted Video } - else - { - return Box("dvh1", stream.ToArray()); // HEVC Simple Entry - } - } - else - { - throw new NotSupportedException(); + return Box("dvh1", stream.ToArray()); // HEVC Simple Entry } + + throw new NotSupportedException(); } else if (StreamType == "text") { @@ -581,10 +563,7 @@ public partial class MSSMoovProcessor writer.Write("\0"); // auxilary mime types(??) return Box("stpp", stream.ToArray()); // TTML Simple Entry } - else - { - throw new NotSupportedException(); - } + throw new NotSupportedException(); } else { @@ -635,7 +614,7 @@ public partial class MSSMoovProcessor while (_reader.BaseStream.Position < _reader.BaseStream.Length) { encList.Add(_reader.ReadByte()); - if (encList.Count >= 3 && encList[encList.Count - 3] == 0x00 && encList[encList.Count - 2] == 0x00 && encList[encList.Count - 1] == 0x03) + if (encList is [.., 0x00, 0x00, 0x03]) { encList.RemoveAt(encList.Count - 1); } @@ -805,7 +784,7 @@ public partial class MSSMoovProcessor new MP4Parser() .Box("moof", MP4Parser.Children) .Box("traf", MP4Parser.Children) - .FullBox("tfhd", (box) => + .FullBox("tfhd", box => { TrackId = (int)box.Reader.ReadUInt32(); }) diff --git a/src/N_m3u8DL-RE.Parser/Processor/ContentProcessor.cs b/src/N_m3u8DL-RE.Parser/Processor/ContentProcessor.cs index ba1a6ea..6e4c561 100644 --- a/src/N_m3u8DL-RE.Parser/Processor/ContentProcessor.cs +++ b/src/N_m3u8DL-RE.Parser/Processor/ContentProcessor.cs @@ -1,10 +1,5 @@ using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Parser.Config; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace N_m3u8DL_RE.Parser.Processor; diff --git a/src/N_m3u8DL-RE.Parser/Processor/DASH/DefaultDASHContentProcessor.cs b/src/N_m3u8DL-RE.Parser/Processor/DASH/DefaultDASHContentProcessor.cs index 97ede43..b7aae0a 100644 --- a/src/N_m3u8DL-RE.Parser/Processor/DASH/DefaultDASHContentProcessor.cs +++ b/src/N_m3u8DL-RE.Parser/Processor/DASH/DefaultDASHContentProcessor.cs @@ -1,11 +1,6 @@ using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Parser.Config; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace N_m3u8DL_RE.Parser.Processor.DASH; @@ -18,11 +13,7 @@ public class DefaultDASHContentProcessor : ContentProcessor { if (extractorType != ExtractorType.MPEG_DASH) return false; - if (mpdContent.Contains(" extractorType == ExtractorType.HLS; @@ -30,7 +25,7 @@ public partial class DefaultHLSContentProcessor : ContentProcessor public override string Process(string m3u8Content, ParserConfig parserConfig) { // 处理content以\r作为换行符的情况 - if (m3u8Content.Contains("\r") && !m3u8Content.Contains("\n")) + if (m3u8Content.Contains('\r') && !m3u8Content.Contains('\n')) { m3u8Content = m3u8Content.Replace("\r", Environment.NewLine); } @@ -51,7 +46,7 @@ public partial class DefaultHLSContentProcessor : ContentProcessor // 针对YK #EXT-X-VERSION:7杜比视界片源修正 if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Content.Contains("ott.cibntv.net") && m3u8Content.Contains("ccode=")) { - Regex ykmap = YkDVRegex(); + var ykmap = YkDVRegex(); foreach (Match m in ykmap.Matches(m3u8Content)) { m3u8Content = m3u8Content.Replace(m.Value, $"#EXTINF:0.000000,\n#EXT-X-BYTERANGE:{m.Groups[2].Value}\n{m.Groups[1].Value}"); diff --git a/src/N_m3u8DL-RE.Parser/Processor/HLS/DefaultHLSKeyProcessor.cs b/src/N_m3u8DL-RE.Parser/Processor/HLS/DefaultHLSKeyProcessor.cs index 9f8826c..7e3eed0 100644 --- a/src/N_m3u8DL-RE.Parser/Processor/HLS/DefaultHLSKeyProcessor.cs +++ b/src/N_m3u8DL-RE.Parser/Processor/HLS/DefaultHLSKeyProcessor.cs @@ -83,12 +83,11 @@ public class DefaultHLSKeyProcessor : KeyProcessor encryptInfo.Method = EncryptMethod.UNKNOWN; } + if (parserConfig.CustomMethod == null) return encryptInfo; + // 处理自定义加密方式 - if (parserConfig.CustomMethod != null) - { - encryptInfo.Method = parserConfig.CustomMethod.Value; - Logger.Warn("METHOD changed from {} to {}", method, encryptInfo.Method); - } + encryptInfo.Method = parserConfig.CustomMethod.Value; + Logger.Warn("METHOD changed from {} to {}", method, encryptInfo.Method); return encryptInfo; } diff --git a/src/N_m3u8DL-RE.Parser/Processor/KeyProcessor.cs b/src/N_m3u8DL-RE.Parser/Processor/KeyProcessor.cs index 66dd612..70b3cf2 100644 --- a/src/N_m3u8DL-RE.Parser/Processor/KeyProcessor.cs +++ b/src/N_m3u8DL-RE.Parser/Processor/KeyProcessor.cs @@ -1,11 +1,6 @@ using N_m3u8DL_RE.Common.Entity; using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Parser.Config; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace N_m3u8DL_RE.Parser.Processor; diff --git a/src/N_m3u8DL-RE.Parser/StreamExtractor.cs b/src/N_m3u8DL-RE.Parser/StreamExtractor.cs index a7ef235..27966dc 100644 --- a/src/N_m3u8DL-RE.Parser/StreamExtractor.cs +++ b/src/N_m3u8DL-RE.Parser/StreamExtractor.cs @@ -1,4 +1,5 @@ -using N_m3u8DL_RE.Parser.Config; +using System.Diagnostics.CodeAnalysis; +using N_m3u8DL_RE.Parser.Config; using N_m3u8DL_RE.Common.Entity; using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Resource; @@ -19,11 +20,6 @@ public class StreamExtractor public Dictionary RawFiles { get; set; } = new(); // 存储(文件名,文件内容) - public StreamExtractor() - { - - } - public StreamExtractor(ParserConfig parserConfig) { this.parserConfig = parserConfig; @@ -54,7 +50,8 @@ public class StreamExtractor LoadSourceFromText(this.rawText); } - public void LoadSourceFromText(string rawText) + [MemberNotNull(nameof(this.rawText), nameof(this.extractor))] + private void LoadSourceFromText(string rawText) { var rawType = "txt"; rawText = rawText.Trim(); diff --git a/src/N_m3u8DL-RE.Parser/Util/ParserUtil.cs b/src/N_m3u8DL-RE.Parser/Util/ParserUtil.cs index 4186e9b..4ed6af3 100644 --- a/src/N_m3u8DL-RE.Parser/Util/ParserUtil.cs +++ b/src/N_m3u8DL-RE.Parser/Util/ParserUtil.cs @@ -3,9 +3,9 @@ using System.Text.RegularExpressions; namespace N_m3u8DL_RE.Parser.Util; -public partial class ParserUtil +public static partial class ParserUtil { - [GeneratedRegex("\\$Number%([^$]+)d\\$")] + [GeneratedRegex(@"\$Number%([^$]+)d\$")] private static partial Regex VarsNumberRegex(); ///

@@ -33,8 +33,7 @@ public partial class ParserUtil { var startIndex = index + (key + "=").Length; var endIndex = startIndex + line[startIndex..].IndexOf(','); - if (endIndex >= startIndex) result = line[startIndex..endIndex]; - else result = line[startIndex..]; + result = endIndex >= startIndex ? line[startIndex..endIndex] : line[startIndex..]; } return result; @@ -49,18 +48,13 @@ public partial class ParserUtil public static (long, long?) GetRange(string input) { var t = input.Split('@'); - if (t.Length > 0) + return t.Length switch { - if (t.Length == 1) - { - return (Convert.ToInt64(t[0]), null); - } - if (t.Length == 2) - { - return (Convert.ToInt64(t[0]), Convert.ToInt64(t[1])); - } - } - return (0, null); + <= 0 => (0, null), + 1 => (Convert.ToInt64(t[0]), null), + 2 => (Convert.ToInt64(t[0]), Convert.ToInt64(t[1])), + _ => (0, null) + }; } /// @@ -111,8 +105,8 @@ public partial class ParserUtil if (string.IsNullOrEmpty(baseurl)) return url; - Uri uri1 = new Uri(baseurl); // 这里直接传完整的URL即可 - Uri uri2 = new Uri(uri1, url); + var uri1 = new Uri(baseurl); // 这里直接传完整的URL即可 + var uri2 = new Uri(uri1, url); url = uri2.ToString(); return url; diff --git a/src/N_m3u8DL-RE/Column/DownloadSpeedColumn.cs b/src/N_m3u8DL-RE/Column/DownloadSpeedColumn.cs index 565eef6..7f943e1 100644 --- a/src/N_m3u8DL-RE/Column/DownloadSpeedColumn.cs +++ b/src/N_m3u8DL-RE/Column/DownloadSpeedColumn.cs @@ -27,7 +27,7 @@ internal sealed class DownloadSpeedColumn : ProgressColumn var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); var flag = task.IsFinished || !task.IsStarted; // 单文件下载汇报进度 - if (!flag && speedContainer.SingleSegment && speedContainer.ResponseLength != null) + if (!flag && speedContainer is { SingleSegment: true, ResponseLength: not null }) { task.MaxValue = (double)speedContainer.ResponseLength; task.Value = speedContainer.RDownloaded; diff --git a/src/N_m3u8DL-RE/Column/RecordingDurationColumn.cs b/src/N_m3u8DL-RE/Column/RecordingDurationColumn.cs index 4bb3190..cf887fb 100644 --- a/src/N_m3u8DL-RE/Column/RecordingDurationColumn.cs +++ b/src/N_m3u8DL-RE/Column/RecordingDurationColumn.cs @@ -25,9 +25,6 @@ internal class RecordingDurationColumn : ProgressColumn { if (_refreshedDurDic == null) return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}", MyStyle).LeftJustified(); - else - { - return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}/{GlobalUtil.FormatTime(_refreshedDurDic[task.Id])}", GreyStyle); - } + return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}/{GlobalUtil.FormatTime(_refreshedDurDic[task.Id])}", GreyStyle); } } \ No newline at end of file diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index 1bd9e00..9fb697d 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -15,14 +15,16 @@ using System.Text.RegularExpressions; namespace N_m3u8DL_RE.CommandLine; -internal partial class CommandInvoker +internal static partial class CommandInvoker { - public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20241130"; + public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20241201"; [GeneratedRegex("((best|worst)\\d*|all)")] private static partial Regex ForStrRegex(); - [GeneratedRegex("(\\d*)-(\\d*)")] + [GeneratedRegex(@"(\d*)-(\d*)")] private static partial Regex RangeRegex(); + [GeneratedRegex(@"([\d\\.]+)(M|K)")] + private static partial Regex SpeedStrRegex(); private static readonly Argument Input = new(name: "input", description: ResString.cmd_Input); private static readonly Option TmpDir = new(["--tmp-dir"], description: ResString.cmd_tmpDir); @@ -110,23 +112,22 @@ internal partial class CommandInvoker private static readonly Option DropSubtitleFilter = new(["-ds", "--drop-subtitle"], description: ResString.cmd_dropSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" }; /// - /// 解析录制直播时长限制 + /// 解析下载速度限制 /// /// /// private static long? ParseSpeedLimit(ArgumentResult result) { - var input = result.Tokens.First().Value.ToUpper(); + var input = result.Tokens[0].Value.ToUpper(); try { - var reg = new Regex("([\\d\\\\.]+)(M|K)"); - if (!reg.IsMatch(input)) throw new ArgumentException(); + var reg = SpeedStrRegex(); + if (!reg.IsMatch(input)) throw new ArgumentException($"Invalid Speed Limit: {input}"); var number = double.Parse(reg.Match(input).Groups[1].Value); if (reg.Match(input).Groups[2].Value == "M") return (long)(number * 1024 * 1024); - else - return (long)(number * 1024); + return (long)(number * 1024); } catch (Exception) { @@ -143,7 +144,7 @@ internal partial class CommandInvoker /// private static CustomRange? ParseCustomRange(ArgumentResult result) { - var input = result.Tokens.First().Value; + var input = result.Tokens[0].Value; // 支持的种类 0-100; 01:00:00-02:30:00; -300; 300-; 05:00-; -03:00; try { @@ -154,7 +155,7 @@ internal partial class CommandInvoker if (arr.Length != 2) throw new ArgumentException("Bad format!"); - if (input.Contains(":")) + if (input.Contains(':')) { return new CustomRange() { @@ -163,7 +164,8 @@ internal partial class CommandInvoker EndSec = arr[1] == "" ? double.MaxValue : OtherUtil.ParseDur(arr[1]).TotalSeconds, }; } - else if (RangeRegex().IsMatch(input)) + + if (RangeRegex().IsMatch(input)) { var left = RangeRegex().Match(input).Groups[1].Value; var right = RangeRegex().Match(input).Groups[2].Value; @@ -192,7 +194,7 @@ internal partial class CommandInvoker /// private static WebProxy? ParseProxy(ArgumentResult result) { - var input = result.Tokens.First().Value; + var input = result.Tokens[0].Value; try { if (string.IsNullOrEmpty(input)) @@ -221,17 +223,16 @@ internal partial class CommandInvoker /// private static byte[]? ParseHLSCustomKey(ArgumentResult result) { - var input = result.Tokens.First().Value; + var input = result.Tokens[0].Value; try { if (string.IsNullOrEmpty(input)) return null; if (File.Exists(input)) return File.ReadAllBytes(input); - else if (HexUtil.TryParseHexString(input, out byte[]? bytes)) + if (HexUtil.TryParseHexString(input, out byte[]? bytes)) return bytes; - else - return Convert.FromBase64String(input); + return Convert.FromBase64String(input); } catch (Exception) { @@ -247,7 +248,7 @@ internal partial class CommandInvoker /// private static TimeSpan? ParseLiveLimit(ArgumentResult result) { - var input = result.Tokens.First().Value; + var input = result.Tokens[0].Value; try { return OtherUtil.ParseDur(input); @@ -266,7 +267,7 @@ internal partial class CommandInvoker /// private static DateTime? ParseStartTime(ArgumentResult result) { - var input = result.Tokens.First().Value; + var input = result.Tokens[0].Value; try { CultureInfo provider = CultureInfo.InvariantCulture; @@ -281,7 +282,7 @@ internal partial class CommandInvoker private static string? ParseSaveName(ArgumentResult result) { - var input = result.Tokens.First().Value; + var input = result.Tokens[0].Value; var newName = OtherUtil.GetValidFileName(input); if (string.IsNullOrEmpty(newName)) { @@ -299,7 +300,7 @@ internal partial class CommandInvoker private static StreamFilter? ParseStreamFilter(ArgumentResult result) { var streamFilter = new StreamFilter(); - var input = result.Tokens.First().Value; + var input = result.Tokens[0].Value; var p = new ComplexParamParser(input); @@ -437,7 +438,7 @@ internal partial class CommandInvoker /// private static MuxOptions? ParseMuxAfterDone(ArgumentResult result) { - var v = result.Tokens.First().Value; + var v = result.Tokens[0].Value; var p = new ComplexParamParser(v); // 混流格式 var format = p.GetValue("format") ?? v.Split(':')[0]; // 若未获取到,直接:前的字符串作为format解析 @@ -577,14 +578,12 @@ internal partial class CommandInvoker // 混流设置 var muxAfterDoneValue = bindingContext.ParseResult.GetValueForOption(MuxAfterDone); - if (muxAfterDoneValue != null) - { - option.MuxAfterDone = true; - option.MuxOptions = muxAfterDoneValue; - if (muxAfterDoneValue.UseMkvmerge) option.MkvmergeBinaryPath = muxAfterDoneValue.BinPath; - else option.FFmpegBinaryPath ??= muxAfterDoneValue.BinPath; - } - + if (muxAfterDoneValue == null) return option; + + option.MuxAfterDone = true; + option.MuxOptions = muxAfterDoneValue; + if (muxAfterDoneValue.UseMkvmerge) option.MkvmergeBinaryPath = muxAfterDoneValue.BinPath; + else option.FFmpegBinaryPath ??= muxAfterDoneValue.BinPath; return option; } @@ -626,7 +625,7 @@ internal partial class CommandInvoker }; rootCommand.TreatUnmatchedTokensAsErrors = true; - rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder()); + rootCommand.SetHandler(async myOption => await action(myOption), new MyOptionBinder()); var parser = new CommandLineBuilder(rootCommand) .UseDefaults() diff --git a/src/N_m3u8DL-RE/CommandLine/ComplexParamParser.cs b/src/N_m3u8DL-RE/CommandLine/ComplexParamParser.cs index 4ee32f6..f41f06a 100644 --- a/src/N_m3u8DL-RE/CommandLine/ComplexParamParser.cs +++ b/src/N_m3u8DL-RE/CommandLine/ComplexParamParser.cs @@ -4,7 +4,7 @@ namespace N_m3u8DL_RE.CommandLine; internal class ComplexParamParser { - private string _arg; + private readonly string _arg; public ComplexParamParser(string arg) { _arg = arg; @@ -16,7 +16,7 @@ internal class ComplexParamParser try { - var index = _arg.IndexOf(key + "="); + var index = _arg.IndexOf(key + "=", StringComparison.Ordinal); if (index == -1) return (_arg.Contains(key) && _arg.EndsWith(key)) ? "true" : null; var chars = _arg[(index + key.Length + 1)..].ToCharArray(); diff --git a/src/N_m3u8DL-RE/Crypto/AESUtil.cs b/src/N_m3u8DL-RE/Crypto/AESUtil.cs index 3b9d299..f1f8259 100644 --- a/src/N_m3u8DL-RE/Crypto/AESUtil.cs +++ b/src/N_m3u8DL-RE/Crypto/AESUtil.cs @@ -2,7 +2,7 @@ namespace N_m3u8DL_RE.Crypto; -internal class AESUtil +internal static class AESUtil { /// /// AES-128解密,解密后原地替换文件 diff --git a/src/N_m3u8DL-RE/Crypto/ChaCha20Util.cs b/src/N_m3u8DL-RE/Crypto/ChaCha20Util.cs index 2edab26..0784b03 100644 --- a/src/N_m3u8DL-RE/Crypto/ChaCha20Util.cs +++ b/src/N_m3u8DL-RE/Crypto/ChaCha20Util.cs @@ -2,7 +2,7 @@ namespace N_m3u8DL_RE.Crypto; -internal class ChaCha20Util +internal static class ChaCha20Util { public static byte[] DecryptPer1024Bytes(byte[] encryptedBuff, byte[] keyBytes, byte[] nonceBytes) { diff --git a/src/N_m3u8DL-RE/DownloadManager/HTTPLiveRecordManager.cs b/src/N_m3u8DL-RE/DownloadManager/HTTPLiveRecordManager.cs index 206b78e..563691f 100644 --- a/src/N_m3u8DL-RE/DownloadManager/HTTPLiveRecordManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/HTTPLiveRecordManager.cs @@ -20,7 +20,7 @@ internal class HTTPLiveRecordManager DownloaderConfig DownloaderConfig; StreamExtractor StreamExtractor; List SelectedSteams; - List OutputFiles = new(); + List OutputFiles = []; DateTime NowDateTime; DateTime? PublishDateTime; bool STOP_FLAG = false; @@ -107,7 +107,7 @@ internal class HTTPLiveRecordManager await Task.Delay(200); if (InfoBuffer.Count < 188 * 5000) continue; - UInt16 ConvertToUint16(IEnumerable bytes) + ushort ConvertToUint16(IEnumerable bytes) { if (BitConverter.IsLittleEndian) bytes = bytes.Reverse(); @@ -217,7 +217,7 @@ internal class HTTPLiveRecordManager return (item, task); }).ToDictionary(item => item.item, item => item.task); - DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue; + DownloaderConfig.MyOptions.LiveRecordLimit ??= TimeSpan.MaxValue; var limit = DownloaderConfig.MyOptions.LiveRecordLimit; if (limit != TimeSpan.MaxValue) Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimit}{GlobalUtil.FormatTime((int)limit.Value.TotalSeconds)}[/]"); diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs index ea39578..7947394 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs @@ -23,7 +23,7 @@ internal class SimpleDownloadManager DownloaderConfig DownloaderConfig; StreamExtractor StreamExtractor; List SelectedSteams; - List OutputFiles = new(); + List OutputFiles = []; public SimpleDownloadManager(DownloaderConfig downloaderConfig, List selectedSteams, StreamExtractor streamExtractor) { @@ -48,13 +48,13 @@ internal class SimpleDownloadManager private void ChangeSpecInfo(StreamSpec streamSpec, List mediainfos, ref bool useAACFilter) { - if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison == true)) + if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison)) { DownloaderConfig.MyOptions.BinaryMerge = true; Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge2}[/]"); } - if (DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison == true)) + if (DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison)) { DownloaderConfig.MyOptions.MuxAfterDone = false; Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]"); @@ -72,7 +72,7 @@ internal class SimpleDownloadManager else if (mediainfos.All(m => m.Type == "Subtitle")) { streamSpec.MediaType = MediaType.SUBTITLES; - if (streamSpec.Extension == null || streamSpec.Extension == "ts") + if (streamSpec.Extension is null or "ts") streamSpec.Extension = "vtt"; } } @@ -81,7 +81,7 @@ internal class SimpleDownloadManager { speedContainer.ResetVars(); bool useAACFilter = false; // ffmpeg合并flag - List mediaInfos = new(); + List mediaInfos = []; ConcurrentDictionary FileDic = new(); var segments = streamSpec.Playlist?.MediaParts.SelectMany(m => m.MediaSegments); @@ -158,7 +158,7 @@ internal class SimpleDownloadManager var path = Path.Combine(tmpDir, "_init.mp4.tmp"); var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers); FileDic[streamSpec.Playlist.MediaInit] = result; - if (result == null || !result.Success) + if (result is not { Success: true }) { throw new Exception("Download init file failed!"); } @@ -166,7 +166,7 @@ internal class SimpleDownloadManager task.Increment(1); // 读取mp4信息 - if (result != null && result.Success) + if (result is { Success: true }) { mp4Info = MP4DecryptUtil.GetMP4Info(result.ActualFilePath); currentKID = mp4Info.KID; @@ -212,12 +212,12 @@ internal class SimpleDownloadManager var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp"); var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers); FileDic[seg] = result; - if (result == null || !result.Success) + if (result is not { Success: true }) { throw new Exception("Download first segment failed!"); } task.Increment(1); - if (result != null && result.Success) + if (result is { Success: true }) { // 修复MSS init if (StreamExtractor.ExtractorType == ExtractorType.MSS) @@ -284,10 +284,10 @@ internal class SimpleDownloadManager var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp"); var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers); FileDic[seg] = result; - if (result != null && result.Success) + if (result is { Success: true }) task.Increment(1); // 实时解密 - if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && result != null && result.Success && !string.IsNullOrEmpty(currentKID)) + if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && result is { Success: true } && !string.IsNullOrEmpty(currentKID)) { var enc = result.ActualFilePath; var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); @@ -304,13 +304,12 @@ internal class SimpleDownloadManager // 修改输出后缀 var outputExt = "." + streamSpec.Extension; if (streamSpec.Extension == null) outputExt = ".ts"; - else if (streamSpec.MediaType == MediaType.AUDIO && (streamSpec.Extension == "m4s" || streamSpec.Extension == "mp4")) outputExt = ".m4a"; - else if (streamSpec.MediaType != MediaType.SUBTITLES && (streamSpec.Extension == "m4s" || streamSpec.Extension == "mp4")) outputExt = ".mp4"; + else if (streamSpec is { MediaType: MediaType.AUDIO, Extension: "m4s" or "mp4" }) outputExt = ".m4a"; + else if (streamSpec.MediaType != MediaType.SUBTITLES && streamSpec.Extension is "m4s" or "mp4") outputExt = ".mp4"; if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == MediaType.SUBTITLES) { - if (DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT) outputExt = ".srt"; - else outputExt = ".vtt"; + outputExt = DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT ? ".srt" : ".vtt"; } var output = Path.Combine(saveDir, saveName + outputExt); @@ -320,7 +319,7 @@ internal class SimpleDownloadManager Logger.WarnMarkUp($"{Path.GetFileName(output)} => {Path.GetFileName(output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output)))}"); } - if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0 && mp4InitFile != "") + if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, Keys.Length: > 0 } && mp4InitFile != "") { File.Delete(mp4InitFile); // shaka/ffmpeg实时解密不需要init文件用于合并 @@ -333,7 +332,7 @@ internal class SimpleDownloadManager // 校验分片数量 if (DownloaderConfig.MyOptions.CheckSegmentsCount && FileDic.Values.Any(s => s == null)) { - Logger.ErrorMarkUp(ResString.segmentCountCheckNotPass, totalCount, FileDic.Values.Where(s => s != null).Count()); + Logger.ErrorMarkUp(ResString.segmentCountCheckNotPass, totalCount, FileDic.Values.Count(s => s != null)); return false; } @@ -351,8 +350,7 @@ internal class SimpleDownloadManager } // 自动修复VTT raw字幕 - if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES - && streamSpec.Extension != null && streamSpec.Extension.Contains("vtt")) + if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("vtt")) { Logger.WarnMarkUp(ResString.fixingVTT); // 排序字幕并修正时间戳 @@ -398,7 +396,7 @@ internal class SimpleDownloadManager if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES && streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")) { - var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault(); + var initFile = FileDic.Values.FirstOrDefault(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")); var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath); var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes); if (sawVtt) @@ -432,8 +430,7 @@ internal class SimpleDownloadManager } // 自动修复TTML raw字幕 - if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES - && streamSpec.Extension != null && streamSpec.Extension.Contains("ttml")) + if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("ttml")) { Logger.WarnMarkUp(ResString.fixingTTML); var first = true; @@ -479,9 +476,8 @@ internal class SimpleDownloadManager } // 自动修复TTML mp4字幕 - if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES - && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s") - && streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp")) + if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("m4s") + && streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp")) { Logger.WarnMarkUp(ResString.fixingTTMLmp4); // sawTtml暂时不判断 @@ -575,7 +571,7 @@ internal class SimpleDownloadManager } // 删除临时文件夹 - if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && mergeSuccess) + if (DownloaderConfig.MyOptions is { SkipMerge: false, DelAfterDone: true } && mergeSuccess) { var files = FileDic.Values.Select(v => v!.ActualFilePath); foreach (var file in files) @@ -598,7 +594,7 @@ internal class SimpleDownloadManager } // 调用mp4decrypt解密 - if (mergeSuccess && File.Exists(output) && !string.IsNullOrEmpty(currentKID) && !DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0) + if (mergeSuccess && File.Exists(output) && !string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions is { MP4RealTimeDecryption: false, Keys.Length: > 0 }) { var enc = output; var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); @@ -700,7 +696,7 @@ internal class SimpleDownloadManager var success = Results.Values.All(v => v == true); // 删除临时文件夹 - if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && success) + if (DownloaderConfig.MyOptions is { SkipMerge: false, DelAfterDone: true } && success) { foreach (var item in StreamExtractor.RawFiles) { diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs index 66dab78..7f1cee4 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs @@ -27,7 +27,7 @@ internal class SimpleLiveRecordManager2 StreamExtractor StreamExtractor; List SelectedSteams; ConcurrentDictionary PipeSteamNamesDic = new(); - List OutputFiles = new(); + List OutputFiles = []; DateTime? PublishDateTime; bool STOP_FLAG = false; int WAIT_SEC = 0; // 刷新间隔 @@ -41,7 +41,7 @@ internal class SimpleLiveRecordManager2 ConcurrentDictionary DateTimeDic = new(); // 上次下载的dateTime CancellationTokenSource CancellationTokenSource = new(); // 取消Wait - private readonly object lockObj = new object(); + private readonly Lock lockObj = new(); TimeSpan? audioStart = null; public SimpleLiveRecordManager2(DownloaderConfig downloaderConfig, List selectedSteams, StreamExtractor streamExtractor) @@ -60,9 +60,9 @@ internal class SimpleLiveRecordManager2 if (_key != null) { if (DownloaderConfig.MyOptions.Keys == null) - DownloaderConfig.MyOptions.Keys = new string[] { _key }; + DownloaderConfig.MyOptions.Keys = [_key]; else - DownloaderConfig.MyOptions.Keys = DownloaderConfig.MyOptions.Keys.Concat(new string[] { _key }).ToArray(); + DownloaderConfig.MyOptions.Keys = [..DownloaderConfig.MyOptions.Keys, _key]; } } @@ -111,13 +111,13 @@ internal class SimpleLiveRecordManager2 private void ChangeSpecInfo(StreamSpec streamSpec, List mediainfos, ref bool useAACFilter) { - if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison == true)) + if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison)) { DownloaderConfig.MyOptions.BinaryMerge = true; Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge2}[/]"); } - if (DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison == true)) + if (DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison)) { DownloaderConfig.MyOptions.MuxAfterDone = false; Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]"); @@ -136,7 +136,7 @@ internal class SimpleLiveRecordManager2 { streamSpec.MediaType = MediaType.SUBTITLES; - if (streamSpec.Extension == null || streamSpec.Extension == "ts") + if (streamSpec.Extension is null or "ts") streamSpec.Extension = "vtt"; } } @@ -151,7 +151,7 @@ internal class SimpleLiveRecordManager2 bool useAACFilter = false; // ffmpeg合并flag bool initDownloaded = false; // 是否下载过init文件 ConcurrentDictionary FileDic = new(); - List mediaInfos = new(); + List mediaInfos = []; Stream? fileOutputStream = null; WebVttSub currentVtt = new(); // 字幕流始终维护一个实例 bool firstSub = true; @@ -197,7 +197,7 @@ internal class SimpleLiveRecordManager2 var path = Path.Combine(tmpDir, "_init.mp4.tmp"); var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers); FileDic[streamSpec.Playlist.MediaInit] = result; - if (result == null || !result.Success) + if (result is not { Success: true }) { throw new Exception("Download init file failed!"); } @@ -205,7 +205,7 @@ internal class SimpleLiveRecordManager2 task.Increment(1); // 读取mp4信息 - if (result != null && result.Success) + if (result is { Success: true }) { currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID; // 从文件读取KEY @@ -257,12 +257,12 @@ internal class SimpleLiveRecordManager2 var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp"); var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers); FileDic[seg] = result; - if (result == null || !result.Success) + if (result is not { Success: true }) { throw new Exception("Download first segment failed!"); } task.Increment(1); - if (result != null && result.Success) + if (result is { Success: true }) { // 修复MSS init if (StreamExtractor.ExtractorType == ExtractorType.MSS) @@ -330,10 +330,10 @@ internal class SimpleLiveRecordManager2 var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp"); var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers); FileDic[seg] = result; - if (result != null && result.Success) + if (result is { Success: true }) task.Increment(1); // 实时解密 - if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && result != null && result.Success && !string.IsNullOrEmpty(currentKID)) + if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && result is { Success: true } && !string.IsNullOrEmpty(currentKID)) { var enc = result.ActualFilePath; var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); @@ -347,14 +347,13 @@ internal class SimpleLiveRecordManager2 }); // 自动修复VTT raw字幕 - if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES - && streamSpec.Extension != null && streamSpec.Extension.Contains("vtt")) + if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("vtt")) { // 排序字幕并修正时间戳 - var keys = FileDic.Keys.OrderBy(k => k.Index); + var keys = FileDic.Keys.OrderBy(k => k.Index).ToList(); foreach (var seg in keys) { - var vttContent = File.ReadAllText(FileDic[seg]!.ActualFilePath); + var vttContent = await File.ReadAllTextAsync(FileDic[seg]!.ActualFilePath); var waitCount = 0; while (DownloaderConfig.MyOptions.LiveFixVttByAudio && audioStart == null && waitCount++ < 5) { @@ -376,7 +375,7 @@ internal class SimpleLiveRecordManager2 if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES && streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")) { - var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault(); + var initFile = FileDic.Values.FirstOrDefault(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")); var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath); var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes); if (sawVtt) @@ -396,10 +395,9 @@ internal class SimpleLiveRecordManager2 } // 自动修复TTML raw字幕 - if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES - && streamSpec.Extension != null && streamSpec.Extension.Contains("ttml")) + if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("ttml")) { - var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key); + var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key).ToList(); if (firstSub) { if (baseTimestamp != 0) @@ -437,9 +435,8 @@ internal class SimpleLiveRecordManager2 } // 自动修复TTML mp4字幕 - if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES - && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s") - && streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp")) + if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("m4s") + && streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp")) { // sawTtml暂时不判断 // var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault(); @@ -498,12 +495,11 @@ internal class SimpleLiveRecordManager2 // 合并 var outputExt = "." + streamSpec.Extension; if (streamSpec.Extension == null) outputExt = ".ts"; - else if (streamSpec.MediaType == MediaType.AUDIO && streamSpec.Extension == "m4s") outputExt = ".m4a"; + else if (streamSpec is { MediaType: MediaType.AUDIO, Extension: "m4s" }) outputExt = ".m4a"; else if (streamSpec.MediaType != MediaType.SUBTITLES && streamSpec.Extension == "m4s") outputExt = ".mp4"; else if (streamSpec.MediaType == MediaType.SUBTITLES) { - if (DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT) outputExt = ".srt"; - else outputExt = ".vtt"; + outputExt = DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT ? ".srt" : ".vtt"; } var output = Path.Combine(saveDir, saveName + outputExt); @@ -536,7 +532,7 @@ internal class SimpleLiveRecordManager2 fileOutputStream = PipeUtil.CreatePipe(pipeName); Logger.InfoMarkUp($"{ResString.namedPipeCreated} [cyan]{pipeName.EscapeMarkup()}[/]"); PipeSteamNamesDic[task.Id] = pipeName; - if (PipeSteamNamesDic.Count == SelectedSteams.Where(x => x.MediaType != MediaType.SUBTITLES).Count()) + if (PipeSteamNamesDic.Count == SelectedSteams.Count(x => x.MediaType != MediaType.SUBTITLES)) { var names = PipeSteamNamesDic.OrderBy(i => i.Key).Select(k => k.Value).ToArray(); Logger.WarnMarkUp($"{ResString.namedPipeMux} [deepskyblue1]{Path.GetFileName(output).EscapeMarkup()}[/]"); @@ -622,24 +618,23 @@ internal class SimpleLiveRecordManager2 break; } - if (fileOutputStream != null) + if (fileOutputStream == null) return true; + + if (!DownloaderConfig.MyOptions.LivePipeMux) { - if (!DownloaderConfig.MyOptions.LivePipeMux) + // 记录所有文件信息 + OutputFiles.Add(new OutputFile() { - // 记录所有文件信息 - OutputFiles.Add(new OutputFile() - { - Index = task.Id, - FilePath = (fileOutputStream as FileStream)!.Name, - LangCode = streamSpec.Language, - Description = streamSpec.Name, - Mediainfos = mediaInfos, - MediaType = streamSpec.MediaType, - }); - } - fileOutputStream.Close(); - fileOutputStream.Dispose(); + Index = task.Id, + FilePath = (fileOutputStream as FileStream)!.Name, + LangCode = streamSpec.Language, + Description = streamSpec.Name, + Mediainfos = mediaInfos, + MediaType = streamSpec.MediaType, + }); } + fileOutputStream.Close(); + fileOutputStream.Dispose(); return true; } @@ -648,78 +643,76 @@ internal class SimpleLiveRecordManager2 { while (!STOP_FLAG) { - if (WAIT_SEC != 0) + if (WAIT_SEC == 0) continue; + + // 1. MPD 所有URL相同 单次请求即可获得所有轨道的信息 + // 2. M3U8 所有URL不同 才需要多次请求 + await Parallel.ForEachAsync(dic, async (dic, _) => { - // 1. MPD 所有URL相同 单次请求即可获得所有轨道的信息 - // 2. M3U8 所有URL不同 才需要多次请求 + var streamSpec = dic.Key; + var task = dic.Value; - await Parallel.ForEachAsync(dic, async (dic, _) => + // 达到上限时 不需要刷新了 + if (RecordLimitReachedDic[task.Id]) + return; + + var allHasDatetime = streamSpec.Playlist!.MediaParts[0].MediaSegments.All(s => s.DateTime != null); + if (!SamePathDic.ContainsKey(task.Id)) { - var streamSpec = dic.Key; - var task = dic.Value; - - // 达到上限时 不需要刷新了 - if (RecordLimitReachedDic[task.Id]) - return; - - var allHasDatetime = streamSpec.Playlist!.MediaParts[0].MediaSegments.All(s => s.DateTime != null); - if (!SamePathDic.ContainsKey(task.Id)) - { - var allName = streamSpec.Playlist!.MediaParts[0].MediaSegments.Select(s => OtherUtil.GetFileNameFromInput(s.Url, false)); - var allSamePath = allName.Count() > 1 && allName.Distinct().Count() == 1; - SamePathDic[task.Id] = allSamePath; - } - // 过滤不需要下载的片段 - FilterMediaSegments(streamSpec, task, allHasDatetime, SamePathDic[task.Id]); - var newList = streamSpec.Playlist!.MediaParts[0].MediaSegments; - if (newList.Count > 0) - { - task.MaxValue += newList.Count; - // 推送给消费者 - await BlockDic[task.Id].SendAsync(newList); - // 更新最新链接 - LastFileNameDic[task.Id] = GetSegmentName(newList.Last(), allHasDatetime, SamePathDic[task.Id]); - // 尝试更新时间戳 - var dt = newList.Last().DateTime; - DateTimeDic[task.Id] = dt != null ? GetUnixTimestamp(dt.Value) : 0L; - // 累加已获取到的时长 - RefreshedDurDic[task.Id] += (int)newList.Sum(s => s.Duration); - } - - if (!STOP_FLAG && RefreshedDurDic[task.Id] >= DownloaderConfig.MyOptions.LiveRecordLimit?.TotalSeconds) - { - RecordLimitReachedDic[task.Id] = true; - } - - // 检测时长限制 - if (!STOP_FLAG && RecordLimitReachedDic.Values.All(x => x == true)) - { - Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]"); - STOP_FLAG = true; - CancellationTokenSource.Cancel(); - } - }); - - try - { - // Logger.WarnMarkUp($"wait {waitSec}s"); - if (!STOP_FLAG) await Task.Delay(WAIT_SEC * 1000, CancellationTokenSource.Token); - // 刷新列表 - if (!STOP_FLAG) await StreamExtractor.RefreshPlayListAsync(dic.Keys.ToList()); + var allName = streamSpec.Playlist!.MediaParts[0].MediaSegments.Select(s => OtherUtil.GetFileNameFromInput(s.Url, false)); + var allSamePath = allName.Count() > 1 && allName.Distinct().Count() == 1; + SamePathDic[task.Id] = allSamePath; } - catch (OperationCanceledException oce) when (oce.CancellationToken == CancellationTokenSource.Token) + // 过滤不需要下载的片段 + FilterMediaSegments(streamSpec, task, allHasDatetime, SamePathDic[task.Id]); + var newList = streamSpec.Playlist!.MediaParts[0].MediaSegments; + if (newList.Count > 0) { - // 不需要做事 + task.MaxValue += newList.Count; + // 推送给消费者 + await BlockDic[task.Id].SendAsync(newList); + // 更新最新链接 + LastFileNameDic[task.Id] = GetSegmentName(newList.Last(), allHasDatetime, SamePathDic[task.Id]); + // 尝试更新时间戳 + var dt = newList.Last().DateTime; + DateTimeDic[task.Id] = dt != null ? GetUnixTimestamp(dt.Value) : 0L; + // 累加已获取到的时长 + RefreshedDurDic[task.Id] += (int)newList.Sum(s => s.Duration); } - catch (Exception e) + + if (!STOP_FLAG && RefreshedDurDic[task.Id] >= DownloaderConfig.MyOptions.LiveRecordLimit?.TotalSeconds) { - Logger.ErrorMarkUp(e); + RecordLimitReachedDic[task.Id] = true; + } + + // 检测时长限制 + if (!STOP_FLAG && RecordLimitReachedDic.Values.All(x => x)) + { + Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]"); STOP_FLAG = true; - // 停止所有Block - foreach (var target in BlockDic.Values) - { - target.Complete(); - } + CancellationTokenSource.Cancel(); + } + }); + + try + { + // Logger.WarnMarkUp($"wait {waitSec}s"); + if (!STOP_FLAG) await Task.Delay(WAIT_SEC * 1000, CancellationTokenSource.Token); + // 刷新列表 + if (!STOP_FLAG) await StreamExtractor.RefreshPlayListAsync(dic.Keys.ToList()); + } + catch (OperationCanceledException oce) when (oce.CancellationToken == CancellationTokenSource.Token) + { + // 不需要做事 + } + catch (Exception e) + { + Logger.ErrorMarkUp(e); + STOP_FLAG = true; + // 停止所有Block + foreach (var target in BlockDic.Values) + { + target.Complete(); } } } @@ -783,7 +776,7 @@ internal class SimpleLiveRecordManager2 Logger.WarnMarkUp($"set refresh interval to {WAIT_SEC} seconds"); } // 如果没有选中音频 取消通过音频修复vtt时间轴 - if (!SelectedSteams.Any(x => x.MediaType == MediaType.AUDIO)) + if (SelectedSteams.All(x => x.MediaType != MediaType.AUDIO)) { DownloaderConfig.MyOptions.LiveFixVttByAudio = false; } @@ -839,7 +832,7 @@ internal class SimpleLiveRecordManager2 DownloaderConfig.MyOptions.ConcurrentDownload = true; DownloaderConfig.MyOptions.MP4RealTimeDecryption = true; - DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue; + DownloaderConfig.MyOptions.LiveRecordLimit ??= TimeSpan.MaxValue; if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, DecryptionEngine: not DecryptEngine.SHAKA_PACKAGER, Keys.Length: > 0 }) Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]"); var limit = DownloaderConfig.MyOptions.LiveRecordLimit; @@ -865,7 +858,7 @@ internal class SimpleLiveRecordManager2 var success = Results.Values.All(v => v == true); // 删除临时文件夹 - if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && success) + if (DownloaderConfig.MyOptions is { SkipMerge: false, DelAfterDone: true } && success) { foreach (var item in StreamExtractor.RawFiles) { diff --git a/src/N_m3u8DL-RE/Downloader/SimpleDownloader.cs b/src/N_m3u8DL-RE/Downloader/SimpleDownloader.cs index f8a4c1c..35deacf 100644 --- a/src/N_m3u8DL-RE/Downloader/SimpleDownloader.cs +++ b/src/N_m3u8DL-RE/Downloader/SimpleDownloader.cs @@ -25,23 +25,25 @@ internal class SimpleDownloader : IDownloader { var url = segment.Url; var (des, dResult) = await DownClipAsync(url, savePath, speedContainer, segment.StartRange, segment.StopRange, headers, DownloaderConfig.MyOptions.DownloadRetryCount); - if (dResult != null && dResult.Success && dResult.ActualFilePath != des) + if (dResult is { Success: true } && dResult.ActualFilePath != des) { - if (segment.EncryptInfo != null) + switch (segment.EncryptInfo.Method) { - if (segment.EncryptInfo.Method == EncryptMethod.AES_128) + case EncryptMethod.AES_128: { var key = segment.EncryptInfo.Key; var iv = segment.EncryptInfo.IV; AESUtil.AES128Decrypt(dResult.ActualFilePath, key!, iv!); + break; } - else if (segment.EncryptInfo.Method == EncryptMethod.AES_128_ECB) + case EncryptMethod.AES_128_ECB: { var key = segment.EncryptInfo.Key; var iv = segment.EncryptInfo.IV; AESUtil.AES128Decrypt(dResult.ActualFilePath, key!, iv!, System.Security.Cryptography.CipherMode.ECB); + break; } - else if (segment.EncryptInfo.Method == EncryptMethod.CHACHA20) + case EncryptMethod.CHACHA20: { var key = segment.EncryptInfo.Key; var nonce = segment.EncryptInfo.IV; @@ -49,22 +51,22 @@ internal class SimpleDownloader : IDownloader var fileBytes = File.ReadAllBytes(dResult.ActualFilePath); var decrypted = ChaCha20Util.DecryptPer1024Bytes(fileBytes, key!, nonce!); await File.WriteAllBytesAsync(dResult.ActualFilePath, decrypted); + break; } - else if (segment.EncryptInfo.Method == EncryptMethod.SAMPLE_AES_CTR) - { + case EncryptMethod.SAMPLE_AES_CTR: // throw new NotSupportedException("SAMPLE-AES-CTR"); - } + break; + } - // Image头处理 - if (dResult.ImageHeader) - { - await ImageHeaderUtil.ProcessAsync(dResult.ActualFilePath); - } - // Gzip解压 - if (dResult.GzipHeader) - { - await OtherUtil.DeGzipFileAsync(dResult.ActualFilePath); - } + // Image头处理 + if (dResult.ImageHeader) + { + await ImageHeaderUtil.ProcessAsync(dResult.ActualFilePath); + } + // Gzip解压 + if (dResult.GzipHeader) + { + await OtherUtil.DeGzipFileAsync(dResult.ActualFilePath); } // 处理完成后改名 @@ -99,14 +101,15 @@ internal class SimpleDownloader : IDownloader } // 另起线程进行监控 + var cts = cancellationTokenSource; using var watcher = Task.Factory.StartNew(async () => { while (true) { - if (cancellationTokenSource == null || cancellationTokenSource.IsCancellationRequested) break; + if (cts.IsCancellationRequested) break; if (speedContainer.ShouldStop) { - cancellationTokenSource.Cancel(); + cts.Cancel(); Logger.DebugMarkUp("Cancel..."); break; } @@ -123,7 +126,7 @@ internal class SimpleDownloader : IDownloader catch (Exception ex) { Logger.DebugMarkUp($"[grey]{ex.Message.EscapeMarkup()} retryCount: {retryCount}[/]"); - Logger.Debug(url + " " + ex.ToString()); + Logger.Debug(url + " " + ex); Logger.Extra($"Ah oh!{Environment.NewLine}RetryCount => {retryCount}{Environment.NewLine}Exception => {ex.Message}{Environment.NewLine}Url => {url}"); if (retryCount-- > 0) { diff --git a/src/N_m3u8DL-RE/Entity/DownloadResult.cs b/src/N_m3u8DL-RE/Entity/DownloadResult.cs index ac085f8..62193fb 100644 --- a/src/N_m3u8DL-RE/Entity/DownloadResult.cs +++ b/src/N_m3u8DL-RE/Entity/DownloadResult.cs @@ -1,15 +1,8 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace N_m3u8DL_RE.Entity; +namespace N_m3u8DL_RE.Entity; internal class DownloadResult { - public bool Success { get => (ActualContentLength != null && RespContentLength != null) ? (RespContentLength == ActualContentLength) : (ActualContentLength == null ? false : true); } + public bool Success => (ActualContentLength != null && RespContentLength != null) ? (RespContentLength == ActualContentLength) : (ActualContentLength != null); public long? RespContentLength { get; set; } public long? ActualContentLength { get; set; } public bool ImageHeader { get; set; } = false; // 图片伪装 diff --git a/src/N_m3u8DL-RE/Entity/OutputFile.cs b/src/N_m3u8DL-RE/Entity/OutputFile.cs index 849ad78..79f400a 100644 --- a/src/N_m3u8DL-RE/Entity/OutputFile.cs +++ b/src/N_m3u8DL-RE/Entity/OutputFile.cs @@ -9,5 +9,5 @@ internal class OutputFile public required string FilePath { get; set; } public string? LangCode { get; set; } public string? Description { get; set; } - public List Mediainfos { get; set; } = new(); + public List Mediainfos { get; set; } = []; } \ No newline at end of file diff --git a/src/N_m3u8DL-RE/Entity/SpeedContainer.cs b/src/N_m3u8DL-RE/Entity/SpeedContainer.cs index 13f4365..c463cc3 100644 --- a/src/N_m3u8DL-RE/Entity/SpeedContainer.cs +++ b/src/N_m3u8DL-RE/Entity/SpeedContainer.cs @@ -1,13 +1,4 @@ -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; +namespace N_m3u8DL_RE.Entity; internal class SpeedContainer { @@ -24,7 +15,7 @@ internal class SpeedContainer private long _downloaded = 0; private long _Rdownloaded = 0; - public long Downloaded { get => _downloaded; } + public long Downloaded => _downloaded; public int AddLowSpeedCount() { diff --git a/src/N_m3u8DL-RE/Entity/StreamFilter.cs b/src/N_m3u8DL-RE/Entity/StreamFilter.cs index 54e5bf2..f5a3423 100644 --- a/src/N_m3u8DL-RE/Entity/StreamFilter.cs +++ b/src/N_m3u8DL-RE/Entity/StreamFilter.cs @@ -46,6 +46,6 @@ public class StreamFilter if (BandwidthMax != null) sb.Append($"{nameof(BandwidthMax)}: {BandwidthMax} "); if (Role.HasValue) sb.Append($"Role: {Role} "); - return sb.ToString() + $"For: {For}"; + return sb + $"For: {For}"; } } \ No newline at end of file diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs index 40e6223..c2fc961 100644 --- a/src/N_m3u8DL-RE/Program.cs +++ b/src/N_m3u8DL-RE/Program.cs @@ -13,7 +13,6 @@ using N_m3u8DL_RE.Util; using N_m3u8DL_RE.DownloadManager; using N_m3u8DL_RE.CommandLine; using System.Net; -using System.Net.Http.Headers; using N_m3u8DL_RE.Enum; namespace N_m3u8DL_RE; @@ -26,7 +25,7 @@ internal class Program if (OperatingSystem.IsWindows()) { var osVersion = Environment.OSVersion.Version; - if (osVersion.Major < 6 || (osVersion.Major == 6 && osVersion.Minor == 0)) + if (osVersion.Major < 6 || osVersion is { Major: 6, Minor: 0 }) { Environment.SetEnvironmentVariable("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1"); } @@ -38,7 +37,7 @@ internal class Program string loc = ResString.CurrentLoc; string currLoc = Thread.CurrentThread.CurrentUICulture.Name; - if (currLoc == "zh-CN" || currLoc == "zh-SG") loc = "zh-CN"; + if (currLoc is "zh-CN" or "zh-SG") loc = "zh-CN"; else if (currLoc.StartsWith("zh-")) loc = "zh-TW"; // 处理用户-h等请求 @@ -147,33 +146,36 @@ internal class Program // 预先检查 if (option.Keys is { Length: > 0 } || option.KeyTextFile != null) { - if (string.IsNullOrEmpty(option.DecryptionBinaryPath)) + if (!string.IsNullOrEmpty(option.DecryptionBinaryPath) && !File.Exists(option.DecryptionBinaryPath)) { - if (option.DecryptionEngine is DecryptEngine.SHAKA_PACKAGER) + throw new FileNotFoundException(option.DecryptionBinaryPath); + } + switch (option.DecryptionEngine) + { + case DecryptEngine.SHAKA_PACKAGER: { var file = GlobalUtil.FindExecutable("shaka-packager"); var file2 = GlobalUtil.FindExecutable("packager-linux-x64"); var file3 = GlobalUtil.FindExecutable("packager-osx-x64"); var file4 = GlobalUtil.FindExecutable("packager-win-x64"); - if (file == null && file2 == null && file3 == null && file4 == null) throw new FileNotFoundException(ResString.shakaPackagerNotFound); + if (file == null && file2 == null && file3 == null && file4 == null) + throw new FileNotFoundException(ResString.shakaPackagerNotFound); option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4; Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}"); + break; } - else if (option.DecryptionEngine is DecryptEngine.MP4DECRYPT) + case DecryptEngine.MP4DECRYPT: { var file = GlobalUtil.FindExecutable("mp4decrypt"); if (file == null) throw new FileNotFoundException(ResString.mp4decryptNotFound); option.DecryptionBinaryPath = file; Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}"); + break; } - else - { + case DecryptEngine.FFMPEG: + default: option.DecryptionBinaryPath = option.FFmpegBinaryPath; - } - } - else if (!File.Exists(option.DecryptionBinaryPath)) - { - throw new FileNotFoundException(option.DecryptionBinaryPath); + break; } } @@ -283,7 +285,7 @@ internal class Program if (option.AutoSelect) { - if (basicStreams.Any()) + if (basicStreams.Count != 0) selectedStreams.Add(basicStreams.First()); var langs = audios.DistinctBy(a => a.Language).Select(a => a.Language); foreach (var lang in langs) @@ -309,7 +311,7 @@ internal class Program selectedStreams = FilterUtil.SelectStreams(lists); } - if (!selectedStreams.Any()) + if (selectedStreams.Count == 0) throw new Exception(ResString.noStreamsToDownload); // HLS: 选中流中若有没加载出playlist的,加载playlist @@ -363,7 +365,7 @@ internal class Program Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]"); // 开始MuxAfterDone后自动使用二进制版 - if (!option.BinaryMerge && option.MuxAfterDone) + if (option is { BinaryMerge: false, MuxAfterDone: true }) { option.BinaryMerge = true; Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge6}[/]"); @@ -446,24 +448,21 @@ internal class Program static async Task Get302Async(string url) { // this allows you to set the settings so that we can get the redirect url - var handler = new HttpClientHandler() + var handler = new HttpClientHandler { AllowAutoRedirect = false }; - string redirectedUrl = ""; - using (HttpClient client = new(handler)) - using (HttpResponseMessage response = await client.GetAsync(url)) - using (HttpContent content = response.Content) + var redirectedUrl = ""; + using var client = new HttpClient(handler); + using var response = await client.GetAsync(url); + using var content = response.Content; + // ... Read the response to see if we have the redirected url + if (response.StatusCode != HttpStatusCode.Found) return redirectedUrl; + + var headers = response.Headers; + if (headers.Location != null) { - // ... Read the response to see if we have the redirected url - if (response.StatusCode == System.Net.HttpStatusCode.Found) - { - HttpResponseHeaders headers = response.Headers; - if (headers != null && headers.Location != null) - { - redirectedUrl = headers.Location.AbsoluteUri; - } - } + redirectedUrl = headers.Location.AbsoluteUri; } return redirectedUrl; diff --git a/src/N_m3u8DL-RE/Util/DownloadUtil.cs b/src/N_m3u8DL-RE/Util/DownloadUtil.cs index 0e8bd5b..d0787a9 100644 --- a/src/N_m3u8DL-RE/Util/DownloadUtil.cs +++ b/src/N_m3u8DL-RE/Util/DownloadUtil.cs @@ -25,7 +25,7 @@ internal static class DownloadUtil { var buffer = new byte[expect]; _ = await inputStream.ReadAsync(buffer); - await outputStream.WriteAsync(buffer, 0, buffer.Length); + await outputStream.WriteAsync(buffer); speedContainer.Add(buffer.Length); } return new DownloadResult() @@ -81,7 +81,7 @@ internal static class DownloadUtil { HttpResponseHeaders respHeaders = response.Headers; Logger.Debug(respHeaders.ToString()); - if (respHeaders != null && respHeaders.Location != null) + if (respHeaders.Location != null) { var redirectedUrl = ""; if (!respHeaders.Location.IsAbsoluteUri) @@ -108,7 +108,7 @@ internal static class DownloadUtil size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token); speedContainer.Add(size); - await stream.WriteAsync(buffer, 0, size); + await stream.WriteAsync(buffer.AsMemory(0, size)); // 检测imageHeader bool imageHeader = ImageHeaderUtil.IsImageHeader(buffer); // 检测GZip(For DDP Audio) @@ -117,7 +117,7 @@ internal static class DownloadUtil while ((size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token)) > 0) { speedContainer.Add(size); - await stream.WriteAsync(buffer, 0, size); + await stream.WriteAsync(buffer.AsMemory(0, size)); // 限速策略 while (speedContainer.Downloaded > speedContainer.SpeedLimit) { diff --git a/src/N_m3u8DL-RE/Util/FilterUtil.cs b/src/N_m3u8DL-RE/Util/FilterUtil.cs index 1a30e5c..2378c00 100644 --- a/src/N_m3u8DL-RE/Util/FilterUtil.cs +++ b/src/N_m3u8DL-RE/Util/FilterUtil.cs @@ -65,7 +65,7 @@ public static class FilterUtil public static List DoFilterDrop(IEnumerable lists, StreamFilter? filter) { - if (filter == null) return new List(lists); + if (filter == null) return [..lists]; var inputs = lists.Where(_ => true); var selected = DoFilterKeep(lists, filter); @@ -93,9 +93,8 @@ public static class FilterUtil .UseConverter(x => { if (x.Name != null && x.Name.StartsWith("__")) - return $"[darkslategray1]{x.Name.Substring(2)}[/]"; - else - return x.ToString().EscapeMarkup().RemoveMarkup(); + return $"[darkslategray1]{x.Name[2..]}[/]"; + return x.ToString().EscapeMarkup().RemoveMarkup(); }) .Required() .PageSize(10) @@ -107,12 +106,12 @@ public static class FilterUtil var first = streamSpecs.First(); prompt.Select(first); - if (basicStreams.Any()) + if (basicStreams.Count != 0) { prompt.AddChoiceGroup(new StreamSpec() { Name = "__Basic" }, basicStreams); } - if (audios.Any()) + if (audios.Count != 0) { prompt.AddChoiceGroup(new StreamSpec() { Name = "__Audio" }, audios); // 默认音轨 @@ -121,7 +120,7 @@ public static class FilterUtil prompt.Select(audios.First(a => a.GroupId == first.AudioId)); } } - if (subs.Any()) + if (subs.Count != 0) { prompt.AddChoiceGroup(new StreamSpec() { Name = "__Subtitle" }, subs); // 默认字幕轨 diff --git a/src/N_m3u8DL-RE/Util/LargeSingleFileSplitUtil.cs b/src/N_m3u8DL-RE/Util/LargeSingleFileSplitUtil.cs index c4870c5..d35eaaf 100644 --- a/src/N_m3u8DL-RE/Util/LargeSingleFileSplitUtil.cs +++ b/src/N_m3u8DL-RE/Util/LargeSingleFileSplitUtil.cs @@ -80,7 +80,7 @@ internal static class LargeSingleFileSplitUtil // 此函数主要是切片下载逻辑 private static List GetAllClips(string url, long fileSize) { - List clips = new(); + List clips = []; int index = 0; long counter = 0; int perSize = 10 * 1024 * 1024; diff --git a/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs b/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs index df6e95a..3097398 100644 --- a/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs +++ b/src/N_m3u8DL-RE/Util/MP4DecryptUtil.cs @@ -7,7 +7,7 @@ using N_m3u8DL_RE.Enum; namespace N_m3u8DL_RE.Util; -internal static class MP4DecryptUtil +internal static partial class MP4DecryptUtil { private static readonly string ZeroKid = "00000000000000000000000000000000"; public static async Task DecryptAsync(DecryptEngine decryptEngine, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false) @@ -29,7 +29,7 @@ internal static class MP4DecryptUtil if (!string.IsNullOrEmpty(kid)) { var test = keyPairs.Where(k => k.StartsWith(kid)).ToList(); - if (test.Any()) keyPair = test.First(); + if (test.Count != 0) keyPair = test.First(); } // Apple @@ -159,11 +159,10 @@ internal static class MP4DecryptUtil using var reader = new StreamReader(stream); while (await reader.ReadLineAsync() is { } line) { - if (line.Trim().StartsWith(kid)) - { - Logger.InfoMarkUp($"[green]OK[/] [grey]{line.Trim()}[/]"); - return line.Trim(); - } + if (!line.Trim().StartsWith(kid)) continue; + + Logger.InfoMarkUp($"[green]OK[/] [grey]{line.Trim()}[/]"); + return line.Trim(); } } catch (Exception ex) @@ -192,7 +191,7 @@ internal static class MP4DecryptUtil public static string? ReadInitShaka(string output, string bin) { - Regex shakaKeyIdRegex = new("Key for key_id=([0-9a-f]+) was not found"); + Regex shakaKeyIdRegex = KidOutputRegex(); // TODO: handle the case that shaka packager actually decrypted (key ID == ZeroKid) // - stop process @@ -214,4 +213,7 @@ internal static class MP4DecryptUtil p.WaitForExit(); return shakaKeyIdRegex.Match(errorOutput).Groups[1].Value; } + + [GeneratedRegex("Key for key_id=([0-9a-f]+) was not found")] + private static partial Regex KidOutputRegex(); } \ No newline at end of file diff --git a/src/N_m3u8DL-RE/Util/MediainfoUtil.cs b/src/N_m3u8DL-RE/Util/MediainfoUtil.cs index 1e89664..db9c00f 100644 --- a/src/N_m3u8DL-RE/Util/MediainfoUtil.cs +++ b/src/N_m3u8DL-RE/Util/MediainfoUtil.cs @@ -8,23 +8,23 @@ internal static partial class MediainfoUtil { [GeneratedRegex(" Stream #.*")] private static partial Regex TextRegex(); - [GeneratedRegex("#0:\\d(\\[0x\\w+?\\])")] + [GeneratedRegex(@"#0:\d(\[0x\w+?\])")] private static partial Regex IdRegex(); [GeneratedRegex(": (\\w+): (.*)")] private static partial Regex TypeRegex(); [GeneratedRegex("(.*?)(,|$)")] private static partial Regex BaseInfoRegex(); - [GeneratedRegex(" \\/ 0x\\w+")] + [GeneratedRegex(@" \/ 0x\w+")] private static partial Regex ReplaceRegex(); - [GeneratedRegex("\\d{2,}x\\d+")] + [GeneratedRegex(@"\d{2,}x\d+")] private static partial Regex ResRegex(); - [GeneratedRegex("\\d+ kb\\/s")] + [GeneratedRegex(@"\d+ kb\/s")] private static partial Regex BitrateRegex(); - [GeneratedRegex("(\\d+(\\.\\d+)?) fps")] + [GeneratedRegex(@"(\d+(\.\d+)?) fps")] private static partial Regex FpsRegex(); - [GeneratedRegex("DOVI configuration record.*profile: (\\d).*compatibility id: (\\d)")] + [GeneratedRegex(@"DOVI configuration record.*profile: (\d).*compatibility id: (\d)")] private static partial Regex DoViRegex(); - [GeneratedRegex("Duration.*?start: (\\d+\\.?\\d{0,3})")] + [GeneratedRegex(@"Duration.*?start: (\d+\.?\d{0,3})")] private static partial Regex StartRegex(); public static async Task> ReadInfoAsync(string binary, string file) diff --git a/src/N_m3u8DL-RE/Util/MergeUtil.cs b/src/N_m3u8DL-RE/Util/MergeUtil.cs index 240acbe..51150fc 100644 --- a/src/N_m3u8DL-RE/Util/MergeUtil.cs +++ b/src/N_m3u8DL-RE/Util/MergeUtil.cs @@ -68,20 +68,16 @@ internal static class MergeUtil public static string[] PartialCombineMultipleFiles(string[] files) { var newFiles = new List(); - int div = 0; - if (files.Length <= 90000) - div = 100; - else - div = 200; + var div = files.Length <= 90000 ? 100 : 200; - string outputName = Path.Combine(Path.GetDirectoryName(files[0])!, "T"); - int index = 0; // 序号 + var outputName = Path.Combine(Path.GetDirectoryName(files[0])!, "T"); + var index = 0; // 序号 // 按照div的容量分割为小数组 - string[][] li = Enumerable.Range(0, files.Count() / div + 1).Select(x => files.Skip(x * div).Take(div).ToArray()).ToArray(); + var li = Enumerable.Range(0, files.Length / div + 1).Select(x => files.Skip(x * div).Take(div).ToArray()).ToArray(); foreach (var items in li) { - if (!items.Any()) + if (items.Length == 0) continue; var output = outputName + index.ToString("0000") + ".ts"; CombineMultipleFilesIntoSingleFile(items, output); diff --git a/src/N_m3u8DL-RE/Util/OtherUtil.cs b/src/N_m3u8DL-RE/Util/OtherUtil.cs index 9ea336c..b2487f3 100644 --- a/src/N_m3u8DL-RE/Util/OtherUtil.cs +++ b/src/N_m3u8DL-RE/Util/OtherUtil.cs @@ -4,7 +4,7 @@ using System.Text.RegularExpressions; namespace N_m3u8DL_RE.Util; -internal static class OtherUtil +internal static partial class OtherUtil { public static Dictionary SplitHeaderArrayToDic(string[]? headers) { @@ -25,13 +25,9 @@ internal static class OtherUtil private static readonly char[] InvalidChars = "34,60,62,124,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,58,42,63,92,47" .Split(',').Select(s => (char)int.Parse(s)).ToArray(); - public static string GetValidFileName(string input, string re = ".", bool filterSlash = false) + public static string GetValidFileName(string input, string re = "_", bool filterSlash = false) { - string title = input; - foreach (char invalidChar in InvalidChars) - { - title = title.Replace(invalidChar.ToString(), re); - } + var title = InvalidChars.Aggregate(input, (current, invalidChar) => current.Replace(invalidChar.ToString(), re)); if (filterSlash) { title = title.Replace("/", re); @@ -98,7 +94,7 @@ internal static class OtherUtil /// public static double ParseSeconds(string timeStr) { - var pattern = new Regex(@"^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$"); + var pattern = TimeStrRegex(); var match = pattern.Match(timeStr); @@ -171,4 +167,7 @@ internal static class OtherUtil _ => throw new ArgumentException($"unknown format: {muxFormat}") }; } + + [GeneratedRegex(@"^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$")] + private static partial Regex TimeStrRegex(); } \ No newline at end of file diff --git a/src/N_m3u8DL-RE/Util/PipeUtil.cs b/src/N_m3u8DL-RE/Util/PipeUtil.cs index e3452a4..65a988f 100644 --- a/src/N_m3u8DL-RE/Util/PipeUtil.cs +++ b/src/N_m3u8DL-RE/Util/PipeUtil.cs @@ -14,24 +14,22 @@ internal static class PipeUtil { return new NamedPipeServerStream(pipeName, PipeDirection.InOut); } - else + + var path = Path.Combine(Path.GetTempPath(), pipeName); + using var p = new Process(); + p.StartInfo = new ProcessStartInfo() { - var path = Path.Combine(Path.GetTempPath(), pipeName); - using var p = new Process(); - p.StartInfo = new ProcessStartInfo() - { - FileName = "mkfifo", - Arguments = path, - CreateNoWindow = true, - UseShellExecute = false, - RedirectStandardError = true, - RedirectStandardOutput = true, - }; - p.Start(); - p.WaitForExit(); - Thread.Sleep(200); - return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); - } + FileName = "mkfifo", + Arguments = path, + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + }; + p.Start(); + p.WaitForExit(); + Thread.Sleep(200); + return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); } public static async Task StartPipeMuxAsync(string binary, string[] pipeNames, string outputPath) @@ -77,7 +75,7 @@ internal static class PipeUtil if (!string.IsNullOrEmpty(customDest)) { - if (customDest.Trim().StartsWith("-")) + if (customDest.Trim().StartsWith('-')) command.Append(customDest); else command.Append($" -f mpegts -shortest \"{customDest}\"");