You've already forked N_m3u8DL-CLI
mirror of
https://github.com/nilaoda/N_m3u8DL-CLI
synced 2025-09-10 12:40:52 +02:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7e916b65fd | ||
![]() |
4ead563fa2 | ||
![]() |
1b387a06e5 | ||
![]() |
6e7b4ac7ea | ||
![]() |
e98c5205d1 | ||
![]() |
d7890dd124 | ||
![]() |
82f2111522 | ||
![]() |
4c3207586f | ||
![]() |
69b411e37c | ||
![]() |
1e8525041f | ||
![]() |
65ae72d4a4 | ||
![]() |
4a4bfae5ab | ||
![]() |
d586dddfcd | ||
![]() |
fca6b3ff6c | ||
![]() |
5d75626a36 | ||
![]() |
a94271c244 | ||
![]() |
c51118dce7 | ||
![]() |
81b2e87bf7 | ||
![]() |
71a9878aaa | ||
![]() |
769fe4e926 | ||
![]() |
1f57ba7c09 |
53
N_m3u8DL-CLI/DecodeHuke88Key.cs
Normal file
53
N_m3u8DL-CLI/DecodeHuke88Key.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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
@@ -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
Reference in New Issue
Block a user