优化部分代码

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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Common.Entity;
@ -34,9 +29,6 @@ public class EncryptInfo
{
return m;
}
else
{
return EncryptMethod.UNKNOWN;
}
return EncryptMethod.UNKNOWN;
}
}

View File

@ -3,5 +3,5 @@
// 主要处理 EXT-X-DISCONTINUITY
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 string Url { get; set; }
public string Url { get; set; } = string.Empty;
public string? NameFromVar { get; set; } // MPD分段文件名

View File

@ -3,17 +3,18 @@
public class Playlist
{
// 对应Url信息
public string Url { get; set; }
public string Url { get; set; } = string.Empty;
// 是否直播
public bool IsLive { get; set; } = false;
// 直播刷新间隔毫秒默认15秒
public double RefreshIntervalMs { get; set; } = 15000;
// 所有分片时长总和
public double TotalDuration { get => MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration)); }
public double TotalDuration => MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration));
// 所有分片中最长时长
public double? TargetDuration { get; set; }
// INIT信息
public MediaSegment? MediaInit { get; set; }
// 分片信息
public List<MediaPart> MediaParts { get; set; } = new List<MediaPart>();
public List<MediaPart> MediaParts { get; set; } = [];
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ using System.Text.RegularExpressions;
namespace N_m3u8DL_RE.Common.Log;
public partial class Logger
public static partial class Logger
{
[GeneratedRegex("{}")]
private static partial Regex VarsRepRegex();
@ -80,24 +80,23 @@ public partial class Logger
Console.WriteLine(subWrite);
}
if (IsWriteFile && File.Exists(LogFilePath))
if (!IsWriteFile || !File.Exists(LogFilePath)) return;
var plain = write.RemoveMarkup() + subWrite.RemoveMarkup();
try
{
var plain = write.RemoveMarkup() + subWrite.RemoveMarkup();
try
// 进入写入
LogWriteLock.EnterWriteLock();
using (StreamWriter sw = File.AppendText(LogFilePath))
{
// 进入写入
LogWriteLock.EnterWriteLock();
using (StreamWriter sw = File.AppendText(LogFilePath))
{
sw.WriteLine(plain);
}
}
finally
{
// 释放占用
LogWriteLock.ExitWriteLock();
sw.WriteLine(plain);
}
}
finally
{
// 释放占用
LogWriteLock.ExitWriteLock();
}
}
catch (Exception)
{
@ -117,82 +116,74 @@ public partial class Logger
public static void Info(string data, params object[] ps)
{
if (LogLevel >= LogLevel.INFO)
{
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : ";
HandleLog(write, data);
}
if (LogLevel < LogLevel.INFO) return;
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : ";
HandleLog(write, data);
}
public static void InfoMarkUp(string data, params object[] ps)
{
if (LogLevel >= LogLevel.INFO)
{
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : " + data;
HandleLog(write);
}
if (LogLevel < LogLevel.INFO) return;
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : " + data;
HandleLog(write);
}
public static void Debug(string data, params object[] ps)
{
if (LogLevel >= LogLevel.DEBUG)
{
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: ";
HandleLog(write, data);
}
if (LogLevel < LogLevel.DEBUG) return;
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: ";
HandleLog(write, data);
}
public static void DebugMarkUp(string data, params object[] ps)
{
if (LogLevel >= LogLevel.DEBUG)
{
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: " + data;
HandleLog(write);
}
if (LogLevel < LogLevel.DEBUG) return;
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: " + data;
HandleLog(write);
}
public static void Warn(string data, params object[] ps)
{
if (LogLevel >= LogLevel.WARN)
{
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : ";
HandleLog(write, data);
}
if (LogLevel < LogLevel.WARN) return;
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : ";
HandleLog(write, data);
}
public static void WarnMarkUp(string data, params object[] ps)
{
if (LogLevel >= LogLevel.WARN)
{
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : " + data;
HandleLog(write);
}
if (LogLevel < LogLevel.WARN) return;
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : " + data;
HandleLog(write);
}
public static void Error(string data, params object[] ps)
{
if (LogLevel >= LogLevel.ERROR)
{
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: ";
HandleLog(write, data);
}
if (LogLevel < LogLevel.ERROR) return;
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: ";
HandleLog(write, data);
}
public static void ErrorMarkUp(string data, params object[] ps)
{
if (LogLevel >= LogLevel.ERROR)
{
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: " + data;
HandleLog(write);
}
if (LogLevel < LogLevel.ERROR) return;
data = ReplaceVars(data, ps);
var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: " + data;
HandleLog(write);
}
public static void ErrorMarkUp(Exception exception)
@ -213,24 +204,23 @@ public partial class Logger
/// <param name="ps"></param>
public static void Extra(string data, params object[] ps)
{
if (IsWriteFile && File.Exists(LogFilePath))
if (!IsWriteFile || !File.Exists(LogFilePath)) return;
data = ReplaceVars(data, ps);
var plain = GetCurrTime() + " " + "EXTRA: " + data.RemoveMarkup();
try
{
data = ReplaceVars(data, ps);
var plain = GetCurrTime() + " " + "EXTRA: " + data.RemoveMarkup();
try
// 进入写入
LogWriteLock.EnterWriteLock();
using (StreamWriter sw = File.AppendText(LogFilePath))
{
// 进入写入
LogWriteLock.EnterWriteLock();
using (StreamWriter sw = File.AppendText(LogFilePath))
{
sw.WriteLine(plain, Encoding.UTF8);
}
}
finally
{
// 释放占用
LogWriteLock.ExitWriteLock();
sw.WriteLine(plain, Encoding.UTF8);
}
}
finally
{
// 释放占用
LogWriteLock.ExitWriteLock();
}
}
}

View File

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

View File

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

View File

@ -67,8 +67,7 @@ public static class GlobalUtil
{
var fileExt = OperatingSystem.IsWindows() ? ".exe" : "";
var searchPath = new[] { Environment.CurrentDirectory, Path.GetDirectoryName(Environment.ProcessPath) };
var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ??
Array.Empty<string>();
var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? [];
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)
{
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>

View File

@ -7,11 +7,11 @@ namespace N_m3u8DL_RE.Parser.Config;
public class ParserConfig
{
public string Url { get; set; }
public string Url { get; set; } = string.Empty;
public string OriginalUrl { get; set; }
public string OriginalUrl { get; set; } = string.Empty;
public string BaseUrl { get; set; }
public string BaseUrl { get; set; } = string.Empty;
public Dictionary<string, string> CustomParserArgs { get; } = new();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,5 @@
using N_m3u8DL_RE.Parser.Config;
using N_m3u8DL_RE.Common.Entity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using N_m3u8DL_RE.Common.Enum;
namespace N_m3u8DL_RE.Parser.Extractor;

View File

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

View File

@ -1,70 +1,62 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mp4SubtitleParser;
namespace Mp4SubtitleParser
// make BinaryReader in Big Endian
class BinaryReader2 : BinaryReader
{
// make BinaryReader in Big Endian
class BinaryReader2 : BinaryReader
public BinaryReader2(System.IO.Stream stream) : base(stream) { }
public bool HasMoreData()
{
public BinaryReader2(System.IO.Stream stream) : base(stream) { }
public bool HasMoreData()
{
return BaseStream.Position < BaseStream.Length;
}
public long GetLength()
{
return BaseStream.Length;
}
public long GetPosition()
{
return BaseStream.Position;
}
public override int ReadInt32()
{
var data = base.ReadBytes(4);
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToInt32(data, 0);
}
public override short ReadInt16()
{
var data = base.ReadBytes(2);
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToInt16(data, 0);
}
public override long ReadInt64()
{
var data = base.ReadBytes(8);
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToInt64(data, 0);
}
public override uint ReadUInt32()
{
var data = base.ReadBytes(4);
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToUInt32(data, 0);
}
public override ulong ReadUInt64()
{
var data = base.ReadBytes(8);
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToUInt64(data, 0);
}
return BaseStream.Position < BaseStream.Length;
}
}
public long GetLength()
{
return BaseStream.Length;
}
public long GetPosition()
{
return BaseStream.Position;
}
public override int ReadInt32()
{
var data = base.ReadBytes(4);
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToInt32(data, 0);
}
public override short ReadInt16()
{
var data = base.ReadBytes(2);
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToInt16(data, 0);
}
public override long ReadInt64()
{
var data = base.ReadBytes(8);
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToInt64(data, 0);
}
public override uint ReadUInt32()
{
var data = base.ReadBytes(4);
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToUInt32(data, 0);
}
public override ulong ReadUInt64()
{
var data = base.ReadBytes(8);
if (BitConverter.IsLittleEndian)
Array.Reverse(data);
return BitConverter.ToUInt64(data, 0);
}
}

View File

@ -1,89 +1,83 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text;
namespace Mp4SubtitleParser
namespace Mp4SubtitleParser;
// make BinaryWriter in Big Endian
class BinaryWriter2 : BinaryWriter
{
// make BinaryWriter in Big Endian
class BinaryWriter2 : BinaryWriter
private static bool IsLittleEndian = BitConverter.IsLittleEndian;
public BinaryWriter2(System.IO.Stream stream) : base(stream) { }
public void WriteUInt(decimal n, int offset = 0)
{
private static bool IsLittleEndian = BitConverter.IsLittleEndian;
public BinaryWriter2(System.IO.Stream stream) : base(stream) { }
public void WriteUInt(decimal n, int offset = 0)
{
var arr = BitConverter.GetBytes((uint)n);
if (IsLittleEndian)
Array.Reverse(arr);
if (offset != 0)
arr = arr[offset..];
BaseStream.Write(arr);
}
public override void Write(string text)
{
BaseStream.Write(Encoding.ASCII.GetBytes(text));
}
public void WriteInt(decimal n, int offset = 0)
{
var arr = BitConverter.GetBytes((int)n);
if (IsLittleEndian)
Array.Reverse(arr);
if (offset != 0)
arr = arr[offset..];
BaseStream.Write(arr);
}
public void WriteULong(decimal n, int offset = 0)
{
var arr = BitConverter.GetBytes((ulong)n);
if (IsLittleEndian)
Array.Reverse(arr);
if (offset != 0)
arr = arr[offset..];
BaseStream.Write(arr);
}
public void WriteUShort(decimal n, int padding = 0)
{
var arr = BitConverter.GetBytes((ushort)n);
if (IsLittleEndian)
Array.Reverse(arr);
while (padding > 0)
{
arr = arr.Concat(new byte[] { 0x00 }).ToArray();
padding--;
}
BaseStream.Write(arr);
}
public void WriteShort(decimal n, int padding = 0)
{
var arr = BitConverter.GetBytes((short)n);
if (IsLittleEndian)
Array.Reverse(arr);
while (padding > 0)
{
arr = arr.Concat(new byte[] { 0x00 }).ToArray();
padding--;
}
BaseStream.Write(arr);
}
public void WriteByte(byte n, int padding = 0)
{
var arr = new byte[] { n };
while (padding > 0)
{
arr = arr.Concat(new byte[] { 0x00 }).ToArray();
padding--;
}
BaseStream.Write(arr);
}
var arr = BitConverter.GetBytes((uint)n);
if (IsLittleEndian)
Array.Reverse(arr);
if (offset != 0)
arr = arr[offset..];
BaseStream.Write(arr);
}
}
public override void Write(string text)
{
BaseStream.Write(Encoding.ASCII.GetBytes(text));
}
public void WriteInt(decimal n, int offset = 0)
{
var arr = BitConverter.GetBytes((int)n);
if (IsLittleEndian)
Array.Reverse(arr);
if (offset != 0)
arr = arr[offset..];
BaseStream.Write(arr);
}
public void WriteULong(decimal n, int offset = 0)
{
var arr = BitConverter.GetBytes((ulong)n);
if (IsLittleEndian)
Array.Reverse(arr);
if (offset != 0)
arr = arr[offset..];
BaseStream.Write(arr);
}
public void WriteUShort(decimal n, int padding = 0)
{
var arr = BitConverter.GetBytes((ushort)n);
if (IsLittleEndian)
Array.Reverse(arr);
while (padding > 0)
{
arr = arr.Concat(new byte[] { 0x00 }).ToArray();
padding--;
}
BaseStream.Write(arr);
}
public void WriteShort(decimal n, int padding = 0)
{
var arr = BitConverter.GetBytes((short)n);
if (IsLittleEndian)
Array.Reverse(arr);
while (padding > 0)
{
arr = arr.Concat(new byte[] { 0x00 }).ToArray();
padding--;
}
BaseStream.Write(arr);
}
public void WriteByte(byte n, int padding = 0)
{
var arr = new byte[] { n };
while (padding > 0)
{
arr = arr.Concat(new byte[] { 0x00 }).ToArray();
padding--;
}
BaseStream.Write(arr);
}
}

View File

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

View File

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

View File

