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
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
faf67cd527 | ||
![]() |
38d1a1a2dc | ||
![]() |
12eb68d592 | ||
![]() |
0804e295e5 | ||
![]() |
847c4683cb | ||
![]() |
8c72947860 | ||
![]() |
f0b240a6ee | ||
![]() |
793cf53042 | ||
![]() |
612fc29197 | ||
![]() |
307e2389de | ||
![]() |
1c932abdc3 | ||
![]() |
314f0065c7 | ||
![]() |
59060bb74d | ||
![]() |
cab882c3a3 | ||
![]() |
9955532ce5 | ||
![]() |
7e127be8c2 | ||
![]() |
b46571a57f | ||
![]() |
da5861d907 | ||
![]() |
92bc91a1fb | ||
![]() |
439f50103e | ||
![]() |
8a95e31b2f | ||
![]() |
115b8a156a | ||
![]() |
120bcaebb5 | ||
![]() |
455d56707c | ||
![]() |
048adcf118 | ||
![]() |
fe5aa27b1c | ||
![]() |
039aa489b1 | ||
![]() |
14e80f0b06 | ||
![]() |
2256fff549 | ||
![]() |
84cfd4e138 | ||
![]() |
e70c229135 |
2
.github/workflows/build_latest.yml
vendored
2
.github/workflows/build_latest.yml
vendored
@@ -31,4 +31,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v1.0.0
|
||||
with:
|
||||
name: N_m3u8DL-CLI_latest
|
||||
path: N_m3u8DL-CLI\bin\Release\
|
||||
path: N_m3u8DL-CLI\bin\Release\N_m3u8DL-CLI.exe
|
||||
|
@@ -36,12 +36,14 @@ namespace N_m3u8DL_CLI
|
||||
public string MuxSetJson { get; set; } = string.Empty;
|
||||
public int TimeOut { get; set; } = 10000; //超时设置
|
||||
public static double DownloadedSize { get; set; } = 0; //已下载大小
|
||||
public static double ToDoSize { 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; //关闭完整性检查
|
||||
public static bool HasExtMap { get; set; } = false; //是否有MAP
|
||||
|
||||
static CancellationTokenSource cts = new CancellationTokenSource();
|
||||
//计算下载速度
|
||||
@@ -52,14 +54,24 @@ namespace N_m3u8DL_CLI
|
||||
timer.AutoReset = true;
|
||||
timer.Elapsed += delegate
|
||||
{
|
||||
Console.SetCursorPosition(0, 1);
|
||||
Console.Write("Speed: " + Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + " / s".PadRight(70));
|
||||
var eta = "";
|
||||
if (ToDoSize != 0)
|
||||
{
|
||||
eta = " @ " + Global.FormatTime(Convert.ToInt32(ToDoSize / (Global.BYTEDOWN / CalcTime)));
|
||||
}
|
||||
var print = Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + "/s" + eta;
|
||||
ProgressReporter.Report("", "(" + print + ")");
|
||||
|
||||
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));
|
||||
eta = "";
|
||||
if (ToDoSize != 0)
|
||||
{
|
||||
eta = " @ " + Global.FormatTime(Convert.ToInt32(ToDoSize / (Global.BYTEDOWN / CalcTime)));
|
||||
}
|
||||
print = Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + "/s [" + stopCount + "]" + eta;
|
||||
ProgressReporter.Report("", "(" + print + ")");
|
||||
|
||||
if (stopCount >= 12)
|
||||
{
|
||||
@@ -127,8 +139,6 @@ namespace N_m3u8DL_CLI
|
||||
watcher.PartsCount = PartsCount;
|
||||
watcher.WatcherStrat();
|
||||
|
||||
//开始计算速度
|
||||
timer.Enabled = true;
|
||||
cts = new CancellationTokenSource();
|
||||
|
||||
//开始调用下载
|
||||
@@ -136,8 +146,10 @@ namespace N_m3u8DL_CLI
|
||||
LOGGER.PrintLine(strings.startDownloading, LOGGER.Warning);
|
||||
|
||||
//下载MAP文件(若有)
|
||||
try
|
||||
downloadMap:
|
||||
if (HasExtMap)
|
||||
{
|
||||
LOGGER.PrintLine(strings.downloadingMapFile);
|
||||
Downloader sd = new Downloader();
|
||||
sd.TimeOut = TimeOut;
|
||||
sd.FileUrl = initJson["m3u8Info"]["extMAP"].Value<string>();
|
||||
@@ -155,12 +167,12 @@ namespace N_m3u8DL_CLI
|
||||
File.Delete(sd.SavePath);
|
||||
if (File.Exists(DownDir + "\\Part_0\\!MAP.ts"))
|
||||
File.Delete(DownDir + "\\Part_0\\!MAP.ts");
|
||||
LOGGER.PrintLine(strings.downloadingMapFile);
|
||||
sd.Down(); //开始下载
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//LOG.WriteLineError(e.ToString());
|
||||
if (!File.Exists(DownDir + "\\!MAP.ts")) //检测是否成功下载
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
goto downloadMap;
|
||||
}
|
||||
}
|
||||
|
||||
//首先下载第一个分片
|
||||
@@ -192,6 +204,8 @@ namespace N_m3u8DL_CLI
|
||||
if (File.Exists(sd.SavePath))
|
||||
File.Delete(sd.SavePath);
|
||||
LOGGER.PrintLine(strings.downloadingFirstSegement);
|
||||
//开始计算速度
|
||||
timer.Enabled = true;
|
||||
if (!Global.ShouldStop)
|
||||
sd.Down(); //开始下载
|
||||
}
|
||||
@@ -337,7 +351,6 @@ namespace N_m3u8DL_CLI
|
||||
else //开始合并
|
||||
{
|
||||
LOGGER.PrintLine(strings.downloadComplete + (DisableIntegrityCheck ? "(" + strings.disableIntegrityCheck + ")" : ""));
|
||||
Console.WriteLine();
|
||||
if (NoMerge == false)
|
||||
{
|
||||
string exePath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
|
||||
@@ -381,7 +394,6 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
if (Global.VIDEO_TYPE != "DV") //不是杜比视界
|
||||
{
|
||||
LOGGER.FFmpegCorsorIndex = LOGGER.CursorIndex;
|
||||
//检测是否为MPEG-TS封装,不是的话就转换为TS封装
|
||||
foreach (string s in Global.GetFiles(DownDir + "\\Part_0", ".ts"))
|
||||
{
|
||||
@@ -467,7 +479,6 @@ namespace N_m3u8DL_CLI
|
||||
DownDir = parser.DownDir;
|
||||
parser.Parse(); //开始解析
|
||||
Thread.Sleep(1000);
|
||||
LOGGER.CursorIndex = 5;
|
||||
Global.HadReadInfo = false;
|
||||
Global.VIDEO_TYPE = "";
|
||||
Global.AUDIO_TYPE = "";
|
||||
@@ -492,16 +503,13 @@ namespace N_m3u8DL_CLI
|
||||
DownDir = parser.DownDir;
|
||||
parser.Parse(); //开始解析
|
||||
Thread.Sleep(1000);
|
||||
LOGGER.CursorIndex = 5;
|
||||
Global.HadReadInfo = false;
|
||||
Global.VIDEO_TYPE = "";
|
||||
Global.AUDIO_TYPE = "";
|
||||
DoDownload();
|
||||
}
|
||||
LOGGER.PrintLine(strings.taskDone, LOGGER.Warning);
|
||||
Console.CursorVisible = true;
|
||||
Environment.Exit(0); //正常退出程序
|
||||
Console.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -540,7 +548,6 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
if (Global.VIDEO_TYPE != "DV") //不是爱奇艺杜比视界
|
||||
{
|
||||
LOGGER.FFmpegCorsorIndex = LOGGER.CursorIndex;
|
||||
//检测是否为MPEG-TS封装,不是的话就转换为TS封装
|
||||
foreach (string s in Global.GetFiles(DownDir, ".ts"))
|
||||
{
|
||||
@@ -615,7 +622,6 @@ namespace N_m3u8DL_CLI
|
||||
DownDir = parser.DownDir;
|
||||
parser.Parse(); //开始解析
|
||||
Thread.Sleep(1000);
|
||||
LOGGER.CursorIndex = 5;
|
||||
Global.HadReadInfo = false;
|
||||
Global.VIDEO_TYPE = "";
|
||||
Global.AUDIO_TYPE = "";
|
||||
@@ -640,17 +646,13 @@ namespace N_m3u8DL_CLI
|
||||
DownDir = parser.DownDir;
|
||||
parser.Parse(); //开始解析
|
||||
Thread.Sleep(1000);
|
||||
LOGGER.CursorIndex = 5;
|
||||
Global.HadReadInfo = false;
|
||||
Global.VIDEO_TYPE = "";
|
||||
Global.AUDIO_TYPE = "";
|
||||
DoDownload();
|
||||
}
|
||||
LOGGER.PrintLine(strings.taskDone, LOGGER.Warning);
|
||||
Console.CursorVisible = true;
|
||||
Environment.Exit(0); //正常退出程序
|
||||
|
||||
Console.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using Newtonsoft.Json;
|
||||
using BrotliSharpLib;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@@ -34,17 +35,11 @@ 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 = "20210325";
|
||||
static string nowDate = "20211120";
|
||||
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}...");
|
||||
Console.ResetColor(); //将控制台的前景色和背景色设为默认值
|
||||
Console.WriteLine("Speed: waiting");
|
||||
Console.WriteLine("Progress: waiting");
|
||||
Console.WriteLine($"N_m3u8DL-CLI version {nowVer} 2018-2021");
|
||||
Console.WriteLine($" built date: {nowDate}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
@@ -59,8 +54,8 @@ namespace N_m3u8DL_CLI
|
||||
Console.Title = string.Format(strings.newerVisionDetected, latestVer);
|
||||
try
|
||||
{
|
||||
//尝试下载新版本(去码云)
|
||||
string url = $"https://gitee.com/nilaoda/N_m3u8DL-CLI/raw/master/N_m3u8DL-CLI_v{latestVer}.exe";
|
||||
//尝试下载新版本
|
||||
string url = $"https://mirror.ghproxy.com/https://github.com/nilaoda/N_m3u8DL-CLI/releases/download/{latestVer}/N_m3u8DL-CLI_v{latestVer}.exe";
|
||||
if (File.Exists(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), $"N_m3u8DL-CLI_v{latestVer}.exe")))
|
||||
{
|
||||
Console.Title = string.Format(strings.newerVerisonDownloaded, latestVer);
|
||||
@@ -186,6 +181,20 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (webResponse.ContentEncoding != null
|
||||
&& webResponse.ContentEncoding.ToLower() == "br") //如果使用了Brotli则先解压
|
||||
{
|
||||
using (Stream streamReceive = webResponse.GetResponseStream())
|
||||
{
|
||||
using (var bs = new BrotliStream(streamReceive, CompressionMode.Decompress))
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(bs, Encoding.UTF8))
|
||||
{
|
||||
htmlCode = sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (Stream streamReceive = webResponse.GetResponseStream())
|
||||
@@ -697,27 +706,27 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[118] && 130 == u[119])
|
||||
else if (u.Length > 120 && 137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[118] && 130 == u[119])
|
||||
{
|
||||
u = u.Skip(120).ToArray();
|
||||
}
|
||||
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[6100] && 130 == u[6101])
|
||||
else if (u.Length > 6102 && 137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[6100] && 130 == u[6101])
|
||||
{
|
||||
u = u.Skip(6102).ToArray();
|
||||
}
|
||||
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[67] && 130 == u[68])
|
||||
else if (u.Length > 69 && 137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[67] && 130 == u[68])
|
||||
{
|
||||
u = u.Skip(69).ToArray();
|
||||
}
|
||||
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[769] && 130 == u[770])
|
||||
else if (u.Length > 771 && 137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[769] && 130 == u[770])
|
||||
{
|
||||
u = u.Skip(771).ToArray();
|
||||
}
|
||||
else if (137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3])
|
||||
else if (u.Length > 4 && 137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3])
|
||||
{
|
||||
//确定是PNG但是需要手动查询结尾标记 0x47 出现两次
|
||||
int skip = 0;
|
||||
for (int i = 4; i < u.Length - 188 * 2; i++)
|
||||
for (int i = 4; i < u.Length - 188 * 2 - 4; i++)
|
||||
{
|
||||
if (u[i] == 0x47 && u[i + 188] == 0x47 && u[i + 188 + 188] == 0x47)
|
||||
{
|
||||
|
116
N_m3u8DL-CLI/IqJsonParser.cs
Normal file
116
N_m3u8DL-CLI/IqJsonParser.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class IqJsonParser
|
||||
{
|
||||
public static string Parse(string downDir, string json)
|
||||
{
|
||||
JObject jObject = JObject.Parse(json);
|
||||
var aClips = jObject["payload"]["wm_a"]["audio_track1"]["files"].Value<JArray>();
|
||||
var vClips = jObject["payload"]["wm_a"]["video_track1"]["files"].Value<JArray>();
|
||||
|
||||
var codecsList = new List<string>();
|
||||
|
||||
var audioPath = "";
|
||||
var videoPath = "";
|
||||
var audioInitPath = "";
|
||||
var videoInitPath = "";
|
||||
|
||||
if (aClips.Count > 0)
|
||||
{
|
||||
var init = jObject["payload"]["wm_a"]["audio_track1"]["codec_init"].Value<string>();
|
||||
byte[] bytes = Convert.FromBase64String(init);
|
||||
//输出init文件
|
||||
audioInitPath = Path.Combine(downDir, "iqAudioInit.mp4");
|
||||
File.WriteAllBytes(audioInitPath, bytes);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("#EXTM3U");
|
||||
sb.AppendLine("#EXT-X-VERSION:3");
|
||||
sb.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
|
||||
sb.AppendLine("#CREATED-BY:N_m3u8DL-CLI");
|
||||
sb.AppendLine($"#EXT-CODEC:{jObject["payload"]["wm_a"]["audio_track1"]["codec"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-KID:{jObject["payload"]["wm_a"]["audio_track1"]["key_id"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-X-MAP:URI=\"{new Uri(Path.Combine(downDir + "(Audio)", "iqAudioInit.mp4")).ToString()}\"");
|
||||
sb.AppendLine("#EXT-X-KEY:METHOD=PLZ-KEEP-RAW,URI=\"None\"");
|
||||
foreach (var a in aClips)
|
||||
{
|
||||
sb.AppendLine($"#EXTINF:{a["duration_second"].ToString()}");
|
||||
sb.AppendLine(a["file_name"].Value<string>());
|
||||
}
|
||||
sb.AppendLine("#EXT-X-ENDLIST");
|
||||
//输出m3u8文件
|
||||
var _path = Path.Combine(downDir, "iqAudio.m3u8");
|
||||
File.WriteAllText(_path, sb.ToString());
|
||||
audioPath = new Uri(_path).ToString();
|
||||
codecsList.Add(jObject["payload"]["wm_a"]["audio_track1"]["codec"].Value<string>());
|
||||
}
|
||||
|
||||
if (vClips.Count > 0)
|
||||
{
|
||||
var init = jObject["payload"]["wm_a"]["video_track1"]["codec_init"].Value<string>();
|
||||
byte[] bytes = Convert.FromBase64String(init);
|
||||
//输出init文件
|
||||
videoInitPath = Path.Combine(downDir, "iqVideoInit.mp4");
|
||||
File.WriteAllBytes(videoInitPath, bytes);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("#EXTM3U");
|
||||
sb.AppendLine("#EXT-X-VERSION:3");
|
||||
sb.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
|
||||
sb.AppendLine("#CREATED-BY:N_m3u8DL-CLI");
|
||||
sb.AppendLine($"#EXT-CODEC:{jObject["payload"]["wm_a"]["video_track1"]["codec"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-KID:{jObject["payload"]["wm_a"]["video_track1"]["key_id"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-X-MAP:URI=\"{new Uri(videoInitPath).ToString()}\"");
|
||||
sb.AppendLine("#EXT-X-KEY:METHOD=PLZ-KEEP-RAW,URI=\"None\"");
|
||||
foreach (var a in vClips)
|
||||
{
|
||||
var start = a["seekable"]["pos_start"].Value<long>();
|
||||
var size = a["size"].Value<long>();
|
||||
sb.AppendLine($"#EXTINF:{a["duration_second"].ToString()}");
|
||||
sb.AppendLine($"#EXT-X-BYTERANGE:{size}@{start}");
|
||||
sb.AppendLine(a["file_name"].Value<string>());
|
||||
}
|
||||
sb.AppendLine("#EXT-X-ENDLIST");
|
||||
//输出m3u8文件
|
||||
var _path = Path.Combine(downDir, "iqVideo.m3u8");
|
||||
File.WriteAllText(_path, sb.ToString());
|
||||
videoPath = new Uri(_path).ToString();
|
||||
codecsList.Add(jObject["payload"]["wm_a"]["video_track1"]["codec"].Value<string>());
|
||||
}
|
||||
|
||||
var content = "";
|
||||
if ((videoPath == "" && audioPath != "") || Global.VIDEO_TYPE == "IGNORE")
|
||||
{
|
||||
return audioPath;
|
||||
}
|
||||
else if (audioPath == "" && videoPath != "")
|
||||
{
|
||||
return videoPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Directory.Exists(downDir + "(Audio)"))
|
||||
Directory.CreateDirectory(downDir + "(Audio)");
|
||||
var _path = Path.Combine(downDir + "(Audio)", "iqAudio.m3u8");
|
||||
var _pathInit = Path.Combine(downDir + "(Audio)", "iqAudioInit.mp4");
|
||||
File.Copy(new Uri(audioPath).LocalPath, _path, true);
|
||||
File.Copy(new Uri(audioInitPath).LocalPath, _pathInit, true);
|
||||
audioPath = new Uri(_path).ToString();
|
||||
content = $"#EXTM3U\r\n" +
|
||||
$"#EXT-X-MEDIA:TYPE=AUDIO,URI=\"{audioPath}\",GROUP-ID=\"default-audio-group\",NAME=\"stream_0\",AUTOSELECT=YES,CHANNELS=\"0\"\r\n" +
|
||||
$"#EXT-X-STREAM-INF:BANDWIDTH=99999,CODECS=\"{string.Join(",", codecsList)}\",RESOLUTION=0x0,AUDIO=\"default-audio-group\"\r\n" +
|
||||
$"{videoPath}";
|
||||
}
|
||||
|
||||
var _masterPath = Path.Combine(downDir, "master.m3u8");
|
||||
File.WriteAllText(_masterPath, content);
|
||||
return new Uri(_masterPath).ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,8 +11,6 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
class LOGGER
|
||||
{
|
||||
public static int CursorIndex = 5;
|
||||
public static int FFmpegCorsorIndex = 5;
|
||||
public const int Default = 1;
|
||||
public const int Error = 2;
|
||||
public const int Warning = 3;
|
||||
@@ -36,8 +34,13 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
if (!Directory.Exists(Path.GetDirectoryName(LOGFILE)))//若文件夹不存在则新建文件夹
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(LOGFILE)); //新建文件夹
|
||||
if (File.Exists(LOGFILE))//若文件存在则删除
|
||||
File.Delete(LOGFILE);
|
||||
//若文件存在则加序号
|
||||
int index = 1;
|
||||
var fileName = Path.GetFileNameWithoutExtension(LOGFILE);
|
||||
while (File.Exists(LOGFILE))
|
||||
{
|
||||
LOGFILE = Path.Combine(Path.GetDirectoryName(LOGFILE), $"{fileName}-{index++}.log");
|
||||
}
|
||||
string file = LOGFILE;
|
||||
string now = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
string init = "LOG " + DateTime.Now.ToString("yyyy/MM/dd") + "\r\n"
|
||||
@@ -57,40 +60,28 @@ namespace N_m3u8DL_CLI
|
||||
//读写锁机制,当资源被占用,其他线程等待
|
||||
static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
|
||||
|
||||
public static void PrintLine(string text, int printLevel = 1, int cursorIndex = 0)
|
||||
public static void PrintLine(string text, int printLevel = 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CursorIndex > 1000)
|
||||
{
|
||||
Console.Clear();
|
||||
CursorIndex = 0;
|
||||
}
|
||||
if (cursorIndex == 0)
|
||||
Console.SetCursorPosition(0, CursorIndex++);
|
||||
else
|
||||
Console.SetCursorPosition(0, cursorIndex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
;
|
||||
}
|
||||
switch (printLevel)
|
||||
{
|
||||
case 0:
|
||||
Console.Write("\r" + new string(' ', Console.WindowWidth - 1) + "\r");
|
||||
Console.WriteLine(" ".PadRight(12) + " " + text);
|
||||
break;
|
||||
case 1:
|
||||
Console.Write("\r" + new string(' ', Console.WindowWidth - 1) + "\r");
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.WriteLine(text);
|
||||
break;
|
||||
case 2:
|
||||
Console.Write("\r" + new string(' ', Console.WindowWidth - 1) + "\r");
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(text);
|
||||
Console.ResetColor();
|
||||
break;
|
||||
case 3:
|
||||
Console.Write("\r" + new string(' ', Console.WindowWidth - 1) + "\r");
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
Console.WriteLine(text);
|
||||
@@ -118,7 +109,7 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -143,7 +134,7 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -156,7 +147,7 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(DateTime.Now.ToString("o") + " " + text);
|
||||
while (Console.ForegroundColor == ConsoleColor.Red)
|
||||
while (Console.ForegroundColor == ConsoleColor.Red)
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
@@ -135,7 +135,16 @@ namespace N_m3u8DL_CLI
|
||||
XmlDocument mpdDoc = new XmlDocument();
|
||||
mpdDoc.LoadXml(mpdContent);
|
||||
|
||||
XmlNode xn = mpdDoc.LastChild;
|
||||
XmlNode xn = null;
|
||||
//Select MPD node
|
||||
foreach (XmlNode node in mpdDoc.ChildNodes)
|
||||
{
|
||||
if (node.NodeType == XmlNodeType.Element && node.Name == "MPD")
|
||||
{
|
||||
xn = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var mediaPresentationDuration = ((XmlElement)xn).GetAttribute("mediaPresentationDuration");
|
||||
var ns = ((XmlElement)xn).GetAttribute("xmlns");
|
||||
|
||||
@@ -145,9 +154,11 @@ namespace N_m3u8DL_CLI
|
||||
TimeSpan ts = XmlConvert.ToTimeSpan(mediaPresentationDuration); //时长
|
||||
|
||||
var formatList = new List<Dictionary<string, dynamic>>(); //存放所有音视频清晰度
|
||||
var periodIndex = 0; //解决同一个period且同id导致被重复添加分片
|
||||
|
||||
foreach (XmlElement period in xn.SelectNodes("ns:Period", nsMgr))
|
||||
{
|
||||
periodIndex++;
|
||||
var periodDuration = string.IsNullOrEmpty(period.GetAttribute("duration")) ? XmlConvert.ToTimeSpan(mediaPresentationDuration) : XmlConvert.ToTimeSpan(period.GetAttribute("duration"));
|
||||
var periodMsInfo = ExtractMultisegmentInfo(period, nsMgr, new Dictionary<string, dynamic>()
|
||||
{
|
||||
@@ -224,6 +235,7 @@ namespace N_m3u8DL_CLI
|
||||
var bandwidth = IntOrNull(GetAttribute("bandwidth"));
|
||||
var f = new Dictionary<string, dynamic>
|
||||
{
|
||||
["PeriodIndex"] = periodIndex,
|
||||
["ContentType"] = contentType,
|
||||
["FormatId"] = representationId,
|
||||
["ManifestUrl"] = mpdUrl,
|
||||
@@ -448,7 +460,7 @@ namespace N_m3u8DL_CLI
|
||||
f["FragmentBaseUrl"] = baseUrl;
|
||||
if (representationMsInfo.ContainsKey("InitializationUrl"))
|
||||
{
|
||||
f["InitializationUrl"] = representationMsInfo["InitializationUrl"];
|
||||
f["InitializationUrl"] = CombineURL(baseUrl, representationMsInfo["InitializationUrl"]);
|
||||
if (f["InitializationUrl"].StartsWith("$$Range"))
|
||||
{
|
||||
f["InitializationUrl"] = CombineURL(baseUrl, f["InitializationUrl"]);
|
||||
@@ -473,7 +485,8 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
for (int i = 0; i < formatList.Count; i++)
|
||||
{
|
||||
if (formatList[i]["FormatId"] == f["FormatId"] && formatList[i]["Width"] == f["Width"] && formatList[i]["ContentType"] == f["ContentType"])
|
||||
//参数相同但不在同一个Period才可以
|
||||
if (formatList[i]["FormatId"] == f["FormatId"] && formatList[i]["Width"] == f["Width"] && formatList[i]["ContentType"] == f["ContentType"] && formatList[i]["PeriodIndex"] != f["PeriodIndex"])
|
||||
{
|
||||
formatList[i]["Fragments"].AddRange(f["Fragments"]);
|
||||
break;
|
||||
@@ -510,7 +523,7 @@ namespace N_m3u8DL_CLI
|
||||
{
|
||||
string Stringify(Dictionary<string, dynamic> f)
|
||||
{
|
||||
var type = f["ContentType"] == "aduio" ? "Audio" : "Video";
|
||||
var type = f["ContentType"] == "audio" ? "Audio" : "Video";
|
||||
var res = type == "Video" ? $"[{f["Width"]}x{f["Height"]}]" : "";
|
||||
var id = $"[{f["FormatId"]}] ";
|
||||
var tbr = $"[{((int)f["Tbr"]).ToString().PadLeft(4)} Kbps] ";
|
||||
@@ -521,24 +534,23 @@ namespace N_m3u8DL_CLI
|
||||
return $"{type} => {id}{tbr}{asr}{fps}{lang}{codecs}{res}";
|
||||
}
|
||||
|
||||
var startCursorIndex = LOGGER.CursorIndex;
|
||||
var startCursorIndex = Console.CursorTop;
|
||||
var cursorIndex = startCursorIndex;
|
||||
for (int i = 0; i < formatList.Count; i++)
|
||||
{
|
||||
Console.WriteLine("".PadRight(13) + $"[{i.ToString().PadLeft(2)}]. {Stringify(formatList[i])}");
|
||||
LOGGER.CursorIndex++;
|
||||
cursorIndex++;
|
||||
}
|
||||
Console.CursorVisible = true;
|
||||
LOGGER.PrintLine("Found Multiple Language Audio Tracks.\r\n" + "".PadRight(13) + "Please Select What You Want(Up to 1 Video and 1 Audio).");
|
||||
Console.Write("".PadRight(13) + "Enter Numbers Separated By A Space: ");
|
||||
var input = Console.ReadLine();
|
||||
LOGGER.CursorIndex += 2;
|
||||
Console.CursorVisible = false;
|
||||
for (int i = startCursorIndex; i < LOGGER.CursorIndex; i++)
|
||||
cursorIndex += 2;
|
||||
for (int i = startCursorIndex; i < cursorIndex; i++)
|
||||
{
|
||||
Console.SetCursorPosition(0, i);
|
||||
Console.Write("".PadRight(300));
|
||||
}
|
||||
LOGGER.CursorIndex = startCursorIndex;
|
||||
Console.SetCursorPosition(0, startCursorIndex);
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
bestVideo = new Dictionary<string, dynamic>() { ["Tbr"] = 0 };
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props')" />
|
||||
<Import Project="..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props')" />
|
||||
<Import Project="..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props" Condition="Exists('..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -41,9 +42,14 @@
|
||||
<ApplicationIcon>logo_3Iv_icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BrotliSharpLib, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3f4e2a1cd615fcb7, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\BrotliSharpLib.0.3.3\lib\net451\BrotliSharpLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Costura, Version=4.1.0.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Costura.Fody.4.1.0\lib\net40\Costura.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<Reference Include="Microsoft.Build.Utilities.v4.0" />
|
||||
<Reference Include="Microsoft.JScript" />
|
||||
<Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
@@ -57,6 +63,9 @@
|
||||
<Reference Include="System.Collections" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.IO" />
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
@@ -78,11 +87,13 @@
|
||||
<Compile Include="Global.cs" />
|
||||
<Compile Include="HLSLiveDownloader.cs" />
|
||||
<Compile Include="HLSTags.cs" />
|
||||
<Compile Include="IqJsonParser.cs" />
|
||||
<Compile Include="LOGGER.cs" />
|
||||
<Compile Include="DownloadManager.cs" />
|
||||
<Compile Include="MPDParser.cs" />
|
||||
<Compile Include="Parser.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="ProgressReporter.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Downloader.cs" />
|
||||
<Compile Include="strings.Designer.cs">
|
||||
@@ -137,12 +148,13 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\packages\Fody.6.0.0\build\Fody.targets" Condition="Exists('..\packages\Fody.6.0.0\build\Fody.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Fody.6.0.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.6.0.0\build\Fody.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Fody.6.0.0\build\Fody.targets" Condition="Exists('..\packages\Fody.6.0.0\build\Fody.targets')" />
|
||||
</Project>
|
||||
|
@@ -102,8 +102,13 @@ namespace N_m3u8DL_CLI
|
||||
|
||||
|
||||
//获取m3u8内容
|
||||
if (!LiveStream)
|
||||
LOGGER.PrintLine(strings.downloadingM3u8, LOGGER.Warning);
|
||||
//if (!LiveStream)
|
||||
// LOGGER.PrintLine(strings.downloadingM3u8, LOGGER.Warning);
|
||||
|
||||
if (M3u8Url.Contains(".cntv."))
|
||||
{
|
||||
M3u8Url = M3u8Url.Replace("/h5e/", "/");
|
||||
}
|
||||
|
||||
if (M3u8Url.StartsWith("http"))
|
||||
{
|
||||
@@ -139,15 +144,10 @@ namespace N_m3u8DL_CLI
|
||||
m3u8Content = DecodeImooc.DecodeM3u8(m3u8Content);
|
||||
}
|
||||
|
||||
if (M3u8Url.Contains("cntv.qcloudcdn.com"))
|
||||
{
|
||||
M3u8Url = M3u8Url.Replace("/h5e/", "/");
|
||||
}
|
||||
|
||||
if (m3u8Content.Contains("</MPD>") && m3u8Content.Contains("<MPD"))
|
||||
{
|
||||
LOGGER.PrintLine(strings.startParsingMpd, LOGGER.Warning);
|
||||
LOGGER.WriteLine(strings.startParsingMpd);
|
||||
//LOGGER.PrintLine(strings.startParsingMpd, LOGGER.Warning);
|
||||
//LOGGER.WriteLine(strings.startParsingMpd);
|
||||
var mpdSavePath = Path.Combine(DownDir, "dash.mpd");
|
||||
//输出mpd文件
|
||||
File.WriteAllText(mpdSavePath, m3u8Content);
|
||||
@@ -158,6 +158,17 @@ namespace N_m3u8DL_CLI
|
||||
m3u8Content = File.ReadAllText(new Uri(M3u8Url).LocalPath);
|
||||
}
|
||||
|
||||
if (m3u8Content.StartsWith("{\"payload\""))
|
||||
{
|
||||
var iqJsonPath = Path.Combine(DownDir, "iq.json");
|
||||
//输出mpd文件
|
||||
File.WriteAllText(iqJsonPath, m3u8Content);
|
||||
//分析json文件
|
||||
var newUri = IqJsonParser.Parse(DownDir, m3u8Content);
|
||||
M3u8Url = newUri;
|
||||
m3u8Content = File.ReadAllText(new Uri(M3u8Url).LocalPath);
|
||||
}
|
||||
|
||||
//输出m3u8文件
|
||||
File.WriteAllText(m3u8SavePath, m3u8Content);
|
||||
|
||||
@@ -182,10 +193,10 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
|
||||
//针对AppleTv修正
|
||||
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && M3u8Url.Contains(".apple.com/"))
|
||||
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && (M3u8Url.Contains(".apple.com/") || Regex.IsMatch(m3u8Content, "#EXT-X-MAP.*\\.apple\\.com/")))
|
||||
{
|
||||
//只取加密部分即可
|
||||
Regex ykmap = new Regex("(#EXT-X-KEY:[\\s\\S]*?)#EXT-X-DISCONTINUITY");
|
||||
Regex ykmap = new Regex("(#EXT-X-KEY:[\\s\\S]*?)(#EXT-X-DISCONTINUITY|#EXT-X-ENDLIST)");
|
||||
if (ykmap.IsMatch(m3u8Content))
|
||||
{
|
||||
m3u8Content = "#EXTM3U\r\n" + ykmap.Match(m3u8Content).Groups[1].Value + "\r\n#EXT-X-ENDLIST";
|
||||
@@ -201,11 +212,11 @@ namespace N_m3u8DL_CLI
|
||||
BaseUrl = GetBaseUrl(M3u8Url, Headers);
|
||||
}
|
||||
|
||||
if (!LiveStream)
|
||||
{
|
||||
LOGGER.WriteLine(strings.parsingM3u8);
|
||||
LOGGER.PrintLine(strings.parsingM3u8);
|
||||
}
|
||||
//if (!LiveStream)
|
||||
//{
|
||||
// LOGGER.WriteLine(strings.parsingM3u8);
|
||||
// LOGGER.PrintLine(strings.parsingM3u8);
|
||||
//}
|
||||
|
||||
if (!string.IsNullOrEmpty(KeyBase64))
|
||||
{
|
||||
@@ -542,7 +553,7 @@ namespace N_m3u8DL_CLI
|
||||
}
|
||||
sb.Append("}");
|
||||
extLists.Add(sb.ToString().Replace(",}", "}"));
|
||||
if (Convert.ToInt64(extList[0]) > bestBandwidth)
|
||||
if (Convert.ToInt64(extList[0]) >= bestBandwidth)
|
||||
{
|
||||
bestBandwidth = Convert.ToInt64(extList[0]);
|
||||
bestUrl = listUrl;
|
||||
@@ -606,25 +617,24 @@ namespace N_m3u8DL_CLI
|
||||
//多种音频语言 让用户选择
|
||||
else
|
||||
{
|
||||
var startCursorIndex = LOGGER.CursorIndex;
|
||||
var startCursorIndex = Console.CursorTop;
|
||||
var cursorIndex = startCursorIndex;
|
||||
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++;
|
||||
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++)
|
||||
cursorIndex += 2;
|
||||
for (int i = startCursorIndex; i < cursorIndex; i++)
|
||||
{
|
||||
Console.SetCursorPosition(0, i);
|
||||
Console.Write("".PadRight(300));
|
||||
}
|
||||
LOGGER.CursorIndex = startCursorIndex;
|
||||
Console.SetCursorPosition(0, startCursorIndex);
|
||||
audioUrl = MEDIA_AUDIO_GROUP[bestUrlAudio][int.Parse(input)].Uri;
|
||||
}
|
||||
}
|
||||
@@ -637,25 +647,24 @@ namespace N_m3u8DL_CLI
|
||||
//多种字幕语言 让用户选择
|
||||
else
|
||||
{
|
||||
var startCursorIndex = LOGGER.CursorIndex;
|
||||
var startCursorIndex = Console.CursorTop;
|
||||
var cursorIndex = startCursorIndex;
|
||||
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++;
|
||||
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++)
|
||||
cursorIndex += 2;
|
||||
for (int i = startCursorIndex; i < cursorIndex; i++)
|
||||
{
|
||||
Console.SetCursorPosition(0, i);
|
||||
Console.Write("".PadRight(300));
|
||||
}
|
||||
LOGGER.CursorIndex = startCursorIndex;
|
||||
Console.SetCursorPosition(0, startCursorIndex);
|
||||
subUrl = MEDIA_SUB_GROUP[bestUrlSub][int.Parse(input)].Uri;
|
||||
}
|
||||
}
|
||||
@@ -665,11 +674,16 @@ namespace N_m3u8DL_CLI
|
||||
jsonM3u8Info.Add("sub", subUrl);
|
||||
if (extMAP[0] != "")
|
||||
{
|
||||
DownloadManager.HasExtMap = true;
|
||||
if (extMAP[1] == "")
|
||||
jsonM3u8Info.Add("extMAP", extMAP[0]);
|
||||
else
|
||||
jsonM3u8Info.Add("extMAP", extMAP[0] + "|" + extMAP[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
DownloadManager.HasExtMap = false;
|
||||
}
|
||||
|
||||
//根据DurRange来生成分片Range
|
||||
if (DurStart != "" || DurEnd != "")
|
||||
|
@@ -28,14 +28,10 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
case 0:
|
||||
LOGGER.WriteLine(strings.ExitedCtrlC
|
||||
+ "\r\n\r\nTask End: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")); //Ctrl+C关闭
|
||||
Console.CursorVisible = true;
|
||||
Console.SetCursorPosition(0, LOGGER.CursorIndex);
|
||||
break;
|
||||
case 2:
|
||||
LOGGER.WriteLine(strings.ExitedForce
|
||||
+ "\r\n\r\nTask End: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")); //按控制台关闭按钮关闭
|
||||
Console.CursorVisible = true;
|
||||
Console.SetCursorPosition(0, LOGGER.CursorIndex);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
@@ -279,6 +275,7 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
{
|
||||
Parser.DurStart = reg2.Match(p).Groups[1].Value;
|
||||
Parser.DurEnd = reg2.Match(p).Groups[5].Value;
|
||||
if (Parser.DurEnd == "00:00:00") Parser.DurEnd = "";
|
||||
Parser.DelAd = false;
|
||||
}
|
||||
}
|
||||
@@ -335,15 +332,14 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
testurl = args[0];
|
||||
else
|
||||
{
|
||||
Console.CursorVisible = true;
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.Write("N_m3u8DL-CLI");
|
||||
Console.ResetColor();
|
||||
Console.Write(" > ");
|
||||
|
||||
args = Global.ParseArguments(Console.ReadLine()).ToArray(); //解析命令行
|
||||
Console.Clear();
|
||||
Global.WriteInit();
|
||||
Console.CursorVisible = false;
|
||||
goto parseArgs;
|
||||
}
|
||||
|
||||
@@ -376,7 +372,6 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
|
||||
//开始解析
|
||||
|
||||
Console.CursorVisible = false;
|
||||
LOGGER.PrintLine($"{strings.fileName}{fileName}");
|
||||
LOGGER.PrintLine($"{strings.savePath}{Path.GetDirectoryName(Path.Combine(workDir, fileName))}");
|
||||
|
||||
@@ -391,10 +386,10 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
parser.BaseUrl = baseUrl;
|
||||
parser.Headers = reqHeaders;
|
||||
string exePath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
|
||||
LOGGER.LOGFILE = Path.Combine(exePath, "Logs", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".log");
|
||||
LOGGER.LOGFILE = Path.Combine(exePath, "Logs", DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss-fff") + ".log");
|
||||
LOGGER.InitLog();
|
||||
LOGGER.WriteLine(strings.startParsing + testurl);
|
||||
LOGGER.PrintLine(strings.startParsing, LOGGER.Warning);
|
||||
LOGGER.PrintLine(strings.startParsing + " " + testurl, LOGGER.Warning);
|
||||
if (testurl.EndsWith(".json") && File.Exists(testurl)) //可直接跳过解析
|
||||
{
|
||||
if (!Directory.Exists(Path.Combine(workDir, fileName)))//若文件夹不存在则新建文件夹
|
||||
@@ -428,7 +423,6 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
DirectoryInfo directoryInfo = new DirectoryInfo(Path.Combine(workDir, fileName));
|
||||
directoryInfo.Delete(true);
|
||||
LOGGER.PrintLine(strings.InvalidUri, LOGGER.Error);
|
||||
LOGGER.CursorIndex = 5;
|
||||
inputRetryCount--;
|
||||
goto input;
|
||||
}
|
||||
@@ -486,14 +480,12 @@ namespace N_m3u8DL_CLI.NetCore
|
||||
HTTPListener.StartListening();*/
|
||||
LOGGER.WriteLineError(strings.downloadFailed);
|
||||
LOGGER.PrintLine(strings.downloadFailed, LOGGER.Error);
|
||||
Console.CursorVisible = true;
|
||||
Thread.Sleep(3000);
|
||||
Environment.Exit(-1);
|
||||
//Console.Write("按任意键继续..."); Console.ReadKey(); return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.CursorVisible = true;
|
||||
LOGGER.PrintLine(ex.Message, LOGGER.Error);
|
||||
}
|
||||
}
|
||||
|
30
N_m3u8DL-CLI/ProgressReporter.cs
Normal file
30
N_m3u8DL-CLI/ProgressReporter.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class ProgressReporter
|
||||
{
|
||||
private static string speed = "";
|
||||
private static string progress = "";
|
||||
|
||||
static object lockThis = new object();
|
||||
public static void Report(string progress, string speed)
|
||||
{
|
||||
lock (lockThis)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(progress)) ProgressReporter.progress = progress;
|
||||
if (!string.IsNullOrEmpty(speed)) ProgressReporter.speed = speed;
|
||||
string now = DateTime.Now.ToString("HH:mm:ss.000");
|
||||
var sub = Console.WindowWidth - 4 - ProgressReporter.progress.Length - ProgressReporter.speed.Length - now.Length;
|
||||
if (sub <= 0) sub = 0;
|
||||
string print = now + " " + ProgressReporter.progress + " " + ProgressReporter.speed + new string(' ', sub);
|
||||
Console.Write("\r" + print + "\r");
|
||||
//Console.Write(print);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("nilaoda")]
|
||||
[assembly: AssemblyProduct("N_m3u8DL-CLI")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2021")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
||||
// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
|
||||
// 方法是按如下所示使用“*”: :
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.7.0.0")]
|
||||
[assembly: AssemblyVersion("2.9.8.0")]
|
||||
[assembly: AssemblyFileVersion("2.9.8.0")]
|
||||
|
@@ -59,10 +59,12 @@ namespace N_m3u8DL_CLI
|
||||
//Console.Title = Now + " / " + Total;
|
||||
string downloadedSize = Global.FormatFileSize(DownloadManager.DownloadedSize);
|
||||
string estimatedSize = Global.FormatFileSize(DownloadManager.DownloadedSize * total / now);
|
||||
int padding = downloadedSize.Length > estimatedSize.Length ? downloadedSize.Length : estimatedSize.Length;
|
||||
DownloadManager.ToDoSize = (DownloadManager.DownloadedSize * total / now) - DownloadManager.DownloadedSize;
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
Console.SetCursorPosition(0, 2);
|
||||
Console.Write(("Progress: " + Now + " of " + Total
|
||||
+ $" ({percent}/{downloadedSize}/{estimatedSize}/{Global.FormatTime(Convert.ToInt32(TotalDuration))})").PadRight(62));
|
||||
var print = "Progress: " + Now + "/" + Total
|
||||
+ $" ({percent}) -- {downloadedSize.PadLeft(padding)}/{estimatedSize.PadRight(padding)}";
|
||||
ProgressReporter.Report(print, "");
|
||||
}
|
||||
|
||||
private void OnRenamed(object source, RenamedEventArgs e)
|
||||
@@ -77,10 +79,12 @@ namespace N_m3u8DL_CLI
|
||||
//Console.Title = Now + " / " + Total;
|
||||
string downloadedSize = Global.FormatFileSize(DownloadManager.DownloadedSize);
|
||||
string estimatedSize = Global.FormatFileSize(DownloadManager.DownloadedSize * total / now);
|
||||
int padding = downloadedSize.Length > estimatedSize.Length ? downloadedSize.Length : estimatedSize.Length;
|
||||
DownloadManager.ToDoSize = (DownloadManager.DownloadedSize * total / now) - DownloadManager.DownloadedSize;
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
Console.SetCursorPosition(0, 2);
|
||||
Console.Write(("Progress: " + Now + " of " + Total
|
||||
+ $" ({percent}/{downloadedSize}/{estimatedSize}/{Global.FormatTime(Convert.ToInt32(TotalDuration))})").PadRight(62));
|
||||
var print = "Progress: " + Now + "/" + Total
|
||||
+ $" ({percent}) -- {downloadedSize.PadLeft(padding)}/{estimatedSize.PadRight(padding)}";
|
||||
ProgressReporter.Report(print, "");
|
||||
}
|
||||
|
||||
private void OnDeleted(object source, FileSystemEventArgs e)
|
||||
@@ -95,10 +99,12 @@ namespace N_m3u8DL_CLI
|
||||
//Console.Title = Now + " / " + Total;
|
||||
string downloadedSize = Global.FormatFileSize(DownloadManager.DownloadedSize);
|
||||
string estimatedSize = Global.FormatFileSize(DownloadManager.DownloadedSize * total / now);
|
||||
int padding = downloadedSize.Length > estimatedSize.Length ? downloadedSize.Length : estimatedSize.Length;
|
||||
DownloadManager.ToDoSize = (DownloadManager.DownloadedSize * total / now) - DownloadManager.DownloadedSize;
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
Console.SetCursorPosition(0, 2);
|
||||
Console.Write(("Progress: " + Now + " of " + Total
|
||||
+ $" ({percent}/{downloadedSize}/{estimatedSize}/{Global.FormatTime(Convert.ToInt32(TotalDuration))})").PadRight(62));
|
||||
var print = "Progress: " + Now + "/" + Total
|
||||
+ $" ({percent}) -- {downloadedSize.PadLeft(padding)}/{estimatedSize.PadRight(padding)}";
|
||||
ProgressReporter.Report(print, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -337,4 +337,26 @@
|
||||
- 适配AppleTv资源
|
||||
2021年3月25日
|
||||
- 优化下载监控
|
||||
- 为下载分片增加了自动重试机制(3次)
|
||||
- 为下载分片增加了自动重试机制(3次)
|
||||
2021年3月27日
|
||||
- 优化显示输出
|
||||
- 增加ETA显示
|
||||
2021年6月27日
|
||||
- 修正判断png图片时可能出现的数组越界bug
|
||||
- 支持解压brotli(测试地址 https://www.baobuzz.com/m3u8/236963.m3u8?sign=811ae52382b7dd1d247f705e1bcaddf4)
|
||||
2021年7月4日
|
||||
- 优化master选择最高清晰度逻辑(大于改为大于等于)
|
||||
- 支持爱奇艺DRM-JSON自动转换为m3u8
|
||||
2021年8月15日
|
||||
- 优化显示输出
|
||||
- 强校验MAP下载成功
|
||||
2021年9月5日
|
||||
- 修复MPD节点选择BUG
|
||||
- 修复速度输出padding负值问题
|
||||
- 修复同一个period且同id导致被重复添加分片
|
||||
- 优化AppleTV判断
|
||||
2021年10月19日
|
||||
- 修复选择清晰度在输入选项后界面异常问题
|
||||
- 修复日志冲突问题
|
||||
2021年11月12日
|
||||
- 修复init url缺失baseurl问题
|
@@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Fody" version="6.0.0" targetFramework="net46" developmentDependency="true" />
|
||||
<package id="BrotliSharpLib" version="0.3.3" targetFramework="net46" />
|
||||
<package id="Costura.Fody" version="4.1.0" targetFramework="net46" />
|
||||
<package id="Fody" version="6.0.0" targetFramework="net46" developmentDependency="true" />
|
||||
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net46" />
|
||||
<package id="NiL.JS" version="2.5.1428" targetFramework="net46" />
|
||||
<package id="Resource.Embedder" version="2.1.1" targetFramework="net46" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net46" />
|
||||
</packages>
|
@@ -13,9 +13,13 @@
|
||||
|
||||
|
||||
# [ENGLISH VERSION](https://github.com/nilaoda/N_m3u8DL-CLI/blob/master/README_ENG.md)
|
||||
|
||||
# 下载使用
|
||||
* 发行版: https://github.com/nilaoda/N_m3u8DL-CLI/releases
|
||||
* 自动构建版`(供测试)`: https://github.com/nilaoda/N_m3u8DL-CLI/actions
|
||||
|
||||
# 关于开源
|
||||
本项目已与2019年10月9日开源,采用MIT许可证,各取所需。
|
||||
本项目已于2019年10月9日开源,采用MIT许可证,各取所需。
|
||||
|
||||
# 关于跨平台
|
||||
搁置了
|
||||
@@ -81,5 +85,8 @@ N_m3u8DL-CLI.exe <URL|File|JSON> [OPTIONS]
|
||||
# 用户文档
|
||||
https://nilaoda.github.io/N_m3u8DL-CLI/
|
||||
|
||||
# 聊聊
|
||||
https://discord.gg/W5tvcRJDPs
|
||||
|
||||
# 赞赏
|
||||

|
||||
|
@@ -8,10 +8,10 @@
|
||||
╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚════╝ ╚═════╝ ╚══════╝ ╚═════╝╚══════╝╚═╝
|
||||
|
||||
```
|
||||
This is a m3u8 downloader.
|
||||
This is an m3u8 downloader.
|
||||
## Summary
|
||||
Supports:
|
||||
* Auto deceypt for `AES-128-CBC`
|
||||
* Auto decrypt for `AES-128-CBC`
|
||||
* `Master List`
|
||||
* Live stream recording(`BETA`)
|
||||
* Customize HTTP headers
|
||||
@@ -20,8 +20,8 @@ Supports:
|
||||
* Network driver on Windows OS
|
||||
* Alternative audio/video track
|
||||
* Mux without video track
|
||||
* Costom HTTP proxy or Use system proxy
|
||||
* Optimization for Chinese streaming platform
|
||||
* Custom HTTP proxy or Use system proxy
|
||||
* Optimization for Chinese streaming platforms
|
||||
|
||||

|
||||
|
||||
@@ -50,7 +50,7 @@ N_m3u8DL-CLI.exe <URL|JSON|FILE> [OPTIONS]
|
||||
--proxyAddress http://xx Set HTTP Proxy, like http://127.0.0.1:8080
|
||||
--enableDelAfterDone Enable delete clips after download completed
|
||||
--enableMuxFastStart Enable fast start for mp4
|
||||
--enableBinaryMerge Enable use binary merge instead ffmpeg
|
||||
--enableBinaryMerge Enable use binary merge instead of ffmpeg
|
||||
--enableParseOnly Enable parse mode
|
||||
--enableAudioOnly Enable only audio track when mux use ffmpeg
|
||||
--disableDateInfo Disable write date info when mux use ffmpeg
|
||||
@@ -61,3 +61,6 @@ N_m3u8DL-CLI.exe <URL|JSON|FILE> [OPTIONS]
|
||||
|
||||
## Document
|
||||
https://nilaoda.github.io/N_m3u8DL-CLI/
|
||||
|
||||
## Chit-chat
|
||||
https://discord.gg/W5tvcRJDPs
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user