优化部分代码

This commit is contained in:
nilaoda 2024-12-01 00:30:53 +08:00
parent cacf9b0ff0
commit adbe376ae0
60 changed files with 1140 additions and 1368 deletions

View File

@ -1,9 +1,4 @@
using N_m3u8DL_RE.Common.Enum; 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; namespace N_m3u8DL_RE.Common.Entity;
@ -34,9 +29,6 @@ public class EncryptInfo
{ {
return m; return m;
} }
else
{
return EncryptMethod.UNKNOWN; return EncryptMethod.UNKNOWN;
} }
} }
}

View File

@ -3,5 +3,5 @@
// 主要处理 EXT-X-DISCONTINUITY // 主要处理 EXT-X-DISCONTINUITY
public class MediaPart public class MediaPart
{ {
public List<MediaSegment> MediaSegments { get; set; } = new List<MediaSegment>(); public List<MediaSegment> MediaSegments { get; set; } = [];
} }

View File

@ -17,7 +17,7 @@ public class MediaSegment
public bool IsEncrypted => EncryptInfo.Method != EncryptMethod.NONE; 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分段文件名 public string? NameFromVar { get; set; } // MPD分段文件名

View File

@ -3,17 +3,18 @@
public class Playlist public class Playlist
{ {
// 对应Url信息 // 对应Url信息
public string Url { get; set; } public string Url { get; set; } = string.Empty;
// 是否直播 // 是否直播
public bool IsLive { get; set; } = false; public bool IsLive { get; set; } = false;
// 直播刷新间隔毫秒默认15秒 // 直播刷新间隔毫秒默认15秒
public double RefreshIntervalMs { get; set; } = 15000; 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; } public double? TargetDuration { get; set; }
// INIT信息 // INIT信息
public MediaSegment? MediaInit { get; set; } public MediaSegment? MediaInit { get; set; }
// 分片信息 // 分片信息
public List<MediaPart> MediaParts { get; set; } = new List<MediaPart>(); public List<MediaPart> MediaParts { get; set; } = [];
} }

View File

@ -46,12 +46,12 @@ public class StreamSpec
/// <summary> /// <summary>
/// URL /// URL
/// </summary> /// </summary>
public string Url { get; set; } public string Url { get; set; } = string.Empty;
/// <summary> /// <summary>
/// 原始URL /// 原始URL
/// </summary> /// </summary>
public string OriginalUrl { get; set; } public string OriginalUrl { get; set; } = string.Empty;
public Playlist? Playlist { get; set; } public Playlist? Playlist { get; set; }

View File

