增强可定制性
This commit is contained in:
parent
9722079c65
commit
d21d3924cc
|
@ -1,44 +0,0 @@
|
||||||
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.Config
|
|
||||||
{
|
|
||||||
public class ParserConfig
|
|
||||||
{
|
|
||||||
public string Url { get; set; }
|
|
||||||
public string BaseUrl { get; set; }
|
|
||||||
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>()
|
|
||||||
{
|
|
||||||
["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 自定义的加密方式 默认AES_128_CBC
|
|
||||||
/// </summary>
|
|
||||||
public EncryptMethod CustomMethod { get; set; } = EncryptMethod.AES_128;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 自定义的解密KEY
|
|
||||||
/// </summary>
|
|
||||||
public byte[]? CustomeKey { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 自定义的解密IV
|
|
||||||
/// </summary>
|
|
||||||
public byte[]? CustomeIV { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 组装视频分段的URL时,是否要把原本URL后的参数也加上去
|
|
||||||
/// 如 Base URL = "http://xxx.com/playlist.m3u8?hmac=xxx&token=xxx"
|
|
||||||
/// 相对路径 = clip_01.ts
|
|
||||||
/// 如果 AppendUrlParams=false,得 http://xxx.com/clip_01.ts
|
|
||||||
/// 如果 AppendUrlParams=true,得 http://xxx.com/clip_01.ts?hmac=xxx&token=xxx
|
|
||||||
/// </summary>
|
|
||||||
public bool AppendUrlParams { get; set; } = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -70,6 +71,13 @@ namespace N_m3u8DL_RE.Common.Entity
|
||||||
returnStr = returnStr.Replace("| |", "|");
|
returnStr = returnStr.Replace("| |", "|");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//计算时长
|
||||||
|
if (Playlist != null)
|
||||||
|
{
|
||||||
|
var total = Playlist.MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration));
|
||||||
|
returnStr += " | " + GlobalUtil.FormatTime((int)total);
|
||||||
|
}
|
||||||
|
|
||||||
return returnStr;
|
return returnStr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.Common.Entity
|
||||||
|
{
|
||||||
|
public class SubCue
|
||||||
|
{
|
||||||
|
public TimeSpan StartTime { get; set; }
|
||||||
|
public TimeSpan EndTime { get; set; }
|
||||||
|
public string Payload { get; set; }
|
||||||
|
public string Settings { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.Common.Entity
|
||||||
|
{
|
||||||
|
public class WebSub
|
||||||
|
{
|
||||||
|
public List<SubCue> Cues { get; set; } = new List<SubCue>();
|
||||||
|
public long MpegtsTimestamp { get; set; } = 0L;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从字节数组解析WEBVTT
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="textBytes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static WebSub Parse(byte[] textBytes)
|
||||||
|
{
|
||||||
|
return Parse(Encoding.UTF8.GetString(textBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从字符串解析WEBVTT
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static WebSub Parse(string text)
|
||||||
|
{
|
||||||
|
if (!text.Trim().StartsWith("WEBVTT"))
|
||||||
|
throw new Exception("Bad vtt!");
|
||||||
|
|
||||||
|
|
||||||
|
var webSub = new WebSub();
|
||||||
|
var needPayload = false;
|
||||||
|
var timeLine = "";
|
||||||
|
var regex1 = new Regex("X-TIMESTAMP-MAP.*");
|
||||||
|
|
||||||
|
if (regex1.IsMatch(text))
|
||||||
|
{
|
||||||
|
var timestamp = Regex.Match(regex1.Match(text).Value, "MPEGTS:(\\d+)").Groups[1].Value;
|
||||||
|
webSub.MpegtsTimestamp = Convert.ToInt64(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var line in text.Split('\n'))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(line)) continue;
|
||||||
|
|
||||||
|
if (!needPayload && line.Contains(" --> "))
|
||||||
|
{
|
||||||
|
needPayload = true;
|
||||||
|
timeLine = line.Trim();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needPayload)
|
||||||
|
{
|
||||||
|
var payload = line.Trim();
|
||||||
|
var arr = Regex.Split(timeLine.Replace("-->", ""), "\\s").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 = payload,
|
||||||
|
Settings = style
|
||||||
|
});
|
||||||
|
needPayload = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return webSub;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TimeSpan ConvertToTS(string str)
|
||||||
|
{
|
||||||
|
var ms = Convert.ToInt32(str.Split('.').Last());
|
||||||
|
var o = str.Split('.').First();
|
||||||
|
var t = o.Split(':').Reverse().ToList();
|
||||||
|
var time = 0L + ms;
|
||||||
|
for (int i = 0; i < t.Count(); i++)
|
||||||
|
{
|
||||||
|
time += (int)Math.Pow(60, i) * Convert.ToInt32(t[i]) * 1000;
|
||||||
|
}
|
||||||
|
return TimeSpan.FromMilliseconds(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
foreach (var c in this.Cues)
|
||||||
|
{
|
||||||
|
sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\.fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\.fff") + " " + c.Settings);
|
||||||
|
sb.AppendLine(c.Payload);
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -87,6 +87,15 @@ namespace N_m3u8DL_RE.Common.Resource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 找不到支持的Processor 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string keyProcessorNotFound {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("keyProcessorNotFound", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 检测到直播流 的本地化字符串。
|
/// 查找类似 检测到直播流 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -145,4 +145,7 @@
|
||||||
<data name="parsingStream" xml:space="preserve">
|
<data name="parsingStream" xml:space="preserve">
|
||||||
<value>Parsing streams...</value>
|
<value>Parsing streams...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="keyProcessorNotFound" xml:space="preserve">
|
||||||
|
<value>No Processor matched</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -145,4 +145,7 @@
|
||||||
<data name="parsingStream" xml:space="preserve">
|
<data name="parsingStream" xml:space="preserve">
|
||||||
<value>正在解析媒体信息...</value>
|
<value>正在解析媒体信息...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="keyProcessorNotFound" xml:space="preserve">
|
||||||
|
<value>找不到支持的Processor</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -145,4 +145,7 @@
|
||||||
<data name="parsingStream" xml:space="preserve">
|
<data name="parsingStream" xml:space="preserve">
|
||||||
<value>正在解析媒體信息...</value>
|
<value>正在解析媒體信息...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="keyProcessorNotFound" xml:space="preserve">
|
||||||
|
<value>找不到支持的Processor</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
|
@ -22,5 +22,14 @@ namespace N_m3u8DL_RE.Common.Util
|
||||||
};
|
};
|
||||||
return JsonSerializer.Serialize(o, options);
|
return JsonSerializer.Serialize(o, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//此函数用于格式化输出时长
|
||||||
|
public static string FormatTime(int time)
|
||||||
|
{
|
||||||
|
TimeSpan ts = new TimeSpan(0, 0, time);
|
||||||
|
string str = "";
|
||||||
|
str = (ts.Hours.ToString("00") == "00" ? "" : ts.Hours.ToString("00") + "h") + ts.Minutes.ToString("00") + "m" + ts.Seconds.ToString("00") + "s";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,43 @@ namespace N_m3u8DL_RE.Common.Util
|
||||||
return webResponse;
|
return webResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//重定向
|
||||||
|
public static async Task<string> Get302Async(string url, Dictionary<string, string>? headers = null)
|
||||||
|
{
|
||||||
|
Logger.Debug(ResString.fetch + url);
|
||||||
|
var handler = new HttpClientHandler()
|
||||||
|
{
|
||||||
|
AllowAutoRedirect = false
|
||||||
|
};
|
||||||
|
string redirectedUrl = url;
|
||||||
|
|
||||||
|
using (HttpClient client = new HttpClient(handler))
|
||||||
|
{
|
||||||
|
if (headers != null)
|
||||||
|
{
|
||||||
|
foreach (var item in headers)
|
||||||
|
{
|
||||||
|
client.DefaultRequestHeaders.TryAddWithoutValidation(item.Key, item.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using (HttpResponseMessage response = await client.GetAsync(url))
|
||||||
|
using (HttpContent content = response.Content)
|
||||||
|
{
|
||||||
|
Logger.Debug(ResString.fetch + response.Headers);
|
||||||
|
if (response.StatusCode == HttpStatusCode.Found || response.StatusCode == HttpStatusCode.Moved)
|
||||||
|
{
|
||||||
|
HttpResponseHeaders respHeaders = response.Headers;
|
||||||
|
if (respHeaders != null && respHeaders.Location != null)
|
||||||
|
{
|
||||||
|
redirectedUrl = respHeaders.Location.AbsoluteUri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirectedUrl;
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<byte[]> GetBytesAsync(string url, Dictionary<string, string>? headers = null)
|
public static async Task<byte[]> GetBytesAsync(string url, Dictionary<string, string>? headers = null)
|
||||||
{
|
{
|
||||||
byte[] bytes = new byte[0];
|
byte[] bytes = new byte[0];
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<RootNamespace>N_m3u8DL_RE.Extends</RootNamespace>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\N_m3u8DL-RE.Common\N_m3u8DL-RE.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Crypto\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,35 @@
|
||||||
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.Extends.Subtitle
|
||||||
|
{
|
||||||
|
public class WebVTTUtil
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 修复VTT起始时间戳 <br/>
|
||||||
|
/// X-TIMESTAMP-MAP=MPEGTS:8528254208,LOCAL:00:00:00.000
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sub"></param>
|
||||||
|
/// <param name="baseTimestamp">基础时间戳</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static void FixTimestamp(WebSub sub, long baseTimestamp)
|
||||||
|
{
|
||||||
|
if (baseTimestamp == 0 || sub.MpegtsTimestamp == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//The MPEG2 transport stream clocks (PCR, PTS, DTS) all have units of 1/90000 second
|
||||||
|
var seconds = (sub.MpegtsTimestamp - baseTimestamp) / 90000;
|
||||||
|
for (int i = 0; i < sub.Cues.Count; i++)
|
||||||
|
{
|
||||||
|
sub.Cues[i].StartTime += TimeSpan.FromSeconds(seconds);
|
||||||
|
sub.Cues[i].EndTime += TimeSpan.FromSeconds(seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
|
using N_m3u8DL_RE.Parser.Processor;
|
||||||
|
using N_m3u8DL_RE.Parser.Processor.DASH;
|
||||||
|
using N_m3u8DL_RE.Parser.Processor.HLS;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.Parser.Config
|
||||||
|
{
|
||||||
|
public class ParserConfig
|
||||||
|
{
|
||||||
|
public string Url { get; set; }
|
||||||
|
|
||||||
|
public string BaseUrl { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HLS内容前置处理器. 调用顺序与列表顺序相同
|
||||||
|
/// </summary>
|
||||||
|
public IList<ContentProcessor> HLSContentProcessors { get; } = new List<ContentProcessor>() { new DefaultHLSContentProcessor() };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DASH内容前置处理器. 调用顺序与列表顺序相同
|
||||||
|
/// </summary>
|
||||||
|
public IList<ContentProcessor> DASHContentProcessors { get; } = new List<ContentProcessor>() { new DefaultDASHContentProcessor() };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加分片URL前置处理器. 调用顺序与列表顺序相同
|
||||||
|
/// </summary>
|
||||||
|
public IList<UrlProcessor> HLSUrlProcessors { get; } = new List<UrlProcessor>() { new DefaultUrlProcessor() };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DASH内容前置处理器. 调用顺序与列表顺序相同
|
||||||
|
/// </summary>
|
||||||
|
public IList<UrlProcessor> DASHUrlProcessors { get; } = new List<UrlProcessor>() { new DefaultUrlProcessor() };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HLS-KEY解析器. 调用顺序与列表顺序相同
|
||||||
|
/// </summary>
|
||||||
|
public IList<KeyProcessor> HLSKeyProcessors { get; } = new List<KeyProcessor>() { new DefaultHLSKeyProcessor() };
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义的加密方式
|
||||||
|
/// </summary>
|
||||||
|
public EncryptMethod? CustomMethod { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义的解密KEY
|
||||||
|
/// </summary>
|
||||||
|
public byte[]? CustomeKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 自定义的解密IV
|
||||||
|
/// </summary>
|
||||||
|
public byte[]? CustomeIV { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 组装视频分段的URL时,是否要把原本URL后的参数也加上去
|
||||||
|
/// 如 Base URL = "http://xxx.com/playlist.m3u8?hmac=xxx&token=xxx"
|
||||||
|
/// 相对路径 = clip_01.ts
|
||||||
|
/// 如果 AppendUrlParams=false,得 http://xxx.com/clip_01.ts
|
||||||
|
/// 如果 AppendUrlParams=true,得 http://xxx.com/clip_01.ts?hmac=xxx&token=xxx
|
||||||
|
/// </summary>
|
||||||
|
public bool AppendUrlParams { get; set; } = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
using N_m3u8DL_RE.Common.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
|
||||||
using N_m3u8DL_RE.Parser.Util;
|
using N_m3u8DL_RE.Parser.Util;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -723,9 +722,12 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string PreProcessUrl(string url)
|
private string PreProcessUrl(string url)
|
||||||
{
|
{
|
||||||
if (ParserConfig.AppendUrlParams)
|
foreach (var p in ParserConfig.DASHUrlProcessors)
|
||||||
{
|
{
|
||||||
url += new Regex("\\?.*").Match(MpdUrl).Value;
|
if (p.CanProcess(url, ParserConfig))
|
||||||
|
{
|
||||||
|
url = p.Process(url, ParserConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
|
@ -733,10 +735,12 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
|
|
||||||
private void PreProcessContent()
|
private void PreProcessContent()
|
||||||
{
|
{
|
||||||
//XiGua
|
foreach (var p in ParserConfig.DASHContentProcessors)
|
||||||
if (this.MpdContent.Contains("<mas:") && !this.MpdContent.Contains("xmlns:mas"))
|
|
||||||
{
|
{
|
||||||
this.MpdContent = this.MpdContent.Replace("<MPD ", "<MPD xmlns:mas=\"urn:marlin:mas:1-0:services:schemas:mpd\" ");
|
if (p.CanProcess(MpdContent, ParserConfig))
|
||||||
|
{
|
||||||
|
MpdContent = p.Process(MpdContent, ParserConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
using N_m3u8DL_RE.Common.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
|
||||||
using N_m3u8DL_RE.Parser.Constants;
|
|
||||||
using N_m3u8DL_RE.Parser.Util;
|
using N_m3u8DL_RE.Parser.Util;
|
||||||
|
using N_m3u8DL_RE.Parser.Constants;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using N_m3u8DL_RE.Common.Util;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
{
|
{
|
||||||
|
@ -30,9 +30,13 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
this.ParserConfig = parserConfig;
|
this.ParserConfig = parserConfig;
|
||||||
this.M3u8Url = parserConfig.Url ?? string.Empty;
|
this.M3u8Url = parserConfig.Url ?? string.Empty;
|
||||||
if (!string.IsNullOrEmpty(parserConfig.BaseUrl))
|
if (!string.IsNullOrEmpty(parserConfig.BaseUrl))
|
||||||
|
{
|
||||||
this.BaseUrl = parserConfig.BaseUrl;
|
this.BaseUrl = parserConfig.BaseUrl;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
this.BaseUrl = this.M3u8Url;
|
{
|
||||||
|
this.BaseUrl = parserConfig.BaseUrl = this.M3u8Url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -46,59 +50,12 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
throw new Exception(ResString.badM3u8);
|
throw new Exception(ResString.badM3u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
//央视频回放
|
foreach (var p in ParserConfig.HLSContentProcessors)
|
||||||
if (M3u8Url.Contains("tlivecloud-playback-cdn.ysp.cctv.cn") && M3u8Url.Contains("endtime="))
|
|
||||||
{
|
{
|
||||||
M3u8Content += Environment.NewLine + HLSTags.ext_x_endlist;
|
if (p.CanProcess(M3u8Content, ParserConfig))
|
||||||
|
{
|
||||||
|
M3u8Content = p.Process(M3u8Content, ParserConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
//IMOOC
|
|
||||||
if (M3u8Url.Contains("imooc.com/"))
|
|
||||||
{
|
|
||||||
//M3u8Content = DecodeImooc.DecodeM3u8(M3u8Content);
|
|
||||||
}
|
|
||||||
|
|
||||||
//iqy
|
|
||||||
if (M3u8Content.StartsWith("{\"payload\""))
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
//针对优酷#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 = new Regex("#EXT-X-DISCONTINUITY\\s+#EXT-X-MAP:URI=\\\"(.*?)\\\",BYTERANGE=\\\"(.*?)\\\"");
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//针对Disney+修正
|
|
||||||
if (M3u8Content.Contains("#EXT-X-DISCONTINUITY") && M3u8Content.Contains("#EXT-X-MAP") && M3u8Url.Contains("media.dssott.com/"))
|
|
||||||
{
|
|
||||||
Regex ykmap = new Regex("#EXT-X-MAP:URI=\\\".*?BUMPER/[\\s\\S]+?#EXT-X-DISCONTINUITY");
|
|
||||||
if (ykmap.IsMatch(M3u8Content))
|
|
||||||
{
|
|
||||||
M3u8Content = M3u8Content.Replace(ykmap.Match(M3u8Content).Value, "#XXX");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//针对AppleTv修正
|
|
||||||
if (M3u8Content.Contains("#EXT-X-DISCONTINUITY") && M3u8Content.Contains("#EXT-X-MAP") && (M3u8Url.Contains(".apple.com/") || Regex.IsMatch(M3u8Content, "#EXT-X-MAP.*\\.apple\\.com/")))
|
|
||||||
{
|
|
||||||
//只取加密部分即可
|
|
||||||
Regex ykmap = new Regex("(#EXT-X-KEY:[\\s\\S]*?)(#EXT-X-DISCONTINUITY|#EXT-X-ENDLIST)");
|
|
||||||
if (ykmap.IsMatch(M3u8Content))
|
|
||||||
{
|
|
||||||
M3u8Content = "#EXTM3U\r\n" + ykmap.Match(M3u8Content).Groups[1].Value + "\r\n#EXT-X-ENDLIST";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//修复#EXT-X-KEY与#EXTINF出现次序异常问题
|
|
||||||
if (Regex.IsMatch(M3u8Content, "(#EXTINF.*)(\\s+)(#EXT-X-KEY.*)"))
|
|
||||||
{
|
|
||||||
M3u8Content = Regex.Replace(M3u8Content, "(#EXTINF.*)(\\s+)(#EXT-X-KEY.*)", "$3$2$1");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,9 +64,12 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string PreProcessUrl(string url)
|
private string PreProcessUrl(string url)
|
||||||
{
|
{
|
||||||
if (ParserConfig.AppendUrlParams)
|
foreach (var p in ParserConfig.HLSUrlProcessors)
|
||||||
{
|
{
|
||||||
url += new Regex("\\?.*").Match(M3u8Url).Value;
|
if (p.CanProcess(url, ParserConfig))
|
||||||
|
{
|
||||||
|
url = p.Process(url, ParserConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
|
@ -338,7 +298,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
//自定义KEY情况 判断是否需要读取IV
|
//自定义KEY情况 判断是否需要读取IV
|
||||||
if (line.Contains("IV=0x") && ParserConfig.CustomeKey != null && ParserConfig.CustomeIV == null)
|
if (line.Contains("IV=0x") && ParserConfig.CustomeKey != null && ParserConfig.CustomeIV == null)
|
||||||
{
|
{
|
||||||
currentEncryptInfo.Method = ParserConfig.CustomMethod;
|
currentEncryptInfo.Method = ParserConfig.CustomMethod ?? EncryptMethod.AES_128;
|
||||||
currentEncryptInfo.Key = ParserConfig.CustomeKey;
|
currentEncryptInfo.Key = ParserConfig.CustomeKey;
|
||||||
currentEncryptInfo.IV = HexUtil.HexToBytes(iv);
|
currentEncryptInfo.IV = HexUtil.HexToBytes(iv);
|
||||||
}
|
}
|
||||||
|
@ -346,25 +306,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
if (uri != uri_last)
|
if (uri != uri_last)
|
||||||
{
|
{
|
||||||
//解析key
|
//解析key
|
||||||
currentEncryptInfo.Key = await ParseKeyAsync(uri);
|
currentEncryptInfo = ParseKey(method, uri, iv, segIndex);
|
||||||
//加密方式
|
|
||||||
if (Enum.TryParse(method.Replace("-", "_"), out EncryptMethod m))
|
|
||||||
{
|
|
||||||
currentEncryptInfo.Method = m;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentEncryptInfo.Method = EncryptMethod.UNKNOWN;
|
|
||||||
}
|
|
||||||
//没有读取到IV,自己生成
|
|
||||||
if (string.IsNullOrEmpty(iv))
|
|
||||||
{
|
|
||||||
currentEncryptInfo.IV = HexUtil.HexToBytes(Convert.ToString(segIndex, 16).PadLeft(32, '0'));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentEncryptInfo.IV = HexUtil.HexToBytes(iv);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
lastKeyLine = line;
|
lastKeyLine = line;
|
||||||
}
|
}
|
||||||
|
@ -475,22 +417,17 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
||||||
return playlist;
|
return playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<byte[]> ParseKeyAsync(string uri)
|
private EncryptInfo ParseKey(string method, string uriText, string ivText, int segIndex)
|
||||||
{
|
{
|
||||||
if (uri.ToLower().StartsWith("base64:"))
|
foreach (var p in ParserConfig.HLSKeyProcessors)
|
||||||
{
|
{
|
||||||
return Convert.FromBase64String(uri.Substring(7));
|
if (p.CanProcess(method, uriText, ivText, ParserConfig))
|
||||||
|
{
|
||||||
|
return p.Process(method, uriText, ivText, segIndex, ParserConfig);
|
||||||
}
|
}
|
||||||
else if (uri.ToLower().StartsWith("data:text/plain;base64,"))
|
|
||||||
{
|
|
||||||
return Convert.FromBase64String(uri.Substring(23));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var segUrl = PreProcessUrl(ParserUtil.CombineURL(BaseUrl, uri));
|
|
||||||
var bytes = await HTTPUtil.GetBytesAsync(segUrl, ParserConfig.Headers);
|
|
||||||
return bytes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Exception(ResString.keyProcessorNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
public async Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using N_m3u8DL_RE.Common.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
public abstract class ContentProcessor
|
||||||
|
{
|
||||||
|
public abstract bool CanProcess(string rawText, ParserConfig parserConfig);
|
||||||
|
public abstract string Process(string rawText, ParserConfig parserConfig);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 西瓜视频处理
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultDASHContentProcessor : ContentProcessor
|
||||||
|
{
|
||||||
|
public override bool CanProcess(string mpdContent, ParserConfig parserConfig)
|
||||||
|
{
|
||||||
|
if (mpdContent.Contains("<mas:") && !mpdContent.Contains("xmlns:mas"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Process(string mpdContent, ParserConfig parserConfig)
|
||||||
|
{
|
||||||
|
Logger.Debug("Fix xigua mpd...");
|
||||||
|
mpdContent = mpdContent.Replace("<MPD ", "<MPD xmlns:mas=\"urn:marlin:mas:1-0:services:schemas:mpd\" ");
|
||||||
|
|
||||||
|
return mpdContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.Parser.Processor
|
||||||
|
{
|
||||||
|
public class DefaultUrlProcessor : UrlProcessor
|
||||||
|
{
|
||||||
|
public override bool CanProcess(string oriUrl, ParserConfig paserConfig) => true;
|
||||||
|
|
||||||
|
public override string Process(string oriUrl, ParserConfig paserConfig)
|
||||||
|
{
|
||||||
|
if (paserConfig.AppendUrlParams)
|
||||||
|
{
|
||||||
|
Logger.Debug("Before: " + oriUrl);
|
||||||
|
oriUrl += new Regex("\\?.*").Match(paserConfig.Url).Value;
|
||||||
|
Logger.Debug("After: " + oriUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return oriUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
public class DefaultHLSContentProcessor : ContentProcessor
|
||||||
|
{
|
||||||
|
public override bool CanProcess(string rawText, ParserConfig parserConfig) => true;
|
||||||
|
|
||||||
|
public override string Process(string m3u8Content, ParserConfig parserConfig)
|
||||||
|
{
|
||||||
|
var m3u8Url = parserConfig.Url;
|
||||||
|
//央视频回放
|
||||||
|
if (m3u8Url.Contains("tlivecloud-playback-cdn.ysp.cctv.cn") && m3u8Url.Contains("endtime="))
|
||||||
|
{
|
||||||
|
m3u8Content += Environment.NewLine + HLSTags.ext_x_endlist;
|
||||||
|
}
|
||||||
|
|
||||||
|
//IMOOC
|
||||||
|
if (m3u8Url.Contains("imooc.com/"))
|
||||||
|
{
|
||||||
|
//M3u8Content = DecodeImooc.DecodeM3u8(M3u8Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
//iqy
|
||||||
|
if (m3u8Content.StartsWith("{\"payload\""))
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
//针对优酷#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 = new Regex("#EXT-X-DISCONTINUITY\\s+#EXT-X-MAP:URI=\\\"(.*?)\\\",BYTERANGE=\\\"(.*?)\\\"");
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//针对Disney+修正
|
||||||
|
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Url.Contains("media.dssott.com/"))
|
||||||
|
{
|
||||||
|
Regex ykmap = new Regex("#EXT-X-MAP:URI=\\\".*?BUMPER/[\\s\\S]+?#EXT-X-DISCONTINUITY");
|
||||||
|
if (ykmap.IsMatch(m3u8Content))
|
||||||
|
{
|
||||||
|
m3u8Content = m3u8Content.Replace(ykmap.Match(m3u8Content).Value, "#XXX");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//针对AppleTv修正
|
||||||
|
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && (m3u8Url.Contains(".apple.com/") || Regex.IsMatch(m3u8Content, "#EXT-X-MAP.*\\.apple\\.com/")))
|
||||||
|
{
|
||||||
|
//只取加密部分即可
|
||||||
|
Regex ykmap = new Regex("(#EXT-X-KEY:[\\s\\S]*?)(#EXT-X-DISCONTINUITY|#EXT-X-ENDLIST)");
|
||||||
|
if (ykmap.IsMatch(m3u8Content))
|
||||||
|
{
|
||||||
|
m3u8Content = "#EXTM3U\r\n" + ykmap.Match(m3u8Content).Groups[1].Value + "\r\n#EXT-X-ENDLIST";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//修复#EXT-X-KEY与#EXTINF出现次序异常问题
|
||||||
|
if (Regex.IsMatch(m3u8Content, "(#EXTINF.*)(\\s+)(#EXT-X-KEY.*)"))
|
||||||
|
{
|
||||||
|
m3u8Content = Regex.Replace(m3u8Content, "(#EXTINF.*)(\\s+)(#EXT-X-KEY.*)", "$3$2$1");
|
||||||
|
}
|
||||||
|
|
||||||
|
return m3u8Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
|
using N_m3u8DL_RE.Common.Util;
|
||||||
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
|
using N_m3u8DL_RE.Parser.Util;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.Parser.Processor.HLS
|
||||||
|
{
|
||||||
|
public class DefaultHLSKeyProcessor : KeyProcessor
|
||||||
|
{
|
||||||
|
public override bool CanProcess(string method, string uriText, string ivText, ParserConfig paserConfig) => true;
|
||||||
|
|
||||||
|
public override EncryptInfo Process(string method, string uriText, string ivText, int segIndex, ParserConfig parserConfig)
|
||||||
|
{
|
||||||
|
var encryptInfo = new EncryptInfo();
|
||||||
|
|
||||||
|
|
||||||
|
if (uriText.ToLower().StartsWith("base64:"))
|
||||||
|
{
|
||||||
|
encryptInfo.Key = Convert.FromBase64String(uriText.Substring(7));
|
||||||
|
}
|
||||||
|
else if (uriText.ToLower().StartsWith("data:text/plain;base64,"))
|
||||||
|
{
|
||||||
|
encryptInfo.Key = Convert.FromBase64String(uriText.Substring(23));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var segUrl = PreProcessUrl(ParserUtil.CombineURL(parserConfig.BaseUrl, uriText), parserConfig);
|
||||||
|
var bytes = HTTPUtil.GetBytesAsync(segUrl, parserConfig.Headers).Result;
|
||||||
|
encryptInfo.Key = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
//加密方式
|
||||||
|
if (Enum.TryParse(method.Replace("-", "_"), out EncryptMethod m))
|
||||||
|
{
|
||||||
|
encryptInfo.Method = m;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encryptInfo.Method = EncryptMethod.UNKNOWN;
|
||||||
|
}
|
||||||
|
//没有读取到IV,自己生成
|
||||||
|
if (string.IsNullOrEmpty(ivText))
|
||||||
|
{
|
||||||
|
encryptInfo.IV = HexUtil.HexToBytes(Convert.ToString(segIndex, 16).PadLeft(32, '0'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
encryptInfo.IV = HexUtil.HexToBytes(ivText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预处理URL
|
||||||
|
/// </summary>
|
||||||
|
private string PreProcessUrl(string url, ParserConfig parserConfig)
|
||||||
|
{
|
||||||
|
foreach (var p in parserConfig.HLSUrlProcessors)
|
||||||
|
{
|
||||||
|
if (p.CanProcess(url, parserConfig))
|
||||||
|
{
|
||||||
|
url = p.Process(url, parserConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
public abstract class KeyProcessor
|
||||||
|
{
|
||||||
|
public abstract bool CanProcess(string method, string uriText, string ivText, ParserConfig parserConfig);
|
||||||
|
public abstract EncryptInfo Process(string method, string uriText, string ivText, int segIndex, ParserConfig parserConfig);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
public abstract class UrlProcessor
|
||||||
|
{
|
||||||
|
public abstract bool CanProcess(string oriUrl, ParserConfig parserConfig);
|
||||||
|
public abstract string Process(string oriUrl, ParserConfig parserConfig);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
using N_m3u8DL_RE.Common.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Parser.Util;
|
||||||
using N_m3u8DL_RE.Parser.Constants;
|
using N_m3u8DL_RE.Parser.Constants;
|
||||||
using N_m3u8DL_RE.Parser.Extractor;
|
using N_m3u8DL_RE.Parser.Extractor;
|
||||||
using System;
|
using System;
|
||||||
|
@ -10,6 +10,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using N_m3u8DL_RE.Common.Util;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser
|
namespace N_m3u8DL_RE.Parser
|
||||||
{
|
{
|
||||||
|
@ -19,6 +20,11 @@ namespace N_m3u8DL_RE.Parser
|
||||||
private ParserConfig parserConfig = new ParserConfig();
|
private ParserConfig parserConfig = new ParserConfig();
|
||||||
private string rawText;
|
private string rawText;
|
||||||
|
|
||||||
|
public StreamExtractor()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public StreamExtractor(ParserConfig parserConfig)
|
public StreamExtractor(ParserConfig parserConfig)
|
||||||
{
|
{
|
||||||
this.parserConfig = parserConfig;
|
this.parserConfig = parserConfig;
|
||||||
|
|
|
@ -3,11 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.3.32505.426
|
VisualStudioVersion = 17.3.32505.426
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "N_m3u8DL-RE", "N_m3u8DL-RE\N_m3u8DL-RE.csproj", "{E6915BF9-8306-4F62-B357-23430F0D80B5}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "N_m3u8DL-RE", "N_m3u8DL-RE\N_m3u8DL-RE.csproj", "{E6915BF9-8306-4F62-B357-23430F0D80B5}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "N_m3u8DL-RE.Common", "N_m3u8DL-RE.Common\N_m3u8DL-RE.Common.csproj", "{18D8BC30-512C-4A07-BD4C-E96DF5CF6341}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "N_m3u8DL-RE.Common", "N_m3u8DL-RE.Common\N_m3u8DL-RE.Common.csproj", "{18D8BC30-512C-4A07-BD4C-E96DF5CF6341}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "N_m3u8DL-RE.Parser", "N_m3u8DL-RE.Parser\N_m3u8DL-RE.Parser.csproj", "{0DA02925-AF3A-4598-AF01-91AE5539FCA1}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "N_m3u8DL-RE.Parser", "N_m3u8DL-RE.Parser\N_m3u8DL-RE.Parser.csproj", "{0DA02925-AF3A-4598-AF01-91AE5539FCA1}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "N_m3u8DL-RE.Extends", "N_m3u8DL-RE.Extends\N_m3u8DL-RE.Extends.csproj", "{99175570-6FE1-45C0-87BD-D2E1B52A35CC}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -27,6 +29,10 @@ Global
|
||||||
{0DA02925-AF3A-4598-AF01-91AE5539FCA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{0DA02925-AF3A-4598-AF01-91AE5539FCA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{0DA02925-AF3A-4598-AF01-91AE5539FCA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{0DA02925-AF3A-4598-AF01-91AE5539FCA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{0DA02925-AF3A-4598-AF01-91AE5539FCA1}.Release|Any CPU.Build.0 = Release|Any CPU
|
{0DA02925-AF3A-4598-AF01-91AE5539FCA1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{99175570-6FE1-45C0-87BD-D2E1B52A35CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{99175570-6FE1-45C0-87BD-D2E1B52A35CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{99175570-6FE1-45C0-87BD-D2E1B52A35CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{99175570-6FE1-45C0-87BD-D2E1B52A35CC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\N_m3u8DL-RE.Extends\N_m3u8DL-RE.Extends.csproj" />
|
||||||
<ProjectReference Include="..\N_m3u8DL-RE.Parser\N_m3u8DL-RE.Parser.csproj" />
|
<ProjectReference Include="..\N_m3u8DL-RE.Parser\N_m3u8DL-RE.Parser.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
using N_m3u8DL_RE.Common.Log;
|
||||||
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
|
using N_m3u8DL_RE.Parser.Processor;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.Processor
|
||||||
|
{
|
||||||
|
internal class DemoProcessor : ContentProcessor
|
||||||
|
{
|
||||||
|
public override bool CanProcess(string rawText, ParserConfig parserConfig)
|
||||||
|
{
|
||||||
|
return parserConfig.Url.Contains("bitmovin");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Process(string rawText, ParserConfig parserConfig)
|
||||||
|
{
|
||||||
|
Logger.InfoMarkUp("[red]Match bitmovin![/]");
|
||||||
|
return rawText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
|
using N_m3u8DL_RE.Common.Log;
|
||||||
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
|
using N_m3u8DL_RE.Parser.Processor;
|
||||||
|
using N_m3u8DL_RE.Parser.Processor.HLS;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.Processor
|
||||||
|
{
|
||||||
|
internal class DemoProcessor2 : KeyProcessor
|
||||||
|
{
|
||||||
|
public override bool CanProcess(string method, string uriText, string ivText, ParserConfig parserConfig)
|
||||||
|
{
|
||||||
|
return parserConfig.Url.Contains("playertest.longtailvideo.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override EncryptInfo Process(string method, string uriText, string ivText, int segIndex, ParserConfig parserConfig)
|
||||||
|
{
|
||||||
|
Logger.InfoMarkUp("[white on green]My Key Processor![/]");
|
||||||
|
return new DefaultHLSKeyProcessor().Process(method, uriText, ivText, segIndex, parserConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
using N_m3u8DL_RE.Common.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Parser.Util;
|
||||||
using N_m3u8DL_RE.Parser;
|
using N_m3u8DL_RE.Parser;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
@ -10,7 +10,11 @@ using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using N_m3u8DL_RE.Parser.Util;
|
using System.Text.RegularExpressions;
|
||||||
|
using N_m3u8DL_RE.Extends.Subtitle;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using N_m3u8DL_RE.Common.Util;
|
||||||
|
using N_m3u8DL_RE.Processor;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE
|
namespace N_m3u8DL_RE
|
||||||
{
|
{
|
||||||
|
@ -26,18 +30,25 @@ namespace N_m3u8DL_RE
|
||||||
//设置语言
|
//设置语言
|
||||||
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(loc);
|
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(loc);
|
||||||
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(loc);
|
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(loc);
|
||||||
|
//Logger.LogLevel = LogLevel.DEBUG;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//Logger.LogLevel = LogLevel.DEBUG;
|
|
||||||
var config = new ParserConfig();
|
var config = new ParserConfig();
|
||||||
|
//demo1
|
||||||
|
config.DASHContentProcessors.Insert(0, new DemoProcessor());
|
||||||
|
//demo2
|
||||||
|
config.HLSKeyProcessors.Insert(0, new DemoProcessor2());
|
||||||
|
|
||||||
var url = string.Empty;
|
var url = string.Empty;
|
||||||
//url = "http://playertest.longtailvideo.com/adaptive/oceans_aes/oceans_aes.m3u8";
|
url = "http://playertest.longtailvideo.com/adaptive/oceans_aes/oceans_aes.m3u8";
|
||||||
url = "https://bitmovin-a.akamaihd.net/content/MI201109210084_1/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd";
|
//url = "https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/mpds/11331.mpd";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(url))
|
if (string.IsNullOrEmpty(url))
|
||||||
{
|
{
|
||||||
url = AnsiConsole.Ask<string>("Input [green]URL[/]: ");
|
url = AnsiConsole.Ask<string>("请输入 [green]URL[/]: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
//流提取器配置
|
//流提取器配置
|
||||||
|
@ -47,11 +58,6 @@ namespace N_m3u8DL_RE
|
||||||
//解析流信息
|
//解析流信息
|
||||||
var streams = await extractor.ExtractStreamsAsync();
|
var streams = await extractor.ExtractStreamsAsync();
|
||||||
|
|
||||||
if (streams.Count == 0)
|
|
||||||
{
|
|
||||||
throw new Exception("解析失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
//全部媒体
|
//全部媒体
|
||||||
var lists = streams.OrderByDescending(p => p.Bandwidth);
|
var lists = streams.OrderByDescending(p => p.Bandwidth);
|
||||||
//基本流
|
//基本流
|
||||||
|
@ -83,6 +89,8 @@ namespace N_m3u8DL_RE
|
||||||
Logger.InfoMarkUp(item.ToString());
|
Logger.InfoMarkUp(item.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Info("按任意键继续");
|
||||||
|
Console.ReadKey();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue