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

Compare commits

...

10 Commits
2.6.0 ... 2.7.1

Author SHA1 Message Date
nilaoda
ba40641a21 v2.7.1 2020-07-19 09:24:24 +08:00
nilaoda
5d76418780 v2.7.0
优酷杜比视界下载逻辑优化;支持IMOCO m3u8/key解密;从当前路径和exe路径同时寻找ffmpeg;支持多语言本地化(简繁英)
2020-07-18 17:20:28 +08:00
nilaoda
fd48b766b5 v2.6.3 2020-04-17 22:54:57 +08:00
nilaoda
0f25cc0ae8 v2.6.2 2020-04-17 20:17:56 +08:00
nilaoda
1c6bd688e3 Merge branch 'master' of https://github.com/nilaoda/N_m3u8DL-CLI 2020-04-17 19:25:37 +08:00
nilaoda
480857cc3b v2.6.1 2020-04-17 19:25:32 +08:00
nilaoda
52af9a44a8 Update README.md 2020-03-12 14:39:29 +08:00
nilaoda
84d137b504 Update README.md 2020-03-12 00:50:16 +08:00
nilaoda
a4f1064c81 Update README.md 2020-03-11 21:21:25 +08:00
nilaoda
eac08b12f8 v2.6.0 2020-03-11 18:01:33 +08:00
18 changed files with 1840 additions and 171 deletions

View File