@ -11,10 +11,10 @@ public partial class WebVttSub
private static partial Regex TSValueRegex(); private static partial Regex TSValueRegex();
[GeneratedRegex("\\s")] [GeneratedRegex("\\s")]
private static partial Regex SplitRegex(); private static partial Regex SplitRegex();
[GeneratedRegex("<c\\..*?>([\\s\\S]*?)<\\/c>")] [GeneratedRegex(@"<c\..*?>([\s\S]*?)<\/c>")]
private static partial Regex VttClassRegex(); private static partial Regex VttClassRegex();
public List<SubCue> Cues { get; set; } = new List<SubCue>(); public List<SubCue> Cues { get; set; } = [];
public long MpegtsTimestamp { get; set; } = 0L; public long MpegtsTimestamp { get; set; } = 0L;
/// <summary> /// <summary>
@ -71,8 +71,8 @@ public partial class WebVttSub
continue; continue;
} }
if (needPayload) if (!needPayload) continue;
{
if (string.IsNullOrEmpty(line.Trim())) if (string.IsNullOrEmpty(line.Trim()))
{ {
var payload = string.Join(Environment.NewLine, payloads); var payload = string.Join(Environment.NewLine, payloads);
@ -97,10 +97,9 @@ public partial class WebVttSub
payloads.Add(line.Trim()); 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)
@ -113,7 +112,6 @@ public partial class WebVttSub
break; break;
} }
} }
}
return webSub; return webSub;
} }
@ -127,7 +125,7 @@ public partial class WebVttSub
return string.Concat(VttClassRegex().Matches(line).Select(x => x.Groups[1].Value + " ")); return string.Concat(VttClassRegex().Matches(line).Select(x => x.Groups[1].Value + " "));
})).TrimEnd(); })).TrimEnd();
} }
else return text; return text;
} }
/// <summary> /// <summary>
@ -140,8 +138,8 @@ public partial class WebVttSub
FixTimestamp(webSub, this.MpegtsTimestamp); FixTimestamp(webSub, this.MpegtsTimestamp);
foreach (var item in webSub.Cues) foreach (var item in webSub.Cues)
{ {
if (!this.Cues.Contains(item)) if (this.Cues.Contains(item)) continue;
{
// 如果相差只有1ms且payload相同则拼接 // 如果相差只有1ms且payload相同则拼接
var last = this.Cues.LastOrDefault(); var last = this.Cues.LastOrDefault();
if (last != null && this.Cues.Count > 0 && (item.StartTime - last.EndTime).TotalMilliseconds <= 1 && item.Payload == last.Payload) if (last != null && this.Cues.Count > 0 && (item.StartTime - last.EndTime).TotalMilliseconds <= 1 && item.Payload == last.Payload)
@ -153,7 +151,6 @@ public partial class WebVttSub
this.Cues.Add(item); this.Cues.Add(item);
} }
} }
}
return this; return this;
} }
@ -173,10 +170,10 @@ public partial class WebVttSub
// 当前预添加的字幕的起始时间小于实际上已经走过的时间(如offset已经是100秒而字幕起始却是2秒),才修复 // 当前预添加的字幕的起始时间小于实际上已经走过的时间(如offset已经是100秒而字幕起始却是2秒),才修复
if (sub.Cues.Count > 0 && sub.Cues.First().StartTime < offset) 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; subCue.StartTime += offset;
sub.Cues[i].EndTime += offset; subCue.EndTime += offset;
} }
} }
} }
@ -205,7 +202,7 @@ public partial class WebVttSub
str = parts.First(); str = parts.First();
} }
var t = str.Split(':').Reverse().ToList(); 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; time += (long)Math.Pow(60, i) * Convert.ToInt32(t[i]) * 1000;
} }
@ -214,7 +211,7 @@ public partial class WebVttSub
public override string ToString() public override string ToString()
{ {
StringBuilder sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var c in GetCues()) // 输出时去除空串 foreach (var c in GetCues()) // 输出时去除空串
{ {
sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\.fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\.fff") + " " + c.Settings); sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\.fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\.fff") + " " + c.Settings);

View File

@ -1,10 +1,5 @@
using System; using System.Text.Json;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Common.JsonConverter; namespace N_m3u8DL_RE.Common.JsonConverter;

View File

@ -4,7 +4,7 @@ using Spectre.Console;
namespace N_m3u8DL_RE.Common.Log; namespace N_m3u8DL_RE.Common.Log;
public class NonAnsiWriter : TextWriter public partial class NonAnsiWriter : TextWriter
{ {
public override Encoding Encoding => Console.OutputEncoding; public override Encoding Encoding => Console.OutputEncoding;
@ -28,15 +28,22 @@ public class NonAnsiWriter : TextWriter
private void RemoveAnsiEscapeSequences(string? input) private void RemoveAnsiEscapeSequences(string? input)
{ {
// Use regular expression to remove ANSI escape sequences // Use regular expression to remove ANSI escape sequences
string output = Regex.Replace(input ?? "", @"\x1B\[(\d+;?)+m", ""); var output = MyRegex().Replace(input ?? "", "");
output = Regex.Replace(output, @"\[\??\d+[AKlh]", ""); output = MyRegex1().Replace(output, "");
output = Regex.Replace(output,"[\r\n] +",""); output = MyRegex2().Replace(output, "");
if (string.IsNullOrWhiteSpace(output)) if (string.IsNullOrWhiteSpace(output))
{ {
return; return;
} }
Console.Write(output); 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();
} }
/// <summary> /// <summary>

View File

@ -1,10 +1,4 @@
using System; namespace N_m3u8DL_RE.Common.Log;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Common.Log;
public enum LogLevel public enum LogLevel
{ {

View File

@ -4,7 +4,7 @@ using System.Text.RegularExpressions;
namespace N_m3u8DL_RE.Common.Log; namespace N_m3u8DL_RE.Common.Log;
public partial class Logger public static partial class Logger
{ {
[GeneratedRegex("{}")] [GeneratedRegex("{}")]
private static partial Regex VarsRepRegex(); private static partial Regex VarsRepRegex();
@ -80,8 +80,8 @@ public partial class Logger
Console.WriteLine(subWrite); Console.WriteLine(subWrite);
} }
if (IsWriteFile && File.Exists(LogFilePath)) if (!IsWriteFile || !File.Exists(LogFilePath)) return;
{
var plain = write.RemoveMarkup() + subWrite.RemoveMarkup(); var plain = write.RemoveMarkup() + subWrite.RemoveMarkup();
try try
{ {
@ -98,7 +98,6 @@ public partial class Logger
LogWriteLock.ExitWriteLock(); LogWriteLock.ExitWriteLock();
} }
} }
}
catch (Exception) catch (Exception)
{ {
Console.WriteLine("Failed to write: " + write); Console.WriteLine("Failed to write: " + write);
@ -117,83 +116,75 @@ public partial class Logger
public static void Info(string data, params object[] ps) public static void Info(string data, params object[] ps)
{ {
if (LogLevel >= LogLevel.INFO) if (LogLevel < LogLevel.INFO) return;
{
data = ReplaceVars(data, ps); data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : "; var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : ";
HandleLog(write, data); HandleLog(write, data);
} }
}
public static void InfoMarkUp(string data, params object[] ps) public static void InfoMarkUp(string data, params object[] ps)
{ {
if (LogLevel >= LogLevel.INFO) if (LogLevel < LogLevel.INFO) return;
{
data = ReplaceVars(data, ps); data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : " + data; var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : " + data;
HandleLog(write); HandleLog(write);
} }
}
public static void Debug(string data, params object[] ps) public static void Debug(string data, params object[] ps)
{ {
if (LogLevel >= LogLevel.DEBUG) if (LogLevel < LogLevel.DEBUG) return;
{
data = ReplaceVars(data, ps); data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: "; var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: ";
HandleLog(write, data); HandleLog(write, data);
} }
}
public static void DebugMarkUp(string data, params object[] ps) public static void DebugMarkUp(string data, params object[] ps)
{ {
if (LogLevel >= LogLevel.DEBUG) if (LogLevel < LogLevel.DEBUG) return;
{
data = ReplaceVars(data, ps); data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: " + data; var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: " + data;
HandleLog(write); HandleLog(write);
} }
}
public static void Warn(string data, params object[] ps) public static void Warn(string data, params object[] ps)
{ {
if (LogLevel >= LogLevel.WARN) if (LogLevel < LogLevel.WARN) return;
{
data = ReplaceVars(data, ps); data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : "; var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : ";
HandleLog(write, data); HandleLog(write, data);
} }
}
public static void WarnMarkUp(string data, params object[] ps) public static void WarnMarkUp(string data, params object[] ps)
{ {
if (LogLevel >= LogLevel.WARN) if (LogLevel < LogLevel.WARN) return;
{
data = ReplaceVars(data, ps); data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : " + data; var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : " + data;
HandleLog(write); HandleLog(write);
} }
}
public static void Error(string data, params object[] ps) public static void Error(string data, params object[] ps)
{ {
if (LogLevel >= LogLevel.ERROR) if (LogLevel < LogLevel.ERROR) return;
{
data = ReplaceVars(data, ps); data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: "; var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: ";
HandleLog(write, data); HandleLog(write, data);
} }
}
public static void ErrorMarkUp(string data, params object[] ps) public static void ErrorMarkUp(string data, params object[] ps)
{ {
if (LogLevel >= LogLevel.ERROR) if (LogLevel < LogLevel.ERROR) return;
{
data = ReplaceVars(data, ps); data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: " + data; var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: " + data;
HandleLog(write); HandleLog(write);
} }
}
public static void ErrorMarkUp(Exception exception) public static void ErrorMarkUp(Exception exception)
{ {
@ -213,8 +204,8 @@ public partial class Logger
/// <param name="ps"></param> /// <param name="ps"></param>
public static void Extra(string data, params object[] ps) public static void Extra(string data, params object[] ps)
{ {
if (IsWriteFile && File.Exists(LogFilePath)) if (!IsWriteFile || !File.Exists(LogFilePath)) return;
{
data = ReplaceVars(data, ps); data = ReplaceVars(data, ps);
var plain = GetCurrTime() + " " + "EXTRA: " + data.RemoveMarkup(); var plain = GetCurrTime() + " " + "EXTRA: " + data.RemoveMarkup();
try try
@ -233,4 +224,3 @@ public partial class Logger
} }
} }
} }
}

View File

@ -2,7 +2,7 @@
public static class ResString public static class ResString
{ {
public static string CurrentLoc = "en-US"; public static string CurrentLoc { get; set; } = "en-US";
public static readonly string ReLiveTs = "<RE_LIVE_TS>"; public static readonly string ReLiveTs = "<RE_LIVE_TS>";
public static string singleFileRealtimeDecryptWarn => GetText("singleFileRealtimeDecryptWarn"); public static string singleFileRealtimeDecryptWarn => GetText("singleFileRealtimeDecryptWarn");
@ -140,13 +140,11 @@ public static class ResString
private static string GetText(string key) 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...>"; return "<...LANG TEXT MISSING...>";
if (CurrentLoc == "zh-CN" || CurrentLoc == "zh-SG" || CurrentLoc == "zh-Hans") if (CurrentLoc is "zh-CN" or "zh-SG" or "zh-Hans")
return StaticText.LANG_DIC[key].ZH_CN; return textObj.ZH_CN;
if (CurrentLoc.StartsWith("zh-")) return CurrentLoc.StartsWith("zh-") ? textObj.ZH_TW : textObj.EN_US;
return StaticText.LANG_DIC[key].ZH_TW;
return StaticText.LANG_DIC[key].EN_US;
} }
} }

View File

@ -1,14 +1,8 @@
using System; namespace N_m3u8DL_RE.Common.Resource;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Common.Resource; internal static class StaticText
internal class StaticText
{ {
public static Dictionary<string, TextContainer> LANG_DIC = new() public static readonly Dictionary<string, TextContainer> LANG_DIC = new()
{ {
["singleFileSplitWarn"] = new TextContainer ["singleFileSplitWarn"] = new TextContainer
( (
@ -228,9 +222,9 @@ internal class StaticText
), ),
["cmd_decryptionBinaryPath"] = new TextContainer ["cmd_decryptionBinaryPath"] = new TextContainer
( (
zhCN: "MP4解密所用工具的全路径, 例如 C:\\Tools\\mp4decrypt.exe", zhCN: @"MP4解密所用工具的全路径, 例如 C:\Tools\mp4decrypt.exe",
zhTW: "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" enUS: @"Full path to the tool used for MP4 decryption, like C:\Tools\mp4decrypt.exe"
), ),
["cmd_delAfterDone"] = new TextContainer ["cmd_delAfterDone"] = new TextContainer
( (
@ -240,15 +234,15 @@ internal class StaticText
), ),
["cmd_ffmpegBinaryPath"] = new TextContainer ["cmd_ffmpegBinaryPath"] = new TextContainer
( (
zhCN: "ffmpeg可执行程序全路径, 例如 C:\\Tools\\ffmpeg.exe", zhCN: @"ffmpeg可执行程序全路径, 例如 C:\Tools\ffmpeg.exe",
zhTW: "ffmpeg可執行程序全路徑, 例如 C:\\Tools\\ffmpeg.exe", zhTW: @"ffmpeg可執行程序全路徑, 例如 C:\Tools\ffmpeg.exe",
enUS: "Full path to the ffmpeg binary, like C:\\Tools\\ffmpeg.exe" enUS: @"Full path to the ffmpeg binary, like C:\Tools\ffmpeg.exe"
), ),
["cmd_mkvmergeBinaryPath"] = new TextContainer ["cmd_mkvmergeBinaryPath"] = new TextContainer
( (
zhCN: "mkvmerge可执行程序全路径, 例如 C:\\Tools\\mkvmerge.exe", zhCN: @"mkvmerge可执行程序全路径, 例如 C:\Tools\mkvmerge.exe",
zhTW: "mkvmerge可執行程序全路徑, 例如 C:\\Tools\\mkvmerge.exe", zhTW: @"mkvmerge可執行程序全路徑, 例如 C:\Tools\mkvmerge.exe",
enUS: "Full path to the mkvmerge binary, like C:\\Tools\\mkvmerge.exe" enUS: @"Full path to the mkvmerge binary, like C:\Tools\mkvmerge.exe"
), ),
["cmd_liveFixVttByAudio"] = new TextContainer ["cmd_liveFixVttByAudio"] = new TextContainer
( (

View File

@ -67,8 +67,7 @@ public static class GlobalUtil
{ {
var fileExt = OperatingSystem.IsWindows() ? ".exe" : ""; var fileExt = OperatingSystem.IsWindows() ? ".exe" : "";
var searchPath = new[] { Environment.CurrentDirectory, Path.GetDirectoryName(Environment.ProcessPath) }; var searchPath = new[] { Environment.CurrentDirectory, Path.GetDirectoryName(Environment.ProcessPath) };
var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? [];
Array.Empty<string>();
return searchPath.Concat(envPath).Select(p => Path.Combine(p!, name + fileExt)).FirstOrDefault(File.Exists); return searchPath.Concat(envPath).Select(p => Path.Combine(p!, name + fileExt)).FirstOrDefault(File.Exists);
} }
} }

View File

@ -99,7 +99,7 @@ public static class HTTPUtil
private static bool CheckMPEG2TS(HttpResponseMessage? webResponse) private static bool CheckMPEG2TS(HttpResponseMessage? webResponse)
{ {
var mediaType = webResponse?.Content.Headers.ContentType?.MediaType?.ToLower(); 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";
} }
/// <summary> /// <summary>

View File

@ -7,11 +7,11 @@ namespace N_m3u8DL_RE.Parser.Config;
public class ParserConfig 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<string, string> CustomParserArgs { get; } = new(); public Dictionary<string, string> CustomParserArgs { get; } = new();

View File

@ -1,15 +1,9 @@
using System; namespace N_m3u8DL_RE.Parser.Constants;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Parser.Constants; internal static class DASHTags
internal class DASHTags
{ {
public static string TemplateRepresentationID = "$RepresentationID$"; public const string TemplateRepresentationID = "$RepresentationID$";
public static string TemplateBandwidth = "$Bandwidth$"; public const string TemplateBandwidth = "$Bandwidth$";
public static string TemplateNumber = "$Number$"; public const string TemplateNumber = "$Number$";
public static string TemplateTime = "$Time$"; public const string TemplateTime = "$Time$";
} }

View File

@ -1,37 +1,31 @@
using System; namespace N_m3u8DL_RE.Parser.Constants;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Parser.Constants; internal static class HLSTags
internal class HLSTags
{ {
public static string ext_m3u = "#EXTM3U"; public const string ext_m3u = "#EXTM3U";
public static string ext_x_targetduration = "#EXT-X-TARGETDURATION"; public const string ext_x_targetduration = "#EXT-X-TARGETDURATION";
public static string ext_x_media_sequence = "#EXT-X-MEDIA-SEQUENCE"; public const string ext_x_media_sequence = "#EXT-X-MEDIA-SEQUENCE";
public static string ext_x_discontinuity_sequence = "#EXT-X-DISCONTINUITY-SEQUENCE"; public const string ext_x_discontinuity_sequence = "#EXT-X-DISCONTINUITY-SEQUENCE";
public static string ext_x_program_date_time = "#EXT-X-PROGRAM-DATE-TIME"; public const string ext_x_program_date_time = "#EXT-X-PROGRAM-DATE-TIME";
public static string ext_x_media = "#EXT-X-MEDIA"; public const string ext_x_media = "#EXT-X-MEDIA";
public static string ext_x_playlist_type = "#EXT-X-PLAYLIST-TYPE"; public const string ext_x_playlist_type = "#EXT-X-PLAYLIST-TYPE";
public static string ext_x_key = "#EXT-X-KEY"; public const string ext_x_key = "#EXT-X-KEY";
public static string ext_x_stream_inf = "#EXT-X-STREAM-INF"; public const string ext_x_stream_inf = "#EXT-X-STREAM-INF";
public static string ext_x_version = "#EXT-X-VERSION"; public const string ext_x_version = "#EXT-X-VERSION";
public static string ext_x_allow_cache = "#EXT-X-ALLOW-CACHE"; public const string ext_x_allow_cache = "#EXT-X-ALLOW-CACHE";
public static string ext_x_endlist = "#EXT-X-ENDLIST"; public const string ext_x_endlist = "#EXT-X-ENDLIST";
public static string extinf = "#EXTINF"; public const string extinf = "#EXTINF";
public static string ext_i_frames_only = "#EXT-X-I-FRAMES-ONLY"; public const string ext_i_frames_only = "#EXT-X-I-FRAMES-ONLY";
public static string ext_x_byterange = "#EXT-X-BYTERANGE"; public const string ext_x_byterange = "#EXT-X-BYTERANGE";
public static string ext_x_i_frame_stream_inf = "#EXT-X-I-FRAME-STREAM-INF"; public const string ext_x_i_frame_stream_inf = "#EXT-X-I-FRAME-STREAM-INF";
public static string ext_x_discontinuity = "#EXT-X-DISCONTINUITY"; public const string ext_x_discontinuity = "#EXT-X-DISCONTINUITY";
public static string ext_x_cue_out_start = "#EXT-X-CUE-OUT"; public const string ext_x_cue_out_start = "#EXT-X-CUE-OUT";
public static string ext_x_cue_out = "#EXT-X-CUE-OUT-CONT"; public const string ext_x_cue_out = "#EXT-X-CUE-OUT-CONT";
public static string ext_is_independent_segments = "#EXT-X-INDEPENDENT-SEGMENTS"; public const string ext_is_independent_segments = "#EXT-X-INDEPENDENT-SEGMENTS";
public static string ext_x_scte35 = "#EXT-OATCLS-SCTE35"; public const string ext_x_scte35 = "#EXT-OATCLS-SCTE35";
public static string ext_x_cue_start = "#EXT-X-CUE-OUT"; public const string ext_x_cue_start = "#EXT-X-CUE-OUT";
public static string ext_x_cue_end = "#EXT-X-CUE-IN"; public const string ext_x_cue_end = "#EXT-X-CUE-IN";
public static string ext_x_cue_span = "#EXT-X-CUE-SPAN"; public const string ext_x_cue_span = "#EXT-X-CUE-SPAN";
public static string ext_x_map = "#EXT-X-MAP"; public const string ext_x_map = "#EXT-X-MAP";
public static string ext_x_start = "#EXT-X-START"; public const string ext_x_start = "#EXT-X-START";
} }

View File

@ -1,15 +1,9 @@
using System; namespace N_m3u8DL_RE.Parser.Constants;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Parser.Constants; internal static class MSSTags
internal class MSSTags
{ {
public static string Bitrate = "{Bitrate}"; public const string Bitrate = "{Bitrate}";
public static string Bitrate_BK = "{bitrate}"; public const string Bitrate_BK = "{bitrate}";
public static string StartTime = "{start_time}"; public const string StartTime = "{start_time}";
public static string StartTime_BK = "{start time}"; public const string StartTime_BK = "{start time}";
} }

View File

@ -4,19 +4,14 @@ using N_m3u8DL_RE.Common.Util;
using N_m3u8DL_RE.Parser.Config; using N_m3u8DL_RE.Parser.Config;
using N_m3u8DL_RE.Parser.Constants; using N_m3u8DL_RE.Parser.Constants;
using N_m3u8DL_RE.Parser.Util; using N_m3u8DL_RE.Parser.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml; using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
namespace N_m3u8DL_RE.Parser.Extractor; namespace N_m3u8DL_RE.Parser.Extractor;
// https://blog.csdn.net/leek5533/article/details/117750191 // https://blog.csdn.net/leek5533/article/details/117750191
internal class DASHExtractor2 : IExtractor internal partial class DASHExtractor2 : IExtractor
{ {
private static EncryptMethod DEFAULT_METHOD = EncryptMethod.CENC; private static EncryptMethod DEFAULT_METHOD = EncryptMethod.CENC;
@ -37,10 +32,7 @@ internal class DASHExtractor2 : IExtractor
private void SetInitUrl() private void SetInitUrl()
{ {
this.MpdUrl = ParserConfig.Url ?? string.Empty; this.MpdUrl = ParserConfig.Url ?? string.Empty;
if (!string.IsNullOrEmpty(ParserConfig.BaseUrl)) this.BaseUrl = !string.IsNullOrEmpty(ParserConfig.BaseUrl) ? ParserConfig.BaseUrl : this.MpdUrl;
this.BaseUrl = ParserConfig.BaseUrl;
else
this.BaseUrl = this.MpdUrl;
} }
private string ExtendBaseUrl(XElement element, string oriBaseUrl) private string ExtendBaseUrl(XElement element, string oriBaseUrl)
@ -57,14 +49,12 @@ internal class DASHExtractor2 : IExtractor
private double? GetFrameRate(XElement element) private double? GetFrameRate(XElement element)
{ {
var frameRate = element.Attribute("frameRate")?.Value; var frameRate = element.Attribute("frameRate")?.Value;
if (frameRate != null && frameRate.Contains('/')) if (frameRate == null || !frameRate.Contains('/')) return null;
{
var d = Convert.ToDouble(frameRate.Split('/')[0]) / Convert.ToDouble(frameRate.Split('/')[1]); var d = Convert.ToDouble(frameRate.Split('/')[0]) / Convert.ToDouble(frameRate.Split('/')[1]);
frameRate = d.ToString("0.000"); frameRate = d.ToString("0.000");
return Convert.ToDouble(frameRate); return Convert.ToDouble(frameRate);
} }
return null;
}
public Task<List<StreamSpec>> ExtractStreamsAsync(string rawText) public Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
{ {
@ -180,7 +170,7 @@ internal class DASHExtractor2 : IExtractor
streamSpec.Extension = mTypeSplit.Length == 2 ? mTypeSplit[1] : null; 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; streamSpec.MediaType = MediaType.SUBTITLES;
} }
@ -493,7 +483,7 @@ internal class DASHExtractor2 : IExtractor
else else
{ {
// 修复mp4类型字幕 // 修复mp4类型字幕
if (streamSpec.MediaType == MediaType.SUBTITLES && streamSpec.Extension == "mp4") if (streamSpec is { MediaType: MediaType.SUBTITLES, Extension: "mp4" })
{ {
streamSpec.Extension = "m4s"; streamSpec.Extension = "m4s";
} }
@ -513,22 +503,19 @@ internal class DASHExtractor2 : IExtractor
} }
// 为视频设置默认轨道 // 为视频设置默认轨道
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO); var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO).ToList();
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES); var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES).ToList();
foreach (var item in streamList) 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; item.AudioId = aL.OrderByDescending(x => x.Bandwidth).First().GroupId;
} }
if (sL.Any()) if (sL.Count != 0)
{ {
item.SubtitleId = sL.OrderByDescending(x => x.Bandwidth).First().GroupId; item.SubtitleId = sL.OrderByDescending(x => x.Bandwidth).First().GroupId;
} }
} }
}
return Task.FromResult(streamList); return Task.FromResult(streamList);
} }
@ -541,8 +528,7 @@ internal class DASHExtractor2 : IExtractor
private string? FilterLanguage(string? v) private string? FilterLanguage(string? v)
{ {
if (v == null) return null; if (v == null) return null;
if (Regex.IsMatch(v, "^[\\w_\\-\\d]+$")) return v; return LangCodeRegex().IsMatch(v) ? v : "und";
return "und";
} }
public async Task RefreshPlayListAsync(List<StreamSpec> streamSpecs) public async Task RefreshPlayListAsync(List<StreamSpec> streamSpecs)
@ -581,22 +567,21 @@ internal class DASHExtractor2 : IExtractor
private Task ProcessUrlAsync(List<StreamSpec> streamSpecs) private Task ProcessUrlAsync(List<StreamSpec> 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); playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url);
} }
for (int ii = 0; ii < playlist!.MediaParts.Count; ii++) for (var ii = 0; ii < playlist!.MediaParts.Count; ii++)
{ {
var part = playlist.MediaParts[ii]; var part = playlist.MediaParts[ii];
for (int iii = 0; iii < part.MediaSegments.Count; iii++) foreach (var mediaSegment in part.MediaSegments)
{ {
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();
} }

View File

@ -5,12 +5,6 @@ using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Resource; using N_m3u8DL_RE.Common.Resource;
using N_m3u8DL_RE.Parser.Util; using N_m3u8DL_RE.Parser.Util;
using N_m3u8DL_RE.Parser.Constants; 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; using N_m3u8DL_RE.Common.Util;
namespace N_m3u8DL_RE.Parser.Extractor; namespace N_m3u8DL_RE.Parser.Extractor;
@ -26,8 +20,6 @@ internal class HLSExtractor : IExtractor
public ParserConfig ParserConfig { get; set; } public ParserConfig ParserConfig { get; set; }
private HLSExtractor() { }
public HLSExtractor(ParserConfig parserConfig) public HLSExtractor(ParserConfig parserConfig)
{ {
this.ParserConfig = parserConfig; this.ParserConfig = parserConfig;
@ -37,14 +29,7 @@ internal class HLSExtractor : IExtractor
private void SetBaseUrl() private void SetBaseUrl()
{ {
if (!string.IsNullOrEmpty(ParserConfig.BaseUrl)) this.BaseUrl = !string.IsNullOrEmpty(ParserConfig.BaseUrl) ? ParserConfig.BaseUrl : this.M3u8Url;
{
this.BaseUrl = ParserConfig.BaseUrl;
}
else
{
this.BaseUrl = this.M3u8Url;
}
} }
/// <summary> /// <summary>
@ -87,7 +72,7 @@ internal class HLSExtractor : IExtractor
{ {
MasterM3u8Flag = true; MasterM3u8Flag = true;
List<StreamSpec> streams = new List<StreamSpec>(); List<StreamSpec> streams = [];
using StringReader sr = new StringReader(M3u8Content); using StringReader sr = new StringReader(M3u8Content);
string? line; string? line;
@ -201,7 +186,7 @@ internal class HLSExtractor : IExtractor
streams.Add(streamSpec); streams.Add(streamSpec);
} }
else if (line.StartsWith("#")) else if (line.StartsWith('#'))
{ {
continue; continue;
} }
@ -237,22 +222,22 @@ internal class HLSExtractor : IExtractor
long startIndex; long startIndex;
Playlist playlist = new(); Playlist playlist = new();
List<MediaPart> mediaParts = new(); List<MediaPart> mediaParts = [];
// 当前的加密信息 // 当前的加密信息
EncryptInfo currentEncryptInfo = new(); EncryptInfo currentEncryptInfo = new();
if (ParserConfig.CustomMethod != null) if (ParserConfig.CustomMethod != null)
currentEncryptInfo.Method = ParserConfig.CustomMethod.Value; currentEncryptInfo.Method = ParserConfig.CustomMethod.Value;
if (ParserConfig.CustomeKey != null && ParserConfig.CustomeKey.Length > 0) if (ParserConfig.CustomeKey is { Length: > 0 })
currentEncryptInfo.Key = ParserConfig.CustomeKey; currentEncryptInfo.Key = ParserConfig.CustomeKey;
if (ParserConfig.CustomeIV != null && ParserConfig.CustomeIV.Length > 0) if (ParserConfig.CustomeIV is { Length: > 0 })
currentEncryptInfo.IV = ParserConfig.CustomeIV; currentEncryptInfo.IV = ParserConfig.CustomeIV;
// 上次读取到的加密行,#EXT-X-KEY:…… // 上次读取到的加密行,#EXT-X-KEY:……
string lastKeyLine = ""; string lastKeyLine = "";
MediaPart mediaPart = new(); MediaPart mediaPart = new();
MediaSegment segment = new(); MediaSegment segment = new();
List<MediaSegment> segments = new(); List<MediaSegment> segments = [];
while ((line = sr.ReadLine()) != null) while ((line = sr.ReadLine()) != null)
@ -304,21 +289,20 @@ internal class HLSExtractor : IExtractor
// 修复YK去除广告后的遗留问题 // 修复YK去除广告后的遗留问题
if (hasAd && mediaParts.Count > 0) if (hasAd && mediaParts.Count > 0)
{ {
segments = mediaParts[mediaParts.Count - 1].MediaSegments; segments = mediaParts[^1].MediaSegments;
mediaParts.RemoveAt(mediaParts.Count - 1); mediaParts.RemoveAt(mediaParts.Count - 1);
hasAd = false; hasAd = false;
continue; continue;
} }
// 常规情况的#EXT-X-DISCONTINUITY标记新建part // 常规情况的#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, MediaSegments = segments,
}); });
segments = new(); segments = new();
} }
}
// 解析KEY // 解析KEY
else if (line.StartsWith(HLSTags.ext_x_key)) else if (line.StartsWith(HLSTags.ext_x_key))
{ {
@ -382,14 +366,12 @@ internal class HLSExtractor : IExtractor
playlist.MediaInit.ExpectLength = n; playlist.MediaInit.ExpectLength = n;
playlist.MediaInit.StartRange = o ?? 0L; playlist.MediaInit.StartRange = o ?? 0L;
} }
// 是否有加密有的话写入KEY和IV if (currentEncryptInfo.Method == EncryptMethod.NONE) continue;
if (currentEncryptInfo.Method != EncryptMethod.NONE) // 有加密的话写入KEY和IV
{
playlist.MediaInit.EncryptInfo.Method = currentEncryptInfo.Method; playlist.MediaInit.EncryptInfo.Method = currentEncryptInfo.Method;
playlist.MediaInit.EncryptInfo.Key = currentEncryptInfo.Key; playlist.MediaInit.EncryptInfo.Key = currentEncryptInfo.Key;
playlist.MediaInit.EncryptInfo.IV = currentEncryptInfo.IV ?? HexUtil.HexToBytes(Convert.ToString(segIndex, 16).PadLeft(32, '0')); playlist.MediaInit.EncryptInfo.IV = currentEncryptInfo.IV ?? HexUtil.HexToBytes(Convert.ToString(segIndex, 16).PadLeft(32, '0'));
} }
}
// 遇到了其他的map说明已经不是一个视频了全部丢弃即可 // 遇到了其他的map说明已经不是一个视频了全部丢弃即可
else 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; else if (line.StartsWith("\r\n")) continue;
// 解析分片的地址 // 解析分片的地址
@ -488,15 +470,15 @@ internal class HLSExtractor : IExtractor
} }
var playlist = await ParseListAsync(); var playlist = await ParseListAsync();
return new List<StreamSpec> return
{ [
new() new()
{ {
Url = ParserConfig.Url, Url = ParserConfig.Url,
Playlist = playlist, Playlist = playlist,
Extension = playlist.MediaInit != null ? "mp4" : "ts" Extension = playlist.MediaInit != null ? "mp4" : "ts"
} }
}; ];
} }
private async Task LoadM3u8FromUrlAsync(string url) private async Task LoadM3u8FromUrlAsync(string url)
@ -539,13 +521,12 @@ internal class HLSExtractor : IExtractor
foreach (var l in lists) foreach (var l in lists)
{ {
var match = newStreams.Where(n => n.ToShortString() == l.ToShortString()).ToList(); var match = newStreams.Where(n => n.ToShortString() == l.ToShortString()).ToList();
if (match.Any()) if (match.Count == 0) continue;
{
Logger.DebugMarkUp($"{l.Url} => {match.First().Url}"); Logger.DebugMarkUp($"{l.Url} => {match.First().Url}");
l.Url = match.First().Url; l.Url = match.First().Url;
} }
} }
}
public async Task FetchPlayListAsync(List<StreamSpec> lists) public async Task FetchPlayListAsync(List<StreamSpec> lists)
{ {
@ -556,7 +537,7 @@ internal class HLSExtractor : IExtractor
// 直接重新加载m3u8 // 直接重新加载m3u8
await LoadM3u8FromUrlAsync(lists[i].Url!); 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..."); Logger.WarnMarkUp("Can not load m3u8. Try refreshing url from master url...");
// 当前URL无法加载 尝试从Master链接中刷新URL // 当前URL无法加载 尝试从Master链接中刷新URL

View File

@ -1,10 +1,5 @@
using N_m3u8DL_RE.Parser.Config; using N_m3u8DL_RE.Parser.Config;
using N_m3u8DL_RE.Common.Entity; 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; using N_m3u8DL_RE.Common.Enum;
namespace N_m3u8DL_RE.Parser.Extractor; namespace N_m3u8DL_RE.Parser.Extractor;

View File

@ -6,14 +6,7 @@ using N_m3u8DL_RE.Parser.Config;
using N_m3u8DL_RE.Parser.Constants; using N_m3u8DL_RE.Parser.Constants;
using N_m3u8DL_RE.Parser.Mp4; using N_m3u8DL_RE.Parser.Mp4;
using N_m3u8DL_RE.Parser.Util; 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.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
namespace N_m3u8DL_RE.Parser.Extractor; namespace N_m3u8DL_RE.Parser.Extractor;
@ -46,10 +39,7 @@ internal partial class MSSExtractor : IExtractor
private void SetInitUrl() private void SetInitUrl()
{ {
this.IsmUrl = ParserConfig.Url ?? string.Empty; this.IsmUrl = ParserConfig.Url ?? string.Empty;
if (!string.IsNullOrEmpty(ParserConfig.BaseUrl)) this.BaseUrl = !string.IsNullOrEmpty(ParserConfig.BaseUrl) ? ParserConfig.BaseUrl : this.IsmUrl;
this.BaseUrl = ParserConfig.BaseUrl;
else
this.BaseUrl = this.IsmUrl;
} }
public Task<List<StreamSpec>> ExtractStreamsAsync(string rawText) public Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
@ -248,22 +238,19 @@ internal partial class MSSExtractor : IExtractor
} }
// 为视频设置默认轨道 // 为视频设置默认轨道
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO); var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO).ToList();
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES); var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES).ToList();
foreach (var item in streamList) 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; item.AudioId = aL.First().GroupId;
} }
if (sL.Any()) if (sL.Count != 0)
{ {
item.SubtitleId = sL.First().GroupId; item.SubtitleId = sL.First().GroupId;
} }
} }
}
return Task.FromResult(streamList); return Task.FromResult(streamList);
} }
@ -318,22 +305,21 @@ internal partial class MSSExtractor : IExtractor
private Task ProcessUrlAsync(List<StreamSpec> streamSpecs) private Task ProcessUrlAsync(List<StreamSpec> 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); playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url);
} }
for (int ii = 0; ii < playlist!.MediaParts.Count; ii++) for (var ii = 0; ii < playlist!.MediaParts.Count; ii++)
{ {
var part = playlist.MediaParts[ii]; var part = playlist.MediaParts[ii];
for (int iii = 0; iii < part.MediaSegments.Count; iii++) foreach (var segment in part.MediaSegments)
{ {
part.MediaSegments[iii].Url = PreProcessUrl(part.MediaSegments[iii].Url); segment.Url = PreProcessUrl(segment.Url);
}
} }
} }
} }

View File

@ -1,12 +1,5 @@
using System; namespace Mp4SubtitleParser;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mp4SubtitleParser
{
// make BinaryReader in Big Endian // make BinaryReader in Big Endian
class BinaryReader2 : BinaryReader class BinaryReader2 : BinaryReader
{ {
@ -67,4 +60,3 @@ namespace Mp4SubtitleParser
return BitConverter.ToUInt64(data, 0); return BitConverter.ToUInt64(data, 0);
} }
} }
}

View File

@ -1,12 +1,7 @@
using System; using System.Text;
using System.Collections.Generic;
using System.IO; namespace Mp4SubtitleParser;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mp4SubtitleParser
{
// make BinaryWriter in Big Endian // make BinaryWriter in Big Endian
class BinaryWriter2 : BinaryWriter class BinaryWriter2 : BinaryWriter
{ {
@ -86,4 +81,3 @@ namespace Mp4SubtitleParser
BaseStream.Write(arr); BaseStream.Write(arr);
} }
} }
}

View File

@ -1,5 +1,4 @@
using N_m3u8DL_RE.Common.Util; using N_m3u8DL_RE.Common.Util;
using System.Security.Cryptography;
namespace Mp4SubtitleParser namespace Mp4SubtitleParser
{ {
@ -11,10 +10,10 @@ namespace Mp4SubtitleParser
public bool isMultiDRM; 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_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_PLAYREADY = [0x9A, 0x04, 0xF0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xAB, 0x92, 0xE6, 0x5B, 0xE0, 0x88, 0x5F, 0x95];
public static ParsedMP4Info ReadInit(byte[] data) public static ParsedMP4Info ReadInit(byte[] data)
{ {
@ -28,22 +27,20 @@ namespace Mp4SubtitleParser
.Box("minf", MP4Parser.Children) .Box("minf", MP4Parser.Children)
.Box("stbl", MP4Parser.Children) .Box("stbl", MP4Parser.Children)
.FullBox("stsd", MP4Parser.SampleDescription) .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"); throw new Exception("PSSH version can only be 0 or 1");
var systemId = box.Reader.ReadBytes(16); var systemId = box.Reader.ReadBytes(16);
if (SYSTEM_ID_WIDEVINE.SequenceEqual(systemId)) if (!SYSTEM_ID_WIDEVINE.SequenceEqual(systemId)) return;
{
var dataSize = box.Reader.ReadUInt32(); var dataSize = box.Reader.ReadUInt32();
var psshData = box.Reader.ReadBytes((int)dataSize); var psshData = box.Reader.ReadBytes((int)dataSize);
info.PSSH = Convert.ToBase64String(psshData); info.PSSH = Convert.ToBase64String(psshData);
if (info.KID == "00000000000000000000000000000000") if (info.KID != "00000000000000000000000000000000") return;
{
info.KID = HexUtil.BytesToHex(psshData[2..18]).ToLower(); info.KID = HexUtil.BytesToHex(psshData[2..18]).ToLower();
info.isMultiDRM = true; info.isMultiDRM = true;
}
}
}) })
.FullBox("encv", MP4Parser.AllData(data => ReadBox(data, info))) .FullBox("encv", MP4Parser.AllData(data => ReadBox(data, info)))
.FullBox("enca", 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) private static void ReadBox(byte[] data, ParsedMP4Info info)
{ {
// find schm // find schm
var schmBytes = new byte[4] { 0x73, 0x63, 0x68, 0x6d }; byte[] schmBytes = [0x73, 0x63, 0x68, 0x6d];
var schmIndex = 0; 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; schmIndex = i;
break; break;
@ -75,11 +72,11 @@ namespace Mp4SubtitleParser
// if (info.Scheme != "cenc") return; // if (info.Scheme != "cenc") return;
// find KID // find KID
var tencBytes = new byte[4] { 0x74, 0x65, 0x6E, 0x63 }; byte[] tencBytes = [0x74, 0x65, 0x6E, 0x6];
var tencIndex = -1; var tencIndex = -1;
for (int i = 0; i < data.Length - 4; i++) 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; tencIndex = i;
break; break;

View File

@ -9,12 +9,12 @@ namespace Mp4SubtitleParser
{ {
class ParsedBox class ParsedBox
{ {
public MP4Parser Parser { get; set; } public required MP4Parser Parser { get; set; }
public bool PartialOkay { get; set; } public bool PartialOkay { get; set; }
public long Start { get; set; } public long Start { get; set; }
public uint Version { get; set; } = 1000; public uint Version { get; set; } = 1000;
public uint Flags { 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; } public bool Has64BitSize { get; set; }
} }
@ -28,7 +28,7 @@ namespace Mp4SubtitleParser
class TRUN class TRUN
{ {
public uint SampleCount { get; set; } public uint SampleCount { get; set; }
public List<Sample> SampleData { get; set; } = new List<Sample>(); public List<Sample> SampleData { get; set; } = [];
} }
class Sample class Sample
@ -55,7 +55,7 @@ namespace Mp4SubtitleParser
public static BoxHandler AllData(DataHandler handler) public static BoxHandler AllData(DataHandler handler)
{ {
return (box) => return box =>
{ {
var all = box.Reader.GetLength() - box.Reader.GetPosition(); var all = box.Reader.GetLength() - box.Reader.GetPosition();
handler(box.Reader.ReadBytes((int)all)); handler(box.Reader.ReadBytes((int)all));
@ -161,7 +161,7 @@ namespace Mp4SubtitleParser
} }
int payloadSize = (int)(end - reader.GetPosition()); 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() var box = new ParsedBox()
{ {
Parser = this, Parser = this,

View File

@ -3,15 +3,15 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Xml; using System.Xml;
namespace Mp4SubtitleParser namespace Mp4SubtitleParser;
{
class SubEntity class SubEntity
{ {
public string Begin { get; set; } public required string Begin { get; set; }
public string End { get; set; } public required string End { get; set; }
public string Region { get; set; } public required string Region { get; set; }
public List<XmlElement> Contents { get; set; } = new(); public List<XmlElement> Contents { get; set; } = [];
public List<string> ContentStrings { get; set; } = new(); public List<string> ContentStrings { get; set; } = [];
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
@ -28,13 +28,13 @@ namespace Mp4SubtitleParser
} }
} }
public partial class MP4TtmlUtil public static partial class MP4TtmlUtil
{ {
[GeneratedRegex(" \\w+:\\w+=\\\"[^\\\"]*\\\"")] [GeneratedRegex(" \\w+:\\w+=\\\"[^\\\"]*\\\"")]
private static partial Regex AttrRegex(); private static partial Regex AttrRegex();
[GeneratedRegex("<p.*?>(.+?)<\\/p>")] [GeneratedRegex("<p.*?>(.+?)<\\/p>")]
private static partial Regex LabelFixRegex(); private static partial Regex LabelFixRegex();
[GeneratedRegex("\\<tt[\\s\\S]*?\\<\\/tt\\>")] [GeneratedRegex(@"\<tt[\s\S]*?\<\/tt\>")]
private static partial Regex MultiElementsFixRegex(); private static partial Regex MultiElementsFixRegex();
[GeneratedRegex("\\<smpte:image.*xml:id=\\\"(.*?)\\\".*\\>([\\s\\S]*?)<\\/smpte:image>")] [GeneratedRegex("\\<smpte:image.*xml:id=\\\"(.*?)\\\".*\\>([\\s\\S]*?)<\\/smpte:image>")]
private static partial Regex ImageRegex(); private static partial Regex ImageRegex();
@ -51,7 +51,7 @@ namespace Mp4SubtitleParser
.Box("minf", MP4Parser.Children) .Box("minf", MP4Parser.Children)
.Box("stbl", MP4Parser.Children) .Box("stbl", MP4Parser.Children)
.FullBox("stsd", MP4Parser.SampleDescription) .FullBox("stsd", MP4Parser.SampleDescription)
.Box("stpp", (box) => { .Box("stpp", box => {
sawSTPP = true; sawSTPP = true;
}) })
.Parse(data); .Parse(data);
@ -65,7 +65,7 @@ namespace Mp4SubtitleParser
{ {
var dt = DateTime.ParseExact(xmlTime, "HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture); var dt = DateTime.ParseExact(xmlTime, "HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture);
var ts = TimeSpan.FromMilliseconds(dt.TimeOfDay.TotalMilliseconds + segTimeMs * index); 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); return $"{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}.{ts.Milliseconds:000}";
} }
if (!xmlSrc.Contains("<tt") || !xmlSrc.Contains("<head>")) return xmlSrc; if (!xmlSrc.Contains("<tt") || !xmlSrc.Contains("<head>")) return xmlSrc;
@ -114,7 +114,7 @@ namespace Mp4SubtitleParser
{ {
sb.Append(item.InnerText.Trim()); sb.Append(item.InnerText.Trim());
} }
else if(item.NodeType == XmlNodeType.Element && item.Name == "br") else if(item is { NodeType: XmlNodeType.Element, Name: "br" })
{ {
sb.AppendLine(); sb.AppendLine();
} }
@ -122,21 +122,20 @@ namespace Mp4SubtitleParser
return sb.ToString(); return sb.ToString();
} }
public static List<string> SplitMultipleRootElements(string xml) private static List<string> SplitMultipleRootElements(string xml)
{ {
if (!MultiElementsFixRegex().IsMatch(xml)) return new List<string>(); return !MultiElementsFixRegex().IsMatch(xml) ? [] : MultiElementsFixRegex().Matches(xml).Select(m => m.Value).ToList();
return MultiElementsFixRegex().Matches(xml).Select(m => m.Value).ToList();
} }
public static WebVttSub ExtractFromMp4(string item, long segTimeMs, long baseTimestamp = 0L) public static WebVttSub ExtractFromMp4(string item, long segTimeMs, long baseTimestamp = 0L)
{ {
return ExtractFromMp4s(new string[] { item }, segTimeMs, baseTimestamp); return ExtractFromMp4s([item], segTimeMs, baseTimestamp);
} }
public static WebVttSub ExtractFromMp4s(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L) private static WebVttSub ExtractFromMp4s(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
{ {
// read ttmls // read ttmls
List<string> xmls = new List<string>(); List<string> xmls = [];
int segIndex = 0; int segIndex = 0;
foreach (var item in items) foreach (var item in items)
{ {
@ -145,7 +144,7 @@ namespace Mp4SubtitleParser
var sawMDAT = false; var sawMDAT = false;
// parse media // parse media
new MP4Parser() new MP4Parser()
.Box("mdat", MP4Parser.AllData((data) => .Box("mdat", MP4Parser.AllData(data =>
{ {
sawMDAT = true; sawMDAT = true;
// Join this to any previous payload, in case the mp4 has multiple // Join this to any previous payload, in case the mp4 has multiple
@ -173,25 +172,18 @@ namespace Mp4SubtitleParser
public static WebVttSub ExtractFromTTML(string item, long segTimeMs, long baseTimestamp = 0L) public static WebVttSub ExtractFromTTML(string item, long segTimeMs, long baseTimestamp = 0L)
{ {
return ExtractFromTTMLs(new string[] { item }, segTimeMs, baseTimestamp); return ExtractFromTTMLs([item], segTimeMs, baseTimestamp);
} }
public static WebVttSub ExtractFromTTMLs(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L) public static WebVttSub ExtractFromTTMLs(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
{ {
// read ttmls // read ttmls
List<string> xmls = new List<string>(); List<string> xmls = [];
int segIndex = 0; int segIndex = 0;
foreach (var item in items) foreach (var item in items)
{ {
var xml = File.ReadAllText(item); var xml = File.ReadAllText(item);
if (segTimeMs != 0) xmls.Add(segTimeMs != 0 ? ShiftTime(xml, segTimeMs, segIndex) : xml);
{
xmls.Add(ShiftTime(xml, segTimeMs, segIndex));
}
else
{
xmls.Add(xml);
}
segIndex++; segIndex++;
} }
@ -321,10 +313,10 @@ namespace Mp4SubtitleParser
else else
{ {
var id = _bgImg.Replace("#", ""); var id = _bgImg.Replace("#", "");
if (imageDic.ContainsKey(id)) if (imageDic.TryGetValue(id, out var value))
{ {
var _span = new XmlDocument().CreateElement("span"); var _span = new XmlDocument().CreateElement("span");
_span.InnerText = $"Base64::{imageDic[id]}"; _span.InnerText = $"Base64::{value}";
sub.Contents.Add(_span); sub.Contents.Add(_span);
sub.ContentStrings.Add(_span.OuterXml); sub.ContentStrings.Add(_span.OuterXml);
} }
@ -333,8 +325,8 @@ namespace Mp4SubtitleParser
// Check if one <p> has been splitted // Check if one <p> has been splitted
var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings)); var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings));
// Skip empty lines // Skip empty lines
if (sub.ContentStrings.Count > 0) if (sub.ContentStrings.Count <= 0)
{ continue;
// Extend <p> duration // Extend <p> duration
if (index != -1) if (index != -1)
finalSubs[index].End = sub.End; finalSubs[index].End = sub.End;
@ -342,7 +334,6 @@ namespace Mp4SubtitleParser
finalSubs.Add(sub); finalSubs.Add(sub);
} }
} }
}
var dic = new Dictionary<string, string>(); var dic = new Dictionary<string, string>();
@ -381,4 +372,3 @@ namespace Mp4SubtitleParser
return WebVttSub.Parse(vtt.ToString(), baseTimestamp); return WebVttSub.Parse(vtt.ToString(), baseTimestamp);
} }
} }
}

View File

@ -3,7 +3,7 @@ using System.Text;
namespace Mp4SubtitleParser; namespace Mp4SubtitleParser;
public class MP4VttUtil public static class MP4VttUtil
{ {
public static (bool, uint) CheckInit(byte[] data) public static (bool, uint) CheckInit(byte[] data)
{ {
@ -15,16 +15,16 @@ public class MP4VttUtil
.Box("moov", MP4Parser.Children) .Box("moov", MP4Parser.Children)
.Box("trak", MP4Parser.Children) .Box("trak", MP4Parser.Children)
.Box("mdia", 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"); throw new Exception("MDHD version can only be 0 or 1");
timescale = MP4Parser.ParseMDHD(box.Reader, box.Version); timescale = MP4Parser.ParseMDHD(box.Reader, box.Version);
}) })
.Box("minf", MP4Parser.Children) .Box("minf", MP4Parser.Children)
.Box("stbl", MP4Parser.Children) .Box("stbl", MP4Parser.Children)
.FullBox("stsd", MP4Parser.SampleDescription) .FullBox("stsd", MP4Parser.SampleDescription)
.Box("wvtt", (box) => { .Box("wvtt", _ => {
// A valid vtt init segment, though we have no actual subtitles yet. // A valid vtt init segment, though we have no actual subtitles yet.
sawWVTT = true; sawWVTT = true;
}) })
@ -38,7 +38,7 @@ public class MP4VttUtil
if (timescale == 0) if (timescale == 0)
throw new Exception("Missing timescale for VTT content!"); throw new Exception("Missing timescale for VTT content!");
List<SubCue> cues = new(); List<SubCue> cues = [];
foreach (var item in files) foreach (var item in files)
{ {
@ -50,27 +50,27 @@ public class MP4VttUtil
byte[]? rawPayload = null; byte[]? rawPayload = null;
ulong baseTime = 0; ulong baseTime = 0;
ulong defaultDuration = 0; ulong defaultDuration = 0;
List<Sample> presentations = new(); List<Sample> presentations = [];
// parse media // parse media
new MP4Parser() new MP4Parser()
.Box("moof", MP4Parser.Children) .Box("moof", MP4Parser.Children)
.Box("traf", MP4Parser.Children) .Box("traf", MP4Parser.Children)
.FullBox("tfdt", (box) => .FullBox("tfdt", box =>
{ {
sawTFDT = true; 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"); throw new Exception("TFDT version can only be 0 or 1");
baseTime = MP4Parser.ParseTFDT(box.Reader, box.Version); baseTime = MP4Parser.ParseTFDT(box.Reader, box.Version);
}) })
.FullBox("tfhd", (box) => .FullBox("tfhd", box =>
{ {
if (box.Flags == 1000) if (box.Flags == 1000)
throw new Exception("A TFHD box should have a valid flags value"); throw new Exception("A TFHD box should have a valid flags value");
defaultDuration = MP4Parser.ParseTFHD(box.Reader, box.Flags).DefaultSampleDuration; defaultDuration = MP4Parser.ParseTFHD(box.Reader, box.Flags).DefaultSampleDuration;
}) })
.FullBox("trun", (box) => .FullBox("trun", box =>
{ {
sawTRUN = true; sawTRUN = true;
if (box.Version == 1000) if (box.Version == 1000)
@ -79,7 +79,7 @@ public class MP4VttUtil
throw new Exception("A TRUN box should have a valid flags value"); throw new Exception("A TRUN box should have a valid flags value");
presentations = MP4Parser.ParseTRUN(box.Reader, box.Version, box.Flags).SampleData; presentations = MP4Parser.ParseTRUN(box.Reader, box.Version, box.Flags).SampleData;
}) })
.Box("mdat", MP4Parser.AllData((data) => .Box("mdat", MP4Parser.AllData(data =>
{ {
if (sawMDAT) if (sawMDAT)
throw new Exception("VTT cues in mp4 with multiple MDAT are not currently supported"); 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 (payload != null)
{ {
if (timescale == 0)
throw new Exception("Timescale should not be zero!");
var cue = ParseVTTC( var cue = ParseVTTC(
payload, payload,
0 + (double)startTime / timescale, 0 + (double)startTime / timescale,
@ -192,15 +190,15 @@ public class MP4VttUtil
string id = string.Empty; string id = string.Empty;
string settings = string.Empty; string settings = string.Empty;
new MP4Parser() new MP4Parser()
.Box("payl", MP4Parser.AllData((data) => .Box("payl", MP4Parser.AllData(data =>
{ {
payload = Encoding.UTF8.GetString(data); payload = Encoding.UTF8.GetString(data);
})) }))
.Box("iden", MP4Parser.AllData((data) => .Box("iden", MP4Parser.AllData(data =>
{ {
id = Encoding.UTF8.GetString(data); id = Encoding.UTF8.GetString(data);
})) }))
.Box("sttg", MP4Parser.AllData((data) => .Box("sttg", MP4Parser.AllData(data =>
{ {
settings = Encoding.UTF8.GetString(data); settings = Encoding.UTF8.GetString(data);
})) }))

View File

@ -13,7 +13,7 @@ namespace N_m3u8DL_RE.Parser.Mp4;
public partial class MSSMoovProcessor public partial class MSSMoovProcessor
{ {
[GeneratedRegex("\\<KID\\>(.*?)\\<")] [GeneratedRegex(@"\<KID\>(.*?)\<")]
private static partial Regex KIDRegex(); private static partial Regex KIDRegex();
private static string StartCode = "00000001"; private static string StartCode = "00000001";
@ -23,7 +23,7 @@ public partial class MSSMoovProcessor
private string CodecPrivateData; private string CodecPrivateData;
private int Timesacle; private int Timesacle;
private long Duration; 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 Width => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').First());
private int Height => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').Last()); private int Height => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').Last());
private string StreamType; private string StreamType;
@ -36,8 +36,8 @@ public partial class MSSMoovProcessor
private bool IsProtection; private bool IsProtection;
private string ProtectionSystemId; private string ProtectionSystemId;
private string ProtectionData; private string ProtectionData;
private string ProtecitonKID; private string? ProtecitonKID;
private string ProtecitonKID_PR; private string? ProtecitonKID_PR;
private byte[] UnityMatrix private byte[] UnityMatrix
{ {
get get
@ -60,10 +60,9 @@ public partial class MSSMoovProcessor
private static byte TRACK_IN_MOVIE = 0x2; private static byte TRACK_IN_MOVIE = 0x2;
private static byte TRACK_IN_PREVIEW = 0x4; private static byte TRACK_IN_PREVIEW = 0x4;
private static byte SELF_CONTAINED = 0x1; private static byte SELF_CONTAINED = 0x1;
private static List<string> SupportedFourCC = new()
{ private static List<string> SupportedFourCC =
"HVC1","HEV1","AACL","AACH","EC-3","H264","AVC1","DAVC","AVC1","TTML","DVHE","DVH1" ["HVC1", "HEV1", "AACL", "AACH", "EC-3", "H264", "AVC1", "DAVC", "AVC1", "TTML", "DVHE", "DVH1"];
};
public MSSMoovProcessor(StreamSpec streamSpec) 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 private int SamplingFrequencyIndex(int samplingRate) => samplingRate switch
{ {
96000 => 0x0, 96000 => 0x0,
@ -169,8 +168,8 @@ public partial class MSSMoovProcessor
// save kid for playready // save kid for playready
this.ProtecitonKID_PR = HexUtil.BytesToHex(kidBytes); this.ProtecitonKID_PR = HexUtil.BytesToHex(kidBytes);
// fix byte order // fix byte order
var reverse1 = new byte[4] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] }; var reverse1 = new[] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] };
var reverse2 = new byte[4] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] }; var reverse2 = new[] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] };
Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length); Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length);
Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length); Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length);
this.ProtecitonKID = HexUtil.BytesToHex(kidBytes); this.ProtecitonKID = HexUtil.BytesToHex(kidBytes);
@ -217,13 +216,13 @@ public partial class MSSMoovProcessor
var schmPayload = new List<byte>(); var schmPayload = new List<byte>();
schmPayload.AddRange(Encoding.ASCII.GetBytes("cenc")); // scheme_type 'cenc' => common encryption 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()); var schmBox = FullBox("schm", 0, 0, schmPayload.ToArray());
sinfPayload.AddRange(schmBox); sinfPayload.AddRange(schmBox);
var tencPayload = new List<byte>(); var tencPayload = new List<byte>();
tencPayload.AddRange(new byte[] { 0, 0 }); tencPayload.AddRange([0, 0]);
tencPayload.Add(0x1); // default_IsProtected tencPayload.Add(0x1); // default_IsProtected
tencPayload.Add(0x8); // default_Per_Sample_IV_size tencPayload.Add(0x8); // default_Per_Sample_IV_size
tencPayload.AddRange(HexUtil.HexToBytes(ProtecitonKID)); // default_KID tencPayload.AddRange(HexUtil.HexToBytes(ProtecitonKID)); // default_KID
@ -368,7 +367,7 @@ public partial class MSSMoovProcessor
} }
else if (StreamType == "text") 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 else
{ {
@ -377,7 +376,7 @@ public partial class MSSMoovProcessor
var drefPayload = new List<byte>(); var drefPayload = new List<byte>();
drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(1); // entry count 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 var dinfPayload = FullBox("dref", 0, 0, drefPayload.ToArray()); // Data Reference Box
minfPayload.AddRange(Box("dinf", dinfPayload.ToArray())); // Data Information Box minfPayload.AddRange(Box("dinf", dinfPayload.ToArray())); // Data Information Box
@ -466,11 +465,8 @@ public partial class MSSMoovProcessor
writer.Write(sinfBox); writer.Write(sinfBox);
return Box("enca", stream.ToArray()); // Encrypted Audio return Box("enca", stream.ToArray()); // Encrypted Audio
} }
else
{
return Box("mp4a", stream.ToArray()); return Box("mp4a", stream.ToArray());
} }
}
if (FourCC == "EC-3") if (FourCC == "EC-3")
{ {
if (IsProtection) if (IsProtection)
@ -479,12 +475,9 @@ public partial class MSSMoovProcessor
writer.Write(sinfBox); writer.Write(sinfBox);
return Box("enca", stream.ToArray()); // Encrypted Audio return Box("enca", stream.ToArray()); // Encrypted Audio
} }
else
{
return Box("ec-3", stream.ToArray()); return Box("ec-3", stream.ToArray());
} }
} }
}
else if (StreamType == "video") else if (StreamType == "video")
{ {
writer.WriteUShort(0); // pre defined writer.WriteUShort(0); // pre defined
@ -507,11 +500,11 @@ public partial class MSSMoovProcessor
var codecPrivateData = HexUtil.HexToBytes(CodecPrivateData); 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 arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries);
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 7).First()); var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 7));
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 8).First()); var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 8));
// make avcC // make avcC
var avcC = GetAvcC(sps, pps); var avcC = GetAvcC(sps, pps);
writer.Write(avcC); writer.Write(avcC);
@ -521,17 +514,14 @@ public partial class MSSMoovProcessor
writer.Write(sinfBox); writer.Write(sinfBox);
return Box("encv", stream.ToArray()); // Encrypted Video return Box("encv", stream.ToArray()); // Encrypted Video
} }
else
{
return Box("avc1", stream.ToArray()); // AVC Simple Entry return Box("avc1", stream.ToArray()); // AVC Simple Entry
} }
} if (FourCC is "HVC1" or "HEV1")
else if (FourCC == "HVC1" || FourCC == "HEV1")
{ {
var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries); var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries);
var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First()); var vps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20));
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First()); var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21));
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First()); var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22));
// make hvcC // make hvcC
var hvcC = GetHvcC(sps, pps, vps); var hvcC = GetHvcC(sps, pps, vps);
writer.Write(hvcC); writer.Write(hvcC);
@ -541,18 +531,15 @@ public partial class MSSMoovProcessor
writer.Write(sinfBox); writer.Write(sinfBox);
return Box("encv", stream.ToArray()); // Encrypted Video 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处理 // 杜比视界也按照hevc处理
else if (FourCC == "DVHE" || FourCC == "DVH1") if (FourCC is "DVHE" or "DVH1")
{ {
var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries); var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries);
var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First()); var vps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20));
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First()); var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21));
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First()); var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22));
// make hvcC // make hvcC
var hvcC = GetHvcC(sps, pps, vps, "dvh1"); var hvcC = GetHvcC(sps, pps, vps, "dvh1");
writer.Write(hvcC); writer.Write(hvcC);
@ -562,16 +549,11 @@ public partial class MSSMoovProcessor
writer.Write(sinfBox); writer.Write(sinfBox);
return Box("encv", stream.ToArray()); // Encrypted Video return Box("encv", stream.ToArray()); // Encrypted Video
} }
else
{
return Box("dvh1", stream.ToArray()); // HEVC Simple Entry return Box("dvh1", stream.ToArray()); // HEVC Simple Entry
} }
}
else
{
throw new NotSupportedException(); throw new NotSupportedException();
} }
}
else if (StreamType == "text") else if (StreamType == "text")
{ {
if (FourCC == "TTML") if (FourCC == "TTML")
@ -581,11 +563,8 @@ public partial class MSSMoovProcessor
writer.Write("\0"); // auxilary mime types(??) writer.Write("\0"); // auxilary mime types(??)
return Box("stpp", stream.ToArray()); // TTML Simple Entry return Box("stpp", stream.ToArray()); // TTML Simple Entry
} }
else
{
throw new NotSupportedException(); throw new NotSupportedException();
} }
}
else else
{ {
throw new NotSupportedException(); throw new NotSupportedException();
@ -635,7 +614,7 @@ public partial class MSSMoovProcessor
while (_reader.BaseStream.Position < _reader.BaseStream.Length) while (_reader.BaseStream.Position < _reader.BaseStream.Length)
{ {
encList.Add(_reader.ReadByte()); 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); encList.RemoveAt(encList.Count - 1);
} }
@ -805,7 +784,7 @@ public partial class MSSMoovProcessor
new MP4Parser() new MP4Parser()
.Box("moof", MP4Parser.Children) .Box("moof", MP4Parser.Children)
.Box("traf", MP4Parser.Children) .Box("traf", MP4Parser.Children)
.FullBox("tfhd", (box) => .FullBox("tfhd", box =>
{ {
TrackId = (int)box.Reader.ReadUInt32(); TrackId = (int)box.Reader.ReadUInt32();
}) })

View File

@ -1,10 +1,5 @@
using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Parser.Config; 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; namespace N_m3u8DL_RE.Parser.Processor;

View File

@ -1,11 +1,6 @@
using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Parser.Config; 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; namespace N_m3u8DL_RE.Parser.Processor.DASH;
@ -18,11 +13,7 @@ public class DefaultDASHContentProcessor : ContentProcessor
{ {
if (extractorType != ExtractorType.MPEG_DASH) return false; if (extractorType != ExtractorType.MPEG_DASH) return false;
if (mpdContent.Contains("<mas:") && !mpdContent.Contains("xmlns:mas")) return mpdContent.Contains("<mas:") && !mpdContent.Contains("xmlns:mas");
{
return true;
}
return false;
} }
public override string Process(string mpdContent, ParserConfig parserConfig) public override string Process(string mpdContent, ParserConfig parserConfig)

View File

@ -1,12 +1,6 @@
using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Parser.Config; using N_m3u8DL_RE.Parser.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web; using System.Web;
namespace N_m3u8DL_RE.Parser.Processor; namespace N_m3u8DL_RE.Parser.Processor;
@ -17,8 +11,8 @@ public class DefaultUrlProcessor : UrlProcessor
public override string Process(string oriUrl, ParserConfig paserConfig) public override string Process(string oriUrl, ParserConfig paserConfig)
{ {
if (oriUrl.StartsWith("http")) if (!oriUrl.StartsWith("http")) return oriUrl;
{
var uriFromConfig = new Uri(paserConfig.Url); var uriFromConfig = new Uri(paserConfig.Url);
var uriFromConfigQuery = HttpUtility.ParseQueryString(uriFromConfig.Query); var uriFromConfigQuery = HttpUtility.ParseQueryString(uriFromConfig.Query);
@ -32,13 +26,11 @@ public class DefaultUrlProcessor : UrlProcessor
newQuery.Add(item, uriFromConfigQuery.Get(item)); newQuery.Add(item, uriFromConfigQuery.Get(item));
} }
if (!string.IsNullOrEmpty(newQuery.ToString())) if (string.IsNullOrEmpty(newQuery.ToString())) return oriUrl;
{
Logger.Debug("Before: " + oriUrl); Logger.Debug("Before: " + oriUrl);
oriUrl = (oldUri.GetLeftPart(UriPartial.Path) + "?" + newQuery.ToString()).TrimEnd('?'); oriUrl = (oldUri.GetLeftPart(UriPartial.Path) + "?" + newQuery).TrimEnd('?');
Logger.Debug("After: " + oriUrl); Logger.Debug("After: " + oriUrl);
}
}
return oriUrl; return oriUrl;
} }

