You've already forked N_m3u8DL-CLI
mirror of
https://github.com/nilaoda/N_m3u8DL-CLI
synced 2025-09-13 22:40:51 +02:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 = "20210124";
|
||||
public static void WriteInit()
|
||||
{
|
||||
Console.Clear();
|
||||
|
@@ -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" />
|
||||
|
@@ -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 = { "", "" };
|
||||
@@ -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 == "")
|
||||
{
|
||||
@@ -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] != "")
|
||||
{
|
||||
@@ -660,7 +778,7 @@ namespace N_m3u8DL_CLI
|
||||
string keyUrl = key[1];
|
||||
if (isQiQiuYun)
|
||||
{
|
||||
string encKey = Encoding.Default.GetString(Global.HttpDownloadFileToBytes(keyUrl, Headers));
|
||||
/*string encKey = Encoding.Default.GetString(Global.HttpDownloadFileToBytes(keyUrl, Headers));
|
||||
var indexs = "0-1-2-3-4-5-6-7-8-10-11-12-14-15-16-18".Split('-');
|
||||
if (encKey.Length == 20)
|
||||
{
|
||||
@@ -740,12 +858,30 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
decKey += encKey[Convert.ToInt32(_i)];
|
||||
}
|
||||
key[1] = Convert.ToBase64String(Encoding.Default.GetBytes(decKey));
|
||||
key[1] = Convert.ToBase64String(Encoding.Default.GetBytes(decKey));*/
|
||||
|
||||
key[1] = Convert.ToBase64String(Global.HttpDownloadFileToBytes(keyUrl, "User-Agent:Mozilla/5.0 (Linux; U; Android 7.0; zh-cn; 15 Plus Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.4 Mobile Safari/537.36"));
|
||||
} //气球云
|
||||
else if (key[1].Contains("imooc.com/"))
|
||||
{
|
||||
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
|
||||
@@ -789,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);
|
||||
|
@@ -299,4 +299,17 @@
|
||||
- 优化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后缀问题
|
Reference in New Issue
Block a user