@@ -1,6 +1,8 @@
using NiL.JS.BaseLibrary;
using NiL.JS.Core;
using NiL.JS.Extensions;
using System.Security.Cryptography;
using System.Text;
namespace N_m3u8DL_CLI
{
@@ -285,6 +287,19 @@ function getKey(text, lid) {
return btoa(dec(text, lid));
}";
private static string MD5Encoding(string rawPass)
{
MD5 md5 = MD5.Create();
byte[] bs = Encoding.UTF8.GetBytes(rawPass);
byte[] hs = md5.ComputeHash(bs);
StringBuilder sb = new StringBuilder();
foreach (byte b in hs)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
public static string GetDecodeKey(string encodeKey, string lid)
{
var context = new Context();
@@ -293,5 +308,11 @@ function getKey(text, lid) {
string key = concatFunction.Call(new Arguments { encodeKey, lid }).ToString();
return key;
}
public static string GetSign(string lid)
{
var data = lid + "eDu_51Cto_siyuanTlw";
return MD5Encoding(data);
}
}
}

203
N_m3u8DL-CLI/DecodeImooc.cs Normal file
View File

@@ -0,0 +1,203 @@
using NiL.JS.BaseLibrary;
using NiL.JS.Core;
using NiL.JS.Extensions;
using System;
using Array = System.Array;
namespace N_m3u8DL_CLI
{
/*
* js代码来自https://www.imooc.com/static/moco/player/3.0.6.3/mocoplayer.js?v=202006122046
*
*/
class DecodeImooc
{
private static string JS = @"
function n(t, e) {
function r(t, e) {
var r = '';
if ('object' == typeof t)
for (var n = 0; n < t.length; n++)
r += String.fromCharCode(t[n]);
t = r || t;
for (var i, o, a = new Uint8Array(t.length), s = e.length, n = 0; n < t.length; n++)
o = n % s,
i = t[n],
i = i.toString().charCodeAt(0),
a[n] = i ^ e.charCodeAt(o);
return a
}
function n(t) {
var e = '';
if ('object' == typeof t)
for (var r = 0; r < t.length; r++)
e += String.fromCharCode(t[r]);
t = e || t;
var n = new Uint8Array(t.length);
for (r = 0; r < t.length; r++)
n[r] = t[r].toString().charCodeAt(0);
var i, o, r = 0;
for (r = 0; r < n.length; r++)
0 != (i = n[r] % 3) && r + i < n.length && (o = n[r + 1],
n[r + 1] = n[r + i],
n[r + i] = o,
r = r + i + 1);
return n
}
function i(t) {
var e = '';
if ('object' == typeof t)
for (var r = 0; r < t.length; r++)
e += String.fromCharCode(t[r]);
t = e || t;
var n = new Uint8Array(t.length);
for (r = 0; r < t.length; r++)
n[r] = t[r].toString().charCodeAt(0);
var r = 0
, i = 0
, o = 0
, a = 0;
for (r = 0; r < n.length; r++)
o = n[r] % 2,
o && r++,
a++;
var s = new Uint8Array(a);
for (r = 0; r < n.length; r++)
o = n[r] % 2,
s[i++] = o ? n[r++] : n[r];
return s
}
function o(t, e) {
var r = 0
, n = 0
, i = 0
, o = 0
, a = '';
if ('object' == typeof t)
for (var r = 0; r < t.length; r++)
a += String.fromCharCode(t[r]);
t = a || t;
var s = new Uint8Array(t.length);
for (r = 0; r < t.length; r++)
s[r] = t[r].toString().charCodeAt(0);
for (r = 0; r < t.length; r++)
if (0 != (o = s[r] % 5) && 1 != o && r + o < s.length && (i = s[r + 1],
n = r + 2,
s[r + 1] = s[r + o],
s[o + r] = i,
(r = r + o + 1) - 2 > n))
for (; n < r - 2; n++)
s[n] = s[n] ^ e.charCodeAt(n % e.length);
for (r = 0; r < t.length; r++)
s[r] = s[r] ^ e.charCodeAt(r % e.length);
return s
}
for (var a = {
data: {
info: t
}
}, s = {
q: r,
h: n,
m: i,
k: o
}, l = a.data.info, u = l.substring(l.length - 4).split(''), c = 0; c < u.length; c++)
u[c] = u[c].toString().charCodeAt(0) % 4;
u.reverse();
for (var d = [], c = 0; c < u.length; c++)
d.push(l.substring(u[c] + 1, u[c] + 2)),
l = l.substring(0, u[c] + 1) + l.substring(u[c] + 2);
a.data.encrypt_table = d,
a.data.key_table = [];
for (var c in a.data.encrypt_table)
'q' != a.data.encrypt_table[c] && 'k' != a.data.encrypt_table[c] || (a.data.key_table.push(l.substring(l.length - 12)),
l = l.substring(0, l.length - 12));
a.data.key_table.reverse(),
a.data.info = l;
var f = new Array(-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1);
a.data.info = function(t) {
var e, r, n, i, o, a, s;
for (a = t.length,
o = 0,
s = ''; o < a; ) {
do {
e = f[255 & t.charCodeAt(o++)]
} while (o < a && -1 == e);if (-1 == e)
break;
do {
r = f[255 & t.charCodeAt(o++)]
} while (o < a && -1 == r);if (-1 == r)
break;
s += String.fromCharCode(e << 2 | (48 & r) >> 4);
do {
if (61 == (n = 255 & t.charCodeAt(o++)))
return s;
n = f[n]
} while (o < a && -1 == n);if (-1 == n)
break;
s += String.fromCharCode((15 & r) << 4 | (60 & n) >> 2);
do {
if (61 == (i = 255 & t.charCodeAt(o++)))
return s;
i = f[i]
} while (o < a && -1 == i);if (-1 == i)
break;
s += String.fromCharCode((3 & n) << 6 | i)
}
return s
}(a.data.info);
for (var c in a.data.encrypt_table) {
var h = a.data.encrypt_table[c];
if ('q' == h || 'k' == h) {
var p = a.data.key_table.pop();
a.data.info = s[a.data.encrypt_table[c]](a.data.info, p)
} else
a.data.info = s[a.data.encrypt_table[c]](a.data.info)
}
if (e)
return a.data.info;
var g = '';
for (c = 0; c < a.data.info.length; c++)
g += String.fromCharCode(a.data.info[c]);
return g
}
function Uint8ArrayToString(fileData){
var dataString = '';
for (var i = 0; i < fileData.length; i++) {
dataString += Number(fileData[i]) + ',';
}
return dataString;
}
function decodeKey(resp){
var string = eval('('+resp+')');
//return btoa(String.fromCharCode.apply(null, new Uint8Array(n(string.data.info, 1))));
return Uint8ArrayToString(new Uint8Array(n(string.data.info, 1)));
}
function decodeM3u8(resp){
var string = eval('('+resp+')');
return n(string.data.info);
}
";
public static string DecodeM3u8(string resp)
{
var context = new Context();
context.Eval(JS);
var concatFunction = context.GetVariable("decodeM3u8").As<Function>();
string m3u8 = concatFunction.Call(new Arguments { resp }).ToString();
return m3u8;
}
public static string DecodeKey(string resp)
{
var context = new Context();
context.Eval(JS);
var concatFunction = context.GetVariable("decodeKey").As<Function>();
string key = concatFunction.Call(new Arguments { resp }).ToString();
byte[] v = Array.ConvertAll(key.Trim(',').Split(','), s => (byte)int.Parse(s));
string realKey = Convert.ToBase64String(v);
return realKey;
}
}
}

View File

@@ -19,7 +19,7 @@ namespace N_m3u8DL_CLI
fs.Read(inBuff, 0, inBuff.Length);
fs.Close();
Aes dcpt = Aes.Create("AES");
Aes dcpt = Aes.Create();
dcpt.BlockSize = 128;
dcpt.KeySize = 128;
dcpt.Key = keyByte;
@@ -36,7 +36,7 @@ namespace N_m3u8DL_CLI
{
byte[] inBuff = encryptedBuff;
Aes dcpt = Aes.Create("AES");
Aes dcpt = Aes.Create();
dcpt.BlockSize = 128;
dcpt.KeySize = 128;
dcpt.Key = keyByte;

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
//using DecryptYK;
using System;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
@@ -13,8 +12,6 @@ namespace N_m3u8DL_CLI
{
class Downloader
{
public static bool YouKuAES = false;
private int timeOut = 0;
private int retry = 5;
private int count = 0;
@@ -93,23 +90,11 @@ namespace N_m3u8DL_CLI
byte[] encryptedBuff = Global.HttpDownloadFileToBytes(fileUrl, Headers, TimeOut);
//byte[] encryptedBuff = Global.WebClientDownloadToBytes(fileUrl, Headers);
byte[] decryptBuff = null;
if (YouKuAES)
{
//decryptBuff = DecrypterYK.Decrypt(
decryptBuff = Decrypter.AES128Decrypt(
encryptedBuff,
Convert.FromBase64String(Key),
Decrypter.HexStringToBytes(Iv)
);
}
else
{
decryptBuff = Decrypter.AES128Decrypt(
encryptedBuff,
Convert.FromBase64String(Key),
Decrypter.HexStringToBytes(Iv)
);
}
decryptBuff = Decrypter.AES128Decrypt(
encryptedBuff,
Convert.FromBase64String(Key),
Decrypter.HexStringToBytes(Iv)
);
Global.AppendBytesToFileStreamAndDoNotClose(LiveStream, decryptBuff);
LOGGER.PrintLine("<" + SegIndex + " Complete>\r\n");
LOGGER.WriteLine("<" + SegIndex + " Complete>");
@@ -131,10 +116,10 @@ namespace N_m3u8DL_CLI
//LOGGER.STOPLOG = true; //停止记录日志
}
HLSLiveDownloader.REC_DUR += SegDur;
if (HLSLiveDownloader.REC_DUR_LIMIT != -1 && HLSLiveDownloader.REC_DUR >= HLSLiveDownloader.REC_DUR_LIMIT)
if (HLSLiveDownloader.REC_DUR_LIMIT != -1 && HLSLiveDownloader.REC_DUR >= HLSLiveDownloader.REC_DUR_LIMIT)
{
LOGGER.PrintLine("录制已到达限定长度", LOGGER.Warning);
LOGGER.WriteLine("录制已到达限定长度");
LOGGER.PrintLine(strings.recordLimitReached, LOGGER.Warning);
LOGGER.WriteLine(strings.recordLimitReached);
Environment.Exit(0); //正常退出
}
return;
@@ -207,16 +192,7 @@ namespace N_m3u8DL_CLI
try
{
byte[] decryptBuff = null;
if (YouKuAES)
{
//decryptBuff = DecrypterYK.Decrypt(
decryptBuff = Decrypter.AES128Decrypt(
File.ReadAllBytes(fi.FullName),
Convert.FromBase64String(Key),
Decrypter.HexStringToBytes(Iv)
);
}
else if(fileUrl.Contains(".51cto.com/")) //使用AES-128-ECB模式解密
if(fileUrl.Contains(".51cto.com/")) //使用AES-128-ECB模式解密
{
decryptBuff = Decrypter.AES128Decrypt(
fi.FullName,
@@ -244,13 +220,14 @@ namespace N_m3u8DL_CLI
{
LOGGER.PrintLine(ex.Message, LOGGER.Error);
LOGGER.WriteLineError(ex.Message);
Thread.Sleep(3000);
Environment.Exit(-1);
}
}
else
{
LOGGER.WriteLineError("Something was wrong!");
LOGGER.PrintLine("遇到了某些错误!", LOGGER.Error);
LOGGER.WriteLineError(strings.SomethingWasWrong);
LOGGER.PrintLine(strings.SomethingWasWrong, LOGGER.Error);
return;
}
return;

View File

@@ -84,7 +84,7 @@ namespace N_m3u8DL_CLI
}
Run("ffmpeg", command, Path.GetDirectoryName(files[0]));
LOGGER.WriteLine("Result in [ffreport.log]");
LOGGER.WriteLine(strings.ffmpegDone);
//Console.WriteLine(command);
}

View File

@@ -30,11 +30,12 @@ namespace N_m3u8DL_CLI
/*===============================================================================*/
static string nowVer = "2.5.7";
static string nowDate = "20200305";
static string nowVer = "2.7.1";
static string nowDate = "20200719";
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}...");
@@ -52,21 +53,21 @@ namespace N_m3u8DL_CLI
string latestVer = redirctUrl.Replace("https://github.com/nilaoda/N_m3u8DL-CLI/releases/tag/", "");
if (nowVer != latestVer && !latestVer.StartsWith("https"))
{
Console.Title = $"检测到更新,版本:{latestVer}! 正在尝试自动下载新版";
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";
if (File.Exists(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), $"N_m3u8DL-CLI_v{latestVer}.exe")))
{
Console.Title = $"检测到更新,版本:{latestVer}! 新版下载成功,请您自行替换";
Console.Title = string.Format(strings.newerVerisonDownloaded, latestVer);
return;
}
HttpDownloadFile(url, Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), $"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 = $"检测到更新,版本:{latestVer}! 新版下载成功,请您自行替换";
Console.Title = string.Format(strings.newerVerisonDownloaded, latestVer);
else
Console.Title = $"检测到更新,版本:{latestVer}! 新版下载失败,请您自行下载";
Console.Title = string.Format(strings.newerVerisonDownloadFailed, latestVer);
}
catch (Exception)
{
@@ -665,8 +666,8 @@ namespace N_m3u8DL_CLI
//调用ffmpeg获取视频信息
public static ArrayList GetVideoInfo(string file)
{
LOGGER.WriteLine("Reading Video Info");
LOGGER.PrintLine("读取文件信息...", LOGGER.Warning);
LOGGER.WriteLine(strings.readingFileInfo);
LOGGER.PrintLine(strings.readingFileInfo, LOGGER.Warning);
StringBuilder sb = new StringBuilder();
ArrayList info = new ArrayList();
string cmd = "-hide_banner -i \"" + file + "\"";
@@ -684,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窗口的输出信息

View File

@@ -99,5 +99,17 @@
<ItemGroup>
<Content Include="logo_3Iv_icon.ico" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="strings.en-US.resx" />
<EmbeddedResource Include="strings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>strings.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="strings.zh-TW.resx" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -89,7 +89,7 @@ namespace N_m3u8DL_CLI
//获取m3u8内容
if (!liveStream)
LOGGER.PrintLine("获取m3u8内容", LOGGER.Warning);
LOGGER.PrintLine(strings.downloadingM3u8, LOGGER.Warning);
if (M3u8Url.StartsWith("http"))
m3u8Content = Global.GetWebSource(M3u8Url, headers);
@@ -116,9 +116,24 @@ namespace N_m3u8DL_CLI
if (M3u8Url.Contains("tlivecloud-playback-cdn.ysp.cctv.cn") && M3u8Url.Contains("endtime="))
isEndlist = true;
if (M3u8Url.Contains("imooc.com/"))
{
m3u8Content = DecodeImooc.DecodeM3u8(m3u8Content);
}
//输出m3u8文件
File.WriteAllText(m3u8SavePath, m3u8Content);
//针对优酷#EXT-X-VERSION:7杜比视界片源修正
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Content.Contains("ott.cibntv.net") && m3u8Content.Contains("ccode="))
{
Regex ykmap = new Regex("#EXT-X-DISCONTINUITY\\s+#EXT-X-MAP:URI=\\\"(.*?)\\\",BYTERANGE=\\\"(.*?)\\\"");
foreach (Match m in ykmap.Matches(m3u8Content))
{
m3u8Content = m3u8Content.Replace(m.Value, $"#EXTINF:0.000000,\n#EXT-X-BYTERANGE:{m.Groups[2].Value}\n{m.Groups[1].Value}");
}
}
//如果BaseUrl为空则截取字符串充当
if (BaseUrl == "")
{
@@ -130,8 +145,8 @@ namespace N_m3u8DL_CLI
if (!liveStream)
{
LOGGER.WriteLine("Parsing Content");
LOGGER.PrintLine("解析m3u8内容");
LOGGER.WriteLine(strings.parsingM3u8);
LOGGER.PrintLine(strings.parsingM3u8);
}
if (!string.IsNullOrEmpty(keyBase64))
@@ -428,8 +443,8 @@ namespace N_m3u8DL_CLI
if (isM3u == false)
{
LOGGER.WriteLineError("NOT Contain #EXTM3U");
LOGGER.PrintLine("无法读取m3u8", LOGGER.Error);
LOGGER.WriteLineError(strings.invalidM3u8);
LOGGER.PrintLine(strings.invalidM3u8, LOGGER.Error);
return;
}
@@ -557,8 +572,8 @@ namespace N_m3u8DL_CLI
//输出JSON文件
if (!liveStream)
{
LOGGER.WriteLine("Writing Json: [meta.json]");
LOGGER.PrintLine("写出meta.json");
LOGGER.WriteLine(strings.wrtingMeta);
LOGGER.PrintLine(strings.wrtingMeta);
}
File.WriteAllText(jsonSavePath, jsonResult.ToString());
//检测是否为master list
@@ -579,7 +594,7 @@ namespace N_m3u8DL_CLI
{
if (m != "AES-128")
{
LOGGER.PrintLine($"不支持{m}加密方式,将不被处理,且强制开启二进制合并", LOGGER.Error);
LOGGER.PrintLine(string.Format(strings.notSupportMethod, m), LOGGER.Error);
DownloadManager.BinaryMerge = true;
return new string[] { $"{m}(NOTSUPPORTED)", "", "" };
}
@@ -593,8 +608,8 @@ namespace N_m3u8DL_CLI
}
else
{
LOGGER.PrintLine("获取m3u8 key...", LOGGER.Warning);
LOGGER.WriteLine("Opening " + key[1]);
LOGGER.PrintLine(strings.downloadingM3u8Key, LOGGER.Warning);
LOGGER.WriteLine(strings.downloadingM3u8Key + " " + key[1]);
if (key[1].StartsWith("http"))
{
string keyUrl = key[1];
@@ -682,6 +697,10 @@ namespace N_m3u8DL_CLI
}
key[1] = Convert.ToBase64String(Encoding.Default.GetBytes(decKey));
} //气球云
else if (key[1].Contains("imooc.com/"))
{
key[1] = DecodeImooc.DecodeKey(Global.GetWebSource(key[1], Headers));
}
else
{
if (keyUrl.Contains("https://keydeliver.linetv.tw/jurassicPark")) //linetv
@@ -699,8 +718,8 @@ namespace N_m3u8DL_CLI
string keyUrl = CombineURL(BaseUrl, key[1]);
if (keyUrl.Contains("edu.51cto.com")) //51cto
{
keyUrl = keyUrl + "&sign=" + Global.GetTimeStamp(false);
string lessonId = Global.GetQueryString("lesson_id", keyUrl);
keyUrl = keyUrl + "&sign=" + Decode51CtoKey.GetSign(lessonId);
var encodeKey = Encoding.UTF8.GetString(Global.HttpDownloadFileToBytes(keyUrl, Headers));
key[1] = Decode51CtoKey.GetDecodeKey(encodeKey, lessonId);
}
@@ -724,18 +743,18 @@ namespace N_m3u8DL_CLI
{
File.Copy(m3u8SavePath, Path.GetDirectoryName(m3u8SavePath) + "\\master.m3u8", true);
LOGGER.WriteLine("Master List Found");
LOGGER.PrintLine("识别到大师列表", LOGGER.Warning);
LOGGER.PrintLine(strings.masterListFound, LOGGER.Warning);
string t = "{" + "\"masterUri\":\"" + M3u8Url + "\","
+ "\"updateTime\":\"" + DateTime.Now.ToString("o") + "\","
+ "\"playLists:\":[" + string.Join(",", extLists.ToArray()) + "]" + "}";
//输出json文件
LOGGER.WriteLine("Writing Master List Json: [playLists.json]");
LOGGER.PrintLine("写出playLists.json");
LOGGER.WriteLine(strings.wrtingMasterMeta);
LOGGER.PrintLine(strings.wrtingMasterMeta);
File.WriteAllText(Path.GetDirectoryName(jsonSavePath) + "\\playLists.json", Global.ConvertJsonString(t));
LOGGER.WriteLine("Select Playlist: " + bestUrl);
LOGGER.PrintLine("已自动选择最高清晰度");
LOGGER.WriteLine("Start Re-Parsing");
LOGGER.PrintLine("重新解析m3u8...", LOGGER.Warning);
LOGGER.WriteLine(strings.selectPlaylist + ": " + bestUrl);
LOGGER.PrintLine(strings.selectPlaylist);
LOGGER.WriteLine(strings.startReParsing);
LOGGER.PrintLine(strings.startReParsing, LOGGER.Warning);
//重置Baseurl并重新解析
M3u8Url = bestUrl;
BaseUrl = "";

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
@@ -234,14 +235,11 @@ namespace N_m3u8DL_CLI.NetCore
/// 2020年2月23日
/// - 不支持的加密方式将标记为NOTSUPPORTED并强制启用二进制合并
/// - 启用二进制合并的情况下如果m3u8文件中存在map文件则合并为mp4格式
/// - 支持优酷视频解密
/// 2020年2月24日
/// - 直播流录制优化逻辑,避免忙等待
/// - 直播Waiting时不再输出Parser内容
/// - 直播录制的日志记录
/// - 增加新的选项--liveRecDur限制直播录制时长
/// 2020年2月25日
/// - 修复优酷解密过程错误写入冗余数据的bug
/// 2020年2月27日
/// - 细节bug修复
/// 2020年2月28日
@@ -263,6 +261,18 @@ namespace N_m3u8DL_CLI.NetCore
/// - 逻辑优化
/// 2020年3月5日
/// - 增加同名文件合并时共存策略
/// 2020年4月17日
/// - 优化异常捕获
/// - 细节优化
/// 2020年4月22日
/// - 51cto getsign
/// 2020年5月23日
/// - 优酷杜比视界下载逻辑优化
/// 2020年6月15日
/// - 支持IMOCO m3u8/key解密
/// 2020年7月18日
/// - 从当前路径和exe路径同时寻找ffmpeg
/// - 支持多语言本地化(简繁英)
/// </summary>
///
@@ -277,13 +287,13 @@ namespace N_m3u8DL_CLI.NetCore
switch (CtrlType)
{
case 0:
LOGGER.WriteLine("Exited: Ctrl + C"
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("Exited: Force"
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);
@@ -302,6 +312,19 @@ namespace N_m3u8DL_CLI.NetCore
{
SetConsoleCtrlHandler(cancelHandler, true);
ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
string loc = "zh-CN";
string currLoc = Thread.CurrentThread.CurrentUICulture.Name;
if (currLoc == "zh-TW" || currLoc == "zh-HK" || currLoc == "zh-MO")
{
loc = "zh-TW";
}
else if (loc != "zh-CN" && loc != "zh-SG")
{
loc = "en-US";
}
//设置语言
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(loc);
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(loc);
try
{
@@ -311,7 +334,7 @@ namespace N_m3u8DL_CLI.NetCore
string fileName = "";
//寻找ffmpeg.exe
if (!File.Exists("ffmpeg.exe"))
if (!File.Exists("ffmpeg.exe") && !File.Exists(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "ffmpeg.exe")))
{
try
{
@@ -329,14 +352,14 @@ namespace N_m3u8DL_CLI.NetCore
Console.BackgroundColor = ConsoleColor.Red; //设置背景色
Console.ForegroundColor = ConsoleColor.White; //设置前景色,即字体颜色
Console.WriteLine("在PATH和程序路径下找不到 ffmpeg.exe");
Console.WriteLine(strings.ffmpegLost);
Console.ResetColor(); //将控制台的前景色和背景色设为默认值
Console.WriteLine("请下载ffmpeg.exe并把他放到程序同目录.");
Console.WriteLine(strings.ffmpegTip);
Console.WriteLine();
Console.WriteLine("x86 https://ffmpeg.zeranoe.com/builds/win32/static/");
Console.WriteLine("x64 https://ffmpeg.zeranoe.com/builds/win64/static/");
Console.WriteLine();
Console.WriteLine("按任意键退出.");
Console.WriteLine(strings.pressAnyKeyExit);
Console.ReadKey();
Environment.Exit(-1);
}
@@ -384,33 +407,7 @@ namespace N_m3u8DL_CLI.NetCore
var arguments = CommandLineArgumentParser.Parse(args);
if (args.Length == 1 && args[0] == "--help")
{
Console.WriteLine(@"N_m3u8DL-CLI.exe <URL|File|JSON> [OPTIONS]
--workDir Directory 设定程序工作目录
--saveName Filename 设定存储文件名(不包括后缀)
--baseUrl BaseUrl 设定Baseurl
--headers headers 设定请求头,格式 key:value 使用|分割不同的key&value
--maxThreads Thread 设定程序的最大线程数(默认为32)
--minThreads Thread 设定程序的最小线程数(默认为16)
--retryCount Count 设定程序的重试次数(默认为15)
--timeOut Sec 设定程序网络请求的超时时间(单位为秒默认为10秒)
--muxSetJson File 使用外部json文件定义混流选项
--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)
--enableYouKuAes 使用优酷AES-128解密方案
--enableDelAfterDone 开启下载后删除临时文件夹的功能
--enableMuxFastStart 开启混流mp4的FastStart特性
--enableBinaryMerge 开启二进制合并分片
--enableParseOnly 开启仅解析模式(程序只进行到meta.json)
--enableAudioOnly 合并时仅封装音频轨道
--disableDateInfo 关闭混流中的日期写入
--noMerge 禁用自动合并
--noProxy 不自动使用系统代理
--disableIntegrityCheck 不检测分片数量是否完整");
Console.WriteLine(strings.helpInfo);
return;
}
if (arguments.Has("--enableDelAfterDone"))
@@ -445,10 +442,6 @@ namespace N_m3u8DL_CLI.NetCore
{
muxFastStart = true;
}
if (arguments.Has("--enableYouKuAes"))
{
Downloader.YouKuAES = true;
}
if (arguments.Has("--disableIntegrityCheck"))
{
DownloadManager.DisableIntegrityCheck = true;
@@ -630,8 +623,8 @@ namespace N_m3u8DL_CLI.NetCore
//开始解析
Console.CursorVisible = false;
LOGGER.PrintLine($"文件名称:{fileName}");
LOGGER.PrintLine($"存储路径:{Path.GetDirectoryName(Path.Combine(workDir, fileName))}");
LOGGER.PrintLine($"{strings.fileName}{fileName}");
LOGGER.PrintLine($"{strings.savePath}{Path.GetDirectoryName(Path.Combine(workDir, fileName))}");
Parser parser = new Parser();
parser.DownName = fileName;
@@ -645,8 +638,8 @@ namespace N_m3u8DL_CLI.NetCore
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.InitLog();
LOGGER.WriteLine("Start Parsing " + testurl);
LOGGER.PrintLine("开始解析地址...", LOGGER.Warning);
LOGGER.WriteLine(strings.startParsing + testurl);
LOGGER.PrintLine(strings.startParsing, LOGGER.Warning);
if (testurl.EndsWith(".json") && File.Exists(testurl)) //可直接跳过解析
{
if (!Directory.Exists(Path.Combine(workDir, fileName)))//若文件夹不存在则新建文件夹
@@ -661,7 +654,7 @@ namespace N_m3u8DL_CLI.NetCore
//仅解析模式
if (parseOnly)
{
LOGGER.PrintLine("解析m3u8成功, 程序退出");
LOGGER.PrintLine(strings.parseExit);
Environment.Exit(0);
}
@@ -671,15 +664,15 @@ namespace N_m3u8DL_CLI.NetCore
isVOD = Convert.ToBoolean(initJson["m3u8Info"]["vod"].ToString());
//传给Watcher总时长
Watcher.TotalDuration = initJson["m3u8Info"]["totalDuration"].Value<double>();
LOGGER.PrintLine($"文件时长:{Global.FormatTime((int)Watcher.TotalDuration)}");
LOGGER.PrintLine("总分片:" + initJson["m3u8Info"]["originalCount"].Value<int>()
+ ", 已选择分片:" + initJson["m3u8Info"]["count"].Value<int>());
LOGGER.PrintLine($"{strings.fileDuration}{Global.FormatTime((int)Watcher.TotalDuration)}");
LOGGER.PrintLine(strings.segCount + initJson["m3u8Info"]["originalCount"].Value<int>()
+ $", {strings.selectedCount}" + initJson["m3u8Info"]["count"].Value<int>());
}
else
{
DirectoryInfo directoryInfo = new DirectoryInfo(Path.Combine(workDir, fileName));
directoryInfo.Delete(true);
LOGGER.PrintLine("地址无效", LOGGER.Error);
LOGGER.PrintLine(strings.InvalidUri, LOGGER.Error);
LOGGER.CursorIndex = 5;
inputRetryCount--;
goto input;
@@ -716,9 +709,8 @@ namespace N_m3u8DL_CLI.NetCore
//直播
if (isVOD == false)
{
LOGGER.WriteLine("Living Stream Found");
LOGGER.WriteLine("Start Recording");
LOGGER.PrintLine("识别为直播流, 开始录制");
LOGGER.WriteLine(strings.liveStreamFoundAndRecoding);
LOGGER.PrintLine(strings.liveStreamFoundAndRecoding);
//LOGGER.STOPLOG = true; //停止记录日志
//开辟文件流,且不关闭。(便于播放器不断读取文件)
string LivePath = Path.Combine(Directory.GetParent(parser.DownDir).FullName
@@ -737,9 +729,10 @@ namespace N_m3u8DL_CLI.NetCore
//监听测试
/*httplitsen:
HTTPListener.StartListening();*/
LOGGER.WriteLineError("Download Failed");
LOGGER.PrintLine("下载失败, 程序退出", LOGGER.Error);
LOGGER.WriteLineError(strings.downloadFailed);
LOGGER.PrintLine(strings.downloadFailed, LOGGER.Error);
Console.CursorVisible = true;
Thread.Sleep(3000);
Environment.Exit(-1);
//Console.Write("按任意键继续..."); Console.ReadKey(); return;
}

View File

@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("nilaoda")]
[assembly: AssemblyProduct("N_m3u8DL-CLI")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -33,4 +33,4 @@ using System.Runtime.InteropServices;
// 方法是按如下所示使用“*”: :
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("2.7.0.0")]

541
N_m3u8DL-CLI/strings.Designer.cs generated Normal file

File diff suppressed because it is too large Load Diff

0
N_m3u8DL-CLI/strings.en-US.Designer.cs generated Normal file
View File

File diff suppressed because it is too large Load Diff

301
N_m3u8DL-CLI/strings.resx Normal file

File diff suppressed because it is too large Load Diff

0
N_m3u8DL-CLI/strings.zh-TW.Designer.cs generated Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@
一个**简单易用的**m3u8下载器下载地址https://github.com/nilaoda/N_m3u8DL-CLI/releases
支持下载m3u8链接或文件为`mp4`或`ts`格式,并提供丰富的命令行选项。
* **不支持**优酷视频解密
* 支持`AES-128-CBC`加密自动解密
* 支持多线程下载
* 支持下载限速
@@ -39,7 +40,6 @@
* 支持下载外挂字幕轨道、音频轨道
* 支持仅合并为音频
* 自动使用系统代理(可禁止)
* 针对国内视频网站`m3u8`进行了优化
* 提供SimpleG简易的`GUI`生成常用参数
@@ -65,7 +65,6 @@ N_m3u8DL-CLI.exe <URL|JSON|FILE> [OPTIONS]
--liveRecDur HH:MM:SS 直播录制时,达到此长度自动退出软件
--stopSpeed Number 当速度低于此值时,重试(单位为KB/s)
--maxSpeed Number 设置下载速度上限(单位为KB/s)
--enableYouKuAes 使用优酷AES-128解密方案
--enableDelAfterDone 开启下载后删除临时文件夹的功能
--enableMuxFastStart 开启混流mp4的FastStart特性
--enableBinaryMerge 开启二进制合并分片