View File

@ -1,12 +1,7 @@
using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Parser.Config; using N_m3u8DL_RE.Parser.Config;
using N_m3u8DL_RE.Parser.Constants; using N_m3u8DL_RE.Parser.Constants;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Parser.Processor.HLS; namespace N_m3u8DL_RE.Parser.Processor.HLS;
@ -16,13 +11,13 @@ public partial class DefaultHLSContentProcessor : ContentProcessor
private static partial Regex YkDVRegex(); private static partial Regex YkDVRegex();
[GeneratedRegex("#EXT-X-MAP:URI=\\\".*?BUMPER/[\\s\\S]+?#EXT-X-DISCONTINUITY")] [GeneratedRegex("#EXT-X-MAP:URI=\\\".*?BUMPER/[\\s\\S]+?#EXT-X-DISCONTINUITY")]
private static partial Regex DNSPRegex(); private static partial Regex DNSPRegex();
[GeneratedRegex("#EXTINF:.*?,\\s+.*BUMPER.*\\s+?#EXT-X-DISCONTINUITY")] [GeneratedRegex(@"#EXTINF:.*?,\s+.*BUMPER.*\s+?#EXT-X-DISCONTINUITY")]
private static partial Regex DNSPSubRegex(); private static partial Regex DNSPSubRegex();
[GeneratedRegex("(#EXTINF.*)(\\s+)(#EXT-X-KEY.*)")] [GeneratedRegex("(#EXTINF.*)(\\s+)(#EXT-X-KEY.*)")]
private static partial Regex OrderFixRegex(); private static partial Regex OrderFixRegex();
[GeneratedRegex("#EXT-X-MAP.*\\.apple\\.com/")] [GeneratedRegex(@"#EXT-X-MAP.*\.apple\.com/")]
private static partial Regex ATVRegex(); private static partial Regex ATVRegex();
[GeneratedRegex("(#EXT-X-KEY:[\\s\\S]*?)(#EXT-X-DISCONTINUITY|#EXT-X-ENDLIST)")] [GeneratedRegex(@"(#EXT-X-KEY:[\s\S]*?)(#EXT-X-DISCONTINUITY|#EXT-X-ENDLIST)")]
private static partial Regex ATVRegex2(); private static partial Regex ATVRegex2();
public override bool CanProcess(ExtractorType extractorType, string rawText, ParserConfig parserConfig) => extractorType == ExtractorType.HLS; public override bool CanProcess(ExtractorType extractorType, string rawText, ParserConfig parserConfig) => extractorType == ExtractorType.HLS;
@ -30,7 +25,7 @@ public partial class DefaultHLSContentProcessor : ContentProcessor
public override string Process(string m3u8Content, ParserConfig parserConfig) public override string Process(string m3u8Content, ParserConfig parserConfig)
{ {
// 处理content以\r作为换行符的情况 // 处理content以\r作为换行符的情况
if (m3u8Content.Contains("\r") && !m3u8Content.Contains("\n")) if (m3u8Content.Contains('\r') && !m3u8Content.Contains('\n'))
{ {
m3u8Content = m3u8Content.Replace("\r", Environment.NewLine); m3u8Content = m3u8Content.Replace("\r", Environment.NewLine);
} }
@ -51,7 +46,7 @@ public partial class DefaultHLSContentProcessor : ContentProcessor
// 针对YK #EXT-X-VERSION:7杜比视界片源修正 // 针对YK #EXT-X-VERSION:7杜比视界片源修正
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Content.Contains("ott.cibntv.net") && m3u8Content.Contains("ccode=")) 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)) 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}"); m3u8Content = m3u8Content.Replace(m.Value, $"#EXTINF:0.000000,\n#EXT-X-BYTERANGE:{m.Groups[2].Value}\n{m.Groups[1].Value}");

