1
mirror of https://github.com/nilaoda/N_m3u8DL-CLI synced 2025-09-10 12:40:52 +02:00

Compare commits

..

21 Commits
2.8.9 ... 2.9.5

Author SHA1 Message Date
nilaoda
7e916b65fd Update changelog.txt 2021-02-01 22:57:37 +08:00
nilaoda
4ead563fa2 修正自定义KEY切存在IV时的隐患 2021-02-01 22:57:27 +08:00
nilaoda
1b387a06e5 update docs 2021-02-01 22:52:07 +08:00
nilaoda
6e7b4ac7ea ddyun识别90mm 2021-02-01 22:42:56 +08:00
nilaoda
e98c5205d1 优化跳过png的算法 2021-02-01 14:18:03 +08:00
nilaoda
d7890dd124 优化跳过png的算法 2021-02-01 14:11:28 +08:00
nilaoda
82f2111522 update 2021-01-24 16:26:32 +08:00
nilaoda
4c3207586f Update MPDParser.cs 2021-01-24 16:24:03 +08:00
nilaoda
69b411e37c fix sub merge bug 2021-01-24 16:22:13 +08:00
nilaoda
1e8525041f Download from DSNP 2021-01-24 16:21:32 +08:00
nilaoda
65ae72d4a4 Update MPDParser.cs 2021-01-18 20:36:43 +08:00
nilaoda
4a4bfae5ab 优化MPD下载行为 2021-01-18 02:00:19 +08:00
nilaoda
d586dddfcd Update changelog.txt 2021-01-18 01:59:31 +08:00
nilaoda
fca6b3ff6c Update changelog.txt 2020-12-29 23:19:39 +08:00
nilaoda
5d75626a36 Update Global.cs 2020-12-29 23:18:16 +08:00
nilaoda
a94271c244 mpd - xigua 2020-12-20 18:53:40 +08:00
nilaoda
c51118dce7 解密huke88 2020-12-20 18:53:02 +08:00
nilaoda
81b2e87bf7 处理同一ID分散在不同Period的情况 2020-12-12 02:07:41 +08:00
nilaoda
71a9878aaa Update changelog.txt 2020-12-06 21:32:48 +08:00
nilaoda
769fe4e926 Update Global.cs 2020-12-06 21:32:38 +08:00
nilaoda
1f57ba7c09 Update Parser.cs 2020-12-06 21:32:29 +08:00
18 changed files with 354 additions and 66 deletions

View File

@@ -0,0 +1,53 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace N_m3u8DL_CLI
{
//https://js.huke88.com/assets/revision/js/plugins/tcplayer/tcplayer.v4.1.min.js?v=930
//https://js.huke88.com/assets/revision/js/plugins/tcplayer/libs/hls.min.0.13.2m.js?v=930
class DecodeHuke88Key
{
private static string[] GetOverlayInfo(string url)
{
var enc = new Regex("eyJ\\w{100,}").Match(url).Value;
var json = Encoding.UTF8.GetString(Convert.FromBase64String(enc));
JObject jObject = JObject.Parse(json);
var key = jObject["overlayKey"].ToString();
var iv = jObject["overlayIv"].ToString();
return new string[] { key, iv };
}
public static string DecodeKey(string url, byte[] data)
{
var info = GetOverlayInfo(url);
var overlayKey = info[0];
var overlayIv = info[1];
var l = new List<byte>();
var c = new List<byte>();
for (int h = 0; h < 16; h++)
{
var f = overlayKey.Substring(2 * h, 2);
var g = overlayIv.Substring(2 * h, 2);
l.Add(Convert.ToByte(f, 16));
c.Add(Convert.ToByte(g, 16));
}
var _lastCipherblock = c.ToArray();
var t = new byte[data.Length];
var r = data;
r = Decrypter.AES128Decrypt(data, l.ToArray(), Decrypter.HexStringToBytes("00000000000000000000000000000000"), CipherMode.CBC, PaddingMode.Zeros);
for (var o = 0; o < 16; o++)
t[o] = (byte)(r[o] ^ _lastCipherblock[o]);
var key = Convert.ToBase64String(t);
return key;
}
}
}

View File