@ -3,75 +3,229 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
namespace Mp4SubtitleParser
namespace Mp4SubtitleParser;
class SubEntity
{
class SubEntity
public required string Begin { get; set; }
public required string End { get; set; }
public required string Region { get; set; }
public List<XmlElement> Contents { get; set; } = [];
public List<string> ContentStrings { get; set; } = [];
public override bool Equals(object? obj)
{
public string Begin { get; set; }
public string End { get; set; }
public string Region { get; set; }
public List<XmlElement> Contents { get; set; } = new();
public List<string> ContentStrings { get; set; } = new();
public override bool Equals(object? obj)
{
return obj is SubEntity entity &&
Begin == entity.Begin &&
End == entity.End &&
Region == entity.Region &&
ContentStrings.SequenceEqual(entity.ContentStrings);
}
public override int GetHashCode()
{
return HashCode.Combine(Begin, End, Region, ContentStrings);
}
return obj is SubEntity entity &&
Begin == entity.Begin &&
End == entity.End &&
Region == entity.Region &&
ContentStrings.SequenceEqual(entity.ContentStrings);
}
public partial class MP4TtmlUtil
public override int GetHashCode()
{
[GeneratedRegex(" \\w+:\\w+=\\\"[^\\\"]*\\\"")]
private static partial Regex AttrRegex();
[GeneratedRegex("<p.*?>(.+?)<\\/p>")]
private static partial Regex LabelFixRegex();
[GeneratedRegex("\\<tt[\\s\\S]*?\\<\\/tt\\>")]
private static partial Regex MultiElementsFixRegex();
[GeneratedRegex("\\<smpte:image.*xml:id=\\\"(.*?)\\\".*\\>([\\s\\S]*?)<\\/smpte:image>")]
private static partial Regex ImageRegex();
return HashCode.Combine(Begin, End, Region, ContentStrings);
}
}
public static bool CheckInit(byte[] data)
public static partial class MP4TtmlUtil
{
[GeneratedRegex(" \\w+:\\w+=\\\"[^\\\"]*\\\"")]
private static partial Regex AttrRegex();
[GeneratedRegex("<p.*?>(.+?)<\\/p>")]
private static partial Regex LabelFixRegex();
[GeneratedRegex(@"\<tt[\s\S]*?\<\/tt\>")]
private static partial Regex MultiElementsFixRegex();
[GeneratedRegex("\\<smpte:image.*xml:id=\\\"(.*?)\\\".*\\>([\\s\\S]*?)<\\/smpte:image>")]
private static partial Regex ImageRegex();
public static bool CheckInit(byte[] data)
{
bool sawSTPP = false;
// parse init
new MP4Parser()
.Box("moov", MP4Parser.Children)
.Box("trak", MP4Parser.Children)
.Box("mdia", MP4Parser.Children)
.Box("minf", MP4Parser.Children)
.Box("stbl", MP4Parser.Children)
.FullBox("stsd", MP4Parser.SampleDescription)
.Box("stpp", box => {
sawSTPP = true;
})
.Parse(data);
return sawSTPP;
}
private static string ShiftTime(string xmlSrc, long segTimeMs, int index)
{
string Add(string xmlTime)
{
bool sawSTPP = false;
// parse init
new MP4Parser()
.Box("moov", MP4Parser.Children)
.Box("trak", MP4Parser.Children)
.Box("mdia", MP4Parser.Children)
.Box("minf", MP4Parser.Children)
.Box("stbl", MP4Parser.Children)
.FullBox("stsd", MP4Parser.SampleDescription)
.Box("stpp", (box) => {
sawSTPP = true;
})
.Parse(data);
return sawSTPP;
var dt = DateTime.ParseExact(xmlTime, "HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture);
var ts = TimeSpan.FromMilliseconds(dt.TimeOfDay.TotalMilliseconds + segTimeMs * index);
return $"{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}.{ts.Milliseconds:000}";
}
private static string ShiftTime(string xmlSrc, long segTimeMs, int index)
if (!xmlSrc.Contains("<tt") || !xmlSrc.Contains("<head>")) return xmlSrc;
var xmlDoc = new XmlDocument();
XmlNamespaceManager? nsMgr = null;
xmlDoc.LoadXml(xmlSrc);
var ttNode = xmlDoc.LastChild;
if (nsMgr == null)
{
string Add(string xmlTime)
{
var dt = DateTime.ParseExact(xmlTime, "HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture);
var ts = TimeSpan.FromMilliseconds(dt.TimeOfDay.TotalMilliseconds + segTimeMs * index);
return string.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
}
var ns = ((XmlElement)ttNode!).GetAttribute("xmlns");
nsMgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsMgr.AddNamespace("ns", ns);
}
if (!xmlSrc.Contains("<tt") || !xmlSrc.Contains("<head>")) return xmlSrc;
var xmlDoc = new XmlDocument();
XmlNamespaceManager? nsMgr = null;
xmlDoc.LoadXml(xmlSrc);
var bodyNode = ttNode!.SelectSingleNode("ns:body", nsMgr);
if (bodyNode == null)
return xmlSrc;
var _div = bodyNode.SelectSingleNode("ns:div", nsMgr);
// Parse <p> label
foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!)
{
var _begin = _p.GetAttribute("begin");
var _end = _p.GetAttribute("end");
// Handle namespace
foreach (XmlAttribute attr in _p.Attributes)
{
if (attr.LocalName == "begin") _begin = attr.Value;
else if (attr.LocalName == "end") _end = attr.Value;
}
_p.SetAttribute("begin", Add(_begin));
_p.SetAttribute("end", Add(_end));
// Console.WriteLine($"{_begin} {_p.GetAttribute("begin")}");
// Console.WriteLine($"{_end} {_p.GetAttribute("begin")}");
}
return xmlDoc.OuterXml;
}
private static string GetTextFromElement(XmlElement node)
{
var sb = new StringBuilder();
foreach (XmlNode item in node.ChildNodes)
{
if (item.NodeType == XmlNodeType.Text)
{
sb.Append(item.InnerText.Trim());
}
else if(item is { NodeType: XmlNodeType.Element, Name: "br" })
{
sb.AppendLine();
}
}
return sb.ToString();
}
private static List<string> SplitMultipleRootElements(string xml)
{
return !MultiElementsFixRegex().IsMatch(xml) ? [] : MultiElementsFixRegex().Matches(xml).Select(m => m.Value).ToList();
}
public static WebVttSub ExtractFromMp4(string item, long segTimeMs, long baseTimestamp = 0L)
{
return ExtractFromMp4s([item], segTimeMs, baseTimestamp);
}
private static WebVttSub ExtractFromMp4s(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
{
// read ttmls
List<string> xmls = [];
int segIndex = 0;
foreach (var item in items)
{
var dataSeg = File.ReadAllBytes(item);
var sawMDAT = false;
// parse media
new MP4Parser()
.Box("mdat", MP4Parser.AllData(data =>
{
sawMDAT = true;
// Join this to any previous payload, in case the mp4 has multiple
// mdats.
if (segTimeMs != 0)
{
var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data));
foreach (var item in datas)
{
xmls.Add(ShiftTime(item, segTimeMs, segIndex));
}
}
else
{
var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data));
xmls.AddRange(datas);
}
}))
.Parse(dataSeg,/* partialOkay= */ false);
segIndex++;
}
return ExtractSub(xmls, baseTimestamp);
}
public static WebVttSub ExtractFromTTML(string item, long segTimeMs, long baseTimestamp = 0L)
{
return ExtractFromTTMLs([item], segTimeMs, baseTimestamp);
}
public static WebVttSub ExtractFromTTMLs(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
{
// read ttmls
List<string> xmls = [];
int segIndex = 0;
foreach (var item in items)
{
var xml = File.ReadAllText(item);
xmls.Add(segTimeMs != 0 ? ShiftTime(xml, segTimeMs, segIndex) : xml);
segIndex++;
}
return ExtractSub(xmls, baseTimestamp);
}
private static WebVttSub ExtractSub(List<string> xmls, long baseTimestamp)
{
// parsing
var xmlDoc = new XmlDocument();
var finalSubs = new List<SubEntity>();
XmlNode? headNode = null;
XmlNamespaceManager? nsMgr = null;
var regex = LabelFixRegex();
var attrRegex = AttrRegex();
foreach (var item in xmls)
{
var xmlContent = item;
if (!xmlContent.Contains("<tt")) continue;
// fix non-standard xml
var xmlContentFix = xmlContent;
if (regex.IsMatch(xmlContent))
{
foreach (Match m in regex.Matches(xmlContentFix))
{
try
{
var inner = m.Groups[1].Value;
if (attrRegex.IsMatch(inner))
{
inner = attrRegex.Replace(inner, "");
}
new XmlDocument().LoadXml($"<p>{inner}</p>");
}
catch (Exception)
{
xmlContentFix = xmlContentFix.Replace(m.Groups[1].Value, System.Web.HttpUtility.HtmlEncode(m.Groups[1].Value));
}
}
}
xmlDoc.LoadXml(xmlContentFix);
var ttNode = xmlDoc.LastChild;
if (nsMgr == null)
{
@ -79,306 +233,142 @@ namespace Mp4SubtitleParser
nsMgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsMgr.AddNamespace("ns", ns);
}
if (headNode == null)
headNode = ttNode!.SelectSingleNode("ns:head", nsMgr);
var bodyNode = ttNode!.SelectSingleNode("ns:body", nsMgr);
if (bodyNode == null)
return xmlSrc;
continue;
var _div = bodyNode.SelectSingleNode("ns:div", nsMgr);
if (_div == null)
continue;
// PNG Subs
var imageDic = new Dictionary<string, string>(); // id, Base64
if (ImageRegex().IsMatch(xmlDoc.InnerXml))
{
foreach (Match img in ImageRegex().Matches(xmlDoc.InnerXml))
{
imageDic.Add(img.Groups[1].Value.Trim(), img.Groups[2].Value.Trim());
}
}
// convert <div> to <p>
if (_div!.SelectNodes("ns:p", nsMgr) == null || _div!.SelectNodes("ns:p", nsMgr)!.Count == 0)
{
foreach (XmlElement _tDiv in bodyNode.SelectNodes("ns:div", nsMgr)!)
{
var _p = xmlDoc.CreateDocumentFragment();
_p.InnerXml = _tDiv.OuterXml.Replace("<div ", "<p ").Replace("</div>", "</p>");
_div.AppendChild(_p);
}
}
// Parse <p> label
foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!)
{
var _begin = _p.GetAttribute("begin");
var _end = _p.GetAttribute("end");
var _region = _p.GetAttribute("region");
var _bgImg = _p.GetAttribute("smpte:backgroundImage");
// Handle namespace
foreach (XmlAttribute attr in _p.Attributes)
{
if (attr.LocalName == "begin") _begin = attr.Value;
else if (attr.LocalName == "end") _end = attr.Value;
else if (attr.LocalName == "region") _region = attr.Value;
}
_p.SetAttribute("begin", Add(_begin));
_p.SetAttribute("end", Add(_end));
// Console.WriteLine($"{_begin} {_p.GetAttribute("begin")}");
// Console.WriteLine($"{_end} {_p.GetAttribute("begin")}");
}
return xmlDoc.OuterXml;
}
private static string GetTextFromElement(XmlElement node)
{
var sb = new StringBuilder();
foreach (XmlNode item in node.ChildNodes)
{
if (item.NodeType == XmlNodeType.Text)
var sub = new SubEntity
{
sb.Append(item.InnerText.Trim());
}
else if(item.NodeType == XmlNodeType.Element && item.Name == "br")
Begin = _begin,
End = _end,
Region = _region
};
if (string.IsNullOrEmpty(_bgImg))
{
sb.AppendLine();
}
}
return sb.ToString();
}
public static List<string> SplitMultipleRootElements(string xml)
{
if (!MultiElementsFixRegex().IsMatch(xml)) return new List<string>();
return MultiElementsFixRegex().Matches(xml).Select(m => m.Value).ToList();
}
public static WebVttSub ExtractFromMp4(string item, long segTimeMs, long baseTimestamp = 0L)
{
return ExtractFromMp4s(new string[] { item }, segTimeMs, baseTimestamp);
}
public static WebVttSub ExtractFromMp4s(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
{
// read ttmls
List<string> xmls = new List<string>();
int segIndex = 0;
foreach (var item in items)
{
var dataSeg = File.ReadAllBytes(item);
var sawMDAT = false;
// parse media
new MP4Parser()
.Box("mdat", MP4Parser.AllData((data) =>
var _spans = _p.ChildNodes;
// Collect <span>
foreach (XmlNode _node in _spans)
{
sawMDAT = true;
// Join this to any previous payload, in case the mp4 has multiple
// mdats.
if (segTimeMs != 0)
if (_node.NodeType == XmlNodeType.Element)
{
var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data));
foreach (var item in datas)
{
xmls.Add(ShiftTime(item, segTimeMs, segIndex));
}
var _span = (XmlElement)_node;
if (string.IsNullOrEmpty(_span.InnerText))
continue;
sub.Contents.Add(_span);
sub.ContentStrings.Add(_span.OuterXml);
}
else
{
var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data));
xmls.AddRange(datas);
}
}))
.Parse(dataSeg,/* partialOkay= */ false);
segIndex++;
}
return ExtractSub(xmls, baseTimestamp);
}
public static WebVttSub ExtractFromTTML(string item, long segTimeMs, long baseTimestamp = 0L)
{
return ExtractFromTTMLs(new string[] { item }, segTimeMs, baseTimestamp);
}
public static WebVttSub ExtractFromTTMLs(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
{
// read ttmls
List<string> xmls = new List<string>();
int segIndex = 0;
foreach (var item in items)
{
var xml = File.ReadAllText(item);
if (segTimeMs != 0)
{
xmls.Add(ShiftTime(xml, segTimeMs, segIndex));
}
else
{
xmls.Add(xml);
}
segIndex++;
}
return ExtractSub(xmls, baseTimestamp);
}
private static WebVttSub ExtractSub(List<string> xmls, long baseTimestamp)
{
// parsing
var xmlDoc = new XmlDocument();
var finalSubs = new List<SubEntity>();
XmlNode? headNode = null;
XmlNamespaceManager? nsMgr = null;
var regex = LabelFixRegex();
var attrRegex = AttrRegex();
foreach (var item in xmls)
{
var xmlContent = item;
if (!xmlContent.Contains("<tt")) continue;
// fix non-standard xml
var xmlContentFix = xmlContent;
if (regex.IsMatch(xmlContent))
{
foreach (Match m in regex.Matches(xmlContentFix))
{
try
{
var inner = m.Groups[1].Value;
if (attrRegex.IsMatch(inner))
{
inner = attrRegex.Replace(inner, "");
}
new XmlDocument().LoadXml($"<p>{inner}</p>");
}
catch (Exception)
{
xmlContentFix = xmlContentFix.Replace(m.Groups[1].Value, System.Web.HttpUtility.HtmlEncode(m.Groups[1].Value));
}
}
}
xmlDoc.LoadXml(xmlContentFix);
var ttNode = xmlDoc.LastChild;
if (nsMgr == null)
{
var ns = ((XmlElement)ttNode!).GetAttribute("xmlns");
nsMgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsMgr.AddNamespace("ns", ns);
}
if (headNode == null)
headNode = ttNode!.SelectSingleNode("ns:head", nsMgr);
var bodyNode = ttNode!.SelectSingleNode("ns:body", nsMgr);
if (bodyNode == null)
continue;
var _div = bodyNode.SelectSingleNode("ns:div", nsMgr);
if (_div == null)
continue;
// PNG Subs
var imageDic = new Dictionary<string, string>(); // id, Base64
if (ImageRegex().IsMatch(xmlDoc.InnerXml))
{
foreach (Match img in ImageRegex().Matches(xmlDoc.InnerXml))
{
imageDic.Add(img.Groups[1].Value.Trim(), img.Groups[2].Value.Trim());
}
}
// convert <div> to <p>
if (_div!.SelectNodes("ns:p", nsMgr) == null || _div!.SelectNodes("ns:p", nsMgr)!.Count == 0)
{
foreach (XmlElement _tDiv in bodyNode.SelectNodes("ns:div", nsMgr)!)
{
var _p = xmlDoc.CreateDocumentFragment();
_p.InnerXml = _tDiv.OuterXml.Replace("<div ", "<p ").Replace("</div>", "</p>");
_div.AppendChild(_p);
}
}
// Parse <p> label
foreach (XmlElement _p in _div!.SelectNodes("ns:p", nsMgr)!)
{
var _begin = _p.GetAttribute("begin");
var _end = _p.GetAttribute("end");
var _region = _p.GetAttribute("region");
var _bgImg = _p.GetAttribute("smpte:backgroundImage");
// Handle namespace
foreach (XmlAttribute attr in _p.Attributes)
{
if (attr.LocalName == "begin") _begin = attr.Value;
else if (attr.LocalName == "end") _end = attr.Value;
else if (attr.LocalName == "region") _region = attr.Value;
}
var sub = new SubEntity
{
Begin = _begin,
End = _end,
Region = _region
};
if (string.IsNullOrEmpty(_bgImg))
{
var _spans = _p.ChildNodes;
// Collect <span>
foreach (XmlNode _node in _spans)
{
if (_node.NodeType == XmlNodeType.Element)
{
var _span = (XmlElement)_node;
if (string.IsNullOrEmpty(_span.InnerText))
continue;
sub.Contents.Add(_span);
sub.ContentStrings.Add(_span.OuterXml);
}
else if (_node.NodeType == XmlNodeType.Text)
{
var _span = new XmlDocument().CreateElement("span");
_span.InnerText = _node.Value!;
sub.Contents.Add(_span);
sub.ContentStrings.Add(_span.OuterXml);
}
}
}
else
{
var id = _bgImg.Replace("#", "");
if (imageDic.ContainsKey(id))
else if (_node.NodeType == XmlNodeType.Text)
{
var _span = new XmlDocument().CreateElement("span");
_span.InnerText = $"Base64::{imageDic[id]}";
_span.InnerText = _node.Value!;
sub.Contents.Add(_span);
sub.ContentStrings.Add(_span.OuterXml);
}
}
// Check if one <p> has been splitted
var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings));
// Skip empty lines
if (sub.ContentStrings.Count > 0)
{
// Extend <p> duration
if (index != -1)
finalSubs[index].End = sub.End;
else if (!finalSubs.Contains(sub))
finalSubs.Add(sub);
}
}
}
var dic = new Dictionary<string, string>();
foreach (var sub in finalSubs)
{
var key = $"{sub.Begin} --> {sub.End}";
foreach (var item in sub.Contents)
else
{
if (dic.ContainsKey(key))
var id = _bgImg.Replace("#", "");
if (imageDic.TryGetValue(id, out var value))
{
if (item.GetAttribute("tts:fontStyle") == "italic" || item.GetAttribute("tts:fontStyle") == "oblique")
dic[key] = $"{dic[key]}\r\n<i>{GetTextFromElement(item)}</i>";
else
dic[key] = $"{dic[key]}\r\n{GetTextFromElement(item)}";
}
else
{
if (item.GetAttribute("tts:fontStyle") == "italic" || item.GetAttribute("tts:fontStyle") == "oblique")
dic.Add(key, $"<i>{GetTextFromElement(item)}</i>");
else
dic.Add(key, GetTextFromElement(item));
var _span = new XmlDocument().CreateElement("span");
_span.InnerText = $"Base64::{value}";
sub.Contents.Add(_span);
sub.ContentStrings.Add(_span.OuterXml);
}
}
// Check if one <p> has been splitted
var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings));
// Skip empty lines
if (sub.ContentStrings.Count <= 0)
continue;
// Extend <p> duration
if (index != -1)
finalSubs[index].End = sub.End;
else if (!finalSubs.Contains(sub))
finalSubs.Add(sub);
}
var vtt = new StringBuilder();
vtt.AppendLine("WEBVTT");
foreach (var item in dic)
{
vtt.AppendLine(item.Key);
vtt.AppendLine(item.Value);
vtt.AppendLine();
}
return WebVttSub.Parse(vtt.ToString(), baseTimestamp);
}
var dic = new Dictionary<string, string>();
foreach (var sub in finalSubs)
{
var key = $"{sub.Begin} --> {sub.End}";
foreach (var item in sub.Contents)
{
if (dic.ContainsKey(key))
{
if (item.GetAttribute("tts:fontStyle") == "italic" || item.GetAttribute("tts:fontStyle") == "oblique")
dic[key] = $"{dic[key]}\r\n<i>{GetTextFromElement(item)}</i>";
else
dic[key] = $"{dic[key]}\r\n{GetTextFromElement(item)}";
}
else
{
if (item.GetAttribute("tts:fontStyle") == "italic" || item.GetAttribute("tts:fontStyle") == "oblique")
dic.Add(key, $"<i>{GetTextFromElement(item)}</i>");
else
dic.Add(key, GetTextFromElement(item));
}
}
}
var vtt = new StringBuilder();
vtt.AppendLine("WEBVTT");
foreach (var item in dic)
{
vtt.AppendLine(item.Key);
vtt.AppendLine(item.Value);
vtt.AppendLine();
}
return WebVttSub.Parse(vtt.ToString(), baseTimestamp);
}
}
}

View File

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

View File

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

View File

@ -1,10 +1,5 @@
using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Parser.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Parser.Processor;

View File

@ -1,11 +1,6 @@
using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Parser.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Parser.Processor.DASH;
@ -18,11 +13,7 @@ public class DefaultDASHContentProcessor : ContentProcessor
{
if (extractorType != ExtractorType.MPEG_DASH) return false;
if (mpdContent.Contains("<mas:") && !mpdContent.Contains("xmlns:mas"))
{
return true;
}
return false;
return mpdContent.Contains("<mas:") && !mpdContent.Contains("xmlns:mas");
}
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.Log;
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;
namespace N_m3u8DL_RE.Parser.Processor;
@ -17,29 +11,27 @@ public class DefaultUrlProcessor : UrlProcessor
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 uriFromConfigQuery = HttpUtility.ParseQueryString(uriFromConfig.Query);
var oldUri = new Uri(oriUrl);
var newQuery = HttpUtility.ParseQueryString(oldUri.Query);
foreach (var item in uriFromConfigQuery.AllKeys)
{
var uriFromConfig = new Uri(paserConfig.Url);
var uriFromConfigQuery = HttpUtility.ParseQueryString(uriFromConfig.Query);
var oldUri = new Uri(oriUrl);
var newQuery = HttpUtility.ParseQueryString(oldUri.Query);
foreach (var item in uriFromConfigQuery.AllKeys)
{
if (newQuery.AllKeys.Contains(item))
newQuery.Set(item, uriFromConfigQuery.Get(item));
else
newQuery.Add(item, uriFromConfigQuery.Get(item));
}
if (!string.IsNullOrEmpty(newQuery.ToString()))
{
Logger.Debug("Before: " + oriUrl);
oriUrl = (oldUri.GetLeftPart(UriPartial.Path) + "?" + newQuery.ToString()).TrimEnd('?');
Logger.Debug("After: " + oriUrl);
}
if (newQuery.AllKeys.Contains(item))
newQuery.Set(item, uriFromConfigQuery.Get(item));
else
newQuery.Add(item, uriFromConfigQuery.Get(item));
}
if (string.IsNullOrEmpty(newQuery.ToString())) return oriUrl;
Logger.Debug("Before: " + oriUrl);
oriUrl = (oldUri.GetLeftPart(UriPartial.Path) + "?" + newQuery).TrimEnd('?');
Logger.Debug("After: " + oriUrl);
return oriUrl;
}
}