View File

@ -83,12 +83,11 @@ public class DefaultHLSKeyProcessor : KeyProcessor
encryptInfo.Method = EncryptMethod.UNKNOWN; encryptInfo.Method = EncryptMethod.UNKNOWN;
} }
if (parserConfig.CustomMethod == null) return encryptInfo;
// 处理自定义加密方式 // 处理自定义加密方式
if (parserConfig.CustomMethod != null)
{
encryptInfo.Method = parserConfig.CustomMethod.Value; encryptInfo.Method = parserConfig.CustomMethod.Value;
Logger.Warn("METHOD changed from {} to {}", method, encryptInfo.Method); Logger.Warn("METHOD changed from {} to {}", method, encryptInfo.Method);
}
return encryptInfo; return encryptInfo;
} }

View File

@ -1,11 +1,6 @@
using N_m3u8DL_RE.Common.Entity; using N_m3u8DL_RE.Common.Entity;
using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Parser.Config; 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; namespace N_m3u8DL_RE.Parser.Processor;

View File

@ -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.Entity;
using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Resource; using N_m3u8DL_RE.Common.Resource;
@ -19,11 +20,6 @@ public class StreamExtractor
public Dictionary<string, string> RawFiles { get; set; } = new(); // 存储(文件名,文件内容) public Dictionary<string, string> RawFiles { get; set; } = new(); // 存储(文件名,文件内容)
public StreamExtractor()
{
}
public StreamExtractor(ParserConfig parserConfig) public StreamExtractor(ParserConfig parserConfig)
{ {
this.parserConfig = parserConfig; this.parserConfig = parserConfig;
@ -54,7 +50,8 @@ public class StreamExtractor
LoadSourceFromText(this.rawText); LoadSourceFromText(this.rawText);
} }
public void LoadSourceFromText(string rawText) [MemberNotNull(nameof(this.rawText), nameof(this.extractor))]
private void LoadSourceFromText(string rawText)
{ {
var rawType = "txt"; var rawType = "txt";
rawText = rawText.Trim(); rawText = rawText.Trim();

View File

@ -3,9 +3,9 @@ using System.Text.RegularExpressions;
namespace N_m3u8DL_RE.Parser.Util; 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(); private static partial Regex VarsNumberRegex();
/// <summary> /// <summary>
@ -33,8 +33,7 @@ public partial class ParserUtil
{ {
var startIndex = index + (key + "=").Length; var startIndex = index + (key + "=").Length;
var endIndex = startIndex + line[startIndex..].IndexOf(','); var endIndex = startIndex + line[startIndex..].IndexOf(',');
if (endIndex >= startIndex) result = line[startIndex..endIndex]; result = endIndex >= startIndex ? line[startIndex..endIndex] : line[startIndex..];
else result = line[startIndex..];
} }
return result; return result;
@ -49,18 +48,13 @@ public partial class ParserUtil
public static (long, long?) GetRange(string input) public static (long, long?) GetRange(string input)
{ {
var t = input.Split('@'); var t = input.Split('@');
if (t.Length > 0) return t.Length switch
{ {
if (t.Length == 1) <= 0 => (0, null),
{ 1 => (Convert.ToInt64(t[0]), null),
return (Convert.ToInt64(t[0]), null); 2 => (Convert.ToInt64(t[0]), Convert.ToInt64(t[1])),
} _ => (0, null)
if (t.Length == 2) };
{
return (Convert.ToInt64(t[0]), Convert.ToInt64(t[1]));
}
}
return (0, null);
} }
/// <summary> /// <summary>
@ -111,8 +105,8 @@ public partial class ParserUtil
if (string.IsNullOrEmpty(baseurl)) if (string.IsNullOrEmpty(baseurl))
return url; return url;
Uri uri1 = new Uri(baseurl); // 这里直接传完整的URL即可 var uri1 = new Uri(baseurl); // 这里直接传完整的URL即可
Uri uri2 = new Uri(uri1, url); var uri2 = new Uri(uri1, url);
url = uri2.ToString(); url = uri2.ToString();
return url; return url;

View File

@ -27,7 +27,7 @@ internal sealed class DownloadSpeedColumn : ProgressColumn
var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
var flag = task.IsFinished || !task.IsStarted; var flag = task.IsFinished || !task.IsStarted;
// 单文件下载汇报进度 // 单文件下载汇报进度
if (!flag && speedContainer.SingleSegment && speedContainer.ResponseLength != null) if (!flag && speedContainer is { SingleSegment: true, ResponseLength: not null })
{ {
task.MaxValue = (double)speedContainer.ResponseLength; task.MaxValue = (double)speedContainer.ResponseLength;
task.Value = speedContainer.RDownloaded; task.Value = speedContainer.RDownloaded;

View File

@ -25,9 +25,6 @@ internal class RecordingDurationColumn : ProgressColumn
{ {
if (_refreshedDurDic == null) if (_refreshedDurDic == null)
return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}", MyStyle).LeftJustified(); 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);
} }
} }
}

View File