@@ -1,16 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace N_m3u8DL_CLI
{
class Decrypter
{
public static byte[] AES128Decrypt(string filePath, byte[] keyByte, byte[] ivByte, CipherMode mode = CipherMode.CBC)
public static byte[] AES128Decrypt(string filePath, byte[] keyByte, byte[] ivByte, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
{
FileStream fs = new FileStream(filePath, FileMode.Open);
//获取文件大小
@@ -25,14 +21,14 @@ namespace N_m3u8DL_CLI
dcpt.Key = keyByte;
dcpt.IV = ivByte;
dcpt.Mode = mode;
dcpt.Padding = PaddingMode.PKCS7;
dcpt.Padding = padding;
ICryptoTransform cTransform = dcpt.CreateDecryptor();
Byte[] resultArray = cTransform.TransformFinalBlock(inBuff, 0, inBuff.Length);
return resultArray;
}
public static byte[] AES128Decrypt(byte[] encryptedBuff, byte[] keyByte, byte[] ivByte, CipherMode mode = CipherMode.CBC)
public static byte[] AES128Decrypt(byte[] encryptedBuff, byte[] keyByte, byte[] ivByte, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
{
byte[] inBuff = encryptedBuff;
@@ -42,7 +38,7 @@ namespace N_m3u8DL_CLI
dcpt.Key = keyByte;
dcpt.IV = ivByte;
dcpt.Mode = mode;
dcpt.Padding = PaddingMode.PKCS7;
dcpt.Padding = padding;
ICryptoTransform cTransform = dcpt.CreateDecryptor();
Byte[] resultArray = cTransform.TransformFinalBlock(inBuff, 0, inBuff.Length);
@@ -56,7 +52,7 @@ namespace N_m3u8DL_CLI
return new byte[0];
}
if (hexStr.StartsWith("0x") || hexStr.StartsWith("0X"))
if (hexStr.StartsWith("0x") || hexStr.StartsWith("0X"))
{
hexStr = hexStr.Remove(0, 2);
}

View File

@@ -368,6 +368,8 @@ namespace N_m3u8DL_CLI
//有MAP文件一般为mp4采取默认动作
if(File.Exists(DownDir + "\\Part_0\\!MAP.ts"))
MuxFormat = "mp4";
if (isVTT)
MuxFormat = "vtt";
if (Global.AUDIO_TYPE != "")
MuxFormat = Global.AUDIO_TYPE;
@@ -528,6 +530,8 @@ namespace N_m3u8DL_CLI
//有MAP文件一般为mp4采取默认动作
if (File.Exists(DownDir + "\\!MAP.ts"))
MuxFormat = "mp4";
if (isVTT)
MuxFormat = "vtt";
Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir, ".ts"), FFmpeg.OutPutPath + $".{MuxFormat}");
}
else

View File

@@ -32,7 +32,7 @@ namespace N_m3u8DL_CLI
/*===============================================================================*/
static Version ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
static string nowVer = $"{ver.Major}.{ver.Minor}.{ver.Build}";
static string nowDate = "20201202";
static string nowDate = "20210201";
public static void WriteInit()
{
Console.Clear();
@@ -664,13 +664,13 @@ namespace N_m3u8DL_CLI
}
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3])
{
//确定是PNG但是需要手动查询结尾标记(0x60 0x82 0x47)
//确定是PNG但是需要手动查询结尾标记 0x47 出现两次
int skip = 0;
for (int i = 4; i < u.Length - 3; i++)
for (int i = 4; i < u.Length - 188 * 2; i++)
{
if (u[i] == 0x60 && u[i + 1] == 0x82 && u[i + 2] == 0x47)
if (u[i] == 0x47 && u[i + 188] == 0x47 && u[i + 188 + 188] == 0x47)
{
skip = i + 2;
skip = i;
break;
}
}

View File

@@ -61,14 +61,18 @@ namespace N_m3u8DL_CLI
void ExtractInitialization(XmlNode source)
{
var initialization = source.SelectSingleNode("//ns:Initialization", nsMgr);
var initialization = source.SelectSingleNode("ns:Initialization", nsMgr);
if (initialization != null)
{
MultisegmentInfo["InitializationUrl"] = ((XmlElement)initialization).GetAttribute("sourceURL");
if (((XmlElement)initialization).HasAttribute("range"))
{
MultisegmentInfo["InitializationUrl"] += "$$Range=" + ((XmlElement)initialization).GetAttribute("range");
}
}
}
var segmentList = Period.SelectSingleNode("//ns:SegmentList", nsMgr);
var segmentList = Period.SelectSingleNode("ns:SegmentList", nsMgr);
if (segmentList != null)
{
ExtractCommon(segmentList);
@@ -77,7 +81,14 @@ namespace N_m3u8DL_CLI
MultisegmentInfo["SegmentUrls"] = new List<string>();
foreach (XmlElement segment in segmentUrlsE)
{
MultisegmentInfo["SegmentUrls"].Add(segment.GetAttribute("media"));
if (segment.HasAttribute("mediaRange"))
{
MultisegmentInfo["SegmentUrls"].Add("$$Range=" + segment.GetAttribute("mediaRange"));
}
else
{
MultisegmentInfo["SegmentUrls"].Add(segment.GetAttribute("media"));
}
}
}
else
@@ -116,6 +127,10 @@ namespace N_m3u8DL_CLI
/// <returns></returns>
public static string Parse(string downDir, string mpdUrl, string mpdContent, string defaultBase = "")
{
//XiGua
if (mpdContent.Contains("<mas:") && !mpdContent.Contains("xmlns:mas"))
mpdContent = mpdContent.Replace("<MPD ", "<MPD xmlns:mas=\"urn:marlin:mas:1-0:services:schemas:mpd\" ");
XmlDocument mpdDoc = new XmlDocument();
mpdDoc.LoadXml(mpdContent);
@@ -208,6 +223,7 @@ namespace N_m3u8DL_CLI
var bandwidth = IntOrNull(GetAttribute("bandwidth"));
var f = new Dictionary<string, dynamic>
{
["ContentType"] = contentType,
["FormatId"] = representationId,
["ManifestUrl"] = mpdUrl,
["Width"] = IntOrNull(GetAttribute("width")),
@@ -432,6 +448,10 @@ namespace N_m3u8DL_CLI
if (representationMsInfo.ContainsKey("InitializationUrl"))
{
f["InitializationUrl"] = representationMsInfo["InitializationUrl"];
if (f["InitializationUrl"].StartsWith("$$Range"))
{
f["InitializationUrl"] = CombineURL(baseUrl, f["InitializationUrl"]);
}
f["Fragments"] = representationMsInfo["Fragments"];
}
}
@@ -447,7 +467,23 @@ namespace N_m3u8DL_CLI
};
}
formatList.Add(f);
//处理同一ID分散在不同Period的情况
if (formatList.Any(_f => _f["FormatId"] == f["FormatId"] && _f["Width"] == f["Width"] && _f["ContentType"] == f["ContentType"]))
{
for (int i = 0; i < formatList.Count; i++)
{
if (formatList[i]["FormatId"] == f["FormatId"] && formatList[i]["Width"] == f["Width"] && formatList[i]["ContentType"] == f["ContentType"])
{
formatList[i]["Fragments"].AddRange(f["Fragments"]);
break;
}
}
}
else
{
formatList.Add(f);
}
}
}
}
@@ -466,14 +502,14 @@ namespace N_m3u8DL_CLI
var audioLangList = new List<string>();
formatList.ForEach(f =>
{
if (f["Width"] == -1 && !audioLangList.Contains(f["Language"])) audioLangList.Add(f["Language"]);
if (f["ContentType"] == "audio" && !audioLangList.Contains(f["Language"])) audioLangList.Add(f["Language"]);
});
if (audioLangList.Count > 1)
{
string Stringify(Dictionary<string, dynamic> f)
{
var type = f["Width"] == -1 && f["Height"] == -1 ? "Audio" : "Video";
var type = f["ContentType"] == "aduio" ? "Audio" : "Video";
var res = type == "Video" ? $"[{f["Width"]}x{f["Height"]}]" : "";
var id = $"[{f["FormatId"]}] ";
var tbr = $"[{((int)f["Tbr"]).ToString().PadLeft(4)} Kbps] ";
@@ -484,6 +520,7 @@ namespace N_m3u8DL_CLI
return $"{type} => {id}{tbr}{asr}{fps}{lang}{codecs}{res}";
}
var startCursorIndex = LOGGER.CursorIndex;
for (int i = 0; i < formatList.Count; i++)
{
Console.WriteLine("".PadRight(13) + $"[{i.ToString().PadLeft(2)}]. {Stringify(formatList[i])}");
@@ -495,6 +532,12 @@ namespace N_m3u8DL_CLI
var input = Console.ReadLine();
LOGGER.CursorIndex += 2;
Console.CursorVisible = false;
for (int i = startCursorIndex; i < LOGGER.CursorIndex; i++)
{
Console.SetCursorPosition(0, i);
Console.Write("".PadRight(300));
}
LOGGER.CursorIndex = startCursorIndex;
if (!string.IsNullOrEmpty(input))
{
bestVideo = new Dictionary<string, dynamic>() { ["Tbr"] = 0 };
@@ -503,7 +546,7 @@ namespace N_m3u8DL_CLI
{
var n = 0;
int.TryParse(index, out n);
if (formatList[n]["Width"] == -1)
if (formatList[n]["ContentType"] == "audio")
{
bestAudio = formatList[n];
}
@@ -605,7 +648,7 @@ namespace N_m3u8DL_CLI
sb.AppendLine("#CREATED-BY:N_m3u8DL-CLI");
//Video
if (f["Width"] != -1 && f["Height"] != -1)
if (f["ContentType"] != "audio")
{
sb.AppendLine($"#EXT-VIDEO-WIDTH:{f["Width"]}");
sb.AppendLine($"#EXT-VIDEO-HEIGHT:{f["Height"]}");
@@ -615,7 +658,19 @@ namespace N_m3u8DL_CLI
sb.AppendLine($"#EXT-TBR:{f["Tbr"]}");
if (f.ContainsKey("InitializationUrl"))
{
sb.AppendLine($"#EXT-X-MAP:URI=\"{f["InitializationUrl"]}\"");
string initUrl = f["InitializationUrl"];
if (Regex.IsMatch(initUrl, "\\$\\$Range=(\\d+)-(\\d+)"))
{
var match = Regex.Match(initUrl, "\\$\\$Range=(\\d+)-(\\d+)");
string rangeStr = match.Value;
long start = Convert.ToInt64(match.Groups[1].Value);
long end = Convert.ToInt64(match.Groups[2].Value);
sb.AppendLine($"#EXT-X-MAP:URI=\"{initUrl.Replace(rangeStr, "")}\",BYTERANGE=\"{end + 1 - start}@{start}\"");
}
else
{
sb.AppendLine($"#EXT-X-MAP:URI=\"{initUrl}\"");
}
}
sb.AppendLine("#EXT-X-KEY:METHOD=PLZ-KEEP-RAW,URI=\"None\""); //使下载器使用二进制合并
@@ -625,7 +680,19 @@ namespace N_m3u8DL_CLI
var dur = seg.ContainsKey("duration") ? seg["duration"] : 0.0;
var url = seg.ContainsKey("url") ? seg["url"] : seg["path"];
sb.AppendLine($"#EXTINF:{dur.ToString("0.00")}");
sb.AppendLine(url);
if (Regex.IsMatch(url, "\\$\\$Range=(\\d+)-(\\d+)"))
{
var match = Regex.Match(url, "\\$\\$Range=(\\d+)-(\\d+)");
string rangeStr = match.Value;
long start = Convert.ToInt64(match.Groups[1].Value);
long end = Convert.ToInt64(match.Groups[2].Value);
sb.AppendLine($"#EXT-X-BYTERANGE:{end + 1 - start}@{start}");
sb.AppendLine(url.Replace(rangeStr, ""));
}
else
{
sb.AppendLine(url);
}
}
sb.AppendLine("#EXT-X-ENDLIST");
@@ -637,17 +704,19 @@ namespace N_m3u8DL_CLI
{
var best = new Dictionary<string, dynamic>() { ["Tbr"] = 0 };
var bandwidth = best["Tbr"];
var width = 0;
foreach (var f in fs)
{
var w = f["Width"];
var h = f["Height"];
if (w != -1 && h != -1)
if (f["ContentType"] == "video")
{
if (f["Tbr"] > bandwidth)
if (f["Tbr"] > bandwidth && w > width)
{
best = f;
bandwidth = f["Tbr"];
width = w;
}
}
}
@@ -662,9 +731,7 @@ namespace N_m3u8DL_CLI
foreach (var f in fs)
{
var w = f["Width"];
var h = f["Height"];
if (w == -1 && h == -1)
if (f["ContentType"] == "audio")
{
if (f["Tbr"] > bandwidth)
{
@@ -709,6 +776,10 @@ namespace N_m3u8DL_CLI
/// <returns></returns>
static string CombineURL(string baseurl, string url)
{
if (url.StartsWith("$$Range"))
{
return baseurl + url;
}
Uri uri1 = new Uri(baseurl);
Uri uri2 = new Uri(uri1, url);
url = uri2.ToString();

View File

@@ -70,6 +70,7 @@
<Compile Include="CommandLineArgumentParser.cs" />
<Compile Include="Decode51CtoKey.cs" />
<Compile Include="DecodeDdyun.cs" />
<Compile Include="DecodeHuke88Key.cs" />
<Compile Include="DecodeImooc.cs" />
<Compile Include="DecodeNfmovies.cs" />
<Compile Include="Decrypter.cs" />

File diff suppressed because it is too large Load Diff

View File

@@ -299,4 +299,20 @@
- 优化MPD识别方案
- 修复MPD情况下时间戳溢出问题
2020年12月2日
- FIX Language Bug
- FIX Language Bug
2020年12月6日
- 使用手机UA请求气球云密钥服务器
2020年12月12日
- 修复MPD下同一个ID分散在不同Period导致下载不完全问题
2020年12月20日
- 支持解密虎课网
2021年1月18日
- 完善MPD下载相关
- 重新打包多语言资源
2021年1月24日
- 适配Disney+资源
- MPD选择流行为优化
- 修复二进制合并时vtt字幕被合并为ts后缀问题
2021年2月1日
- 修正自定义KEY切存在IV时的隐患
- 优化跳过PNG Header的算法

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long