View File

@ -1,12 +1,7 @@
using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Parser.Config;
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;
namespace N_m3u8DL_RE.Parser.Processor.HLS;
@ -16,13 +11,13 @@ public partial class DefaultHLSContentProcessor : ContentProcessor
private static partial Regex YkDVRegex();
[GeneratedRegex("#EXT-X-MAP:URI=\\\".*?BUMPER/[\\s\\S]+?#EXT-X-DISCONTINUITY")]
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();
[GeneratedRegex("(#EXTINF.*)(\\s+)(#EXT-X-KEY.*)")]
private static partial Regex OrderFixRegex();
[GeneratedRegex("#EXT-X-MAP.*\\.apple\\.com/")]
[GeneratedRegex(@"#EXT-X-MAP.*\.apple\.com/")]
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();
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)
{
// 处理content以\r作为换行符的情况
if (m3u8Content.Contains("\r") && !m3u8Content.Contains("\n"))
if (m3u8Content.Contains('\r') && !m3u8Content.Contains('\n'))
{
m3u8Content = m3u8Content.Replace("\r", Environment.NewLine);
}
@ -51,7 +46,7 @@ public partial class DefaultHLSContentProcessor : ContentProcessor
// 针对YK #EXT-X-VERSION:7杜比视界片源修正
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Content.Contains("ott.cibntv.net") && m3u8Content.Contains("ccode="))
{
Regex ykmap = YkDVRegex();
var ykmap = YkDVRegex();
foreach (Match m in ykmap.Matches(m3u8Content))
{
m3u8Content = m3u8Content.Replace(m.Value, $"#EXTINF:0.000000,\n#EXT-X-BYTERANGE:{m.Groups[2].Value}\n{m.Groups[1].Value}");

View File

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

View File

@ -1,11 +1,6 @@
using N_m3u8DL_RE.Common.Entity;
using N_m3u8DL_RE.Common.Enum;
using N_m3u8DL_RE.Parser.Config;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Parser.Processor;

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

View File

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

View File

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

View File

@ -25,9 +25,6 @@ internal class RecordingDurationColumn : ProgressColumn
{
if (_refreshedDurDic == null)
return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}", MyStyle).LeftJustified();
else
{
return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}/{GlobalUtil.FormatTime(_refreshedDurDic[task.Id])}", GreyStyle);
}
return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}/{GlobalUtil.FormatTime(_refreshedDurDic[task.Id])}", GreyStyle);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ internal class SimpleLiveRecordManager2
StreamExtractor StreamExtractor;
List<StreamSpec> SelectedSteams;
ConcurrentDictionary<int, string> PipeSteamNamesDic = new();
List<OutputFile> OutputFiles = new();
List<OutputFile> OutputFiles = [];
DateTime? PublishDateTime;
bool STOP_FLAG = false;
int WAIT_SEC = 0; // 刷新间隔
@ -41,7 +41,7 @@ internal class SimpleLiveRecordManager2
ConcurrentDictionary<int, long> DateTimeDic = new(); // 上次下载的dateTime
CancellationTokenSource CancellationTokenSource = new(); // 取消Wait
private readonly object lockObj = new object();
private readonly Lock lockObj = new();
TimeSpan? audioStart = null;
public SimpleLiveRecordManager2(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
@ -60,9 +60,9 @@ internal class SimpleLiveRecordManager2
if (_key != null)
{
if (DownloaderConfig.MyOptions.Keys == null)
DownloaderConfig.MyOptions.Keys = new string[] { _key };
DownloaderConfig.MyOptions.Keys = [_key];
else
DownloaderConfig.MyOptions.Keys = DownloaderConfig.MyOptions.Keys.Concat(new string[] { _key }).ToArray();
DownloaderConfig.MyOptions.Keys = [..DownloaderConfig.MyOptions.Keys, _key];
}
}
@ -111,13 +111,13 @@ internal class SimpleLiveRecordManager2
private void ChangeSpecInfo(StreamSpec streamSpec, List<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;
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge2}[/]");
}
if (DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison == true))
if (DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison))
{
DownloaderConfig.MyOptions.MuxAfterDone = false;
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]");
@ -136,7 +136,7 @@ internal class SimpleLiveRecordManager2
{
streamSpec.MediaType = MediaType.SUBTITLES;
if (streamSpec.Extension == null || streamSpec.Extension == "ts")
if (streamSpec.Extension is null or "ts")
streamSpec.Extension = "vtt";
}
}
@ -151,7 +151,7 @@ internal class SimpleLiveRecordManager2
bool useAACFilter = false; // ffmpeg合并flag
bool initDownloaded = false; // 是否下载过init文件
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
List<Mediainfo> mediaInfos = new();
List<Mediainfo> mediaInfos = [];
Stream? fileOutputStream = null;
WebVttSub currentVtt = new(); // 字幕流始终维护一个实例
bool firstSub = true;
@ -197,7 +197,7 @@ internal class SimpleLiveRecordManager2
var path = Path.Combine(tmpDir, "_init.mp4.tmp");
var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers);
FileDic[streamSpec.Playlist.MediaInit] = result;
if (result == null || !result.Success)
if (result is not { Success: true })
{
throw new Exception("Download init file failed!");
}
@ -205,7 +205,7 @@ internal class SimpleLiveRecordManager2
task.Increment(1);
// 读取mp4信息
if (result != null && result.Success)
if (result is { Success: true })
{
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
// 从文件读取KEY
@ -257,12 +257,12 @@ internal class SimpleLiveRecordManager2
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
FileDic[seg] = result;
if (result == null || !result.Success)
if (result is not { Success: true })
{
throw new Exception("Download first segment failed!");
}
task.Increment(1);
if (result != null && result.Success)
if (result is { Success: true })
{
// 修复MSS init
if (StreamExtractor.ExtractorType == ExtractorType.MSS)
@ -330,10 +330,10 @@ internal class SimpleLiveRecordManager2
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
FileDic[seg] = result;
if (result != null && result.Success)
if (result is { Success: true })
task.Increment(1);
// 实时解密
if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && result != null && result.Success && !string.IsNullOrEmpty(currentKID))
if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && result is { Success: true } && !string.IsNullOrEmpty(currentKID))
{
var enc = result.ActualFilePath;
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
@ -347,14 +347,13 @@ internal class SimpleLiveRecordManager2
});
// 自动修复VTT raw字幕
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
&& streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("vtt"))
{
// 排序字幕并修正时间戳
var keys = FileDic.Keys.OrderBy(k => k.Index);
var keys = FileDic.Keys.OrderBy(k => k.Index).ToList();
foreach (var seg in keys)
{
var vttContent = File.ReadAllText(FileDic[seg]!.ActualFilePath);
var vttContent = await File.ReadAllTextAsync(FileDic[seg]!.ActualFilePath);
var waitCount = 0;
while (DownloaderConfig.MyOptions.LiveFixVttByAudio && audioStart == null && waitCount++ < 5)
{
@ -376,7 +375,7 @@ internal class SimpleLiveRecordManager2
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
{
var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault();
var initFile = FileDic.Values.FirstOrDefault(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init"));
var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes);
if (sawVtt)
@ -396,10 +395,9 @@ internal class SimpleLiveRecordManager2
}
// 自动修复TTML raw字幕
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
&& streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("ttml"))
{
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key);
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key).ToList();
if (firstSub)
{
if (baseTimestamp != 0)
@ -437,9 +435,8 @@ internal class SimpleLiveRecordManager2
}
// 自动修复TTML mp4字幕
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
&& streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("m4s")
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
{
// sawTtml暂时不判断
// var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault();
@ -498,12 +495,11 @@ internal class SimpleLiveRecordManager2
// 合并
var outputExt = "." + streamSpec.Extension;
if (streamSpec.Extension == null) outputExt = ".ts";
else if (streamSpec.MediaType == MediaType.AUDIO && streamSpec.Extension == "m4s") outputExt = ".m4a";
else if (streamSpec is { MediaType: MediaType.AUDIO, Extension: "m4s" }) outputExt = ".m4a";
else if (streamSpec.MediaType != MediaType.SUBTITLES && streamSpec.Extension == "m4s") outputExt = ".mp4";
else if (streamSpec.MediaType == MediaType.SUBTITLES)
{
if (DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT) outputExt = ".srt";
else outputExt = ".vtt";
outputExt = DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT ? ".srt" : ".vtt";
}
var output = Path.Combine(saveDir, saveName + outputExt);
@ -536,7 +532,7 @@ internal class SimpleLiveRecordManager2
fileOutputStream = PipeUtil.CreatePipe(pipeName);
Logger.InfoMarkUp($"{ResString.namedPipeCreated} [cyan]{pipeName.EscapeMarkup()}[/]");
PipeSteamNamesDic[task.Id] = pipeName;
if (PipeSteamNamesDic.Count == SelectedSteams.Where(x => x.MediaType != MediaType.SUBTITLES).Count())
if (PipeSteamNamesDic.Count == SelectedSteams.Count(x => x.MediaType != MediaType.SUBTITLES))
{
var names = PipeSteamNamesDic.OrderBy(i => i.Key).Select(k => k.Value).ToArray();
Logger.WarnMarkUp($"{ResString.namedPipeMux} [deepskyblue1]{Path.GetFileName(output).EscapeMarkup()}[/]");
@ -622,24 +618,23 @@ internal class SimpleLiveRecordManager2
break;
}
if (fileOutputStream != null)
if (fileOutputStream == null) return true;
if (!DownloaderConfig.MyOptions.LivePipeMux)
{
if (!DownloaderConfig.MyOptions.LivePipeMux)
// 记录所有文件信息
OutputFiles.Add(new OutputFile()
{
// 记录所有文件信息
OutputFiles.Add(new OutputFile()
{
Index = task.Id,
FilePath = (fileOutputStream as FileStream)!.Name,
LangCode = streamSpec.Language,
Description = streamSpec.Name,
Mediainfos = mediaInfos,
MediaType = streamSpec.MediaType,
});
}
fileOutputStream.Close();
fileOutputStream.Dispose();
Index = task.Id,
FilePath = (fileOutputStream as FileStream)!.Name,
LangCode = streamSpec.Language,
Description = streamSpec.Name,
Mediainfos = mediaInfos,
MediaType = streamSpec.MediaType,
});
}
fileOutputStream.Close();
fileOutputStream.Dispose();
return true;
}
@ -648,78 +643,76 @@ internal class SimpleLiveRecordManager2
{
while (!STOP_FLAG)
{
if (WAIT_SEC != 0)
if (WAIT_SEC == 0) continue;
// 1. MPD 所有URL相同 单次请求即可获得所有轨道的信息
// 2. M3U8 所有URL不同 才需要多次请求
await Parallel.ForEachAsync(dic, async (dic, _) =>
{
// 1. MPD 所有URL相同 单次请求即可获得所有轨道的信息
// 2. M3U8 所有URL不同 才需要多次请求
var streamSpec = dic.Key;
var task = dic.Value;
await Parallel.ForEachAsync(dic, async (dic, _) =>
// 达到上限时 不需要刷新了
if (RecordLimitReachedDic[task.Id])
return;
var allHasDatetime = streamSpec.Playlist!.MediaParts[0].MediaSegments.All(s => s.DateTime != null);
if (!SamePathDic.ContainsKey(task.Id))
{
var streamSpec = dic.Key;
var task = dic.Value;
// 达到上限时 不需要刷新了
if (RecordLimitReachedDic[task.Id])
return;
var allHasDatetime = streamSpec.Playlist!.MediaParts[0].MediaSegments.All(s => s.DateTime != null);
if (!SamePathDic.ContainsKey(task.Id))
{
var allName = streamSpec.Playlist!.MediaParts[0].MediaSegments.Select(s => OtherUtil.GetFileNameFromInput(s.Url, false));
var allSamePath = allName.Count() > 1 && allName.Distinct().Count() == 1;
SamePathDic[task.Id] = allSamePath;
}
// 过滤不需要下载的片段
FilterMediaSegments(streamSpec, task, allHasDatetime, SamePathDic[task.Id]);
var newList = streamSpec.Playlist!.MediaParts[0].MediaSegments;
if (newList.Count > 0)
{
task.MaxValue += newList.Count;
// 推送给消费者
await BlockDic[task.Id].SendAsync(newList);
// 更新最新链接
LastFileNameDic[task.Id] = GetSegmentName(newList.Last(), allHasDatetime, SamePathDic[task.Id]);
// 尝试更新时间戳
var dt = newList.Last().DateTime;
DateTimeDic[task.Id] = dt != null ? GetUnixTimestamp(dt.Value) : 0L;
// 累加已获取到的时长
RefreshedDurDic[task.Id] += (int)newList.Sum(s => s.Duration);
}
if (!STOP_FLAG && RefreshedDurDic[task.Id] >= DownloaderConfig.MyOptions.LiveRecordLimit?.TotalSeconds)
{
RecordLimitReachedDic[task.Id] = true;
}
// 检测时长限制
if (!STOP_FLAG && RecordLimitReachedDic.Values.All(x => x == true))
{
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]");
STOP_FLAG = true;
CancellationTokenSource.Cancel();
}
});
try
{
// Logger.WarnMarkUp($"wait {waitSec}s");
if (!STOP_FLAG) await Task.Delay(WAIT_SEC * 1000, CancellationTokenSource.Token);
// 刷新列表
if (!STOP_FLAG) await StreamExtractor.RefreshPlayListAsync(dic.Keys.ToList());
var allName = streamSpec.Playlist!.MediaParts[0].MediaSegments.Select(s => OtherUtil.GetFileNameFromInput(s.Url, false));
var allSamePath = allName.Count() > 1 && allName.Distinct().Count() == 1;
SamePathDic[task.Id] = allSamePath;
}
catch (OperationCanceledException oce) when (oce.CancellationToken == CancellationTokenSource.Token)
// 过滤不需要下载的片段
FilterMediaSegments(streamSpec, task, allHasDatetime, SamePathDic[task.Id]);
var newList = streamSpec.Playlist!.MediaParts[0].MediaSegments;
if (newList.Count > 0)
{
// 不需要做事
task.MaxValue += newList.Count;
// 推送给消费者
await BlockDic[task.Id].SendAsync(newList);
// 更新最新链接
LastFileNameDic[task.Id] = GetSegmentName(newList.Last(), allHasDatetime, SamePathDic[task.Id]);
// 尝试更新时间戳
var dt = newList.Last().DateTime;
DateTimeDic[task.Id] = dt != null ? GetUnixTimestamp(dt.Value) : 0L;
// 累加已获取到的时长
RefreshedDurDic[task.Id] += (int)newList.Sum(s => s.Duration);
}
catch (Exception e)
if (!STOP_FLAG && RefreshedDurDic[task.Id] >= DownloaderConfig.MyOptions.LiveRecordLimit?.TotalSeconds)
{
Logger.ErrorMarkUp(e);
RecordLimitReachedDic[task.Id] = true;
}
// 检测时长限制
if (!STOP_FLAG && RecordLimitReachedDic.Values.All(x => x))
{
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]");
STOP_FLAG = true;
// 停止所有Block
foreach (var target in BlockDic.Values)
{
target.Complete();
}
CancellationTokenSource.Cancel();
}
});
try
{
// Logger.WarnMarkUp($"wait {waitSec}s");
if (!STOP_FLAG) await Task.Delay(WAIT_SEC * 1000, CancellationTokenSource.Token);
// 刷新列表
if (!STOP_FLAG) await StreamExtractor.RefreshPlayListAsync(dic.Keys.ToList());
}
catch (OperationCanceledException oce) when (oce.CancellationToken == CancellationTokenSource.Token)
{
// 不需要做事
}
catch (Exception e)
{
Logger.ErrorMarkUp(e);
STOP_FLAG = true;
// 停止所有Block
foreach (var target in BlockDic.Values)
{
target.Complete();
}
}
}
@ -783,7 +776,7 @@ internal class SimpleLiveRecordManager2
Logger.WarnMarkUp($"set refresh interval to {WAIT_SEC} seconds");
}
// 如果没有选中音频 取消通过音频修复vtt时间轴
if (!SelectedSteams.Any(x => x.MediaType == MediaType.AUDIO))
if (SelectedSteams.All(x => x.MediaType != MediaType.AUDIO))
{
DownloaderConfig.MyOptions.LiveFixVttByAudio = false;
}
@ -839,7 +832,7 @@ internal class SimpleLiveRecordManager2
DownloaderConfig.MyOptions.ConcurrentDownload = true;
DownloaderConfig.MyOptions.MP4RealTimeDecryption = true;
DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue;
DownloaderConfig.MyOptions.LiveRecordLimit ??= TimeSpan.MaxValue;
if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, DecryptionEngine: not DecryptEngine.SHAKA_PACKAGER, Keys.Length: > 0 })
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
@ -865,7 +858,7 @@ internal class SimpleLiveRecordManager2
var success = Results.Values.All(v => v == true);
// 删除临时文件夹
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && success)
if (DownloaderConfig.MyOptions is { SkipMerge: false, DelAfterDone: true } && success)
{
foreach (var item in StreamExtractor.RawFiles)
{

View File

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

View File

@ -1,15 +1,8 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_RE.Entity;
namespace N_m3u8DL_RE.Entity;
internal class DownloadResult
{
public bool Success { get => (ActualContentLength != null && RespContentLength != null) ? (RespContentLength == ActualContentLength) : (ActualContentLength == null ? false : true); }
public bool Success => (ActualContentLength != null && RespContentLength != null) ? (RespContentLength == ActualContentLength) : (ActualContentLength != null);
public long? RespContentLength { get; set; }
public long? ActualContentLength { get; set; }
public bool ImageHeader { get; set; } = false; // 图片伪装

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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