@ -15,14 +15,16 @@ using System.Text.RegularExpressions;
namespace N_m3u8DL_RE.CommandLine; 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)")] [GeneratedRegex("((best|worst)\\d*|all)")]
private static partial Regex ForStrRegex(); private static partial Regex ForStrRegex();
[GeneratedRegex("(\\d*)-(\\d*)")] [GeneratedRegex(@"(\d*)-(\d*)")]
private static partial Regex RangeRegex(); private static partial Regex RangeRegex();
[GeneratedRegex(@"([\d\\.]+)(M|K)")]
private static partial Regex SpeedStrRegex();
private static readonly Argument<string> Input = new(name: "input", description: ResString.cmd_Input); private static readonly Argument<string> Input = new(name: "input", description: ResString.cmd_Input);
private static readonly Option<string?> TmpDir = new(["--tmp-dir"], description: ResString.cmd_tmpDir); private static readonly Option<string?> TmpDir = new(["--tmp-dir"], description: ResString.cmd_tmpDir);
@ -110,22 +112,21 @@ internal partial class CommandInvoker
private static readonly Option<StreamFilter?> DropSubtitleFilter = new(["-ds", "--drop-subtitle"], description: ResString.cmd_dropSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" }; private static readonly Option<StreamFilter?> DropSubtitleFilter = new(["-ds", "--drop-subtitle"], description: ResString.cmd_dropSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
/// <summary> /// <summary>
/// 解析录制直播时长限制 /// 解析下载速度限制
/// </summary> /// </summary>
/// <param name="result"></param> /// <param name="result"></param>
/// <returns></returns> /// <returns></returns>
private static long? ParseSpeedLimit(ArgumentResult result) private static long? ParseSpeedLimit(ArgumentResult result)
{ {
var input = result.Tokens.First().Value.ToUpper(); var input = result.Tokens[0].Value.ToUpper();
try try
{ {
var reg = new Regex("([\\d\\\\.]+)(M|K)"); var reg = SpeedStrRegex();
if (!reg.IsMatch(input)) throw new ArgumentException(); if (!reg.IsMatch(input)) throw new ArgumentException($"Invalid Speed Limit: {input}");
var number = double.Parse(reg.Match(input).Groups[1].Value); var number = double.Parse(reg.Match(input).Groups[1].Value);
if (reg.Match(input).Groups[2].Value == "M") if (reg.Match(input).Groups[2].Value == "M")
return (long)(number * 1024 * 1024); return (long)(number * 1024 * 1024);
else
return (long)(number * 1024); return (long)(number * 1024);
} }
catch (Exception) catch (Exception)
@ -143,7 +144,7 @@ internal partial class CommandInvoker
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
private static CustomRange? ParseCustomRange(ArgumentResult result) 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; // 支持的种类 0-100; 01:00:00-02:30:00; -300; 300-; 05:00-; -03:00;
try try
{ {
@ -154,7 +155,7 @@ internal partial class CommandInvoker
if (arr.Length != 2) if (arr.Length != 2)
throw new ArgumentException("Bad format!"); throw new ArgumentException("Bad format!");
if (input.Contains(":")) if (input.Contains(':'))
{ {
return new CustomRange() return new CustomRange()
{ {
@ -163,7 +164,8 @@ internal partial class CommandInvoker
EndSec = arr[1] == "" ? double.MaxValue : OtherUtil.ParseDur(arr[1]).TotalSeconds, 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 left = RangeRegex().Match(input).Groups[1].Value;
var right = RangeRegex().Match(input).Groups[2].Value; var right = RangeRegex().Match(input).Groups[2].Value;
@ -192,7 +194,7 @@ internal partial class CommandInvoker
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
private static WebProxy? ParseProxy(ArgumentResult result) private static WebProxy? ParseProxy(ArgumentResult result)
{ {
var input = result.Tokens.First().Value; var input = result.Tokens[0].Value;
try try
{ {
if (string.IsNullOrEmpty(input)) if (string.IsNullOrEmpty(input))
@ -221,16 +223,15 @@ internal partial class CommandInvoker
/// <returns></returns> /// <returns></returns>
private static byte[]? ParseHLSCustomKey(ArgumentResult result) private static byte[]? ParseHLSCustomKey(ArgumentResult result)
{ {
var input = result.Tokens.First().Value; var input = result.Tokens[0].Value;
try try
{ {
if (string.IsNullOrEmpty(input)) if (string.IsNullOrEmpty(input))
return null; return null;
if (File.Exists(input)) if (File.Exists(input))
return File.ReadAllBytes(input); return File.ReadAllBytes(input);
else if (HexUtil.TryParseHexString(input, out byte[]? bytes)) if (HexUtil.TryParseHexString(input, out byte[]? bytes))
return bytes; return bytes;
else
return Convert.FromBase64String(input); return Convert.FromBase64String(input);
} }
catch (Exception) catch (Exception)
@ -247,7 +248,7 @@ internal partial class CommandInvoker
/// <returns></returns> /// <returns></returns>
private static TimeSpan? ParseLiveLimit(ArgumentResult result) private static TimeSpan? ParseLiveLimit(ArgumentResult result)
{ {
var input = result.Tokens.First().Value; var input = result.Tokens[0].Value;
try try
{ {
return OtherUtil.ParseDur(input); return OtherUtil.ParseDur(input);
@ -266,7 +267,7 @@ internal partial class CommandInvoker
/// <returns></returns> /// <returns></returns>
private static DateTime? ParseStartTime(ArgumentResult result) private static DateTime? ParseStartTime(ArgumentResult result)
{ {
var input = result.Tokens.First().Value; var input = result.Tokens[0].Value;
try try
{ {
CultureInfo provider = CultureInfo.InvariantCulture; CultureInfo provider = CultureInfo.InvariantCulture;
@ -281,7 +282,7 @@ internal partial class CommandInvoker
private static string? ParseSaveName(ArgumentResult result) private static string? ParseSaveName(ArgumentResult result)
{ {
var input = result.Tokens.First().Value; var input = result.Tokens[0].Value;
var newName = OtherUtil.GetValidFileName(input); var newName = OtherUtil.GetValidFileName(input);
if (string.IsNullOrEmpty(newName)) if (string.IsNullOrEmpty(newName))
{ {
@ -299,7 +300,7 @@ internal partial class CommandInvoker
private static StreamFilter? ParseStreamFilter(ArgumentResult result) private static StreamFilter? ParseStreamFilter(ArgumentResult result)
{ {
var streamFilter = new StreamFilter(); var streamFilter = new StreamFilter();
var input = result.Tokens.First().Value; var input = result.Tokens[0].Value;
var p = new ComplexParamParser(input); var p = new ComplexParamParser(input);
@ -437,7 +438,7 @@ internal partial class CommandInvoker
/// <returns></returns> /// <returns></returns>
private static MuxOptions? ParseMuxAfterDone(ArgumentResult result) private static MuxOptions? ParseMuxAfterDone(ArgumentResult result)
{ {
var v = result.Tokens.First().Value; var v = result.Tokens[0].Value;
var p = new ComplexParamParser(v); var p = new ComplexParamParser(v);
// 混流格式 // 混流格式
var format = p.GetValue("format") ?? v.Split(':')[0]; // 若未获取到,直接:前的字符串作为format解析 var format = p.GetValue("format") ?? v.Split(':')[0]; // 若未获取到,直接:前的字符串作为format解析
@ -577,14 +578,12 @@ internal partial class CommandInvoker
// 混流设置 // 混流设置
var muxAfterDoneValue = bindingContext.ParseResult.GetValueForOption(MuxAfterDone); var muxAfterDoneValue = bindingContext.ParseResult.GetValueForOption(MuxAfterDone);
if (muxAfterDoneValue != null) if (muxAfterDoneValue == null) return option;
{
option.MuxAfterDone = true; option.MuxAfterDone = true;
option.MuxOptions = muxAfterDoneValue; option.MuxOptions = muxAfterDoneValue;
if (muxAfterDoneValue.UseMkvmerge) option.MkvmergeBinaryPath = muxAfterDoneValue.BinPath; if (muxAfterDoneValue.UseMkvmerge) option.MkvmergeBinaryPath = muxAfterDoneValue.BinPath;
else option.FFmpegBinaryPath ??= muxAfterDoneValue.BinPath; else option.FFmpegBinaryPath ??= muxAfterDoneValue.BinPath;
}
return option; return option;
} }
@ -626,7 +625,7 @@ internal partial class CommandInvoker
}; };
rootCommand.TreatUnmatchedTokensAsErrors = true; 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) var parser = new CommandLineBuilder(rootCommand)
.UseDefaults() .UseDefaults()

View File

@ -4,7 +4,7 @@ namespace N_m3u8DL_RE.CommandLine;
internal class ComplexParamParser internal class ComplexParamParser
{ {
private string _arg; private readonly string _arg;
public ComplexParamParser(string arg) public ComplexParamParser(string arg)
{ {
_arg = arg; _arg = arg;
@ -16,7 +16,7 @@ internal class ComplexParamParser
try 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; if (index == -1) return (_arg.Contains(key) && _arg.EndsWith(key)) ? "true" : null;
var chars = _arg[(index + key.Length + 1)..].ToCharArray(); var chars = _arg[(index + key.Length + 1)..].ToCharArray();

View File

@ -2,7 +2,7 @@
namespace N_m3u8DL_RE.Crypto; namespace N_m3u8DL_RE.Crypto;
internal class AESUtil internal static class AESUtil
{ {
/// <summary> /// <summary>
/// AES-128解密解密后原地替换文件 /// AES-128解密解密后原地替换文件

View File

@ -2,7 +2,7 @@
namespace N_m3u8DL_RE.Crypto; namespace N_m3u8DL_RE.Crypto;
internal class ChaCha20Util internal static class ChaCha20Util
{ {
public static byte[] DecryptPer1024Bytes(byte[] encryptedBuff, byte[] keyBytes, byte[] nonceBytes) public static byte[] DecryptPer1024Bytes(byte[] encryptedBuff, byte[] keyBytes, byte[] nonceBytes)
{ {

View File

@ -20,7 +20,7 @@ internal class HTTPLiveRecordManager
DownloaderConfig DownloaderConfig; DownloaderConfig DownloaderConfig;
StreamExtractor StreamExtractor; StreamExtractor StreamExtractor;
List<StreamSpec> SelectedSteams; List<StreamSpec> SelectedSteams;
List<OutputFile> OutputFiles = new(); List<OutputFile> OutputFiles = [];
DateTime NowDateTime; DateTime NowDateTime;
DateTime? PublishDateTime; DateTime? PublishDateTime;
bool STOP_FLAG = false; bool STOP_FLAG = false;
@ -107,7 +107,7 @@ internal class HTTPLiveRecordManager
await Task.Delay(200); await Task.Delay(200);
if (InfoBuffer.Count < 188 * 5000) continue; if (InfoBuffer.Count < 188 * 5000) continue;
UInt16 ConvertToUint16(IEnumerable<byte> bytes) ushort ConvertToUint16(IEnumerable<byte> bytes)
{ {
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
bytes = bytes.Reverse(); bytes = bytes.Reverse();
@ -217,7 +217,7 @@ internal class HTTPLiveRecordManager
return (item, task); return (item, task);
}).ToDictionary(item => item.item, item => 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; var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
if (limit != TimeSpan.MaxValue) if (limit != TimeSpan.MaxValue)
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimit}{GlobalUtil.FormatTime((int)limit.Value.TotalSeconds)}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimit}{GlobalUtil.FormatTime((int)limit.Value.TotalSeconds)}[/]");

View File

@ -23,7 +23,7 @@ internal class SimpleDownloadManager
DownloaderConfig DownloaderConfig; DownloaderConfig DownloaderConfig;
StreamExtractor StreamExtractor; StreamExtractor StreamExtractor;
List<StreamSpec> SelectedSteams; List<StreamSpec> SelectedSteams;
List<OutputFile> OutputFiles = new(); List<OutputFile> OutputFiles = [];
public SimpleDownloadManager(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor) public SimpleDownloadManager(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
{ {
@ -48,13 +48,13 @@ internal class SimpleDownloadManager
private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> mediainfos, ref bool useAACFilter) private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> 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; DownloaderConfig.MyOptions.BinaryMerge = true;
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge2}[/]"); 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; DownloaderConfig.MyOptions.MuxAfterDone = false;
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]");
@ -72,7 +72,7 @@ internal class SimpleDownloadManager
else if (mediainfos.All(m => m.Type == "Subtitle")) else if (mediainfos.All(m => m.Type == "Subtitle"))
{ {
streamSpec.MediaType = MediaType.SUBTITLES; streamSpec.MediaType = MediaType.SUBTITLES;
if (streamSpec.Extension == null || streamSpec.Extension == "ts") if (streamSpec.Extension is null or "ts")
streamSpec.Extension = "vtt"; streamSpec.Extension = "vtt";
} }
} }
@ -81,7 +81,7 @@ internal class SimpleDownloadManager
{ {
speedContainer.ResetVars(); speedContainer.ResetVars();
bool useAACFilter = false; // ffmpeg合并flag bool useAACFilter = false; // ffmpeg合并flag
List<Mediainfo> mediaInfos = new(); List<Mediainfo> mediaInfos = [];
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new(); ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
var segments = streamSpec.Playlist?.MediaParts.SelectMany(m => m.MediaSegments); 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 path = Path.Combine(tmpDir, "_init.mp4.tmp");
var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers); var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers);
FileDic[streamSpec.Playlist.MediaInit] = result; FileDic[streamSpec.Playlist.MediaInit] = result;
if (result == null || !result.Success) if (result is not { Success: true })
{ {
throw new Exception("Download init file failed!"); throw new Exception("Download init file failed!");
} }
@ -166,7 +166,7 @@ internal class SimpleDownloadManager
task.Increment(1); task.Increment(1);
// 读取mp4信息 // 读取mp4信息
if (result != null && result.Success) if (result is { Success: true })
{ {
mp4Info = MP4DecryptUtil.GetMP4Info(result.ActualFilePath); mp4Info = MP4DecryptUtil.GetMP4Info(result.ActualFilePath);
currentKID = mp4Info.KID; currentKID = mp4Info.KID;
@ -212,12 +212,12 @@ internal class SimpleDownloadManager
var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp"); var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp");
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers); var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
FileDic[seg] = result; FileDic[seg] = result;
if (result == null || !result.Success) if (result is not { Success: true })
{ {
throw new Exception("Download first segment failed!"); throw new Exception("Download first segment failed!");
} }
task.Increment(1); task.Increment(1);
if (result != null && result.Success) if (result is { Success: true })
{ {
// 修复MSS init // 修复MSS init
if (StreamExtractor.ExtractorType == ExtractorType.MSS) 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 path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp");
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers); var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
FileDic[seg] = result; FileDic[seg] = result;
if (result != null && result.Success) if (result is { Success: true })
task.Increment(1); 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 enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); 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; var outputExt = "." + streamSpec.Extension;
if (streamSpec.Extension == null) outputExt = ".ts"; if (streamSpec.Extension == null) outputExt = ".ts";
else if (streamSpec.MediaType == MediaType.AUDIO && (streamSpec.Extension == "m4s" || streamSpec.Extension == "mp4")) outputExt = ".m4a"; else if (streamSpec is { MediaType: MediaType.AUDIO, Extension: "m4s" or "mp4" }) outputExt = ".m4a";
else if (streamSpec.MediaType != MediaType.SUBTITLES && (streamSpec.Extension == "m4s" || streamSpec.Extension == "mp4")) outputExt = ".mp4"; 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.AutoSubtitleFix && streamSpec.MediaType == MediaType.SUBTITLES)
{ {
if (DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT) outputExt = ".srt"; outputExt = DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT ? ".srt" : ".vtt";
else outputExt = ".vtt";
} }
var output = Path.Combine(saveDir, saveName + outputExt); 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)))}"); 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); File.Delete(mp4InitFile);
// shaka/ffmpeg实时解密不需要init文件用于合并 // shaka/ffmpeg实时解密不需要init文件用于合并
@ -333,7 +332,7 @@ internal class SimpleDownloadManager
// 校验分片数量 // 校验分片数量
if (DownloaderConfig.MyOptions.CheckSegmentsCount && FileDic.Values.Any(s => s == null)) 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; return false;
} }
@ -351,8 +350,7 @@ internal class SimpleDownloadManager
} }
// 自动修复VTT raw字幕 // 自动修复VTT raw字幕
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("vtt"))
&& streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
{ {
Logger.WarnMarkUp(ResString.fixingVTT); Logger.WarnMarkUp(ResString.fixingVTT);
// 排序字幕并修正时间戳 // 排序字幕并修正时间戳
@ -398,7 +396,7 @@ internal class SimpleDownloadManager
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")) && 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 iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes); var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes);
if (sawVtt) if (sawVtt)
@ -432,8 +430,7 @@ internal class SimpleDownloadManager
} }
// 自动修复TTML raw字幕 // 自动修复TTML raw字幕
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("ttml"))
&& streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
{ {
Logger.WarnMarkUp(ResString.fixingTTML); Logger.WarnMarkUp(ResString.fixingTTML);
var first = true; var first = true;
@ -479,8 +476,7 @@ internal class SimpleDownloadManager
} }
// 自动修复TTML mp4字幕 // 自动修复TTML mp4字幕
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("m4s")
&& streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp")) && streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
{ {
Logger.WarnMarkUp(ResString.fixingTTMLmp4); Logger.WarnMarkUp(ResString.fixingTTMLmp4);
@ -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); var files = FileDic.Values.Select(v => v!.ActualFilePath);
foreach (var file in files) foreach (var file in files)
@ -598,7 +594,7 @@ internal class SimpleDownloadManager
} }
// 调用mp4decrypt解密 // 调用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 enc = output;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); 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); 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) foreach (var item in StreamExtractor.RawFiles)
{ {

View File

@ -27,7 +27,7 @@ internal class SimpleLiveRecordManager2
StreamExtractor StreamExtractor; StreamExtractor StreamExtractor;
List<StreamSpec> SelectedSteams; List<StreamSpec> SelectedSteams;
ConcurrentDictionary<int, string> PipeSteamNamesDic = new(); ConcurrentDictionary<int, string> PipeSteamNamesDic = new();
List<OutputFile> OutputFiles = new(); List<OutputFile> OutputFiles = [];
DateTime? PublishDateTime; DateTime? PublishDateTime;
bool STOP_FLAG = false; bool STOP_FLAG = false;
int WAIT_SEC = 0; // 刷新间隔 int WAIT_SEC = 0; // 刷新间隔
@ -41,7 +41,7 @@ internal class SimpleLiveRecordManager2
ConcurrentDictionary<int, long> DateTimeDic = new(); // 上次下载的dateTime ConcurrentDictionary<int, long> DateTimeDic = new(); // 上次下载的dateTime
CancellationTokenSource CancellationTokenSource = new(); // 取消Wait CancellationTokenSource CancellationTokenSource = new(); // 取消Wait
private readonly object lockObj = new object(); private readonly Lock lockObj = new();
TimeSpan? audioStart = null; TimeSpan? audioStart = null;
public SimpleLiveRecordManager2(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor) public SimpleLiveRecordManager2(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
@ -60,9 +60,9 @@ internal class SimpleLiveRecordManager2
if (_key != null) if (_key != null)
{ {
if (DownloaderConfig.MyOptions.Keys == null) if (DownloaderConfig.MyOptions.Keys == null)
DownloaderConfig.MyOptions.Keys = new string[] { _key }; DownloaderConfig.MyOptions.Keys = [_key];
else 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<Mediainfo> mediainfos, ref bool useAACFilter) private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> 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; DownloaderConfig.MyOptions.BinaryMerge = true;
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge2}[/]"); 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; DownloaderConfig.MyOptions.MuxAfterDone = false;
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]");
@ -136,7 +136,7 @@ internal class SimpleLiveRecordManager2
{ {
streamSpec.MediaType = MediaType.SUBTITLES; streamSpec.MediaType = MediaType.SUBTITLES;
if (streamSpec.Extension == null || streamSpec.Extension == "ts") if (streamSpec.Extension is null or "ts")
streamSpec.Extension = "vtt"; streamSpec.Extension = "vtt";
} }
} }
@ -151,7 +151,7 @@ internal class SimpleLiveRecordManager2
bool useAACFilter = false; // ffmpeg合并flag bool useAACFilter = false; // ffmpeg合并flag
bool initDownloaded = false; // 是否下载过init文件 bool initDownloaded = false; // 是否下载过init文件
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new(); ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
List<Mediainfo> mediaInfos = new(); List<Mediainfo> mediaInfos = [];
Stream? fileOutputStream = null; Stream? fileOutputStream = null;
WebVttSub currentVtt = new(); // 字幕流始终维护一个实例 WebVttSub currentVtt = new(); // 字幕流始终维护一个实例
bool firstSub = true; bool firstSub = true;
@ -197,7 +197,7 @@ internal class SimpleLiveRecordManager2
var path = Path.Combine(tmpDir, "_init.mp4.tmp"); var path = Path.Combine(tmpDir, "_init.mp4.tmp");
var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers); var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers);
FileDic[streamSpec.Playlist.MediaInit] = result; FileDic[streamSpec.Playlist.MediaInit] = result;
if (result == null || !result.Success) if (result is not { Success: true })
{ {
throw new Exception("Download init file failed!"); throw new Exception("Download init file failed!");
} }
@ -205,7 +205,7 @@ internal class SimpleLiveRecordManager2
task.Increment(1); task.Increment(1);
// 读取mp4信息 // 读取mp4信息
if (result != null && result.Success) if (result is { Success: true })
{ {
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID; currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
// 从文件读取KEY // 从文件读取KEY
@ -257,12 +257,12 @@ internal class SimpleLiveRecordManager2
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp"); var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers); var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
FileDic[seg] = result; FileDic[seg] = result;
if (result == null || !result.Success) if (result is not { Success: true })
{ {
throw new Exception("Download first segment failed!"); throw new Exception("Download first segment failed!");
} }
task.Increment(1); task.Increment(1);
if (result != null && result.Success) if (result is { Success: true })
{ {
// 修复MSS init // 修复MSS init
if (StreamExtractor.ExtractorType == ExtractorType.MSS) if (StreamExtractor.ExtractorType == ExtractorType.MSS)
@ -330,10 +330,10 @@ internal class SimpleLiveRecordManager2
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp"); var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers); var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
FileDic[seg] = result; FileDic[seg] = result;
if (result != null && result.Success) if (result is { Success: true })
task.Increment(1); 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 enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc)); var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
@ -347,14 +347,13 @@ internal class SimpleLiveRecordManager2
}); });
// 自动修复VTT raw字幕 // 自动修复VTT raw字幕
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("vtt"))
&& streamSpec.Extension != 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) foreach (var seg in keys)
{ {
var vttContent = File.ReadAllText(FileDic[seg]!.ActualFilePath); var vttContent = await File.ReadAllTextAsync(FileDic[seg]!.ActualFilePath);
var waitCount = 0; var waitCount = 0;
while (DownloaderConfig.MyOptions.LiveFixVttByAudio && audioStart == null && waitCount++ < 5) 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 if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")) && 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 iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes); var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes);
if (sawVtt) if (sawVtt)
@ -396,10 +395,9 @@ internal class SimpleLiveRecordManager2
} }
// 自动修复TTML raw字幕 // 自动修复TTML raw字幕
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("ttml"))
&& streamSpec.Extension != 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 (firstSub)
{ {
if (baseTimestamp != 0) if (baseTimestamp != 0)
@ -437,8 +435,7 @@ internal class SimpleLiveRecordManager2
} }
// 自动修复TTML mp4字幕 // 自动修复TTML mp4字幕
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("m4s")
&& streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp")) && streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
{ {
// sawTtml暂时不判断 // sawTtml暂时不判断
@ -498,12 +495,11 @@ internal class SimpleLiveRecordManager2
// 合并 // 合并
var outputExt = "." + streamSpec.Extension; var outputExt = "." + streamSpec.Extension;
if (streamSpec.Extension == null) outputExt = ".ts"; 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 && streamSpec.Extension == "m4s") outputExt = ".mp4";
else if (streamSpec.MediaType == MediaType.SUBTITLES) else if (streamSpec.MediaType == MediaType.SUBTITLES)
{ {
if (DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT) outputExt = ".srt"; outputExt = DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT ? ".srt" : ".vtt";
else outputExt = ".vtt";
} }
var output = Path.Combine(saveDir, saveName + outputExt); var output = Path.Combine(saveDir, saveName + outputExt);
@ -536,7 +532,7 @@ internal class SimpleLiveRecordManager2
fileOutputStream = PipeUtil.CreatePipe(pipeName); fileOutputStream = PipeUtil.CreatePipe(pipeName);
Logger.InfoMarkUp($"{ResString.namedPipeCreated} [cyan]{pipeName.EscapeMarkup()}[/]"); Logger.InfoMarkUp($"{ResString.namedPipeCreated} [cyan]{pipeName.EscapeMarkup()}[/]");
PipeSteamNamesDic[task.Id] = pipeName; 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(); var names = PipeSteamNamesDic.OrderBy(i => i.Key).Select(k => k.Value).ToArray();
Logger.WarnMarkUp($"{ResString.namedPipeMux} [deepskyblue1]{Path.GetFileName(output).EscapeMarkup()}[/]"); Logger.WarnMarkUp($"{ResString.namedPipeMux} [deepskyblue1]{Path.GetFileName(output).EscapeMarkup()}[/]");
@ -622,8 +618,8 @@ internal class SimpleLiveRecordManager2
break; break;
} }
if (fileOutputStream != null) if (fileOutputStream == null) return true;
{
if (!DownloaderConfig.MyOptions.LivePipeMux) if (!DownloaderConfig.MyOptions.LivePipeMux)
{ {
// 记录所有文件信息 // 记录所有文件信息
@ -639,7 +635,6 @@ internal class SimpleLiveRecordManager2
} }
fileOutputStream.Close(); fileOutputStream.Close();
fileOutputStream.Dispose(); fileOutputStream.Dispose();
}
return true; return true;
} }
@ -648,11 +643,10 @@ internal class SimpleLiveRecordManager2
{ {
while (!STOP_FLAG) while (!STOP_FLAG)
{ {
if (WAIT_SEC != 0) if (WAIT_SEC == 0) continue;
{
// 1. MPD 所有URL相同 单次请求即可获得所有轨道的信息 // 1. MPD 所有URL相同 单次请求即可获得所有轨道的信息
// 2. M3U8 所有URL不同 才需要多次请求 // 2. M3U8 所有URL不同 才需要多次请求
await Parallel.ForEachAsync(dic, async (dic, _) => await Parallel.ForEachAsync(dic, async (dic, _) =>
{ {
var streamSpec = dic.Key; var streamSpec = dic.Key;
@ -692,7 +686,7 @@ internal class SimpleLiveRecordManager2
} }
// 检测时长限制 // 检测时长限制
if (!STOP_FLAG && RecordLimitReachedDic.Values.All(x => x == true)) if (!STOP_FLAG && RecordLimitReachedDic.Values.All(x => x))
{ {
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]");
STOP_FLAG = true; STOP_FLAG = true;
@ -723,7 +717,6 @@ internal class SimpleLiveRecordManager2
} }
} }
} }
}
private void FilterMediaSegments(StreamSpec streamSpec, ProgressTask task, bool allHasDatetime, bool allSamePath) private void FilterMediaSegments(StreamSpec streamSpec, ProgressTask task, bool allHasDatetime, bool allSamePath)
{ {
@ -783,7 +776,7 @@ internal class SimpleLiveRecordManager2
Logger.WarnMarkUp($"set refresh interval to {WAIT_SEC} seconds"); Logger.WarnMarkUp($"set refresh interval to {WAIT_SEC} seconds");
} }
// 如果没有选中音频 取消通过音频修复vtt时间轴 // 如果没有选中音频 取消通过音频修复vtt时间轴
if (!SelectedSteams.Any(x => x.MediaType == MediaType.AUDIO)) if (SelectedSteams.All(x => x.MediaType != MediaType.AUDIO))
{ {
DownloaderConfig.MyOptions.LiveFixVttByAudio = false; DownloaderConfig.MyOptions.LiveFixVttByAudio = false;
} }
@ -839,7 +832,7 @@ internal class SimpleLiveRecordManager2
DownloaderConfig.MyOptions.ConcurrentDownload = true; DownloaderConfig.MyOptions.ConcurrentDownload = true;
DownloaderConfig.MyOptions.MP4RealTimeDecryption = 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 }) if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, DecryptionEngine: not DecryptEngine.SHAKA_PACKAGER, Keys.Length: > 0 })
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
var limit = DownloaderConfig.MyOptions.LiveRecordLimit; var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
@ -865,7 +858,7 @@ internal class SimpleLiveRecordManager2
var success = Results.Values.All(v => v == true); 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) foreach (var item in StreamExtractor.RawFiles)
{ {

View File

@ -25,23 +25,25 @@ internal class SimpleDownloader : IDownloader
{ {
var url = segment.Url; var url = segment.Url;
var (des, dResult) = await DownClipAsync(url, savePath, speedContainer, segment.StartRange, segment.StopRange, headers, DownloaderConfig.MyOptions.DownloadRetryCount); 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 key = segment.EncryptInfo.Key;
var iv = segment.EncryptInfo.IV; var iv = segment.EncryptInfo.IV;
AESUtil.AES128Decrypt(dResult.ActualFilePath, key!, 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 key = segment.EncryptInfo.Key;
var iv = segment.EncryptInfo.IV; var iv = segment.EncryptInfo.IV;
AESUtil.AES128Decrypt(dResult.ActualFilePath, key!, iv!, System.Security.Cryptography.CipherMode.ECB); 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 key = segment.EncryptInfo.Key;
var nonce = segment.EncryptInfo.IV; var nonce = segment.EncryptInfo.IV;
@ -49,10 +51,11 @@ internal class SimpleDownloader : IDownloader
var fileBytes = File.ReadAllBytes(dResult.ActualFilePath); var fileBytes = File.ReadAllBytes(dResult.ActualFilePath);
var decrypted = ChaCha20Util.DecryptPer1024Bytes(fileBytes, key!, nonce!); var decrypted = ChaCha20Util.DecryptPer1024Bytes(fileBytes, key!, nonce!);
await File.WriteAllBytesAsync(dResult.ActualFilePath, decrypted); 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"); // throw new NotSupportedException("SAMPLE-AES-CTR");
break;
} }
// Image头处理 // Image头处理
@ -65,7 +68,6 @@ internal class SimpleDownloader : IDownloader
{ {
await OtherUtil.DeGzipFileAsync(dResult.ActualFilePath); await OtherUtil.DeGzipFileAsync(dResult.ActualFilePath);
} }
}
// 处理完成后改名 // 处理完成后改名
File.Move(dResult.ActualFilePath, des); File.Move(dResult.ActualFilePath, des);
@ -99,14 +101,15 @@ internal class SimpleDownloader : IDownloader
} }
// 另起线程进行监控 // 另起线程进行监控
var cts = cancellationTokenSource;
using var watcher = Task.Factory.StartNew(async () => using var watcher = Task.Factory.StartNew(async () =>
{ {
while (true) while (true)
{ {
if (cancellationTokenSource == null || cancellationTokenSource.IsCancellationRequested) break; if (cts.IsCancellationRequested) break;
if (speedContainer.ShouldStop) if (speedContainer.ShouldStop)
{ {
cancellationTokenSource.Cancel(); cts.Cancel();
Logger.DebugMarkUp("Cancel..."); Logger.DebugMarkUp("Cancel...");
break; break;
} }
@ -123,7 +126,7 @@ internal class SimpleDownloader : IDownloader
catch (Exception ex) catch (Exception ex)
{ {
Logger.DebugMarkUp($"[grey]{ex.Message.EscapeMarkup()} retryCount: {retryCount}[/]"); 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}"); Logger.Extra($"Ah oh!{Environment.NewLine}RetryCount => {retryCount}{Environment.NewLine}Exception => {ex.Message}{Environment.NewLine}Url => {url}");
if (retryCount-- > 0) if (retryCount-- > 0)
{ {

View File

@ -1,15 +1,8 @@
using System; namespace N_m3u8DL_RE.Entity;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Entity;
internal class DownloadResult 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? RespContentLength { get; set; }
public long? ActualContentLength { get; set; } public long? ActualContentLength { get; set; }
public bool ImageHeader { get; set; } = false; // 图片伪装 public bool ImageHeader { get; set; } = false; // 图片伪装

View File

@ -9,5 +9,5 @@ internal class OutputFile
public required string FilePath { get; set; } public required string FilePath { get; set; }
public string? LangCode { get; set; } public string? LangCode { get; set; }
public string? Description { get; set; } public string? Description { get; set; }
public List<Mediainfo> Mediainfos { get; set; } = new(); public List<Mediainfo> Mediainfos { get; set; } = [];
} }

View File

@ -1,13 +1,4 @@
using NiL.JS.Statements; namespace N_m3u8DL_RE.Entity;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Entity;
internal class SpeedContainer internal class SpeedContainer
{ {
@ -24,7 +15,7 @@ internal class SpeedContainer
private long _downloaded = 0; private long _downloaded = 0;
private long _Rdownloaded = 0; private long _Rdownloaded = 0;
public long Downloaded { get => _downloaded; } public long Downloaded => _downloaded;
public int AddLowSpeedCount() public int AddLowSpeedCount()
{ {

View File

@ -46,6 +46,6 @@ public class StreamFilter
if (BandwidthMax != null) sb.Append($"{nameof(BandwidthMax)}: {BandwidthMax} "); if (BandwidthMax != null) sb.Append($"{nameof(BandwidthMax)}: {BandwidthMax} ");
if (Role.HasValue) sb.Append($"Role: {Role} "); if (Role.HasValue) sb.Append($"Role: {Role} ");
return sb.ToString() + $"For: {For}"; return sb + $"For: {For}";
} }
} }

View File

@ -13,7 +13,6 @@ using N_m3u8DL_RE.Util;
using N_m3u8DL_RE.DownloadManager; using N_m3u8DL_RE.DownloadManager;
using N_m3u8DL_RE.CommandLine; using N_m3u8DL_RE.CommandLine;
using System.Net; using System.Net;
using System.Net.Http.Headers;
using N_m3u8DL_RE.Enum; using N_m3u8DL_RE.Enum;
namespace N_m3u8DL_RE; namespace N_m3u8DL_RE;
@ -26,7 +25,7 @@ internal class Program
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
var osVersion = Environment.OSVersion.Version; 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"); Environment.SetEnvironmentVariable("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1");
} }
@ -38,7 +37,7 @@ internal class Program
string loc = ResString.CurrentLoc; string loc = ResString.CurrentLoc;
string currLoc = Thread.CurrentThread.CurrentUICulture.Name; 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"; else if (currLoc.StartsWith("zh-")) loc = "zh-TW";
// 处理用户-h等请求 // 处理用户-h等请求
@ -147,33 +146,36 @@ internal class Program
// 预先检查 // 预先检查
if (option.Keys is { Length: > 0 } || option.KeyTextFile != null) 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 file = GlobalUtil.FindExecutable("shaka-packager");
var file2 = GlobalUtil.FindExecutable("packager-linux-x64"); var file2 = GlobalUtil.FindExecutable("packager-linux-x64");
var file3 = GlobalUtil.FindExecutable("packager-osx-x64"); var file3 = GlobalUtil.FindExecutable("packager-osx-x64");
var file4 = GlobalUtil.FindExecutable("packager-win-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; option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4;
Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}"); Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}");
break;
} }
else if (option.DecryptionEngine is DecryptEngine.MP4DECRYPT) case DecryptEngine.MP4DECRYPT:
{ {
var file = GlobalUtil.FindExecutable("mp4decrypt"); var file = GlobalUtil.FindExecutable("mp4decrypt");
if (file == null) throw new FileNotFoundException(ResString.mp4decryptNotFound); if (file == null) throw new FileNotFoundException(ResString.mp4decryptNotFound);
option.DecryptionBinaryPath = file; option.DecryptionBinaryPath = file;
Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}"); Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}");
break;
} }
else case DecryptEngine.FFMPEG:
{ default:
option.DecryptionBinaryPath = option.FFmpegBinaryPath; option.DecryptionBinaryPath = option.FFmpegBinaryPath;
} break;
}
else if (!File.Exists(option.DecryptionBinaryPath))
{
throw new FileNotFoundException(option.DecryptionBinaryPath);
} }
} }
@ -283,7 +285,7 @@ internal class Program
if (option.AutoSelect) if (option.AutoSelect)
{ {
if (basicStreams.Any()) if (basicStreams.Count != 0)
selectedStreams.Add(basicStreams.First()); selectedStreams.Add(basicStreams.First());
var langs = audios.DistinctBy(a => a.Language).Select(a => a.Language); var langs = audios.DistinctBy(a => a.Language).Select(a => a.Language);
foreach (var lang in langs) foreach (var lang in langs)
@ -309,7 +311,7 @@ internal class Program
selectedStreams = FilterUtil.SelectStreams(lists); selectedStreams = FilterUtil.SelectStreams(lists);
} }
if (!selectedStreams.Any()) if (selectedStreams.Count == 0)
throw new Exception(ResString.noStreamsToDownload); throw new Exception(ResString.noStreamsToDownload);
// HLS: 选中流中若有没加载出playlist的加载playlist // HLS: 选中流中若有没加载出playlist的加载playlist
@ -363,7 +365,7 @@ internal class Program
Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]"); Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]");
// 开始MuxAfterDone后自动使用二进制版 // 开始MuxAfterDone后自动使用二进制版
if (!option.BinaryMerge && option.MuxAfterDone) if (option is { BinaryMerge: false, MuxAfterDone: true })
{ {
option.BinaryMerge = true; option.BinaryMerge = true;
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge6}[/]"); Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge6}[/]");
@ -446,25 +448,22 @@ internal class Program
static async Task<string> Get302Async(string url) static async Task<string> Get302Async(string url)
{ {
// this allows you to set the settings so that we can get the redirect 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 AllowAutoRedirect = false
}; };
string redirectedUrl = ""; var redirectedUrl = "";
using (HttpClient client = new(handler)) using var client = new HttpClient(handler);
using (HttpResponseMessage response = await client.GetAsync(url)) using var response = await client.GetAsync(url);
using (HttpContent content = response.Content) using var content = response.Content;
{
// ... Read the response to see if we have the redirected url // ... Read the response to see if we have the redirected url
if (response.StatusCode == System.Net.HttpStatusCode.Found) if (response.StatusCode != HttpStatusCode.Found) return redirectedUrl;
{
HttpResponseHeaders headers = response.Headers; var headers = response.Headers;
if (headers != null && headers.Location != null) if (headers.Location != null)
{ {
redirectedUrl = headers.Location.AbsoluteUri; redirectedUrl = headers.Location.AbsoluteUri;
} }
}
}
return redirectedUrl; return redirectedUrl;
} }

