You've already forked N_m3u8DL-CLI
mirror of
https://github.com/nilaoda/N_m3u8DL-CLI
synced 2025-09-08 06:00:50 +02:00
Compare commits
74 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0f25cc0ae8 | ||
![]() |
1c6bd688e3 | ||
![]() |
480857cc3b | ||
![]() |
52af9a44a8 | ||
![]() |
84d137b504 | ||
![]() |
a4f1064c81 | ||
![]() |
eac08b12f8 | ||
![]() |
c81c911888 | ||
![]() |
361e901e5b | ||
![]() |
3567f4c4cc | ||
![]() |
2f7b0f7304 | ||
![]() |
000d0db4ae | ||
![]() |
d551ad52c1 | ||
![]() |
c064c83c21 | ||
![]() |
f6ed0f9e4d | ||
![]() |
f65fee94ca | ||
![]() |
ce64a92b0d | ||
![]() |
42790ce540 | ||
![]() |
83d8ca1c8c | ||
![]() |
4b5a64eb98 | ||
![]() |
2b5af09c3b | ||
![]() |
6368adc2ab | ||
![]() |
fcd7840091 | ||
![]() |
d77cb62dff | ||
![]() |
34394c6a2b | ||
![]() |
091cba8555 | ||
![]() |
b02b6b7168 | ||
![]() |
b7408b0599 | ||
![]() |
b83cb35170 | ||
![]() |
ed0a7b71a7 | ||
![]() |
50eae19bf3 | ||
![]() |
cba8f3ea52 | ||
![]() |
7fd93e1232 | ||
![]() |
74a7e3c3ec | ||
![]() |
849d712e11 | ||
![]() |
7544f3a02c | ||
![]() |
0120736c53 | ||
![]() |
e4bde4926c | ||
![]() |
d42cd6a60d | ||
![]() |
175f13adc9 | ||
![]() |
72f1c043b1 | ||
![]() |
a2e2070f5d | ||
![]() |
6c96deb366 | ||
![]() |
2bd900ee5d | ||
![]() |
1261810510 | ||
![]() |
26823dbd7e | ||
![]() |
698699d9fc | ||
![]() |
ebed7fa1e3 | ||
![]() |
73a8348155 | ||
![]() |
5ace0b3a4f | ||
![]() |
5abe889da0 | ||
![]() |
f1070fd1b4 | ||
![]() |
4af82cc7f9 | ||
![]() |
809380b7ab | ||
![]() |
ab57420507 | ||
![]() |
80230f12fe | ||
![]() |
a6c7c0fd8c | ||
![]() |
bd6df6b58c | ||
![]() |
e0a9071d62 | ||
![]() |
5c9bcf72d2 | ||
![]() |
7cf2c12d0c | ||
![]() |
1b35fe2d2c | ||
![]() |
ed3aae1cb9 | ||
![]() |
01c2ecbeb5 | ||
![]() |
9993ec8177 | ||
![]() |
464300c860 | ||
![]() |
45fa58a46f | ||
![]() |
f93ddc7107 | ||
![]() |
05f450fa6d | ||
![]() |
580c374d7b | ||
![]() |
b58dc5d8e0 | ||
![]() |
f50cf7862a | ||
![]() |
fdad68d483 | ||
![]() |
d7aaa5323b |
@@ -1,12 +1,10 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27703.2000
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29215.179
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "N_m3u8DL-CLI", "N_m3u8DL-CLI\N_m3u8DL-CLI.csproj", "{4FB61439-B738-46AC-B3AF-2BF72150D057}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GUI-MainWindow", "GUI-MainWindow\GUI-MainWindow.csproj", "{FE91DB43-1F1F-4119-B808-A7F4795BB4D0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -17,10 +15,6 @@ Global
|
||||
{4FB61439-B738-46AC-B3AF-2BF72150D057}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4FB61439-B738-46AC-B3AF-2BF72150D057}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4FB61439-B738-46AC-B3AF-2BF72150D057}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FE91DB43-1F1F-4119-B808-A7F4795BB4D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FE91DB43-1F1F-4119-B808-A7F4795BB4D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FE91DB43-1F1F-4119-B808-A7F4795BB4D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FE91DB43-1F1F-4119-B808-A7F4795BB4D0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
297
N_m3u8DL-CLI/Decode51CtoKey.cs
Normal file
297
N_m3u8DL-CLI/Decode51CtoKey.cs
Normal file
File diff suppressed because one or more lines are too long
@@ -10,7 +10,7 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
class Decrypter
|
||||
{
|
||||
public static byte[] AES128Decrypt(string filePath, byte[] keyByte, byte[] ivByte)
|
||||
public static byte[] AES128Decrypt(string filePath, byte[] keyByte, byte[] ivByte, CipherMode mode = CipherMode.CBC)
|
||||
{
|
||||
FileStream fs = new FileStream(filePath, FileMode.Open);
|
||||
//获取文件大小
|
||||
@@ -24,7 +24,7 @@ namespace N_m3u8DL_CLI
|
||||
dcpt.KeySize = 128;
|
||||
dcpt.Key = keyByte;
|
||||
dcpt.IV = ivByte;
|
||||
dcpt.Mode = CipherMode.CBC;
|
||||
dcpt.Mode = mode;
|
||||
dcpt.Padding = PaddingMode.PKCS7;
|
||||
|
||||
ICryptoTransform cTransform = dcpt.CreateDecryptor();
|
||||
@@ -32,7 +32,7 @@ namespace N_m3u8DL_CLI
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
public static byte[] AES128Decrypt(byte[] encryptedBuff, byte[] keyByte, byte[] ivByte)
|
||||
public static byte[] AES128Decrypt(byte[] encryptedBuff, byte[] keyByte, byte[] ivByte, CipherMode mode = CipherMode.CBC)
|
||||
{
|
||||
byte[] inBuff = encryptedBuff;
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace N_m3u8DL_CLI
|
||||
dcpt.KeySize = 128;
|
||||
dcpt.Key = keyByte;
|
||||
dcpt.IV = ivByte;
|
||||
dcpt.Mode = CipherMode.CBC;
|
||||
dcpt.Mode = mode;
|
||||
dcpt.Padding = PaddingMode.PKCS7;
|
||||
|
||||
ICryptoTransform cTransform = dcpt.CreateDecryptor();
|
||||
|
@@ -36,7 +36,7 @@ namespace N_m3u8DL_CLI
|
||||
bool externalSub = false; //额外的字幕
|
||||
string externalSubUrl = "";
|
||||
string fflogName = "_ffreport.log";
|
||||
private bool binaryMerge = false;
|
||||
public static bool BinaryMerge = false;
|
||||
private bool noMerge = false;
|
||||
private bool muxFastStart = true;
|
||||
private string muxFormat = "mp4";
|
||||
@@ -51,7 +51,6 @@ namespace N_m3u8DL_CLI
|
||||
public string MuxFormat { get => muxFormat; set => muxFormat = value; }
|
||||
public bool MuxFastStart { get => muxFastStart; set => muxFastStart = value; }
|
||||
public string MuxSetJson { get => muxSetJson; set => muxSetJson = value; }
|
||||
public bool BinaryMerge { get => binaryMerge; set => binaryMerge = value; }
|
||||
public int TimeOut { get => timeOut; set => timeOut = value; }
|
||||
public static double DownloadedSize { get => downloadedSize; set => downloadedSize = value; }
|
||||
public static bool HasSetDir { get => hasSetDir; set => hasSetDir = value; }
|
||||
@@ -171,6 +170,8 @@ namespace N_m3u8DL_CLI
|
||||
sd.SavePath = DownDir + "\\!MAP.tsdownloading";
|
||||
if (File.Exists(sd.SavePath))
|
||||
File.Delete(sd.SavePath);
|
||||
if (File.Exists(DownDir + "\\Part_0\\!MAP.ts"))
|
||||
File.Delete(DownDir + "\\Part_0\\!MAP.ts");
|
||||
LOGGER.PrintLine("下载MAP文件...");
|
||||
sd.Down(); //开始下载
|
||||
}
|
||||
@@ -397,6 +398,10 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
LOGGER.PrintLine("二进制合并...请耐心等待");
|
||||
MuxFormat = "ts";
|
||||
//有MAP文件,一般为mp4,采取默认动作
|
||||
if(File.Exists(DownDir + "\\Part_0\\!MAP.ts"))
|
||||
MuxFormat = "mp4";
|
||||
|
||||
if (Global.AUDIO_TYPE != "")
|
||||
MuxFormat = Global.AUDIO_TYPE;
|
||||
Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir + "\\Part_0", ".ts"), FFmpeg.OutPutPath + $".{MuxFormat}");
|
||||
@@ -409,7 +414,8 @@ namespace N_m3u8DL_CLI
|
||||
//检测是否为MPEG-TS封装,不是的话就转换为TS封装
|
||||
foreach (string s in Global.GetFiles(DownDir + "\\Part_0", ".ts"))
|
||||
{
|
||||
if (!FFmpeg.CheckMPEGTS(s))
|
||||
//跳过有MAP的情况
|
||||
if (!isVTT && !File.Exists(DownDir + "\\Part_0\\!MAP.ts") && !FFmpeg.CheckMPEGTS(s))
|
||||
{
|
||||
//转换
|
||||
LOGGER.PrintLine("将文件转换到 MPEG-TS 封装:" + Path.GetFileName(s));
|
||||
@@ -546,6 +552,9 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
LOGGER.PrintLine("二进制合并...请耐心等待");
|
||||
MuxFormat = "ts";
|
||||
//有MAP文件,一般为mp4,采取默认动作
|
||||
if (File.Exists(DownDir + "\\!MAP.ts"))
|
||||
MuxFormat = "mp4";
|
||||
Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir, ".ts"), FFmpeg.OutPutPath + $".{MuxFormat}");
|
||||
}
|
||||
else
|
||||
@@ -556,7 +565,8 @@ namespace N_m3u8DL_CLI
|
||||
//检测是否为MPEG-TS封装,不是的话就转换为TS封装
|
||||
foreach (string s in Global.GetFiles(DownDir, ".ts"))
|
||||
{
|
||||
if (!FFmpeg.CheckMPEGTS(s))
|
||||
//跳过有MAP的情况
|
||||
if (!isVTT && !File.Exists(DownDir + "\\!MAP.ts") && !FFmpeg.CheckMPEGTS(s))
|
||||
{
|
||||
//转换
|
||||
LOGGER.PrintLine("将文件转换到 MPEG-TS 封装:" + Path.GetFileName(s));
|
||||
|
@@ -72,43 +72,55 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
IsDone = false; //设置为未完成下载
|
||||
|
||||
if (Method == "NONE")
|
||||
if (Method == "NONE" || method.Contains("NOTSUPPORTED"))
|
||||
{
|
||||
LOGGER.PrintLine("<" + SegIndex + " Downloading>");
|
||||
LOGGER.WriteLine("<" + SegIndex + " Downloading>");
|
||||
byte[] segBuff = Global.HttpDownloadFileToBytes(fileUrl, Headers, TimeOut);
|
||||
//byte[] segBuff = Global.WebClientDownloadToBytes(fileUrl, Headers);
|
||||
Global.AppendBytesToFileStreamAndDoNotClose(LiveStream, segBuff);
|
||||
LOGGER.PrintLine("<" + SegIndex + " Complete>\r\n");
|
||||
LOGGER.WriteLine("<" + SegIndex + " Complete>");
|
||||
IsDone = true;
|
||||
}
|
||||
else if (Method == "AES-128")
|
||||
{
|
||||
LOGGER.PrintLine("<" + SegIndex + " Downloading>");
|
||||
LOGGER.WriteLine("<" + SegIndex + " Downloading>");
|
||||
byte[] encryptedBuff = Global.HttpDownloadFileToBytes(fileUrl, Headers, TimeOut);
|
||||
//byte[] encryptedBuff = Global.WebClientDownloadToBytes(fileUrl, Headers);
|
||||
byte[] decryptBuff = Decrypter.AES128Decrypt(
|
||||
byte[] decryptBuff = null;
|
||||
decryptBuff = Decrypter.AES128Decrypt(
|
||||
encryptedBuff,
|
||||
Convert.FromBase64String(Key),
|
||||
Decrypter.HexStringToBytes(Iv)
|
||||
);
|
||||
Global.AppendBytesToFileStreamAndDoNotClose(LiveStream, decryptBuff);
|
||||
LOGGER.PrintLine("<" + SegIndex + " Complete>\r\n");
|
||||
LOGGER.WriteLine("<" + SegIndex + " Complete>");
|
||||
IsDone = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.PrintLine("不支持这种加密方式!", LOGGER.Error);
|
||||
//LOGGER.PrintLine("不支持这种加密方式!", LOGGER.Error);
|
||||
IsDone = true;
|
||||
}
|
||||
if (firstSeg && Global.FileSize(LiveFile) != 0)
|
||||
{
|
||||
LOGGER.STOPLOG = false; //记录日志
|
||||
//LOGGER.STOPLOG = false; //记录日志
|
||||
foreach (string ss in (string[])Global.GetVideoInfo(LiveFile).ToArray(typeof(string)))
|
||||
{
|
||||
LOGGER.WriteLine(ss.Trim());
|
||||
}
|
||||
firstSeg = false;
|
||||
LOGGER.STOPLOG = true; //停止记录日志
|
||||
//LOGGER.STOPLOG = true; //停止记录日志
|
||||
}
|
||||
HLSLiveDownloader.REC_DUR += SegDur;
|
||||
if (HLSLiveDownloader.REC_DUR_LIMIT != -1 && HLSLiveDownloader.REC_DUR >= HLSLiveDownloader.REC_DUR_LIMIT)
|
||||
{
|
||||
LOGGER.PrintLine("录制已到达限定长度", LOGGER.Warning);
|
||||
LOGGER.WriteLine("录制已到达限定长度");
|
||||
Environment.Exit(0); //正常退出
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -167,7 +179,7 @@ namespace N_m3u8DL_CLI
|
||||
if (File.Exists(savePath) && Global.ShouldStop == false)
|
||||
{
|
||||
FileInfo fi = new FileInfo(savePath);
|
||||
if (Method == "NONE")
|
||||
if (Method == "NONE" || method.Contains("NOTSUPPORTED"))
|
||||
{
|
||||
fi.MoveTo(Path.GetDirectoryName(savePath) + "\\" + Path.GetFileNameWithoutExtension(savePath) + ".ts");
|
||||
DownloadManager.DownloadedSize += fi.Length;
|
||||
@@ -179,11 +191,24 @@ namespace N_m3u8DL_CLI
|
||||
//解密
|
||||
try
|
||||
{
|
||||
byte[] decryptBuff = Decrypter.AES128Decrypt(
|
||||
fi.FullName,
|
||||
Convert.FromBase64String(Key),
|
||||
Decrypter.HexStringToBytes(Iv)
|
||||
);
|
||||
byte[] decryptBuff = null;
|
||||
if(fileUrl.Contains(".51cto.com/")) //使用AES-128-ECB模式解密
|
||||
{
|
||||
decryptBuff = Decrypter.AES128Decrypt(
|
||||
fi.FullName,
|
||||
Convert.FromBase64String(Key),
|
||||
Decrypter.HexStringToBytes(Iv),
|
||||
System.Security.Cryptography.CipherMode.ECB
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
decryptBuff = Decrypter.AES128Decrypt(
|
||||
fi.FullName,
|
||||
Convert.FromBase64String(Key),
|
||||
Decrypter.HexStringToBytes(Iv)
|
||||
);
|
||||
}
|
||||
FileStream fs = new FileStream(Path.GetDirectoryName(savePath) + "\\" + Path.GetFileNameWithoutExtension(savePath) + ".ts", FileMode.Create);
|
||||
fs.Write(decryptBuff, 0, decryptBuff.Length);
|
||||
fs.Close();
|
||||
@@ -193,16 +218,12 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOGGER.PrintLine(ex.Message, LOGGER.Error);
|
||||
LOGGER.WriteLineError(ex.Message);
|
||||
Thread.Sleep(3000);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
}
|
||||
else if(File.Exists(fi.FullName)
|
||||
&& Method != "AES-128")
|
||||
{
|
||||
LOGGER.WriteLineError($"Do not support this METHOD: {Method}");
|
||||
LOGGER.PrintLine("不支持这种加密方式!", LOGGER.Error);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.WriteLineError("Something was wrong!");
|
||||
@@ -215,14 +236,14 @@ namespace N_m3u8DL_CLI
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOGGER.WriteLineError(ex.Message);
|
||||
if (ex.Message.Contains("404"))
|
||||
if (ex.Message.Contains("404") || ex.Message.Contains("400"))//(400) 错误的请求,片段过期会提示400错误
|
||||
{
|
||||
IsDone = true;
|
||||
return;
|
||||
}
|
||||
else if (IsLive && count++ < Retry)
|
||||
{
|
||||
Thread.Sleep(5000);
|
||||
Thread.Sleep(2000);//直播一般3-6秒一个片段
|
||||
Down();
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,13 @@ namespace N_m3u8DL_CLI
|
||||
string poster = "", string audioName = "", string title = "",
|
||||
string copyright = "", string comment = "", string encodingTool = "")
|
||||
{
|
||||
//同名文件已存在的共存策略
|
||||
if (File.Exists($"{OutPutPath}.{muxFormat.ToLower()}"))
|
||||
{
|
||||
OutPutPath = Path.Combine(Path.GetDirectoryName(OutPutPath),
|
||||
Path.GetFileName(OutPutPath) + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
|
||||
}
|
||||
|
||||
string command = "-loglevel warning -i concat:\"";
|
||||
string data = string.Empty;
|
||||
string ddpAudio = string.Empty;
|
||||
@@ -86,7 +93,7 @@ namespace N_m3u8DL_CLI
|
||||
if (Global.VIDEO_TYPE == "H264")
|
||||
{
|
||||
Run("ffmpeg",
|
||||
"-loglevel quiet -i \"" + file + "\" -map 0 -c copy -f mpegts -bsf:v h264_mp4toannexb \""
|
||||
"-loglevel quiet -i \"" + file + "\" -map 0 -c copy -copy_unknown -f mpegts -bsf:v h264_mp4toannexb \""
|
||||
+ Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts\"",
|
||||
Path.GetDirectoryName(file));
|
||||
if (File.Exists(Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts"))
|
||||
@@ -98,7 +105,7 @@ namespace N_m3u8DL_CLI
|
||||
else if (Global.VIDEO_TYPE == "H265")
|
||||
{
|
||||
Run("ffmpeg",
|
||||
"-loglevel quiet -i \"" + file + "\" -map 0 -c copy -f mpegts -bsf:v hevc_mp4toannexb \""
|
||||
"-loglevel quiet -i \"" + file + "\" -map 0 -c copy -copy_unknown -f mpegts -bsf:v hevc_mp4toannexb \""
|
||||
+ Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts\"",
|
||||
Path.GetDirectoryName(file));
|
||||
if (File.Exists(Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts"))
|
||||
|
@@ -30,11 +30,12 @@ namespace N_m3u8DL_CLI
|
||||
|
||||
|
||||
/*===============================================================================*/
|
||||
static string nowVer = "2.4.4";
|
||||
static string nowDate = "20191218";
|
||||
static string nowVer = "2.6.2";
|
||||
static string nowDate = "20200417";
|
||||
public static void WriteInit()
|
||||
{
|
||||
Console.Clear();
|
||||
Console.SetCursorPosition(0, 0);
|
||||
Console.BackgroundColor = ConsoleColor.Blue; //设置背景色
|
||||
Console.ForegroundColor = ConsoleColor.White; //设置前景色,即字体颜色
|
||||
Console.WriteLine($"N_m3u8DL-CLI v{nowVer} {nowDate}...");
|
||||
@@ -100,87 +101,93 @@ namespace N_m3u8DL_CLI
|
||||
public static string GetWebSource(String url, string headers = "", int TimeOut = 60000)
|
||||
{
|
||||
string htmlCode = string.Empty;
|
||||
try
|
||||
for(int i = 0; i < 10; i++)
|
||||
{
|
||||
HttpWebRequest webRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
|
||||
webRequest.Method = "GET";
|
||||
if (NoProxy) webRequest.Proxy = null;
|
||||
webRequest.UserAgent = "Mozilla/4.0";
|
||||
webRequest.Headers.Add("Accept-Encoding", "gzip, deflate");
|
||||
webRequest.Timeout = TimeOut; //设置超时
|
||||
webRequest.KeepAlive = false;
|
||||
webRequest.AllowAutoRedirect = true; //自动跳转
|
||||
if (url.Contains("pcvideo") && url.Contains(".titan.mgtv.com"))
|
||||
try
|
||||
{
|
||||
webRequest.UserAgent = "";
|
||||
if (!url.Contains("/internettv/"))
|
||||
webRequest.Referer = "https://player.mgtv.com/mgtv_v6_player/PlayerCore.swf";
|
||||
webRequest.Headers.Add("Cookie", "MQGUID");
|
||||
}
|
||||
//添加headers
|
||||
if (headers != "")
|
||||
{
|
||||
foreach (string att in headers.Split('|'))
|
||||
HttpWebRequest webRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
|
||||
webRequest.Method = "GET";
|
||||
if (NoProxy) webRequest.Proxy = null;
|
||||
webRequest.UserAgent = "Mozilla/4.0";
|
||||
webRequest.Headers.Add("Accept-Encoding", "gzip, deflate");
|
||||
webRequest.Timeout = TimeOut; //设置超时
|
||||
webRequest.KeepAlive = false;
|
||||
webRequest.AllowAutoRedirect = true; //自动跳转
|
||||
if (url.Contains("pcvideo") && url.Contains(".titan.mgtv.com"))
|
||||
{
|
||||
try
|
||||
webRequest.UserAgent = "";
|
||||
if (!url.Contains("/internettv/"))
|
||||
webRequest.Referer = "https://player.mgtv.com/mgtv_v6_player/PlayerCore.swf";
|
||||
webRequest.Headers.Add("Cookie", "MQGUID");
|
||||
}
|
||||
//添加headers
|
||||
if (headers != "")
|
||||
{
|
||||
foreach (string att in headers.Split('|'))
|
||||
{
|
||||
if (att.Split(':')[0].ToLower() == "referer")
|
||||
webRequest.Referer = att.Substring(att.IndexOf(":") + 1);
|
||||
else if (att.Split(':')[0].ToLower() == "user-agent")
|
||||
webRequest.UserAgent = att.Substring(att.IndexOf(":") + 1);
|
||||
else if (att.Split(':')[0].ToLower() == "range")
|
||||
webRequest.AddRange(Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[0], Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[1])));
|
||||
else if (att.Split(':')[0].ToLower() == "accept")
|
||||
webRequest.Accept = att.Substring(att.IndexOf(":") + 1);
|
||||
else
|
||||
webRequest.Headers.Add(att);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.WriteLineError(e.Message);
|
||||
try
|
||||
{
|
||||
if (att.Split(':')[0].ToLower() == "referer")
|
||||
webRequest.Referer = att.Substring(att.IndexOf(":") + 1);
|
||||
else if (att.Split(':')[0].ToLower() == "user-agent")
|
||||
webRequest.UserAgent = att.Substring(att.IndexOf(":") + 1);
|
||||
else if (att.Split(':')[0].ToLower() == "range")
|
||||
webRequest.AddRange(Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[0], Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[1])));
|
||||
else if (att.Split(':')[0].ToLower() == "accept")
|
||||
webRequest.Accept = att.Substring(att.IndexOf(":") + 1);
|
||||
else
|
||||
webRequest.Headers.Add(att);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.WriteLineError(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
|
||||
if (webResponse.ContentEncoding != null
|
||||
&& webResponse.ContentEncoding.ToLower() == "gzip") //如果使用了GZip则先解压
|
||||
{
|
||||
using (Stream streamReceive = webResponse.GetResponseStream())
|
||||
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
|
||||
if (webResponse.ContentEncoding != null
|
||||
&& webResponse.ContentEncoding.ToLower() == "gzip") //如果使用了GZip则先解压
|
||||
{
|
||||
using (var zipStream =
|
||||
new System.IO.Compression.GZipStream(streamReceive, System.IO.Compression.CompressionMode.Decompress))
|
||||
using (Stream streamReceive = webResponse.GetResponseStream())
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(zipStream, Encoding.UTF8))
|
||||
using (var zipStream =
|
||||
new System.IO.Compression.GZipStream(streamReceive, System.IO.Compression.CompressionMode.Decompress))
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(zipStream, Encoding.UTF8))
|
||||
{
|
||||
htmlCode = sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Stream streamReceive = webResponse.GetResponseStream())
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(streamReceive, Encoding.UTF8))
|
||||
{
|
||||
htmlCode = sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Stream streamReceive = webResponse.GetResponseStream())
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(streamReceive, Encoding.UTF8))
|
||||
{
|
||||
htmlCode = sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (webResponse != null)
|
||||
{
|
||||
webResponse.Close();
|
||||
if (webResponse != null)
|
||||
{
|
||||
webResponse.Close();
|
||||
}
|
||||
if (webRequest != null)
|
||||
{
|
||||
webRequest.Abort();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (webRequest != null)
|
||||
catch (Exception e) //捕获所有异常
|
||||
{
|
||||
webRequest.Abort();
|
||||
LOGGER.WriteLineError(e.Message);
|
||||
Thread.Sleep(1000); //1秒后重试
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception e) //捕获所有异常
|
||||
{
|
||||
LOGGER.WriteLineError(e.Message);
|
||||
}
|
||||
|
||||
return htmlCode;
|
||||
}
|
||||
@@ -290,6 +297,12 @@ namespace N_m3u8DL_CLI
|
||||
/// <param name="outputFilePath"></param>
|
||||
public static void CombineMultipleFilesIntoSingleFile(string[] files, string outputFilePath)
|
||||
{
|
||||
//同名文件已存在的共存策略
|
||||
if (File.Exists(outputFilePath))
|
||||
{
|
||||
outputFilePath = Path.Combine(Path.GetDirectoryName(outputFilePath),
|
||||
Path.GetFileNameWithoutExtension(outputFilePath) + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + Path.GetExtension(outputFilePath));
|
||||
}
|
||||
if (files.Length == 1)
|
||||
{
|
||||
FileInfo fi = new FileInfo(files[0]);
|
||||
@@ -672,6 +685,7 @@ namespace N_m3u8DL_CLI
|
||||
p.StartInfo.RedirectStandardOutput = true; //由调用程序获取输出信息
|
||||
p.StartInfo.RedirectStandardError = true; //重定向标准错误输出
|
||||
p.StartInfo.CreateNoWindow = true; //不显示程序窗口
|
||||
p.StartInfo.StandardErrorEncoding = Encoding.UTF8;
|
||||
p.Start();//启动程序
|
||||
p.StandardInput.AutoFlush = true;
|
||||
//获取cmd窗口的输出信息
|
||||
|
@@ -12,15 +12,17 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
class HLSLiveDownloader
|
||||
{
|
||||
public static int REC_DUR_LIMIT = -1; //默认不限制录制时长
|
||||
public static double REC_DUR = 0; //已录制时长
|
||||
private string liveFile = string.Empty;
|
||||
private string jsonFile = string.Empty;
|
||||
private string headers = string.Empty;
|
||||
private string downDir = string.Empty;
|
||||
private FileStream liveStream = null;
|
||||
private int targetduration = 10;
|
||||
private double targetduration = 10;
|
||||
private bool isFirstJson = true;
|
||||
|
||||
|
||||
public double TotalDuration { get; set; }
|
||||
public string Headers { get => headers; set => headers = value; }
|
||||
public string DownDir { get => downDir; set => downDir = value; }
|
||||
public FileStream LiveStream { get => liveStream; set => liveStream = value; }
|
||||
@@ -33,7 +35,7 @@ namespace N_m3u8DL_CLI
|
||||
public void TimerStart()
|
||||
{
|
||||
timer.Enabled = true;
|
||||
timer.Interval = (targetduration - 2) * 1000; //执行间隔时间,单位为毫秒
|
||||
//timer.Interval = (targetduration - 2) * 1000; //执行间隔时间,单位为毫秒
|
||||
timer.Start();
|
||||
timer.Elapsed += new ElapsedEventHandler(UpdateList);
|
||||
UpdateList(timer, new EventArgs()); //立即执行一次
|
||||
@@ -57,7 +59,9 @@ namespace N_m3u8DL_CLI
|
||||
string jsonContent = File.ReadAllText(jsonFile);
|
||||
JObject initJson = JObject.Parse(jsonContent);
|
||||
string m3u8Url = initJson["m3u8"].Value<string>();
|
||||
targetduration = initJson["m3u8Info"]["targetDuration"].Value<int>();
|
||||
targetduration = initJson["m3u8Info"]["targetDuration"].Value<double>();
|
||||
TotalDuration = initJson["m3u8Info"]["totalDuration"].Value<double>();
|
||||
timer.Interval = (TotalDuration - targetduration) * 1000;//设置定时器运行间隔
|
||||
JArray lastSegments = JArray.Parse(initJson["m3u8Info"]["segments"][0].ToString().Trim()); //上次的分段,用于比对新分段
|
||||
ArrayList tempList = new ArrayList(); //所有待下载的列表
|
||||
tempList.Clear();
|
||||
@@ -76,6 +80,7 @@ namespace N_m3u8DL_CLI
|
||||
Parser parser = new Parser();
|
||||
parser.DownDir = Path.GetDirectoryName(jsonFile);
|
||||
parser.M3u8Url = m3u8Url;
|
||||
parser.LiveStream = true;
|
||||
parser.Parse(); //产生新的json文件
|
||||
|
||||
jsonContent = File.ReadAllText(jsonFile);
|
||||
@@ -89,6 +94,8 @@ namespace N_m3u8DL_CLI
|
||||
//Console.WriteLine(seg.ToString());
|
||||
}
|
||||
}
|
||||
if (toDownList.Count > 0)
|
||||
Record();
|
||||
}
|
||||
|
||||
//public void TryDownload()
|
||||
@@ -108,10 +115,9 @@ namespace N_m3u8DL_CLI
|
||||
|
||||
private void Record()
|
||||
{
|
||||
ArrayList temp = toDownList;
|
||||
while(temp.Count != 0)
|
||||
while (toDownList.Count > 0 && (sd.FileUrl != "" ? sd.IsDone : true))
|
||||
{
|
||||
JObject info = JObject.Parse(temp[0].ToString());
|
||||
JObject info = JObject.Parse(toDownList[0].ToString());
|
||||
int index = info["index"].Value<int>();
|
||||
sd.FileUrl = info["segUri"].Value<string>();
|
||||
sd.Method = info["method"].Value<string>();
|
||||
@@ -120,22 +126,20 @@ namespace N_m3u8DL_CLI
|
||||
sd.Key = info["key"].Value<string>();
|
||||
sd.Iv = info["iv"].Value<string>();
|
||||
}
|
||||
sd.TimeOut = 60000;
|
||||
sd.TimeOut = (int)timer.Interval - 1000;//超时时间不超过下次执行时间
|
||||
sd.SegIndex = index;
|
||||
sd.Headers = Headers;
|
||||
sd.SegDur = info["duration"].Value<double>();
|
||||
sd.IsLive = true; //标记为直播
|
||||
sd.LiveFile = LiveFile;
|
||||
sd.LiveStream = LiveStream;
|
||||
sd.Down(); //开始下载
|
||||
while (sd.IsDone != true) ; //忙等待
|
||||
while (sd.IsDone != true) { Thread.Sleep(1); }; //忙等待 Thread.Sleep(1) 可防止cpu 100% 防止电脑风扇狂转
|
||||
if (toDownList.Count > 0)
|
||||
toDownList.RemoveAt(0); //下完删除一项
|
||||
}
|
||||
LOGGER.PrintLine("Waiting...");
|
||||
//不断查找是否有新分段,有的话立即开始下载
|
||||
while (isNewSeg() != true)
|
||||
isNewSeg();
|
||||
Record();
|
||||
LOGGER.PrintLine("Waiting...", LOGGER.Warning);
|
||||
LOGGER.WriteLine("Waiting...");
|
||||
}
|
||||
|
||||
//检测是否有新分片
|
||||
|
@@ -61,6 +61,11 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CursorIndex > 1000)
|
||||
{
|
||||
Console.Clear();
|
||||
CursorIndex = 0;
|
||||
}
|
||||
if (cursorIndex == 0)
|
||||
Console.SetCursorPosition(0, CursorIndex++);
|
||||
else
|
||||
|
@@ -39,7 +39,13 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.JScript" />
|
||||
<Reference Include="Newtonsoft.Json" />
|
||||
<Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>..\packages\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NiL.JS, Version=2.5.1428.0, Culture=neutral, PublicKeyToken=fa941a7c2a4de689, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NiL.JS.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Collections" />
|
||||
@@ -56,6 +62,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="CommandLineArgument.cs" />
|
||||
<Compile Include="CommandLineArgumentParser.cs" />
|
||||
<Compile Include="Decode51CtoKey.cs" />
|
||||
<Compile Include="Decrypter.cs" />
|
||||
<Compile Include="FFmpeg.cs" />
|
||||
<Compile Include="Global.cs" />
|
||||
@@ -73,6 +80,7 @@
|
||||
<None Include="App.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="bin\Debug\Newtonsoft.Json.dll" />
|
||||
|
@@ -2,15 +2,10 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
@@ -29,6 +24,7 @@ namespace N_m3u8DL_CLI
|
||||
private string downName = string.Empty;
|
||||
private string keyFile = string.Empty;
|
||||
private string keyBase64 = string.Empty;
|
||||
private bool liveStream = false;
|
||||
private long bestBandwidth = 0;
|
||||
private string bestUrl = string.Empty;
|
||||
private string bestUrlAudio = string.Empty;
|
||||
@@ -63,6 +59,7 @@ namespace N_m3u8DL_CLI
|
||||
public static string DurEnd { get => durEnd; set => durEnd = value; }
|
||||
public string KeyFile { get => keyFile; set => keyFile = value; }
|
||||
public string KeyBase64 { get => keyBase64; set => keyBase64 = value; }
|
||||
public bool LiveStream { get => liveStream; set => liveStream = value; }
|
||||
|
||||
public void Parse()
|
||||
{
|
||||
@@ -91,11 +88,16 @@ namespace N_m3u8DL_CLI
|
||||
|
||||
|
||||
//获取m3u8内容
|
||||
|
||||
LOGGER.PrintLine("获取m3u8内容", LOGGER.Warning);
|
||||
if (!liveStream)
|
||||
LOGGER.PrintLine("获取m3u8内容", LOGGER.Warning);
|
||||
|
||||
if (M3u8Url.StartsWith("http"))
|
||||
m3u8Content = Global.GetWebSource(M3u8Url, headers);
|
||||
else if (M3u8Url.StartsWith("file:"))
|
||||
{
|
||||
Uri t = new Uri(M3u8Url);
|
||||
m3u8Content = File.ReadAllText(t.LocalPath);
|
||||
}
|
||||
else if (File.Exists(M3u8Url))
|
||||
{
|
||||
m3u8Content = File.ReadAllText(M3u8Url);
|
||||
@@ -111,6 +113,9 @@ namespace N_m3u8DL_CLI
|
||||
if (m3u8Content.Contains("qiqiuyun.net/") || m3u8Content.Contains("aliyunedu.net/") || m3u8Content.Contains("qncdn.edusoho.net/")) //气球云
|
||||
isQiQiuYun = true;
|
||||
|
||||
if (M3u8Url.Contains("tlivecloud-playback-cdn.ysp.cctv.cn") && M3u8Url.Contains("endtime="))
|
||||
isEndlist = true;
|
||||
|
||||
//输出m3u8文件
|
||||
File.WriteAllText(m3u8SavePath, m3u8Content);
|
||||
|
||||
@@ -123,8 +128,11 @@ namespace N_m3u8DL_CLI
|
||||
BaseUrl = GetBaseUrl(M3u8Url, headers);
|
||||
}
|
||||
|
||||
LOGGER.WriteLine("Parsing Content");
|
||||
LOGGER.PrintLine("解析m3u8内容");
|
||||
if (!liveStream)
|
||||
{
|
||||
LOGGER.WriteLine("Parsing Content");
|
||||
LOGGER.PrintLine("解析m3u8内容");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(keyBase64))
|
||||
{
|
||||
@@ -191,7 +199,7 @@ namespace N_m3u8DL_CLI
|
||||
//解析定义的分段长度
|
||||
else if (line.StartsWith(HLSTags.ext_x_targetduration))
|
||||
{
|
||||
targetDuration = Convert.ToInt32(line.Replace(HLSTags.ext_x_targetduration + ":", "").Trim());
|
||||
targetDuration = Convert.ToInt32(Convert.ToDouble(line.Replace(HLSTags.ext_x_targetduration + ":", "").Trim()));
|
||||
}
|
||||
//解析起始编号
|
||||
else if (line.StartsWith(HLSTags.ext_x_media_sequence))
|
||||
@@ -210,10 +218,10 @@ namespace N_m3u8DL_CLI
|
||||
segments = (JArray)parts[parts.Count - 1];
|
||||
parts.RemoveAt(parts.Count - 1);
|
||||
hasAd = false;
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
//常规情况的#EXT-X-DISCONTINUITY标记,新建part
|
||||
if (!hasAd && segments.Count > 1)
|
||||
if (!hasAd && segments.Count > 1)
|
||||
{
|
||||
parts.Add(segments);
|
||||
segments = new JArray();
|
||||
@@ -225,7 +233,7 @@ namespace N_m3u8DL_CLI
|
||||
else if (line.StartsWith(HLSTags.ext_x_version)) ;
|
||||
else if (line.StartsWith(HLSTags.ext_x_allow_cache)) ;
|
||||
//解析KEY
|
||||
else if (line.StartsWith(HLSTags.ext_x_key) && string.IsNullOrEmpty(keyFile) && string.IsNullOrEmpty(keyBase64))
|
||||
else if (line.StartsWith(HLSTags.ext_x_key) && string.IsNullOrEmpty(keyFile) && string.IsNullOrEmpty(keyBase64))
|
||||
{
|
||||
m3u8CurrentKey = ParseKey(line);
|
||||
//存储为上一行的key信息
|
||||
@@ -276,7 +284,10 @@ namespace N_m3u8DL_CLI
|
||||
if (Global.GetTagAttribute(line, "TYPE") == "AUDIO")
|
||||
MEDIA_AUDIO.Add(Global.GetTagAttribute(line, "GROUP-ID"), CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI")));
|
||||
if (Global.GetTagAttribute(line, "TYPE") == "SUBTITLES")
|
||||
MEDIA_SUB.Add(Global.GetTagAttribute(line, "GROUP-ID"), CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI")));
|
||||
{
|
||||
if (!MEDIA_SUB.ContainsKey(Global.GetTagAttribute(line, "GROUP-ID")))
|
||||
MEDIA_SUB.Add(Global.GetTagAttribute(line, "GROUP-ID"), CombineURL(BaseUrl, Global.GetTagAttribute(line, "URI")));
|
||||
}
|
||||
}
|
||||
else if (line.StartsWith(HLSTags.ext_x_playlist_type)) ;
|
||||
else if (line.StartsWith(HLSTags.ext_i_frames_only))
|
||||
@@ -298,10 +309,22 @@ namespace N_m3u8DL_CLI
|
||||
//#EXT-X-MAP
|
||||
else if (line.StartsWith(HLSTags.ext_x_map))
|
||||
{
|
||||
extMAP[0] = Global.GetTagAttribute(line, "URI");
|
||||
if (line.Contains("BYTERANGE"))
|
||||
extMAP[1] = Global.GetTagAttribute(line, "BYTERANGE");
|
||||
if (!extMAP[0].StartsWith("http")) extMAP[0] = CombineURL(BaseUrl, extMAP[0]);
|
||||
if (extMAP[0] == "")
|
||||
{
|
||||
extMAP[0] = Global.GetTagAttribute(line, "URI");
|
||||
if (line.Contains("BYTERANGE"))
|
||||
extMAP[1] = Global.GetTagAttribute(line, "BYTERANGE");
|
||||
if (!extMAP[0].StartsWith("http")) extMAP[0] = CombineURL(BaseUrl, extMAP[0]);
|
||||
}
|
||||
//遇到了其他的map,说明已经不是一个视频了,全部丢弃即可
|
||||
else
|
||||
{
|
||||
if (segments.Count > 0)
|
||||
parts.Add(segments);
|
||||
segments = new JArray();
|
||||
isEndlist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (line.StartsWith(HLSTags.ext_x_start)) ;
|
||||
//评论行不解析
|
||||
@@ -312,7 +335,7 @@ namespace N_m3u8DL_CLI
|
||||
else if (expectSegment)
|
||||
{
|
||||
segUrl = CombineURL(BaseUrl, line);
|
||||
if (M3u8Url.Contains("akamaized.net") && M3u8Url.Contains("?__gda__"))
|
||||
if (M3u8Url.Contains("?__gda__"))
|
||||
{
|
||||
segUrl += new Regex("\\?__gda__.*").Match(M3u8Url).Value;
|
||||
}
|
||||
@@ -342,7 +365,7 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
string listUrl;
|
||||
listUrl = CombineURL(BaseUrl, line);
|
||||
if (M3u8Url.Contains("akamaized.net") && M3u8Url.Contains("?__gda__"))
|
||||
if (M3u8Url.Contains("?__gda__"))
|
||||
{
|
||||
listUrl += new Regex("\\?__gda__.*").Match(M3u8Url).Value;
|
||||
}
|
||||
@@ -530,10 +553,13 @@ namespace N_m3u8DL_CLI
|
||||
jsonM3u8Info.Add("segments", parts);
|
||||
jsonResult.Add("m3u8Info", jsonM3u8Info);
|
||||
|
||||
|
||||
|
||||
//输出JSON文件
|
||||
LOGGER.WriteLine("Writing Json: [meta.json]");
|
||||
LOGGER.PrintLine("写出meta.json");
|
||||
if (!liveStream)
|
||||
{
|
||||
LOGGER.WriteLine("Writing Json: [meta.json]");
|
||||
LOGGER.PrintLine("写出meta.json");
|
||||
}
|
||||
File.WriteAllText(jsonSavePath, jsonResult.ToString());
|
||||
//检测是否为master list
|
||||
MasterListCheck();
|
||||
@@ -551,6 +577,12 @@ namespace N_m3u8DL_CLI
|
||||
//存在加密
|
||||
if (m != "")
|
||||
{
|
||||
if (m != "AES-128")
|
||||
{
|
||||
LOGGER.PrintLine($"不支持{m}加密方式,将不被处理,且强制开启二进制合并", LOGGER.Error);
|
||||
DownloadManager.BinaryMerge = true;
|
||||
return new string[] { $"{m}(NOTSUPPORTED)", "", "" };
|
||||
}
|
||||
//METHOD
|
||||
key[0] = m;
|
||||
//URI
|
||||
@@ -665,7 +697,17 @@ namespace N_m3u8DL_CLI
|
||||
else
|
||||
{
|
||||
string keyUrl = CombineURL(BaseUrl, key[1]);
|
||||
key[1] = Convert.ToBase64String(Global.HttpDownloadFileToBytes(keyUrl, Headers));
|
||||
if (keyUrl.Contains("edu.51cto.com")) //51cto
|
||||
{
|
||||
keyUrl = keyUrl + "&sign=" + Global.GetTimeStamp(false);
|
||||
string lessonId = Global.GetQueryString("lesson_id", keyUrl);
|
||||
var encodeKey = Encoding.UTF8.GetString(Global.HttpDownloadFileToBytes(keyUrl, Headers));
|
||||
key[1] = Decode51CtoKey.GetDecodeKey(encodeKey, lessonId);
|
||||
}
|
||||
else
|
||||
{
|
||||
key[1] = Convert.ToBase64String(Global.HttpDownloadFileToBytes(keyUrl, Headers));
|
||||
}
|
||||
}
|
||||
}
|
||||
//IV
|
||||
@@ -760,7 +802,7 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
if (!isQiQiuYun && Global.Get302(m3u8url, headers) != m3u8url)
|
||||
m3u8url = Global.Get302(m3u8url, headers);
|
||||
string url = m3u8url;
|
||||
string url = Global.Get302(m3u8url);
|
||||
if (url.Contains("?"))
|
||||
url = url.Remove(url.LastIndexOf('?'));
|
||||
url = url.Substring(0, url.LastIndexOf('/') + 1);
|
||||
|
@@ -5,7 +5,9 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
@@ -212,6 +214,56 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
/// - 增加杜比视界识别场景
|
||||
/// - 修复part大于1时读取json混流文件的严重错误
|
||||
/// - 自动去除优酷的广告分片及前情提要
|
||||
/// - 修复腾讯视频HDR10视频下载合并异常问题
|
||||
/// 2020年1月26日
|
||||
/// - 在央视频回看链接且有endtime参数的情况下,不识别为直播流
|
||||
/// 2020年1月29日
|
||||
/// - 修复识别大师列表的bug (多个字幕同一个GROUP-ID)
|
||||
/// - 修复vtt字幕无法正常合并的bug
|
||||
/// 2020年1月31日
|
||||
/// - ?__gda__行为优化
|
||||
/// 2020年2月1日
|
||||
/// - 修复bug
|
||||
/// - 支援twitcasting下载
|
||||
/// 2020年2月3日
|
||||
/// - 解密异常则退出程序
|
||||
/// - 通过json下载时若已存在文件则覆盖
|
||||
/// 2020年2月18日
|
||||
/// - 修正获取BaseUrl的BUG
|
||||
/// - 重新打包dll
|
||||
/// 2020年2月23日
|
||||
/// - 不支持的加密方式将标记为NOTSUPPORTED并强制启用二进制合并
|
||||
/// - 启用二进制合并的情况下,如果m3u8文件中存在map文件,则合并为mp4格式
|
||||
/// 2020年2月24日
|
||||
/// - 直播流录制优化逻辑,避免忙等待
|
||||
/// - 直播Waiting时,不再输出Parser内容
|
||||
/// - 直播录制的日志记录
|
||||
/// - 增加新的选项--liveRecDur限制直播录制时长
|
||||
/// 2020年2月27日
|
||||
/// - 细节bug修复
|
||||
/// 2020年2月28日
|
||||
/// - 修复本地masterList的读取问题
|
||||
/// - 在程序目录下创建NO_UPDATE文件可以禁止启动时检测更新
|
||||
/// 2020年2月29日
|
||||
/// - 识别#EXT-X-TARGETDURATION时,支持非整数
|
||||
/// 2020年3月2日
|
||||
/// - 支持51cto的key自动解密
|
||||
/// - 请求m3u8内容时,有10次自动重试
|
||||
/// - 直播下载自动设置请求分段文件时间间隔
|
||||
/// - 修复网络断线一直Downloading及cpu 100%
|
||||
/// - 加入savename参数仍可读取N_m3u8DL-CLI.args.txt
|
||||
/// - 直播下载跳过响应码为400的片段
|
||||
/// 2020年3月3日
|
||||
/// - 修复输出太长只在最后一行显示的问题
|
||||
/// 2020年3月4日
|
||||
/// - 只认第一个"#EXT-X-MAP", 其余的全部丢弃
|
||||
/// - 逻辑优化
|
||||
/// 2020年3月5日
|
||||
/// - 增加同名文件合并时共存策略
|
||||
/// 2020年4月17日
|
||||
/// - 优化异常捕获
|
||||
/// - 控制台输出设置为UTF-8
|
||||
/// - 细节优化
|
||||
/// </summary>
|
||||
///
|
||||
|
||||
@@ -241,9 +293,18 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
SetConsoleCtrlHandler(cancelHandler, true);
|
||||
ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
|
||||
Console.OutputEncoding = Encoding.GetEncoding("gbk");
|
||||
|
||||
try
|
||||
{
|
||||
//goto httplitsen;
|
||||
@@ -284,12 +345,15 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
|
||||
HasFFmpeg:
|
||||
Global.WriteInit();
|
||||
Thread checkUpdate = new Thread(() =>
|
||||
{
|
||||
Global.CheckUpdate();
|
||||
});
|
||||
checkUpdate.IsBackground = true;
|
||||
checkUpdate.Start();
|
||||
if (!File.Exists(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "NO_UPDATE")))
|
||||
{
|
||||
Thread checkUpdate = new Thread(() =>
|
||||
{
|
||||
Global.CheckUpdate();
|
||||
});
|
||||
checkUpdate.IsBackground = true;
|
||||
checkUpdate.Start();
|
||||
}
|
||||
|
||||
int maxThreads = Environment.ProcessorCount;
|
||||
int minThreads = 16;
|
||||
@@ -302,7 +366,6 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
string muxSetJson = "MUXSETS.json";
|
||||
string workDir = CURRENT_PATH + "\\Downloads";
|
||||
bool muxFastStart = false;
|
||||
bool binaryMerge = false;
|
||||
bool delAfterDone = false;
|
||||
bool parseOnly = false;
|
||||
bool noMerge = false;
|
||||
@@ -337,6 +400,7 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
--useKeyFile File 使用外部16字节文件定义AES-128解密KEY
|
||||
--useKeyBase64 Base64String 使用Base64字符串定义AES-128解密KEY
|
||||
--downloadRange Range 仅下载视频的一部分分片或长度
|
||||
--liveRecDur HH:MM:SS 直播录制时,达到此长度自动退出软件
|
||||
--stopSpeed Number 当速度低于此值时,重试(单位为KB/s)
|
||||
--maxSpeed Number 设置下载速度上限(单位为KB/s)
|
||||
--enableDelAfterDone 开启下载后删除临时文件夹的功能
|
||||
@@ -347,7 +411,7 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
--disableDateInfo 关闭混流中的日期写入
|
||||
--noMerge 禁用自动合并
|
||||
--noProxy 不自动使用系统代理
|
||||
--disableIntegrityCheck 不检测分片数量是否完整");
|
||||
--disableIntegrityCheck 不检测分片数量是否完整");
|
||||
return;
|
||||
}
|
||||
if (arguments.Has("--enableDelAfterDone"))
|
||||
@@ -360,7 +424,7 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
}
|
||||
if (arguments.Has("--enableBinaryMerge"))
|
||||
{
|
||||
binaryMerge = true;
|
||||
DownloadManager.BinaryMerge = true;
|
||||
}
|
||||
if (arguments.Has("--disableDateInfo"))
|
||||
{
|
||||
@@ -440,6 +504,19 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
{
|
||||
timeOut = Convert.ToInt32(arguments.Get("--timeOut").Next);
|
||||
}
|
||||
if (arguments.Has("--liveRecDur"))
|
||||
{
|
||||
//时间码
|
||||
Regex reg2 = new Regex(@"(\d+):(\d+):(\d+)");
|
||||
var t = arguments.Get("--liveRecDur").Next;
|
||||
if (reg2.IsMatch(t))
|
||||
{
|
||||
int HH = Convert.ToInt32(reg2.Match(t).Groups[1].Value);
|
||||
int MM = Convert.ToInt32(reg2.Match(t).Groups[2].Value);
|
||||
int SS = Convert.ToInt32(reg2.Match(t).Groups[3].Value);
|
||||
HLSLiveDownloader.REC_DUR_LIMIT = SS + MM * 60 + HH * 60 * 60;
|
||||
}
|
||||
}
|
||||
if (arguments.Has("--downloadRange"))
|
||||
{
|
||||
string p = arguments.Get("--downloadRange").Next;
|
||||
@@ -476,11 +553,18 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
}
|
||||
|
||||
//如果只有URL,没有附加参数,则尝试解析配置文件
|
||||
if (args.Length == 1)
|
||||
if (args.Length == 1 || (args.Length == 3 && args[1].ToLower() == "--savename"))
|
||||
{
|
||||
if (File.Exists(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "N_m3u8DL-CLI.args.txt")))
|
||||
{
|
||||
args = Global.ParseArguments($"\"{args[0]}\"" + File.ReadAllText(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "N_m3u8DL-CLI.args.txt"))).ToArray(); //解析命令行
|
||||
if (args.Length == 3)
|
||||
{
|
||||
args = Global.ParseArguments($"\"{args[0]}\" {args[1]} {args[2]} " + File.ReadAllText(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "N_m3u8DL-CLI.args.txt"))).ToArray(); //解析命令行
|
||||
}
|
||||
else
|
||||
{
|
||||
args = Global.ParseArguments($"\"{args[0]}\" " + File.ReadAllText(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "N_m3u8DL-CLI.args.txt"))).ToArray(); //解析命令行
|
||||
}
|
||||
goto parseArgs;
|
||||
}
|
||||
}
|
||||
@@ -517,6 +601,11 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
fileName = Global.GetUrlFileName(testurl) + "_" + DateTime.Now.ToString("yyyyMMddHHmmss");
|
||||
|
||||
|
||||
if (testurl.Contains("twitcasting") && testurl.Contains("/fmp4/"))
|
||||
{
|
||||
DownloadManager.BinaryMerge = true;
|
||||
}
|
||||
|
||||
//优酷DRM设备更改
|
||||
/*if (testurl.Contains("playlist/m3u8"))
|
||||
{
|
||||
@@ -559,7 +648,7 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
{
|
||||
if (!Directory.Exists(Path.Combine(workDir, fileName)))//若文件夹不存在则新建文件夹
|
||||
Directory.CreateDirectory(Path.Combine(workDir, fileName)); //新建文件夹
|
||||
File.Copy(testurl, Path.Combine(Path.Combine(workDir, fileName), "meta.json"));
|
||||
File.Copy(testurl, Path.Combine(Path.Combine(workDir, fileName), "meta.json"), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -615,7 +704,6 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
md.NoMerge = noMerge;
|
||||
md.DownName = fileName;
|
||||
md.DelAfterDone = delAfterDone;
|
||||
md.BinaryMerge = binaryMerge;
|
||||
md.MuxFormat = "mp4";
|
||||
md.RetryCount = retryCount;
|
||||
md.MuxSetJson = muxSetJson;
|
||||
@@ -628,8 +716,8 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
LOGGER.WriteLine("Living Stream Found");
|
||||
LOGGER.WriteLine("Start Recording");
|
||||
LOGGER.PrintLine("识别为直播流, 开始录制");
|
||||
LOGGER.STOPLOG = true; //停止记录日志
|
||||
//开辟文件流,且不关闭。(便于播放器不断读取文件)
|
||||
//LOGGER.STOPLOG = true; //停止记录日志
|
||||
//开辟文件流,且不关闭。(便于播放器不断读取文件)
|
||||
string LivePath = Path.Combine(Directory.GetParent(parser.DownDir).FullName
|
||||
, DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + "_" + fileName + ".ts");
|
||||
FileStream outputStream = new FileStream(LivePath, FileMode.Append);
|
||||
@@ -649,6 +737,7 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
LOGGER.WriteLineError("Download Failed");
|
||||
LOGGER.PrintLine("下载失败, 程序退出", LOGGER.Error);
|
||||
Console.CursorVisible = true;
|
||||
Thread.Sleep(3000);
|
||||
Environment.Exit(-1);
|
||||
//Console.Write("按任意键继续..."); Console.ReadKey(); return;
|
||||
}
|
||||
|
4
N_m3u8DL-CLI/packages.config
Normal file
4
N_m3u8DL-CLI/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NiL.JS" version="2.5.1428" targetFramework="net46" />
|
||||
</packages>
|
61
README_ENG.md
Normal file
61
README_ENG.md
Normal file
@@ -0,0 +1,61 @@
|
||||
```
|
||||
|
||||
███╗ ██╗ ███╗ ███╗██████╗ ██╗ ██╗ █████╗ ██████╗ ██╗ ██████╗██╗ ██╗
|
||||
████╗ ██║ ████╗ ████║╚════██╗██║ ██║██╔══██╗██╔══██╗██║ ██╔════╝██║ ██║
|
||||
██╔██╗ ██║ ██╔████╔██║ █████╔╝██║ ██║╚█████╔╝██║ ██║██║█████╗██║ ██║ ██║
|
||||
██║╚██╗██║ ██║╚██╔╝██║ ╚═══██╗██║ ██║██╔══██╗██║ ██║██║╚════╝██║ ██║ ██║
|
||||
██║ ╚████║███████╗██║ ╚═╝ ██║██████╔╝╚██████╔╝╚█████╔╝██████╔╝███████╗ ╚██████╗███████╗██║
|
||||
╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚════╝ ╚═════╝ ╚══════╝ ╚═════╝╚══════╝╚═╝
|
||||
|
||||
```
|
||||
This is a m3u8 downloader.
|
||||
## Summary
|
||||
Supports:
|
||||
* Auto deceypt for `AES-128-CBC`
|
||||
* `Master List`
|
||||
* Live stream recording(`BETA`)
|
||||
* Customize HTTP headers
|
||||
* Auto merge clips(Binary or ffmpeg)
|
||||
* Select save clip by `time code` or `index`
|
||||
* Network driver on Windows OS
|
||||
* Alternative audio/video track
|
||||
* Mux without video track
|
||||
* Auto use system proxy
|
||||
* Optimization for Chinese streaming platform
|
||||
|
||||

|
||||
|
||||
## GUI
|
||||
* Easy-to-use `GUI`
|
||||
|
||||
## Options
|
||||
```
|
||||
N_m3u8DL-CLI.exe <URL|JSON|FILE> [OPTIONS]
|
||||
|
||||
--workDir Directory Set work dir (Video will be here)
|
||||
--saveName Filename Set save name(Exclude extention)
|
||||
--baseUrl BaseUrl Set Baseurl
|
||||
--headers headers Set HTTP headers,format: key:value user | split all key&value
|
||||
--maxThreads Thread Set max thread(default: 32)
|
||||
--minThreads Thread Set min thread(default: 16)
|
||||
--retryCount Count Set retry times(default: 15)
|
||||
--timeOut Sec Set timeout for http request(second,default: 10)
|
||||
--muxSetJson File Set a json file for mux
|
||||
--useKeyFile File Use 16 bytes file as KEY for AES-128 decryption
|
||||
--useKeyBase64 Base64String Use Base64 String as KEY for AES-128 decryption
|
||||
--downloadRange Range Set range for a video
|
||||
--stopSpeed Number Speed below this, retry(KB/s)
|
||||
--maxSpeed Number Set max download speed(KB/s)
|
||||
--enableDelAfterDone Enable delete clips after download completed
|
||||
--enableMuxFastStart Enable fast start for mp4
|
||||
--enableBinaryMerge Enable use binary merge instead ffmpeg
|
||||
--enableParseOnly Enable parse mode
|
||||
--enableAudioOnly Enable only audio track when mux use ffmpeg
|
||||
--disableDateInfo Disable write date info when mux use ffmpeg
|
||||
--noMerge Disable auto merge
|
||||
--noProxy Disable use system proxy
|
||||
--disableIntegrityCheck Disable integrity check
|
||||
```
|
||||
|
||||
## Document
|
||||
https://nilaoda.github.io/N_m3u8DL-CLI/
|
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 it is too large
Load Diff
@@ -1,11 +0,0 @@
|
||||
(function() {
|
||||
var newEl = document.createElement('script'),
|
||||
firstScriptTag = document.getElementsByTagName('script')[0];
|
||||
|
||||
if (firstScriptTag) {
|
||||
newEl.async = 1;
|
||||
newEl.src = '//' + window.location.hostname + ':35729/livereload.js';
|
||||
firstScriptTag.parentNode.insertBefore(newEl, firstScriptTag);
|
||||
}
|
||||
|
||||
})();
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
BIN
docs/source/images/163-1.png
Normal file
BIN
docs/source/images/163-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
docs/source/images/163-2.png
Normal file
BIN
docs/source/images/163-2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 391 KiB |
BIN
docs/source/images/163-3.png
Normal file
BIN
docs/source/images/163-3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
BIN
docs/source/images/163-4.png
Normal file
BIN
docs/source/images/163-4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user