1
mirror of https://github.com/nilaoda/N_m3u8DL-CLI synced 2025-09-07 02:45:59 +02:00

Compare commits

...

47 Commits
2.7.3 ... 2.8.7

Author SHA1 Message Date
nilaoda
d657b455cd BUG FIX 2020-11-25 21:16:41 +08:00
nilaoda
cda6575605 BUG FIX 2020-11-25 21:16:18 +08:00
nilaoda
0d6377d41b Update changelog.txt 2020-11-25 17:34:53 +08:00
nilaoda
9c76bdcbce 支持选择音轨 2020-11-25 17:34:12 +08:00
nilaoda
83915ff606 Update Global.cs 2020-11-25 14:12:46 +08:00
nilaoda
9cd4746f33 Update changelog.txt 2020-11-25 14:11:31 +08:00
nilaoda
33a5b917ac 修正MPD判断最高清晰度的逻辑 2020-11-25 14:08:40 +08:00
nilaoda
f69978bd82 修改芒果TV请求头 2020-11-23 21:53:41 +08:00
nilaoda
d9fd526886 Update changelog.txt 2020-11-23 21:53:26 +08:00
nilaoda
1cc8ecfaaf Update Global.cs 2020-11-23 21:30:12 +08:00
nilaoda
2bc2dde2ad 修改默认UA 2020-11-23 21:29:48 +08:00
nilaoda
c3ddcf9e0e Update Program.cs 2020-11-23 21:29:31 +08:00
nilaoda
ba5d20dd02 Update Parser.cs 2020-11-23 21:29:03 +08:00
nilaoda
16f705fe66 Update Global.cs 2020-11-22 23:13:59 +08:00
nilaoda
d141cabc4a Update changelog.txt 2020-11-22 23:08:51 +08:00
nilaoda
adcf884a93 新的任务监控逻辑 2020-11-22 23:08:33 +08:00
nilaoda
9d903a025f Update DownloadManager.cs 2020-11-22 22:36:21 +08:00
nilaoda
0bd23ab641 更新版本号 2020-11-22 18:48:54 +08:00
nilaoda
a38f27ccd7 手动处理重定向
解决HTTPS协议自动重定向后,Referer丢失问题
2020-11-22 18:29:40 +08:00
nilaoda
3acec5efd3 Update Global.cs 2020-11-22 15:40:47 +08:00
nilaoda
90874e4bfe Update Parser.cs 2020-11-22 14:30:41 +08:00
nilaoda
b94768e3e8 修复自定义MPD的BaseURL 2020-11-22 00:40:17 +08:00
nilaoda
311f3b882e 更新版本号 2020-11-21 19:36:04 +08:00
nilaoda
4b4f537984 BUG FIX 2020-11-21 19:34:47 +08:00
nilaoda
8032d50b42 传递MPD_URL时处理302 2020-11-21 19:34:27 +08:00
nilaoda
f615764e55 Update build_latest.yml 2020-11-21 13:48:20 +08:00
nilaoda
ccaa200ef8 Update build_latest.yml 2020-11-21 13:45:56 +08:00
nilaoda
6058d878eb Update N_m3u8DL-CLI.csproj 2020-11-21 13:40:15 +08:00
nilaoda
c712c6dee0 Create changelog.txt 2020-11-21 13:30:56 +08:00
nilaoda
bb24bb998f Update Global.cs 2020-11-21 13:11:15 +08:00
nilaoda
e9d951efa5 检测GIF HEADER 2020-11-21 12:25:01 +08:00
nilaoda
6f88a805ef 修复PNG检测逻辑
多写了一个分号……
2020-11-21 12:08:28 +08:00
nilaoda
6aa6d63a8d Convert MPD to M3U8
通过将MPD转换为m3u8进行下载
2020-11-20 23:34:35 +08:00
nilaoda
147246caba Update Parser.cs 2020-11-18 16:05:18 +08:00
nilaoda
35c1ee4777 Update Parser.cs 2020-11-18 15:52:36 +08:00
nilaoda
8b32081b85 识别m3u8文件中的EXT-X-PROGRAM-DATE-TIME 2020-11-18 15:33:39 +08:00
nilaoda
c00de328d1 修改默认UA 修改音轨判断逻辑
修改UA为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

修改AAC滤镜的使用逻辑

当m3u8文本大小大于50MB时应当放弃
2020-11-18 15:03:21 +08:00
nilaoda
d5193c1645 fix bug 2020-11-06 22:11:21 +08:00
nilaoda
c06fbf5820 update docs 2020-11-03 11:38:24 +08:00
nilaoda
e700edba56 Update Program.cs
修正处理文件名过长的逻辑
2020-10-14 22:18:56 +08:00
nilaoda
aad948da7c v2.7.5 2020-10-14 22:01:03 +08:00
nilaoda
14f7b20176 Merge pull request #246 from Suwmlee/master
Fix build error
2020-09-22 10:19:45 +08:00
Mathhew
a66a9a4096 Fix build error 2020-09-22 09:49:58 +08:00
nilaoda
c862f23a9c v2.7.4
支持ddyun m3u8解密
2020-09-20 13:41:49 +08:00
nilaoda
1a722e80de Delete DecryptNfmovies.cs 2020-09-20 13:41:02 +08:00
nilaoda
eff43e8ac3 Merge pull request #240 from Suwmlee/master
Add github action
2020-09-17 18:58:30 +08:00
Mathhew
7648f8f8dc Add github action 2020-09-17 10:05:35 +08:00
22 changed files with 2025 additions and 517 deletions

34
.github/workflows/build_latest.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Build_Latest
on: [push]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v1
name: Checkout Code
- name: Setup MSBuild Path
uses: warrenbuckley/Setup-MSBuild@v1
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
- name: Setup NuGet
uses: NuGet/setup-nuget@v1.0.2
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
- name: Restore NuGet Packages
run: nuget restore N_m3u8DL-CLI.sln
- name: Build
run: msbuild N_m3u8DL-CLI.sln /p:Configuration=Release /p:DebugSymbols=false /p:DebugType=None
- name: Upload Artifact
uses: actions/upload-artifact@v1.0.0
with:
name: N_m3u8DL-CLI_latest
path: N_m3u8DL-CLI\bin\Release\

View File

@@ -0,0 +1,41 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace N_m3u8DL_CLI
{
class DecodeDdyun
{
public static string DecryptM3u8(byte[] byteArray)
{
string tmp = DecodeNfmovies.DecryptM3u8(byteArray);
if (tmp.StartsWith("duoduo.key"))
{
tmp = Regex.Replace(tmp, @"#EXT-X-BYTERANGE:.*\s", "");
tmp = tmp.Replace("https:", "jump/https:")
.Replace("inews.gtimg.com", "puui.qpic.cn");
}
return tmp;
}
//https://player.ddyunp.com/jQuery.min.js?v1.5
public static string GetVaildM3u8Url(string url)
{
//url: https://hls.ddyunp.com/ddyun/id/1/key/playlist.m3u8
string id = Regex.Match(url, @"\w{20,}").Value;
string tm = Global.GetTimeStamp(false);
string t = ((long.Parse(tm) / 0x186a0) * 0x64).ToString();
string tmp = id + "duoduo" + "1" + t;
MD5 md5 = MD5.Create();
byte[] bs = Encoding.UTF8.GetBytes(tmp);
byte[] hs = md5.ComputeHash(bs);
StringBuilder sb = new StringBuilder();
foreach (byte b in hs)
{
sb.Append(b.ToString("x2"));
}
string key = sb.ToString();
return Regex.Replace(url, @"1/\w{20,}", "1/" + key);
}
}
}

View File

