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

Compare commits

..

18 Commits
2.9.0 ... 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
18 changed files with 348 additions and 64 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 = "20201206";
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" />

View File

@@ -11,6 +11,29 @@ namespace N_m3u8DL_CLI
{
class Parser
{
struct Audio
{
public string Name;
public string Language;
public string Uri;
public string Channels;
public override string ToString()
{
return $"[{Name}] [{Language}] [{(string.IsNullOrEmpty(Channels) ? "" : $"{Channels}ch")}]".Replace("[]", "");
}
}
struct Subtitle
{
public string Name;
public string Language;
public string Uri;
public override string ToString()
{
return $"[{Name}] [{Language}]";
}
}
//存储上一行key的信息如果一样就跳过下载key这一步
private string lastKeyLine = string.Empty;
//METHOD, KEY, IV
@@ -21,9 +44,9 @@ namespace N_m3u8DL_CLI
private string bestUrl = string.Empty;
private string bestUrlAudio = string.Empty;
private string bestUrlSub = string.Empty;
Dictionary<string, string> MEDIA_AUDIO = new Dictionary<string, string>();
Dictionary<string, List<Audio>> MEDIA_AUDIO_GROUP = new Dictionary<string, List<Audio>>(); //外挂音频所有分组信息
private string audioUrl = string.Empty; //音轨地址
Dictionary<string, string> MEDIA_SUB = new Dictionary<string, string>();
Dictionary<string, List<Subtitle>> MEDIA_SUB_GROUP = new Dictionary<string, List<Subtitle>>(); //外挂字幕所有分组信息
private string subUrl = string.Empty; //字幕地址
//存放多轨道的信息
private ArrayList extLists = new ArrayList();
@@ -65,6 +88,8 @@ namespace N_m3u8DL_CLI
JArray segments = new JArray();
JObject segInfo = new JObject();
extLists.Clear();
MEDIA_AUDIO_GROUP.Clear();
MEDIA_SUB_GROUP.Clear();
string m3u8Content = string.Empty;
string m3u8Method = string.Empty;
string[] extMAP = { "", "" };
@@ -85,7 +110,7 @@ namespace N_m3u8DL_CLI
{
if (M3u8Url.Contains("nfmovies.com/hls"))
m3u8Content = DecodeNfmovies.DecryptM3u8(Global.HttpDownloadFileToBytes(M3u8Url, Headers));
else if (M3u8Url.Contains("hls.ddyunp.com/ddyun"))
else if (M3u8Url.Contains("hls.ddyunp.com/ddyun") || M3u8Url.Contains("hls.90mm.me/ddyun"))
m3u8Content = DecodeDdyun.DecryptM3u8(Global.HttpDownloadFileToBytes(DecodeDdyun.GetVaildM3u8Url(M3u8Url), Headers));
else
m3u8Content = Global.GetWebSource(M3u8Url, Headers);
@@ -143,6 +168,16 @@ namespace N_m3u8DL_CLI
}
}
//正对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");
}
}
//如果BaseUrl为空则截取字符串充当
if (BaseUrl == "")
{
@@ -279,8 +314,8 @@ namespace N_m3u8DL_CLI
{
if (m3u8CurrentKey[2] == "" && line.Contains("IV=0x"))
{
var temp = ParseKey(line);
m3u8CurrentKey[2] = temp[2]; //使用m3u8中的IV
var temp = Global.GetTagAttribute(line.Replace(HLSTags.ext_x_key + ":", ""), "IV");
m3u8CurrentKey[2] = temp; //使用m3u8中的IV
}
}
else
@@ -332,12 +367,37 @@ namespace N_m3u8DL_CLI
else if (line.StartsWith(HLSTags.ext_x_i_frame_stream_inf)) ;
else if (line.StartsWith(HLSTags.ext_x_media))
{
if (Global.GetTagAttribute(line, "TYPE") == "AUDIO" && !MEDIA_AUDIO.ContainsKey(Global.GetTagAttribute(line, "GROUP-ID")))
MEDIA_AUDIO.Add(Global.GetTagAttribute(line, "GROUP-ID"), CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI")));
if (Global.GetTagAttribute(line, "TYPE") == "SUBTITLES")
var groupId = Global.GetTagAttribute(line, "GROUP-ID");
if (Global.GetTagAttribute(line, "TYPE") == "AUDIO")
{
if (!MEDIA_SUB.ContainsKey(Global.GetTagAttribute(line, "GROUP-ID")))
MEDIA_SUB.Add(Global.GetTagAttribute(line, "GROUP-ID"), CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI")));
var audio = new Audio();
audio.Channels = Global.GetTagAttribute(line, "CHANNELS");
audio.Language = Global.GetTagAttribute(line, "LANGUAGE");
audio.Name = Global.GetTagAttribute(line, "NAME");
audio.Uri = CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI"));
if (!MEDIA_AUDIO_GROUP.ContainsKey(groupId))
{
MEDIA_AUDIO_GROUP.Add(groupId, new List<Audio>() { audio });
}
else
{
MEDIA_AUDIO_GROUP[groupId].Add(audio);
}
}
else if (Global.GetTagAttribute(line, "TYPE") == "SUBTITLES")
{
var sub = new Subtitle();
sub.Language = Global.GetTagAttribute(line, "LANGUAGE");
sub.Name = Global.GetTagAttribute(line, "NAME");
sub.Uri = CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI"));
if (!MEDIA_SUB_GROUP.ContainsKey(groupId))
{
MEDIA_SUB_GROUP.Add(groupId, new List<Subtitle>() { sub });
}
else
{
MEDIA_SUB_GROUP[groupId].Add(sub);
}
}
}
else if (line.StartsWith(HLSTags.ext_x_playlist_type)) ;
@@ -504,13 +564,71 @@ namespace N_m3u8DL_CLI
jsonM3u8Info.Add("vod", isEndlist);
jsonM3u8Info.Add("targetDuration", targetDuration);
jsonM3u8Info.Add("totalDuration", totalDuration);
if (bestUrlAudio != "" && MEDIA_AUDIO.ContainsKey(bestUrlAudio))
if (audioUrl != "")
jsonM3u8Info.Add("audio", audioUrl);
if (subUrl != "")
jsonM3u8Info.Add("sub", subUrl);
if (bestUrlAudio != "" && MEDIA_AUDIO_GROUP.ContainsKey(bestUrlAudio))
{
jsonM3u8Info.Add("audio", MEDIA_AUDIO[bestUrlAudio]);
if (MEDIA_AUDIO_GROUP[bestUrlAudio].Count == 1)
{
audioUrl = MEDIA_AUDIO_GROUP[bestUrlAudio][0].Uri;
}
//多种音频语言 让用户选择
else
{
var startCursorIndex = LOGGER.CursorIndex;
LOGGER.PrintLine("Found Multiple Language Audio Tracks.", LOGGER.Warning);
for (int i = 0; i < MEDIA_AUDIO_GROUP[bestUrlAudio].Count; i++)
{
Console.WriteLine("".PadRight(13) + $"[{i.ToString().PadLeft(2)}]. {bestUrlAudio} => {MEDIA_AUDIO_GROUP[bestUrlAudio][i]}");
LOGGER.CursorIndex++;
}
Console.CursorVisible = true;
LOGGER.PrintLine("Please Select What You Want.(Up To 1 Track)");
Console.Write("".PadRight(13) + "Enter Number: ");
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;
audioUrl = MEDIA_AUDIO_GROUP[bestUrlAudio][int.Parse(input)].Uri;
}
}
if (bestUrlSub != "" && MEDIA_SUB.ContainsKey(bestUrlSub))
if (bestUrlSub != "" && MEDIA_SUB_GROUP.ContainsKey(bestUrlSub))
{
jsonM3u8Info.Add("sub", MEDIA_SUB[bestUrlSub]);
if (MEDIA_SUB_GROUP[bestUrlSub].Count == 1)
{
subUrl = MEDIA_SUB_GROUP[bestUrlSub][0].Uri;
}
//多种字幕语言 让用户选择
else
{
var startCursorIndex = LOGGER.CursorIndex;
LOGGER.PrintLine("Found Multiple Language Subtitle Tracks.", LOGGER.Warning);
for (int i = 0; i < MEDIA_SUB_GROUP[bestUrlSub].Count; i++)
{
Console.WriteLine("".PadRight(13) + $"[{i.ToString().PadLeft(2)}]. {bestUrlSub} => {MEDIA_SUB_GROUP[bestUrlSub][i]}");
LOGGER.CursorIndex++;
}
Console.CursorVisible = true;
LOGGER.PrintLine("Please Select What You Want.(Up To 1 Track)");
Console.Write("".PadRight(13) + "Enter Number: ");
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;
subUrl = MEDIA_SUB_GROUP[bestUrlSub][int.Parse(input)].Uri;
}
}
if (extMAP[0] != "")
{
@@ -748,6 +866,22 @@ namespace N_m3u8DL_CLI
{
key[1] = DecodeImooc.DecodeKey(Global.GetWebSource(key[1], Headers));
}
else if (key[1] == "https://hls.ventunotech.com/m3u8/pc_videosecurevtnkey.key")
{
string temp = Global.GetWebSource(keyUrl, Headers);
LOGGER.PrintLine(temp);
byte[] tempKey = new byte[16];
for (int d = 0; d < 16; d++)
{
tempKey[d] = Convert.ToByte(temp.Substring(2 * d, 2), 16);
}
key[1] = Convert.ToBase64String(tempKey);
}
else if (key[1].Contains("drm.vod2.myqcloud.com/getlicense"))
{
var temp = Global.HttpDownloadFileToBytes(keyUrl, Headers);
key[1] = DecodeHuke88Key.DecodeKey(key[1], temp);
}
else
{
if (keyUrl.Contains("https://keydeliver.linetv.tw/jurassicPark")) //linetv
@@ -791,13 +925,24 @@ namespace N_m3u8DL_CLI
File.Copy(m3u8SavePath, Path.GetDirectoryName(m3u8SavePath) + "\\master.m3u8", true);
LOGGER.WriteLine("Master List Found");
LOGGER.PrintLine(strings.masterListFound, LOGGER.Warning);
string t = "{" + "\"masterUri\":\"" + M3u8Url + "\","
+ "\"updateTime\":\"" + DateTime.Now.ToString("o") + "\","
+ "\"playLists:\":[" + string.Join(",", extLists.ToArray()) + "]" + "}";
var json = new JObject();
json.Add("masterUri", M3u8Url);
json.Add("updateTime", DateTime.Now.ToString("o"));
json.Add("playLists", JArray.Parse("[" + string.Join(",", extLists.ToArray()) + "]"));
if (MEDIA_AUDIO_GROUP.Keys.Count > 0)
{
var audioGroup = JObject.FromObject(MEDIA_AUDIO_GROUP);
json.Add("audioTracks", audioGroup);
}
if (MEDIA_SUB_GROUP.Keys.Count > 0)
{
var subGroup = JObject.FromObject(MEDIA_SUB_GROUP);
json.Add("subtitleTracks", subGroup);
}
//输出json文件
LOGGER.WriteLine(strings.wrtingMasterMeta);
LOGGER.PrintLine(strings.wrtingMasterMeta);
File.WriteAllText(Path.GetDirectoryName(jsonSavePath) + "\\playLists.json", Global.ConvertJsonString(t));
File.WriteAllText(Path.GetDirectoryName(jsonSavePath) + "\\playLists.json", json.ToString());
LOGGER.WriteLine(strings.selectPlaylist + ": " + bestUrl);
LOGGER.PrintLine(strings.selectPlaylist);
LOGGER.WriteLine(strings.startReParsing);

View File

@@ -301,4 +301,18 @@
2020年12月2日
- FIX Language Bug
2020年12月6日
- 使用手机UA请求气球云密钥服务器
- 使用手机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