View File

@ -25,7 +25,7 @@ internal static class DownloadUtil
{ {
var buffer = new byte[expect]; var buffer = new byte[expect];
_ = await inputStream.ReadAsync(buffer); _ = await inputStream.ReadAsync(buffer);
await outputStream.WriteAsync(buffer, 0, buffer.Length); await outputStream.WriteAsync(buffer);
speedContainer.Add(buffer.Length); speedContainer.Add(buffer.Length);
} }
return new DownloadResult() return new DownloadResult()
@ -81,7 +81,7 @@ internal static class DownloadUtil
{ {
HttpResponseHeaders respHeaders = response.Headers; HttpResponseHeaders respHeaders = response.Headers;
Logger.Debug(respHeaders.ToString()); Logger.Debug(respHeaders.ToString());
if (respHeaders != null && respHeaders.Location != null) if (respHeaders.Location != null)
{ {
var redirectedUrl = ""; var redirectedUrl = "";
if (!respHeaders.Location.IsAbsoluteUri) if (!respHeaders.Location.IsAbsoluteUri)
@ -108,7 +108,7 @@ internal static class DownloadUtil
size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token); size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token);
speedContainer.Add(size); speedContainer.Add(size);
await stream.WriteAsync(buffer, 0, size); await stream.WriteAsync(buffer.AsMemory(0, size));
// 检测imageHeader // 检测imageHeader
bool imageHeader = ImageHeaderUtil.IsImageHeader(buffer); bool imageHeader = ImageHeaderUtil.IsImageHeader(buffer);
// 检测GZipFor DDP Audio // 检测GZipFor DDP Audio
@ -117,7 +117,7 @@ internal static class DownloadUtil
while ((size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token)) > 0) while ((size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token)) > 0)
{ {
speedContainer.Add(size); speedContainer.Add(size);
await stream.WriteAsync(buffer, 0, size); await stream.WriteAsync(buffer.AsMemory(0, size));
// 限速策略 // 限速策略
while (speedContainer.Downloaded > speedContainer.SpeedLimit) while (speedContainer.Downloaded > speedContainer.SpeedLimit)
{ {

View File

@ -65,7 +65,7 @@ public static class FilterUtil
public static List<StreamSpec> DoFilterDrop(IEnumerable<StreamSpec> lists, StreamFilter? filter) public static List<StreamSpec> DoFilterDrop(IEnumerable<StreamSpec> lists, StreamFilter? filter)
{ {
if (filter == null) return new List<StreamSpec>(lists); if (filter == null) return [..lists];
var inputs = lists.Where(_ => true); var inputs = lists.Where(_ => true);
var selected = DoFilterKeep(lists, filter); var selected = DoFilterKeep(lists, filter);
@ -93,8 +93,7 @@ public static class FilterUtil
.UseConverter(x => .UseConverter(x =>
{ {
if (x.Name != null && x.Name.StartsWith("__")) if (x.Name != null && x.Name.StartsWith("__"))
return $"[darkslategray1]{x.Name.Substring(2)}[/]"; return $"[darkslategray1]{x.Name[2..]}[/]";
else
return x.ToString().EscapeMarkup().RemoveMarkup(); return x.ToString().EscapeMarkup().RemoveMarkup();
}) })
.Required() .Required()
@ -107,12 +106,12 @@ public static class FilterUtil
var first = streamSpecs.First(); var first = streamSpecs.First();
prompt.Select(first); prompt.Select(first);
if (basicStreams.Any()) if (basicStreams.Count != 0)
{ {
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Basic" }, basicStreams); prompt.AddChoiceGroup(new StreamSpec() { Name = "__Basic" }, basicStreams);
} }
if (audios.Any()) if (audios.Count != 0)
{ {
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Audio" }, audios); prompt.AddChoiceGroup(new StreamSpec() { Name = "__Audio" }, audios);
// 默认音轨 // 默认音轨
@ -121,7 +120,7 @@ public static class FilterUtil
prompt.Select(audios.First(a => a.GroupId == first.AudioId)); prompt.Select(audios.First(a => a.GroupId == first.AudioId));
} }
} }
if (subs.Any()) if (subs.Count != 0)
{ {
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Subtitle" }, subs); prompt.AddChoiceGroup(new StreamSpec() { Name = "__Subtitle" }, subs);
// 默认字幕轨 // 默认字幕轨

View File

@ -80,7 +80,7 @@ internal static class LargeSingleFileSplitUtil
// 此函数主要是切片下载逻辑 // 此函数主要是切片下载逻辑
private static List<Clip> GetAllClips(string url, long fileSize) private static List<Clip> GetAllClips(string url, long fileSize)
{ {
List<Clip> clips = new(); List<Clip> clips = [];
int index = 0; int index = 0;
long counter = 0; long counter = 0;
int perSize = 10 * 1024 * 1024; int perSize = 10 * 1024 * 1024;

View File

@ -7,7 +7,7 @@ using N_m3u8DL_RE.Enum;
namespace N_m3u8DL_RE.Util; namespace N_m3u8DL_RE.Util;
internal static class MP4DecryptUtil internal static partial class MP4DecryptUtil
{ {
private static readonly string ZeroKid = "00000000000000000000000000000000"; private static readonly string ZeroKid = "00000000000000000000000000000000";
public static async Task<bool> DecryptAsync(DecryptEngine decryptEngine, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false) public static async Task<bool> 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)) if (!string.IsNullOrEmpty(kid))
{ {
var test = keyPairs.Where(k => k.StartsWith(kid)).ToList(); var test = keyPairs.Where(k => k.StartsWith(kid)).ToList();
if (test.Any()) keyPair = test.First(); if (test.Count != 0) keyPair = test.First();
} }
// Apple // Apple
@ -159,13 +159,12 @@ internal static class MP4DecryptUtil
using var reader = new StreamReader(stream); using var reader = new StreamReader(stream);
while (await reader.ReadLineAsync() is { } line) while (await reader.ReadLineAsync() is { } line)
{ {
if (line.Trim().StartsWith(kid)) if (!line.Trim().StartsWith(kid)) continue;
{
Logger.InfoMarkUp($"[green]OK[/] [grey]{line.Trim()}[/]"); Logger.InfoMarkUp($"[green]OK[/] [grey]{line.Trim()}[/]");
return line.Trim(); return line.Trim();
} }
} }
}
catch (Exception ex) catch (Exception ex)
{ {
Logger.ErrorMarkUp(ex.Message); Logger.ErrorMarkUp(ex.Message);
@ -192,7 +191,7 @@ internal static class MP4DecryptUtil
public static string? ReadInitShaka(string output, string bin) 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) // TODO: handle the case that shaka packager actually decrypted (key ID == ZeroKid)
// - stop process // - stop process
@ -214,4 +213,7 @@ internal static class MP4DecryptUtil
p.WaitForExit(); p.WaitForExit();
return shakaKeyIdRegex.Match(errorOutput).Groups[1].Value; return shakaKeyIdRegex.Match(errorOutput).Groups[1].Value;
} }
[GeneratedRegex("Key for key_id=([0-9a-f]+) was not found")]
private static partial Regex KidOutputRegex();
} }

View File

@ -8,23 +8,23 @@ internal static partial class MediainfoUtil
{ {
[GeneratedRegex(" Stream #.*")] [GeneratedRegex(" Stream #.*")]
private static partial Regex TextRegex(); private static partial Regex TextRegex();
[GeneratedRegex("#0:\\d(\\[0x\\w+?\\])")] [GeneratedRegex(@"#0:\d(\[0x\w+?\])")]
private static partial Regex IdRegex(); private static partial Regex IdRegex();
[GeneratedRegex(": (\\w+): (.*)")] [GeneratedRegex(": (\\w+): (.*)")]
private static partial Regex TypeRegex(); private static partial Regex TypeRegex();
[GeneratedRegex("(.*?)(,|$)")] [GeneratedRegex("(.*?)(,|$)")]
private static partial Regex BaseInfoRegex(); private static partial Regex BaseInfoRegex();
[GeneratedRegex(" \\/ 0x\\w+")] [GeneratedRegex(@" \/ 0x\w+")]
private static partial Regex ReplaceRegex(); private static partial Regex ReplaceRegex();
[GeneratedRegex("\\d{2,}x\\d+")] [GeneratedRegex(@"\d{2,}x\d+")]
private static partial Regex ResRegex(); private static partial Regex ResRegex();
[GeneratedRegex("\\d+ kb\\/s")] [GeneratedRegex(@"\d+ kb\/s")]
private static partial Regex BitrateRegex(); private static partial Regex BitrateRegex();
[GeneratedRegex("(\\d+(\\.\\d+)?) fps")] [GeneratedRegex(@"(\d+(\.\d+)?) fps")]
private static partial Regex FpsRegex(); 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(); private static partial Regex DoViRegex();
[GeneratedRegex("Duration.*?start: (\\d+\\.?\\d{0,3})")] [GeneratedRegex(@"Duration.*?start: (\d+\.?\d{0,3})")]
private static partial Regex StartRegex(); private static partial Regex StartRegex();
public static async Task<List<Mediainfo>> ReadInfoAsync(string binary, string file) public static async Task<List<Mediainfo>> ReadInfoAsync(string binary, string file)

View File

@ -68,20 +68,16 @@ internal static class MergeUtil
public static string[] PartialCombineMultipleFiles(string[] files) public static string[] PartialCombineMultipleFiles(string[] files)
{ {
var newFiles = new List<string>(); var newFiles = new List<string>();
int div = 0; var div = files.Length <= 90000 ? 100 : 200;
if (files.Length <= 90000)
div = 100;
else
div = 200;
string outputName = Path.Combine(Path.GetDirectoryName(files[0])!, "T"); var outputName = Path.Combine(Path.GetDirectoryName(files[0])!, "T");
int index = 0; // 序号 var index = 0; // 序号
// 按照div的容量分割为小数组 // 按照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) foreach (var items in li)
{ {
if (!items.Any()) if (items.Length == 0)
continue; continue;
var output = outputName + index.ToString("0000") + ".ts"; var output = outputName + index.ToString("0000") + ".ts";
CombineMultipleFilesIntoSingleFile(items, output); CombineMultipleFilesIntoSingleFile(items, output);

View File

@ -4,7 +4,7 @@ using System.Text.RegularExpressions;
namespace N_m3u8DL_RE.Util; namespace N_m3u8DL_RE.Util;
internal static class OtherUtil internal static partial class OtherUtil
{ {
public static Dictionary<string, string> SplitHeaderArrayToDic(string[]? headers) public static Dictionary<string, string> 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" 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(); .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; var title = InvalidChars.Aggregate(input, (current, invalidChar) => current.Replace(invalidChar.ToString(), re));
foreach (char invalidChar in InvalidChars)
{
title = title.Replace(invalidChar.ToString(), re);
}
if (filterSlash) if (filterSlash)
{ {
title = title.Replace("/", re); title = title.Replace("/", re);
@ -98,7 +94,7 @@ internal static class OtherUtil
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
public static double ParseSeconds(string timeStr) public static double ParseSeconds(string timeStr)
{ {
var pattern = new Regex(@"^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$"); var pattern = TimeStrRegex();
var match = pattern.Match(timeStr); var match = pattern.Match(timeStr);
@ -171,4 +167,7 @@ internal static class OtherUtil
_ => throw new ArgumentException($"unknown format: {muxFormat}") _ => throw new ArgumentException($"unknown format: {muxFormat}")
}; };
} }
[GeneratedRegex(@"^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$")]
private static partial Regex TimeStrRegex();
} }

View File

@ -14,8 +14,7 @@ internal static class PipeUtil
{ {
return new NamedPipeServerStream(pipeName, PipeDirection.InOut); return new NamedPipeServerStream(pipeName, PipeDirection.InOut);
} }
else
{
var path = Path.Combine(Path.GetTempPath(), pipeName); var path = Path.Combine(Path.GetTempPath(), pipeName);
using var p = new Process(); using var p = new Process();
p.StartInfo = new ProcessStartInfo() p.StartInfo = new ProcessStartInfo()
@ -32,7 +31,6 @@ internal static class PipeUtil
Thread.Sleep(200); Thread.Sleep(200);
return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
} }
}
public static async Task<bool> StartPipeMuxAsync(string binary, string[] pipeNames, string outputPath) public static async Task<bool> StartPipeMuxAsync(string binary, string[] pipeNames, string outputPath)
{ {
@ -77,7 +75,7 @@ internal static class PipeUtil
if (!string.IsNullOrEmpty(customDest)) if (!string.IsNullOrEmpty(customDest))
{ {
if (customDest.Trim().StartsWith("-")) if (customDest.Trim().StartsWith('-'))
command.Append(customDest); command.Append(customDest);
else else
command.Append($" -f mpegts -shortest \"{customDest}\""); command.Append($" -f mpegts -shortest \"{customDest}\"");