@@ -5,7 +5,7 @@ using System.Text;
namespace N_m3u8DL_CLI
{
class DecryptNfmovies
class DecodeNfmovies
{
//https://jx.nfmovies.com/hls.min.js
public static string DecryptM3u8(byte[] byteArray)

View File

@@ -11,25 +11,12 @@ namespace N_m3u8DL_CLI
{
class DownloadManager
{
private static int calcTime = 1; //计算文件夹大小的间隔
private int stopCount = 0; //速度为零的停止
private int timeOut = 10000; //超时设置
private static double downloadedSize = 0; //已下载大小
private static bool disableIntegrityCheck = false; //关闭完整性检查
private string jsonFile = string.Empty;
private string headers = string.Empty;
private string downDir = string.Empty;
private string downName = string.Empty;
private string muxSetJson = string.Empty;
private int threads = 1;
private int retryCount = 5;
private static int count = 0;
private static int partsCount = 0;
private int total = 0;
public static string partsPadZero = string.Empty;
string segsPadZero = string.Empty;
bool delAfterDone = false;
private bool isVTT = false;
bool externalAudio = false; //额外的音轨
string externalAudioUrl = "";
@@ -37,28 +24,56 @@ namespace N_m3u8DL_CLI
string externalSubUrl = "";
string fflogName = "_ffreport.log";
public static bool BinaryMerge = false;
private bool noMerge = false;
private bool muxFastStart = true;
private string muxFormat = "mp4";
private static bool hasSetDir = false;
public int Threads { get => threads; set => threads = value; }
public int RetryCount { get => retryCount; set => retryCount = value; }
public string Headers { get => headers; set => headers = value; }
public string DownDir { get => downDir; set => downDir = value; }
public string DownName { get => downName; set => downName = value; }
public bool DelAfterDone { get => delAfterDone; set => delAfterDone = value; }
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 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; }
public bool NoMerge { get => noMerge; set => noMerge = value; }
public static int CalcTime { get => calcTime; set => calcTime = value; }
public static int Count { get => count; set => count = value; }
public static int PartsCount { get => partsCount; set => partsCount = value; }
public static bool DisableIntegrityCheck { get => disableIntegrityCheck; set => disableIntegrityCheck = value; }
public int Threads { get; set; } = 1;
public int RetryCount { get; set; } = 5;
public string Headers { get; set; } = string.Empty;
public string DownDir { get; set; } = string.Empty;
public string DownName { get; set; } = string.Empty;
public bool DelAfterDone { get; set; } = false;
public string MuxFormat { get; set; } = "mp4";
public bool MuxFastStart { get; set; } = true;
public string MuxSetJson { get; set; } = string.Empty;
public int TimeOut { get; set; } = 10000; //超时设置
public static double DownloadedSize { get; set; } = 0; //已下载大小
public static bool HasSetDir { get; set; } = false;
public bool NoMerge { get; set; } = false;
public static int CalcTime { get; set; } = 1; //计算速度的间隔
public static int Count { get; set; } = 0;
public static int PartsCount { get; set; } = 0;
public static bool DisableIntegrityCheck { get; set; } = false; //关闭完整性检查
static CancellationTokenSource cts = new CancellationTokenSource();
//计算下载速度
static System.Timers.Timer timer = new System.Timers.Timer(1000 * CalcTime); //实例化Timer类
public DownloadManager()
{
timer.AutoReset = true;
timer.Elapsed += delegate
{
Console.SetCursorPosition(0, 1);
Console.Write("Speed: " + Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + " / s".PadRight(70));
if (Global.HadReadInfo && Global.BYTEDOWN <= Global.STOP_SPEED * 1024 * CalcTime)
{
stopCount++;
Console.SetCursorPosition(0, 1);
Console.Write("Speed: " + Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + " / s [" + stopCount + "]".PadRight(70));
if (stopCount >= 12)
{
Global.ShouldStop = true;
cts.Cancel();
}
}
else
{
stopCount = 0;
Global.BYTEDOWN = 0;
}
};
}
public void DoDownload()
{
@@ -94,7 +109,6 @@ namespace N_m3u8DL_CLI
PartsCount = parts.Count;
segsPadZero = string.Empty.PadRight(oriCount.Length, '0');
partsPadZero = string.Empty.PadRight(Convert.ToString(parts.Count).Length, '0');
CancellationTokenSource cts = new CancellationTokenSource();
//是直播视频
if (isVOD == "False")
@@ -104,7 +118,6 @@ namespace N_m3u8DL_CLI
Global.ShouldStop = false; //是否该停止下载
//监控文件夹变化
if (!Directory.Exists(DownDir))
Directory.CreateDirectory(DownDir); //新建文件夹
Watcher watcher = new Watcher(DownDir);
@@ -112,41 +125,9 @@ namespace N_m3u8DL_CLI
watcher.PartsCount = PartsCount;
watcher.WatcherStrat();
//监控文件夹大小变化 via https://stackoverflow.com/questions/2869561/what-is-the-fastest-way-to-calculate-a-windows-folders-size/12665904#12665904
System.Timers.Timer timer = new System.Timers.Timer(1000 * CalcTime); //实例化Timer类
timer.AutoReset = true;
//开始计算速度
timer.Enabled = true;
//Scripting.FileSystemObject fso = new Scripting.FileSystemObject();
//Scripting.Folder folder = fso.GetFolder(DownDir);
timer.Elapsed += delegate
{
//采用COM组件获取文件夹的大小需要引入 "Microsoft Scripting Runtime"
//double sizeInBytes = folder.Size;
Console.SetCursorPosition(0, 1);
//Console.WriteLine("Speed: " + Global.FormatFileSize((sizeInBytes - lastSizeInBytes) / calcTime) + " / s ");
Console.Write("Speed: " + Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + " / s".PadRight(70));
if (Global.HadReadInfo && Global.BYTEDOWN <= Global.STOP_SPEED * 1024 * CalcTime)
{
stopCount++;
Console.SetCursorPosition(0, 1);
Console.Write("Speed: " + Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + " / s [" + stopCount + "]".PadRight(70));
if (stopCount >= 12)
{
Global.ShouldStop = true;
cts.Cancel();
GC.Collect();
return;
}
}
else
{
stopCount = 0;
Global.BYTEDOWN = 0;
}
};
cts = new CancellationTokenSource();
//开始调用下载
LOGGER.WriteLine(strings.startDownloading);
@@ -200,16 +181,10 @@ namespace N_m3u8DL_CLI
sd.Key = firstSeg["key"].Value<string>();
sd.Iv = firstSeg["iv"].Value<string>();
}
try
{
if (firstSeg["expectByte"] != null)
sd.ExpectByte = firstSeg["expectByte"].Value<long>();
}
catch (Exception) { }
try
{
if (firstSeg["startByte"] != null)
sd.StartByte = firstSeg["startByte"].Value<long>();
}
catch (Exception) { }
sd.Headers = Headers;
sd.SavePath = DownDir + "\\Part_" + 0.ToString(partsPadZero) + "\\" + firstSeg["index"].Value<int>().ToString(segsPadZero) + ".tsdownloading";
if (File.Exists(sd.SavePath))
@@ -290,16 +265,10 @@ namespace N_m3u8DL_CLI
sd.Key = info["key"].Value<string>();
sd.Iv = info["iv"].Value<string>();
}
try
{
if (firstSeg["expectByte"] != null)
sd.ExpectByte = info["expectByte"].Value<long>();
}
catch (Exception) { }
try
{
if (firstSeg["startByte"] != null)
sd.StartByte = info["startByte"].Value<long>();
}
catch (Exception) { }
sd.Headers = Headers;
sd.SavePath = DownDir + "\\Part_" + info["part"].Value<int>().ToString(partsPadZero) + "\\" + info["index"].Value<int>().ToString(segsPadZero) + ".tsdownloading";
if (File.Exists(sd.SavePath))
@@ -327,12 +296,8 @@ namespace N_m3u8DL_CLI
watcher.WatcherStop();
//监控文件夹大小变化的收尾工作
//停止速度监测
timer.Enabled = false;
timer.Close();
// cleanup COM
//System.Runtime.InteropServices.Marshal.ReleaseComObject(folder);
//System.Runtime.InteropServices.Marshal.ReleaseComObject(fso);
//检测是否下完
IsComplete(Convert.ToInt32(segCount));
@@ -363,14 +328,13 @@ namespace N_m3u8DL_CLI
Count++;
LOGGER.WriteLine(strings.retryCount + Count + " / " + RetryCount);
LOGGER.PrintLine(strings.retryCount + Count + " / " + RetryCount, LOGGER.Warning);
Thread.Sleep(3000);
GC.Collect(); //垃圾回收
Thread.Sleep(6000);
DoDownload();
}
}
else //开始合并
{
LOGGER.PrintLine(strings.downloadComplete + (DisableIntegrityCheck ? "("+strings.disableIntegrityCheck+")" : ""));
LOGGER.PrintLine(strings.downloadComplete + (DisableIntegrityCheck ? "(" + strings.disableIntegrityCheck + ")" : ""));
Console.WriteLine();
if (NoMerge == false)
{
@@ -539,7 +503,7 @@ namespace N_m3u8DL_CLI
FFmpeg.OutPutPath = Path.Combine(Directory.GetParent(DownDir).FullName, DownName);
FFmpeg.ReportFile = driverName + "\\:" + exePath.Remove(0, exePath.IndexOf(':') + 1).Replace("\\", "/") + "/Logs/" + Path.GetFileNameWithoutExtension(LOGGER.LOGFILE) + fflogName;
//合并分段
LOGGER.PrintLine(strings.startMerging);
for (int i = 0; i < PartsCount; i++)
@@ -640,7 +604,7 @@ namespace N_m3u8DL_CLI
parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName);
LOGGER.WriteLine(strings.startParsing + externalAudioUrl);
LOGGER.WriteLine(strings.downloadingExternalAudioTrack);
DownName = DownName + "(Audio)";
DownName = parser.DownName;
fflogName = "_ffreport(Audio).log";
DownDir = parser.DownDir;
parser.Parse(); //开始解析

View File

@@ -10,19 +10,20 @@ namespace N_m3u8DL_CLI
{
class FFmpeg
{
private static string outPutPath = string.Empty;
private static string reportFile = string.Empty;
private static bool useAACFilter = false; //是否启用滤镜
private static bool writeDate = true; //是否写入录制日期
public static string OutPutPath { get => outPutPath; set => outPutPath = value; }
public static string ReportFile { get => reportFile; set => reportFile = value; }
public static bool UseAACFilter { get => useAACFilter; set => useAACFilter = value; }
public static bool WriteDate { get => writeDate; set => writeDate = value; }
public static string FFMPEG_PATH = "ffmpeg";
public static string REC_TIME = ""; //录制日期
public static string OutPutPath { get; set; } = string.Empty;
public static string ReportFile { get; set; } = string.Empty;
public static bool UseAACFilter { get; set; } = false; //是否启用滤镜
public static bool WriteDate { get; set; } = true; //是否写入录制日期
public static void Merge(string[] files, string muxFormat, bool fastStart,
string poster = "", string audioName = "", string title = "",
string copyright = "", string comment = "", string encodingTool = "")
{
string dateString = string.IsNullOrEmpty(REC_TIME) ? DateTime.Now.ToString("o") : REC_TIME;
//同名文件已存在的共存策略
if (File.Exists($"{OutPutPath}.{muxFormat.ToLower()}"))
{
@@ -50,7 +51,7 @@ namespace N_m3u8DL_CLI
command += " " + (string.IsNullOrEmpty(ddpAudio) ? "" : "-i \"" + ddpAudio + "\"");
command +=
$" -map 0:v? {(string.IsNullOrEmpty(ddpAudio) ? "-map 0:a?" : $"-map {(string.IsNullOrEmpty(poster) ? "1" : "2")}:a -map 0:a?")} -map 0:s? " + (string.IsNullOrEmpty(poster) ? "" : addPoster)
+ (writeDate ? " -metadata date=\"" + DateTime.Now.ToString("o") + "\"" : "") +
+ (WriteDate ? " -metadata date=\"" + dateString + "\"" : "") +
" -metadata encoding_tool=\"" + encodingTool + "\" -metadata title=\"" + title +
"\" -metadata copyright=\"" + copyright + "\" -metadata comment=\"" + comment +
$"\" -metadata:s:a:{(string.IsNullOrEmpty(ddpAudio) ? "0" : "1")} handler_name=\"" + audioName + $"\" -metadata:s:a:{(string.IsNullOrEmpty(ddpAudio) ? "0" : "1")} handler=\"" + audioName + "\" ";
@@ -83,7 +84,7 @@ namespace N_m3u8DL_CLI
}
Run("ffmpeg", command, Path.GetDirectoryName(files[0]));
Run(FFMPEG_PATH, command, Path.GetDirectoryName(files[0]));
LOGGER.WriteLine(strings.ffmpegDone);
//Console.WriteLine(command);
}
@@ -92,7 +93,7 @@ namespace N_m3u8DL_CLI
{
if (Global.VIDEO_TYPE == "H264")
{
Run("ffmpeg",
Run(FFMPEG_PATH,
"-loglevel quiet -i \"" + file + "\" -map 0 -c copy -copy_unknown -f mpegts -bsf:v h264_mp4toannexb \""
+ Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts\"",
Path.GetDirectoryName(file));
@@ -104,7 +105,7 @@ namespace N_m3u8DL_CLI
}
else if (Global.VIDEO_TYPE == "H265")
{
Run("ffmpeg",
Run(FFMPEG_PATH,
"-loglevel quiet -i \"" + file + "\" -map 0 -c copy -copy_unknown -f mpegts -bsf:v hevc_mp4toannexb \""
+ Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts\"",
Path.GetDirectoryName(file));

View File

@@ -30,8 +30,9 @@ namespace N_m3u8DL_CLI
/*===============================================================================*/
static string nowVer = "2.7.3";
static string nowDate = "20200914";
static Version ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
static string nowVer = $"{ver.Major}.{ver.Minor}.{ver.Build}";
static string nowDate = "20201125";
public static void WriteInit()
{
Console.Clear();
@@ -101,23 +102,25 @@ namespace N_m3u8DL_CLI
public static string GetWebSource(String url, string headers = "", int TimeOut = 60000)
{
string htmlCode = string.Empty;
for(int i = 0; i < 10; i++)
for (int i = 0; i < 10; i++)
{
try
{
reProcess:
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.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36";
webRequest.Accept = "*/*";
webRequest.Headers.Add("Accept-Encoding", "gzip, deflate, br");
webRequest.Timeout = TimeOut; //设置超时
webRequest.KeepAlive = false;
webRequest.AllowAutoRedirect = true; //自动跳转
webRequest.AllowAutoRedirect = false; //手动处理重定向否则会丢失Referer
if (url.Contains("pcvideo") && url.Contains(".titan.mgtv.com"))
{
webRequest.UserAgent = "";
if (!url.Contains("/internettv/"))
webRequest.Referer = "https://player.mgtv.com/mgtv_v6_player/PlayerCore.swf";
webRequest.Referer = "https://www.mgtv.com";
webRequest.Headers.Add("Cookie", "MQGUID");
}
//添加headers
@@ -145,6 +148,18 @@ namespace N_m3u8DL_CLI
}
}
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
//302
if (webResponse.Headers.Get("Location") != null)
{
url = webResponse.Headers.Get("Location");
webResponse.Close();
goto reProcess;
}
//文件过大则认为不是m3u8
if (webResponse.ContentLength != -1 && webResponse.ContentLength > 50 * 1024 * 1024) return "";
if (webResponse.ContentEncoding != null
&& webResponse.ContentEncoding.ToLower() == "gzip") //如果使用了GZip则先解压
{
@@ -402,14 +417,17 @@ namespace N_m3u8DL_CLI
}
}
reProcess:
byte[] arraryByte;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "GET";
req.Timeout = timeOut;
req.ReadWriteTimeout = timeOut; //重要
req.AllowAutoRedirect = false; //手动处理重定向否则会丢失Referer
if (NoProxy) req.Proxy = null;
req.Headers.Add("Accept-Encoding", "gzip, deflate");
req.Accept = "*/*";
req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36";
//添加headers
if (headers != "")
{
@@ -437,6 +455,13 @@ namespace N_m3u8DL_CLI
using (HttpWebResponse wr = (HttpWebResponse)req.GetResponse())
{
//302
if (wr.Headers.Get("Location") != null)
{
url = wr.Headers.Get("Location");
wr.Close();
goto reProcess;
}
if (wr.ContentEncoding != null && wr.ContentEncoding.ToLower() == "gzip") //如果使用了GZip则先解压
{
using (Stream streamReceive = wr.GetResponseStream())
@@ -497,10 +522,11 @@ namespace N_m3u8DL_CLI
if (shouldStop)
return;
reProcess:
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Timeout = timeOut;
request.ReadWriteTimeout = timeOut; //重要
request.AllowAutoRedirect = true;
request.AllowAutoRedirect = false; //手动处理重定向否则会丢失Referer
request.KeepAlive = false;
request.Method = "GET";
if (NoProxy) request.Proxy = null;
@@ -510,11 +536,11 @@ namespace N_m3u8DL_CLI
{
request.UserAgent = "";
if (!url.Contains("/internettv/"))
request.Referer = "https://player.mgtv.com/mgtv_v6_player/PlayerCore.swf";
request.Referer = "https://www.mgtv.com";
request.Headers.Add("Cookie", "MQGUID");
}
else
request.UserAgent = "VLC/2.2.1 LibVLC/2.2.1";
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36";
//下载部分字节
if (expectByte != -1)
request.AddRange("bytes", startByte, startByte + expectByte - 1);
@@ -548,6 +574,13 @@ namespace N_m3u8DL_CLI
bool pngHeader = false; //PNG HEADER检测
using (var response = (HttpWebResponse)request.GetResponse())
{
//302
if (response.Headers.Get("Location") != null)
{
url = response.Headers.Get("Location");
response.Close();
goto reProcess;
}
using (var responseStream = response.GetResponseStream())
{
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write))
@@ -556,10 +589,17 @@ namespace N_m3u8DL_CLI
totalLen = response.ContentLength;
byte[] bArr = new byte[1024];
int size = responseStream.Read(bArr, 0, (int)bArr.Length);
if (!pngHeader && size > 3 && 137 == bArr[0] && 80 == bArr[1] && 78 == bArr[2] && 71 == bArr[3]) ;
if (!pngHeader && size > 3 && 137 == bArr[0] && 80 == bArr[1] && 78 == bArr[2] && 71 == bArr[3])
{
pngHeader = true;
}
//GIF HEADER检测
if (!pngHeader && size > 3 && 0x47 == bArr[0] && 0x49 == bArr[1] && 0x46 == bArr[2] && 0x38 == bArr[3])
{
bArr = bArr.Skip(42).ToArray();
size -= 42;
downLen += 42;
}
while (size > 0)
{
stream.Write(bArr, 0, size);
@@ -582,10 +622,11 @@ namespace N_m3u8DL_CLI
}
if (shouldStop)
try { File.Delete(path); } catch (Exception) { }
if (totalLen != -1 && downLen != totalLen)
if (totalLen != -1 && downLen != totalLen)
try { File.Delete(path); } catch (Exception) { }
if (pngHeader)
TrySkipPngHeader(path);
}
catch (Exception e)
{
@@ -800,11 +841,17 @@ namespace N_m3u8DL_CLI
}
}
if(res.Contains("Audio aac"))
if (res.Contains("Audio aac"))
{
FFmpeg.UseAACFilter = true;
}
//有非AAC音轨则关闭UseAACFilter
if (res.Contains("Audio") && !res.Contains("Audio aac"))
{
FFmpeg.UseAACFilter = false;
}
if ((VIDEO_TYPE == "" || VIDEO_TYPE == "IGNORE") && res.Contains("Audio eac3"))
{
AUDIO_TYPE = "eac3";

702
N_m3u8DL-CLI/MPDParser.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -40,8 +40,8 @@
<ItemGroup>
<Reference Include="Microsoft.JScript" />
<Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
<Reference Include="Newtonsoft.Json">
<HintPath>..\..\Newtonsoft.Json.dll</HintPath>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NiL.JS, Version=2.5.1428.0, Culture=neutral, PublicKeyToken=fa941a7c2a4de689, processorArchitecture=MSIL">
<HintPath>..\packages\NiL.JS.2.5.1428\lib\net45\NiL.JS.dll</HintPath>
@@ -63,15 +63,17 @@
<Compile Include="CommandLineArgument.cs" />
<Compile Include="CommandLineArgumentParser.cs" />
<Compile Include="Decode51CtoKey.cs" />
<Compile Include="DecodeDdyun.cs" />
<Compile Include="DecodeImooc.cs" />
<Compile Include="DecodeNfmovies.cs" />
<Compile Include="Decrypter.cs" />
<Compile Include="DecryptNfmovies.cs" />
<Compile Include="FFmpeg.cs" />
<Compile Include="Global.cs" />
<Compile Include="HLSLiveDownloader.cs" />
<Compile Include="HLSTags.cs" />
<Compile Include="LOGGER.cs" />
<Compile Include="DownloadManager.cs" />
<Compile Include="MPDParser.cs" />
<Compile Include="Parser.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -99,9 +101,6 @@
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<None Include="bin\Debug\Newtonsoft.Json.dll" />
</ItemGroup>
<ItemGroup>
<COMReference Include="Scripting">
<Guid>{420B2830-E718-11CF-893D-00A0C9054228}</Guid>
@@ -131,4 +130,4 @@
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>

View File

@@ -17,15 +17,6 @@ namespace N_m3u8DL_CLI
string[] m3u8CurrentKey = new string[] { "NONE", "", "" };
private string m3u8SavePath = string.Empty;
private string jsonSavePath = string.Empty;
private string headers = string.Empty;
private string baseUrl = string.Empty;
private string m3u8Url = string.Empty;
private string downDir = string.Empty;
private string downName = string.Empty;
private string keyFile = string.Empty;
private string keyBase64 = string.Empty;
private string keyIV = string.Empty;
private bool liveStream = false;
private long bestBandwidth = 0;
private string bestUrl = string.Empty;
private string bestUrlAudio = string.Empty;
@@ -37,34 +28,31 @@ namespace N_m3u8DL_CLI
//存放多轨道的信息
private ArrayList extLists = new ArrayList();
private static bool isQiQiuYun = false;
//存放Range信息允许用户只下载部分视频
private static int rangeStart = 0;
private static int rangeEnd = -1;
//存放Range信息允许用户只下载部分视频
private static string durStart = "";
private static string durEnd = "";
//是否自动清除优酷广告分片
private static bool delAd = true;
//标记是否已清除优酷广告分片
private static bool hasAd = false;
public string BaseUrl { get => baseUrl; set => baseUrl = value; }
public string M3u8Url { get => m3u8Url; set => m3u8Url = value; }
public string DownDir { get => downDir; set => downDir = value; }
public string DownName { get => downName; set => downName = value; }
public string Headers { get => headers; set => headers = value; }
public static int RangeStart { get => rangeStart; set => rangeStart = value; }
public static int RangeEnd { get => rangeEnd; set => rangeEnd = value; }
public static bool DelAd { get => delAd; set => delAd = value; }
public static string DurStart { get => durStart; set => durStart = value; }
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 string KeyIV { get => keyIV; set => keyIV = value; }
public string BaseUrl { get; set; } = string.Empty;
public string M3u8Url { get; set; } = string.Empty;
public string DownDir { get; set; } = string.Empty;
public string DownName { get; set; } = string.Empty;
public string Headers { get; set; } = string.Empty;
//存放Range信息允许用户只下载部分视频
public static int RangeStart { get; set; } = 0;
public static int RangeEnd { get; set; } = -1;
//是否自动清除优酷广告分片
public static bool DelAd { get; set; } = true;
//存放Range信息允许用户只下载部分视频
public static string DurStart { get; set; } = "";
public static string DurEnd { get; set; } = "";
public string KeyFile { get; set; } = string.Empty;
public string KeyBase64 { get; set; } = string.Empty;
public bool LiveStream { get; set; } = false;
public string KeyIV { get; set; } = string.Empty;
public void Parse()
{
FFmpeg.REC_TIME = "";
m3u8SavePath = Path.Combine(DownDir, "raw.m3u8");
jsonSavePath = Path.Combine(DownDir, "meta.json");
@@ -90,15 +78,17 @@ namespace N_m3u8DL_CLI
//获取m3u8内容
if (!liveStream)
if (!LiveStream)
LOGGER.PrintLine(strings.downloadingM3u8, LOGGER.Warning);
if (M3u8Url.StartsWith("http"))
{
if (M3u8Url.Contains("nfmovies.com/hls"))
m3u8Content = DecryptNfmovies.DecryptM3u8(Global.HttpDownloadFileToBytes(M3u8Url, headers));
m3u8Content = DecodeNfmovies.DecryptM3u8(Global.HttpDownloadFileToBytes(M3u8Url, Headers));
else if (M3u8Url.Contains("hls.ddyunp.com/ddyun"))
m3u8Content = DecodeDdyun.DecryptM3u8(Global.HttpDownloadFileToBytes(DecodeDdyun.GetVaildM3u8Url(M3u8Url), Headers));
else
m3u8Content = Global.GetWebSource(M3u8Url, headers);
m3u8Content = Global.GetWebSource(M3u8Url, Headers);
}
else if (M3u8Url.StartsWith("file:"))
{
@@ -128,6 +118,18 @@ namespace N_m3u8DL_CLI
m3u8Content = DecodeImooc.DecodeM3u8(m3u8Content);
}
if (m3u8Content.Trim().StartsWith("<?xml version") && m3u8Content.Contains("<MPD"))
{
var mpdSavePath = Path.Combine(DownDir, "dash.mpd");
//输出mpd文件
File.WriteAllText(mpdSavePath, m3u8Content);
//分析mpd文件
M3u8Url = Global.Get302(M3u8Url, Headers);
var newUri = MPDParser.Parse(DownDir, M3u8Url, m3u8Content, BaseUrl);
M3u8Url = newUri;
m3u8Content = File.ReadAllText(new Uri(M3u8Url).LocalPath);
}
//输出m3u8文件
File.WriteAllText(m3u8SavePath, m3u8Content);
@@ -147,32 +149,32 @@ namespace N_m3u8DL_CLI
if (new Regex("#YUMING\\|(.*)").IsMatch(m3u8Content))
BaseUrl = new Regex("#YUMING\\|(.*)").Match(m3u8Content).Groups[1].Value;
else
BaseUrl = GetBaseUrl(M3u8Url, headers);
BaseUrl = GetBaseUrl(M3u8Url, Headers);
}
if (!liveStream)
if (!LiveStream)
{
LOGGER.WriteLine(strings.parsingM3u8);
LOGGER.PrintLine(strings.parsingM3u8);
}
if (!string.IsNullOrEmpty(keyBase64))
if (!string.IsNullOrEmpty(KeyBase64))
{
string line = "";
if (string.IsNullOrEmpty(keyIV))
line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"base64:{keyBase64}\"";
if (string.IsNullOrEmpty(KeyIV))
line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"base64:{KeyBase64}\"";
else
line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"base64:{keyBase64}\",IV=0x{keyIV.Replace("0x", "")}";
line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"base64:{KeyBase64}\",IV=0x{KeyIV.Replace("0x", "")}";
m3u8CurrentKey = ParseKey(line);
}
if (!string.IsNullOrEmpty(keyFile))
if (!string.IsNullOrEmpty(KeyFile))
{
string line = "";
Uri u = new Uri(keyFile);
if (string.IsNullOrEmpty(keyIV))
Uri u = new Uri(KeyFile);
if (string.IsNullOrEmpty(KeyIV))
line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"{u.ToString()}\"";
else
line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"{u.ToString()}\",IV=0x{keyIV.Replace("0x", "")}";
line = $"#EXT-X-KEY:METHOD=AES-128,URI=\"{u.ToString()}\",IV=0x{KeyIV.Replace("0x", "")}";
m3u8CurrentKey = ParseKey(line);
}
@@ -239,7 +241,13 @@ namespace N_m3u8DL_CLI
startIndex = segIndex;
}
else if (line.StartsWith(HLSTags.ext_x_discontinuity_sequence)) ;
else if (line.StartsWith(HLSTags.ext_x_program_date_time)) ;
else if (line.StartsWith(HLSTags.ext_x_program_date_time))
{
if (string.IsNullOrEmpty(FFmpeg.REC_TIME))
{
FFmpeg.REC_TIME = line.Replace(HLSTags.ext_x_program_date_time + ":", "").Trim();
}
}
//解析不连续标记需要单独合并timestamp不同
else if (line.StartsWith(HLSTags.ext_x_discontinuity))
{
@@ -264,11 +272,23 @@ 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))
{
m3u8CurrentKey = ParseKey(line);
//存储为上一行的key信息
lastKeyLine = line;
//自定义KEY情况 判断是否需要读取IV
if (!string.IsNullOrEmpty(KeyFile) || !string.IsNullOrEmpty(KeyBase64))
{
if (m3u8CurrentKey[2] == "" && line.Contains("IV=0x"))
{
var temp = ParseKey(line);
m3u8CurrentKey[2] = temp[2]; //使用m3u8中的IV
}
}
else
{
m3u8CurrentKey = ParseKey(line);
//存储为上一行的key信息
lastKeyLine = line;
}
}
//解析分片时长(暂时不考虑标题属性)
else if (line.StartsWith(HLSTags.extinf))
@@ -312,7 +332,7 @@ 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")
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")
{
@@ -370,6 +390,10 @@ namespace N_m3u8DL_CLI
{
segUrl += new Regex("\\?__gda__.*").Match(M3u8Url).Value;
}
if (M3u8Url.Contains("//dlsc.hcs.cmvideo.cn") && (segUrl.EndsWith(".ts") || segUrl.EndsWith(".mp4")))
{
segUrl += new Regex("\\?.*").Match(M3u8Url).Value;
}
segInfo.Add("segUri", segUrl);
segments.Add(segInfo);
segInfo = new JObject();
@@ -586,7 +610,7 @@ namespace N_m3u8DL_CLI
//输出JSON文件
if (!liveStream)
if (!LiveStream)
{
LOGGER.WriteLine(strings.wrtingMeta);
LOGGER.PrintLine(strings.wrtingMeta);
@@ -840,9 +864,7 @@ namespace N_m3u8DL_CLI
/// <returns></returns>
public static string GetBaseUrl(string m3u8url, string headers)
{
if (!isQiQiuYun && Global.Get302(m3u8url, headers) != m3u8url)
m3u8url = Global.Get302(m3u8url, headers);
string url = Global.Get302(m3u8url);
string url = Global.Get302(m3u8url, headers);
if (url.Contains("?"))
url = url.Remove(url.LastIndexOf('?'));
url = url.Substring(0, url.LastIndexOf('/') + 1);

File diff suppressed because it is too large Load Diff

297
N_m3u8DL-CLI/changelog.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net46" />
<package id="NiL.JS" version="2.5.1428" targetFramework="net46" />
</packages>

File diff suppressed because one or more lines are too long

555
docs/GetM3u8.html Normal file

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